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;
}