vibefpmgui.themes
View → Theme: the colors of the whole window (the editor's syntax colors are View → Syntax Colors; with those on Automatic, a theme picks the matching Pygments style).
System, Light and Dark keep the platform's own look (on macOS: its buttons, tabs and dialogs) and only choose light or dark, through Qt's color scheme. The colorful themes need a style that draws with the palette's colors (macOS's doesn't), so they use Qt's Fusion style with a palette of their own; the color scheme is still set to light or dark to match, so the window's title bar and native dialogs agree.
View → Window Style chooses the style itself instead (any of Qt's on this platform: Fusion everywhere, macOS on a Mac, windows11 / windowsvista on Windows…). Fusion draws the panels' dividers with a grip of dots, macOS's a plain line; only Fusion (and the plain Windows style) show a colorful theme's colors throughout.
Either style is wrapped in StillStyle, which turns off the moving of
panels: Qt's dock layout could otherwise keep "animating" forever on macOS.
1"""*View → Theme*: the colors of the whole window (the editor's syntax colors 2are *View → Syntax Colors*; with those on Automatic, a theme picks the 3matching Pygments style). 4 5*System*, *Light* and *Dark* keep the platform's own look (on macOS: its 6buttons, tabs and dialogs) and only choose light or dark, through Qt's color 7scheme. The colorful themes need a style that draws with the palette's 8colors (macOS's doesn't), so they use Qt's Fusion style with a palette of 9their own; the color scheme is still set to light or dark to match, so the 10window's title bar and native dialogs agree. 11 12*View → Window Style* chooses the style itself instead (any of Qt's on this 13platform: Fusion everywhere, macOS on a Mac, windows11 / windowsvista on 14Windows…). Fusion draws the panels' dividers with a grip of dots, macOS's a 15plain line; only Fusion (and the plain Windows style) show a colorful 16theme's colors throughout. 17 18Either style is wrapped in `StillStyle`, which turns off the moving of 19panels: Qt's dock layout could otherwise keep "animating" forever on macOS. 20""" 21 22from typing import NamedTuple 23 24from qtpy.QtCore import Qt 25from qtpy.QtGui import QColor, QFont, QPalette 26from qtpy.QtWidgets import QApplication, QProxyStyle, QStyle, QStyleFactory, QTabBar 27 28 29class Theme(NamedTuple): 30 title: str 31 dark: bool 32 window: str = "" # "" for the platform's own (System, Light, Dark) 33 base: str = "" # text fields, lists, the editor's surroundings 34 text: str = "" 35 button: str = "" 36 highlight: str = "" # selection and accents 37 highlighted_text: str = "" 38 link: str = "" 39 syntax: str = "" # the Pygments style for Syntax Colors → Automatic ("": light/dark default) 40 41 @property 42 def native(self) -> bool: 43 """The platform's look (only light or dark chosen), not our palette.""" 44 return not self.window 45 46 47SYSTEM = "system" 48THEMES: dict[str, Theme] = { 49 SYSTEM: Theme("System (follow light/dark mode)", False), 50 "light": Theme("Light", False), 51 "dark": Theme("Dark", True), 52 # Dark, colorful 53 "nord": Theme("Nord", True, "#2e3440", "#272c36", "#d8dee9", "#3b4252", "#88c0d0", "#2e3440", "#81a1c1", 54 "nord"), 55 "dracula": Theme("Dracula", True, "#282a36", "#21222c", "#f8f8f2", "#44475a", "#bd93f9", "#282a36", 56 "#8be9fd", "dracula"), 57 "solarized-dark": Theme("Solarized Dark", True, "#002b36", "#073642", "#93a1a1", "#0a4352", "#268bd2", 58 "#fdf6e3", "#2aa198", "solarized-dark"), 59 "gruvbox-dark": Theme("Gruvbox Dark", True, "#282828", "#1d2021", "#ebdbb2", "#3c3836", "#d79921", "#282828", 60 "#83a598", "gruvbox-dark"), 61 "one-dark": Theme("One Dark", True, "#282c34", "#21252b", "#abb2bf", "#323842", "#61afef", "#282c34", 62 "#56b6c2", "one-dark"), 63 # Light, colorful 64 "solarized-light": Theme("Solarized Light", False, "#eee8d5", "#fdf6e3", "#586e75", "#e4ddc8", "#268bd2", 65 "#fdf6e3", "#2aa198", "solarized-light"), 66 "gruvbox-light": Theme("Gruvbox Light", False, "#ebdbb2", "#fbf1c7", "#3c3836", "#d5c4a1", "#b57614", 67 "#fbf1c7", "#076678", "gruvbox-light"), 68 "ocean": Theme("Ocean", False, "#e3eef9", "#f7fbff", "#1b2a3a", "#d0e2f4", "#2f7fd8", "#ffffff", "#1f6fbf", 69 "friendly"), 70 "mint": Theme("Mint", False, "#e6f4ea", "#f8fffa", "#1f3326", "#d2eadb", "#2f9e5c", "#ffffff", "#1d7a47", 71 "tango"), 72} 73 74class StillStyle(QProxyStyle): 75 """A style (the platform's, or Fusion) whose widgets don't animate their 76 geometry. Even with the main window's AnimatedDocks off, Qt moves panels 77 with a zero-length animation whenever the style has an animation 78 duration; on macOS each one's end could re-lay out the panels' tab bars, 79 which started another, forever (a frozen window at 100% CPU). With no 80 duration, Qt moves them in one go.""" 81 82 def styleHint(self, hint, option=None, widget=None, data=None): 83 if hint == QStyle.SH_Widget_Animation_Duration: 84 return 0 85 return super().styleHint(hint, option, widget, data) 86 87 88def style_name(style: QStyle) -> str: 89 """The name of a style, or of the one a `StillStyle` wraps.""" 90 return (style.baseStyle() if isinstance(style, QProxyStyle) else style).name() 91 92 93current = SYSTEM # the theme in use (see apply) 94_native_style = "" # the platform's style ("macOS", "Windows", …), to go back to from Fusion 95AUTOMATIC = "" # View → Window Style: the theme's (the platform's, or Fusion for a colorful theme) 96chosen_style = AUTOMATIC # View → Window Style (see window_style) 97PALETTE_STYLES = {"fusion", "windows"} # (styles that draw with a colorful theme's palette) 98 99 100def styles() -> list[str]: 101 """Qt's styles on this platform (always Fusion).""" 102 return list(QStyleFactory.keys()) 103 104 105def native_style() -> str: 106 """The platform's own style (its name as Qt has it).""" 107 return _native_style or style_name(QApplication.instance().style()) 108 109 110def window_style(theme: Theme) -> str: 111 """The style to use with a theme: the one chosen in View → Window Style 112 if it's here (a setting from another platform may not be), else the 113 platform's for System / Light / Dark and Fusion for a colorful theme.""" 114 available = {name.lower(): name for name in styles()} 115 if chosen_style and chosen_style.lower() in available: 116 return available[chosen_style.lower()] 117 return native_style() if theme.native else "Fusion" 118 119 120def set_window_style(name: str): 121 """View → Window Style (AUTOMATIC: the theme's), applied now.""" 122 global chosen_style 123 chosen_style = name 124 apply(current) 125 126 127def palette(theme: Theme) -> QPalette: 128 """A full palette from a theme's few colors.""" 129 window, base, text = QColor(theme.window), QColor(theme.base), QColor(theme.text) 130 button, highlight = QColor(theme.button), QColor(theme.highlight) 131 p = QPalette() 132 for group in (QPalette.Active, QPalette.Inactive, QPalette.Disabled): 133 p.setColor(group, QPalette.Window, window) 134 p.setColor(group, QPalette.Base, base) 135 p.setColor(group, QPalette.AlternateBase, base.lighter(112) if theme.dark else base.darker(104)) 136 p.setColor(group, QPalette.Button, button) 137 p.setColor(group, QPalette.ToolTipBase, button) 138 p.setColor(group, QPalette.Highlight, highlight) 139 p.setColor(group, QPalette.HighlightedText, QColor(theme.highlighted_text)) 140 p.setColor(group, QPalette.Link, QColor(theme.link)) 141 p.setColor(group, QPalette.LinkVisited, QColor(theme.link).darker(120)) 142 p.setColor(group, QPalette.BrightText, QColor("#ff6b6b")) 143 # Shades for frames and bevels, around the button color. 144 p.setColor(group, QPalette.Light, button.lighter(130)) 145 p.setColor(group, QPalette.Midlight, button.lighter(115)) 146 p.setColor(group, QPalette.Mid, button.darker(130)) 147 p.setColor(group, QPalette.Dark, button.darker(160)) 148 p.setColor(group, QPalette.Shadow, QColor(0, 0, 0, 160)) 149 muted = QColor(text) 150 muted.setAlpha(140) 151 p.setColor(group, QPalette.PlaceholderText, muted) 152 faint = text if group != QPalette.Disabled else muted 153 for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText, QPalette.ToolTipText): 154 p.setColor(group, role, faint) 155 p.setColor(QPalette.Inactive, QPalette.Highlight, highlight.darker(115) if theme.dark else highlight.lighter(115)) 156 return p 157 158 159def apply(key: str): 160 """Use a theme for the whole app (an unknown key: System). Qt then sends 161 every window a palette change, which the app's panels follow.""" 162 global current, _native_style 163 app = QApplication.instance() 164 theme = THEMES.get(key) or THEMES[SYSTEM] 165 current = key if key in THEMES else SYSTEM 166 if not _native_style: 167 _native_style = style_name(app.style()) 168 hints = app.styleHints() 169 if hasattr(hints, "setColorScheme"): # (Qt 6.8+) 170 scheme = Qt.ColorScheme.Unknown if current == SYSTEM else \ 171 Qt.ColorScheme.Dark if theme.dark else Qt.ColorScheme.Light 172 hints.setColorScheme(scheme) 173 wanted = window_style(theme) 174 if not isinstance(app.style(), StillStyle) or style_name(app.style()).lower() != wanted.lower(): 175 app.setStyle(StillStyle(wanted)) 176 _unpolish_tab_bars(app) 177 app.setPalette(QPalette() if theme.native else palette(theme)) # (native: the platform's, for the scheme) 178 179 180def _unpolish_tab_bars(app: QApplication): 181 """The macOS style gives a document-mode tab bar (the editor's tabs, the 182 panels') a near-black text color and a small font of its own when it 183 polishes it. At startup the app's font and palette replace them; after a 184 theme's change of style (Fusion → macOS) nothing did: dark text on a dark 185 bar, and names cut off (the tabs measured in the small font). Back to the 186 app's, as at startup.""" 187 for widget in app.allWidgets(): 188 if isinstance(widget, QTabBar) and widget.documentMode(): 189 widget.setPalette(QPalette()) 190 widget.setFont(QFont()) 191 192 193def syntax_style() -> str: 194 """The Pygments style the current theme goes with ("" for none).""" 195 return THEMES.get(current, THEMES[SYSTEM]).syntax
30class Theme(NamedTuple): 31 title: str 32 dark: bool 33 window: str = "" # "" for the platform's own (System, Light, Dark) 34 base: str = "" # text fields, lists, the editor's surroundings 35 text: str = "" 36 button: str = "" 37 highlight: str = "" # selection and accents 38 highlighted_text: str = "" 39 link: str = "" 40 syntax: str = "" # the Pygments style for Syntax Colors → Automatic ("": light/dark default) 41 42 @property 43 def native(self) -> bool: 44 """The platform's look (only light or dark chosen), not our palette.""" 45 return not self.window
Theme(title, dark, window, base, text, button, highlight, highlighted_text, link, syntax)
75class StillStyle(QProxyStyle): 76 """A style (the platform's, or Fusion) whose widgets don't animate their 77 geometry. Even with the main window's AnimatedDocks off, Qt moves panels 78 with a zero-length animation whenever the style has an animation 79 duration; on macOS each one's end could re-lay out the panels' tab bars, 80 which started another, forever (a frozen window at 100% CPU). With no 81 duration, Qt moves them in one go.""" 82 83 def styleHint(self, hint, option=None, widget=None, data=None): 84 if hint == QStyle.SH_Widget_Animation_Duration: 85 return 0 86 return super().styleHint(hint, option, widget, data)
A style (the platform's, or Fusion) whose widgets don't animate their geometry. Even with the main window's AnimatedDocks off, Qt moves panels with a zero-length animation whenever the style has an animation duration; on macOS each one's end could re-lay out the panels' tab bars, which started another, forever (a frozen window at 100% CPU). With no duration, Qt moves them in one go.
83 def styleHint(self, hint, option=None, widget=None, data=None): 84 if hint == QStyle.SH_Widget_Animation_Duration: 85 return 0 86 return super().styleHint(hint, option, widget, data)
styleHint(self, hint: PySide6.QtWidgets.QStyle.StyleHint, /, option: PySide6.QtWidgets.QStyleOption | None = None, widget: PySide6.QtWidgets.QWidget | None = None, returnData: PySide6.QtWidgets.QStyleHintReturn | None = None) -> int
89def style_name(style: QStyle) -> str: 90 """The name of a style, or of the one a `StillStyle` wraps.""" 91 return (style.baseStyle() if isinstance(style, QProxyStyle) else style).name()
The name of a style, or of the one a StillStyle wraps.
101def styles() -> list[str]: 102 """Qt's styles on this platform (always Fusion).""" 103 return list(QStyleFactory.keys())
Qt's styles on this platform (always Fusion).
106def native_style() -> str: 107 """The platform's own style (its name as Qt has it).""" 108 return _native_style or style_name(QApplication.instance().style())
The platform's own style (its name as Qt has it).
111def window_style(theme: Theme) -> str: 112 """The style to use with a theme: the one chosen in View → Window Style 113 if it's here (a setting from another platform may not be), else the 114 platform's for System / Light / Dark and Fusion for a colorful theme.""" 115 available = {name.lower(): name for name in styles()} 116 if chosen_style and chosen_style.lower() in available: 117 return available[chosen_style.lower()] 118 return native_style() if theme.native else "Fusion"
The style to use with a theme: the one chosen in View → Window Style if it's here (a setting from another platform may not be), else the platform's for System / Light / Dark and Fusion for a colorful theme.
121def set_window_style(name: str): 122 """View → Window Style (AUTOMATIC: the theme's), applied now.""" 123 global chosen_style 124 chosen_style = name 125 apply(current)
View → Window Style (AUTOMATIC: the theme's), applied now.
128def palette(theme: Theme) -> QPalette: 129 """A full palette from a theme's few colors.""" 130 window, base, text = QColor(theme.window), QColor(theme.base), QColor(theme.text) 131 button, highlight = QColor(theme.button), QColor(theme.highlight) 132 p = QPalette() 133 for group in (QPalette.Active, QPalette.Inactive, QPalette.Disabled): 134 p.setColor(group, QPalette.Window, window) 135 p.setColor(group, QPalette.Base, base) 136 p.setColor(group, QPalette.AlternateBase, base.lighter(112) if theme.dark else base.darker(104)) 137 p.setColor(group, QPalette.Button, button) 138 p.setColor(group, QPalette.ToolTipBase, button) 139 p.setColor(group, QPalette.Highlight, highlight) 140 p.setColor(group, QPalette.HighlightedText, QColor(theme.highlighted_text)) 141 p.setColor(group, QPalette.Link, QColor(theme.link)) 142 p.setColor(group, QPalette.LinkVisited, QColor(theme.link).darker(120)) 143 p.setColor(group, QPalette.BrightText, QColor("#ff6b6b")) 144 # Shades for frames and bevels, around the button color. 145 p.setColor(group, QPalette.Light, button.lighter(130)) 146 p.setColor(group, QPalette.Midlight, button.lighter(115)) 147 p.setColor(group, QPalette.Mid, button.darker(130)) 148 p.setColor(group, QPalette.Dark, button.darker(160)) 149 p.setColor(group, QPalette.Shadow, QColor(0, 0, 0, 160)) 150 muted = QColor(text) 151 muted.setAlpha(140) 152 p.setColor(group, QPalette.PlaceholderText, muted) 153 faint = text if group != QPalette.Disabled else muted 154 for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText, QPalette.ToolTipText): 155 p.setColor(group, role, faint) 156 p.setColor(QPalette.Inactive, QPalette.Highlight, highlight.darker(115) if theme.dark else highlight.lighter(115)) 157 return p
A full palette from a theme's few colors.
160def apply(key: str): 161 """Use a theme for the whole app (an unknown key: System). Qt then sends 162 every window a palette change, which the app's panels follow.""" 163 global current, _native_style 164 app = QApplication.instance() 165 theme = THEMES.get(key) or THEMES[SYSTEM] 166 current = key if key in THEMES else SYSTEM 167 if not _native_style: 168 _native_style = style_name(app.style()) 169 hints = app.styleHints() 170 if hasattr(hints, "setColorScheme"): # (Qt 6.8+) 171 scheme = Qt.ColorScheme.Unknown if current == SYSTEM else \ 172 Qt.ColorScheme.Dark if theme.dark else Qt.ColorScheme.Light 173 hints.setColorScheme(scheme) 174 wanted = window_style(theme) 175 if not isinstance(app.style(), StillStyle) or style_name(app.style()).lower() != wanted.lower(): 176 app.setStyle(StillStyle(wanted)) 177 _unpolish_tab_bars(app) 178 app.setPalette(QPalette() if theme.native else palette(theme)) # (native: the platform's, for the scheme)
Use a theme for the whole app (an unknown key: System). Qt then sends every window a palette change, which the app's panels follow.
194def syntax_style() -> str: 195 """The Pygments style the current theme goes with ("" for none).""" 196 return THEMES.get(current, THEMES[SYSTEM]).syntax
The Pygments style the current theme goes with ("" for none).