feat: light/dark theme support

Add a Theme preference (Automatic / Light / Dark). Automatic follows the
system org.gnome.desktop.interface color-scheme; the dark theme is
unchanged. Light styling lives in light.css, every rule scoped under a
.strata-theme-light class the panel toggles on its root box, loaded into the
St theme context by the extension. St's CSS engine has no custom properties,
so this specificity-based scoped override is how the palette is switched
without generating a stylesheet at runtime; switching is a single class
toggle off the ingest/render hot paths.

Also in this change:
- fix: the keyboard shortcut now hides the panel when it is already open.
  The binding lacked Shell.ActionMode.POPUP, so while the panel's modal grab
  was active the second press was swallowed and toggle() never ran.
- fix: the search placeholder ("Search...") is now legible in the light
  theme; it previously inherited a light-on-dark Shell color.
- pack: bundle light.css via --extra-source (gnome-extensions pack only
  auto-includes stylesheet.css), so packaged installs ship the light theme.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Eduard Tolosa 2026-05-26 14:49:08 -05:00
parent 42230470fc
commit 945ad61bf7
11 changed files with 328 additions and 9 deletions

View file

@ -76,6 +76,10 @@ export default class StrataExtension extends Extension {
this._settings.connect('changed::max-image-mb', () => { this._readSizeLimits(); this._pushConfig(); }),
];
// Load the light theme overrides into the Shell theme context. They stay
// inert until the panel toggles the `.strata-theme-light` class (panel.js).
this._loadThemeStylesheet();
// 1. Top-bar indicator icon.
this._addIndicator();
@ -119,6 +123,7 @@ export default class StrataExtension extends Extension {
this._panel = null;
this._indicator?.destroy();
this._indicator = null;
this._unloadThemeStylesheet();
this._stopDaemon();
this._proxy = null;
this._settings = null;
@ -566,11 +571,15 @@ export default class StrataExtension extends Extension {
_registerShortcut() {
// POPUP is included so the shortcut still fires while the panel's own
// modal grab is active (pushModal sets actionMode POPUP). Without it the
// second press is swallowed by the grab and toggle() never runs, so the
// panel could open but not close via the shortcut.
Main.wm.addKeybinding(
'keyboard-shortcut',
this._settings,
Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW | Shell.ActionMode.POPUP,
() => this._panel?.toggle()
);
}
@ -578,4 +587,41 @@ export default class StrataExtension extends Extension {
_unregisterShortcut() {
Main.wm.removeKeybinding('keyboard-shortcut');
}
/** Load light.css into the global St theme context. Scoped under
* `.strata-theme-light`, so it does nothing until the panel adds that
* class. Reloaded on theme-context changes (a Shell-theme switch drops
* dynamically loaded stylesheets). */
_loadThemeStylesheet() {
try {
const themeContext = St.ThemeContext.get_for_stage(global.stage);
this._lightCssFile = this.dir.get_child('light.css');
themeContext.get_theme().load_stylesheet(this._lightCssFile);
this._stThemeChangedId = themeContext.connect('changed', () => {
try {
themeContext.get_theme().load_stylesheet(this._lightCssFile);
} catch (e) {
console.error('[Strata] light.css reload failed:', e);
}
});
} catch (e) {
console.error('[Strata] Failed to load light.css:', e);
}
}
_unloadThemeStylesheet() {
try {
const themeContext = St.ThemeContext.get_for_stage(global.stage);
if (this._stThemeChangedId) {
themeContext.disconnect(this._stThemeChangedId);
this._stThemeChangedId = null;
}
if (this._lightCssFile)
themeContext.get_theme().unload_stylesheet(this._lightCssFile);
} catch (e) {
console.error('[Strata] Failed to unload light.css:', e);
}
this._lightCssFile = null;
}
}