comments-ux: figma bubble pins, floating panel, seen/reactions/mentions UI, theme follow (0001 A–E + 0002)
- GAL pin = one closed polygon: round body, squared-off bottom-left corner ON the anchor; PIN gains unread (accent ring); tuner knobs; shipped defaults r9/ring4/alpha.9. DOM hit/highlight sized+offset from a LIVE pin-geometry radius store the tuner feeds. - Floating comments panel: draggable (shared useDraggablePanel with always-onscreen restore; overlay FAB retrofitted), collapsible to header, header carries add/show-hide/mark-all; unread badges (rose on mention). - Reactions (emoji-mart lazy, quick-row) + @-mention autocomplete (MentionInput; backend roster with presence/author fallback). - Theme: ?theme= > storage > OS, no-flash boot, toggles (HomePage + overlay View row), boot-seeded pcbjam-dark schematic colors + kicadSetColorTheme / kicadSetDarkChrome bridges (canvas + wx chrome live flip), light/dark variants across all overlay surfaces. - e2e: panel/seen/reactions/mentions/theme specs + resize-spec geometry; bumps pcbjam-shared (flat-key seen/reactions + listCollaborators) and wxwidgets (dark chrome) pointers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HLwn1toiNKi1MgxGKnZTes
This commit is contained in:
parent
d3200e464c
commit
21a96b0440
39 changed files with 2436 additions and 232 deletions
|
|
@ -66,6 +66,7 @@ struct PIN
|
|||
VECTOR2D pos; // world coords (IU)
|
||||
KIGFX::COLOR4D color;
|
||||
bool resolved = false;
|
||||
bool unread = false; // comments-ux 0001 C: accent ring
|
||||
};
|
||||
|
||||
inline long long nowMs()
|
||||
|
|
@ -384,7 +385,7 @@ struct CORE
|
|||
for( const PIN& pin : pins )
|
||||
{
|
||||
KIGFX::COLOR4D color = peerColor( style, pin.name, pin.color );
|
||||
drawPin( chipOverlay.get(), pin.pos, color, pin.resolved, px, style );
|
||||
drawPin( chipOverlay.get(), pin.pos, color, pin.resolved, pin.unread, px, style );
|
||||
}
|
||||
|
||||
view->Update( overlay.get() );
|
||||
|
|
@ -532,9 +533,9 @@ struct CORE
|
|||
scheduleRedraw();
|
||||
}
|
||||
|
||||
/** kicadCollabSetPins (0005): comment pin dots — `{pins:[{id,name,x,y,
|
||||
* color,resolved}]}`, world IU coords resolved by the TS side from the
|
||||
* ydoc anchors. Snapshot semantics like setRemote. */
|
||||
/** kicadCollabSetPins (0005): comment pins — `{pins:[{id,name,x,y,
|
||||
* color,resolved,unread}]}`, world IU coords resolved by the TS side
|
||||
* from the ydoc anchors. Snapshot semantics like setRemote. */
|
||||
void setPins( const std::string& aJson )
|
||||
{
|
||||
json j = json::parse( aJson, nullptr, /*allow_exceptions*/ false );
|
||||
|
|
@ -552,6 +553,7 @@ struct CORE
|
|||
pin.pos = VECTOR2D( p.value( "x", 0.0 ), p.value( "y", 0.0 ) );
|
||||
pin.color = parsePeerColor( p.value( "color", "" ) );
|
||||
pin.resolved = p.value( "resolved", false );
|
||||
pin.unread = p.value( "unread", false );
|
||||
parsed.push_back( std::move( pin ) );
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
#include <view/view_overlay.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
|
@ -75,12 +76,20 @@ struct STYLE
|
|||
std::string fixedColor;
|
||||
std::vector<std::string> palette;
|
||||
|
||||
// ── comment pin dots ──────────────────────────────────────────────────
|
||||
// ── comment pins ──────────────────────────────────────────────────────
|
||||
// 0 circle dot · 1 figma-style bubble: a round body whose bottom-left
|
||||
// corner is squared off — the SHARP CORNER is the anchored point
|
||||
// (comments-ux 0001 A, reshaped per user feedback 2026-07-24).
|
||||
// Shipped look picked with the tuner 2026-07-24: r9 body, 4px ring,
|
||||
// slightly translucent ring+fill. pinRadiusPx is MIRRORED in the
|
||||
// standalone's pin-geometry.ts (DEFAULT_PIN_RADIUS_PX) — change together.
|
||||
int pinShape = 1;
|
||||
double pinRadiusPx = 9.0;
|
||||
double pinRingPx = 3.0;
|
||||
double pinRingAlpha = 1.0;
|
||||
double pinFillAlpha = 1.0;
|
||||
double pinRingPx = 4.0;
|
||||
double pinRingAlpha = 0.9;
|
||||
double pinFillAlpha = 0.9;
|
||||
double pinResolvedAlpha = 0.3;
|
||||
std::string pinUnreadRingColor = "#ffb020"; // unread accent ring ("" = plain white)
|
||||
|
||||
// ── cross-app "ghost" selection (0006) ────────────────────────────────
|
||||
// A peer's selection in the OTHER editor (eeschema symbol ⇄ pcbnew
|
||||
|
|
@ -159,11 +168,13 @@ inline void patchStyle( STYLE& aStyle, const json& j )
|
|||
}
|
||||
}
|
||||
|
||||
aStyle.pinShape = j.value( "pinShape", aStyle.pinShape );
|
||||
aStyle.pinRadiusPx = j.value( "pinRadiusPx", aStyle.pinRadiusPx );
|
||||
aStyle.pinRingPx = j.value( "pinRingPx", aStyle.pinRingPx );
|
||||
aStyle.pinRingAlpha = j.value( "pinRingAlpha", aStyle.pinRingAlpha );
|
||||
aStyle.pinFillAlpha = j.value( "pinFillAlpha", aStyle.pinFillAlpha );
|
||||
aStyle.pinResolvedAlpha = j.value( "pinResolvedAlpha", aStyle.pinResolvedAlpha );
|
||||
aStyle.pinUnreadRingColor = j.value( "pinUnreadRingColor", aStyle.pinUnreadRingColor );
|
||||
|
||||
aStyle.xselAlphaScale = j.value( "xselAlphaScale", aStyle.xselAlphaScale );
|
||||
}
|
||||
|
|
@ -494,21 +505,60 @@ inline void drawCursor( KIGFX::VIEW_OVERLAY* aOv, KIGFX::VIEW_OVERLAY* aChipOv,
|
|||
}
|
||||
}
|
||||
|
||||
/** 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). */
|
||||
/** Comment pin. Draw onto the CHIPS overlay: at the shapes depth an
|
||||
* earlier-painted selection fill would reject the pin's fragments (the 0005
|
||||
* "drawn last sits above" comment had it backwards — later fragments LOSE).
|
||||
* Bubble shape (comments-ux 0001 A, figma-style): a round body whose
|
||||
* bottom-left corner is squared off; `aPos` is that SHARP CORNER = the
|
||||
* anchored world point, the body center sits at aPos + (r, -r) (screen
|
||||
* up-right; KiCad IU y grows downward). One closed polygon — three sampled
|
||||
* round corners + the sharp one — so the stroke traces the outline exactly
|
||||
* with no depth-order seams. */
|
||||
inline void drawPin( KIGFX::VIEW_OVERLAY* aOv, const VECTOR2D& aPos, const KIGFX::COLOR4D& aColor,
|
||||
bool aResolved, double aPx, const STYLE& aS )
|
||||
bool aResolved, bool aUnread, double aPx, const STYLE& aS )
|
||||
{
|
||||
double fillAlpha = aResolved ? aS.pinResolvedAlpha : aS.pinFillAlpha;
|
||||
double ringAlpha = aResolved ? aS.pinRingAlpha * 0.4 : aS.pinRingAlpha;
|
||||
|
||||
KIGFX::COLOR4D ring( 1, 1, 1, ringAlpha );
|
||||
|
||||
if( aUnread && !aResolved && !aS.pinUnreadRingColor.empty() )
|
||||
ring = parseHexColor( aS.pinUnreadRingColor, ring ).WithAlpha( ringAlpha );
|
||||
|
||||
aOv->SetIsStroke( true );
|
||||
aOv->SetIsFill( true );
|
||||
aOv->SetFillColor( aColor.WithAlpha( fillAlpha ) );
|
||||
aOv->SetStrokeColor( KIGFX::COLOR4D( 1, 1, 1, ringAlpha ) );
|
||||
aOv->SetStrokeColor( ring );
|
||||
aOv->SetLineWidth( aS.pinRingPx * aPx );
|
||||
aOv->Circle( aPos, aS.pinRadiusPx * aPx );
|
||||
|
||||
if( aS.pinShape == 0 )
|
||||
{
|
||||
aOv->Circle( aPos, aS.pinRadiusPx * aPx );
|
||||
return;
|
||||
}
|
||||
|
||||
double r = aS.pinRadiusPx * aPx;
|
||||
VECTOR2D c = aPos + VECTOR2D( r, -r );
|
||||
|
||||
constexpr int SEGS = 24; // sampling of the 270° round part
|
||||
VECTOR2D pts[SEGS + 3];
|
||||
int n = 0;
|
||||
|
||||
pts[n++] = aPos; // the sharp corner
|
||||
|
||||
for( int i = 0; i <= SEGS; i++ )
|
||||
{
|
||||
// South (90° in y-down coords) → east → north → west: the round part.
|
||||
double th = ( 90.0 - 270.0 * i / SEGS ) * M_PI / 180.0;
|
||||
pts[n++] = c + VECTOR2D( cos( th ) * r, sin( th ) * r );
|
||||
}
|
||||
|
||||
// Close the outline explicitly: the overlay strokes the point list as a
|
||||
// polyline, so without repeating the first point the west → sharp-corner
|
||||
// edge would have fill but no ring.
|
||||
pts[n++] = aPos;
|
||||
|
||||
aOv->Polygon( pts, n );
|
||||
}
|
||||
|
||||
} // namespace pcbjam_presence
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
#include "collab_common.h"
|
||||
#include "collab_presence_core.h"
|
||||
#include "collab_presence_style.h"
|
||||
#include "pcbjam_theme.h"
|
||||
#include "pcbjam_libs_reload.h"
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -1658,6 +1659,18 @@ void schCollabSetStyle( std::string aJson )
|
|||
presenceCore().setStyle( aJson );
|
||||
}
|
||||
|
||||
// JS → C++ (comments-ux 0002 F4): live color-theme switch (see pcbjam_theme.h).
|
||||
void schSetColorTheme( std::string aTheme )
|
||||
{
|
||||
pcbjam_theme::setColorTheme( schFrame(), aTheme );
|
||||
}
|
||||
|
||||
// Pre-main chrome appearance seed (called at onRuntimeInitialized).
|
||||
void schSetDarkChrome( bool aDark )
|
||||
{
|
||||
pcbjam_theme::setDarkChromeFlag( aDark );
|
||||
}
|
||||
|
||||
// Tuner helper: a VARIED demo-selection set for the current sheet — smallest +
|
||||
// largest symbol and two bundles of wires (net-ish), mirroring pcbnew's
|
||||
// pcbCollabTestDemoSet so the style preview shows the range of shapes.
|
||||
|
|
@ -2027,6 +2040,9 @@ EMSCRIPTEN_BINDINGS(eeschema) {
|
|||
// Follow-user (collab-presence 0008).
|
||||
function("kicadCollabFitViewport", &schCollabFitViewport);
|
||||
function("kicadCollabSetStyle", &schCollabSetStyle);
|
||||
// Live color-theme switch (comments-ux 0002 F4).
|
||||
function("kicadSetColorTheme", &schSetColorTheme);
|
||||
function("kicadSetDarkChrome", &schSetDarkChrome);
|
||||
function("kicadCollabTestListItems", &schCollabTestListItems);
|
||||
function("kicadCollabTestDemoSet", &schCollabTestDemoSet);
|
||||
function("kicadCollabGetViewport", &schCollabGetViewport);
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ void pcbCollabReleaseSelection( std::string aUuidsJson, std::string aHold
|
|||
std::string pcbCollabTestGetLocked();
|
||||
std::string pcbCollabTestSelectFirst();
|
||||
bool pcbCollabTestClearSelection();
|
||||
// Live color-theme switch (comments-ux 0002 F4).
|
||||
void pcbSetColorTheme( std::string aTheme );
|
||||
void pcbSetDarkChrome( bool aDark );
|
||||
|
||||
bool schEditorActive();
|
||||
int schLibsSymbolUsage( std::string aLibNickname, std::string aSymbolName );
|
||||
|
|
@ -101,6 +104,8 @@ 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 );
|
||||
// Live color-theme switch (comments-ux 0002 F4).
|
||||
void schSetColorTheme( std::string aTheme );
|
||||
std::string schCollabTestListItems( int aCount );
|
||||
std::string schCollabTestDemoSet();
|
||||
std::string schCollabGetViewport();
|
||||
|
|
@ -388,6 +393,21 @@ static void collabSetStyle( std::string aJson )
|
|||
pcbEditorActive() ? pcbCollabSetStyle( aJson ) : schCollabSetStyle( aJson );
|
||||
}
|
||||
|
||||
// Theme switch (comments-ux 0002 F4): BOTH editors, not just the active one —
|
||||
// a later frame switch (eeschema-switch-nav) must come up already themed.
|
||||
// Each side no-ops on a null frame.
|
||||
static void setColorTheme( std::string aTheme )
|
||||
{
|
||||
pcbSetColorTheme( aTheme );
|
||||
schSetColorTheme( aTheme );
|
||||
}
|
||||
|
||||
// The chrome flag is process-global — one call suffices.
|
||||
static void setDarkChrome( bool aDark )
|
||||
{
|
||||
pcbSetDarkChrome( aDark );
|
||||
}
|
||||
|
||||
static std::string collabTestListItems( int aCount )
|
||||
{
|
||||
return pcbEditorActive() ? pcbCollabTestListItems( aCount ) : schCollabTestListItems( aCount );
|
||||
|
|
@ -490,6 +510,9 @@ EMSCRIPTEN_BINDINGS(kicad_editor) {
|
|||
function("kicadCollabSetRemote", &collabSetRemote);
|
||||
function("kicadCollabSetPins", &collabSetPins);
|
||||
function("kicadCollabSetViewport", &collabSetViewport);
|
||||
// Live color-theme switch (comments-ux 0002 F4).
|
||||
function("kicadSetColorTheme", &setColorTheme);
|
||||
function("kicadSetDarkChrome", &setDarkChrome);
|
||||
// Follow-user (collab-presence 0008).
|
||||
function("kicadCollabFitViewport", &collabFitViewport);
|
||||
function("kicadCollabSetStyle", &collabSetStyle);
|
||||
|
|
|
|||
101
wasm/bindings/pcbjam_theme.h
Normal file
101
wasm/bindings/pcbjam_theme.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Live color-theme switch (comments-ux 0002 F4): kicadSetColorTheme(name)
|
||||
* re-points the frame's app settings `color_theme` and drives the frame's own
|
||||
* CommonSettingsChanged — the exact path the desktop Preferences dialog uses
|
||||
* to hot-apply a theme (SCH_BASE_FRAME / PCB_BASE_FRAME reload COLOR_SETTINGS
|
||||
* into the painter, recache the view, refresh). SETTINGS_MANAGER resolves
|
||||
* colors/<name>.json from the MEMFS config dir on demand, so the JSON only
|
||||
* has to exist (standalone boot seeding writes it — web/standalone
|
||||
* src/wasm/boot.ts). The changed app settings are saved back to MEMFS so a
|
||||
* same-session relaunch (warm pool) comes up already themed.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
|
||||
#include <eda_draw_frame.h>
|
||||
#include <pgm_base.h>
|
||||
#include <settings/app_settings.h>
|
||||
#include <settings/settings_manager.h>
|
||||
#include <string>
|
||||
#include <wx/event.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include "collab_common.h"
|
||||
|
||||
// wx wasm port chrome appearance (wxwidgets src/wasm/settings.cpp): the
|
||||
// system-colour table every widget paints from.
|
||||
extern "C" void wxWasmSetDarkAppearance( bool dark );
|
||||
extern "C" bool wxWasmGetDarkAppearance();
|
||||
|
||||
namespace pcbjam_theme {
|
||||
|
||||
/** Set the chrome appearance FLAG only — no widget traffic, no fiber. Safe
|
||||
* from the browser main thread at any point (it writes one bool in shared
|
||||
* wasm memory); the embedder calls it at onRuntimeInitialized, BEFORE main()
|
||||
* spawns on the KiCad pthread, so the first widget paint is already themed.
|
||||
* (Module.ENV proved unreliable for this: the pthread builds its environ
|
||||
* from its own worker's ENV, not the main runtime's.) */
|
||||
inline void setDarkChromeFlag( bool aDark )
|
||||
{
|
||||
wxWasmSetDarkAppearance( aDark );
|
||||
}
|
||||
|
||||
/** Flip the wx CHROME (panels/toolbars/dialogs — the system-colour table) and
|
||||
* broadcast the change so every live window repaints:
|
||||
* wxWindowBase::OnSysColourChanged recurses to children, and
|
||||
* EDA_BASE_FRAME's handler additionally re-themes icons and rebuilds
|
||||
* toolbars/menubar. No-op when the appearance didn't change (the merged
|
||||
* image calls the theme entry once per editor). No DOM probing here — this
|
||||
* runs on the KiCad pthread, which has no `document`. */
|
||||
inline void syncChromeAppearance( bool aDark )
|
||||
{
|
||||
if( aDark == wxWasmGetDarkAppearance() )
|
||||
return;
|
||||
|
||||
wxWasmSetDarkAppearance( aDark );
|
||||
|
||||
for( wxWindowList::const_iterator it = wxTopLevelWindows.begin();
|
||||
it != wxTopLevelWindows.end(); ++it )
|
||||
{
|
||||
wxWindow* tlw = *it;
|
||||
wxSysColourChangedEvent evt;
|
||||
evt.SetEventObject( tlw );
|
||||
tlw->GetEventHandler()->ProcessEvent( evt );
|
||||
tlw->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/** Apply `aTheme` ("pcbjam-dark", "_builtin_default", …) to one frame. Runs
|
||||
* on the frame's fiber: CommonSettingsChanged reaches tool/view internals
|
||||
* that must not run from a bare JS callback. Null frame no-ops (the merged
|
||||
* dispatcher calls every editor, open or not). */
|
||||
inline void setColorTheme( EDA_DRAW_FRAME* aFrame, const std::string& aTheme )
|
||||
{
|
||||
if( !aFrame )
|
||||
return;
|
||||
|
||||
pcbjam_collab::runOnFiber( aFrame, [aFrame, aTheme]() {
|
||||
if( APP_SETTINGS_BASE* cfg = aFrame->config() )
|
||||
{
|
||||
cfg->m_ColorTheme = wxString::FromUTF8( aTheme.c_str() );
|
||||
// Persist now — wasm sessions never exit cleanly, so the normal
|
||||
// save-on-close path would lose the choice.
|
||||
Pgm().GetSettingsManager().Save( cfg );
|
||||
}
|
||||
|
||||
// wx chrome first (panels/toolbars), then the GAL canvas colors. The
|
||||
// shell only ever sends our dark theme name or the builtin default,
|
||||
// so the chrome appearance rides on that distinction.
|
||||
syncChromeAppearance( aTheme != "_builtin_default" );
|
||||
|
||||
// 0 flags: no env/text vars changed; the frame's override chain still
|
||||
// unconditionally reloads colors and recaches the view.
|
||||
aFrame->CommonSettingsChanged( 0 );
|
||||
} );
|
||||
}
|
||||
|
||||
} // namespace pcbjam_theme
|
||||
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
#include "collab_common.h"
|
||||
#include "collab_presence_core.h"
|
||||
#include "collab_presence_style.h"
|
||||
#include "pcbjam_theme.h"
|
||||
#include "pcbjam_libs_reload.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
|
@ -1603,6 +1604,18 @@ void pcbCollabSetStyle( std::string aJson )
|
|||
presenceCore().setStyle( aJson );
|
||||
}
|
||||
|
||||
// JS → C++ (comments-ux 0002 F4): live color-theme switch (see pcbjam_theme.h).
|
||||
void pcbSetColorTheme( std::string aTheme )
|
||||
{
|
||||
pcbjam_theme::setColorTheme( pcbFrame(), aTheme );
|
||||
}
|
||||
|
||||
// Pre-main chrome appearance seed (called at onRuntimeInitialized).
|
||||
void pcbSetDarkChrome( bool aDark )
|
||||
{
|
||||
pcbjam_theme::setDarkChromeFlag( aDark );
|
||||
}
|
||||
|
||||
// Tuner helper: a VARIED demo-selection set — labeled uuid groups (smallest +
|
||||
// largest footprint, the two busiest nets' track segments) so the style
|
||||
// preview shows the real range of shapes instead of two overlapping items.
|
||||
|
|
@ -2351,6 +2364,9 @@ EMSCRIPTEN_BINDINGS(pcbnew) {
|
|||
// Follow-user (collab-presence 0008).
|
||||
function("kicadCollabFitViewport", &pcbCollabFitViewport);
|
||||
function("kicadCollabSetStyle", &pcbCollabSetStyle);
|
||||
// Live color-theme switch (comments-ux 0002 F4).
|
||||
function("kicadSetColorTheme", &pcbSetColorTheme);
|
||||
function("kicadSetDarkChrome", &pcbSetDarkChrome);
|
||||
function("kicadCollabTestListItems", &pcbCollabTestListItems);
|
||||
function("kicadCollabTestDemoSet", &pcbCollabTestDemoSet);
|
||||
function("kicadCollabGetViewport", &pcbCollabGetViewport);
|
||||
|
|
|
|||
Loading…
Reference in a new issue