fix(theme): stop the boot theme re-send from resurrecting hidden chrome

Since comments-ux 0002, WasmTool re-sends the shell theme through
kicadSetColorTheme on every boot; setColorTheme ran CommonSettingsChanged
unconditionally, which recreates the menubar/toolbars — and they come back
SHOWN, undoing the kicadSetChrome(false) the read-only viewer and mobile
canvas-only mode applied moments earlier (CI: read-only-editor +
mobile-editor "9 visible menu titles").

Two-part fix in pcbjam_theme:
- early-out when the requested theme AND the wx dark-chrome flag are
  already applied — the every-boot re-send (still needed for warm
  relaunches with stale MEMFS settings) becomes a true no-op;
- after a REAL apply, re-assert the hidden chrome via a hook the merged
  image installs (kicadSetChrome(false) when the snapshot says hidden),
  queued with CallAfter from inside the fiber body so FIFO lands it
  behind the CallAfter-deferred ReCreateMenuBar. Covers viewers/mobile
  users toggling dark mode mid-session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y32etYBCKV6t1qDoLoGmgF
This commit is contained in:
Gergő Törcsvári 2026-07-27 18:48:25 +02:00
commit b0e84ff849
No known key found for this signature in database
GPG key ID: 8E75F2CDE64E5322
2 changed files with 52 additions and 6 deletions

View file

@ -83,6 +83,14 @@ bool pcbCollabTestClearSelection();
void pcbSetColorTheme( std::string aTheme );
void pcbSetDarkChrome( bool aDark );
// Post-theme-apply hook shared with pcbjam_theme.h — identical inline-variable
// definition instead of including that header (this TU stays header-light);
// keep the two declarations in sync.
namespace pcbjam_theme
{
inline void ( *g_afterThemeApplied )() = nullptr;
}
bool schEditorActive();
int schLibsSymbolUsage( std::string aLibNickname, std::string aSymbolName );
void schCollabApply( std::string aJson );
@ -398,6 +406,15 @@ static void collabSetStyle( std::string aJson )
// Each side no-ops on a null frame.
static void setColorTheme( std::string aTheme )
{
// A real theme apply rebuilds the menubar/toolbars, which come back
// SHOWN — re-hide them when the chrome is supposed to be hidden
// (read-only viewer / mobile canvas-only). Installed here, not in
// pcbjam_theme.h, because the chrome snapshot is merged-image state.
pcbjam_theme::g_afterThemeApplied = []() {
if( s_chromeSnap.valid )
kicadSetChrome( false );
};
pcbSetColorTheme( aTheme );
schSetColorTheme( aTheme );
}

View file

@ -31,6 +31,17 @@ extern "C" bool wxWasmGetDarkAppearance();
namespace pcbjam_theme {
/** Merged-image hook (installed by kicad_editor_embind.cpp): re-assert the
* canvas-only chrome state after a theme apply. CommonSettingsChanged
* recreates the menubar (CallAfter-deferred, eda_base_frame.cpp) and the
* toolbars, and they come back SHOWN without this, a theme apply
* resurrects the chrome kicadSetChrome(false) hid (read-only viewer /
* mobile canvas-only). Inline variable: the dispatcher TU re-declares the
* identical definition instead of including this (deliberately
* header-light) keep the two in sync. Null in the standalone bundles,
* which have no chrome API. */
inline void ( *g_afterThemeApplied )() = nullptr;
/** 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()
@ -77,22 +88,40 @@ inline void setColorTheme( EDA_DRAW_FRAME* aFrame, const std::string& aTheme )
return;
pcbjam_collab::runOnFiber( aFrame, [aFrame, aTheme]() {
if( APP_SETTINGS_BASE* cfg = aFrame->config() )
// The shell only ever sends our dark theme name or the builtin
// default, so the chrome appearance rides on that distinction.
const bool dark = aTheme != "_builtin_default";
const wxString theme = wxString::FromUTF8( aTheme.c_str() );
APP_SETTINGS_BASE* cfg = aFrame->config();
// The shell re-sends its theme on EVERY boot (a warm relaunch can
// have stale MEMFS settings from a HomePage theme switch) — skip the
// apply when nothing changes: CommonSettingsChanged below is not
// free, it rebuilds the menubar and toolbars.
if( cfg && cfg->m_ColorTheme == theme && dark == wxWasmGetDarkAppearance() )
return;
if( cfg )
{
cfg->m_ColorTheme = wxString::FromUTF8( aTheme.c_str() );
cfg->m_ColorTheme = theme;
// 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" );
// wx chrome first (panels/toolbars), then the GAL canvas colors.
syncChromeAppearance( dark );
// 0 flags: no env/text vars changed; the frame's override chain still
// unconditionally reloads colors and recaches the view.
aFrame->CommonSettingsChanged( 0 );
// Queued from INSIDE the fiber body, AFTER CommonSettingsChanged: the
// menubar rebuild it triggers is itself a CallAfter on this same
// handler, so FIFO puts the re-assert behind the rebuilt (shown)
// menubar.
if( void ( *hook )() = g_afterThemeApplied )
aFrame->CallAfter( hook );
} );
}