Architecture

WakaPAC brings Win32's application architecture to the browser. Win32's window model is a natural implementation of the Presentation-Abstraction-Control pattern, and WakaPAC adopts it wholesale — the same structure, the same message-passing model, the same mental model.

explanation

Win32 and PAC

The Presentation-Abstraction-Control (PAC) pattern separates an application into three layers: Presentation (what the user sees), Abstraction (application state and logic), and Control (the layer that keeps them in sync). In Win32, every window works this way:

  • The window is the Presentation — it owns its visual area and renders content to screen.
  • The application state is the Abstraction — data, computed values, business logic.
  • The WndProc receives all messages for the window and decides how each event affects state and UI.

The critical property of this model is that Presentation and Abstraction never communicate directly. Everything flows through the Control layer. State changes update the window; user input arrives as messages to WndProc. The application is predictable because there is only one path for anything to happen.

WakaPAC maps this model directly to the browser. A WakaPAC component is the browser equivalent of a Win32 window. Its HTML template is the Presentation. Its data and methods are the Abstraction. WakaPAC itself is the Control layer — and msgProc is the equivalent of WndProc, the function you implement to handle messages within it.

Components as Windows

State declared in a component is wrapped in a reactive proxy. HTML bindings that reference state properties register as dependents when first evaluated. When a property changes, only the bindings that depend on it are re-evaluated, and the DOM is updated only if the value actually changed. This is the Control layer at work — mediating between Abstraction and Presentation without either layer knowing about the other.

Computed properties follow the same model. They are derived values defined as functions, tracked automatically for dependencies, and invalidated when those dependencies change. From the template's perspective they are indistinguishable from plain data.

msgProc and the Message Pipeline

In Win32, WndProc is a single function that receives every message for a window — mouse events, keyboard events, paint requests, timer ticks — identified by a numeric constant. You handle what you need and ignore the rest. WakaPAC's msgProc is exactly this.

wakaPAC('#app', {
    msgProc(event) {
        switch (event.message) {
            case wakaPAC.MSG_LBUTTONDOWN:
                const x = wakaPAC.LOWORD(event.lParam);
                const y = wakaPAC.HIWORD(event.lParam);
                this.handleClick(x, y);
                break;

            case wakaPAC.MSG_KEYDOWN:
                if (event.wParam === wakaPAC.VK_ESCAPE) {
                    this.close();
                }

                break;
        }
    }
});

Internally, WakaPAC registers DOM event listeners and browser observers on your behalf. When an event fires, the toolkit translates it into Win32 message format — packing mouse coordinates into lParam, modifier flags into wParam — and dispatches to msgProc. The message constants, parameter conventions, and virtual key codes are identical to their Win32 counterparts. The browser is the runtime; Win32 is the vocabulary.

msgProc is optional. Components that only need reactive data binding do not need to implement it.

sendMessage and postMessage

Win32 windows communicate by sending messages to each other using SendMessage() and PostMessage(). WakaPAC components do the same. sendMessage() delivers a message synchronously and waits for the receiving component's msgProc to return. postMessage() queues the message and returns immediately.

// Sender
wakaPAC('#toolbar', {
    notifyContent() {
        wakaPAC.sendMessage('content', wakaPAC.MSG_USER + 1, 0, 0, { action: 'refresh' });
    }
});

// Receiver
wakaPAC('#content', {
    msgProc(event) {
        switch (event.message) {
            case wakaPAC.MSG_USER + 1:
                this.refresh(event.detail.action);
                break;
        }
    }
});
Custom message types are defined by offsetting from MSG_USER (0x1000), exactly as Win32 applications use WM_USER.

Win32 Concepts Applied to the DOM

The following maps each Win32 concept to the browser primitive WakaPAC uses underneath.

Message Loop → Event Listeners

Each WakaPAC message is backed by a DOM event listener on document or window, or by a browser observer such as ResizeObserver or MutationObserver. The toolkit translates the DOM event into Win32 message format before dispatching to msgProc.

Virtual Key (VK) Codes → KeyboardEvent.code

WakaPAC maps KeyboardEvent.code — the physical key identifier, e.g. 'KeyA' — to VK_* constants via a lookup table. The deprecated KeyboardEvent.keyCode (and .which) is used only as a fallback when no .code mapping exists.

SetCapture / ReleaseCapture → Mouse Capture Redirection

wakaPAC.setCapture() redirects all subsequent mouse events to a single target container regardless of hit-testing — the same single-capture-owner model Win32 uses, where setting capture on a new element automatically releases it from whichever element held it before. wakaPAC.releaseCapture() restores normal event routing and dispatches MSG_CAPTURECHANGED to the container that lost capture.

WM_SIZE → ResizeObserver

Each component's container is observed via ResizeObserver. A size change dispatches MSG_SIZE with the new dimensions packed into lParam and a wParam of SIZE_HIDDEN, SIZE_FULLSCREEN, or SIZE_RESTORED — mirroring Win32's SIZE_MINIMIZED / SIZE_MAXIMIZED / SIZE_RESTORED.

WM_SETFOCUS / WM_KILLFOCUS → focusin / focusout

Focus entering or leaving a component's container dispatches MSG_SETFOCUS / MSG_KILLFOCUS, exactly as Win32 notifies a window when it gains or loses keyboard focus.

WM_MOUSEWHEEL → wheel

Wheel events dispatch MSG_MOUSEWHEEL with the scroll delta packed into wParam, the same parameter Win32 uses to carry wheel rotation.

WM_COPY / WM_PASTE → copy / paste

Clipboard copy and paste events dispatch MSG_COPY / MSG_PASTE to the component under the cursor or focus.

WM_CONTEXTMENU → contextmenu

Right-click (or equivalent) dispatches MSG_CONTEXTMENU before the browser's native context menu appears. Handling it and calling event.preventDefault() suppresses the native menu, the same pattern Win32 applications use to replace it with a custom one.

Drag-and-Drop → HTML5 Drag Events

The closest Win32 analog is WM_DROPFILES / OLE drag-and-drop. WakaPAC dispatches MSG_DRAGENTER, MSG_DRAGOVER, MSG_DRAGLEAVE, and MSG_DROP from the corresponding native dragenter/dragover/dragleave/drop events, with the dropped payload available on the message detail.

SetTimer / MSG_TIMER → shared requestAnimationFrame loop

setTimer() registers with a single, shared requestAnimationFrame-driven loop used by every component's timers — not setInterval. This is a deliberate choice: some browsers throttle setInterval/setTimeout in background tabs, which caused irregular MSG_TIMER delivery. The loop checks each registered timer's elapsed time every frame and dispatches MSG_TIMER when its interval has passed; killTimer() deregisters it, and the loop stops itself once no timers remain.

MSG_PAINT Scheduling → requestAnimationFrame

When a WakaPAC component is bound to a <canvas> element, calling invalidateRect() schedules a requestAnimationFrame callback — mirroring how Win32 coalesces WM_PAINT messages. Multiple invalidations before the frame fires are merged into a dirty region; a set of rectangles MSG_PAINT clips against.

Device Context (DC) → CanvasRenderingContext2D

When a WakaPAC component is bound to a <canvas> element, getDC() returns its CanvasRenderingContext2D wrapped with clip state management. The abstraction is in how the handle is acquired and released, not in the drawing calls themselves.

Compatible DC → Offscreen CanvasRenderingContext2D

When a WakaPAC component is bound to a <canvas> element, createCompatibleDC() creates a CanvasRenderingContext2D backed by a hidden <canvas> sized to match the target container. bitBlt() copies between them using drawImage() internally.

Bitmap Handle → Opaque Offscreen Canvas Wrapper

Bitmap handles returned by loadBitmap() wrap an offscreen canvas with the decoded image. Pass them to bitBlt(), getBitmapSize(), saveBitmap(), or deleteBitmap() exactly as you would an HBITMAP.

Display Scaling → devicePixelRatio

WakaPAC handles high-DPI internally. Canvas backing stores are sized in physical pixels, and getCanvasSize() always returns physical pixel dimensions. You always work in physical pixel coordinates when drawing.

WM_DPICHANGED → devicePixelRatio Change Notification

Separately from the static sizing above, WakaPAC watches for devicePixelRatio changing at runtime — for example when a window is dragged to a monitor with a different DPI — via matchMedia, and dispatches MSG_DPR_CHANGE when it does, the same event Win32's WM_DPICHANGED exists to deliver.