feat(collab): presence P2 — pcbnew selection/cursor emit + remote VIEW_OVERLAY render (collab-presence 0002)

Zero kicad-fork changes — all in the embind layer:
- pcbnew_embind.cpp: presence section — canvas wx Bind() triggers (motion/
  leave/up/key/wheel) + COLLAB_LISTENER piggyback → post-settle selection
  check (dedupe) → onSelection; throttled cursor emit via VIEW::ToWorld;
  viewport push/pull (px-per-IU via the GAL matrix — GetScale() is the zoom
  and sized the first cut's overlay nm-small); kicadCollabSetRemote renders
  peers' cursors (cross + name) and selection bbox outlines into one
  per-user-colored VIEW_OVERLAY (CallAfter+COROUTINE), never touching local
  selection; PresenceStart/GetSelection/TestSelectFirst/TestClearSelection.
- kicad_editor_embind.cpp: merged-image dispatch (pcb-only until 0003).
- presence-kicad.ts: bindKicadPresence — routes emits into awareness (0001)
  and pushes trailing-throttled peer snapshots into the wasm; wired from
  WasmTool.startPresence (pcbnew-gated). +5 unit tests.
- tests/kicad/presence-pcbnew.spec.ts: 5 e2e — programmatic + real box-select
  emit, throttled cursor, remote render with no-leak + pixel restore,
  viewport unit band. Existing pcbnew-collab/items-bridge suites stay green.

Verified live: two tabs over partykit — peer cursor cross + label + selection
outline visible on the other tab's canvas.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
This commit is contained in:
Gergő Törcsvári 2026-07-06 15:59:51 +02:00
commit 8ad1cc673a
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
6 changed files with 1162 additions and 0 deletions

View file

@ -45,6 +45,13 @@ std::string pcbCollabTestMoveFirst( int aDx, int aDy );
std::string pcbCollabGetPos( std::string aId );
bool pcbCollabTestRemoveItem( std::string aId );
bool pcbCollabTestRotateItem( std::string aId, double aDeg );
// Presence (collab-presence 0002) — pcbnew only until 0003 adds the sch side.
void pcbCollabPresenceStart();
void pcbCollabSetRemote( std::string aJson );
std::string pcbCollabGetViewport();
std::string pcbCollabGetSelection();
std::string pcbCollabTestSelectFirst();
bool pcbCollabTestClearSelection();
bool schEditorActive();
void schCollabApply( std::string aJson );
@ -133,6 +140,41 @@ static bool collabTestRotateItem( std::string aId, double aDeg )
: schCollabTestRotateItem( aId, aDeg );
}
// Presence shims (collab-presence 0002): pcb-only for now — the sch frame no-ops /
// returns empty until 0003 lands schCollab* counterparts, matching how the JS side
// gates presence on the active tool.
static void collabPresenceStart()
{
if( pcbEditorActive() )
pcbCollabPresenceStart();
}
static void collabSetRemote( std::string aJson )
{
if( pcbEditorActive() )
pcbCollabSetRemote( aJson );
}
static std::string collabGetViewport()
{
return pcbEditorActive() ? pcbCollabGetViewport() : std::string();
}
static std::string collabGetSelection()
{
return pcbEditorActive() ? pcbCollabGetSelection() : std::string( "[]" );
}
static std::string collabTestSelectFirst()
{
return pcbEditorActive() ? pcbCollabTestSelectFirst() : std::string();
}
static bool collabTestClearSelection()
{
return pcbEditorActive() ? pcbCollabTestClearSelection() : false;
}
EMSCRIPTEN_BINDINGS(kicad_editor) {
// Programmatic file open (preferred over UI automation from the web app).
@ -150,6 +192,13 @@ EMSCRIPTEN_BINDINGS(kicad_editor) {
// endpoint, field text — flow from the per-editor blocks unchanged).
function("kicadCollabTestRemoveItem", &collabTestRemoveItem);
function("kicadCollabTestRotateItem", &collabTestRotateItem);
// Presence (collab-presence 0002).
function("kicadCollabPresenceStart", &collabPresenceStart);
function("kicadCollabSetRemote", &collabSetRemote);
function("kicadCollabGetViewport", &collabGetViewport);
function("kicadCollabGetSelection", &collabGetSelection);
function("kicadCollabTestSelectFirst", &collabTestSelectFirst);
function("kicadCollabTestClearSelection", &collabTestClearSelection);
}
#endif // __EMSCRIPTEN__