fix(collab): name badges render for real — text overlay above a depth-offset shapes overlay
The full root-cause chain of the empty/mangled badges: - GAL text justify is painter-residue → PRESENCE_TEXT_OVERLAY pins TOP-LEFT. - VIEW_OVERLAY::ViewDraw hard-sets EVERY overlay to GetMinDepth(), so the shapes and text overlays always collided at one depth, where later-drawn fragments lose (and bitmap glyphs are textured quads whose transparent cells also write depth — punch-through produced cell-shaped holes instead). Fix rides the new fork VIEW_OVERLAY::SetDepthOffset: shapes at min+1, labels at min — 'rect first, text on top' now holds regardless of paint order. Verified: chips contain crisp names (dark-on-light, white-on-dark) for selection tags and cursor labels; presence suites 10/10. - kicad pointer bump (fork 24c5854d5b). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvqUd4QsJSGHN28aunJRTq
This commit is contained in:
parent
4c287c2a27
commit
c8c2c5f583
4 changed files with 89 additions and 52 deletions
2
kicad
2
kicad
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4a7250d11a91042ba7cb58e3962881373a1c904d
|
Subproject commit 24c5854d5bf76fc908b8ba8a3bed63675d5067cf
|
||||||
|
|
@ -77,6 +77,7 @@ struct STYLE
|
||||||
double pinRingAlpha = 0.9;
|
double pinRingAlpha = 0.9;
|
||||||
double pinFillAlpha = 1.0;
|
double pinFillAlpha = 1.0;
|
||||||
double pinResolvedAlpha = 0.3;
|
double pinResolvedAlpha = 0.3;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline KIGFX::COLOR4D parseHexColor( const std::string& aHex, const KIGFX::COLOR4D& aFallback )
|
inline KIGFX::COLOR4D parseHexColor( const std::string& aHex, const KIGFX::COLOR4D& aFallback )
|
||||||
|
|
@ -179,14 +180,21 @@ inline KIGFX::COLOR4D chipTextColor( const KIGFX::COLOR4D& aBg )
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VIEW_OVERLAY that PINS the GAL text justify before executing its commands.
|
* The TEXT half of the presence drawing. All labels go through this overlay
|
||||||
* A plain overlay draws with whatever justify the last painter left in the
|
* because a plain VIEW_OVERLAY has two text hazards:
|
||||||
* GAL (CENTER is only the reset default), so BitmapText anchoring was
|
* - it draws with whatever justify the last painter left in the GAL
|
||||||
* nondeterministic — labels wandered around their chips depending on what
|
* (CENTER is only the reset default) → anchoring was nondeterministic;
|
||||||
* rendered before them. This guarantees TOP-LEFT anchoring for every
|
* this pins TOP-LEFT, which the label math is written against.
|
||||||
* BitmapText below; the label math is written against that.
|
* - every overlay draws its whole command list at ONE depth
|
||||||
|
* (VIEW_OVERLAY::ViewDraw hard-sets GetMinDepth()) and same-depth
|
||||||
|
* fragments drawn later LOSE the depth test; bitmap glyphs are textured
|
||||||
|
* QUADS whose transparent cells also write depth, so text can neither be
|
||||||
|
* drawn under a chip (erased) nor over one (punches cell-shaped holes).
|
||||||
|
* The SHAPES overlay is therefore pushed DEEPER via the fork's
|
||||||
|
* VIEW_OVERLAY::SetDepthOffset (chips at min+1, text at min) — "rect
|
||||||
|
* first, text on top" then holds regardless of paint order.
|
||||||
*/
|
*/
|
||||||
class PRESENCE_OVERLAY : public KIGFX::VIEW_OVERLAY
|
class PRESENCE_TEXT_OVERLAY : public KIGFX::VIEW_OVERLAY
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override
|
void ViewDraw( int aLayer, KIGFX::VIEW* aView ) const override
|
||||||
|
|
@ -198,20 +206,24 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** VIEW::MakeOverlay's body, for our subclass (make + Add to the view). */
|
/** Depth offset for the SHAPES overlay — one unit deeper than the text. */
|
||||||
inline std::shared_ptr<PRESENCE_OVERLAY> makePresenceOverlay( KIGFX::VIEW* aView )
|
constexpr double PRESENCE_SHAPES_DEPTH_OFFSET = 1.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 )
|
||||||
{
|
{
|
||||||
auto overlay = std::make_shared<PRESENCE_OVERLAY>();
|
auto overlay = std::make_shared<PRESENCE_TEXT_OVERLAY>();
|
||||||
aView->Add( overlay.get() );
|
aView->Add( overlay.get() );
|
||||||
return overlay;
|
return overlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Name tag next to (or inside) a box, per the label placement knobs. `px` is
|
/** Name tag next to (or inside) a box, per the label placement knobs. `px` is
|
||||||
* world-units-per-screen-pixel. Text anchoring is TOP-LEFT — guaranteed by
|
* world-units-per-screen-pixel. The chip rect goes to the SHAPES overlay,
|
||||||
* PRESENCE_OVERLAY pinning the GAL justify (a plain overlay inherits
|
* the text to the TEXT overlay (nearest depth + pinned TOP-LEFT justify —
|
||||||
* whatever the last painter left, which made anchoring nondeterministic). */
|
* see PRESENCE_TEXT_OVERLAY), so the text always renders on top. */
|
||||||
inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, const BOX2I& aBox, const std::string& aText,
|
inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv, const BOX2I& aBox,
|
||||||
const KIGFX::COLOR4D& aColor, double aPx, const STYLE& aS )
|
const std::string& aText, const KIGFX::COLOR4D& aColor, double aPx,
|
||||||
|
const STYLE& aS )
|
||||||
{
|
{
|
||||||
if( !aS.labelShow || aText.empty() )
|
if( !aS.labelShow || aText.empty() )
|
||||||
return;
|
return;
|
||||||
|
|
@ -234,17 +246,6 @@ inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, const BOX2I& aBox, const std::s
|
||||||
else
|
else
|
||||||
y = aS.labelInside ? aBox.GetEnd().y - off - h : aBox.GetEnd().y + off;
|
y = aS.labelInside ? aBox.GetEnd().y - off - h : aBox.GetEnd().y + off;
|
||||||
|
|
||||||
// The whole overlay draws at ONE depth: same-depth fragments drawn LATER
|
|
||||||
// lose the depth test, so anything covering the text erases it. Draw the
|
|
||||||
// TEXT FIRST and the chip rect AFTER — the rect is rejected exactly on
|
|
||||||
// the glyph pixels, punching the text through the chip.
|
|
||||||
aOv->SetIsStroke( true );
|
|
||||||
aOv->SetIsFill( false );
|
|
||||||
aOv->SetStrokeColor( aS.labelChip ? chipTextColor( aColor ) : aColor );
|
|
||||||
aOv->SetGlyphSize( VECTOR2I( KiROUND( h ), KiROUND( h ) ) );
|
|
||||||
aOv->BitmapText( wxString::FromUTF8( aText.c_str() ), VECTOR2I( KiROUND( x ), KiROUND( y ) ),
|
|
||||||
ANGLE_0 );
|
|
||||||
|
|
||||||
if( aS.labelChip )
|
if( aS.labelChip )
|
||||||
{
|
{
|
||||||
double padX = 3 * aPx, padY = 2 * aPx;
|
double padX = 3 * aPx, padY = 2 * aPx;
|
||||||
|
|
@ -256,14 +257,21 @@ inline void drawLabel( KIGFX::VIEW_OVERLAY* aOv, const BOX2I& aBox, const std::s
|
||||||
aOv->SetIsStroke( true );
|
aOv->SetIsStroke( true );
|
||||||
aOv->SetIsFill( false );
|
aOv->SetIsFill( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aTextOv->SetIsStroke( true );
|
||||||
|
aTextOv->SetIsFill( false );
|
||||||
|
aTextOv->SetStrokeColor( aS.labelChip ? chipTextColor( aColor ) : aColor );
|
||||||
|
aTextOv->SetGlyphSize( VECTOR2I( KiROUND( h ), KiROUND( h ) ) );
|
||||||
|
aTextOv->BitmapText( wxString::FromUTF8( aText.c_str() ),
|
||||||
|
VECTOR2I( KiROUND( x ), KiROUND( y ) ), ANGLE_0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Selection highlight for one item, in the chosen shape. `aOutline` is the
|
/** Selection highlight for one item, in the chosen shape. `aOutline` is the
|
||||||
* item's exact geometry for selShape 5 (pcbnew supplies it; eeschema passes
|
* item's exact geometry for selShape 5 (pcbnew supplies it; eeschema passes
|
||||||
* nullptr and shape 5 falls back to the bbox rectangle). */
|
* nullptr and shape 5 falls back to the bbox rectangle). */
|
||||||
inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, BOX2I aBox, const std::string& aName,
|
inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv, BOX2I aBox,
|
||||||
const KIGFX::COLOR4D& aColor, double aPx, const STYLE& aS,
|
const std::string& aName, const KIGFX::COLOR4D& aColor, double aPx,
|
||||||
const SHAPE_POLY_SET* aOutline = nullptr )
|
const STYLE& aS, const SHAPE_POLY_SET* aOutline = nullptr )
|
||||||
{
|
{
|
||||||
if( aS.selShape == 5 && aOutline && aOutline->OutlineCount() > 0 )
|
if( aS.selShape == 5 && aOutline && aOutline->OutlineCount() > 0 )
|
||||||
{
|
{
|
||||||
|
|
@ -276,7 +284,7 @@ inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, BOX2I aBox, const std::s
|
||||||
|
|
||||||
BOX2I labelBox = aOutline->BBox();
|
BOX2I labelBox = aOutline->BBox();
|
||||||
labelBox.Inflate( KiROUND( aS.selPaddingPx * aPx ) );
|
labelBox.Inflate( KiROUND( aS.selPaddingPx * aPx ) );
|
||||||
drawLabel( aOv, labelBox, aName, aColor, aPx, aS );
|
drawLabel( aOv, aTextOv, labelBox, aName, aColor, aPx, aS );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -356,11 +364,12 @@ inline void drawSelectionBox( KIGFX::VIEW_OVERLAY* aOv, BOX2I aBox, const std::s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawLabel( aOv, aBox, aName, aColor, aPx, aS );
|
drawLabel( aOv, aTextOv, aBox, aName, aColor, aPx, aS );
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remote cursor (+ name label) in the chosen shape. */
|
/** Remote cursor (+ name label) in the chosen shape. */
|
||||||
inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, const VECTOR2D& aPos, const std::string& aName,
|
inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aTextOv,
|
||||||
|
const VECTOR2D& aPos, const std::string& aName,
|
||||||
const KIGFX::COLOR4D& aColor, double aPx, const STYLE& aS )
|
const KIGFX::COLOR4D& aColor, double aPx, const STYLE& aS )
|
||||||
{
|
{
|
||||||
double s = aS.cursorSizePx * aPx;
|
double s = aS.cursorSizePx * aPx;
|
||||||
|
|
@ -407,15 +416,8 @@ inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, const VECTOR2D& aPos, const st
|
||||||
VECTOR2D at = aPos + VECTOR2D( ( aS.cursorSizePx + 4 ) * aPx,
|
VECTOR2D at = aPos + VECTOR2D( ( aS.cursorSizePx + 4 ) * aPx,
|
||||||
( aS.cursorSizePx + 4 ) * aPx );
|
( aS.cursorSizePx + 4 ) * aPx );
|
||||||
|
|
||||||
// Text FIRST, chip rect AFTER — see drawLabel (same-depth z rejection
|
// Chip rect on the shapes overlay, text on the TEXT overlay (nearest
|
||||||
// punches the text through the chip).
|
// depth) — text always renders on top of its chip.
|
||||||
aOv->SetIsStroke( true );
|
|
||||||
aOv->SetIsFill( false );
|
|
||||||
aOv->SetStrokeColor( aS.cursorLabelChip ? chipTextColor( aColor ) : c );
|
|
||||||
aOv->SetGlyphSize( VECTOR2I( KiROUND( h ), KiROUND( h ) ) );
|
|
||||||
aOv->BitmapText( wxString::FromUTF8( aName.c_str() ),
|
|
||||||
VECTOR2I( KiROUND( at.x ), KiROUND( at.y ) ), ANGLE_0 );
|
|
||||||
|
|
||||||
if( aS.cursorLabelChip )
|
if( aS.cursorLabelChip )
|
||||||
{
|
{
|
||||||
double padX = 3 * aPx, padY = 2 * aPx;
|
double padX = 3 * aPx, padY = 2 * aPx;
|
||||||
|
|
@ -427,6 +429,13 @@ inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, const VECTOR2D& aPos, const st
|
||||||
aOv->SetIsStroke( true );
|
aOv->SetIsStroke( true );
|
||||||
aOv->SetIsFill( false );
|
aOv->SetIsFill( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aTextOv->SetIsStroke( true );
|
||||||
|
aTextOv->SetIsFill( false );
|
||||||
|
aTextOv->SetStrokeColor( aS.cursorLabelChip ? chipTextColor( aColor ) : c );
|
||||||
|
aTextOv->SetGlyphSize( VECTOR2I( KiROUND( h ), KiROUND( h ) ) );
|
||||||
|
aTextOv->BitmapText( wxString::FromUTF8( aName.c_str() ),
|
||||||
|
VECTOR2I( KiROUND( at.x ), KiROUND( at.y ) ), ANGLE_0 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -720,6 +720,9 @@ std::vector<PIN> g_pins;
|
||||||
// — see collab_presence_style.h; live-patched by kicadCollabSetStyle (tuner).
|
// — see collab_presence_style.h; live-patched by kicadCollabSetStyle (tuner).
|
||||||
pcbjam_presence::STYLE g_style;
|
pcbjam_presence::STYLE g_style;
|
||||||
std::shared_ptr<KIGFX::VIEW_OVERLAY> g_overlay;
|
std::shared_ptr<KIGFX::VIEW_OVERLAY> g_overlay;
|
||||||
|
// Labels render from their own overlay at the nearest depth (chip rects
|
||||||
|
// would otherwise erase same-depth text) — see PRESENCE_TEXT_OVERLAY.
|
||||||
|
std::shared_ptr<KIGFX::VIEW_OVERLAY> g_textOverlay;
|
||||||
bool g_started = false;
|
bool g_started = false;
|
||||||
bool g_redrawScheduled = false;
|
bool g_redrawScheduled = false;
|
||||||
bool g_selCheckScheduled = false;
|
bool g_selCheckScheduled = false;
|
||||||
|
|
@ -799,9 +802,18 @@ void redrawOverlay()
|
||||||
KIGFX::VIEW* view = fr->GetCanvas()->GetView();
|
KIGFX::VIEW* view = fr->GetCanvas()->GetView();
|
||||||
|
|
||||||
if( !g_overlay )
|
if( !g_overlay )
|
||||||
g_overlay = pcbjam_presence::makePresenceOverlay( view );
|
{
|
||||||
|
g_overlay = view->MakeOverlay();
|
||||||
|
// Shapes sit one depth unit BELOW the text overlay (fork
|
||||||
|
// SetDepthOffset) so labels always render on top of chips/boxes.
|
||||||
|
g_overlay->SetDepthOffset( pcbjam_presence::PRESENCE_SHAPES_DEPTH_OFFSET );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !g_textOverlay )
|
||||||
|
g_textOverlay = pcbjam_presence::makePresenceTextOverlay( view );
|
||||||
|
|
||||||
g_overlay->Clear();
|
g_overlay->Clear();
|
||||||
|
g_textOverlay->Clear();
|
||||||
|
|
||||||
// Screen-constant sizing via the GAL matrix (GetScale() is the zoom, not px/IU).
|
// Screen-constant sizing via the GAL matrix (GetScale() is the zoom, not px/IU).
|
||||||
double px = view->ToWorld( 1.0 );
|
double px = view->ToWorld( 1.0 );
|
||||||
|
|
@ -821,13 +833,14 @@ void redrawOverlay()
|
||||||
if( !item )
|
if( !item )
|
||||||
continue; // not in this schematic (yet) — skip silently
|
continue; // not in this schematic (yet) — skip silently
|
||||||
|
|
||||||
pcbjam_presence::drawSelectionBox( g_overlay.get(), item->ViewBBox(), peer.name,
|
pcbjam_presence::drawSelectionBox( g_overlay.get(), g_textOverlay.get(),
|
||||||
color, px, g_style );
|
item->ViewBBox(), peer.name, color, px,
|
||||||
|
g_style );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( peer.hasCursor )
|
if( peer.hasCursor )
|
||||||
pcbjam_presence::drawCursor( g_overlay.get(), peer.cursor, peer.name, color, px,
|
pcbjam_presence::drawCursor( g_overlay.get(), g_textOverlay.get(), peer.cursor,
|
||||||
g_style );
|
peer.name, color, px, g_style );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment pin dots (0005), drawn last so they sit above selection outlines.
|
// Comment pin dots (0005), drawn last so they sit above selection outlines.
|
||||||
|
|
@ -838,6 +851,7 @@ void redrawOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
view->Update( g_overlay.get() );
|
view->Update( g_overlay.get() );
|
||||||
|
view->Update( g_textOverlay.get() );
|
||||||
fr->GetCanvas()->ForceRefresh();
|
fr->GetCanvas()->ForceRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1126,6 +1126,9 @@ std::vector<PIN> g_pins;
|
||||||
// — see collab_presence_style.h; live-patched by kicadCollabSetStyle (tuner).
|
// — see collab_presence_style.h; live-patched by kicadCollabSetStyle (tuner).
|
||||||
pcbjam_presence::STYLE g_style;
|
pcbjam_presence::STYLE g_style;
|
||||||
std::shared_ptr<KIGFX::VIEW_OVERLAY> g_overlay;
|
std::shared_ptr<KIGFX::VIEW_OVERLAY> g_overlay;
|
||||||
|
// Labels render from their own overlay at the nearest depth (chip rects
|
||||||
|
// would otherwise erase same-depth text) — see PRESENCE_TEXT_OVERLAY.
|
||||||
|
std::shared_ptr<KIGFX::VIEW_OVERLAY> g_textOverlay;
|
||||||
bool g_started = false;
|
bool g_started = false;
|
||||||
bool g_redrawScheduled = false;
|
bool g_redrawScheduled = false;
|
||||||
bool g_selCheckScheduled = false;
|
bool g_selCheckScheduled = false;
|
||||||
|
|
@ -1207,9 +1210,18 @@ void redrawOverlay()
|
||||||
KIGFX::VIEW* view = fr->GetCanvas()->GetView();
|
KIGFX::VIEW* view = fr->GetCanvas()->GetView();
|
||||||
|
|
||||||
if( !g_overlay )
|
if( !g_overlay )
|
||||||
g_overlay = pcbjam_presence::makePresenceOverlay( view );
|
{
|
||||||
|
g_overlay = view->MakeOverlay();
|
||||||
|
// Shapes sit one depth unit BELOW the text overlay (fork
|
||||||
|
// SetDepthOffset) so labels always render on top of chips/boxes.
|
||||||
|
g_overlay->SetDepthOffset( pcbjam_presence::PRESENCE_SHAPES_DEPTH_OFFSET );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !g_textOverlay )
|
||||||
|
g_textOverlay = pcbjam_presence::makePresenceTextOverlay( view );
|
||||||
|
|
||||||
g_overlay->Clear();
|
g_overlay->Clear();
|
||||||
|
g_textOverlay->Clear();
|
||||||
|
|
||||||
BOARD* board = fr->GetBoard();
|
BOARD* board = fr->GetBoard();
|
||||||
// Screen-constant sizing: px → world units, so cursors/outline widths don't
|
// Screen-constant sizing: px → world units, so cursors/outline widths don't
|
||||||
|
|
@ -1265,13 +1277,14 @@ void redrawOverlay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pcbjam_presence::drawSelectionBox( g_overlay.get(), item->ViewBBox(), peer.name,
|
pcbjam_presence::drawSelectionBox( g_overlay.get(), g_textOverlay.get(),
|
||||||
color, px, g_style, &outline );
|
item->ViewBBox(), peer.name, color, px,
|
||||||
|
g_style, &outline );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( peer.hasCursor )
|
if( peer.hasCursor )
|
||||||
pcbjam_presence::drawCursor( g_overlay.get(), peer.cursor, peer.name, color, px,
|
pcbjam_presence::drawCursor( g_overlay.get(), g_textOverlay.get(), peer.cursor,
|
||||||
g_style );
|
peer.name, color, px, g_style );
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment pin dots (0005), drawn last so they sit above selection outlines.
|
// Comment pin dots (0005), drawn last so they sit above selection outlines.
|
||||||
|
|
@ -1282,6 +1295,7 @@ void redrawOverlay()
|
||||||
}
|
}
|
||||||
|
|
||||||
view->Update( g_overlay.get() );
|
view->Update( g_overlay.get() );
|
||||||
|
view->Update( g_textOverlay.get() );
|
||||||
// The canvas repaints on its own only with focus/input — force it, exactly as
|
// The canvas repaints on its own only with focus/input — force it, exactly as
|
||||||
// the cross-probe flash does.
|
// the cross-probe flash does.
|
||||||
fr->GetCanvas()->ForceRefresh();
|
fr->GetCanvas()->ForceRefresh();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue