amiga-bootcamp/09_intuition/windows.md
Ilia Sharin 9f5d9de1ed 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
2026-05-12 22:04:16 -04:00

778 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

[← Home](../README.md) · [Intuition](README.md)
# Windows
## What Is a Window?
A window is Intuition's fundamental unit of user interaction. Every GUI element — gadgets, menus, text, graphics — lives inside a window. A window belongs to exactly one [screen](screens.md), receives user input through [IDCMP](idcmp.md), and is managed by the system's layered display architecture.
Unlike modern windowing systems where windows are heavyweight objects backed by compositor surfaces, Amiga windows are **lightweight wrappers around Layers** — the graphics.library's clipping and damage-tracking mechanism. This is why a 7 MHz 68000 can manage dozens of overlapping windows smoothly.
---
## Window Anatomy
```mermaid
graph TB
subgraph "Window Structure"
TITLE["Title Bar (drag area)"]
CLOSE["×"]
DEPTH["⊡"]
ZOOM["□"]
BORDER_L["Left Border"]
CONTENT["Content Area<br/>(your drawing region)"]
BORDER_R["Right Border"]
SIZE["Size Gadget ◢"]
BOTTOM["Bottom Border"]
end
style TITLE fill:#e8f4fd,stroke:#2196f3,color:#333
style CONTENT fill:#fff,stroke:#bdbdbd,color:#333
style CLOSE fill:#ffebee,stroke:#f44336,color:#333
style DEPTH fill:#e8f5e9,stroke:#4caf50,color:#333
style SIZE fill:#fff3e0,stroke:#ff9800,color:#333
```
| Element | System Gadget | Purpose |
|---|---|---|
| **Close** | `WFLG_CLOSEGADGET` | Sends `IDCMP_CLOSEWINDOW` |
| **Depth** | `WFLG_DEPTHGADGET` | Moves window front/back |
| **Zoom** | `WFLG_HASZOOM` (OS 2.0+) | Toggles between two sizes |
| **Drag Bar** | `WFLG_DRAGBAR` | User drags the window |
| **Size** | `WFLG_SIZEGADGET` | Resize handle (bottom-right) |
| **Borders** | Automatic | Visual frame; width depends on screen resolution |
---
## Opening a Window
### Modern Pattern (OS 2.0+ TagList)
```c
struct Window *win = OpenWindowTags(NULL,
WA_Left, 100,
WA_Top, 50,
WA_InnerWidth, 400, /* Content area width */
WA_InnerHeight, 300, /* Content area height */
WA_Title, "My Window",
WA_ScreenTitle, "Status bar text when this window is active",
WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_GADGETUP |
IDCMP_RAWKEY | IDCMP_NEWSIZE,
WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR |
WFLG_DEPTHGADGET | WFLG_SIZEGADGET |
WFLG_ACTIVATE | WFLG_SMART_REFRESH,
WA_MinWidth, 200,
WA_MinHeight, 100,
WA_MaxWidth, -1, /* No maximum (screen width) */
WA_MaxHeight, -1, /* No maximum (screen height) */
WA_PubScreen, NULL, /* Default public screen (Workbench) */
TAG_DONE);
if (!win) { /* Handle failure — screen may be locked or out of memory */ }
```
### Legacy Pattern (struct NewWindow)
```c
/* Pre-OS 2.0 — avoid in new code */
struct NewWindow nw = {
100, 50, 400, 300, /* Left, Top, Width, Height */
0, 1, /* DetailPen, BlockPen */
IDCMP_CLOSEWINDOW, /* IDCMPFlags */
WFLG_CLOSEGADGET | WFLG_DRAGBAR | WFLG_ACTIVATE,
NULL, NULL, /* FirstGadget, CheckMark */
"My Window", /* Title */
NULL, NULL, /* Screen, BitMap */
200, 100, -1, -1, /* Min/Max Width/Height */
WBENCHSCREEN /* Type */
};
struct Window *win = OpenWindow(&nw);
```
---
## Common WA_ Tags
### Position and Size
| Tag | Type | Description |
|---|---|---|
| `WA_Left`, `WA_Top` | `WORD` | Window position (outer edge) |
| `WA_Width`, `WA_Height` | `WORD` | Total window size (including borders) |
| `WA_InnerWidth`, `WA_InnerHeight` | `WORD` | Content area size (excluding borders) — preferred |
| `WA_MinWidth`, `WA_MinHeight` | `WORD` | Minimum resize dimensions |
| `WA_MaxWidth`, `WA_MaxHeight` | `WORD` | Maximum resize dimensions; `-1` = screen size |
| `WA_Zoom` | `WORD[4]` | Alternate position/size for zoom gadget |
### Appearance
| Tag | Type | Description |
|---|---|---|
| `WA_Title` | `STRPTR` | Title bar text |
| `WA_ScreenTitle` | `STRPTR` | Screen title shown when this window is active |
| `WA_Borderless` | `BOOL` | No borders or system gadgets |
| `WA_GimmeZeroZero` | `BOOL` | Inner content area starts at (0,0) — adds extra layer |
| `WA_NoCareRefresh` | `BOOL` | Ignore REFRESHWINDOW — Intuition handles it (may cause glitches) |
### Screen Placement
| Tag | Type | Description |
|---|---|---|
| `WA_PubScreen` | `struct Screen *` | Open on a public screen (`NULL` = default/Workbench) |
| `WA_CustomScreen` | `struct Screen *` | Open on a custom (private) screen |
| `WA_PubScreenName` | `STRPTR` | Open on named public screen; falls back to default |
| `WA_PubScreenFallBack` | `BOOL` | If named screen unavailable, use default |
### Behavior
| Tag | Type | Description |
|---|---|---|
| `WA_Activate` | `BOOL` | Window becomes active immediately on open |
| `WA_Backdrop` | `BOOL` | Always stays behind all normal windows |
| `WA_SmartRefresh` | `BOOL` | Intuition saves obscured content automatically |
| `WA_SimpleRefresh` | `BOOL` | Application must redraw on expose (less memory) |
| `WA_SuperBitMap` | `struct BitMap *` | Application provides full off-screen buffer |
| `WA_AutoAdjust` | `BOOL` | Auto-adjust position/size to fit screen |
| `WA_NewLookMenus` | `BOOL` | OS 3.0+ 3D menu appearance |
---
## Refresh Modes
How Intuition handles window content when areas are obscured and then revealed:
| Mode | Flag | Memory Cost | App Responsibility | Best For |
|---|---|---|---|---|
| **Simple Refresh** | `WFLG_SIMPLE_REFRESH` | Lowest | Must handle `IDCMP_REFRESHWINDOW` — redraw exposed areas | Text editors, games (redraw every frame anyway) |
| **Smart Refresh** | `WFLG_SMART_REFRESH` | Medium | Intuition saves/restores obscured areas automatically | Most applications — recommended default |
| **SuperBitMap** | `WFLG_SUPER_BITMAP` | Highest | Application provides full-size `BitMap`; Intuition copies from it | CAD, paint programs with large canvases |
### Simple Refresh Handler
```c
case IDCMP_REFRESHWINDOW:
BeginRefresh(win);
/* Redraw only the damaged region — clipping is set automatically */
RedrawWindowContents(win);
EndRefresh(win, TRUE); /* TRUE = damage fully repaired */
break;
```
### Smart Refresh Memory Cost
Smart Refresh allocates off-screen buffers proportional to the **obscured area**. If a 640×400 window is 50% covered by other windows, Smart Refresh consumes ~128 KB (at 4 bitplanes). On a 512 KB A500, this is significant.
---
## Window Types
### Standard Window
The default — has borders, title bar, and system gadgets:
```c
WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR |
WFLG_DEPTHGADGET | WFLG_SIZEGADGET | WFLG_ACTIVATE,
```
### Backdrop Window
Stays behind all other windows. Commonly used for:
- Desktop manager backgrounds
- Full-screen applications that want to coexist with Workbench windows
```c
WA_Flags, WFLG_BACKDROP | WFLG_BORDERLESS | WFLG_ACTIVATE,
WA_IDCMP, IDCMP_MOUSEBUTTONS | IDCMP_RAWKEY,
```
### Borderless Window
No system gadgets or decorations — the application draws everything:
```c
WA_Borderless, TRUE,
WA_Flags, WFLG_ACTIVATE | WFLG_RMBTRAP, /* Trap right-click too */
```
### GimmeZeroZero Window
Creates a separate layer for window borders. The content area's `RastPort` starts at (0,0) regardless of border width:
```c
WA_Flags, WFLG_GIMMEZEROZERO | WFLG_CLOSEGADGET | WFLG_DRAGBAR,
```
**Trade-off**: Uses an extra layer (memory + blitter operations) but simplifies rendering code since you don't need to account for border offsets.
### Sizing Constraints
```c
WA_MinWidth, 200,
WA_MinHeight, 100,
WA_MaxWidth, -1, /* Screen width */
WA_MaxHeight, -1, /* Screen height */
WA_SizeGadget, TRUE,
WA_SizeBRight, TRUE, /* Size gadget on right border */
WA_SizeBBottom, TRUE, /* Size gadget on bottom border */
```
---
## Window Coordinates
### Border Offsets
The content area does NOT start at (0,0) in a normal window. You must account for borders:
```c
WORD contentLeft = win->BorderLeft;
WORD contentTop = win->BorderTop;
WORD contentWidth = win->Width - win->BorderLeft - win->BorderRight;
WORD contentHeight = win->Height - win->BorderTop - win->BorderBottom;
/* Draw at content origin */
Move(win->RPort, contentLeft, contentTop + baseline);
Text(win->RPort, "Hello", 5);
```
With `WFLG_GIMMEZEROZERO`, the window provides a separate `GZZWidth`/`GZZHeight` and a content `RastPort` where (0,0) is the content origin.
---
## Closing a Window
### Simple Case
```c
CloseWindow(win);
```
### Safe Shutdown (Drain Messages First)
```c
/* Drain any pending IDCMP messages */
struct IntuiMessage *msg;
while ((msg = (struct IntuiMessage *)GetMsg(win->UserPort)))
ReplyMsg((struct Message *)msg);
CloseWindow(win);
```
### With Shared Port
See [IDCMP — Multi-Window Shared Port](idcmp.md#multi-window-shared-port) for the full `Forbid()`/strip/detach protocol.
---
## Modifying a Window
### Move and Resize
```c
MoveWindow(win, deltaX, deltaY); /* Relative move */
SizeWindow(win, deltaWidth, deltaHeight); /* Relative resize */
ChangeWindowBox(win, left, top, w, h); /* Absolute reposition + resize */
WindowToFront(win);
WindowToBack(win);
ActivateWindow(win);
```
### Change Title
```c
SetWindowTitles(win, "New Title", "New Screen Title");
/* Pass (UBYTE *)-1 to leave unchanged */
SetWindowTitles(win, "New Title", (UBYTE *)-1);
```
### Busy Pointer
```c
/* OS 3.0+ — show busy pointer */
SetWindowPointer(win, WA_BusyPointer, TRUE, TAG_DONE);
/* Restore normal pointer */
SetWindowPointer(win, WA_Pointer, NULL, TAG_DONE);
```
---
## struct Window — Key Fields
| Field | Type | Description |
|---|---|---|
| `LeftEdge`, `TopEdge` | `WORD` | Position on screen |
| `Width`, `Height` | `WORD` | Total size (including borders) |
| `BorderLeft/Right/Top/Bottom` | `BYTE` | Border thickness (pixels) |
| `RPort` | `struct RastPort *` | Drawing context for this window |
| `UserPort` | `struct MsgPort *` | IDCMP message port |
| `IDCMPFlags` | `ULONG` | Currently active IDCMP flags |
| `Flags` | `ULONG` | Window flags (`WFLG_*`) |
| `Title` | `UBYTE *` | Title string |
| `WScreen` | `struct Screen *` | Screen this window belongs to |
| `FirstGadget` | `struct Gadget *` | Head of gadget list |
| `MouseX`, `MouseY` | `WORD` | Current mouse position (relative to window) |
| `GZZWidth`, `GZZHeight` | `WORD` | Inner dimensions (GimmeZeroZero only) |
| `MinWidth/Height`, `MaxWidth/Height` | `WORD` | Size constraints |
---
## Pitfalls
### 1. Using WA_Width Instead of WA_InnerWidth
`WA_Width` includes borders. On different screen resolutions, border width varies. Always use `WA_InnerWidth`/`WA_InnerHeight` for predictable content area sizing.
### 2. Drawing Outside Content Area
Drawing at (0,0) in a non-GZZ window overwrites the border:
```c
/* WRONG — draws over title bar */
Move(win->RPort, 0, 10);
/* CORRECT — offset by borders */
Move(win->RPort, win->BorderLeft, win->BorderTop + 10);
```
### 3. Forgetting MinWidth/MinHeight
Without size constraints, users can resize the window to 1×1 pixel, causing division-by-zero in layout code.
### 4. Not Handling NEWSIZE
If your window is resizable, you must redraw content when `IDCMP_NEWSIZE` arrives — the old content is clipped or garbage.
### 5. Opening on a Closed Screen
If you cache a screen pointer and it becomes invalid, `OpenWindowTags()` will crash. Always `LockPubScreen()` → open window → `UnlockPubScreen()`.
---
## Best Practices
1. **Use `WA_InnerWidth`/`WA_InnerHeight`** for predictable content dimensions
2. **Use Smart Refresh** unless you have a specific reason not to
3. **Always set `WA_MinWidth`/`WA_MinHeight`** for resizable windows
4. **Handle `IDCMP_NEWSIZE`** — recalculate and redraw layout
5. **Use `WA_PubScreen, NULL`** to open on the default public screen
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 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