From f169ee093384ab3509dc0a954aa3f153df574d29 Mon Sep 17 00:00:00 2001 From: Matthias Metelka <62722460+kleinerpirat@users.noreply.github.com> Date: Wed, 18 Jan 2023 12:24:16 +0100 Subject: [PATCH] Revamp Preferences, implement Minimalist Mode and Qt widget gallery to test GUI changes (#2289) * Create widget gallery dialog * Add WidgetGallery to debug dialog * Use enum for its intended purpose * Rename "reduced-motion" to "reduce-motion" * Add another border-radius value and make former large radius a bit smaller. * 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 * Indent QTabBar style definitions * Add missing styles for QPushButton states * Fix QTableView background * Remove unused layout from Preferences * Fix QTabView focused tab style * Highlight QCheckBox and QRadioButton when focused * Fix toolbar styles * Reorder preferences * Add setting to hide bottom toolbar * Move toolbar settings above minimalist modes * Remove unused lines * Implement proper full-screen mode * Sort imports * Tweak deck overview appearance in minimalist mode * Undo TitledContainer changes since nobody asked for that * Remove dynamic toolbar background from minimalist mode * Tweak buttons in minimalist mode * Fix some issues * Reduce theme check interval to 5s on Linux * Increase hide timer interval to 2s * Collapse toolbars with slight delay when moving to review state This should ensure the bottom toolbar collapses too. * Allow users to make hiding exclusive to full screen * Rename full screen option * Fix hide mode dropdown ignoring checkbox state on startup * Fix typing issue * Refine background image handling Giving the toolbar body the main webview height ensures background-size: cover behaves exactly the same. To prevent an override of other background properties, users are advised to only set background-images via the background-image property, not the background shorthand. * Fix top toolbar getting huge when switching modes The issue was caused by the min-height hack to align the background images. A call to web.adjustHeightToFit would set the toolbar to the same height as the main webview, as the function makes use of document.offsetHeight. * Prevent scrollbar from appearing on bottom toolbar resize * Cleanup * Put review tab before editing; fix some tab orders * Rename 'network' to 'syncing' * Fix bottom toolbar disappearing on UI > 100 * Improve Preferences layout by adding vertical spacers to the bottom also make the hiding of video_driver and its label more obvious in preferences.py. * Fix bottom toolbar animating on startup Also fix bottom toolbar not appearing when unchecking hide mode in reviewer. * Hide/Show menubar in fullscreen mode along with toolbar * Attempt to fix broken native theme on macOS * Format * Improve native theme on other systems by not forcing palette with the caveat that theme switching can get weird. * Fix theme switching in native style * Remove redundant condition * Add back check for Qt5 to prevent theme issues * Add check for macOS before setting fusion theme * Do not force scrollbar styles on macOS * Remove all of that crazy theme logic * Use canvas instead of button-bg for ColorRole.Button * Make sure Anki style is always based on Fusion otherwise we can't guarantee the same look on all systems. * Explicitly apply default style when Anki style is not selected This should fix the style not switching back after it was selected. * Remove reduncant default_palette * Revert 8af4c1cc2 On Mac with native theme, both Qt5 and Qt6 look correct already. On the Anki theme, without this change, we get the fusion-style scrollbars instead of the rounded ones. * Rename AnkiStyles enum to WidgetStyle * Fix theme switching shades on same theme * Format * Remove unused placeholderText that caused an error when opening the widget gallery on Qt5. * Check for full screen windowState using bitwise operator to prevent error in Qt5. Credit: https://stackoverflow.com/a/65425151 * Hide style option on Windows also exclude native option from dropdown just in case. * Format * Minor naming tweak --- ftl/core/preferences.ftl | 33 +- ftl/qt/preferences.ftl | 2 +- qt/aqt/__init__.py | 1 + qt/aqt/data/web/css/deckbrowser.scss | 44 +- qt/aqt/data/web/css/toolbar-bottom.scss | 4 + qt/aqt/data/web/css/toolbar.scss | 110 +- qt/aqt/data/web/css/webview.scss | 9 +- qt/aqt/forms/__init__.py | 1 + qt/aqt/forms/debug.ui | 24 +- qt/aqt/forms/preferences.ui | 1435 ++++++++++++++--------- qt/aqt/forms/setlang.ui | 2 +- qt/aqt/forms/widgets.py | 6 + qt/aqt/forms/widgets.ui | 375 ++++++ qt/aqt/main.py | 76 +- qt/aqt/preferences.py | 78 +- qt/aqt/profiles.py | 62 +- qt/aqt/reviewer.py | 2 - qt/aqt/stylesheets.py | 1004 +++++++++------- qt/aqt/theme.py | 89 +- qt/aqt/toolbar.py | 192 ++- qt/aqt/webview.py | 15 +- qt/aqt/widgetgallery.py | 40 + qt/tools/genhooks_gui.py | 4 + sass/_vars.scss | 8 +- sass/base.scss | 23 +- sass/buttons.scss | 23 +- ts/components/Collapsible.svelte | 2 +- ts/components/TitledContainer.svelte | 24 +- ts/deck-options/HelpModal.svelte | 2 +- ts/editor/PlainTextBadge.svelte | 2 +- ts/editor/RichTextBadge.svelte | 2 +- ts/editor/StickyBadge.svelte | 2 +- ts/reviewer/reviewer.scss | 3 +- 33 files changed, 2495 insertions(+), 1204 deletions(-) create mode 100644 qt/aqt/forms/widgets.py create mode 100644 qt/aqt/forms/widgets.ui create mode 100644 qt/aqt/widgetgallery.py diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl index a47b5f86e..3e1583192 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 = Syncing 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,6 +48,25 @@ 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-custom-sync-url = Self-hosted sync server preferences-custom-sync-url-disclaimer = For advanced users - please see the manual +preferences-hide-top-bar-during-review = Hide top bar during review +preferences-hide-bottom-bar-during-review = Hide bottom bar during review +preferences-always = Always +preferences-full-screen-only = Full screen only +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-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 3ebacdbe0..352848cfd 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -350,6 +350,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 00e875290..79949cbf4 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,13 @@ th { } tr.deck td { - padding: 4px 12px; + padding: 1px 12px; + border-bottom: 1px solid var(--border-subtle); + + .fancy & { + border: unset; + padding: 4px 12px; + } } tr.top-level-drag-row td { @@ -62,12 +70,12 @@ tr:hover:not(.top-level-drag-row) { td { background: color(border-subtle); &:first-child { - border-top-left-radius: prop(border-radius-large); - border-bottom-left-radius: prop(border-radius-large); + border-top-left-radius: prop(border-radius-medium); + border-bottom-left-radius: prop(border-radius-medium); } &:last-child { - border-top-right-radius: prop(border-radius-large); - border-bottom-right-radius: prop(border-radius-large); + border-top-right-radius: prop(border-radius-medium); + border-bottom-right-radius: prop(border-radius-medium); } .gears { visibility: visible; @@ -82,14 +90,14 @@ tr:hover:not(.top-level-drag-row) { &:first-child { border-top-left-radius: 0; border-bottom-left-radius: 0; - border-top-right-radius: prop(border-radius-large); - border-bottom-right-radius: prop(border-radius-large); + border-top-right-radius: prop(border-radius-medium); + border-bottom-right-radius: prop(border-radius-medium); } &:last-child { border-top-right-radius: 0; border-bottom-right-radius: 0; - border-top-left-radius: prop(border-radius-large); - border-bottom-left-radius: prop(border-radius-large); + border-top-left-radius: prop(border-radius-medium); + border-bottom-left-radius: prop(border-radius-medium); } } } diff --git a/qt/aqt/data/web/css/toolbar-bottom.scss b/qt/aqt/data/web/css/toolbar-bottom.scss index 28e0f1f41..eb7fdb9c8 100644 --- a/qt/aqt/data/web/css/toolbar-bottom.scss +++ b/qt/aqt/data/web/css/toolbar-bottom.scss @@ -1,6 +1,10 @@ /* Copyright: Ankitects Pty Ltd and contributors * License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */ +body { + overflow: hidden; +} + #header { border-bottom: 0; margin-top: 0; diff --git a/qt/aqt/data/web/css/toolbar.scss b/qt/aqt/data/web/css/toolbar.scss index b80cce550..4f5dda13d 100644 --- a/qt/aqt/data/web/css/toolbar.scss +++ b/qt/aqt/data/web/css/toolbar.scss @@ -7,11 +7,13 @@ @use "sass/button-mixins" as button; .header { - height: 41px; display: grid; grid-template-columns: repeat(3, 1fr); align-items: start; align-content: space-between; + body:not(.fancy) & { + border-bottom: 1px solid var(--border-subtle); + } } .left-tray { @@ -31,65 +33,89 @@ } .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).hidden { + opacity: 0; + } + transition: opacity var(--transition) ease-in-out; + + &.fancy { + margin-bottom: 5px; + + &.hidden { + transform: translateY(-100vh); + } + transition: transform var(--transition) ease-in-out; + + .toolbar { + 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); + + // glass effect + background: var(--canvas-glass); + backdrop-filter: blur(var(--blur)); + } + + // elevated state (deck browser, overview) + &:not(.flat) .toolbar { + background: var(--canvas-elevated); + @include elevation(1); + &:hover { + @include elevation(2); + } + } + + &:not(.flat) .hitem { + @include button.base($border: false, $with-hover: false); + background: var(--canvas-glass); + border: 1px solid transparent; + } + .hitem { + text-decoration: none; + border: 1px solid transparent; + + &:hover { + border: 1px solid var(--border-subtle); + } + &:active { + background: var(--canvas-inset); + } + &:first-child { + padding-left: 18px; + } + &:last-child { + padding-right: 18px; + } + } } - 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 dd954efee..589387aaa 100644 --- a/qt/aqt/data/web/css/webview.scss +++ b/qt/aqt/data/web/css/webview.scss @@ -22,14 +22,19 @@ body { &:not(.isMac) * { @include scrollbar.custom; } - &.reduced-motion, - &.reduced-motion * { + &.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; + } } a { diff --git a/qt/aqt/forms/__init__.py b/qt/aqt/forms/__init__.py index cfae51e1c..424a1f314 100644 --- a/qt/aqt/forms/__init__.py +++ b/qt/aqt/forms/__init__.py @@ -42,4 +42,5 @@ from . import ( synclog, taglimit, template, + widgets, ) diff --git a/qt/aqt/forms/debug.ui b/qt/aqt/forms/debug.ui index 5767301ac..898a42329 100644 --- a/qt/aqt/forms/debug.ui +++ b/qt/aqt/forms/debug.ui @@ -7,7 +7,7 @@ 0 0 643 - 580 + 582 @@ -48,6 +48,9 @@ QPlainTextEdit::NoWrap + + Type commands here (Enter to submit) + @@ -68,9 +71,28 @@ true + + Output + + + + + Styling + + + + + + Qt Widget Gallery + + + + + + diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index 8c7200706..999233097 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -6,8 +6,8 @@ 0 0 - 640 - 660 + 604 + 638 @@ -22,171 +22,599 @@ 0 - + - preferences_basic + preferences_appearance - - - 12 - - - 12 - - - 12 - - - 12 - - - 12 - + - - - - + - + 0 0 - - QComboBox::AdjustToMinimumContentsLengthWithIcon + + preferences_general + + + + + preferences_language + + + + + + + + 0 + 0 + + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + + + + + preferences_video_driver + + + + + + + + 0 + 0 + + + + + - - - - - - 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_user_interface + + + + + + + + + + + + + + + % + + + 100 + + + 200 + + + 5 + + + + + + + preferences_user_interface_size + + + + + + + preferences_style + + + + + + + preferences_theme + + + + - - - - preferences_when_adding_default_to_current_deck - - - - - preferences_change_deck_depending_on_note_type - - - - - - - - preferences_default_search_text + + + preferences_distractions + + + + + + 0 + 0 + + + + preferences_reduce_motion_tooltip + + + preferences_reduce_motion + + + + + + + + 0 + 0 + + + + preferences_hide_bottom_bar_during_review + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + preferences_hide_top_bar_during_review + + + + + + + + 0 + 0 + + + + preferences_minimalist_mode + + + + + + + + 0 + 0 + + + + + - - - preferences_default_search_text_example + + + Qt::Vertical + + + 20 + 40 + + + + + + + + + preferences_review + + + + + + 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 + + + + + + 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 + + + + + + + + + - - - - - preferences_user_interface_size - - - - - - - % - - - 100 - - - 200 - - - 5 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + preferences_review + + + + + + + 0 + 0 + + + + preferences_show_play_buttons_on_cards_with + + + + + + + + 0 + 0 + + + + preferences_interrupt_current_audio_when_answering + + + + + + + + 0 + 0 + + + + preferences_show_remaining_card_count_during_review + + + + + + + + 0 + 0 + + + + preferences_show_next_review_time_above_answer + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + preferences_editing + + + + + + 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_browsing + + + + + + + + preferences_default_search_text + + + + + + + preferences_default_search_text_example + + + + + + + + + + 0 + 0 + + + + preferences_ignore_accents_in_search + + + + + + + + + + preferences_import_export + + + + + + + 0 + 0 + + + + Legacy import/export handling + + + + + @@ -206,149 +634,6 @@ - - - preferences_scheduling - - - - - - 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 - - - - - - - - - - 12 - - - - - 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 - - - - - - - - - Qt::Vertical - - - - 20 - 16 - - - - - - preferences_network @@ -370,114 +655,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 + + + + + @@ -533,145 +844,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 + + + + @@ -692,10 +1033,13 @@ - + preferences_some_settings_will_take_effect_after + + Qt::AlignCenter + @@ -711,35 +1055,40 @@ - theme lang video_driver - showPlayButtons - interrupt_audio - pastePNG - paste_strips_formatting - ignore_accents_in_search - legacy_import_export - reduce_motion - collapse_toolbar - useCurrent - default_search_text + theme + styleComboBox uiScale - showEstimates - showProgress + hide_top_bar + topBarComboBox + hide_bottom_bar + bottomBarComboBox + reduce_motion + minimalist_mode + sched2021 dayLearnFirst legacy_timezone - sched2021 - newSpread dayOffset lrnCutoff timeLimit + newSpread + showPlayButtons + interrupt_audio + showProgress + showEstimates + pastePNG + paste_strips_formatting + useCurrent + default_search_text + ignore_accents_in_search + legacy_import_export syncMedia syncOnProgramOpen autoSyncMedia fullSync - syncDeauth media_log + syncDeauth custom_sync_url minutes_between_backups daily_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.py b/qt/aqt/forms/widgets.py new file mode 100644 index 000000000..b2542fc8b --- /dev/null +++ b/qt/aqt/forms/widgets.py @@ -0,0 +1,6 @@ +from aqt.qt import qtmajor + +if qtmajor > 5: + from _aqt.forms.widgets_qt6 import * +else: + from _aqt.forms.widgets_qt5 import * # type: ignore diff --git a/qt/aqt/forms/widgets.ui b/qt/aqt/forms/widgets.ui new file mode 100644 index 000000000..f82144c48 --- /dev/null +++ b/qt/aqt/forms/widgets.ui @@ -0,0 +1,375 @@ + + + Dialog + + + + 0 + 0 + 925 + 822 + + + + Qt Widget Gallery + + + + + + + + Style + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Disable Widgets + + + + + + + + + + + + Check Buttons + + + + + + RadioButton (not checkable) + + + false + + + + + + + RadioButton (checked) + + + true + + + + + + + RadioButton (unchecked) + + + + + + + CheckBox (tristate) + + + true + + + true + + + + + + + + + + Buttons + + + + + + PushButton + + + + + + + PushButton (checkable) + + + true + + + true + + + + + + + PushButton (flat) + + + true + + + true + + + + + + + + + + CalendarWidget + + + + + + + + + + + + Text Inputs + + + + + + true + + + ComboBox (editable) + + + + + + + + + + LineEdit + + + + + + + Qt::Horizontal + + + + + + + PlainTextEdit + + + + + + + + TextEdit + + + + + + + + + + + Other Inputs + + + + + + + + + + + + + + + + + + + 1 + + + + + + + KeySequenceEdit + + + + + + + DateTimeEdit + + + + + + + SpinBox + + + + + + + + + Slider + + + + + + + Qt::Horizontal + + + + + + + Dial + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + + 0 + + + + ListWidget + + + + + + + + + + TreeWidget + + + + + + + 1 + + + + + + + + + TableWidget + + + + + + 0 + + + 0 + + + + + + + + + + + + + ProgressBar + + + + + + + 24 + + + + + + + + + + + + + diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 1e98a6ec8..0359ac1bb 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -66,7 +66,7 @@ from aqt.qt import sip from aqt.sync import sync_collection, sync_login from aqt.taskman import TaskManager from aqt.theme import Theme, theme_manager -from aqt.toolbar import Toolbar, ToolbarWebView +from aqt.toolbar import BottomWebView, Toolbar, TopWebView from aqt.undo import UndoActionsInfo from aqt.utils import ( HelpPage, @@ -150,18 +150,17 @@ class MainWebView(AnkiWebView): return handled if evt.type() == QEvent.Type.Leave: - if self.mw.pm.collapse_toolbar(): - # Expand toolbar when mouse moves above main webview - # and automatically collapse it with delay after mouse leaves - if self.mapFromGlobal(QCursor.pos()).y() < self.geometry().y(): - if self.mw.toolbarWeb.collapsed: - self.mw.toolbarWeb.expand() + # Show toolbar when mouse moves outside main webview + # and automatically hide it with delay after mouse has entered again + if self.mw.pm.hide_top_bar() or self.mw.pm.hide_bottom_bar(): + self.mw.toolbarWeb.show() + self.mw.bottomWeb.show() return True if evt.type() == QEvent.Type.Enter: - if self.mw.pm.collapse_toolbar(): - self.mw.toolbarWeb.hide_timer.start() - return True + self.mw.toolbarWeb.hide_timer.start() + self.mw.bottomWeb.hide_timer.start() + return True return False @@ -170,7 +169,7 @@ class AnkiQt(QMainWindow): col: Collection pm: ProfileManagerType web: MainWebView - bottomWeb: AnkiWebView + bottomWeb: BottomWebView def __init__( self, @@ -190,6 +189,7 @@ class AnkiQt(QMainWindow): aqt.mw = self self.app = app self.pm = profileManager + self.fullscreen = False # init rest of app self.safeMode = ( bool(self.app.queryKeyboardModifiers() & Qt.KeyboardModifier.ShiftModifier) @@ -709,7 +709,7 @@ class AnkiQt(QMainWindow): gui_hooks.state_will_change(state, oldState) getattr(self, f"_{state}State", lambda *_: None)(oldState, *args) if state != "resetRequired": - self.bottomWeb.show() + self.bottomWeb.adjustHeightToFit() gui_hooks.state_did_change(state, oldState) def _deckBrowserState(self, oldState: MainWindowState) -> None: @@ -729,16 +729,23 @@ class AnkiQt(QMainWindow): def _reviewState(self, oldState: MainWindowState) -> None: self.reviewer.show() - if self.pm.collapse_toolbar(): - self.toolbarWeb.collapse() + + if self.pm.hide_top_bar(): + self.toolbarWeb.hide_timer.setInterval(500) + self.toolbarWeb.hide_timer.start() else: self.toolbarWeb.flatten() + if self.pm.hide_bottom_bar(): + self.bottomWeb.hide_timer.setInterval(500) + self.bottomWeb.hide_timer.start() + def _reviewCleanup(self, newState: MainWindowState) -> None: if newState != "resetRequired" and newState != "review": self.reviewer.cleanup() self.toolbarWeb.elevate() - self.toolbarWeb.expand() + self.toolbarWeb.show() + self.bottomWeb.show() # Resetting state ########################################################################## @@ -872,12 +879,12 @@ title="{}" {}>{}""".format( self.form = aqt.forms.main.Ui_MainWindow() self.form.setupUi(self) # toolbar - tweb = self.toolbarWeb = ToolbarWebView(self, title="top toolbar") + tweb = self.toolbarWeb = TopWebView(self, title="top toolbar") self.toolbar = Toolbar(self, tweb) # main area self.web = MainWebView(self) # bottom area - sweb = self.bottomWeb = AnkiWebView(title="bottom toolbar") + sweb = self.bottomWeb = BottomWebView(self, title="bottom toolbar") sweb.setFocusPolicy(Qt.FocusPolicy.WheelFocus) sweb.disable_zoom() # add in a layout @@ -1068,12 +1075,12 @@ 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 - else: interval_secs = 5 + else: + interval_secs = 2 self.progress.timer( interval_secs * 1000, - theme_manager.apply_style_if_system_style_changed, + theme_manager.apply_style, True, False, parent=self, @@ -1358,9 +1365,24 @@ title="{}" {}>{}""".format( window.windowState() ^ Qt.WindowState.WindowFullScreen ) - def collapse_toolbar_if_allowed(self) -> None: - if self.pm.collapse_toolbar() and self.state == "review": - self.toolbarWeb.collapse() + # Hide Menubar on Windows and Linux + if window.windowState() & Qt.WindowState.WindowFullScreen and not is_mac: + self.fullscreen = True + self.hide_menubar() + else: + self.fullscreen = False + self.show_menubar() + + # Update Toolbar states + self.toolbarWeb.hide_if_allowed() + self.bottomWeb.hide_if_allowed() + + def hide_menubar(self) -> None: + self.form.menubar.setFixedHeight(0) + + def show_menubar(self) -> None: + self.form.menubar.setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX) + self.form.menubar.setMinimumSize(0, 0) # Auto update ########################################################################## @@ -1633,6 +1655,8 @@ title="{}" {}>{}""".format( s = self.debugDiagShort = QShortcut(QKeySequence("ctrl+shift+l"), d) qconnect(s.activated, frm.text.clear) + qconnect(frm.widgetsButton.clicked, self._on_widgetGallery) + def addContextMenu( ev: Union[QCloseEvent, QContextMenuEvent], name: str ) -> None: @@ -1654,6 +1678,12 @@ title="{}" {}>{}""".format( gui_hooks.debug_console_will_show(d) d.show() + def _on_widgetGallery(self) -> None: + from aqt.widgetgallery import WidgetGallery + + self.widgetGallery = WidgetGallery(self) + self.widgetGallery.show() + def _captureOutput(self, on: bool) -> None: mw2 = self diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 51926f3a8..df60086a8 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -14,7 +14,15 @@ from aqt.operations.collection import set_preferences from aqt.profiles import VideoDriver from aqt.qt import * from aqt.theme import Theme -from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr +from aqt.utils import ( + HelpPage, + disable_help_button, + is_win, + openHelp, + showInfo, + showWarning, + tr, +) class Preferences(QDialog): @@ -209,20 +217,61 @@ 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.collapse_toolbar.setChecked(self.mw.pm.collapse_toolbar()) + 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) + + hide_choices = [tr.preferences_full_screen_only(), tr.preferences_always()] + + self.form.hide_top_bar.setChecked(self.mw.pm.hide_top_bar()) + qconnect(self.form.hide_top_bar.stateChanged, self.mw.pm.set_hide_top_bar) + qconnect( + self.form.hide_top_bar.stateChanged, + self.form.topBarComboBox.setVisible, + ) + self.form.topBarComboBox.addItems(hide_choices) + self.form.topBarComboBox.setCurrentIndex(self.mw.pm.top_bar_hide_mode()) + self.form.topBarComboBox.setVisible(self.form.hide_top_bar.isChecked()) + + qconnect( + self.form.topBarComboBox.currentIndexChanged, + self.mw.pm.set_top_bar_hide_mode, + ) + + self.form.hide_bottom_bar.setChecked(self.mw.pm.hide_bottom_bar()) + qconnect(self.form.hide_bottom_bar.stateChanged, self.mw.pm.set_hide_bottom_bar) + qconnect( + self.form.hide_bottom_bar.stateChanged, + self.form.bottomBarComboBox.setVisible, + ) + self.form.bottomBarComboBox.addItems(hide_choices) + self.form.bottomBarComboBox.setCurrentIndex(self.mw.pm.bottom_bar_hide_mode()) + self.form.bottomBarComboBox.setVisible(self.form.hide_bottom_bar.isChecked()) + + qconnect( + self.form.bottomBarComboBox.currentIndexChanged, + self.mw.pm.set_bottom_bar_hide_mode, + ) + 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(["Anki"] + (["Native"] if not is_win else [])) + self.form.styleComboBox.setCurrentIndex(self.mw.pm.get_widget_style()) + qconnect( + self.form.styleComboBox.currentIndexChanged, + self.mw.pm.set_widget_style, + ) + self.form.styleComboBox.setVisible(not is_win) self.form.legacy_import_export.setChecked(self.mw.pm.legacy_import_export()) self.setup_language() @@ -240,8 +289,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: @@ -291,15 +338,14 @@ 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.setVisible(qtmajor == 5) + if qtmajor > 5: + self.form.video_driver_label.setVisible(False) + self.form.video_driver.setVisible(False) def update_video_driver(self) -> None: new_driver = self.video_drivers[self.form.video_driver.currentIndex()] diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index a534f1bf0..c2a880c29 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -21,9 +21,10 @@ 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 Theme, theme_manager +from aqt.theme import Theme, WidgetStyle, theme_manager +from aqt.toolbar import HideMode from aqt.utils import disable_help_button, send_to_trash, showWarning, tr if TYPE_CHECKING: @@ -518,17 +519,47 @@ create table if not exists profiles def setUiScale(self, scale: float) -> None: self.meta["uiScale"] = scale - def reduced_motion(self) -> bool: - return self.meta.get("reduced_motion", False) + def reduce_motion(self) -> bool: + return self.meta.get("reduce_motion", False) - def set_reduced_motion(self, on: bool) -> None: - self.meta["reduced_motion"] = on + def set_reduce_motion(self, on: bool) -> None: + self.meta["reduce_motion"] = on + gui_hooks.body_classes_need_update() - def collapse_toolbar(self) -> bool: - return self.meta.get("collapse_toolbar", False) + def minimalist_mode(self) -> bool: + return self.meta.get("minimalist_mode", False) - def set_collapse_toolbar(self, on: bool) -> None: - self.meta["collapse_toolbar"] = on + def set_minimalist_mode(self, on: bool) -> None: + self.meta["minimalist_mode"] = on + gui_hooks.body_classes_need_update() + + def hide_top_bar(self) -> bool: + return self.meta.get("hide_top_bar", False) + + def set_hide_top_bar(self, on: bool) -> None: + self.meta["hide_top_bar"] = on + gui_hooks.body_classes_need_update() + + def top_bar_hide_mode(self) -> HideMode: + return self.meta.get("top_bar_hide_mode", HideMode.FULLSCREEN) + + def set_top_bar_hide_mode(self, mode: HideMode) -> None: + self.meta["top_bar_hide_mode"] = mode + gui_hooks.body_classes_need_update() + + def hide_bottom_bar(self) -> bool: + return self.meta.get("hide_bottom_bar", False) + + def set_hide_bottom_bar(self, on: bool) -> None: + self.meta["hide_bottom_bar"] = on + gui_hooks.body_classes_need_update() + + def bottom_bar_hide_mode(self) -> HideMode: + return self.meta.get("bottom_bar_hide_mode", HideMode.FULLSCREEN) + + def set_bottom_bar_hide_mode(self, mode: HideMode) -> None: + self.meta["bottom_bar_hide_mode"] = mode + gui_hooks.body_classes_need_update() def last_addon_update_check(self) -> int: return self.meta.get("last_addon_update_check", 0) @@ -546,11 +577,14 @@ create table if not exists profiles def set_theme(self, theme: Theme) -> None: self.meta["theme"] = theme.value - def force_custom_styles(self) -> bool: - return self.meta.get("force_custom_styles", False) + def set_widget_style(self, style: WidgetStyle) -> None: + self.meta["widget_style"] = style + theme_manager.apply_style() - def set_force_custom_styles(self, enabled: bool) -> None: - self.meta["force_custom_styles"] = enabled + def get_widget_style(self) -> WidgetStyle: + return self.meta.get( + "widget_style", WidgetStyle.NATIVE if is_mac else WidgetStyle.ANKI + ) def browser_layout(self) -> BrowserLayout: from aqt.browser.layout import BrowserLayout diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index dda339e93..eb7e1232a 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -325,7 +325,6 @@ class Reviewer: self.web.allow_drops = True self.web.eval("_blockDefaultDragDropBehavior();") # show answer / ease buttons - self.bottom.web.show() self.bottom.web.stdHtml( self._bottomHTML(), css=["css/toolbar-bottom.css", "css/reviewer-bottom.css"], @@ -706,7 +705,6 @@ time = %(time)d; else: maxTime = 0 self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime)) - self.bottom.web.adjustHeightToFit() def _showEaseButtons(self) -> None: middle = self._answerButtons() diff --git a/qt/aqt/stylesheets.py b/qt/aqt/stylesheets.py index 48ee06fdb..69bc7824b 100644 --- a/qt/aqt/stylesheets.py +++ b/qt/aqt/stylesheets.py @@ -28,449 +28,583 @@ 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:default {{ + border: 1px solid {tm.var(colors.BORDER_FOCUS)}; + }} + QPushButton:focus {{ + border: 2px solid {tm.var(colors.BORDER_FOCUS)}; + outline: none; + }} + 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:default:hover {{ + border-width: 2px; + }} + QPushButton:pressed, + QPushButton:checked, + 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) + ) + }; + }} + QPushButton:flat {{ + border: none; + }} + """ + + 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 {{ - 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( + 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)}; + 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: { + button_gradient( + tm.var(colors.BUTTON_PRIMARY_GRADIENT_START), + tm.var(colors.BUTTON_PRIMARY_GRADIENT_END), + ) + }; + }} + QTabBar::tab:focus {{ + outline: none; + }} + 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_CODE)}; + }} + 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:focus, + QCheckBox::indicator:hover, + QCheckBox::indicator:checked:hover, + QRadioButton::indicator:focus, + 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), - 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 {{ - 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; -}} - """ +custom_styles = CustomStyles() diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py index 48ac93f6d..62e835e0d 100644 --- a/qt/aqt/theme.py +++ b/qt/aqt/theme.py @@ -17,7 +17,6 @@ from anki.utils import is_lin, is_mac, is_win from aqt import QApplication, colors, gui_hooks from aqt.qt import ( QColor, - QGuiApplication, QIcon, QPainter, QPalette, @@ -44,6 +43,11 @@ class ColoredIcon: return ColoredIcon(path=self.path, color=color) +class WidgetStyle(enum.IntEnum): + ANKI = 0 + NATIVE = 1 + + class Theme(enum.IntEnum): FOLLOW_SYSTEM = 0 LIGHT = 1 @@ -56,8 +60,8 @@ class ThemeManager: _icon_cache_dark: dict[str, QIcon] = {} _icon_size = 128 _dark_mode_available: bool | None = None - default_palette: QPalette | None = None _default_style: str | None = None + _current_widget_style: WidgetStyle | None = None def rtl(self) -> bool: return is_rtl(anki.lang.current_lang) @@ -168,8 +172,10 @@ class ThemeManager: classes.extend(["nightMode", "night_mode"]) if self.macos_dark_mode(): classes.append("macos-dark-mode") - if aqt.mw.pm.reduced_motion(): - classes.append("reduced-motion") + 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) @@ -212,56 +218,50 @@ class ThemeManager: else: return get_linux_dark_mode() - def apply_style_if_system_style_changed(self) -> None: - theme = aqt.mw.pm.theme() - if theme != Theme.FOLLOW_SYSTEM: - return - if self._determine_night_mode() != self.night_mode: - self.apply_style() - def apply_style(self) -> None: "Apply currently configured style." + new_theme = self._determine_night_mode() + theme_changed = self.night_mode != new_theme + new_widget_style = aqt.mw.pm.get_widget_style() + style_changed = self._current_widget_style != new_widget_style + if not theme_changed and not style_changed: + return + self.night_mode = new_theme + self._current_widget_style = new_widget_style app = aqt.mw.app - self.night_mode = self._determine_night_mode() - if not self.default_palette: - self.default_palette = QGuiApplication.palette() + if not self._default_style: self._default_style = app.style().objectName() self._apply_palette(app) self._apply_style(app) gui_hooks.theme_did_change() def _apply_style(self, app: QApplication) -> None: - from aqt.stylesheets import splitter_styles + buf = "" - buf = splitter_styles(self) + if aqt.mw.pm.get_widget_style() == WidgetStyle.ANKI: + from aqt.stylesheets import custom_styles - if not is_mac or aqt.mw.pm.force_custom_styles(): - from aqt.stylesheets import ( - button_styles, - checkbox_styles, - combobox_styles, - general_styles, - menu_styles, - scrollbar_styles, - spinbox_styles, - table_styles, - tabwidget_styles, - ) + app.setStyle(QStyleFactory.create("fusion")) # type: ignore 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), ] ) + else: + app.setStyle(QStyleFactory.create(self._default_style)) # type: ignore + # allow addons to modify the styling buf = gui_hooks.style_did_init(buf) @@ -270,19 +270,6 @@ class ThemeManager: def _apply_palette(self, app: QApplication) -> None: set_macos_dark_mode(self.night_mode) - if is_mac and not (qtmajor == 5 or aqt.mw.pm.force_custom_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() text = self.qcolor(colors.FG) palette.setColor(QPalette.ColorRole.WindowText, text) @@ -300,7 +287,7 @@ class ThemeManager: palette.setColor(QPalette.ColorRole.Window, canvas) palette.setColor(QPalette.ColorRole.AlternateBase, canvas) - palette.setColor(QPalette.ColorRole.Button, self.qcolor(colors.BUTTON_BG)) + palette.setColor(QPalette.ColorRole.Button, canvas) input_base = self.qcolor(colors.CANVAS_CODE) palette.setColor(QPalette.ColorRole.Base, input_base) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 6e1bbd2b5..645b6240f 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -2,18 +2,25 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations +import enum import re -from typing import Any, Optional +from typing import Any, Callable, Optional, cast import aqt from anki.sync import SyncStatus -from aqt import gui_hooks +from aqt import gui_hooks, props from aqt.qt import * from aqt.sync import get_sync_status +from aqt.theme import theme_manager from aqt.utils import tr from aqt.webview import AnkiWebView +class HideMode(enum.IntEnum): + FULLSCREEN = 0 + ALWAYS = 1 + + # wrapper class for set_bridge_command() class TopToolbar: def __init__(self, toolbar: Toolbar) -> None: @@ -27,45 +34,94 @@ class BottomToolbar: class ToolbarWebView(AnkiWebView): + hide_condition: Callable[..., bool] + def __init__(self, mw: aqt.AnkiQt, title: str) -> None: AnkiWebView.__init__(self, mw, title=title) self.mw = mw self.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.disable_zoom() - self.collapsed = False - self.web_height = 0 - # collapse timer + self.hidden = False self.hide_timer = QTimer() self.hide_timer.setSingleShot(True) - self.hide_timer.setInterval(1000) - qconnect(self.hide_timer.timeout, self.mw.collapse_toolbar_if_allowed) + self.reset_timer() + + def reset_timer(self) -> None: + self.hide_timer.stop() + self.hide_timer.setInterval(2000) + + def hide(self) -> None: + self.hidden = True + + def show(self) -> None: + self.hidden = False + + +class TopWebView(ToolbarWebView): + def __init__(self, mw: aqt.AnkiQt, title: str) -> None: + super().__init__(mw, title=title) + self.web_height = 0 + qconnect(self.hide_timer.timeout, self.hide_if_allowed) def eventFilter(self, obj, evt): if handled := super().eventFilter(obj, evt): return handled - # prevent collapse if pointer inside + # prevent collapse of both toolbars if pointer is inside one of them if evt.type() == QEvent.Type.Enter: - self.hide_timer.stop() - self.hide_timer.setInterval(1000) + self.reset_timer() + self.mw.bottomWeb.reset_timer() return True return False + def on_body_classes_need_update(self) -> None: + super().on_body_classes_need_update() + + if self.mw.state == "review": + if self.mw.pm.hide_top_bar(): + self.eval("""document.body.classList.remove("flat"); """) + else: + self.flatten() + + self.show() + def _onHeight(self, qvar: Optional[int]) -> None: super()._onHeight(qvar) self.web_height = int(qvar) - def collapse(self) -> None: - self.collapsed = True - self.eval("""document.body.classList.add("collapsed"); """) + def hide_if_allowed(self) -> None: + if self.mw.state != "review": + return - def expand(self) -> None: - self.collapsed = False - self.eval("""document.body.classList.remove("collapsed"); """) + if self.mw.pm.hide_top_bar(): + if ( + self.mw.pm.top_bar_hide_mode() == HideMode.FULLSCREEN + and not self.mw.windowState() & Qt.WindowState.WindowFullScreen + ): + self.show() + return + + self.hide() + + def hide(self) -> None: + super().hide() + + self.hidden = True + self.eval( + """document.body.classList.add("hidden"); """, + ) + if self.mw.fullscreen: + self.mw.hide_menubar() + + def show(self) -> None: + super().show() + + self.eval("""document.body.classList.remove("hidden"); """) + self.mw.show_menubar() def flatten(self) -> None: - self.eval("document.body.classList.add('flat'); ") + self.eval("""document.body.classList.add("flat"); """) def elevate(self) -> None: self.eval( @@ -76,15 +132,24 @@ class ToolbarWebView(AnkiWebView): ) def update_background_image(self) -> None: - def set_background(val: str) -> None: + if self.mw.pm.minimalist_mode(): + return + + def set_background(computed: str) -> None: # remove offset from copy - background = re.sub(r"-\d+px ", "0%", val) + background = re.sub(r"-\d+px ", "0%", computed) + # ensure alignment with main webview + background = re.sub(r"\sfixed", "", background) # change computedStyle px value back to 100vw background = re.sub(r"\d+px", "100vw", background) self.eval( - f"""document.body.style.setProperty("background", '{background}'); """ + f""" + document.body.style.setProperty("background", '{background}'); + """ ) + self.set_body_height(self.mw.web.height()) + # offset reviewer background by toolbar height self.mw.web.eval( f"""document.body.style.setProperty("background-position-y", "-{self.web_height}px"); """ @@ -95,6 +160,93 @@ class ToolbarWebView(AnkiWebView): set_background, ) + def set_body_height(self, height: int) -> None: + self.eval( + f"""document.body.style.setProperty("min-height", "{self.mw.web.height()}px"); """ + ) + + def adjustHeightToFit(self) -> None: + self.eval("""document.body.style.setProperty("min-height", "0px"); """) + self.evalWithCallback("document.documentElement.offsetHeight", self._onHeight) + + def resizeEvent(self, event: QResizeEvent) -> None: + super().resizeEvent(event) + + self.mw.web.evalWithCallback( + """window.innerHeight; """, + self.set_body_height, + ) + + +class BottomWebView(ToolbarWebView): + def __init__(self, mw: aqt.AnkiQt, title: str) -> None: + super().__init__(mw, title=title) + qconnect(self.hide_timer.timeout, self.hide_if_allowed) + + def eventFilter(self, obj, evt): + if handled := super().eventFilter(obj, evt): + return handled + + if evt.type() == QEvent.Type.Enter: + self.reset_timer() + self.mw.toolbarWeb.reset_timer() + return True + + return False + + def on_body_classes_need_update(self) -> None: + super().on_body_classes_need_update() + if self.mw.state == "review": + self.show() + + def animate_height(self, height: int) -> None: + self.web_height = height + + if self.mw.pm.reduce_motion(): + self.setFixedHeight(height) + else: + # Collapse/Expand animation + self.setMinimumHeight(0) + self.animation = QPropertyAnimation( + self, cast(QByteArray, b"maximumHeight") + ) + self.animation.setDuration(int(theme_manager.var(props.TRANSITION))) + self.animation.setStartValue(self.height()) + self.animation.setEndValue(height) + qconnect(self.animation.finished, lambda: self.setFixedHeight(height)) + self.animation.start() + + def hide_if_allowed(self) -> None: + if self.mw.state != "review": + return + + if self.mw.pm.hide_bottom_bar(): + if ( + self.mw.pm.bottom_bar_hide_mode() == HideMode.FULLSCREEN + and not self.mw.windowState() & Qt.WindowState.WindowFullScreen + ): + self.show() + return + + self.hide() + + def hide(self) -> None: + super().hide() + + self.hidden = True + self.animate_height(1) + + def show(self) -> None: + super().show() + + self.hidden = False + if self.mw.state == "review": + self.evalWithCallback( + "document.documentElement.offsetHeight", self.animate_height + ) + else: + self.adjustHeightToFit() + class Toolbar: def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None: diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 5bba81fc7..694849875 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) @@ -410,8 +411,7 @@ class AnkiWebView(QWebEngineView): return 3 def standard_css(self) -> str: - palette = theme_manager.default_palette - color_hl = palette.color(QPalette.ColorRole.Highlight).name() + color_hl = theme_manager.var(colors.BORDER_FOCUS) if is_win: # T: include a font for your language on Windows, eg: "Segoe UI", "MS Mincho" @@ -706,6 +706,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 +734,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 new file mode 100644 index 000000000..16011b967 --- /dev/null +++ b/qt/aqt/widgetgallery.py @@ -0,0 +1,40 @@ +# Copyright: Ankitects Pty Ltd and contributors +# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +import aqt +import aqt.main +from aqt.qt import QDialog, qconnect +from aqt.theme import WidgetStyle +from aqt.utils import restoreGeom, saveGeom + + +class WidgetGallery(QDialog): + silentlyClose = True + + def __init__(self, mw: aqt.main.AnkiQt) -> None: + super().__init__(mw) + self.mw = mw.weakref() + + self.form = aqt.forms.widgets.Ui_Dialog() + self.form.setupUi(self) + restoreGeom(self, "WidgetGallery") + + qconnect( + self.form.disableCheckBox.stateChanged, + lambda: self.form.testGrid.setEnabled( + not self.form.disableCheckBox.isChecked() + ), + ) + + self.form.styleComboBox.addItems( + [member.name.lower().capitalize() for member in WidgetStyle] + ) + self.form.styleComboBox.setCurrentIndex(self.mw.pm.get_widget_style()) + qconnect( + self.form.styleComboBox.currentIndexChanged, + self.mw.pm.set_widget_style, + ) + + def reject(self) -> None: + super().reject() + 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/_vars.scss b/sass/_vars.scss index ef10bca04..7770763cd 100644 --- a/sass/_vars.scss +++ b/sass/_vars.scss @@ -25,8 +25,14 @@ $vars: ( default: 5px, ), ), + medium: ( + "Used for container corners", + ( + default: 12px, + ), + ), large: ( - "Used for big centered buttons", + "Used for pill-shaped buttons", ( default: 15px, ), diff --git a/sass/base.scss b/sass/base.scss index fd8b0e148..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; @@ -57,7 +78,7 @@ samp { unicode-bidi: normal !important; } -.reduced-motion * { +.reduce-motion * { transition: none !important; animation: none !important; } diff --git a/sass/buttons.scss b/sass/buttons.scss index 215df2cce..7e87a5664 100644 --- a/sass/buttons.scss +++ b/sass/buttons.scss @@ -24,15 +24,24 @@ button { outline: none !important; - @include button.base; - border-radius: var(--border-radius-large); - padding: 8px 10px; + background: var(--button-bg); + border-radius: var(--border-radius); + border: 1px solid var(--border-subtle); + &:hover { + background: var(--button-gradient-start); + border: 1px solid var(--border); + } 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/Collapsible.svelte b/ts/components/Collapsible.svelte index 247a31ed2..b6fb6ad7e 100644 --- a/ts/components/Collapsible.svelte +++ b/ts/components/Collapsible.svelte @@ -9,7 +9,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html export let collapse = false; export let toggleDisplay = false; - export let animated = !document.body.classList.contains("reduced-motion"); + export let animated = !document.body.classList.contains("reduce-motion"); let contentHeight = 0; diff --git a/ts/components/TitledContainer.svelte b/ts/components/TitledContainer.svelte index e653c1caf..71da94ccb 100644 --- a/ts/components/TitledContainer.svelte +++ b/ts/components/TitledContainer.svelte @@ -38,17 +38,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html width: 100%; background: var(--canvas-elevated); border: 1px solid var(--border-subtle); - border-radius: var(--border-radius-large, 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); - } - } + border-radius: var(--border-radius-medium, 10px); + &.light { @include elevation(2, $opacity-boost: -0.08); &:hover, @@ -63,6 +54,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html @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; page-break-inside: avoid; } diff --git a/ts/deck-options/HelpModal.svelte b/ts/deck-options/HelpModal.svelte index da6b8ce1b..0b2d06bcb 100644 --- a/ts/deck-options/HelpModal.svelte +++ b/ts/deck-options/HelpModal.svelte @@ -158,7 +158,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html .modal-content { background-color: var(--canvas); color: var(--fg); - border-radius: var(--border-radius-large, 10px); + border-radius: var(--border-radius-medium, 10px); } .invert { diff --git a/ts/editor/PlainTextBadge.svelte b/ts/editor/PlainTextBadge.svelte index c190f75ef..a82f1f428 100644 --- a/ts/editor/PlainTextBadge.svelte +++ b/ts/editor/PlainTextBadge.svelte @@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { context as editorFieldContext } from "./EditorField.svelte"; import { plainTextIcon } from "./icons"; - const animated = !document.body.classList.contains("reduced-motion"); + const animated = !document.body.classList.contains("reduce-motion"); const editorField = editorFieldContext.get(); const keyCombination = "Control+Shift+X"; diff --git a/ts/editor/RichTextBadge.svelte b/ts/editor/RichTextBadge.svelte index 280237648..2fce343d6 100644 --- a/ts/editor/RichTextBadge.svelte +++ b/ts/editor/RichTextBadge.svelte @@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { context as editorFieldContext } from "./EditorField.svelte"; import { richTextIcon } from "./icons"; - const animated = !document.body.classList.contains("reduced-motion"); + const animated = !document.body.classList.contains("reduce-motion"); const editorField = editorFieldContext.get(); const keyCombination = "Control+Shift+X"; diff --git a/ts/editor/StickyBadge.svelte b/ts/editor/StickyBadge.svelte index c14793dc0..5790daa9e 100644 --- a/ts/editor/StickyBadge.svelte +++ b/ts/editor/StickyBadge.svelte @@ -12,7 +12,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import { context as editorFieldContext } from "./EditorField.svelte"; import { stickyIcon } from "./icons"; - const animated = !document.body.classList.contains("reduced-motion"); + const animated = !document.body.classList.contains("reduce-motion"); export let active: boolean; export let show: boolean; diff --git a/ts/reviewer/reviewer.scss b/ts/reviewer/reviewer.scss index 086b84245..5f1e9d97f 100644 --- a/ts/reviewer/reviewer.scss +++ b/ts/reviewer/reviewer.scss @@ -11,8 +11,9 @@ body { margin: 20px; overflow-wrap: break-word; // default background setting to fit with toolbar - background-size: 100vw; + background-size: cover; background-repeat: no-repeat; + background-position: top; background-attachment: fixed; }