Skip to main content

Host pointer input

Shared planar and spherical pointer snapshots for native and low-level WASM hosts.

Host pointer input

The shared core builds pointer snapshots from the applied planar or spherical camera and GeoJSON style-layer queries. Native hosts call sqsm_map_pointer_json; low-level WASM hosts call MapView.pointerEvent(json). SDK dispatchPointerEvent(input) queues input with bounded delivery and forwards canvas input. The eight common pointer events and layer delegation are connected in persistent native scripts and web hosts. Desktop workbench mouse input is connected. The mobile application host remains pending.

Names and the point / lngLat shape follow MapLibre MapMouseEvent. SqsMap supplies an independently implemented, serializable snapshot; there is no browser originalEvent, DOM target or asynchronous preventDefault(). Hosts decide default gestures before forwarding input.

{"type":"click","point":{"x":64,"y":48},"shiftKey":true,"layers":["trees"]}

Supported types: click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave and contextmenu. Coordinates use logical/CSS pixels inside the current viewport, including padding and bearing. High-DPI hosts must not substitute physical canvas pixels. Optional pointerType is mouse (default), pen or touch; button is 0–4, buttons is 0–31, altKey/ctrlKey/metaKey/shiftKey default to false. This is a pointer description, not multi-touch gesture support.

The returned JSON contains type, point {x,y}, lngLat {lng,lat}, features, cameraState and input metadata. Features use queryRenderedFeatures semantics: current visible common GeoJSON circle/line/fill layers, topmost first, original logical feature IDs/properties, feature state and source/layer instance provenance. The snapshot remains historical after later camera/source/layer changes. layers restricts this query; it does not install listeners. A missing layer reports NOT_FOUND. Existing query scan/result limits also apply.

Input JSON is limited to 8 KiB and the complete snapshot to 64 KiB. Unknown fields/types, invalid coordinates or buttons report INVALID_ARGUMENT; queue, output allocation and result-budget pressure report RESOURCE_LIMIT. Oversized results reject completely; restrict layers and retry instead of expecting truncated features.

Native SQSM_MAP_POINTER_INPUT (4096) advertises this host API. Call on the map creation thread and free each successful output with sqsm_map_bytes_free. Successful native input also publishes one event through sqsm_map_poll_events_json, with its snapshot under pointerEvent. Allocation or public-queue failure leaves sequence and notifications unchanged. Drain the queue before retrying. Independent subscriptions retain their existing bounded-queue overflow rules. Physical input does not enter the script command journal, change selection, advance animation or apply gestures.

Complete C host · Public header

WASM capabilities().pointerInput advertises the snapshot binding. pointerEvent() returns its snapshot immediately; the host owns delivery scheduling and listener routing. It does not append to cameraEvents. Shared planar and spherical circle/line/fill queries are supported; arbitrary 3D queries remain pending. The matching WASM and generated native references are included in the development SDK snapshot, not a formal release.

Acceptance covers a real Linux hardware GPU C map, rotated/padded camera, logical high-DPI WASM input, ordered feature hits, historical snapshots, invalid input, exhausted output buffers, full queues and 64 KiB rejection/recovery. The next connected stage also validates the same persistent script in native/main-thread/strict Worker hosts and trusted physical mouse compatibility. External Windows/macOS, mobile and browser hardware WebGPU remain pending.

Pointer events and layer delegation

The facade supports click, dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave and contextmenu. Check map.sqs.events() for actual host input support. Persistent native-library scripts require the host to submit input; the web SDK forwards canvas input. The desktop workbench forwards mouse input from its map viewport. The mobile application host remains pending; declarations alone do not guarantee support.

import { map } from 'sqsmap';
export const sessionMode = 'persistent';

if (map.sqs.events().includes('click')) {
  const show = event => console.log(event.lngLat, event.features);
  const subscription = map.on('click', 'trees', show);
  map.once('click', ['trees', 'roads'], event => console.log(event.point));
  // To stop: subscription.unsubscribe();
  // Or map.off('click', 'trees', show);
}

on(type, listener) receives map-wide input. on(type, layerId | layerIds, listener) only receives hits in the selected layers and filters features accordingly. For ordinary hit events, missing layers or misses remain silent and do not consume once; mouseenter/mouseleave use hit transitions, with empty leave features; subscriptions follow a layer recreated with the same public ID. Layer arrays are normalized and deduplicated. The same function, event and layer set are registered once. off matches that function and normalized set; once(type, layers) returns a Promise. Delegation is limited to the eight pointer event types above.

point uses viewport logical pixels {x,y}; lngLat is {lng,lat}, or null for viewport points without a ground location (outside the world in the latitude/longitude view, or where a pitched view has no ground hit). Events include type, target, mapInstanceId, sequence, features, cameraState and input. Snapshots are immutable and historical. target is the current map object/reference, not a DOM node. Hits cover currently visible GeoJSON circle/line/fill layers, topmost first; symbol layers and complete 3D queries remain pending. There is no DOM originalEvent or preventDefault().

Forwarding input does not enter the script command journal, select features, apply gestures or advance animation. SDK pointer subscriptions, queued inputs and pending asynchronous callbacks each have a budget of 32. Pressure reports RESOURCE_LIMIT; releasing it permits recovery. Stopping a script or destroying a map cancels listeners and waits.

Complete common script and host input guide. Standalone SDK on returns a callable cancellation function that also supports .unsubscribe(); common scripts return Subscription. Legacy SDK numeric-layer clicks retain feature and x/y; their coordinate tuple is now legacyLngLat, while standard lngLat is an object. The legacy configured context-menu callback retains its original shape.

SDK interactive: false disables default gestures while preserving pointer event forwarding for caller-owned interaction.

Desktop workbench input

The desktop workbench routes mouse input inside the map viewport through the same shared query, supporting the eight events and the same layer-based on/off/once overloads. Foreground tools consume their own input. Coordinates subtract the actual viewport offset; repeated layout passes deliver a frame only once. Primary release produces click, double-click adds dblclick, secondary release produces contextmenu, and middle buttons only produce down/up. The desktop host retains its default navigation and selection behavior.

Input queries are enabled only while scripts are active. Each frame retains its previous session recipients, so a newly started script cannot receive old queued input. Stop, rerun and shutdown clear waits and listeners. The desktop input queue holds 128 entries; overflow terminates affected scripts with RESOURCE_LIMIT instead of delivering a truncated event sequence. Independent Rust hosts must explicitly enable pointer_input before starting ScriptService and drive input and polling; the default service does not advertise physical input support.

Desktop queries cover common planar GeoJSON circle/line/fill and spherical circle/line/fill layers. Complete 3D queries and multi-touch remain pending. egui touch-emulated pointers can carry touch metadata; this does not establish mobile-host or physical touch-device acceptance. Native pen identification and Windows/Linux Meta-key mapping also remain pending.

Local acceptance uses the actual Linux workbench, hardware GPU and QuickJS with input injected at eframe's RawInput boundary: the unchanged cross-platform script passes 38 assertions, followed by old-input isolation, fresh-input and stopped-callback checks, plus actual circle pixel readback. This does not establish OS HID, Windows/macOS or mobile-device acceptance.

Spherical event coordinates

cameraState uses the globe branch of MapCameraState, preserving the camera and logical viewport at notification time. lngLat is {lng,lat} on the ground and null when the pointer ray misses the sphere. The SDK compatibility tuple legacyLngLat is also null in the sky. Check before reading coordinates:

map.on('click', event => {
  if (event.lngLat === null) { console.log('Sky'); return; }
  console.log(event.lngLat.lng, event.lngLat.lat, event.features);
});

Sky and an empty feature hit are different: ground without a layer still has coordinates, and a circle billboard can extend beyond the globe edge. Spherical hits use shared circle/line/fill queries; symbols and complete 3D queries reject explicitly. See the complete spherical pointer script. This stage checks the same 38 assertions in actual native QuickJS and browser main/strict Worker at DPR1/2. Desktop acceptance uses eframe RawInput, actual circle pixels and rerun/stop isolation on software Vulkan; the probe clears caller-owned selection to prevent business panels covering later input. Product selection behavior is unchanged. Real touch/pen and the mobile host remain separate acceptance work.

Shared spherical line/fill drawing, filters, order and queries: spherical surfaces.

Layer enter/leave semantics are connected; see that guide for local evidence.

Delegated mouseenter/mouseleave follow each subscription’s mouse/pen hit transitions; touch does not create hover. Leave has empty features. Unsubscribe/once/rerun/destruction clear state. Low-level mouseleave accepts finite points outside the viewport and returns lngLat:null; other input stays inside. Style changes do not fabricate input: the next snapshot determines transitions, following recreated IDs.

Planar snapshots without ground coordinates

A valid input inside a planar viewport can lack usable ground coordinates: the converted longitude/latitude may fall outside the world, or a pitched Web Mercator pointer ray may have no valid ground intersection. In that case lngLat is null; the event still succeeds and still runs queryRenderedFeatures. Missing ground coordinates do not fail the complete event or suppress features. Globe sky and mouseleave can also return null. Check event.lngLat === null before reading coordinates and inspect event.features independently as needed.