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.
|
||||
|
|
|
|||
|
|
@ -245,8 +245,18 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
/** Depth offset for the SHAPES overlay — one unit deeper than the text. */
|
||||
constexpr double PRESENCE_SHAPES_DEPTH_OFFSET = 1.0;
|
||||
/**
|
||||
* Depth layering (0007 lesson, extended): each overlay draws its WHOLE
|
||||
* command list at one depth, and same-depth fragments drawn later LOSE the
|
||||
* depth test — so anything that must render on top of something else needs
|
||||
* its own overlay one unit nearer. Three layers, near → deep:
|
||||
* text (0) < chips + pin dots (1) < selection shapes/fills (2)
|
||||
* Chips above fills fixes the washed-out name tags inside low-alpha
|
||||
* selection areas (the chip fragments were rejected against the
|
||||
* earlier-drawn fill, leaving only the fill's alpha behind the glyphs).
|
||||
*/
|
||||
constexpr double PRESENCE_CHIPS_DEPTH_OFFSET = 1.0;
|
||||
constexpr double PRESENCE_SHAPES_DEPTH_OFFSET = 2.0;
|
||||
|
||||
/** VIEW::MakeOverlay's body, for the text overlay (make + Add to the view). */
|
||||
inline std::shared_ptr<PRESENCE_TEXT_OVERLAY> makePresenceTextOverlay( KIGFX::VIEW* aView )
|
||||
|
|
@ -257,12 +267,13 @@ inline std::shared_ptr<PRESENCE_TEXT_OVERLAY> makePresenceTextOverlay( KIGFX::VI
|
|||
}
|
||||
|
||||
/** Name tag next to (or inside) a box, per the label placement knobs. `px` is
|
||||
* world-units-per-screen-pixel. The chip rect goes to the SHAPES overlay,
|
||||
* the text to the TEXT overlay (nearest depth + pinned TOP-LEFT justify —
|
||||
* see PRESENCE_TEXT_OVERLAY), so the text always renders on top. */
|
||||
inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv, const BOX2I& aBox,
|
||||
const std::string& aText, const KIGFX::COLOR4D& aColor, double aPx,
|
||||
const STYLE& aS )
|
||||
* world-units-per-screen-pixel. The chip rect goes to the CHIPS overlay
|
||||
* (above selection fills, below text), the text to the TEXT overlay (nearest
|
||||
* depth + pinned TOP-LEFT justify — see PRESENCE_TEXT_OVERLAY), so the tag
|
||||
* renders solid regardless of what selection geometry it overlaps. */
|
||||
inline void drawLabel( KIGFX::VIEW_OVERLAY* aChipOv, KIGFX::VIEW_OVERLAY* aTextOv,
|
||||
const BOX2I& aBox, const std::string& aText,
|
||||
const KIGFX::COLOR4D& aColor, double aPx, const STYLE& aS )
|
||||
{
|
||||
if( !aS.labelShow || aText.empty() )
|
||||
return;
|
||||
|
|
@ -288,13 +299,13 @@ inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv, c
|
|||
if( aS.labelChip )
|
||||
{
|
||||
double padX = 3 * aPx, padY = 2 * aPx;
|
||||
aOv->SetIsStroke( false );
|
||||
aOv->SetIsFill( true );
|
||||
aOv->SetFillColor( aColor.WithAlpha( aS.chipBgAlpha ) );
|
||||
aOv->Rectangle( VECTOR2D( x - padX, y - padY ),
|
||||
VECTOR2D( x + w + padX, y + h + padY ) );
|
||||
aOv->SetIsStroke( true );
|
||||
aOv->SetIsFill( false );
|
||||
aChipOv->SetIsStroke( false );
|
||||
aChipOv->SetIsFill( true );
|
||||
aChipOv->SetFillColor( aColor.WithAlpha( aS.chipBgAlpha ) );
|
||||
aChipOv->Rectangle( VECTOR2D( x - padX, y - padY ),
|
||||
VECTOR2D( x + w + padX, y + h + padY ) );
|
||||
aChipOv->SetIsStroke( true );
|
||||
aChipOv->SetIsFill( false );
|
||||
}
|
||||
|
||||
aTextOv->SetIsStroke( true );
|
||||
|
|
@ -307,8 +318,11 @@ inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv, c
|
|||
|
||||
/** Selection highlight for one item, in the chosen shape. `aOutline` is the
|
||||
* item's exact geometry for selShape 5 (pcbnew supplies it; eeschema passes
|
||||
* nullptr and shape 5 falls back to the bbox rectangle). */
|
||||
inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv, BOX2I aBox,
|
||||
* nullptr and shape 5 falls back to the bbox rectangle). Shapes/fills paint
|
||||
* on `aOv` (deepest layer); the name chip goes to `aChipOv` one unit nearer
|
||||
* so it stays solid over any fill (see the depth-layering note above). */
|
||||
inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aChipOv,
|
||||
KIGFX::VIEW_OVERLAY* aTextOv, BOX2I aBox,
|
||||
const std::string& aName, const KIGFX::COLOR4D& aColor, double aPx,
|
||||
const STYLE& aS, const SHAPE_POLY_SET* aOutline = nullptr )
|
||||
{
|
||||
|
|
@ -323,7 +337,7 @@ inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTe
|
|||
|
||||
BOX2I labelBox = aOutline->BBox();
|
||||
labelBox.Inflate( KiROUND( aS.selPaddingPx * aPx ) );
|
||||
drawLabel( aOv, aTextOv, labelBox, aName, aColor, aPx, aS );
|
||||
drawLabel( aChipOv, aTextOv, labelBox, aName, aColor, aPx, aS );
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -403,11 +417,13 @@ inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTe
|
|||
}
|
||||
}
|
||||
|
||||
drawLabel( aOv, aTextOv, aBox, aName, aColor, aPx, aS );
|
||||
drawLabel( aChipOv, aTextOv, aBox, aName, aColor, aPx, aS );
|
||||
}
|
||||
|
||||
/** Remote cursor (+ name label) in the chosen shape. */
|
||||
inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv,
|
||||
/** Remote cursor (+ name label) in the chosen shape. The cursor glyph paints
|
||||
* on `aOv`; its label chip on `aChipOv` (same layering as drawSelectionBox). */
|
||||
inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aChipOv,
|
||||
KIGFX::VIEW_OVERLAY* aTextOv,
|
||||
const VECTOR2D& aPos, const std::string& aName,
|
||||
const KIGFX::COLOR4D& aColor, double aPx, const STYLE& aS )
|
||||
{
|
||||
|
|
@ -455,18 +471,18 @@ inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv,
|
|||
VECTOR2D at = aPos + VECTOR2D( ( aS.cursorSizePx + 4 ) * aPx,
|
||||
( aS.cursorSizePx + 4 ) * aPx );
|
||||
|
||||
// Chip rect on the shapes overlay, text on the TEXT overlay (nearest
|
||||
// depth) — text always renders on top of its chip.
|
||||
// Chip rect on the CHIPS overlay, text on the TEXT overlay (nearest
|
||||
// depth) — text on top of its chip, chip on top of selection fills.
|
||||
if( aS.cursorLabelChip )
|
||||
{
|
||||
double padX = 3 * aPx, padY = 2 * aPx;
|
||||
aOv->SetIsStroke( false );
|
||||
aOv->SetIsFill( true );
|
||||
aOv->SetFillColor( aColor.WithAlpha( aS.chipBgAlpha ) );
|
||||
aOv->Rectangle( at + VECTOR2D( -padX, -padY ),
|
||||
at + VECTOR2D( w + padX, h + padY ) );
|
||||
aOv->SetIsStroke( true );
|
||||
aOv->SetIsFill( false );
|
||||
aChipOv->SetIsStroke( false );
|
||||
aChipOv->SetIsFill( true );
|
||||
aChipOv->SetFillColor( aColor.WithAlpha( aS.chipBgAlpha ) );
|
||||
aChipOv->Rectangle( at + VECTOR2D( -padX, -padY ),
|
||||
at + VECTOR2D( w + padX, h + padY ) );
|
||||
aChipOv->SetIsStroke( true );
|
||||
aChipOv->SetIsFill( false );
|
||||
}
|
||||
|
||||
aTextOv->SetIsStroke( true );
|
||||
|
|
@ -478,7 +494,9 @@ inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv,
|
|||
}
|
||||
}
|
||||
|
||||
/** Comment pin dot. */
|
||||
/** Comment pin dot. Draw onto the CHIPS overlay: at the shapes depth an
|
||||
* earlier-painted selection fill would reject the dot's fragments (the 0005
|
||||
* "drawn last sits above" comment had it backwards — later fragments LOSE). */
|
||||
inline void drawPin( KIGFX::VIEW_OVERLAY* aOv, const VECTOR2D& aPos, const KIGFX::COLOR4D& aColor,
|
||||
bool aResolved, double aPx, const STYLE& aS )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -733,7 +733,8 @@ pcbjam_presence::CORE& presenceCore()
|
|||
if( !item )
|
||||
continue; // not in this schematic (yet) — skip silently
|
||||
|
||||
pcbjam_presence::drawSelectionBox( aCore.overlay.get(), aCore.textOverlay.get(),
|
||||
pcbjam_presence::drawSelectionBox( aCore.overlay.get(), aCore.chipOverlay.get(),
|
||||
aCore.textOverlay.get(),
|
||||
item->ViewBBox(), peer.name, color, px,
|
||||
aCore.style );
|
||||
}
|
||||
|
|
@ -745,7 +746,8 @@ pcbjam_presence::CORE& presenceCore()
|
|||
|
||||
for( SCH_ITEM* item : resolveXsel( fr, peer ) )
|
||||
{
|
||||
pcbjam_presence::drawSelectionBox( aCore.overlay.get(), aCore.textOverlay.get(),
|
||||
pcbjam_presence::drawSelectionBox( aCore.overlay.get(), aCore.chipOverlay.get(),
|
||||
aCore.textOverlay.get(),
|
||||
item->ViewBBox(), peer.name, color, px,
|
||||
ghost );
|
||||
}
|
||||
|
|
@ -1467,6 +1469,12 @@ void schCollabSetViewport( double aCx, double aCy )
|
|||
presenceCore().panTo( aCx, aCy );
|
||||
}
|
||||
|
||||
// JS → C++ (0008 follow-user): fit a leader's world rect into this canvas.
|
||||
void schCollabFitViewport( double aCx, double aCy, double aHalfW, double aHalfH )
|
||||
{
|
||||
presenceCore().fitViewport( aCx, aCy, aHalfW, aHalfH );
|
||||
}
|
||||
|
||||
// JS pull of the current viewport transform: `{cx,cy,scale,w,h}` with scale = px per
|
||||
// IU via the GAL matrix (GetScale() is the zoom, not px/IU — pcbnew 0002 lesson).
|
||||
std::string schCollabGetViewport()
|
||||
|
|
@ -1691,6 +1699,8 @@ EMSCRIPTEN_BINDINGS(eeschema) {
|
|||
function("kicadCollabSetRemote", &schCollabSetRemote);
|
||||
function("kicadCollabSetPins", &schCollabSetPins);
|
||||
function("kicadCollabSetViewport", &schCollabSetViewport);
|
||||
// Follow-user (collab-presence 0008).
|
||||
function("kicadCollabFitViewport", &schCollabFitViewport);
|
||||
function("kicadCollabSetStyle", &schCollabSetStyle);
|
||||
function("kicadCollabTestListItems", &schCollabTestListItems);
|
||||
function("kicadCollabTestDemoSet", &schCollabTestDemoSet);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ void pcbCollabPresenceStart();
|
|||
void pcbCollabSetRemote( std::string aJson );
|
||||
void pcbCollabSetPins( std::string aJson );
|
||||
void pcbCollabSetViewport( double aCx, double aCy );
|
||||
// Follow-user (collab-presence 0008).
|
||||
void pcbCollabFitViewport( double aCx, double aCy, double aHalfW, double aHalfH );
|
||||
void pcbCollabSetStyle( std::string aJson );
|
||||
std::string pcbCollabTestListItems( int aCount );
|
||||
std::string pcbCollabTestDemoSet();
|
||||
|
|
@ -90,6 +92,8 @@ void schCollabPresenceStart();
|
|||
void schCollabSetRemote( std::string aJson );
|
||||
void schCollabSetPins( std::string aJson );
|
||||
void schCollabSetViewport( double aCx, double aCy );
|
||||
// Follow-user (collab-presence 0008).
|
||||
void schCollabFitViewport( double aCx, double aCy, double aHalfW, double aHalfH );
|
||||
void schCollabSetStyle( std::string aJson );
|
||||
std::string schCollabTestListItems( int aCount );
|
||||
std::string schCollabTestDemoSet();
|
||||
|
|
@ -337,6 +341,13 @@ static void collabSetViewport( double aCx, double aCy )
|
|||
pcbEditorActive() ? pcbCollabSetViewport( aCx, aCy ) : schCollabSetViewport( aCx, aCy );
|
||||
}
|
||||
|
||||
// Follow-user (collab-presence 0008).
|
||||
static void collabFitViewport( double aCx, double aCy, double aHalfW, double aHalfH )
|
||||
{
|
||||
pcbEditorActive() ? pcbCollabFitViewport( aCx, aCy, aHalfW, aHalfH )
|
||||
: schCollabFitViewport( aCx, aCy, aHalfW, aHalfH );
|
||||
}
|
||||
|
||||
static void collabSetStyle( std::string aJson )
|
||||
{
|
||||
pcbEditorActive() ? pcbCollabSetStyle( aJson ) : schCollabSetStyle( aJson );
|
||||
|
|
@ -426,6 +437,8 @@ EMSCRIPTEN_BINDINGS(kicad_editor) {
|
|||
function("kicadCollabSetRemote", &collabSetRemote);
|
||||
function("kicadCollabSetPins", &collabSetPins);
|
||||
function("kicadCollabSetViewport", &collabSetViewport);
|
||||
// Follow-user (collab-presence 0008).
|
||||
function("kicadCollabFitViewport", &collabFitViewport);
|
||||
function("kicadCollabSetStyle", &collabSetStyle);
|
||||
function("kicadCollabTestListItems", &collabTestListItems);
|
||||
function("kicadCollabTestDemoSet", &collabTestDemoSet);
|
||||
|
|
|
|||
|
|
@ -1203,7 +1203,8 @@ pcbjam_presence::CORE& presenceCore()
|
|||
}
|
||||
}
|
||||
|
||||
pcbjam_presence::drawSelectionBox( aCore.overlay.get(), aCore.textOverlay.get(),
|
||||
pcbjam_presence::drawSelectionBox( aCore.overlay.get(), aCore.chipOverlay.get(),
|
||||
aCore.textOverlay.get(),
|
||||
item->ViewBBox(), name, itemColor, px,
|
||||
style, &outline );
|
||||
};
|
||||
|
|
@ -1594,6 +1595,12 @@ void pcbCollabSetViewport( double aCx, double aCy )
|
|||
presenceCore().panTo( aCx, aCy );
|
||||
}
|
||||
|
||||
// JS → C++ (0008 follow-user): fit a leader's world rect into this canvas.
|
||||
void pcbCollabFitViewport( double aCx, double aCy, double aHalfW, double aHalfH )
|
||||
{
|
||||
presenceCore().fitViewport( aCx, aCy, aHalfW, aHalfH );
|
||||
}
|
||||
|
||||
// JS pull of the current viewport transform (world↔screen mapping for the DOM layer):
|
||||
// `{cx,cy,scale,w,h}` — world center, pixels-per-IU scale, canvas size in px.
|
||||
std::string pcbCollabGetViewport()
|
||||
|
|
@ -1981,6 +1988,8 @@ EMSCRIPTEN_BINDINGS(pcbnew) {
|
|||
function("kicadCollabSetRemote", &pcbCollabSetRemote);
|
||||
function("kicadCollabSetPins", &pcbCollabSetPins);
|
||||
function("kicadCollabSetViewport", &pcbCollabSetViewport);
|
||||
// Follow-user (collab-presence 0008).
|
||||
function("kicadCollabFitViewport", &pcbCollabFitViewport);
|
||||
function("kicadCollabSetStyle", &pcbCollabSetStyle);
|
||||
function("kicadCollabTestListItems", &pcbCollabTestListItems);
|
||||
function("kicadCollabTestDemoSet", &pcbCollabTestDemoSet);
|
||||
|
|
|
|||
Loading…
Reference in a new issue