From 5ff39cfa5e4d55eecc8eca59267fc63a585db263 Mon Sep 17 00:00:00 2001 From: "[JaviLendi]" Date: Thu, 21 May 2026 13:33:07 +0200 Subject: [PATCH 1/5] Remove example.html; add toolbar search focus Delete prettysumatra/example.html and add focusToolbarSearch helper to prettysumatra/webui/toolbar.html. The new function focuses and selects the search input with try/catch safety and returns a boolean; window.hybridToolbarFocusSearch calls it and retries via setTimeout as a fallback to ensure reliable focus in hybrid/native environments. --- prettysumatra/example.html | 151 ------------------------------- prettysumatra/webui/toolbar.html | 15 +++ 2 files changed, 15 insertions(+), 151 deletions(-) delete mode 100644 prettysumatra/example.html diff --git a/prettysumatra/example.html b/prettysumatra/example.html deleted file mode 100644 index ffa4316d809a..000000000000 --- a/prettysumatra/example.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - Editor con Estilo Heroicons - - - - - -
- - - - - - - - - - - - - - - - -
- - - \ No newline at end of file diff --git a/prettysumatra/webui/toolbar.html b/prettysumatra/webui/toolbar.html index 55131249350e..38be42ff1c78 100644 --- a/prettysumatra/webui/toolbar.html +++ b/prettysumatra/webui/toolbar.html @@ -1900,6 +1900,21 @@ document.getElementById('searchNext')?.addEventListener('click', () => { if(!isToolbarUnlocked) runSearch('forward')}); searchInput?.addEventListener('keydown', (e) => { if(e.key==='Enter' && !isToolbarUnlocked) runSearch('forward'); }); +function focusToolbarSearch() { + if (!searchInput) return false; + try { + searchInput.focus(); + if (typeof searchInput.select === 'function') searchInput.select(); + return document.activeElement === searchInput; + } catch (e) { + return false; + } +} +window.hybridToolbarFocusSearch = function() { + if (focusToolbarSearch()) return; + setTimeout(() => { focusToolbarSearch(); }, 0); +}; + if(themeSwitch) { themeSwitch.querySelectorAll('.mts-btn').forEach(btn => { btn.addEventListener('click', () => { From 670db46ebbd6165a9368caa136eb5fc870dc5ffa Mon Sep 17 00:00:00 2001 From: "[JaviLendi]" Date: Thu, 21 May 2026 16:17:41 +0200 Subject: [PATCH 2/5] Inject full theme payload into web UI Send a structured theme payload from C++ to the web UI and apply it client-side. Added HomePageThemeJs() in BridgeDispatcher to build a JS payload with canvas/panel/panel2/stroke/text/muted/btn/accent and appDark, and evaluate it on home page ready and on theme toggles. Home page HTML now defines new CSS primitives and color-derived variables, adds applyHomePageTheme(themePayload) to apply dynamic colors (with legacy applyTheme kept for boolean dark mode), and immediately applies any pre-injected payload. Toolbar mapping updated (hybridToolbarApplyTheme) to set appropriate CSS vars. Also changed PrettyBorderColor() to use AccentColor(..., 40) and removed the old dark-detection helper. These changes allow precise, per-color theming of the home and toolbar UIs from the native side. --- prettysumatra/webui/home.html | 72 ++++++++++++++++++-------- prettysumatra/webui/toolbar.html | 10 +++- src/Theme.cpp | 2 +- src/prettysumatra/BridgeDispatcher.cpp | 45 +++++++++++----- 4 files changed, 92 insertions(+), 37 deletions(-) diff --git a/prettysumatra/webui/home.html b/prettysumatra/webui/home.html index dfdf927d0778..1f7efe240ceb 100644 --- a/prettysumatra/webui/home.html +++ b/prettysumatra/webui/home.html @@ -72,30 +72,39 @@ - +
PrettySumatraPDF
@@ -247,9 +256,32 @@ renderRecentFiles(window._recentFiles); }; +function hexToRgb(hex) { + if (!hex) return null; + const clean = String(hex).trim().replace(/^#/, ''); + if (clean.length !== 6) return null; + const value = parseInt(clean, 16); + if (Number.isNaN(value)) return null; + return { + r: (value >> 16) & 255, + g: (value >> 8) & 255, + b: value & 255, + }; +} + +function rgbaFromHex(hex, alpha) { + const rgb = hexToRgb(hex); + if (!rgb) return ''; + return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})`; +} + window.applyHomePageTheme = function(themePayload) { if (!themePayload) return; const root = document.documentElement; + const accent = themePayload.accent || themePayload.brandPrimary || '#eab308'; + const brandPrimary = themePayload.brandPrimary || accent; + const brandGlow = themePayload.brandGlow || brandPrimary; + const shadow = themePayload.shadow || '#000000'; // Apply dynamic colors from theme payload (all except brand, which stays stable) if (themePayload.canvas) root.style.setProperty('--canvas', themePayload.canvas); if (themePayload.panel) root.style.setProperty('--panel', themePayload.panel); @@ -258,7 +290,13 @@ if (themePayload.text) root.style.setProperty('--text', themePayload.text); if (themePayload.muted) root.style.setProperty('--muted', themePayload.muted); if (themePayload.btn) root.style.setProperty('--btn', themePayload.btn); - if (themePayload.accent) root.style.setProperty('--accent', themePayload.accent); + root.style.setProperty('--accent', accent); + root.style.setProperty('--brand-primary', brandPrimary); + root.style.setProperty('--brand-glow', rgbaFromHex(brandGlow, themePayload.appDark ? 0.28 : 0.18) || brandGlow); + root.style.setProperty('--brand-gradient', `linear-gradient(135deg, ${brandPrimary} 0%, ${accent} 100%)`); + root.style.setProperty('--shadow-sm', `0 2px 8px ${rgbaFromHex(shadow, themePayload.appDark ? 0.3 : 0.08) || 'rgba(0,0,0,0.12)'}`); + root.style.setProperty('--shadow-md', `0 8px 32px ${rgbaFromHex(shadow, themePayload.appDark ? 0.4 : 0.12) || 'rgba(0,0,0,0.18)'}`); + root.style.setProperty('--shadow-hover', `0 12px 40px ${rgbaFromHex(brandPrimary, themePayload.appDark ? 0.24 : 0.15) || 'rgba(0,0,0,0.18)'}`); // Set data-theme attribute for media queries and dark mode detection root.dataset.theme = themePayload.appDark ? 'dark' : 'light'; }; @@ -271,6 +309,50 @@ if (window.external && typeof window.external.invoke === 'function') window.external.invoke(JSON.stringify({ type: 'command', name: 'homePageReady', payload: {}, ts: Date.now() })); // If theme payload was already injected via C++, apply it immediately if (window.__homePageThemePayload) { window.applyHomePageTheme(window.__homePageThemePayload); } + +// Hide toolbar buttons +(function() { + try { + // Signal via localStorage that toolbar buttons should be hidden + try { + localStorage.setItem('prettysumatra_hide_toolbar', 'true'); + } catch (e) { + // Silently fail if localStorage not available + } + + // Send command to C++ to hide toolbar buttons on homepage + if (window.external && typeof window.external.invoke === 'function') { + window.external.invoke(JSON.stringify({ type: 'command', name: 'hideToolbarButtons', payload: {}, ts: Date.now() })); + } + + // Try to find and call hideToolbarButtons from accessible frames/windows + setTimeout(() => { + if (window.parent && window.parent !== window) { + if (typeof window.parent.hideToolbarButtons === 'function') { + window.parent.hideToolbarButtons(); + } + } + // Also try window.top + if (window.top && window.top !== window && typeof window.top.hideToolbarButtons === 'function') { + window.top.hideToolbarButtons(); + } + + // Try to access all iframes in the document + const iframes = document.querySelectorAll('iframe'); + iframes.forEach(iframe => { + try { + if (iframe.contentWindow && typeof iframe.contentWindow.hideToolbarButtons === 'function') { + iframe.contentWindow.hideToolbarButtons(); + } + } catch (e) { + // Silently ignore cross-origin iframes + } + }); + }, 100); + } catch (err) { + // Silently fail if cross-origin + } +})(); + + + \ No newline at end of file diff --git a/src/HomePage.cpp b/src/HomePage.cpp index 5bebb7a9dc0e..f68e69c9f500 100644 --- a/src/HomePage.cpp +++ b/src/HomePage.cpp @@ -35,13 +35,14 @@ #include "AppSettings.h" #include "OverlayScrollbar.h" #include "DarkModeSubclass.h" - #include "wingui/WebView.h" #include #include #include #include #include "Toolbar.h" + +#include "prettysumatra/BridgeDispatcher.h" #pragma comment(lib, "shlwapi.lib") #define HOMEPAGE_HTML_PATH "..\\prettysumatra\\webui\\home.html" @@ -159,6 +160,9 @@ void HomePageShow(MainWindow* win) { SetWindowPos(hwndWV, nullptr, rc.x, rc.y, rc.dx, rc.dy, SWP_NOZORDER | SWP_SHOWWINDOW); win->homePageWebView->SetControllerVisible(true); win->homePageWebView->UpdateWebviewSize(); + + prettysumatra::bridge::SyncHybridToolbarButtonVisibility(win->hwndFrame, false); + prettysumatra::bridge::SyncHybridToolbarEditableAllowed(win->hwndFrame, false); } #ifndef ABOUT_USE_LESS_COLORS @@ -1172,6 +1176,8 @@ void HomePageHide(MainWindow* win) { return; } + prettysumatra::bridge::SyncHybridToolbarButtonVisibility(win->hwndFrame, true); + prettysumatra::bridge::SyncHybridToolbarEditableAllowed(win->hwndFrame, true); win->homePageWebView->SetControllerVisible(false); ShowWindow(win->homePageWebView->hwnd, SW_HIDE); } diff --git a/src/Settings.h b/src/Settings.h index 8af05881ce98..241c86ae0ab0 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -257,9 +257,24 @@ struct Theme { // control background color char* controlBackgroundColor; ParsedColor controlBackgroundColorParsed; + // toolbar background color + char* ToolbarBackgroundColor; + ParsedColor ToolbarBackgroundColorParsed; // link color char* linkColor; ParsedColor linkColorParsed; + // accent color used by the web UI + char* accentColor; + ParsedColor accentColorParsed; + // brand primary color used by the web UI + char* brandPrimaryColor; + ParsedColor brandPrimaryColorParsed; + // glow color used by the web UI + char* brandGlowColor; + ParsedColor brandGlowColorParsed; + // shadow base color used by the web UI + char* shadowColor; + ParsedColor shadowColorParsed; // should we colorize Windows controls and window areas bool colorizeControls; }; @@ -743,12 +758,18 @@ static const FieldInfo gThemeFields[] = { {offsetof(Theme, textColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, backgroundColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, controlBackgroundColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, ToolbarBackgroundColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, linkColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, accentColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, brandPrimaryColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, brandGlowColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, shadowColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, colorizeControls), SettingType::Bool, false}, }; static const StructInfo gThemeInfo = { - sizeof(Theme), 6, gThemeFields, - "Name\0TextColor\0BackgroundColor\0ControlBackgroundColor\0LinkColor\0ColorizeControls"}; + sizeof(Theme), 11, gThemeFields, + "Name\0TextColor\0BackgroundColor\0ControlBackgroundColor\0ToolbarBackgroundColor\0LinkColor\0AccentColor\0" + "BrandPrimaryColor\0BrandGlowColor\0ShadowColor\0ColorizeControls"}; static const FieldInfo gTabFileFields[] = { {offsetof(TabFile, path), SettingType::String, (intptr_t)""}, @@ -978,11 +999,16 @@ static const FieldInfo gTheme_1_Fields[] = { {offsetof(Theme, backgroundColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, controlBackgroundColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, linkColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, accentColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, brandPrimaryColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, brandGlowColor), SettingType::Color, (intptr_t)""}, + {offsetof(Theme, shadowColor), SettingType::Color, (intptr_t)""}, {offsetof(Theme, colorizeControls), SettingType::Bool, false}, }; static const StructInfo gTheme_1_Info = { - sizeof(Theme), 6, gTheme_1_Fields, - "Name\0TextColor\0BackgroundColor\0ControlBackgroundColor\0LinkColor\0ColorizeControls"}; + sizeof(Theme), 10, gTheme_1_Fields, + "Name\0TextColor\0BackgroundColor\0ControlBackgroundColor\0LinkColor\0AccentColor\0BrandPrimaryColor\0" + "BrandGlowColor\0ShadowColor\0ColorizeControls"}; static const FieldInfo gThemesFields[] = { {offsetof(Themes, themes), SettingType::Array, (intptr_t)&gTheme_1_Info}, diff --git a/src/SumatraPDF.cpp b/src/SumatraPDF.cpp index 3b18c9f95705..870390eae83c 100644 --- a/src/SumatraPDF.cpp +++ b/src/SumatraPDF.cpp @@ -120,7 +120,7 @@ struct ToolbarHeightBreakpoint { }; static const ToolbarHeightBreakpoint kToolbarHeightBreakpoints[] = { - {1700, 56}, {1300, 56}, {1000, 52}, {650, 44}, {0, 44}, // default fallback + {1700, 57}, {1300, 57}, {1000, 52}, {650, 44}, {0, 44}, // default fallback }; static int GetHybridToolbarHeight(HWND hwndFrame) { diff --git a/src/Theme.cpp b/src/Theme.cpp index be0112425cea..96370a54f961 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -41,99 +41,147 @@ constexpr COLORREF kRedColor = RgbToCOLORREF(0xff0000); static const char* themesTxt = R"(Themes [ [ - Name = Light - TextColor = #000000 - BackgroundColor = #f2f2f2 + Name = Sumatra Light + TextColor = #1f2937 + BackgroundColor = #f4f6f8 ControlBackgroundColor = #ffffff LinkColor = #eab308 - ColorizeControls = false + AccentColor = #eab308 + BrandPrimaryColor = #facc15 + BrandGlowColor = #fde68a + ShadowColor = #5b4210 + ColorizeControls = true ] [ - Name = Dark from 3.5 - TextColor = #bac9d0 - BackgroundColor = #263238 - ControlBackgroundColor = #263238 - LinkColor = #facc15 + Name = Sumatra Dark + TextColor = #f8fafc + BackgroundColor = #17130a + ControlBackgroundColor = #3a2901 + LinkColor = #eab308 + AccentColor = #eab308 + BrandPrimaryColor = #facc15 + BrandGlowColor = #fde68a + ShadowColor = #120b02 ColorizeControls = true ] [ - Name = Darker - TextColor = #c3c3c6 - BackgroundColor = #2d2d30 - ControlBackgroundColor = #2d2d30 - LinkColor = #9999a0 + Name = Modern Slate Light + TextColor = #243041 + BackgroundColor = #f4f6f8 + ControlBackgroundColor = #e0f0ee + LinkColor = #0f766e + AccentColor = #0f766e + BrandPrimaryColor = #14b8a6 + BrandGlowColor = #99f6e4 + ShadowColor = #1e293b ColorizeControls = true ] [ - Name = Dark - TextColor = #F9FAFB - BackgroundColor = #000000 - ControlBackgroundColor = #000000 - LinkColor = #6B7280 + Name = Modern Slate Dark + TextColor = #dbe4ee + BackgroundColor = #0f172a + ControlBackgroundColor = #05557a + LinkColor = #38bdf8 + AccentColor = #38bdf8 + BrandPrimaryColor = #0ea5e9 + BrandGlowColor = #7dd3fc + ShadowColor = #020617 ColorizeControls = true ] [ - Name = Dark background Bright text - TextColor = #ffffff - BackgroundColor = #2d2d30 - ControlBackgroundColor = #2d2d30 - LinkColor = #9999a0 + Name = Modern Blue Light + TextColor = #1e293b + BackgroundColor = #f8fafc + ControlBackgroundColor = #daebff + LinkColor = #0f62fe + AccentColor = #0f62fe + BrandPrimaryColor = #2563eb + BrandGlowColor = #93c5fd + ShadowColor = #1e293b ColorizeControls = true ] [ - Name = Solarized Light - TextColor = #212323 - BackgroundColor = #fdf6e3 - ControlBackgroundColor = #eee8d5 - LinkColor = #9999a0 + Name = Modern Blue Dark + TextColor = #e2e8f0 + BackgroundColor = #0b1120 + ControlBackgroundColor = #1c3d74 + LinkColor = #60a5fa + AccentColor = #60a5fa + BrandPrimaryColor = #3b82f6 + BrandGlowColor = #93c5fd + ShadowColor = #020617 ColorizeControls = true ] [ - Name = Solarized Dark - TextColor = #839496 - BackgroundColor = #002b36 - ControlBackgroundColor = #073642 - LinkColor = #268bd2 + Name = Modern Green Light + TextColor = #1f2937 + BackgroundColor = #f4f6f8 + ControlBackgroundColor = #d1fae5 + LinkColor = #16a34a + AccentColor = #16a34a + BrandPrimaryColor = #22c55e + BrandGlowColor = #86efac + ShadowColor = #1f2937 ColorizeControls = true ] [ - Name = Dracula - TextColor = #f8f8f2 - BackgroundColor = #282a36 - ControlBackgroundColor = #44475a - LinkColor = #8be9fd + Name = Modern Green Dark + TextColor = #e5f3ea + BackgroundColor = #10251b + ControlBackgroundColor = #0f5529 + LinkColor = #4ade80 + AccentColor = #4ade80 + BrandPrimaryColor = #22c55e + BrandGlowColor = #86efac + ShadowColor = #052e16 ColorizeControls = true ] [ - Name = Nebula - TextColor = #CBE3E7 - BackgroundColor = #100E23 - ControlBackgroundColor = #1E1C31 - LinkColor = #91DDFF + Name = Modern Purple Light + TextColor = #29223a + BackgroundColor = #f4f6f8 + ControlBackgroundColor = #f3e8ff + LinkColor = #7c3aed + AccentColor = #7c3aed + BrandPrimaryColor = #8b5cf6 + BrandGlowColor = #c4b5fd + ShadowColor = #29223a ColorizeControls = true ] [ - Name = Greeny - TextColor = #FDD085 - BackgroundColor = #4F6232 - ControlBackgroundColor = #1E3304 - LinkColor = #A2E53B + Name = Modern Purple Dark + TextColor = #ede9fe + BackgroundColor = #1c122b + ControlBackgroundColor = rgb(78, 69, 88) + LinkColor = #c084fc + AccentColor = #c084fc + BrandPrimaryColor = #a855f7 + BrandGlowColor = #ddd6fe + ShadowColor = #15051f ColorizeControls = true ] [ - Name = Choco - TextColor = #D7AD62 - BackgroundColor = #2A1104 - ControlBackgroundColor = #172736 - LinkColor = #E8CD12 + Name = Modern Amber Light + TextColor = #31251a + BackgroundColor = #f4f6f8 + ControlBackgroundColor = #fff7ed + LinkColor = #d97706 + AccentColor = #d97706 + BrandPrimaryColor = #f59e0b + BrandGlowColor = #fde68a + ShadowColor = #31251a ColorizeControls = true ] [ - Name = Purpy - TextColor = #E2C3C3 - BackgroundColor = #20222A - ControlBackgroundColor = #1E0126 - LinkColor = #EFF0B8 + Name = Modern Amber Dark + TextColor = #f9ede1 + BackgroundColor = #23160d + ControlBackgroundColor = #634106 + LinkColor = #fbbf24 + AccentColor = #fbbf24 + BrandPrimaryColor = #f59e0b + BrandGlowColor = #fde68a + ShadowColor = #120b02 ColorizeControls = true ] ] @@ -152,6 +200,59 @@ static Theme* gCurrentTheme = nullptr; static Theme* gThemeLight = nullptr; static Themes* gParsedThemes = nullptr; +static bool IsLightThemeVariantName(const char* name) { + if (str::IsEmpty(name)) { + return false; + } + return str::EqI(name, "Light") || str::EndsWithI(name, " Light"); +} + +static int GetThemeIndexByName(const char* name) { + for (int i = 0; i < gThemeCount; i++) { + Theme* theme = gThemes->At(i); + if (str::EqI(theme->name, name)) { + return i; + } + } + return -1; +} + +static int GetThemeVariantIndex(const char* currentName, bool targetDark) { + if (str::IsEmpty(currentName)) { + return -1; + } + + if (str::EqI(currentName, "Light")) { + return targetDark ? GetThemeIndexByName("Dark") : GetThemeIndexByName("Light"); + } + if (str::EqI(currentName, "Dark")) { + return targetDark ? GetThemeIndexByName("Dark") : GetThemeIndexByName("Light"); + } + + const char* lightSuffix = " Light"; + const char* darkSuffix = " Dark"; + if (!str::EndsWithI(currentName, lightSuffix) && !str::EndsWithI(currentName, darkSuffix)) { + return GetThemeIndexByName(targetDark ? "Dark" : "Light"); + } + + const int suffixLen = (int)str::Len(str::EndsWithI(currentName, lightSuffix) ? lightSuffix : darkSuffix); + const int nameLen = (int)str::Len(currentName); + const int baseLen = nameLen - suffixLen; + if (baseLen <= 0 || baseLen >= 256) { + return GetThemeIndexByName(targetDark ? "Dark" : "Light"); + } + + char base[256]; + memcpy(base, currentName, baseLen); + base[baseLen] = '\0'; + + char targetName[256]; + if (!str::BufFmt(targetName, dimof(targetName), "%s %s", base, targetDark ? "Dark" : "Light")) { + return GetThemeIndexByName(targetDark ? "Dark" : "Light"); + } + return GetThemeIndexByName(targetName); +} + bool IsCurrentThemeDefault() { return gCurrThemeIndex == 0; } @@ -161,19 +262,21 @@ bool PrettyStyleEnabled() { } COLORREF PrettySurfaceColor() { - return ThemeWindowControlBackgroundColor(); + return ThemeWindowBackgroundColor(); } COLORREF PrettySurfaceAltColor() { - return ThemeWindowBackgroundColor(); + return ThemeWindowControlBackgroundColor(); } COLORREF PrettyBorderColor() { - return AccentColor(ThemeWindowControlBackgroundColor(), 40); + // Make the border slightly darker than the window background so it stays subtle + // but still separates the toolbar from the canvas in both light and dark themes. + return AdjustLightness2(ThemeWindowControlBackgroundColor(), -10); } COLORREF PrettyAccentColor() { - return ThemeWindowLinkColor(); + return ThemeAccentColor(); } void FreeThemes() { @@ -221,6 +324,9 @@ void CreateThemeCommands() { void SetThemeByIndex(int themeIdx) { ReportIf((themeIdx < 0) || (themeIdx >= gThemeCount)); + if (themeIdx < 0) { + themeIdx = 0; + } if (themeIdx >= gThemeCount) { themeIdx = 0; } @@ -274,15 +380,21 @@ void SelectNextTheme() { SetThemeByIndex(newIdx); } +bool SetThemeVariant(bool targetDark) { + if (gThemeCount <= 0 || gCurrentTheme == nullptr) { + return false; + } + int idx = GetThemeVariantIndex(gCurrentTheme->name, targetDark); + if (idx < 0) { + return false; + } + SetThemeByIndex(idx); + return true; +} + // not case sensitive static int GetThemeByName(const char* name) { - for (int i = 0; i < gThemeCount; i++) { - Theme* theme = gThemes->At(i); - if (str::EqI(theme->name, name)) { - return i; - } - } - return -1; + return GetThemeIndexByName(name); } // this is the default aggressive yellow that we suppress @@ -340,7 +452,7 @@ COLORREF ThemeDocumentColors(COLORREF& bg) { COLORREF text = ThemeWindowTextColor(); bg = ThemeMainWindowBackgroundColor(); - if (gCurrThemeIndex < 3) { + if (IsLightThemeVariantName(gCurrentTheme->name)) { bg = AccentColor(bg, 8); } return text; @@ -375,7 +487,7 @@ COLORREF ThemePageRenderColors(COLORREF& bg) { } // default colors - if (gCurrentTheme == gThemeLight) { + if (IsLightThemeVariantName(gCurrentTheme->name)) { std::swap(text, bg); return text; } @@ -385,7 +497,7 @@ COLORREF ThemePageRenderColors(COLORREF& bg) { text = ThemeWindowTextColor(); bg = ThemeMainWindowBackgroundColor(); - if (gCurrThemeIndex < 3) { + if (IsLightThemeVariantName(gCurrentTheme->name)) { bg = AccentColor(bg, 8); } return text; @@ -440,6 +552,26 @@ COLORREF ThemeWindowLinkColor() { return col; } +COLORREF ThemeAccentColor() { + auto col = GetThemeCol(gCurrentTheme->accentColor, ThemeWindowLinkColor()); + return col; +} + +COLORREF ThemeBrandPrimaryColor() { + auto col = GetThemeCol(gCurrentTheme->brandPrimaryColor, ThemeAccentColor()); + return col; +} + +COLORREF ThemeBrandGlowColor() { + auto col = GetThemeCol(gCurrentTheme->brandGlowColor, ThemeBrandPrimaryColor()); + return col; +} + +COLORREF ThemeShadowColor() { + auto col = GetThemeCol(gCurrentTheme->shadowColor, kColBlack); + return col; +} + COLORREF ThemeNotificationsBackgroundColor() { auto col = ThemeWindowBackgroundColor(); return AdjustLightness2(col, 10); @@ -450,7 +582,20 @@ COLORREF ThemeNotificationsTextColor() { } COLORREF ThemeNotificationsProgressColor() { - return ThemeWindowLinkColor(); + return ThemeAccentColor(); +} + +// Highlight color used for notifications (background) +COLORREF ThemeNotificationsHighlightColor() { + // Use accent color brightened a bit for highlight background + COLORREF base = ThemeAccentColor(); + return AdjustLightness2(base, 24); +} + +// Text color to use on top of notification highlight background +COLORREF ThemeNotificationsHighlightTextColor() { + COLORREF bg = ThemeNotificationsHighlightColor(); + return IsLightColor(bg) ? kColBlack : kColWhite; } bool ThemeColorizeControls() { diff --git a/src/Theme.h b/src/Theme.h index 37bc96607504..41e3af5d72f1 100644 --- a/src/Theme.h +++ b/src/Theme.h @@ -3,6 +3,7 @@ License: GPLv3 */ void SetTheme(const char* name); void SetCurrentThemeFromSettings(); +bool SetThemeVariant(bool targetDark); void SelectNextTheme(); void CreateThemeCommands(); @@ -15,6 +16,10 @@ COLORREF ThemeWindowTextColor(); COLORREF ThemeWindowTextDisabledColor(); COLORREF ThemeWindowControlBackgroundColor(); COLORREF ThemeWindowLinkColor(); +COLORREF ThemeAccentColor(); +COLORREF ThemeBrandPrimaryColor(); +COLORREF ThemeBrandGlowColor(); +COLORREF ThemeShadowColor(); COLORREF ThemeNotificationsBackgroundColor(); COLORREF ThemeNotificationsTextColor(); COLORREF ThemeNotificationsHighlightColor(); diff --git a/src/prettysumatra/BridgeDispatcher.cpp b/src/prettysumatra/BridgeDispatcher.cpp index b92413a206c1..7e80afc1a288 100644 --- a/src/prettysumatra/BridgeDispatcher.cpp +++ b/src/prettysumatra/BridgeDispatcher.cpp @@ -118,30 +118,33 @@ static TempStr HybridToolbarThemeJs(HWND hwndFrame) { } COLORREF canvas = ThemeMainWindowBackgroundColor(); - COLORREF panel = ThemeMainWindowBackgroundColor(); + COLORREF panelOrig = ThemeWindowControlBackgroundColor(); + // Make toolbar background slightly lighter than the main window background + COLORREF panel = AdjustLightness2(panelOrig, 2); COLORREF panel2 = PrettyStyleEnabled() ? PrettySurfaceColor() : ThemeWindowControlBackgroundColor(); COLORREF stroke = PrettyBorderColor(); COLORREF text = ThemeWindowTextColor(); COLORREF muted = ThemeWindowTextDisabledColor(); - COLORREF btn = ThemeControlBackgroundColor(); + COLORREF btn = AdjustLightness2(panelOrig, 10); COLORREF accent = PrettyAccentColor(); - // Keep toolbar brand colors stable (gold) instead of following system accent (often blue). - COLORREF brand1 = RGB(248, 204, 24); - COLORREF brand2 = RGB(215, 167, 0); - bool appDark = DarkMode::isColorDark(panel); + COLORREF brandPrimary = ThemeBrandPrimaryColor(); + COLORREF brandGlow = ThemeBrandGlowColor(); + COLORREF shadow = ThemeShadowColor(); + // Determine appDark based on the original main window background + bool appDark = DarkMode::isColorDark(panelOrig); bool docInverted = gGlobalPrefs->fixedPageUI.invertColors; bool windowsDark = WindowsPrefersDarkModeForHybridToolbar(); bool followWindows = HybridThemeFollowsWindows(); return str::FormatTemp( "window.__hybridToolbarThemePayload={canvas:'%s',panel:'%s',panel2:'%s',stroke:'%s'," - "text:'%s',muted:'%s',btn:'%s',accent:'%s',brand1:'%s',brand2:'%s',appDark:%s,docInverted:%s,windowsDark:%s," - "followWindows:%s};" + "text:'%s',muted:'%s',btn:'%s',accent:'%s',brandPrimary:'%s',brandGlow:'%s',shadow:'%s',appDark:%s," + "docInverted:%s,windowsDark:%s,followWindows:%s};" "if(window.hybridToolbarApplyTheme){window.hybridToolbarApplyTheme(window.__hybridToolbarThemePayload);}", ColorToCssHex(canvas), ColorToCssHex(panel), ColorToCssHex(panel2), ColorToCssHex(stroke), ColorToCssHex(text), - ColorToCssHex(muted), ColorToCssHex(btn), ColorToCssHex(accent), ColorToCssHex(brand1), ColorToCssHex(brand2), - appDark ? "true" : "false", docInverted ? "true" : "false", windowsDark ? "true" : "false", - followWindows ? "true" : "false"); + ColorToCssHex(muted), ColorToCssHex(btn), ColorToCssHex(accent), ColorToCssHex(brandPrimary), + ColorToCssHex(brandGlow), ColorToCssHex(shadow), appDark ? "true" : "false", docInverted ? "true" : "false", + windowsDark ? "true" : "false", followWindows ? "true" : "false"); } // Generate JavaScript to inject theme colors into home page @@ -155,75 +158,53 @@ static TempStr HomePageThemeJs() { COLORREF muted = ThemeWindowTextDisabledColor(); COLORREF btn = ThemeControlBackgroundColor(); COLORREF accent = PrettyAccentColor(); + COLORREF brandPrimary = ThemeBrandPrimaryColor(); + COLORREF brandGlow = ThemeBrandGlowColor(); + COLORREF shadow = ThemeShadowColor(); bool appDark = DarkMode::isColorDark(panel); // Build JavaScript payload with all theme colors return str::FormatTemp( "window.__homePageThemePayload={canvas:'%s',panel:'%s',panel2:'%s',stroke:'%s'," - "text:'%s',muted:'%s',btn:'%s',accent:'%s',appDark:%s};" + "text:'%s',muted:'%s',btn:'%s',accent:'%s',brandPrimary:'%s',brandGlow:'%s',shadow:'%s',appDark:%s};" "if(window.applyHomePageTheme){window.applyHomePageTheme(window.__homePageThemePayload);}", ColorToCssHex(canvas), ColorToCssHex(panel), ColorToCssHex(panel2), ColorToCssHex(stroke), ColorToCssHex(text), - ColorToCssHex(muted), ColorToCssHex(btn), ColorToCssHex(accent), appDark ? "true" : "false"); + ColorToCssHex(muted), ColorToCssHex(btn), ColorToCssHex(accent), ColorToCssHex(brandPrimary), + ColorToCssHex(brandGlow), ColorToCssHex(shadow), appDark ? "true" : "false"); } -static const char* JsQuoted(const char* s) { +static TempStr JsQuoted(const char* s) { if (!s) { - static const char empty[] = {'\'', '\'', '\0'}; - return empty; + return str::FormatTemp("''"); } - // Calculate needed size - size_t len = 0; + StrBuilder sb; + sb.AppendChar('\''); for (const char* p = s; *p; ++p) { switch (*p) { case '\\': - case '\'': - case '\n': - case '\t': - len += 2; - break; - default: - len += 1; - break; - } - } - len += 2; // for surrounding quotes - len += 1; // for null terminator - - char* out = (char*)malloc(len); - if (!out) return nullptr; - - char* dst = out; - *dst++ = '\''; - for (const char* p = s; *p; ++p) { - switch (*p) { - case '\\': - *dst++ = '\\'; - *dst++ = '\\'; + sb.Append("\\\\"); break; case '\'': - *dst++ = '\\'; - *dst++ = '\''; + sb.Append("\\'"); break; case '\r': - break; // skip carriage returns + // skip + break; case '\n': - *dst++ = '\\'; - *dst++ = 'n'; + sb.Append("\\n"); break; case '\t': - *dst++ = '\\'; - *dst++ = 't'; + sb.Append("\\t"); break; default: - *dst++ = *p; + sb.AppendChar(*p); break; } } - *dst++ = '\''; - *dst = '\0'; + sb.AppendChar('\''); - return out; + return (TempStr)sb.StealData(); } static TempStr HybridToolbarTextJs(HWND hwndFrame) { @@ -306,6 +287,29 @@ void FocusHybridToolbarSearch(HWND hwndFrame) { win->hybridToolbar->Eval("window.hybridToolbarFocusSearch && window.hybridToolbarFocusSearch();"); } +void SyncHybridToolbarButtonVisibility(HWND hwndFrame, bool showButtons) { + MainWindow* win = FindWindowForFrame(hwndFrame); + if (!win || !win->hybridToolbar) { + return; + } + + const char* js = showButtons ? "window.showToolbarButtons && window.showToolbarButtons();" + : "window.hideToolbarButtons && window.hideToolbarButtons();"; + win->hybridToolbar->Eval(js); +} + +void SyncHybridToolbarEditableAllowed(HWND hwndFrame, bool allowed) { + MainWindow* win = FindWindowForFrame(hwndFrame); + if (!win || !win->hybridToolbar) { + return; + } + + const char* js = allowed + ? "window.hybridToolbarSetEditableAllowed && window.hybridToolbarSetEditableAllowed(true);" + : "window.hybridToolbarSetEditableAllowed && window.hybridToolbarSetEditableAllowed(false);"; + win->hybridToolbar->Eval(js); +} + void InitHybridToolbarTheme(HWND hwndFrame) { MainWindow* win = FindWindowForFrame(hwndFrame); if (!win || !win->hybridToolbar) { @@ -678,7 +682,9 @@ static bool WindowsPrefersDarkModeForHybrid() { } static void ApplyThemeState(bool targetDark) { - SetTheme(targetDark ? "Dark" : "Light"); + if (!SetThemeVariant(targetDark)) { + SetTheme(targetDark ? "Dark" : "Light"); + } SaveSettings(); } @@ -904,6 +910,8 @@ static bool DispatchToolbarReady() { if (!win) { return false; } + SyncHybridToolbarButtonVisibility(win->hwndFrame, !win->IsCurrentTabAbout()); + SyncHybridToolbarEditableAllowed(win->hwndFrame, !win->IsCurrentTabAbout()); SyncHybridToolbarTheme(win->hwndFrame); if (win->ctrl) { SyncHybridToolbarPageState(win->hwndFrame, win->ctrl->CurrentPageNo(), win->ctrl->PageCount()); diff --git a/src/prettysumatra/BridgeDispatcher.h b/src/prettysumatra/BridgeDispatcher.h index 6788eaf13bce..c075c97706ce 100644 --- a/src/prettysumatra/BridgeDispatcher.h +++ b/src/prettysumatra/BridgeDispatcher.h @@ -23,6 +23,8 @@ void InitHybridToolbarText(HWND hwndFrame); void SyncHybridToolbarText(HWND hwndFrame); void SyncHybridToolbarSearchText(HWND hwndFrame, const char* text); void FocusHybridToolbarSearch(HWND hwndFrame); +void SyncHybridToolbarButtonVisibility(HWND hwndFrame, bool showButtons); +void SyncHybridToolbarEditableAllowed(HWND hwndFrame, bool allowed); void SyncHybridToolbarPageState(HWND hwndFrame, int currentPage, int totalPages); void SyncHybridToolbarZoomState(HWND hwndFrame, float zoomPercent); void SyncHybridToolbarAnnotationAvailability(HWND hwndFrame);