feat(collab): follow-user (collab-presence 0008) + chip depth-layer fix
Follow-user: click a peer's roster avatar to mirror their viewport until local input breaks it. - collab_presence_core.h: CORE::fitViewport(cx, cy, halfW, halfH) — fit the leader's world rect with CONTAIN semantics (zoom derived from the follower's own canvas via the ToScreen ratio; GetScale is the zoom, not px/IU). Exported as kicadCollabFitViewport from both editor TUs + the merged dispatcher. - presence-kicad.ts: publish the visible world rect (viewportRect) into awareness, 100 ms trailing throttle; guarded for pre-0008 handles. - follow-user.ts: createFollow — follows an awareness CLIENT (a tab, not a user); applies leader rect changes via FitViewport, dedupes unchanged republishes; break-on-interact compares local onViewport echoes against the last applied rect (2% rel tolerance, echo-grace before the first fit lands); unfollows on leader-left; pauses on eeschema sheet mismatch. - PresenceRoster: avatars are follow toggles (ring on the followed peer); WasmTool renders the "Following <name> — move to stop" banner. - tests: 7 controller units (85/85 collab), fitViewport round-trip e2e in both kicad presence specs (20/20), two-tab tests/web/follow.spec.ts (converge → track → wheel-zoom breaks → subsequent moves ignored). Chip depth-layer fix (user-reported): name chips washed out inside low-alpha selection fills — chip rects shared the shapes overlay's single depth, and same-depth fragments drawn LATER lose the depth test, so an earlier-painted fill rejected the chip's pixels. Now three layers via the fork's VIEW_OVERLAY::SetDepthOffset: text (0) < chips + pin dots (1) < selection shapes (2). drawLabel/drawCursor/drawSelectionBox take the chip overlay explicitly; comment-pin dots move to the chip layer too (the 0005 "drawn last so pins sit above" comment had the rule backwards). Verified with a chip-inside-30%-fill pixel repro + the full presence suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013u9h8fkQktH7KRECaHJmUG
This commit is contained in:
parent
9697de7b26
commit
1f85e13acf
16 changed files with 821 additions and 52 deletions
|
|
@ -128,8 +128,11 @@ struct CORE
|
|||
STYLE style;
|
||||
|
||||
std::shared_ptr<KIGFX::VIEW_OVERLAY> overlay;
|
||||
// Labels render from their own overlay at the nearest depth (chip rects
|
||||
// would otherwise erase same-depth text) — see PRESENCE_TEXT_OVERLAY.
|
||||
// Name chips + pin dots render one depth unit ABOVE the selection shapes
|
||||
// (else an earlier-painted low-alpha fill rejects the chip's fragments and
|
||||
// the tag washes out to the fill's alpha), and the text one unit above the
|
||||
// chips — see the depth-layering note in collab_presence_style.h.
|
||||
std::shared_ptr<KIGFX::VIEW_OVERLAY> chipOverlay;
|
||||
std::shared_ptr<KIGFX::VIEW_OVERLAY> textOverlay;
|
||||
|
||||
bool started = false;
|
||||
|
|
@ -235,6 +238,38 @@ struct CORE
|
|||
{ "w", sz.x }, { "h", sz.y } }.dump();
|
||||
}
|
||||
|
||||
/** kicadCollabFitViewport (0008 follow-user): fit the given world RECT
|
||||
* (center + half-extents, IU) into this canvas — contain, never crop:
|
||||
* the follower's zoom is derived from ITS OWN canvas size, so leaders
|
||||
* and followers on different monitors see the same world region.
|
||||
* Fiber like every other view mutation from JS. */
|
||||
void fitViewport( double aCx, double aCy, double aHalfW, double aHalfH )
|
||||
{
|
||||
EDA_DRAW_FRAME* fr = frame();
|
||||
|
||||
if( !fr || aHalfW <= 0 || aHalfH <= 0 )
|
||||
return;
|
||||
|
||||
pcbjam_collab::runOnFiber( fr, [this, fr, aCx, aCy, aHalfW, aHalfH]() {
|
||||
KIGFX::VIEW* view = fr->GetCanvas()->GetView();
|
||||
const VECTOR2I& sz = view->GetScreenPixelSize();
|
||||
|
||||
// Pixels-per-IU that CONTAINS the rect in both axes. GetScale() is
|
||||
// the zoom, not px/IU (0002 lesson) — convert via the GAL matrix:
|
||||
// zoom scales linearly with px/IU, so target zoom = current zoom ×
|
||||
// (target px/IU ÷ current px/IU).
|
||||
double pxPerIuNow = view->ToScreen( 1.0 );
|
||||
double pxPerIuFit = std::min( sz.x / ( 2.0 * aHalfW ), sz.y / ( 2.0 * aHalfH ) );
|
||||
|
||||
if( pxPerIuNow > 0 && pxPerIuFit > 0 )
|
||||
view->SetScale( view->GetScale() * ( pxPerIuFit / pxPerIuNow ) );
|
||||
|
||||
view->SetCenter( VECTOR2D( aCx, aCy ) );
|
||||
fr->GetCanvas()->ForceRefresh();
|
||||
emitViewportIfChanged();
|
||||
} );
|
||||
}
|
||||
|
||||
/** kicadCollabSetViewport (0005): pan to a world position (comment panel
|
||||
* "jump to pin"). Fiber like every other view mutation from JS. */
|
||||
void panTo( double aCx, double aCy )
|
||||
|
|
@ -300,16 +335,23 @@ struct CORE
|
|||
|
||||
if( !overlay )
|
||||
{
|
||||
// Depth layering near→deep: text (0) < chips (1) < shapes (2) —
|
||||
// fork SetDepthOffset; see collab_presence_style.h.
|
||||
overlay = view->MakeOverlay();
|
||||
// Shapes sit one depth unit BELOW the text overlay (fork
|
||||
// SetDepthOffset) so labels always render on top of chips/boxes.
|
||||
overlay->SetDepthOffset( PRESENCE_SHAPES_DEPTH_OFFSET );
|
||||
}
|
||||
|
||||
if( !chipOverlay )
|
||||
{
|
||||
chipOverlay = view->MakeOverlay();
|
||||
chipOverlay->SetDepthOffset( PRESENCE_CHIPS_DEPTH_OFFSET );
|
||||
}
|
||||
|
||||
if( !textOverlay )
|
||||
textOverlay = makePresenceTextOverlay( view );
|
||||
|
||||
overlay->Clear();
|
||||
chipOverlay->Clear();
|
||||
textOverlay->Clear();
|
||||
|
||||
// Screen-constant sizing: px → world units, so cursors/outline widths
|
||||
|
|
@ -326,18 +368,20 @@ struct CORE
|
|||
drawPeerShapes( *this, fr, peer, color, px );
|
||||
|
||||
if( peer.hasCursor )
|
||||
drawCursor( overlay.get(), textOverlay.get(), peer.cursor, peer.name, color,
|
||||
px, style );
|
||||
drawCursor( overlay.get(), chipOverlay.get(), textOverlay.get(), peer.cursor,
|
||||
peer.name, color, px, style );
|
||||
}
|
||||
|
||||
// Comment pin dots (0005), drawn last so they sit above selection outlines.
|
||||
// Comment pin dots (0005) — on the CHIPS layer so selection fills
|
||||
// can't reject their fragments (see drawPin).
|
||||
for( const PIN& pin : pins )
|
||||
{
|
||||
KIGFX::COLOR4D color = peerColor( style, pin.name, pin.color );
|
||||
drawPin( overlay.get(), pin.pos, color, pin.resolved, px, style );
|
||||
drawPin( chipOverlay.get(), pin.pos, color, pin.resolved, px, style );
|
||||
}
|
||||
|
||||
view->Update( overlay.get() );
|
||||
view->Update( chipOverlay.get() );
|
||||
view->Update( textOverlay.get() );
|
||||
// The canvas repaints on its own only with focus/input — force it,
|
||||
// exactly as the cross-probe flash does.
|
||||
|
|
|
|||
Loading…
Reference in a new issue