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.keyCode

WakaPAC maps KeyboardEvent.keyCode values directly to VK_* constants. The mapping is one-to-one — the numeric values are identical.

SetTimer / MSG_TIMER → setInterval

setTimer() wraps setInterval internally; killTimer() clears it. The interval fires on the browser's task queue and WakaPAC translates it into a message before dispatch.

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.