diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl index 367ad74ba..089aa7d99 100644 --- a/ftl/core/preferences.ftl +++ b/ftl/core/preferences.ftl @@ -4,13 +4,13 @@ preferences-basic = Basic 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-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-learn-ahead-limit = Learn ahead limit preferences-mins = mins preferences-network = Network 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-paste-clipboard-images-as-png = Paste clipboard images as PNG 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-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-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-synchronisation = Synchronisation +preferences-synchronisation = Synchronisation preferences-synchronizationnot-currently-enabled-click-the-sync = Synchronization
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-timebox-time-limit = Timebox time limit preferences-user-interface-size = User interface size 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-default-search-text = Default search text preferences-default-search-text-example = eg. 'deck:current ' -preferences-theme-label = Theme: { $theme } +preferences-theme = Theme preferences-theme-follow-system = Follow System preferences-theme-light = Light preferences-theme-dark = Dark @@ -48,4 +48,21 @@ preferences-monthly-backups = Monthly backups to keep: preferences-minutes-between-backups = Minutes between automatic backups: preferences-reduce-motion = Reduce motion 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 diff --git a/ftl/qt/preferences.ftl b/ftl/qt/preferences.ftl index c417b9f1c..9f022cb6e 100644 --- a/ftl/qt/preferences.ftl +++ b/ftl/qt/preferences.ftl @@ -1,6 +1,6 @@ ## 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-software-mac = Software (not recommended) preferences-video-driver-opengl-other = OpenGL (faster, may cause issues) diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index c5f54ae2b..af7980d1b 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -351,6 +351,7 @@ class AnkiApp(QApplication): QCheckBox, QRadioButton, QMenu, + QSlider, # classes with PyQt5 compatibility proxy without_qt5_compat_wrapper(QToolButton), without_qt5_compat_wrapper(QTabBar), diff --git a/qt/aqt/data/web/css/deckbrowser.scss b/qt/aqt/data/web/css/deckbrowser.scss index 21d349bb3..b191a278d 100644 --- a/qt/aqt/data/web/css/deckbrowser.scss +++ b/qt/aqt/data/web/css/deckbrowser.scss @@ -7,17 +7,19 @@ table { padding: 1rem; - background: var(--canvas-elevated); - border: 1px solid var(--border-subtle); - border-radius: var(--border-radius-large); - @include elevation(1, $opacity-boost: -0.08); - &:hover { - @include elevation(2); + .fancy & { + border: 1px solid var(--border-subtle); + border-radius: var(--border-radius-medium); + + @include elevation(1, $opacity-boost: -0.08); + &:hover { + @include elevation(2); + } + transition: box-shadow var(--transition) ease-in-out; + background: var(--canvas-glass); + backdrop-filter: blur(var(--blur)); } - transition: box-shadow var(--transition) ease-in-out; - background: var(--canvas-glass); - backdrop-filter: blur(var(--blur)); } a.deck { @@ -37,7 +39,11 @@ th { } tr.deck td { - padding: 4px 12px; + padding: 2px 12px; + + .fancy & { + padding: 4px 12px; + } } tr.top-level-drag-row td { diff --git a/qt/aqt/data/web/css/toolbar.scss b/qt/aqt/data/web/css/toolbar.scss index b80cce550..f8f382d94 100644 --- a/qt/aqt/data/web/css/toolbar.scss +++ b/qt/aqt/data/web/css/toolbar.scss @@ -7,7 +7,6 @@ @use "sass/button-mixins" as button; .header { - height: 41px; display: grid; grid-template-columns: repeat(3, 1fr); align-items: start; @@ -31,65 +30,88 @@ } .toolbar { - height: 31px; justify-self: center; white-space: nowrap; - overflow: hidden; - border-bottom-left-radius: prop(border-radius-large); - border-bottom-right-radius: prop(border-radius-large); - @include elevation(1, $opacity-boost: -0.1); - - // elevated state (deck browser, overview) - body:not(.flat) & { - background: var(--canvas-elevated); - @include elevation(1); - &:hover { - @include elevation(2); - } - } - // glass effect - background: var(--canvas-glass);backdrop-filter: unset; - backdrop-filter: blur(var(--blur)); - 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; - &.collapsed { - transform: translateY(-100vh); + &: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); + + // elevated state (deck browser, overview) + body:not(.flat) & { + background: var(--canvas-elevated); + @include elevation(1); + &:hover { + @include elevation(2); + } + } + // glass effect + background: var(--canvas-glass); + backdrop-filter: blur(var(--blur)); + } + + .hitem { + &:hover { + background: var(--canvas-inset); + text-decoration: none; + } + &:active { + @include button.base($border: false); + background: var(--canvas-elevated); + } + &:first-child { + padding-left: 18px; + } + &:last-child { + padding-right: 18px; + } + } + } + &:not(.fancy) { + border-bottom: 1px solid var(--border-subtle); } - transition: transform var(--transition) ease-in-out; } * { -webkit-user-drag: none; } -.hitem { - font-weight: bold; - padding: 5px 12px; - text-decoration: none; - color: color(fg); - display: inline-block; - - body:not(.flat) &, - &:hover { - @include button.base($border: false); - background: var(--canvas-elevated); - } - &:first-child { - padding-left: 18px; - } - &:last-child { - padding-right: 18px; - } -} - .hitem:focus { outline: 0; } diff --git a/qt/aqt/data/web/css/webview.scss b/qt/aqt/data/web/css/webview.scss index 44e95d433..589387aaa 100644 --- a/qt/aqt/data/web/css/webview.scss +++ b/qt/aqt/data/web/css/webview.scss @@ -30,6 +30,11 @@ body { &.no-blur * { backdrop-filter: none !important; } + &:not(.fancy), + &:not(.fancy) * { + box-shadow: none !important; + backdrop-filter: none !important; + } } a { diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index c550fd45e..af3ce2bb9 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -6,8 +6,8 @@ 0 0 - 640 - 660 + 604 + 634 @@ -22,172 +22,404 @@ 0 + + + preferences_appearance + + + + + + + + + preferences_general + + + + + + preferences_language + + + + + + + + 0 + 0 + + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + + + + + preferences_video_driver + + + + + + + + 0 + 0 + + + + + + + + + + + preferences_user_interface + + + + + + preferences_theme + + + + + + + + + + preferences_style + + + + + + + + + + + + + + % + + + 100 + + + 200 + + + 5 + + + + + + + preferences_user_interface_size + + + + + + + + + + preferences_reviewer + + + + + + + 0 + 0 + + + + preferences_collapse_toolbar + + + + + + + + 0 + 0 + + + + preferences_show_play_buttons_on_cards_with + + + + + + + + + + preferences_distractions + + + + + + + 0 + 0 + + + + preferences_reduce_motion_tooltip + + + preferences_reduce_motion + + + + + + + + 0 + 0 + + + + preferences_minimalist_mode + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + preferences_basic - - - 12 - - - 12 - - - 12 - - - 12 - - - 12 - + - - - - - - - 0 - 0 - - - - QComboBox::AdjustToMinimumContentsLengthWithIcon + + + preferences_editing + + + + + + 0 + 0 + + + + preferences_paste_clipboard_images_as_png + + + + + + + + 0 + 0 + + + + preferences_paste_without_shift_key_strips_formatting + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + + preferences_default_deck + + + + + + + + 0 + 0 + + + + + preferences_when_adding_default_to_current_deck + + + + + preferences_change_deck_depending_on_note_type + + + + + + + - - - - - - preferences_show_play_buttons_on_cards_with - - - - - - - preferences_interrupt_current_audio_when_answering - - - - - - - preferences_paste_clipboard_images_as_png - - - - - - - preferences_paste_without_shift_key_strips_formatting - - - - - - - preferences_ignore_accents_in_search - - - - - - - Legacy import/export handling - - - - - - - preferences_reduce_motion_tooltip - - - preferences_reduce_motion - - - - - - - preferences_collapse_toolbar + + + preferences_browsing + + + + + + + preferences_default_search_text + + + + + + + preferences_default_search_text_example + + + + + + + + + + 0 + 0 + + + + preferences_ignore_accents_in_search + + + + - - - - preferences_when_adding_default_to_current_deck - - - - - preferences_change_deck_depending_on_note_type - - - - - - - - preferences_default_search_text + + + preferences_review + + + + + + 0 + 0 + + + + preferences_show_next_review_time_above_answer + + + + + + + + 0 + 0 + + + + preferences_show_remaining_card_count_during_review + + + + + + + + 0 + 0 + + + + preferences_interrupt_current_audio_when_answering + + + + - - - preferences_default_search_text_example + + + preferences_import_export + + + + + + 0 + 0 + + + + Legacy import/export handling + + + + - - - - - - preferences_user_interface_size - - - - - - - % - - - 100 - - - 200 - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - @@ -208,131 +440,169 @@ - preferences_scheduling + preferences_scheduler - - - - preferences_show_next_review_time_above_answer - - - - - - - preferences_show_remaining_card_count_during_review - - - - - - - preferences_show_learning_cards_with_larger_steps - - - - - - - preferences_legacy_timezone_handling - - - - - - - - - - preferences_v3_scheduler + + + + preferences_scheduler + + + + + + 0 + 0 + + + + + + + preferences_v3_scheduler + + + + + + + + 0 + 0 + + + + preferences_show_learning_cards_with_larger_steps + + + + + + + + 0 + 0 + + + + preferences_legacy_timezone_handling + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + - - - - - - 12 + + + preferences_limits - - - - preferences_mins - - - - - - - 9999 - - - - - - - - 60 - 16777215 - - - - 23 - - - - - - - preferences_timebox_time_limit - - - - - - - preferences_hours_past_midnight - - - - - - - preferences_learn_ahead_limit - - - - - - - preferences_mins - - - - - - - - 60 - 16777215 - - - - 999 - - - - - - - preferences_next_day_starts_at - - - - + + + + + 12 + + + + + + 60 + 16777215 + + + + 999 + + + + + + + preferences_mins + + + + + + + preferences_timebox_time_limit + + + + + + + preferences_hours_past_midnight + + + + + + + + 60 + 16777215 + + + + 23 + + + + + + + preferences_learn_ahead_limit + + + + + + + preferences_next_day_starts_at + + + + + + + preferences_mins + + + + + + + 9999 + + + + + + + @@ -370,114 +640,140 @@ 12 - - - 10 + + + preferences_synchronisation - - - - - - preferences_synchronisation - - - true - - - true - - - - - - - preferences_synchronize_audio_and_images_too - - - - - - - preferences_automatically_sync_on_profile_openclose - - - - - - - preferences_periodically_sync_media - - - - - - - preferences_on_next_sync_force_changes_in - - - - - - - - - LOGOUT - - - false - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 1 - - - - - - - - - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - + + + + + + 0 + 0 + + + + preferences_synchronize_audio_and_images_too + + + + + + + + 0 + 0 + + + + preferences_automatically_sync_on_profile_openclose + + + + + + + + 0 + 0 + + + + preferences_periodically_sync_media + + + + + + + + 0 + 0 + + + + preferences_on_next_sync_force_changes_in + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + preferences_account + + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 0 + + + + LOGOUT + + + false + + + + + @@ -515,145 +811,175 @@ 12 - - - preferences_backup_explanation + + + + 0 + 0 + - - true + + preferences_backups + + + + + preferences_backup_explanation + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + 5 + + + 9999 + + + + + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + preferences_daily_backups + + + + + + + 9999 + + + + + + + preferences_monthly_backups + + + + + + + preferences_weekly_backups + + + + + + + preferences_minutes_between_backups + + + + + + + 9999 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + preferences_you_can_restore_backups_via_fileswitch + + + + - - - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - preferences_daily_backups - - - - - - - preferences_monthly_backups - - - - - - - 9999 - - - - - - - preferences_weekly_backups - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - preferences_minutes_between_backups - - - - - - - 5 - - - 9999 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - preferences_you_can_restore_backups_via_fileswitch - - - - - - - preferences_note_media_is_not_backed_up - - - true + + + preferences_note + + + + + preferences_note_media_is_not_backed_up + + + true + + + + @@ -674,10 +1000,13 @@ - + preferences_some_settings_will_take_effect_after + + Qt::AlignCenter + @@ -693,25 +1022,27 @@ - theme lang video_driver + theme + styleComboBox + uiScale + collapse_toolbar showPlayButtons - interrupt_audio + reduce_motion + minimalist_mode pastePNG paste_strips_formatting - ignore_accents_in_search - legacy_import_export - reduce_motion - collapse_toolbar useCurrent default_search_text - uiScale + ignore_accents_in_search showEstimates showProgress + interrupt_audio + legacy_import_export + sched2021 dayLearnFirst legacy_timezone - sched2021 newSpread dayOffset lrnCutoff @@ -720,8 +1051,8 @@ syncOnProgramOpen autoSyncMedia fullSync - syncDeauth media_log + syncDeauth minutes_between_backups daily_backups weekly_backups diff --git a/qt/aqt/forms/setlang.ui b/qt/aqt/forms/setlang.ui index 6f6fde77d..6f570d6d7 100644 --- a/qt/aqt/forms/setlang.ui +++ b/qt/aqt/forms/setlang.ui @@ -17,7 +17,7 @@ - preferences_interface_language + preferences_language diff --git a/qt/aqt/forms/widgets.ui b/qt/aqt/forms/widgets.ui index 491583083..a5c8d61f5 100644 --- a/qt/aqt/forms/widgets.ui +++ b/qt/aqt/forms/widgets.ui @@ -33,13 +33,6 @@ - - - - Force Style - - - diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 53660e6a7..31c1cffe7 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1068,9 +1068,9 @@ title="{}" {}>{}""".format( if is_lin: # On Linux, the check requires invoking an external binary, # which we don't want to be doing frequently - interval_secs = 300 + interval_secs = 10 else: - interval_secs = 5 + interval_secs = 2 self.progress.timer( interval_secs * 1000, theme_manager.apply_style_if_system_style_changed, diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 0b9048ff6..8e6ec1d11 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -13,7 +13,7 @@ from aqt import AnkiQt from aqt.operations.collection import set_preferences from aqt.profiles import VideoDriver 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 @@ -207,20 +207,35 @@ class Preferences(QDialog): def setup_global(self) -> None: "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()) + qconnect( + self.form.collapse_toolbar.stateChanged, self.mw.pm.set_collapse_toolbar + ) + self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100)) themes = [ - tr.preferences_theme_label(theme=theme) - for theme in ( - tr.preferences_theme_follow_system(), - tr.preferences_theme_light(), - tr.preferences_theme_dark(), - ) + tr.preferences_theme_follow_system(), + tr.preferences_theme_light(), + tr.preferences_theme_dark(), ] self.form.theme.addItems(themes) self.form.theme.setCurrentIndex(self.mw.pm.theme().value) 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.setup_language() @@ -238,8 +253,6 @@ class Preferences(QDialog): self.mw.pm.setUiScale(newScale) 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()) if restart_required: @@ -289,14 +302,12 @@ class Preferences(QDialog): def setup_video_driver(self) -> None: self.video_drivers = VideoDriver.all_for_platform() - names = [ - tr.preferences_video_driver(driver=video_driver_name_for_platform(d)) - for d in self.video_drivers - ] + names = [video_driver_name_for_platform(d) for d in self.video_drivers] self.form.video_driver.addItems(names) self.form.video_driver.setCurrentIndex( self.video_drivers.index(self.mw.pm.video_driver()) ) + self.form.video_driver_label.setVisible(qtmajor == 5) self.form.video_driver.setVisible(qtmajor == 5) def update_video_driver(self) -> None: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 8621b22bb..01434c9b0 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -21,7 +21,7 @@ from anki.db import DB from anki.lang import without_unicode_isolation from anki.sync import SyncAuth 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.theme import AnkiStyles, Theme, theme_manager 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: 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: return self.meta.get("collapse_toolbar", False) def set_collapse_toolbar(self, on: bool) -> None: self.meta["collapse_toolbar"] = on + gui_hooks.body_classes_need_update() def last_addon_update_check(self) -> int: 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: self.meta["theme"] = theme.value - def set_forced_style(self, style: AnkiStyles | None) -> None: - if 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 - + def set_widget_style(self, style: AnkiStyles) -> None: + self.meta["widget_style"] = style theme_manager.apply_style() - def has_forced_style(self) -> bool: - for member in AnkiStyles: - if self.meta[f"force_{AnkiStyles(member).name.lower()}_styles"]: - 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 get_widget_style(self) -> AnkiStyles: + return self.meta.get( + "widget_style", AnkiStyles.NATIVE if is_mac else AnkiStyles.ANKI + ) def browser_layout(self) -> BrowserLayout: from aqt.browser.layout import BrowserLayout diff --git a/qt/aqt/stylesheets.py b/qt/aqt/stylesheets.py index 48ee06fdb..9eeace712 100644 --- a/qt/aqt/stylesheets.py +++ b/qt/aqt/stylesheets.py @@ -28,449 +28,564 @@ qlineargradient( """ -def general_styles(tm: ThemeManager) -> str: - return f""" -QFrame, -QWidget {{ - background: none; -}} -QPushButton, -QComboBox, -QSpinBox, -QLineEdit, -QListWidget, -QTreeWidget, -QListView {{ - border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QLineEdit {{ - padding: 2px; -}} -QLineEdit:focus {{ - border-color: {tm.var(colors.BORDER_FOCUS)}; -}} -QPushButton {{ - margin-top: 1px; -}} -QPushButton, -QComboBox, -QSpinBox {{ - padding: 2px 6px; -}} - """ +class CustomStyles: + def general(self, tm: ThemeManager) -> str: + return f""" + QFrame, + QWidget {{ + background: none; + }} + QPushButton, + QComboBox, + QSpinBox, + QDateTimeEdit, + QLineEdit, + QListWidget, + QTreeWidget, + QListView, + QTextEdit, + QPlainTextEdit {{ + border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QLineEdit, + QTextEdit, + QPlainTextEdit, + QDateTimeEdit, + QListWidget, + QTreeWidget, + QListView {{ + background: {tm.var(colors.CANVAS_CODE)}; + }} + QLineEdit, + QTextEdit, + QPlainTextEdit, + QDateTimeEdit {{ + padding: 2px; + }} + QSpinBox:focus, + QDateTimeEdit:focus, + QLineEdit:focus, + QTextEdit:editable:focus, + QPlainTextEdit:editable:focus, + QWidget:editable:focus {{ + border-color: {tm.var(colors.BORDER_FOCUS)}; + }} + QPushButton {{ + margin-top: 1px; + }} + QPushButton, + QComboBox, + QSpinBox {{ + 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: + return f""" + QMenuBar {{ + border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + }} + QMenuBar::item {{ + background-color: transparent; + padding: 2px 4px; + border-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QMenuBar::item:selected {{ + background-color: {tm.var(colors.CANVAS_ELEVATED)}; + }} + QMenu {{ + background-color: {tm.var(colors.CANVAS_OVERLAY)}; + border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + padding: 4px; + }} + QMenu::item {{ + background-color: transparent; + padding: 3px 14px; + margin-bottom: 4px; + }} + QMenu::item:selected {{ + background-color: {tm.var(colors.HIGHLIGHT_BG)}; + border-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QMenu::separator {{ + height: 1px; + background: {tm.var(colors.BORDER_SUBTLE)}; + margin: 0 8px 4px 8px; + }} + QMenu::indicator {{ + border: 1px solid {tm.var(colors.BORDER)}; + margin-{tm.left()}: 6px; + margin-{tm.right()}: -6px; + }} + """ -def menu_styles(tm: ThemeManager) -> str: - return f""" -QMenuBar {{ - border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)}; -}} -QMenuBar::item {{ - background-color: transparent; - padding: 2px 4px; - border-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QMenuBar::item:selected {{ - background-color: {tm.var(colors.CANVAS_ELEVATED)}; -}} -QMenu {{ - background-color: {tm.var(colors.CANVAS_OVERLAY)}; - border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - padding: 4px; -}} -QMenu::item {{ - background-color: transparent; - padding: 3px 14px; - margin-bottom: 4px; -}} -QMenu::item:selected {{ - background-color: {tm.var(colors.HIGHLIGHT_BG)}; - border-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QMenu::separator {{ - height: 1px; - background: {tm.var(colors.BORDER_SUBTLE)}; - margin: 0 8px 4px 8px; -}} -QMenu::indicator {{ - border: 1px solid {tm.var(colors.BORDER)}; - margin-{tm.left()}: 6px; - margin-{tm.right()}: -6px; -}} - """ - - -def button_styles(tm: ThemeManager) -> str: - # For some reason, Windows needs a larger padding to look the same - button_pad = 25 if is_win else 15 - return f""" -QPushButton {{ padding-left: {button_pad}px; padding-right: {button_pad}px; }} -QPushButton, -QTabBar::tab:!selected, -QComboBox:!editable, -QComboBox::drop-down:editable, -QSpinBox::up-button, -QSpinBox::down-button {{ - background: {tm.var(colors.BUTTON_BG)}; - border-bottom: 1px solid {tm.var(colors.SHADOW)}; -}} -QPushButton:hover, -QTabBar::tab:hover, -QComboBox:!editable:hover, -QSpinBox::up-button, -QSpinBox::down-button {{ - background: { - button_gradient( - tm.var(colors.BUTTON_GRADIENT_START), - tm.var(colors.BUTTON_GRADIENT_END), - ) - }; -}} -QPushButton:pressed, -QSpinBox::up-button, -QSpinBox::down-button {{ - background: { - button_pressed_gradient( - tm.var(colors.BUTTON_GRADIENT_START), - tm.var(colors.BUTTON_GRADIENT_END), - tm.var(colors.SHADOW) - ) - }; -}} - """ - - -def splitter_styles(tm: ThemeManager) -> str: - return f""" -QSplitter::handle, -QMainWindow::separator {{ - height: 16px; -}} -QSplitter::handle:vertical, -QMainWindow::separator:horizontal {{ - image: url({tm.themed_icon("mdi:drag-horizontal-FG_SUBTLE")}); -}} -QSplitter::handle:horizontal, -QMainWindow::separator:vertical {{ - image: url({tm.themed_icon("mdi:drag-vertical-FG_SUBTLE")}); -}} -""" - - -def combobox_styles(tm: ThemeManager) -> str: - return f""" -QComboBox {{ - padding: {"1px 6px 2px 4px" if tm.rtl() else "1px 4px 2px 6px"}; -}} -QComboBox:focus {{ - border-color: {tm.var(colors.BORDER_FOCUS)}; -}} -QComboBox:editable:on, -QComboBox:editable:focus, -QComboBox::drop-down:focus:editable, -QComboBox::drop-down:pressed {{ - border-color: {tm.var(colors.BORDER_FOCUS)}; -}} -QComboBox:on {{ - border-bottom: none; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; -}} -QComboBox::item {{ - color: {tm.var(colors.FG)}; - background: {tm.var(colors.CANVAS_ELEVATED)}; -}} - -QComboBox::item:selected {{ - background: {tm.var(colors.HIGHLIGHT_BG)}; - color: {tm.var(colors.HIGHLIGHT_FG)}; -}} -QComboBox::item::icon:selected {{ - position: absolute; -}} -QComboBox::drop-down {{ - subcontrol-origin: border; - padding: 2px; - padding-left: 4px; - padding-right: 4px; - width: 16px; - subcontrol-position: top right; - border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; - border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QComboBox::drop-down:!editable {{ - background: none; - border-color: transparent; -}} -QComboBox::down-arrow {{ - image: url({tm.themed_icon("mdi:chevron-down")}); -}} -QComboBox::drop-down:hover:editable {{ - background: { - button_gradient( - tm.var(colors.BUTTON_GRADIENT_START), - tm.var(colors.BUTTON_GRADIENT_END), - ) - }; -}} - """ - - -def tabwidget_styles(tm: ThemeManager) -> str: - return f""" -QTabWidget {{ - border-radius: {tm.var(props.BORDER_RADIUS)}; - background: none; -}} -QTabWidget::pane {{ - top: -15px; - padding-top: 1em; - background: {tm.var(colors.CANVAS_ELEVATED)}; - border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QTabWidget::tab-bar {{ - alignment: center; -}} -QTabBar::tab {{ - background: none; - padding: 4px 8px; - min-width: 8ex; -}} -QTabBar::tab {{ - border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-bottom-color: {tm.var(colors.SHADOW)}; -}} -QTabBar::tab:first {{ - border-top-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; - border-bottom-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QTabBar::tab:!first {{ - margin-{tm.left()}: -1px; -}} -QTabBar::tab:last {{ - border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; - border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QTabBar::tab:selected {{ - color: white; - background: {tm.var(colors.BUTTON_PRIMARY_BG)}; -}} -QTabBar::tab:selected:hover {{ - background: { + def button(self, tm: ThemeManager) -> str: + # For some reason, Windows needs a larger padding to look the same + button_pad = 25 if is_win else 15 + return f""" + QPushButton {{ padding-left: {button_pad}px; padding-right: {button_pad}px; }} + QPushButton, + QTabBar::tab:!selected, + QComboBox:!editable, + QComboBox::drop-down:editable {{ + background: {tm.var(colors.BUTTON_BG)}; + border-bottom: 1px solid {tm.var(colors.SHADOW)}; + }} + QPushButton:hover, + QTabBar::tab:hover, + QComboBox:!editable:hover, + QSpinBox::up-button:hover, + QSpinBox::down-button:hover, + QDateTimeEdit::up-button:hover, + QDateTimeEdit::down-button:hover {{ + background: { button_gradient( - tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), - tm.var(colors.BUTTON_PRIMARY_GRADIENT_END), - ) - }; -}} + tm.var(colors.BUTTON_GRADIENT_START), + tm.var(colors.BUTTON_GRADIENT_END), + ) + }; + }} + QPushButton:pressed, + QSpinBox::up-button:pressed, + QSpinBox::down-button:pressed, + QDateTimeEdit::up-button:pressed, + QDateTimeEdit::down-button:pressed {{ + background: { + button_pressed_gradient( + tm.var(colors.BUTTON_GRADIENT_START), + tm.var(colors.BUTTON_GRADIENT_END), + tm.var(colors.SHADOW) + ) + }; + }} + """ + + def splitter(self, tm: ThemeManager) -> str: + return f""" + QSplitter::handle, + QMainWindow::separator {{ + height: 16px; + }} + QSplitter::handle:vertical, + QMainWindow::separator:horizontal {{ + image: url({tm.themed_icon("mdi:drag-horizontal-FG_SUBTLE")}); + }} + QSplitter::handle:horizontal, + QMainWindow::separator:vertical {{ + image: url({tm.themed_icon("mdi:drag-vertical-FG_SUBTLE")}); + }} """ + def combobox(self, tm: ThemeManager) -> str: + return f""" + QComboBox {{ + padding: {"1px 6px 2px 4px" if tm.rtl() else "1px 4px 2px 6px"}; + }} + QComboBox:focus {{ + border-color: {tm.var(colors.BORDER_FOCUS)}; + }} + QComboBox:editable:on, + QComboBox:editable:focus, + QComboBox::drop-down:focus:editable, + QComboBox::drop-down:pressed {{ + border-color: {tm.var(colors.BORDER_FOCUS)}; + }} + QComboBox:on {{ + border-bottom: none; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + }} + QComboBox::item {{ + color: {tm.var(colors.FG)}; + background: {tm.var(colors.CANVAS_ELEVATED)}; + }} -def table_styles(tm: ThemeManager) -> str: - return f""" -QTableView {{ + QComboBox::item:selected {{ + background: {tm.var(colors.HIGHLIGHT_BG)}; + color: {tm.var(colors.HIGHLIGHT_FG)}; + }} + QComboBox::item::icon:selected {{ + position: absolute; + }} + QComboBox::drop-down {{ + subcontrol-origin: border; + padding: 2px; + padding-left: 4px; + padding-right: 4px; + width: 16px; + subcontrol-position: top right; + border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; + border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QComboBox::drop-down:!editable {{ + background: none; + border-color: transparent; + }} + QComboBox::down-arrow {{ + image: url({tm.themed_icon("mdi:chevron-down")}); + }} + QComboBox::drop-down:hover:editable {{ + background: { + button_gradient( + tm.var(colors.BUTTON_GRADIENT_START), + tm.var(colors.BUTTON_GRADIENT_END), + ) + }; + }} + """ + + def tabwidget(self, tm: ThemeManager) -> str: + return f""" + QTabWidget {{ border-radius: {tm.var(props.BORDER_RADIUS)}; - border-{tm.left()}: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - gridline-color: {tm.var(colors.BORDER_SUBTLE)}; - selection-background-color: {tm.var(colors.SELECTED_BG)}; - selection-color: {tm.var(colors.SELECTED_FG)}; -}} -QHeaderView {{ - background: {tm.var(colors.CANVAS)}; -}} -QHeaderView::section {{ - padding-{tm.left()}: 0px; - padding-{tm.right()}: 15px; - border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - background: {tm.var(colors.BUTTON_BG)}; -}} -QHeaderView::section:first {{ - margin-left: -1px; -}} -QHeaderView::section:pressed, -QHeaderView::section:pressed:!first {{ - background: { - button_pressed_gradient( - tm.var(colors.BUTTON_GRADIENT_START), - tm.var(colors.BUTTON_GRADIENT_END), - tm.var(colors.SHADOW) - ) - } -}} -QHeaderView::section:hover {{ - background: { - button_gradient( - tm.var(colors.BUTTON_GRADIENT_START), - tm.var(colors.BUTTON_GRADIENT_END), - ) - }; -}} -QHeaderView::section:first {{ - border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QHeaderView::section:!first {{ - border-left: none; -}} -QHeaderView::section:last {{ - border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)}; - border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QHeaderView::section:only-one {{ - border-left: 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-right-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QHeaderView::up-arrow, -QHeaderView::down-arrow {{ - width: 20px; - height: 20px; - margin-{tm.left()}: -20px; -}} -QHeaderView::up-arrow {{ - image: url({tm.themed_icon("mdi:menu-up")}); -}} -QHeaderView::down-arrow {{ - image: url({tm.themed_icon("mdi:menu-down")}); -}} - """ - - -def spinbox_styles(tm: ThemeManager) -> str: - return f""" -QSpinBox::up-button, -QSpinBox::down-button {{ - subcontrol-origin: border; - width: 16px; -}} -QSpinBox::up-button {{ - margin-bottom: -1px; - subcontrol-position: top right; - border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QSpinBox::down-button {{ - margin-top: -1px; - subcontrol-position: bottom right; - border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; -}} -QSpinBox::up-arrow {{ - image: url({tm.themed_icon("mdi:chevron-up")}); -}} -QSpinBox::down-arrow {{ - image: url({tm.themed_icon("mdi:chevron-down")}); -}} -QSpinBox::up-arrow, -QSpinBox::down-arrow, -QSpinBox::up-arrow:pressed, -QSpinBox::down-arrow:pressed, -QSpinBox::up-arrow:disabled:hover, QSpinBox::up-arrow:off:hover, -QSpinBox::down-arrow:disabled:hover, QSpinBox::down-arrow:off:hover {{ - width: 16px; - height: 16px; -}} -QSpinBox::up-arrow:hover, -QSpinBox::down-arrow:hover {{ - width: 20px; - height: 20px; -}} -QSpinBox::up-button:disabled, QSpinBox::up-button:off, -QSpinBox::down-button:disabled, QSpinBox::down-button:off {{ - background: {tm.var(colors.BUTTON_DISABLED)}; -}} -QSpinBox::up-arrow:off, -QSpinBox::down-arrow:off {{ - image: url({tm.themed_icon("mdi:chevron-down-FG_DISABLED")}); -}} - """ - - -def checkbox_styles(tm: ThemeManager) -> str: - return f""" -QCheckBox, -QRadioButton {{ - spacing: 8px; - margin: 2px 0; -}} -QCheckBox::indicator, -QRadioButton::indicator, -QMenu::indicator {{ - border: 1px solid {tm.var(colors.BORDER)}; - border-radius: {tm.var(props.BORDER_RADIUS)}; - background: {tm.var(colors.CANVAS_ELEVATED)}; - width: 16px; - height: 16px; -}} -QRadioButton::indicator, -QMenu::indicator:exclusive {{ - border-radius: 8px; -}} -QCheckBox::indicator:hover, -QCheckBox::indicator:checked:hover, -QRadioButton::indicator:hover, -QRadioButton::indicator:checked:hover {{ - border: 2px solid {tm.var(colors.BORDER_STRONG)}; - width: 14px; - height: 14px; -}} -QCheckBox::indicator:checked, -QRadioButton::indicator:checked, -QMenu::indicator:checked {{ - image: url({tm.themed_icon("mdi:check")}); -}} -QRadioButton::indicator:checked {{ - image: url({tm.themed_icon("mdi:circle-medium")}); -}} -QCheckBox::indicator:indeterminate {{ - image: url({tm.themed_icon("mdi:minus-thick")}); -}} - """ - - -def scrollbar_styles(tm: ThemeManager) -> str: - return f""" -QAbstractScrollArea::corner {{ background: none; - border: none; -}} -QScrollBar {{ - subcontrol-origin: content; - background-color: transparent; -}} -QScrollBar::handle {{ + }} + QTabWidget::pane {{ + top: -15px; + padding-top: 1em; + background: {tm.var(colors.CANVAS_ELEVATED)}; + border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; border-radius: {tm.var(props.BORDER_RADIUS)}; - background-color: {tm.var(colors.SCROLLBAR_BG)}; -}} -QScrollBar::handle:hover {{ - background-color: {tm.var(colors.SCROLLBAR_BG_HOVER)}; -}} -QScrollBar::handle:pressed {{ - background-color: {tm.var(colors.SCROLLBAR_BG_ACTIVE)}; -}} -QScrollBar:horizontal {{ - height: 12px; -}} -QScrollBar::handle:horizontal {{ - min-width: 60px; -}} -QScrollBar:vertical {{ - width: 12px; -}} -QScrollBar::handle:vertical {{ - min-height: 60px; -}} -QScrollBar::add-line {{ - border: none; - background: none; -}} -QScrollBar::sub-line {{ - border: none; - background: none; -}} + }} + QTabWidget::tab-bar {{ + alignment: center; + }} + QTabBar::tab {{ + background: none; + padding: 4px 8px; + min-width: 8ex; + }} + QTabBar::tab {{ + border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-bottom-color: {tm.var(colors.SHADOW)}; + }} + QTabBar::tab:first {{ + border-top-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; + border-bottom-{tm.left()}-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QTabBar::tab:!first {{ + margin-{tm.left()}: -1px; + }} + QTabBar::tab:last {{ + border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; + border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QTabBar::tab:selected {{ + color: white; + background: {tm.var(colors.BUTTON_PRIMARY_BG)}; + }} + QTabBar::tab:selected:hover {{ + background: { + button_gradient( + tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), + 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: + return f""" + QTableView {{ + border-radius: {tm.var(props.BORDER_RADIUS)}; + border-{tm.left()}: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-bottom: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + gridline-color: {tm.var(colors.BORDER_SUBTLE)}; + selection-background-color: {tm.var(colors.SELECTED_BG)}; + selection-color: {tm.var(colors.SELECTED_FG)}; + background: {tm.var(colors.CANVAS_INSET)}; + }} + QHeaderView {{ + background: {tm.var(colors.CANVAS)}; + }} + QHeaderView::section {{ + padding-{tm.left()}: 0px; + padding-{tm.right()}: 15px; + border: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + background: {tm.var(colors.BUTTON_BG)}; + }} + QHeaderView::section:first {{ + margin-left: -1px; + }} + QHeaderView::section:pressed, + QHeaderView::section:pressed:!first {{ + background: { + button_pressed_gradient( + tm.var(colors.BUTTON_GRADIENT_START), + tm.var(colors.BUTTON_GRADIENT_END), + tm.var(colors.SHADOW) + ) + } + }} + QHeaderView::section:hover {{ + background: { + button_gradient( + tm.var(colors.BUTTON_GRADIENT_START), + tm.var(colors.BUTTON_GRADIENT_END), + ) + }; + }} + QHeaderView::section:first {{ + border-left: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-top-left-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QHeaderView::section:!first {{ + border-left: none; + }} + QHeaderView::section:last {{ + border-right: 1px solid {tm.var(colors.BORDER_SUBTLE)}; + border-top-right-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QHeaderView::section:only-one {{ + border-left: 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-right-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QHeaderView::up-arrow, + QHeaderView::down-arrow {{ + width: 20px; + height: 20px; + margin-{tm.left()}: -20px; + }} + QHeaderView::up-arrow {{ + image: url({tm.themed_icon("mdi:menu-up")}); + }} + QHeaderView::down-arrow {{ + image: url({tm.themed_icon("mdi:menu-down")}); + }} + """ + + def spinbox(self, tm: ThemeManager) -> str: + return f""" + QSpinBox::up-button, + QSpinBox::down-button, + QDateTimeEdit::up-button, + QDateTimeEdit::down-button {{ + subcontrol-origin: border; + width: 16px; + margin: 1px; + }} + QSpinBox::up-button, + QDateTimeEdit::up-button {{ + margin-bottom: -1px; + subcontrol-position: top right; + border-top-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QSpinBox::down-button, + QDateTimeEdit::down-button {{ + margin-top: -1px; + subcontrol-position: bottom right; + border-bottom-{tm.right()}-radius: {tm.var(props.BORDER_RADIUS)}; + }} + QSpinBox::up-arrow, + QDateTimeEdit::up-arrow {{ + image: url({tm.themed_icon("mdi:chevron-up")}); + }} + QSpinBox::down-arrow, + QDateTimeEdit::down-arrow {{ + image: url({tm.themed_icon("mdi:chevron-down")}); + }} + QSpinBox::up-arrow, + QSpinBox::down-arrow, + QSpinBox::up-arrow:pressed, + QSpinBox::down-arrow:pressed, + QSpinBox::up-arrow:disabled:hover, QSpinBox::up-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; + height: 16px; + }} + QSpinBox::up-arrow:hover, + QSpinBox::down-arrow:hover, + QDateTimeEdit::up-arrow:hover, + QDateTimeEdit::down-arrow:hover {{ + width: 20px; + height: 20px; + }} + QSpinBox::up-button:disabled, QSpinBox::up-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)}; + }} + QSpinBox::up-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")}); + }} + """ + + def checkbox(self, tm: ThemeManager) -> str: + return f""" + QCheckBox, + QRadioButton {{ + spacing: 8px; + margin: 2px 0; + }} + QCheckBox::indicator, + QRadioButton::indicator, + QMenu::indicator {{ + border: 1px solid {tm.var(colors.BORDER)}; + border-radius: {tm.var(props.BORDER_RADIUS)}; + background: {tm.var(colors.CANVAS_ELEVATED)}; + width: 16px; + height: 16px; + }} + QRadioButton::indicator, + QMenu::indicator:exclusive {{ + border-radius: 8px; + }} + QCheckBox::indicator:hover, + QCheckBox::indicator:checked:hover, + QRadioButton::indicator:hover, + QRadioButton::indicator:checked:hover {{ + border: 2px solid {tm.var(colors.BORDER_STRONG)}; + width: 14px; + height: 14px; + }} + QCheckBox::indicator:checked, + QRadioButton::indicator:checked, + QMenu::indicator:checked {{ + image: url({tm.themed_icon("mdi:check")}); + }} + QRadioButton::indicator:checked {{ + image: url({tm.themed_icon("mdi:circle-medium")}); + }} + QCheckBox::indicator:indeterminate {{ + image: url({tm.themed_icon("mdi:minus-thick")}); + }} + """ + + def scrollbar(self, tm: ThemeManager) -> str: + return f""" + QAbstractScrollArea::corner {{ + background: none; + border: none; + }} + QScrollBar {{ + subcontrol-origin: content; + background-color: transparent; + }} + QScrollBar::handle {{ + border-radius: {tm.var(props.BORDER_RADIUS)}; + background-color: {tm.var(colors.SCROLLBAR_BG)}; + }} + QScrollBar::handle:hover {{ + background-color: {tm.var(colors.SCROLLBAR_BG_HOVER)}; + }} + QScrollBar::handle:pressed {{ + background-color: {tm.var(colors.SCROLLBAR_BG_ACTIVE)}; + }} + QScrollBar:horizontal {{ + height: 12px; + }} + QScrollBar::handle:horizontal {{ + min-width: 60px; + }} + QScrollBar:vertical {{ + width: 12px; + }} + QScrollBar::handle:vertical {{ + min-height: 60px; + }} + QScrollBar::add-line {{ + border: none; + background: none; + }} + QScrollBar::sub-line {{ + border: 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() diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py index a3ed9f38f..91fd6db3f 100644 --- a/qt/aqt/theme.py +++ b/qt/aqt/theme.py @@ -46,8 +46,7 @@ class ColoredIcon: class AnkiStyles(enum.IntEnum): ANKI = 0 - FUSION = 1 - NATIVE = 2 + NATIVE = 1 class Theme(enum.IntEnum): @@ -176,6 +175,8 @@ class ThemeManager: classes.append("macos-dark-mode") if aqt.mw.pm.reduce_motion(): classes.append("reduce-motion") + if not aqt.mw.pm.minimalist_mode(): + classes.append("fancy") if qtmajor == 5 and qtminor < 15: classes.append("no-blur") return " ".join(classes) @@ -237,36 +238,24 @@ class ThemeManager: gui_hooks.theme_did_change() 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.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, - ) + if aqt.mw.pm.get_widget_style() == AnkiStyles.ANKI: + from aqt.stylesheets import custom_styles buf += "".join( [ - general_styles(self), - button_styles(self), - checkbox_styles(self), - menu_styles(self), - combobox_styles(self), - tabwidget_styles(self), - table_styles(self), - spinbox_styles(self), - scrollbar_styles(self), + custom_styles.general(self), + custom_styles.button(self), + custom_styles.checkbox(self), + custom_styles.menu(self), + custom_styles.combobox(self), + custom_styles.tabwidget(self), + custom_styles.table(self), + custom_styles.spinbox(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: 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 palette = QPalette() diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 6e1bbd2b5..87e580107 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -52,6 +52,11 @@ class ToolbarWebView(AnkiWebView): 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: super()._onHeight(qvar) self.web_height = int(qvar) diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 5bba81fc7..6a23d576b 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -248,6 +248,7 @@ class AnkiWebView(QWebEngineView): self.resetHandlers() self._filterSet = False 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) @@ -706,6 +707,7 @@ html {{ {font} }} return 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)) 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") def get_window_bg_color(self, night_mode: Optional[bool] = None) -> QColor: return theme_manager.qcolor(colors.CANVAS) diff --git a/qt/aqt/widgetgallery.py b/qt/aqt/widgetgallery.py index 09b0bccd1..5b09eb9aa 100644 --- a/qt/aqt/widgetgallery.py +++ b/qt/aqt/widgetgallery.py @@ -5,7 +5,7 @@ import aqt import aqt.main from aqt.qt import QDialog, qconnect from aqt.theme import AnkiStyles -from aqt.utils import is_mac, restoreGeom, saveGeom +from aqt.utils import restoreGeom, saveGeom class WidgetGallery(QDialog): @@ -29,23 +29,12 @@ class WidgetGallery(QDialog): self.form.styleComboBox.addItems( [member.name.lower().capitalize() for member in AnkiStyles] ) - self.form.styleComboBox.setCurrentIndex( - 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()) - + self.form.styleComboBox.setCurrentIndex(self.mw.pm.get_widget_style()) qconnect( self.form.styleComboBox.currentIndexChanged, - self.mw.pm.set_forced_style, + self.mw.pm.set_widget_style, ) def reject(self) -> None: super().reject() - if not self.form.forceCheckBox.isChecked(): - self.mw.pm.unset_forced_styles() - saveGeom(self, "WidgetGallery") diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index f4fda4d17..b43660a03 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -613,6 +613,10 @@ hooks = [ name="theme_did_change", 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 ################### Hook( diff --git a/sass/base.scss b/sass/base.scss index 09f689596..e093eaeac 100644 --- a/sass/base.scss +++ b/sass/base.scss @@ -1,6 +1,7 @@ @use "vars" as *; @use "root-vars"; @use "button-mixins" as button; +@use "sass/scrollbar"; $body-color: color(fg); $body-bg: color(canvas); @@ -43,6 +44,26 @@ html { 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 { /* override transition for instant hover response */ transition: color var(--transition) ease-in-out, box-shadow var(--transition) ease-in-out !important; diff --git a/sass/buttons.scss b/sass/buttons.scss index 215df2cce..070e0873f 100644 --- a/sass/buttons.scss +++ b/sass/buttons.scss @@ -24,15 +24,22 @@ button { outline: none !important; - @include button.base; - border-radius: var(--border-radius-large); - padding: 8px 10px; + border: none; + background: none; + &:hover { + background: var(--button-bg); + } font-weight: 500; + padding: 8px 10px; margin: 0 4px; - @include elevation(1, $opacity-boost: -0.08); - &:hover { - @include elevation(2); - transition: box-shadow var(--transition) linear; + .fancy & { + @include button.base; + border-radius: var(--border-radius-large); + @include elevation(1, $opacity-boost: -0.08); + &:hover { + @include elevation(2); + transition: box-shadow var(--transition) linear; + } } } diff --git a/ts/components/TitledContainer.svelte b/ts/components/TitledContainer.svelte index e653c1caf..7dc295696 100644 --- a/ts/components/TitledContainer.svelte +++ b/ts/components/TitledContainer.svelte @@ -36,9 +36,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html @use "sass/elevation" as *; .container { width: 100%; - background: var(--canvas-elevated); - border: 1px solid var(--border-subtle); - border-radius: var(--border-radius-large, 10px); + :global(.fancy) & { + background: var(--canvas-elevated); + border: 1px solid var(--border-subtle); + border-radius: var(--border-radius-medium, 10px); + + &.light { + @include elevation(2, $opacity-boost: -0.08); + &:hover, + &:focus-within { + @include elevation(3); + } + } + &.dark { + @include elevation(3, $opacity-boost: -0.08); + &:hover, + &:focus-within { + @include elevation(4); + } + } + } padding: 1rem 1.75rem 0.75rem 1.25rem; &.rtl { padding: 1rem 1.25rem 0.75rem 1.75rem; @@ -49,20 +66,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html color: var(--fg-subtle); } } - &.light { - @include elevation(2, $opacity-boost: -0.08); - &:hover, - &:focus-within { - @include elevation(3); - } - } - &.dark { - @include elevation(3, $opacity-boost: -0.08); - &:hover, - &:focus-within { - @include elevation(4); - } - } transition: box-shadow var(--transition) ease-in-out; page-break-inside: avoid; }