cocoa

package
v0.45.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 18, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

Documentation

Overview

Package cocoa is the pure-Go (CGO-free, via purego) macOS AppKit windowing backend for the go-widgets toolkit. It opens a real NSWindow with a content NSView, blits the toolkit's RGBA framebuffer into it through an NSBitmapImageRep in -drawRect:, and routes native NSEvent mouse/scroll/key input into toolkit.Event, so a go-widgets widget tree runs on a macOS desktop exactly as it does on X11, Wayland or in the browser/wasm host.

The Objective-C runtime is reached through the fleet's shared bridge github.com/go-macos/objc (Send/RegisterClass/GetClass/NSString/GoString/…), itself layered over github.com/ebitengine/purego — no cgo — so the whole module builds and links with CGO_ENABLED=0.

This file is the SOVEREIGN, OS-INDEPENDENT half: the NSEvent→toolkit.Event mapping (key decode, modifier decode, button/wheel mapping), the flipped-view coordinate maths and the damage-rect→dirty-rect conversion, all expressed over plain Go values with a single toolkit dependency (the event model). It carries NO objc/purego/unsafe dependency, so it builds — and is unit-tested to 100% — on every GOOS, mirroring internal/wasmbox's protocol.go. The thin AppKit glue that actually creates the NSWindow, presents the bitmap and pumps the run loop lives in cocoa_darwin.go (//go:build darwin) and drives everything here.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func A11yFrame added in v0.10.0

func A11yFrame(n toolkit.A11yNode, scale float64) (x, y, w, h float64)

A11yFrame converts a node's rectangle from the framebuffer's render pixels to the content view's POINTS.

Only a scale division is needed, with no Y flip: the content view is flipped (isFlipped → top-left origin) precisely so it matches the framebuffer, which is also what lets DirtyRect stay this simple. The trip on to screen coordinates — where NSAccessibilityElement wants its frame, bottom-left origin and all — is left to the caller, because it needs live Cocoa state (the view's own convertRect: chain) and so cannot be decided here.

func A11yNodes added in v0.10.0

func A11yNodes(root toolkit.Widget) []toolkit.A11yNode

A11yNodes is the tree to publish for a widget root: every meaningful element, in visual order, with the ones no reader could use already removed.

func A11ySkip added in v0.10.0

func A11ySkip(n toolkit.A11yNode) bool

A11ySkip reports whether a node should be left out of the published tree.

An element with no name says nothing a reader could announce, and one with no area cannot be pointed at; either lands in VoiceOver's rotor as an unlabelled stop the user has to skip past for nothing. The toolkit deliberately does not make this decision — it reports faithfully — because it is the platform that knows what its own screen reader does with an empty element.

func AXRole added in v0.10.0

func AXRole(r toolkit.Role) string

AXRole maps a toolkit role to the NSAccessibility role a screen reader announces. Anything unrecognised becomes AXGroup — AppKit's "something whose meaning is its contents", the honest answer for a role this table does not know, and never a wrong announcement.

func DecodeKey

func DecodeKey(keyCode uint16, chars string) (name string, r rune)

DecodeKey maps an NSEvent keyDown/keyUp to either a symbolic key NAME (DOM-style: "Enter", "ArrowLeft", …, exactly the names the toolkit widgets match and the wasmbox backend emits) or a printable rune. keyCode is checked first so Return/Escape/Delete/arrows never leak through as control or private-use runes. A result of ("", 0) means the key carries nothing to deliver (an unmapped or non-printable key).

chars is the event's -charactersIgnoringModifiers value; only a single genuine printable rune (>= 0x20, not DEL, and outside the NSFunctionKey private-use range U+F700..U+F8FF, where AppKit reports arrows/F-keys) is accepted as text — the identical filter the reader precedent applies.

func DefaultContentSize added in v0.6.1

func DefaultContentSize(visW, visH float64) (w, h int)

DefaultContentSize picks a readable default window content size, in LOGICAL points, from the main screen's visible frame (visW×visH, also in points). It takes defaultScreenFraction of the visible area and clamps each axis to the [min,max] readability band, then to the visible extent so the window never exceeds the usable screen. When the screen size is unknown (visW or visH ≤ 0) it returns the fixed fallback. The result is always ≥ 1×1 and ≤ the visible frame, so a defaulted window is legible without manual sizing.

func DirtyRect

func DirtyRect(r toolkit.Rect, scale float64) (x, y, w, h float64)

DirtyRect converts a damage rectangle in DEVICE pixels (top-left origin, the space RenderDamaged reports and the framebuffer uses) to a rectangle in the flipped content view's POINT coordinates, ready for -setNeedsDisplayInRect:. Because the content view is flipped (isFlipped → top-left origin, matching the buffer), only a scale division is needed — no Y flip. The returned rectangle is clamped to be non-negative and is expanded to whole points (floor origin, ceil far edge) so a sub-point damage rect never leaves a seam.

func MapKey

func MapKey(keyCode uint16, chars string, m Mods, press bool) []toolkit.Event

MapKey turns a decoded keyDown/keyUp into the toolkit event(s) it produces, mirroring the X11 and wasmbox backends EXACTLY:

  • a named key yields a single EventKeyDown (press) / EventKeyUp (release) carrying the name in Code;
  • a printable key yields EventKeyDown+EventChar on press (Char being the committed rune) and a single EventKeyUp on release — the same press/char split the X11 backend performs;
  • a key that decodes to nothing (unmapped / pure modifier) delivers nothing.

The result is nil when the key maps to no toolkit event.

func MapMouseDown

func MapMouseDown(x, y int, m Mods) toolkit.Event

MapMouseDown turns a left/other mouse-button press at the given view-local pixel into an EventClick, mirroring the X11 ButtonPress (buttons 1–3 → click) mapping. macOS delivers separate selectors per button; the backend routes all of them here.

func MapMouseMove

func MapMouseMove(x, y int, buttonHeld bool, m Mods) toolkit.Event

MapMouseMove turns a pointer move into a drag (a button held) or a plain hover move, per buttonHeld — the same drag-vs-move split the X11/Wayland backends derive from the event's button-state mask (AppKit instead delivers -mouseMoved: vs -mouseDragged:, which the glue collapses into buttonHeld).

func MapMouseUp

func MapMouseUp(x, y int, m Mods) toolkit.Event

MapMouseUp turns a mouse-button release into an EventMouseUp.

func MapScroll

func MapScroll(x, y int, deltaY float64, m Mods) toolkit.Event

MapScroll turns an AppKit -scrollingDeltaY into an EventScroll whose Delta is normalised to the toolkit's ±1 row step. AppKit's scrollingDeltaY is POSITIVE when the content is pushed up (a natural upward swipe); the toolkit's Delta is POSITIVE to scroll down/forward, so the sign is inverted — matching the browser/wheel convention the X11 and wasmbox backends use. A zero delta yields a Delta-0 EventScroll (harmless; scrollable widgets clamp it), so the mapping is total.

func MapSecondaryClick added in v0.24.0

func MapSecondaryClick(x, y int, m Mods) toolkit.Event

MapSecondaryClick turns a secondary (right / two-finger / Control-click) press at the given view-local pixel into an EventSecondaryClick — the gesture that opens a context menu. It is a press with no paired release: a menu opens on the down, and there is no secondary-drag to track.

func ParsePressPoint added in v0.10.0

func ParsePressPoint(s string) (x, y int, ok bool)

ParsePressPoint reads back what PressPoint wrote.

A malformed identifier REFUSES rather than defaulting to (0,0): the origin is a real, clickable place — usually the first control in the window — so a silent fallback would turn a failed lookup into a wrong button press.

func PressPoint added in v0.10.0

func PressPoint(n toolkit.A11yNode) string

PressPoint encodes an element's centre, in the render pixels the input path speaks, as the string carried on its accessibilityIdentifier.

Stashing the point ON the element beats holding a Go-side table keyed by index: the tree is rebuilt whenever the frame changes, and an index would go stale the moment content scrolled under a VoiceOver user's cursor — pressing then activates whatever moved into that slot.

func ViewCoords

func ViewCoords(px, py, boundsHPoints, scale float64) (int, int)

ViewCoords converts an NSEvent -locationInWindow (window base coordinates, which are ALWAYS bottom-left origin in points, even in a flipped view) to device-pixel coordinates with a top-left origin — the coordinate space the toolkit framebuffer and every toolkit.Event uses.

boundsHPoints is the content view's height in points; scale is the window's backing scale factor (1 on a non-Retina display, 2 on Retina). The Y axis is flipped (boundsHPoints - py) to move the origin to the top, then both axes are multiplied by scale to reach device pixels.

Types

type Mods added in v0.12.0

type Mods struct{ Shift, Ctrl, Alt, Meta bool }

Mods is the decoded modifier state carried on every toolkit event the Cocoa backend emits: Shift, Ctrl, Alt (⌥ Option) and Meta (⌘ Command).

func DecodeMods

func DecodeMods(flags uint64) Mods

DecodeMods splits an NSEvent modifierFlags mask into the four toolkit modifier flags.

Shift maps from NSEventModifierFlagShift and Alt from NSEventModifierFlagOption (⌥). Meta maps from NSEventModifierFlagCommand (⌘). Ctrl maps from EITHER Control OR Command, so a plain ⌘-based macOS shortcut still reaches a widget with the same Ctrl flag an X11/Wayland Control chord would — preserving the toolkit's platform-neutral Ctrl shortcut semantics (Ctrl+C / ⌘C both set Ctrl) — while the new Meta flag additionally lets code that cares tell a real ⌘ chord apart from Control, and Alt surfaces ⌥ (both previously invisible). So ⌘V sets Ctrl+Meta and ⌘⌥V sets Ctrl+Meta+Alt, letting the file manager distinguish paste from paste-as-move.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL