Revamp preferences, add minimalist mode

Also:
- create additional and missing widget styles and tweak existing ones
- use single profile entry to set widget styles and reduce choices to Anki and Native
This commit is contained in:
Matthias Metelka 2023-01-12 20:33:53 +01:00
parent 0e559bff8b
commit 9f6db2c208
21 changed files with 1649 additions and 1142 deletions

View file

@ -4,13 +4,13 @@ preferences-basic = Basic
preferences-change-deck-depending-on-note-type = Change deck depending on note type preferences-change-deck-depending-on-note-type = Change deck depending on note type
preferences-changes-will-take-effect-when-you = Changes will take effect when you restart Anki. preferences-changes-will-take-effect-when-you = Changes will take effect when you restart Anki.
preferences-hours-past-midnight = hours past midnight preferences-hours-past-midnight = hours past midnight
preferences-interface-language = Interface language: preferences-language = Language
preferences-interrupt-current-audio-when-answering = Interrupt current audio when answering preferences-interrupt-current-audio-when-answering = Interrupt current audio when answering
preferences-learn-ahead-limit = Learn ahead limit preferences-learn-ahead-limit = Learn ahead limit
preferences-mins = mins preferences-mins = mins
preferences-network = Network preferences-network = Network
preferences-next-day-starts-at = Next day starts at preferences-next-day-starts-at = Next day starts at
preferences-note-media-is-not-backed-up = Note: Media is not backed up. Please create a periodic backup of your Anki folder to be safe. preferences-note-media-is-not-backed-up = Media is not backed up. Please create a periodic backup of your Anki folder to be safe.
preferences-on-next-sync-force-changes-in = On next sync, force changes in one direction preferences-on-next-sync-force-changes-in = On next sync, force changes in one direction
preferences-paste-clipboard-images-as-png = Paste clipboard images as PNG preferences-paste-clipboard-images-as-png = Paste clipboard images as PNG
preferences-paste-without-shift-key-strips-formatting = Paste without shift key strips formatting preferences-paste-without-shift-key-strips-formatting = Paste without shift key strips formatting
@ -21,19 +21,19 @@ preferences-scheduling = Scheduling
preferences-show-learning-cards-with-larger-steps = Show learning cards with larger steps before reviews preferences-show-learning-cards-with-larger-steps = Show learning cards with larger steps before reviews
preferences-show-next-review-time-above-answer = Show next review time above answer buttons preferences-show-next-review-time-above-answer = Show next review time above answer buttons
preferences-show-play-buttons-on-cards-with = Show play buttons on cards with audio preferences-show-play-buttons-on-cards-with = Show play buttons on cards with audio
preferences-show-remaining-card-count-during-review = Show remaining card count during review preferences-show-remaining-card-count-during-review = Show remaining card count
preferences-some-settings-will-take-effect-after = Some settings will take effect after you restart Anki. preferences-some-settings-will-take-effect-after = Some settings will take effect after you restart Anki.
preferences-synchronisation = <b>Synchronisation</b> preferences-synchronisation = Synchronisation
preferences-synchronizationnot-currently-enabled-click-the-sync = <b>Synchronization</b><br> Not currently enabled; click the sync button in the main window to enable. preferences-synchronizationnot-currently-enabled-click-the-sync = <b>Synchronization</b><br> Not currently enabled; click the sync button in the main window to enable.
preferences-synchronize-audio-and-images-too = Synchronize audio and images too preferences-synchronize-audio-and-images-too = Synchronize audio and images too
preferences-timebox-time-limit = Timebox time limit preferences-timebox-time-limit = Timebox time limit
preferences-user-interface-size = User interface size preferences-user-interface-size = User interface size
preferences-when-adding-default-to-current-deck = When adding, default to current deck preferences-when-adding-default-to-current-deck = When adding, default to current deck
preferences-you-can-restore-backups-via-fileswitch = You can restore backups via File>Switch Profile. preferences-you-can-restore-backups-via-fileswitch = You can restore backups via File > Switch Profile.
preferences-legacy-timezone-handling = Legacy timezone handling (buggy, but required for AnkiDroid <= 2.14) preferences-legacy-timezone-handling = Legacy timezone handling (buggy, but required for AnkiDroid <= 2.14)
preferences-default-search-text = Default search text preferences-default-search-text = Default search text
preferences-default-search-text-example = eg. 'deck:current ' preferences-default-search-text-example = eg. 'deck:current '
preferences-theme-label = Theme: { $theme } preferences-theme = Theme
preferences-theme-follow-system = Follow System preferences-theme-follow-system = Follow System
preferences-theme-light = Light preferences-theme-light = Light
preferences-theme-dark = Dark preferences-theme-dark = Dark
@ -48,4 +48,21 @@ preferences-monthly-backups = Monthly backups to keep:
preferences-minutes-between-backups = Minutes between automatic backups: preferences-minutes-between-backups = Minutes between automatic backups:
preferences-reduce-motion = Reduce motion preferences-reduce-motion = Reduce motion
preferences-reduce-motion-tooltip = Disable various animations and transitions of the user interface preferences-reduce-motion-tooltip = Disable various animations and transitions of the user interface
preferences-collapse-toolbar = Hide top bar during review preferences-collapse-toolbar = Hide top bar
preferences-appearance = Appearance
preferences-general = General
preferences-style = Style
preferences-review = Review
preferences-reviewer = Reviewer
preferences-distractions = Distractions
preferences-minimalist-mode = Minimalist mode
preferences-editing = Editing
preferences-browsing = Browsing
preferences-default-deck = Default deck
preferences-account = AnkiWeb Account
preferences-media = Media
preferences-note = Note
preferences-limits = Limits
preferences-scheduler = Scheduler
preferences-user-interface = User Interface
preferences-import-export = Import/Export

View file

@ -1,6 +1,6 @@
## Video drivers/hardware acceleration. Please avoid translating 'OpenGL' and 'ANGLE'. ## Video drivers/hardware acceleration. Please avoid translating 'OpenGL' and 'ANGLE'.
preferences-video-driver = Video driver: { $driver } preferences-video-driver = Video driver
preferences-video-driver-opengl-mac = OpenGL (recommended on Macs) preferences-video-driver-opengl-mac = OpenGL (recommended on Macs)
preferences-video-driver-software-mac = Software (not recommended) preferences-video-driver-software-mac = Software (not recommended)
preferences-video-driver-opengl-other = OpenGL (faster, may cause issues) preferences-video-driver-opengl-other = OpenGL (faster, may cause issues)

View file

@ -351,6 +351,7 @@ class AnkiApp(QApplication):
QCheckBox, QCheckBox,
QRadioButton, QRadioButton,
QMenu, QMenu,
QSlider,
# classes with PyQt5 compatibility proxy # classes with PyQt5 compatibility proxy
without_qt5_compat_wrapper(QToolButton), without_qt5_compat_wrapper(QToolButton),
without_qt5_compat_wrapper(QTabBar), without_qt5_compat_wrapper(QTabBar),

View file

@ -7,9 +7,10 @@
table { table {
padding: 1rem; padding: 1rem;
background: var(--canvas-elevated);
.fancy & {
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
border-radius: var(--border-radius-large); border-radius: var(--border-radius-medium);
@include elevation(1, $opacity-boost: -0.08); @include elevation(1, $opacity-boost: -0.08);
&:hover { &:hover {
@ -18,6 +19,7 @@ table {
transition: box-shadow var(--transition) ease-in-out; transition: box-shadow var(--transition) ease-in-out;
background: var(--canvas-glass); background: var(--canvas-glass);
backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur));
}
} }
a.deck { a.deck {
@ -37,7 +39,11 @@ th {
} }
tr.deck td { tr.deck td {
padding: 2px 12px;
.fancy & {
padding: 4px 12px; padding: 4px 12px;
}
} }
tr.top-level-drag-row td { tr.top-level-drag-row td {

View file

@ -7,7 +7,6 @@
@use "sass/button-mixins" as button; @use "sass/button-mixins" as button;
.header { .header {
height: 41px;
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
align-items: start; align-items: start;
@ -31,12 +30,47 @@
} }
.toolbar { .toolbar {
height: 31px;
justify-self: center; justify-self: center;
white-space: nowrap; white-space: nowrap;
transition: all var(--transition) ease-in-out;
}
.hitem {
font-weight: bold;
padding: 5px 12px;
color: color(fg);
display: inline-block;
&:hover {
text-decoration: underline;
}
}
body {
margin: 0;
padding: 0;
-webkit-user-select: none;
overflow: hidden; overflow: hidden;
border-bottom-left-radius: prop(border-radius-large);
border-bottom-right-radius: prop(border-radius-large); &:not(.fancy).collapsed {
opacity: 0;
}
transition: opacity var(--transition) ease-in-out;
&.fancy {
&.collapsed {
transform: translateY(-100vh);
}
transition: transform var(--transition) ease-in-out;
.header {
height: 41px;
}
.toolbar {
height: 31px;
overflow: hidden;
border-bottom-left-radius: prop(border-radius-medium);
border-bottom-right-radius: prop(border-radius-medium);
@include elevation(1, $opacity-boost: -0.1); @include elevation(1, $opacity-boost: -0.1);
// elevated state (deck browser, overview) // elevated state (deck browser, overview)
@ -48,37 +82,16 @@
} }
} }
// glass effect // glass effect
background: var(--canvas-glass);backdrop-filter: unset; background: var(--canvas-glass);
backdrop-filter: blur(var(--blur)); backdrop-filter: blur(var(--blur));
transition: all var(--transition) ease-in-out;
}
body {
margin: 0;
padding: 0;
-webkit-user-select: none;
overflow: hidden;
&.collapsed {
transform: translateY(-100vh);
} }
transition: transform var(--transition) ease-in-out;
}
* { .hitem {
-webkit-user-drag: none;
}
.hitem {
font-weight: bold;
padding: 5px 12px;
text-decoration: none;
color: color(fg);
display: inline-block;
body:not(.flat) &,
&:hover { &:hover {
background: var(--canvas-inset);
text-decoration: none;
}
&:active {
@include button.base($border: false); @include button.base($border: false);
background: var(--canvas-elevated); background: var(--canvas-elevated);
} }
@ -88,6 +101,15 @@ body {
&:last-child { &:last-child {
padding-right: 18px; padding-right: 18px;
} }
}
}
&:not(.fancy) {
border-bottom: 1px solid var(--border-subtle);
}
}
* {
-webkit-user-drag: none;
} }
.hitem:focus { .hitem:focus {

View file

@ -30,6 +30,11 @@ body {
&.no-blur * { &.no-blur * {
backdrop-filter: none !important; backdrop-filter: none !important;
} }
&:not(.fancy),
&:not(.fancy) * {
box-shadow: none !important;
backdrop-filter: none !important;
}
} }
a { a {

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>preferences_interface_language</string> <string>preferences_language</string>
</property> </property>
</widget> </widget>
</item> </item>

View file

@ -33,13 +33,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="forceCheckBox">
<property name="text">
<string notr="true">Force Style</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">

View file

@ -1068,9 +1068,9 @@ title="{}" {}>{}</button>""".format(
if is_lin: if is_lin:
# On Linux, the check requires invoking an external binary, # On Linux, the check requires invoking an external binary,
# which we don't want to be doing frequently # which we don't want to be doing frequently
interval_secs = 300 interval_secs = 10
else: else:
interval_secs = 5 interval_secs = 2
self.progress.timer( self.progress.timer(
interval_secs * 1000, interval_secs * 1000,
theme_manager.apply_style_if_system_style_changed, theme_manager.apply_style_if_system_style_changed,

View file

@ -13,7 +13,7 @@ from aqt import AnkiQt
from aqt.operations.collection import set_preferences from aqt.operations.collection import set_preferences
from aqt.profiles import VideoDriver from aqt.profiles import VideoDriver
from aqt.qt import * from aqt.qt import *
from aqt.theme import Theme from aqt.theme import AnkiStyles, Theme
from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr
@ -207,20 +207,35 @@ class Preferences(QDialog):
def setup_global(self) -> None: def setup_global(self) -> None:
"Setup options global to all profiles." "Setup options global to all profiles."
self.form.reduce_motion.setChecked(self.mw.pm.reduced_motion()) self.form.reduce_motion.setChecked(self.mw.pm.reduce_motion())
qconnect(self.form.reduce_motion.stateChanged, self.mw.pm.set_reduce_motion)
self.form.minimalist_mode.setChecked(self.mw.pm.minimalist_mode())
qconnect(self.form.minimalist_mode.stateChanged, self.mw.pm.set_minimalist_mode)
self.form.collapse_toolbar.setChecked(self.mw.pm.collapse_toolbar()) self.form.collapse_toolbar.setChecked(self.mw.pm.collapse_toolbar())
qconnect(
self.form.collapse_toolbar.stateChanged, self.mw.pm.set_collapse_toolbar
)
self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100)) self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100))
themes = [ themes = [
tr.preferences_theme_label(theme=theme)
for theme in (
tr.preferences_theme_follow_system(), tr.preferences_theme_follow_system(),
tr.preferences_theme_light(), tr.preferences_theme_light(),
tr.preferences_theme_dark(), tr.preferences_theme_dark(),
)
] ]
self.form.theme.addItems(themes) self.form.theme.addItems(themes)
self.form.theme.setCurrentIndex(self.mw.pm.theme().value) self.form.theme.setCurrentIndex(self.mw.pm.theme().value)
qconnect(self.form.theme.currentIndexChanged, self.on_theme_changed) qconnect(self.form.theme.currentIndexChanged, self.on_theme_changed)
self.form.styleComboBox.addItems(
[member.name.lower().capitalize() for member in AnkiStyles]
)
self.form.styleComboBox.setCurrentIndex(self.mw.pm.get_widget_style())
qconnect(
self.form.styleComboBox.currentIndexChanged,
self.mw.pm.set_widget_style,
)
self.form.legacy_import_export.setChecked(self.mw.pm.legacy_import_export()) self.form.legacy_import_export.setChecked(self.mw.pm.legacy_import_export())
self.setup_language() self.setup_language()
@ -238,8 +253,6 @@ class Preferences(QDialog):
self.mw.pm.setUiScale(newScale) self.mw.pm.setUiScale(newScale)
restart_required = True restart_required = True
self.mw.pm.set_reduced_motion(self.form.reduce_motion.isChecked())
self.mw.pm.set_collapse_toolbar(self.form.collapse_toolbar.isChecked())
self.mw.pm.set_legacy_import_export(self.form.legacy_import_export.isChecked()) self.mw.pm.set_legacy_import_export(self.form.legacy_import_export.isChecked())
if restart_required: if restart_required:
@ -289,14 +302,12 @@ class Preferences(QDialog):
def setup_video_driver(self) -> None: def setup_video_driver(self) -> None:
self.video_drivers = VideoDriver.all_for_platform() self.video_drivers = VideoDriver.all_for_platform()
names = [ names = [video_driver_name_for_platform(d) for d in self.video_drivers]
tr.preferences_video_driver(driver=video_driver_name_for_platform(d))
for d in self.video_drivers
]
self.form.video_driver.addItems(names) self.form.video_driver.addItems(names)
self.form.video_driver.setCurrentIndex( self.form.video_driver.setCurrentIndex(
self.video_drivers.index(self.mw.pm.video_driver()) self.video_drivers.index(self.mw.pm.video_driver())
) )
self.form.video_driver_label.setVisible(qtmajor == 5)
self.form.video_driver.setVisible(qtmajor == 5) self.form.video_driver.setVisible(qtmajor == 5)
def update_video_driver(self) -> None: def update_video_driver(self) -> None:

View file

@ -21,7 +21,7 @@ from anki.db import DB
from anki.lang import without_unicode_isolation from anki.lang import without_unicode_isolation
from anki.sync import SyncAuth from anki.sync import SyncAuth
from anki.utils import int_time, is_mac, is_win, point_version from anki.utils import int_time, is_mac, is_win, point_version
from aqt import appHelpSite from aqt import appHelpSite, gui_hooks
from aqt.qt import * from aqt.qt import *
from aqt.theme import AnkiStyles, Theme, theme_manager from aqt.theme import AnkiStyles, Theme, theme_manager
from aqt.utils import disable_help_button, send_to_trash, showWarning, tr from aqt.utils import disable_help_button, send_to_trash, showWarning, tr
@ -523,12 +523,21 @@ create table if not exists profiles
def set_reduce_motion(self, on: bool) -> None: def set_reduce_motion(self, on: bool) -> None:
self.meta["reduce_motion"] = on self.meta["reduce_motion"] = on
gui_hooks.body_classes_need_update()
def minimalist_mode(self) -> bool:
return self.meta.get("tatsumoto_mode", False)
def set_minimalist_mode(self, on: bool) -> None:
self.meta["tatsumoto_mode"] = on
gui_hooks.body_classes_need_update()
def collapse_toolbar(self) -> bool: def collapse_toolbar(self) -> bool:
return self.meta.get("collapse_toolbar", False) return self.meta.get("collapse_toolbar", False)
def set_collapse_toolbar(self, on: bool) -> None: def set_collapse_toolbar(self, on: bool) -> None:
self.meta["collapse_toolbar"] = on self.meta["collapse_toolbar"] = on
gui_hooks.body_classes_need_update()
def last_addon_update_check(self) -> int: def last_addon_update_check(self) -> int:
return self.meta.get("last_addon_update_check", 0) return self.meta.get("last_addon_update_check", 0)
@ -546,34 +555,14 @@ create table if not exists profiles
def set_theme(self, theme: Theme) -> None: def set_theme(self, theme: Theme) -> None:
self.meta["theme"] = theme.value self.meta["theme"] = theme.value
def set_forced_style(self, style: AnkiStyles | None) -> None: def set_widget_style(self, style: AnkiStyles) -> None:
if style: self.meta["widget_style"] = style
self.meta[f"force_{AnkiStyles(style).name.lower()}_styles"] = True
for member in AnkiStyles:
if member != style:
self.meta[f"force_{AnkiStyles(member).name.lower()}_styles"] = False
theme_manager.apply_style() theme_manager.apply_style()
def has_forced_style(self) -> bool: def get_widget_style(self) -> AnkiStyles:
for member in AnkiStyles: return self.meta.get(
if self.meta[f"force_{AnkiStyles(member).name.lower()}_styles"]: "widget_style", AnkiStyles.NATIVE if is_mac else AnkiStyles.ANKI
return True )
return False
# These getters are used by ThemeManager
def unset_forced_styles(self) -> None:
self.set_forced_style(None)
def force_anki_styles(self) -> bool:
return self.meta.get("force_anki_styles", False)
def force_fusion_styles(self) -> bool:
return self.meta.get("force_fusion_styles", False)
def force_native_styles(self) -> bool:
return self.meta.get("force_native_styles", False)
def browser_layout(self) -> BrowserLayout: def browser_layout(self) -> BrowserLayout:
from aqt.browser.layout import BrowserLayout from aqt.browser.layout import BrowserLayout

View file

@ -28,108 +28,144 @@ qlineargradient(
""" """
def general_styles(tm: ThemeManager) -> str: class CustomStyles:
def general(self, tm: ThemeManager) -> str:
return f""" return f"""
QFrame, QFrame,
QWidget {{ QWidget {{
background: none; background: none;
}} }}
QPushButton, QPushButton,
QComboBox, QComboBox,
QSpinBox, QSpinBox,
QLineEdit, QDateTimeEdit,
QListWidget, QLineEdit,
QTreeWidget, QListWidget,
QListView {{ QTreeWidget,
QListView,
QTextEdit,
QPlainTextEdit {{
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QLineEdit {{ QLineEdit,
QTextEdit,
QPlainTextEdit,
QDateTimeEdit,
QListWidget,
QTreeWidget,
QListView {{
background: {tm.var(colors.CANVAS_CODE)};
}}
QLineEdit,
QTextEdit,
QPlainTextEdit,
QDateTimeEdit {{
padding: 2px; padding: 2px;
}} }}
QLineEdit:focus {{ QSpinBox:focus,
QDateTimeEdit:focus,
QLineEdit:focus,
QTextEdit:editable:focus,
QPlainTextEdit:editable:focus,
QWidget:editable:focus {{
border-color: {tm.var(colors.BORDER_FOCUS)}; border-color: {tm.var(colors.BORDER_FOCUS)};
}} }}
QPushButton {{ QPushButton {{
margin-top: 1px; margin-top: 1px;
}} }}
QPushButton, QPushButton,
QComboBox, QComboBox,
QSpinBox {{ QSpinBox {{
padding: 2px 6px; padding: 2px 6px;
}} }}
QGroupBox {{
text-align: center;
font-weight: bold;
border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
padding: 0.75em 0 0.75em 0;
background: {tm.var(colors.CANVAS_ELEVATED)};
border-radius: {tm.var(props.BORDER_RADIUS)};
margin-top: 10px;
}}
QGroupBox::title {{
subcontrol-origin: margin;
subcontrol-position: top left;
margin: 0 2px;
left: 15px;
}}
""" """
def menu(self, tm: ThemeManager) -> str:
def menu_styles(tm: ThemeManager) -> str:
return f""" return f"""
QMenuBar {{ QMenuBar {{
border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)};
}} }}
QMenuBar::item {{ QMenuBar::item {{
background-color: transparent; background-color: transparent;
padding: 2px 4px; padding: 2px 4px;
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QMenuBar::item:selected {{ QMenuBar::item:selected {{
background-color: {tm.var(colors.CANVAS_ELEVATED)}; background-color: {tm.var(colors.CANVAS_ELEVATED)};
}} }}
QMenu {{ QMenu {{
background-color: {tm.var(colors.CANVAS_OVERLAY)}; background-color: {tm.var(colors.CANVAS_OVERLAY)};
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
padding: 4px; padding: 4px;
}} }}
QMenu::item {{ QMenu::item {{
background-color: transparent; background-color: transparent;
padding: 3px 14px; padding: 3px 14px;
margin-bottom: 4px; margin-bottom: 4px;
}} }}
QMenu::item:selected {{ QMenu::item:selected {{
background-color: {tm.var(colors.HIGHLIGHT_BG)}; background-color: {tm.var(colors.HIGHLIGHT_BG)};
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QMenu::separator {{ QMenu::separator {{
height: 1px; height: 1px;
background: {tm.var(colors.BORDER_SUBTLE)}; background: {tm.var(colors.BORDER_SUBTLE)};
margin: 0 8px 4px 8px; margin: 0 8px 4px 8px;
}} }}
QMenu::indicator {{ QMenu::indicator {{
border: 1px solid {tm.var(colors.BORDER)}; border: 1px solid {tm.var(colors.BORDER)};
margin-{tm.left()}: 6px; margin-{tm.left()}: 6px;
margin-{tm.right()}: -6px; margin-{tm.right()}: -6px;
}} }}
""" """
def button(self, tm: ThemeManager) -> str:
def button_styles(tm: ThemeManager) -> str:
# For some reason, Windows needs a larger padding to look the same # For some reason, Windows needs a larger padding to look the same
button_pad = 25 if is_win else 15 button_pad = 25 if is_win else 15
return f""" return f"""
QPushButton {{ padding-left: {button_pad}px; padding-right: {button_pad}px; }} QPushButton {{ padding-left: {button_pad}px; padding-right: {button_pad}px; }}
QPushButton, QPushButton,
QTabBar::tab:!selected, QTabBar::tab:!selected,
QComboBox:!editable, QComboBox:!editable,
QComboBox::drop-down:editable, QComboBox::drop-down:editable {{
QSpinBox::up-button,
QSpinBox::down-button {{
background: {tm.var(colors.BUTTON_BG)}; background: {tm.var(colors.BUTTON_BG)};
border-bottom: 1px solid {tm.var(colors.SHADOW)}; border-bottom: 1px solid {tm.var(colors.SHADOW)};
}} }}
QPushButton:hover, QPushButton:hover,
QTabBar::tab:hover, QTabBar::tab:hover,
QComboBox:!editable:hover, QComboBox:!editable:hover,
QSpinBox::up-button, QSpinBox::up-button:hover,
QSpinBox::down-button {{ QSpinBox::down-button:hover,
QDateTimeEdit::up-button:hover,
QDateTimeEdit::down-button:hover {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END), tm.var(colors.BUTTON_GRADIENT_END),
) )
}; };
}} }}
QPushButton:pressed, QPushButton:pressed,
QSpinBox::up-button, QSpinBox::up-button:pressed,
QSpinBox::down-button {{ QSpinBox::down-button:pressed,
QDateTimeEdit::up-button:pressed,
QDateTimeEdit::down-button:pressed {{
background: { background: {
button_pressed_gradient( button_pressed_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
@ -137,59 +173,57 @@ QSpinBox::down-button {{
tm.var(colors.SHADOW) tm.var(colors.SHADOW)
) )
}; };
}} }}
""" """
def splitter(self, tm: ThemeManager) -> str:
def splitter_styles(tm: ThemeManager) -> str:
return f""" return f"""
QSplitter::handle, QSplitter::handle,
QMainWindow::separator {{ QMainWindow::separator {{
height: 16px; height: 16px;
}} }}
QSplitter::handle:vertical, QSplitter::handle:vertical,
QMainWindow::separator:horizontal {{ QMainWindow::separator:horizontal {{
image: url({tm.themed_icon("mdi:drag-horizontal-FG_SUBTLE")}); image: url({tm.themed_icon("mdi:drag-horizontal-FG_SUBTLE")});
}} }}
QSplitter::handle:horizontal, QSplitter::handle:horizontal,
QMainWindow::separator:vertical {{ QMainWindow::separator:vertical {{
image: url({tm.themed_icon("mdi:drag-vertical-FG_SUBTLE")}); image: url({tm.themed_icon("mdi:drag-vertical-FG_SUBTLE")});
}} }}
""" """
def combobox(self, tm: ThemeManager) -> str:
def combobox_styles(tm: ThemeManager) -> str:
return f""" return f"""
QComboBox {{ QComboBox {{
padding: {"1px 6px 2px 4px" if tm.rtl() else "1px 4px 2px 6px"}; padding: {"1px 6px 2px 4px" if tm.rtl() else "1px 4px 2px 6px"};
}} }}
QComboBox:focus {{ QComboBox:focus {{
border-color: {tm.var(colors.BORDER_FOCUS)}; border-color: {tm.var(colors.BORDER_FOCUS)};
}} }}
QComboBox:editable:on, QComboBox:editable:on,
QComboBox:editable:focus, QComboBox:editable:focus,
QComboBox::drop-down:focus:editable, QComboBox::drop-down:focus:editable,
QComboBox::drop-down:pressed {{ QComboBox::drop-down:pressed {{
border-color: {tm.var(colors.BORDER_FOCUS)}; border-color: {tm.var(colors.BORDER_FOCUS)};
}} }}
QComboBox:on {{ QComboBox:on {{
border-bottom: none; border-bottom: none;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
}} }}
QComboBox::item {{ QComboBox::item {{
color: {tm.var(colors.FG)}; color: {tm.var(colors.FG)};
background: {tm.var(colors.CANVAS_ELEVATED)}; background: {tm.var(colors.CANVAS_ELEVATED)};
}} }}
QComboBox::item:selected {{ QComboBox::item:selected {{
background: {tm.var(colors.HIGHLIGHT_BG)}; background: {tm.var(colors.HIGHLIGHT_BG)};
color: {tm.var(colors.HIGHLIGHT_FG)}; color: {tm.var(colors.HIGHLIGHT_FG)};
}} }}
QComboBox::item::icon:selected {{ QComboBox::item::icon:selected {{
position: absolute; position: absolute;
}} }}
QComboBox::drop-down {{ QComboBox::drop-down {{
subcontrol-origin: border; subcontrol-origin: border;
padding: 2px; padding: 2px;
padding-left: 4px; padding-left: 4px;
@ -199,79 +233,86 @@ QComboBox::drop-down {{
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)};
border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QComboBox::drop-down:!editable {{ QComboBox::drop-down:!editable {{
background: none; background: none;
border-color: transparent; border-color: transparent;
}} }}
QComboBox::down-arrow {{ QComboBox::down-arrow {{
image: url({tm.themed_icon("mdi:chevron-down")}); image: url({tm.themed_icon("mdi:chevron-down")});
}} }}
QComboBox::drop-down:hover:editable {{ QComboBox::drop-down:hover:editable {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END), tm.var(colors.BUTTON_GRADIENT_END),
) )
}; };
}} }}
""" """
def tabwidget(self, tm: ThemeManager) -> str:
def tabwidget_styles(tm: ThemeManager) -> str:
return f""" return f"""
QTabWidget {{ QTabWidget {{
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
background: none; background: none;
}} }}
QTabWidget::pane {{ QTabWidget::pane {{
top: -15px; top: -15px;
padding-top: 1em; padding-top: 1em;
background: {tm.var(colors.CANVAS_ELEVATED)}; background: {tm.var(colors.CANVAS_ELEVATED)};
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QTabWidget::tab-bar {{ QTabWidget::tab-bar {{
alignment: center; alignment: center;
}} }}
QTabBar::tab {{ QTabBar::tab {{
background: none; background: none;
padding: 4px 8px; padding: 4px 8px;
min-width: 8ex; min-width: 8ex;
}} }}
QTabBar::tab {{ QTabBar::tab {{
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-bottom-color: {tm.var(colors.SHADOW)}; border-bottom-color: {tm.var(colors.SHADOW)};
}} }}
QTabBar::tab:first {{ QTabBar::tab:first {{
border-top-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; border-top-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)};
border-bottom-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; border-bottom-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QTabBar::tab:!first {{ QTabBar::tab:!first {{
margin-{tm.left()}: -1px; margin-{tm.left()}: -1px;
}} }}
QTabBar::tab:last {{ QTabBar::tab:last {{
border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)};
border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QTabBar::tab:selected {{ QTabBar::tab:selected {{
color: white; color: white;
background: {tm.var(colors.BUTTON_PRIMARY_BG)}; background: {tm.var(colors.BUTTON_PRIMARY_BG)};
}} }}
QTabBar::tab:selected:hover {{ QTabBar::tab:selected:hover {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), tm.var(colors.BUTTON_PRIMARY_GRADIENT_START),
tm.var(colors.BUTTON_PRIMARY_GRADIENT_END), tm.var(colors.BUTTON_PRIMARY_GRADIENT_END),
) )
}; };
}} }}
QTabBar::tab:disabled,
QTabBar::tab:disabled:hover {{
background: {tm.var(colors.BUTTON_DISABLED)};
color: {tm.var(colors.FG_DISABLED)};
}}
QTabBar::tab:selected:disabled,
QTabBar::tab:selected:hover:disabled {{
background: {tm.var(colors.BUTTON_PRIMARY_DISABLED)};
}}
""" """
def table(self, tm: ThemeManager) -> str:
def table_styles(tm: ThemeManager) -> str:
return f""" return f"""
QTableView {{ QTableView {{
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
border-{tm.left()}: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-{tm.left()}: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)};
@ -280,21 +321,22 @@ QTableView {{
gridline-color: {tm.var(colors.BORDER_SUBTLE)}; gridline-color: {tm.var(colors.BORDER_SUBTLE)};
selection-background-color: {tm.var(colors.SELECTED_BG)}; selection-background-color: {tm.var(colors.SELECTED_BG)};
selection-color: {tm.var(colors.SELECTED_FG)}; selection-color: {tm.var(colors.SELECTED_FG)};
}} background: {tm.var(colors.CANVAS_INSET)};
QHeaderView {{ }}
QHeaderView {{
background: {tm.var(colors.CANVAS)}; background: {tm.var(colors.CANVAS)};
}} }}
QHeaderView::section {{ QHeaderView::section {{
padding-{tm.left()}: 0px; padding-{tm.left()}: 0px;
padding-{tm.right()}: 15px; padding-{tm.right()}: 15px;
border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
background: {tm.var(colors.BUTTON_BG)}; background: {tm.var(colors.BUTTON_BG)};
}} }}
QHeaderView::section:first {{ QHeaderView::section:first {{
margin-left: -1px; margin-left: -1px;
}} }}
QHeaderView::section:pressed, QHeaderView::section:pressed,
QHeaderView::section:pressed:!first {{ QHeaderView::section:pressed:!first {{
background: { background: {
button_pressed_gradient( button_pressed_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
@ -302,175 +344,248 @@ QHeaderView::section:pressed:!first {{
tm.var(colors.SHADOW) tm.var(colors.SHADOW)
) )
} }
}} }}
QHeaderView::section:hover {{ QHeaderView::section:hover {{
background: { background: {
button_gradient( button_gradient(
tm.var(colors.BUTTON_GRADIENT_START), tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END), tm.var(colors.BUTTON_GRADIENT_END),
) )
}; };
}} }}
QHeaderView::section:first {{ QHeaderView::section:first {{
border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; border-top-left-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::section:!first {{ QHeaderView::section:!first {{
border-left: none; border-left: none;
}} }}
QHeaderView::section:last {{ QHeaderView::section:last {{
border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; border-top-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::section:only-one {{ QHeaderView::section:only-one {{
border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; border-top-left-radius: {tm.var(props.BORDER_RADIUS)};
border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; border-top-right-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QHeaderView::up-arrow, QHeaderView::up-arrow,
QHeaderView::down-arrow {{ QHeaderView::down-arrow {{
width: 20px; width: 20px;
height: 20px; height: 20px;
margin-{tm.left()}: -20px; margin-{tm.left()}: -20px;
}} }}
QHeaderView::up-arrow {{ QHeaderView::up-arrow {{
image: url({tm.themed_icon("mdi:menu-up")}); image: url({tm.themed_icon("mdi:menu-up")});
}} }}
QHeaderView::down-arrow {{ QHeaderView::down-arrow {{
image: url({tm.themed_icon("mdi:menu-down")}); image: url({tm.themed_icon("mdi:menu-down")});
}} }}
""" """
def spinbox(self, tm: ThemeManager) -> str:
def spinbox_styles(tm: ThemeManager) -> str:
return f""" return f"""
QSpinBox::up-button, QSpinBox::up-button,
QSpinBox::down-button {{ QSpinBox::down-button,
QDateTimeEdit::up-button,
QDateTimeEdit::down-button {{
subcontrol-origin: border; subcontrol-origin: border;
width: 16px; width: 16px;
}} margin: 1px;
QSpinBox::up-button {{ }}
QSpinBox::up-button,
QDateTimeEdit::up-button {{
margin-bottom: -1px; margin-bottom: -1px;
subcontrol-position: top right; subcontrol-position: top right;
border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QSpinBox::down-button {{ QSpinBox::down-button,
QDateTimeEdit::down-button {{
margin-top: -1px; margin-top: -1px;
subcontrol-position: bottom right; subcontrol-position: bottom right;
border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)};
}} }}
QSpinBox::up-arrow {{ QSpinBox::up-arrow,
QDateTimeEdit::up-arrow {{
image: url({tm.themed_icon("mdi:chevron-up")}); image: url({tm.themed_icon("mdi:chevron-up")});
}} }}
QSpinBox::down-arrow {{ QSpinBox::down-arrow,
QDateTimeEdit::down-arrow {{
image: url({tm.themed_icon("mdi:chevron-down")}); image: url({tm.themed_icon("mdi:chevron-down")});
}} }}
QSpinBox::up-arrow, QSpinBox::up-arrow,
QSpinBox::down-arrow, QSpinBox::down-arrow,
QSpinBox::up-arrow:pressed, QSpinBox::up-arrow:pressed,
QSpinBox::down-arrow:pressed, QSpinBox::down-arrow:pressed,
QSpinBox::up-arrow:disabled:hover, QSpinBox::up-arrow:off:hover, QSpinBox::up-arrow:disabled:hover, QSpinBox::up-arrow:off:hover,
QSpinBox::down-arrow:disabled:hover, QSpinBox::down-arrow:off:hover {{ QSpinBox::down-arrow:disabled:hover, QSpinBox::down-arrow:off:hover,
QDateTimeEdit::up-arrow,
QDateTimeEdit::down-arrow,
QDateTimeEdit::up-arrow:pressed,
QDateTimeEdit::down-arrow:pressed,
QDateTimeEdit::up-arrow:disabled:hover, QDateTimeEdit::up-arrow:off:hover,
QDateTimeEdit::down-arrow:disabled:hover, QDateTimeEdit::down-arrow:off:hover {{
width: 16px; width: 16px;
height: 16px; height: 16px;
}} }}
QSpinBox::up-arrow:hover, QSpinBox::up-arrow:hover,
QSpinBox::down-arrow:hover {{ QSpinBox::down-arrow:hover,
QDateTimeEdit::up-arrow:hover,
QDateTimeEdit::down-arrow:hover {{
width: 20px; width: 20px;
height: 20px; height: 20px;
}} }}
QSpinBox::up-button:disabled, QSpinBox::up-button:off, QSpinBox::up-button:disabled, QSpinBox::up-button:off,
QSpinBox::down-button:disabled, QSpinBox::down-button:off {{ QSpinBox::down-button:disabled, QSpinBox::down-button:off,
QDateTimeEdit::up-button:disabled, QDateTimeEdit::up-button:off,
QDateTimeEdit::down-button:disabled, QDateTimeEdit::down-button:off {{
background: {tm.var(colors.BUTTON_DISABLED)}; background: {tm.var(colors.BUTTON_DISABLED)};
}} }}
QSpinBox::up-arrow:off, QSpinBox::up-arrow:off,
QSpinBox::down-arrow:off {{ QDateTimeEdit::up-arrow:off {{
image: url({tm.themed_icon("mdi:chevron-up-FG_DISABLED")});
}}
QSpinBox::down-arrow:off,
QDateTimeEdit::down-arrow:off {{
image: url({tm.themed_icon("mdi:chevron-down-FG_DISABLED")}); image: url({tm.themed_icon("mdi:chevron-down-FG_DISABLED")});
}} }}
""" """
def checkbox(self, tm: ThemeManager) -> str:
def checkbox_styles(tm: ThemeManager) -> str:
return f""" return f"""
QCheckBox, QCheckBox,
QRadioButton {{ QRadioButton {{
spacing: 8px; spacing: 8px;
margin: 2px 0; margin: 2px 0;
}} }}
QCheckBox::indicator, QCheckBox::indicator,
QRadioButton::indicator, QRadioButton::indicator,
QMenu::indicator {{ QMenu::indicator {{
border: 1px solid {tm.var(colors.BORDER)}; border: 1px solid {tm.var(colors.BORDER)};
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
background: {tm.var(colors.CANVAS_ELEVATED)}; background: {tm.var(colors.CANVAS_ELEVATED)};
width: 16px; width: 16px;
height: 16px; height: 16px;
}} }}
QRadioButton::indicator, QRadioButton::indicator,
QMenu::indicator:exclusive {{ QMenu::indicator:exclusive {{
border-radius: 8px; border-radius: 8px;
}} }}
QCheckBox::indicator:hover, QCheckBox::indicator:hover,
QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:hover,
QRadioButton::indicator:hover, QRadioButton::indicator:hover,
QRadioButton::indicator:checked:hover {{ QRadioButton::indicator:checked:hover {{
border: 2px solid {tm.var(colors.BORDER_STRONG)}; border: 2px solid {tm.var(colors.BORDER_STRONG)};
width: 14px; width: 14px;
height: 14px; height: 14px;
}} }}
QCheckBox::indicator:checked, QCheckBox::indicator:checked,
QRadioButton::indicator:checked, QRadioButton::indicator:checked,
QMenu::indicator:checked {{ QMenu::indicator:checked {{
image: url({tm.themed_icon("mdi:check")}); image: url({tm.themed_icon("mdi:check")});
}} }}
QRadioButton::indicator:checked {{ QRadioButton::indicator:checked {{
image: url({tm.themed_icon("mdi:circle-medium")}); image: url({tm.themed_icon("mdi:circle-medium")});
}} }}
QCheckBox::indicator:indeterminate {{ QCheckBox::indicator:indeterminate {{
image: url({tm.themed_icon("mdi:minus-thick")}); image: url({tm.themed_icon("mdi:minus-thick")});
}} }}
""" """
def scrollbar(self, tm: ThemeManager) -> str:
def scrollbar_styles(tm: ThemeManager) -> str:
return f""" return f"""
QAbstractScrollArea::corner {{ QAbstractScrollArea::corner {{
background: none; background: none;
border: none; border: none;
}} }}
QScrollBar {{ QScrollBar {{
subcontrol-origin: content; subcontrol-origin: content;
background-color: transparent; background-color: transparent;
}} }}
QScrollBar::handle {{ QScrollBar::handle {{
border-radius: {tm.var(props.BORDER_RADIUS)}; border-radius: {tm.var(props.BORDER_RADIUS)};
background-color: {tm.var(colors.SCROLLBAR_BG)}; background-color: {tm.var(colors.SCROLLBAR_BG)};
}} }}
QScrollBar::handle:hover {{ QScrollBar::handle:hover {{
background-color: {tm.var(colors.SCROLLBAR_BG_HOVER)}; background-color: {tm.var(colors.SCROLLBAR_BG_HOVER)};
}} }}
QScrollBar::handle:pressed {{ QScrollBar::handle:pressed {{
background-color: {tm.var(colors.SCROLLBAR_BG_ACTIVE)}; background-color: {tm.var(colors.SCROLLBAR_BG_ACTIVE)};
}} }}
QScrollBar:horizontal {{ QScrollBar:horizontal {{
height: 12px; height: 12px;
}} }}
QScrollBar::handle:horizontal {{ QScrollBar::handle:horizontal {{
min-width: 60px; min-width: 60px;
}} }}
QScrollBar:vertical {{ QScrollBar:vertical {{
width: 12px; width: 12px;
}} }}
QScrollBar::handle:vertical {{ QScrollBar::handle:vertical {{
min-height: 60px; min-height: 60px;
}} }}
QScrollBar::add-line {{ QScrollBar::add-line {{
border: none; border: none;
background: none; background: none;
}} }}
QScrollBar::sub-line {{ QScrollBar::sub-line {{
border: none; border: none;
background: none; background: none;
}} }}
""" """
def slider(self, tm: ThemeManager) -> str:
return f"""
QSlider::horizontal {{
height: 20px;
}}
QSlider::vertical {{
width: 20px;
}}
QSlider::groove {{
border: 1px solid {tm.var(colors.BORDER_SUBTLE)};
border-radius: 3px;
background: {tm.var(colors.CANVAS_ELEVATED)};
}}
QSlider::sub-page {{
background: {tm.var(colors.BUTTON_PRIMARY_GRADIENT_START)};
border-radius: 3px;
margin: 1px;
}}
QSlider::sub-page:disabled {{
background: {tm.var(colors.BUTTON_DISABLED)};
}}
QSlider::add-page {{
margin-{tm.right()}: 2px;
}}
QSlider::groove:vertical {{
width: 6px;
}}
QSlider::groove:horizontal {{
height: 6px;
}}
QSlider::handle {{
background: {tm.var(colors.BUTTON_BG)};
border: 1px solid {tm.var(colors.BORDER)};
border-radius: 9px;
width: 18px;
height: 18px;
border-bottom-color: {tm.var(colors.SHADOW)};
}}
QSlider::handle:vertical {{
margin: 0 -7px;
}}
QSlider::handle:horizontal {{
margin: -7px 0;
}}
QSlider::handle:hover {{
background: {button_gradient(
tm.var(colors.BUTTON_GRADIENT_START),
tm.var(colors.BUTTON_GRADIENT_END),
)}
}}
"""
custom_styles = CustomStyles()

View file

@ -46,8 +46,7 @@ class ColoredIcon:
class AnkiStyles(enum.IntEnum): class AnkiStyles(enum.IntEnum):
ANKI = 0 ANKI = 0
FUSION = 1 NATIVE = 1
NATIVE = 2
class Theme(enum.IntEnum): class Theme(enum.IntEnum):
@ -176,6 +175,8 @@ class ThemeManager:
classes.append("macos-dark-mode") classes.append("macos-dark-mode")
if aqt.mw.pm.reduce_motion(): if aqt.mw.pm.reduce_motion():
classes.append("reduce-motion") classes.append("reduce-motion")
if not aqt.mw.pm.minimalist_mode():
classes.append("fancy")
if qtmajor == 5 and qtminor < 15: if qtmajor == 5 and qtminor < 15:
classes.append("no-blur") classes.append("no-blur")
return " ".join(classes) return " ".join(classes)
@ -237,36 +238,24 @@ class ThemeManager:
gui_hooks.theme_did_change() gui_hooks.theme_did_change()
def _apply_style(self, app: QApplication) -> None: def _apply_style(self, app: QApplication) -> None:
from aqt.stylesheets import splitter_styles buf = ""
buf = splitter_styles(self) if not aqt.mw.pm.force_native_styles() else "" if aqt.mw.pm.get_widget_style() == AnkiStyles.ANKI:
from aqt.stylesheets import custom_styles
if aqt.mw.pm.force_anki_styles() or not (
aqt.mw.pm.force_native_styles() or aqt.mw.pm.force_fusion_styles() or is_mac
):
from aqt.stylesheets import (
button_styles,
checkbox_styles,
combobox_styles,
general_styles,
menu_styles,
scrollbar_styles,
spinbox_styles,
table_styles,
tabwidget_styles,
)
buf += "".join( buf += "".join(
[ [
general_styles(self), custom_styles.general(self),
button_styles(self), custom_styles.button(self),
checkbox_styles(self), custom_styles.checkbox(self),
menu_styles(self), custom_styles.menu(self),
combobox_styles(self), custom_styles.combobox(self),
tabwidget_styles(self), custom_styles.tabwidget(self),
table_styles(self), custom_styles.table(self),
spinbox_styles(self), custom_styles.spinbox(self),
scrollbar_styles(self), custom_styles.scrollbar(self),
custom_styles.slider(self),
custom_styles.splitter(self),
] ]
) )
@ -278,19 +267,6 @@ class ThemeManager:
def _apply_palette(self, app: QApplication) -> None: def _apply_palette(self, app: QApplication) -> None:
set_macos_dark_mode(self.night_mode) set_macos_dark_mode(self.night_mode)
if aqt.mw.pm.force_native_styles() or (
is_mac and not (qtmajor == 5 or aqt.mw.pm.force_anki_styles())
):
app.setStyle(QStyleFactory.create(self._default_style)) # type: ignore
self.default_palette.setColor(
QPalette.ColorRole.Window, self.qcolor(colors.CANVAS)
)
self.default_palette.setColor(
QPalette.ColorRole.AlternateBase, self.qcolor(colors.CANVAS)
)
app.setPalette(self.default_palette)
return
app.setStyle(QStyleFactory.create("fusion")) # type: ignore app.setStyle(QStyleFactory.create("fusion")) # type: ignore
palette = QPalette() palette = QPalette()

View file

@ -52,6 +52,11 @@ class ToolbarWebView(AnkiWebView):
return False return False
def on_body_classes_need_update(self) -> None:
super().on_body_classes_need_update()
super().adjustHeightToFit()
self.expand()
def _onHeight(self, qvar: Optional[int]) -> None: def _onHeight(self, qvar: Optional[int]) -> None:
super()._onHeight(qvar) super()._onHeight(qvar)
self.web_height = int(qvar) self.web_height = int(qvar)

View file

@ -248,6 +248,7 @@ class AnkiWebView(QWebEngineView):
self.resetHandlers() self.resetHandlers()
self._filterSet = False self._filterSet = False
gui_hooks.theme_did_change.append(self.on_theme_did_change) gui_hooks.theme_did_change.append(self.on_theme_did_change)
gui_hooks.body_classes_need_update.append(self.on_body_classes_need_update)
qconnect(self.loadFinished, self._on_load_finished) qconnect(self.loadFinished, self._on_load_finished)
@ -706,6 +707,7 @@ html {{ {font} }}
return return
gui_hooks.theme_did_change.remove(self.on_theme_did_change) gui_hooks.theme_did_change.remove(self.on_theme_did_change)
gui_hooks.body_classes_need_update.remove(self.on_body_classes_need_update)
mw.mediaServer.clear_page_html(id(self)) mw.mediaServer.clear_page_html(id(self))
self._page.deleteLater() self._page.deleteLater()
@ -733,6 +735,16 @@ html {{ {font} }}
""" """
) )
def on_body_classes_need_update(self) -> None:
from aqt import mw
self.eval(
f"""document.body.classList.toggle("fancy", {json.dumps(not mw.pm.minimalist_mode())}); """
)
self.eval(
f"""document.body.classList.toggle("reduce-motion", {json.dumps(mw.pm.minimalist_mode())}); """
)
@deprecated(info="use theme_manager.qcolor() instead") @deprecated(info="use theme_manager.qcolor() instead")
def get_window_bg_color(self, night_mode: Optional[bool] = None) -> QColor: def get_window_bg_color(self, night_mode: Optional[bool] = None) -> QColor:
return theme_manager.qcolor(colors.CANVAS) return theme_manager.qcolor(colors.CANVAS)

View file

@ -5,7 +5,7 @@ import aqt
import aqt.main import aqt.main
from aqt.qt import QDialog, qconnect from aqt.qt import QDialog, qconnect
from aqt.theme import AnkiStyles from aqt.theme import AnkiStyles
from aqt.utils import is_mac, restoreGeom, saveGeom from aqt.utils import restoreGeom, saveGeom
class WidgetGallery(QDialog): class WidgetGallery(QDialog):
@ -29,23 +29,12 @@ class WidgetGallery(QDialog):
self.form.styleComboBox.addItems( self.form.styleComboBox.addItems(
[member.name.lower().capitalize() for member in AnkiStyles] [member.name.lower().capitalize() for member in AnkiStyles]
) )
self.form.styleComboBox.setCurrentIndex( self.form.styleComboBox.setCurrentIndex(self.mw.pm.get_widget_style())
AnkiStyles.FUSION
if self.mw.pm.force_fusion_styles()
else AnkiStyles.NATIVE
if self.mw.pm.force_native_styles() or is_mac
else AnkiStyles.ANKI
)
self.form.forceCheckBox.setChecked(self.mw.pm.has_forced_style())
qconnect( qconnect(
self.form.styleComboBox.currentIndexChanged, self.form.styleComboBox.currentIndexChanged,
self.mw.pm.set_forced_style, self.mw.pm.set_widget_style,
) )
def reject(self) -> None: def reject(self) -> None:
super().reject() super().reject()
if not self.form.forceCheckBox.isChecked():
self.mw.pm.unset_forced_styles()
saveGeom(self, "WidgetGallery") saveGeom(self, "WidgetGallery")

View file

@ -613,6 +613,10 @@ hooks = [
name="theme_did_change", name="theme_did_change",
doc="Called after night mode is toggled.", doc="Called after night mode is toggled.",
), ),
Hook(
name="body_classes_need_update",
doc="Called when a setting involving a webview body class is toggled.",
),
# Webview # Webview
################### ###################
Hook( Hook(

View file

@ -1,6 +1,7 @@
@use "vars" as *; @use "vars" as *;
@use "root-vars"; @use "root-vars";
@use "button-mixins" as button; @use "button-mixins" as button;
@use "sass/scrollbar";
$body-color: color(fg); $body-color: color(fg);
$body-bg: color(canvas); $body-bg: color(canvas);
@ -43,6 +44,26 @@ html {
overscroll-behavior: none; overscroll-behavior: none;
} }
body {
&:not(.isMac),
&:not(.isMac) * {
@include scrollbar.custom;
}
&.reduce-motion,
&.reduce-motion * {
transition: none !important;
animation: none !important;
}
&.no-blur * {
backdrop-filter: none !important;
}
&:not(.fancy),
&:not(.fancy) * {
box-shadow: none !important;
backdrop-filter: none !important;
}
}
button { button {
/* override transition for instant hover response */ /* override transition for instant hover response */
transition: color var(--transition) ease-in-out, box-shadow var(--transition) ease-in-out !important; transition: color var(--transition) ease-in-out, box-shadow var(--transition) ease-in-out !important;

View file

@ -24,15 +24,22 @@
button { button {
outline: none !important; outline: none !important;
@include button.base; border: none;
border-radius: var(--border-radius-large); background: none;
padding: 8px 10px; &:hover {
background: var(--button-bg);
}
font-weight: 500; font-weight: 500;
padding: 8px 10px;
margin: 0 4px; margin: 0 4px;
.fancy & {
@include button.base;
border-radius: var(--border-radius-large);
@include elevation(1, $opacity-boost: -0.08); @include elevation(1, $opacity-boost: -0.08);
&:hover { &:hover {
@include elevation(2); @include elevation(2);
transition: box-shadow var(--transition) linear; transition: box-shadow var(--transition) linear;
} }
}
} }

View file

@ -36,19 +36,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@use "sass/elevation" as *; @use "sass/elevation" as *;
.container { .container {
width: 100%; width: 100%;
:global(.fancy) & {
background: var(--canvas-elevated); background: var(--canvas-elevated);
border: 1px solid var(--border-subtle); border: 1px solid var(--border-subtle);
border-radius: var(--border-radius-large, 10px); border-radius: var(--border-radius-medium, 10px);
padding: 1rem 1.75rem 0.75rem 1.25rem;
&.rtl {
padding: 1rem 1.25rem 0.75rem 1.75rem;
}
&:hover,
&:focus-within {
.help-badge {
color: var(--fg-subtle);
}
}
&.light { &.light {
@include elevation(2, $opacity-boost: -0.08); @include elevation(2, $opacity-boost: -0.08);
&:hover, &:hover,
@ -63,6 +55,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
@include elevation(4); @include elevation(4);
} }
} }
}
padding: 1rem 1.75rem 0.75rem 1.25rem;
&.rtl {
padding: 1rem 1.25rem 0.75rem 1.75rem;
}
&:hover,
&:focus-within {
.help-badge {
color: var(--fg-subtle);
}
}
transition: box-shadow var(--transition) ease-in-out; transition: box-shadow var(--transition) ease-in-out;
page-break-inside: avoid; page-break-inside: avoid;
} }