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
This commit is contained in:
Matthias Metelka 2023-01-18 12:24:16 +01:00 committed by GitHub
parent c923553a53
commit f169ee0933
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 2495 additions and 1204 deletions

View file

@ -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 = <b>Synchronisation</b>
preferences-synchronisation = Synchronisation
preferences-synchronizationnot-currently-enabled-click-the-sync = <b>Synchronization</b><br> Not currently enabled; click the sync button in the main window to enable.
preferences-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

View file

@ -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)

View file

@ -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),

View file

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

View file

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

View file

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

View file

@ -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 {

View file

@ -42,4 +42,5 @@ from . import (
synclog,
taglimit,
template,
widgets,
)

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>643</width>
<height>580</height>
<height>582</height>
</rect>
</property>
<property name="windowTitle">
@ -48,6 +48,9 @@
<property name="lineWrapMode">
<enum>QPlainTextEdit::NoWrap</enum>
</property>
<property name="placeholderText">
<string notr="true">Type commands here (Enter to submit)</string>
</property>
</widget>
<widget class="QPlainTextEdit" name="log">
<property name="sizePolicy">
@ -68,9 +71,28 @@
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string notr="true">Output</string>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string notr="true">Styling</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPushButton" name="widgetsButton">
<property name="text">
<string notr="true">Qt Widget Gallery</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>

File diff suppressed because it is too large Load diff

View file

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

6
qt/aqt/forms/widgets.py Normal file
View file

@ -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

375
qt/aqt/forms/widgets.ui Normal file
View file

@ -0,0 +1,375 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>925</width>
<height>822</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Qt Widget Gallery</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="topLayout">
<item>
<widget class="QLabel" name="styleLabel">
<property name="text">
<string notr="true">Style</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="styleComboBox">
<property name="currentText">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="disableCheckBox">
<property name="text">
<string notr="true">Disable Widgets</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="testGrid" native="true">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QGroupBox" name="checkButtonsGroup">
<property name="title">
<string notr="true">Check Buttons</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="radioButtonNotCheckable">
<property name="text">
<string notr="true">RadioButton (not checkable)</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButtonChecked">
<property name="text">
<string notr="true">RadioButton (checked)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButtonUnchecked">
<property name="text">
<string notr="true">RadioButton (unchecked)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxTristate">
<property name="text">
<string notr="true">CheckBox (tristate)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="1">
<widget class="QGroupBox" name="buttonsGroup">
<property name="title">
<string notr="true">Buttons</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string notr="true">PushButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonCheckable">
<property name="text">
<string notr="true">PushButton (checkable)</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonFlat">
<property name="text">
<string notr="true">PushButton (flat)</string>
</property>
<property name="autoDefault">
<bool>true</bool>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="calendarGroup">
<property name="title">
<string notr="true">CalendarWidget</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCalendarWidget" name="calendarWidget"/>
</item>
</layout>
</widget>
</item>
<item row="1" column="1">
<widget class="QGroupBox" name="textInputsGroup">
<property name="title">
<string notr="true">Text Inputs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QComboBox" name="comboBox">
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string notr="true">ComboBox (editable)</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit">
<property name="text">
<string notr="true"/>
</property>
<property name="placeholderText">
<string notr="true">LineEdit</string>
</property>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QPlainTextEdit" name="plainTextEdit">
<property name="plainText">
<string notr="true"/>
</property>
<property name="placeholderText">
<string notr="true">PlainTextEdit</string>
</property>
</widget>
<widget class="QTextEdit" name="textEdit">
<property name="documentTitle">
<string notr="true"/>
</property>
<property name="placeholderText">
<string notr="true">TextEdit</string>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="otherInputsGroup">
<property name="title">
<string notr="true">Other Inputs</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1" colspan="2">
<widget class="QKeySequenceEdit" name="keySequenceEdit">
<property name="keySequence">
<string notr="true"/>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QSpinBox" name="spinBox">
<property name="suffix">
<string notr="true"/>
</property>
<property name="prefix">
<string notr="true"/>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="keySequenceLabel">
<property name="text">
<string notr="true">KeySequenceEdit</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="dateTimeLabel">
<property name="text">
<string notr="true">DateTimeEdit</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="spinBoxLabel">
<property name="text">
<string notr="true">SpinBox</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="sliderLabel">
<property name="text">
<string notr="true">Slider</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="horizontalSlider">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true">Dial</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="dial"/>
</item>
</layout>
</item>
<item row="1" column="1" colspan="2">
<widget class="QDateTimeEdit" name="dateTimeEdit"/>
</item>
</layout>
</widget>
</item>
<item row="2" column="1">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="listTab">
<attribute name="title">
<string notr="true">ListWidget</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QListWidget" name="listWidget"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="treeTab">
<attribute name="title">
<string notr="true">TreeWidget</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QTreeWidget" name="treeWidget">
<column>
<property name="text">
<string notr="true">1</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tableTab">
<attribute name="title">
<string notr="true">TableWidget</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="QTableWidget" name="tableWidget">
<property name="rowCount">
<number>0</number>
</property>
<property name="columnCount">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="progressBarLayout">
<item>
<widget class="QLabel" name="progressBarLabel">
<property name="text">
<string notr="true">ProgressBar</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -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="{}" {}>{}</button>""".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="{}" {}>{}</button>""".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="{}" {}>{}</button>""".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="{}" {}>{}</button>""".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="{}" {}>{}</button>""".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

View file

@ -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()]

View file

@ -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

View file

@ -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()

File diff suppressed because it is too large Load diff

View file

@ -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)

View file

@ -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:

View file

@ -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)

40
qt/aqt/widgetgallery.py Normal file
View file

@ -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")

View file

@ -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(

View file

@ -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,
),

View file

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

View file

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

View file

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

View file

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

View file

@ -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 {

View file

@ -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";

View file

@ -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";

View file

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

View file

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