mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
basic night mode support
Forces the Fusion theme when running night mode, so we don't need to work around platform themes that don't respond to the defined palette. Feedback/suggestions on the chosen colours welcome - _vars.scss is the file to change if you want to experiment with adjustments.
This commit is contained in:
parent
44f2f16546
commit
7dcbc7efec
26 changed files with 430 additions and 115 deletions
|
@ -226,11 +226,12 @@ def entsToTxt(html: str) -> str:
|
|||
return reEnts.sub(fixup, html)
|
||||
|
||||
|
||||
# legacy function
|
||||
def bodyClass(col, card) -> str:
|
||||
bodyclass = "card card%d" % (card.ord + 1)
|
||||
if col.conf.get("nightMode"):
|
||||
bodyclass += " nightMode"
|
||||
return bodyclass
|
||||
from aqt.theme import theme_manager
|
||||
|
||||
print("bodyClass() deprecated")
|
||||
return theme_manager.body_classes_for_card_ord(card.ord)
|
||||
|
||||
|
||||
# IDs
|
||||
|
|
|
@ -5,7 +5,7 @@ MAKEFLAGS += --warn-undefined-variables
|
|||
MAKEFLAGS += --no-builtin-rules
|
||||
.SUFFIXES:
|
||||
|
||||
BLACKARGS := -t py36 aqt tests setup.py tools/*.py --exclude='aqt/forms|buildinfo'
|
||||
BLACKARGS := -t py36 aqt tests setup.py tools/*.py --exclude='aqt/forms|buildinfo|colors'
|
||||
ISORTARGS := aqt tests setup.py
|
||||
|
||||
$(shell mkdir -p .build ../dist)
|
||||
|
@ -33,6 +33,7 @@ TSDEPS := $(wildcard ts/src/*.ts) $(wildcard ts/scss/*.scss)
|
|||
|
||||
.build/js: $(TSDEPS)
|
||||
(cd ts && npm i && npm run build)
|
||||
python ./tools/extract_scss_colors.py
|
||||
@touch $@
|
||||
|
||||
.build/hooks: tools/genhooks_gui.py ../pylib/tools/hookslib.py
|
||||
|
|
1
qt/aqt/.gitignore
vendored
1
qt/aqt/.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
buildinfo.py
|
||||
colors.py
|
||||
|
|
|
@ -20,19 +20,12 @@ from anki.consts import *
|
|||
from anki.lang import _, ngettext
|
||||
from anki.models import NoteType
|
||||
from anki.notes import Note
|
||||
from anki.utils import (
|
||||
bodyClass,
|
||||
fmtTimeSpan,
|
||||
htmlToTextLine,
|
||||
ids2str,
|
||||
intTime,
|
||||
isMac,
|
||||
isWin,
|
||||
)
|
||||
from anki.utils import fmtTimeSpan, htmlToTextLine, ids2str, intTime, isMac, isWin
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.editor import Editor
|
||||
from aqt.qt import *
|
||||
from aqt.sound import av_player
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
MenuList,
|
||||
SubMenu,
|
||||
|
@ -366,16 +359,6 @@ class DataModel(QAbstractTableModel):
|
|||
# Line painter
|
||||
######################################################################
|
||||
|
||||
COLOUR_SUSPENDED = "#FFFFB2"
|
||||
COLOUR_MARKED = "#ccc"
|
||||
|
||||
flagColours = {
|
||||
1: "#ffaaaa",
|
||||
2: "#ffb347",
|
||||
3: "#82E0AA",
|
||||
4: "#85C1E9",
|
||||
}
|
||||
|
||||
|
||||
class StatusDelegate(QItemDelegate):
|
||||
def __init__(self, browser, model):
|
||||
|
@ -399,13 +382,13 @@ class StatusDelegate(QItemDelegate):
|
|||
|
||||
col = None
|
||||
if c.userFlag() > 0:
|
||||
col = flagColours[c.userFlag()]
|
||||
col = theme_manager.qcolor(f"flag{c.userFlag()}-bg")
|
||||
elif c.note().hasTag("Marked"):
|
||||
col = COLOUR_MARKED
|
||||
col = theme_manager.qcolor("marked-bg")
|
||||
elif c.queue == -1:
|
||||
col = COLOUR_SUSPENDED
|
||||
col = theme_manager.qcolor("suspended-bg")
|
||||
if col:
|
||||
brush = QBrush(QColor(col))
|
||||
brush = QBrush(col)
|
||||
painter.save()
|
||||
painter.fillRect(option.rect, brush)
|
||||
painter.restore()
|
||||
|
@ -450,7 +433,6 @@ class SidebarModel(QAbstractItemModel):
|
|||
def __init__(self, root: SidebarItem) -> None:
|
||||
super().__init__()
|
||||
self.root = root
|
||||
self.iconCache: Dict[str, QIcon] = {}
|
||||
|
||||
# Qt API
|
||||
######################################################################
|
||||
|
@ -510,18 +492,11 @@ class SidebarModel(QAbstractItemModel):
|
|||
elif role == Qt.ToolTipRole:
|
||||
return QVariant(item.tooltip)
|
||||
else:
|
||||
return QVariant(self.iconFromRef(item.icon))
|
||||
return QVariant(theme_manager.icon_from_resources(item.icon))
|
||||
|
||||
# Helpers
|
||||
######################################################################
|
||||
|
||||
def iconFromRef(self, iconRef: str) -> QIcon:
|
||||
icon = self.iconCache.get(iconRef)
|
||||
if icon is None:
|
||||
icon = QIcon(iconRef)
|
||||
self.iconCache[iconRef] = icon
|
||||
return icon
|
||||
|
||||
def expandWhereNeccessary(self, tree: QTreeView) -> None:
|
||||
for row, child in enumerate(self.root.children):
|
||||
if child.expanded:
|
||||
|
@ -821,9 +796,11 @@ class Browser(QMainWindow):
|
|||
self.form.tableView.selectionModel()
|
||||
self.form.tableView.setItemDelegate(StatusDelegate(self, self.model))
|
||||
self.form.tableView.selectionModel().selectionChanged.connect(self.onRowChanged)
|
||||
self.form.tableView.setStyleSheet(
|
||||
"QTableView{ selection-background-color: rgba(127, 127, 127, 50); }"
|
||||
)
|
||||
if not theme_manager.night_mode:
|
||||
self.form.tableView.setStyleSheet(
|
||||
"QTableView{ selection-background-color: rgba(150, 150, 150, 50); "
|
||||
"selection-color: black; }"
|
||||
)
|
||||
self.singleCard = False
|
||||
|
||||
def setupEditor(self):
|
||||
|
@ -1709,7 +1686,7 @@ where id in %s"""
|
|||
txt = c.a()
|
||||
txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt)
|
||||
|
||||
bodyclass = bodyClass(self.mw.col, c)
|
||||
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
||||
|
||||
if self.mw.reviewer.autoplay(c):
|
||||
# if we're showing both sides at once, play question audio first
|
||||
|
|
|
@ -9,10 +9,11 @@ import re
|
|||
import aqt
|
||||
from anki.consts import *
|
||||
from anki.lang import _, ngettext
|
||||
from anki.utils import bodyClass, isMac, isWin, joinFields
|
||||
from anki.utils import isMac, isWin, joinFields
|
||||
from aqt import gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.sound import av_player
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
askUser,
|
||||
downArrow,
|
||||
|
@ -336,7 +337,7 @@ Please create a new card type first."""
|
|||
c = self.card
|
||||
ti = self.maybeTextInput
|
||||
|
||||
bodyclass = bodyClass(self.mw.col, c)
|
||||
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
||||
|
||||
q = ti(mungeQA(self.mw.col, c.q(reload=True)))
|
||||
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
|
||||
|
|
|
@ -209,16 +209,16 @@ where id > ?""",
|
|||
name,
|
||||
)
|
||||
# due counts
|
||||
def nonzeroColour(cnt, colour):
|
||||
def nonzeroColour(cnt, klass):
|
||||
if not cnt:
|
||||
colour = "#e0e0e0"
|
||||
klass = "zero-count"
|
||||
if cnt >= 1000:
|
||||
cnt = "1000+"
|
||||
return "<font color='%s'>%s</font>" % (colour, cnt)
|
||||
return f'<span class="{klass}">{cnt}</span>'
|
||||
|
||||
buf += "<td align=right>%s</td><td align=right>%s</td>" % (
|
||||
nonzeroColour(due, "#007700"),
|
||||
nonzeroColour(new, "#000099"),
|
||||
nonzeroColour(due, "review-count"),
|
||||
nonzeroColour(new, "new-count"),
|
||||
)
|
||||
# options
|
||||
buf += (
|
||||
|
|
|
@ -442,10 +442,10 @@ class Editor:
|
|||
self.web.evalWithCallback("saveNow(%d)" % keepFocus, lambda res: callback())
|
||||
|
||||
def checkValid(self):
|
||||
cols = ["#fff"] * len(self.note.fields)
|
||||
cols = [""] * len(self.note.fields)
|
||||
err = self.note.dupeOrEmpty()
|
||||
if err == 2:
|
||||
cols[0] = "#fcc"
|
||||
cols[0] = "dupe"
|
||||
self.web.eval("showDupes();")
|
||||
else:
|
||||
self.web.eval("hideDupes();")
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import faulthandler
|
||||
import gc
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import signal
|
||||
import time
|
||||
|
@ -37,6 +36,7 @@ from aqt.profiles import ProfileManager as ProfileManagerType
|
|||
from aqt.qt import *
|
||||
from aqt.qt import sip
|
||||
from aqt.taskman import TaskManager
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
askUser,
|
||||
checkInvalidFilename,
|
||||
|
@ -58,6 +58,7 @@ class AnkiQt(QMainWindow):
|
|||
col: _Collection
|
||||
pm: ProfileManagerType
|
||||
web: aqt.webview.AnkiWebView
|
||||
bottomWeb: aqt.webview.AnkiWebView
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -111,9 +112,9 @@ class AnkiQt(QMainWindow):
|
|||
self.setupMediaServer()
|
||||
self.setupSound()
|
||||
self.setupSpellCheck()
|
||||
self.setupStyle()
|
||||
self.setupMainWindow()
|
||||
self.setupSystemSpecific()
|
||||
self.setupStyle()
|
||||
self.setupMenus()
|
||||
self.setupProgress()
|
||||
self.setupErrorHandler()
|
||||
|
@ -850,33 +851,8 @@ title="%s" %s>%s</button>""" % (
|
|||
return True
|
||||
|
||||
def setupStyle(self) -> None:
|
||||
buf = ""
|
||||
|
||||
if isWin and platform.release() == "10":
|
||||
# add missing bottom border to menubar
|
||||
buf += """
|
||||
QMenuBar {
|
||||
border-bottom: 1px solid #aaa;
|
||||
background: white;
|
||||
}
|
||||
"""
|
||||
# qt bug? setting the above changes the browser sidebar
|
||||
# to white as well, so set it back
|
||||
buf += """
|
||||
QTreeWidget {
|
||||
background: #eee;
|
||||
}
|
||||
"""
|
||||
|
||||
# allow addons to modify the styling
|
||||
buf = gui_hooks.style_did_init(buf)
|
||||
|
||||
# allow users to extend styling
|
||||
p = os.path.join(aqt.mw.pm.base, "style.css")
|
||||
if os.path.exists(p):
|
||||
buf += open(p).read()
|
||||
|
||||
self.app.setStyleSheet(buf)
|
||||
theme_manager.night_mode = self.pm.night_mode()
|
||||
theme_manager.apply_style(self.app)
|
||||
|
||||
# Key handling
|
||||
##########################################################################
|
||||
|
|
|
@ -189,9 +189,9 @@ to their original deck."""
|
|||
<table width=400 cellpadding=5>
|
||||
<tr><td align=center valign=top>
|
||||
<table cellspacing=5>
|
||||
<tr><td>%s:</td><td><b><font color=#00a>%s</font></b></td></tr>
|
||||
<tr><td>%s:</td><td><b><font color=#C35617>%s</font></b></td></tr>
|
||||
<tr><td>%s:</td><td><b><font color=#0a0>%s</font></b></td></tr>
|
||||
<tr><td>%s:</td><td><b><span class=new-count>%s</span></b></td></tr>
|
||||
<tr><td>%s:</td><td><b><font class=learn-count>%s</span></b></td></tr>
|
||||
<tr><td>%s:</td><td><b><span class=review-count>%s</span></b></td></tr>
|
||||
</table>
|
||||
</td><td align=center>
|
||||
%s</td></tr></table>""" % (
|
||||
|
|
|
@ -85,7 +85,6 @@ class Preferences(QDialog):
|
|||
f.timeLimit.setValue(qc["timeLim"] / 60.0)
|
||||
f.showEstimates.setChecked(qc["estTimes"])
|
||||
f.showProgress.setChecked(qc["dueCounts"])
|
||||
f.nightMode.setChecked(qc.get("nightMode", False))
|
||||
f.newSpread.addItems(list(c.newCardSchedulingLabels().values()))
|
||||
f.newSpread.setCurrentIndex(qc["newSpread"])
|
||||
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
|
||||
|
@ -113,7 +112,6 @@ class Preferences(QDialog):
|
|||
qc["dueCounts"] = f.showProgress.isChecked()
|
||||
qc["estTimes"] = f.showEstimates.isChecked()
|
||||
qc["newSpread"] = f.newSpread.currentIndex()
|
||||
qc["nightMode"] = f.nightMode.isChecked()
|
||||
qc["timeLim"] = f.timeLimit.value() * 60
|
||||
qc["collapseTime"] = f.lrnCutoff.value() * 60
|
||||
qc["addToCur"] = not f.useCurrent.currentIndex()
|
||||
|
@ -227,12 +225,22 @@ Not currently enabled; click the sync button in the main window to enable."""
|
|||
self.form.uiScale.setValue(self.mw.pm.uiScale() * 100)
|
||||
self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False))
|
||||
self.form.showPlayButtons.setChecked(self.prof.get("showPlayButtons", True))
|
||||
self.form.nightMode.setChecked(self.mw.pm.night_mode())
|
||||
|
||||
def updateOptions(self):
|
||||
restart_required = False
|
||||
|
||||
self.prof["pastePNG"] = self.form.pastePNG.isChecked()
|
||||
self.prof["pasteInvert"] = self.form.pasteInvert.isChecked()
|
||||
newScale = self.form.uiScale.value() / 100
|
||||
if newScale != self.mw.pm.uiScale():
|
||||
self.mw.pm.setUiScale(newScale)
|
||||
showInfo(_("Changes will take effect when you restart Anki."))
|
||||
restart_required = True
|
||||
self.prof["showPlayButtons"] = self.form.showPlayButtons.isChecked()
|
||||
|
||||
if self.mw.pm.night_mode() != self.form.nightMode.isChecked():
|
||||
self.mw.pm.set_night_mode(not self.mw.pm.night_mode())
|
||||
restart_required = True
|
||||
|
||||
if restart_required:
|
||||
showInfo(_("Changes will take effect when you restart Anki."))
|
||||
|
|
|
@ -495,3 +495,9 @@ please see:
|
|||
|
||||
def set_last_addon_update_check(self, secs):
|
||||
self.meta["last_addon_update_check"] = secs
|
||||
|
||||
def night_mode(self) -> bool:
|
||||
return self.meta.get("night_mode", False)
|
||||
|
||||
def set_night_mode(self, on: bool) -> None:
|
||||
self.meta["night_mode"] = on
|
||||
|
|
|
@ -14,10 +14,11 @@ from anki import hooks
|
|||
from anki.cards import Card
|
||||
from anki.lang import _, ngettext
|
||||
from anki.sound import AVTag
|
||||
from anki.utils import bodyClass, stripHTML
|
||||
from anki.utils import stripHTML
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.sound import av_player, getAudio
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.toolbar import BottomBar
|
||||
from aqt.utils import (
|
||||
askUserDialog,
|
||||
|
@ -199,7 +200,7 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
|||
q = self._mungeQA(q)
|
||||
q = gui_hooks.card_will_show(q, c, "reviewQuestion")
|
||||
|
||||
bodyclass = bodyClass(self.mw.col, c)
|
||||
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
||||
|
||||
self.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass))
|
||||
self._drawFlag()
|
||||
|
@ -600,9 +601,9 @@ time = %(time)d;
|
|||
idx = self.mw.col.sched.countIdx(self.card)
|
||||
counts[idx] = "<u>%s</u>" % (counts[idx])
|
||||
space = " + "
|
||||
ctxt = '<font color="#000099">%s</font>' % counts[0]
|
||||
ctxt += space + '<font color="#C35617">%s</font>' % counts[1]
|
||||
ctxt += space + '<font color="#007700">%s</font>' % counts[2]
|
||||
ctxt = "<span class=new-count>%s</span>" % counts[0]
|
||||
ctxt += space + "<span class=learn-count>%s</span>" % counts[1]
|
||||
ctxt += space + "<span class=review-count>%s</span>" % counts[2]
|
||||
return ctxt
|
||||
|
||||
def _defaultEase(self):
|
||||
|
|
126
qt/aqt/theme.py
Normal file
126
qt/aqt/theme.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
import platform
|
||||
from typing import Dict
|
||||
|
||||
from aqt import QApplication, gui_hooks, isWin
|
||||
from aqt.colors import colors
|
||||
from aqt.qt import QColor, QIcon, QPalette, QPixmap, QStyleFactory, Qt
|
||||
|
||||
|
||||
class ThemeManager:
|
||||
night_mode = True
|
||||
|
||||
_icon_cache: Dict[str, QIcon] = {}
|
||||
_icon_size = 128
|
||||
|
||||
def icon_from_resources(self, path: str) -> QIcon:
|
||||
"Fetch icon from Qt resources, and invert if in night mode."
|
||||
icon = self._icon_cache.get(path)
|
||||
if icon:
|
||||
return icon
|
||||
|
||||
icon = QIcon(path)
|
||||
|
||||
if self.night_mode:
|
||||
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
|
||||
img.invertPixels()
|
||||
icon = QIcon(QPixmap(img))
|
||||
|
||||
return self._icon_cache.setdefault(path, icon)
|
||||
|
||||
def body_class(self) -> str:
|
||||
"Returns '' in normal mode, 'nightMode' in night mode."
|
||||
return self.night_mode and "nightMode" or ""
|
||||
|
||||
def body_classes_for_card_ord(self, card_ord: int) -> str:
|
||||
"Returns body classes used when showing a card."
|
||||
return f"card card{card_ord+1} {self.body_class()}"
|
||||
|
||||
def str_color(self, key: str) -> str:
|
||||
"""Get a color defined in _vars.scss
|
||||
|
||||
If the colour is called '$day-frame-bg', key should be
|
||||
'frame-bg'.
|
||||
|
||||
Returns the color as a string hex code or color name."""
|
||||
prefix = self.night_mode and "night-" or "day-"
|
||||
c = colors.get(prefix + key)
|
||||
if c is None:
|
||||
raise Exception("no such color:", key)
|
||||
return c
|
||||
|
||||
def qcolor(self, key: str) -> QColor:
|
||||
"""Get a color defined in _vars.scss as a QColor."""
|
||||
return QColor(self.str_color(key))
|
||||
|
||||
def apply_style(self, app: QApplication) -> None:
|
||||
self._apply_palette(app)
|
||||
self._apply_style(app)
|
||||
|
||||
def _apply_style(self, app: QApplication) -> None:
|
||||
buf = ""
|
||||
|
||||
if isWin and platform.release() == "10" and not self.night_mode:
|
||||
# add missing bottom border to menubar
|
||||
buf += """
|
||||
QMenuBar {
|
||||
border-bottom: 1px solid #aaa;
|
||||
background: white;
|
||||
}
|
||||
"""
|
||||
# qt bug? setting the above changes the browser sidebar
|
||||
# to white as well, so set it back
|
||||
buf += """
|
||||
QTreeWidget {
|
||||
background: #eee;
|
||||
}
|
||||
"""
|
||||
|
||||
# allow addons to modify the styling
|
||||
buf = gui_hooks.style_did_init(buf)
|
||||
|
||||
app.setStyleSheet(buf)
|
||||
|
||||
def _apply_palette(self, app: QApplication) -> None:
|
||||
if not self.night_mode:
|
||||
return
|
||||
|
||||
app.setStyle(QStyleFactory.create("fusion")) # type: ignore
|
||||
|
||||
palette = QPalette()
|
||||
|
||||
text_fg = self.qcolor("text-fg")
|
||||
palette.setColor(QPalette.WindowText, text_fg)
|
||||
palette.setColor(QPalette.ToolTipBase, text_fg)
|
||||
palette.setColor(QPalette.ToolTipText, text_fg)
|
||||
palette.setColor(QPalette.Text, text_fg)
|
||||
palette.setColor(QPalette.ButtonText, text_fg)
|
||||
|
||||
hlbg = self.qcolor("highlight-bg")
|
||||
hlbg.setAlpha(64)
|
||||
palette.setColor(QPalette.HighlightedText, self.qcolor("highlight-fg"))
|
||||
palette.setColor(QPalette.Highlight, hlbg)
|
||||
|
||||
window_bg = self.qcolor("window-bg")
|
||||
palette.setColor(QPalette.Window, window_bg)
|
||||
palette.setColor(QPalette.AlternateBase, window_bg)
|
||||
palette.setColor(QPalette.Button, window_bg)
|
||||
|
||||
palette.setColor(QPalette.Base, self.qcolor("frame-bg"))
|
||||
|
||||
disabled_color = self.qcolor("disabled")
|
||||
palette.setColor(QPalette.Disabled, QPalette.Text, disabled_color)
|
||||
palette.setColor(QPalette.Disabled, QPalette.ButtonText, disabled_color)
|
||||
palette.setColor(QPalette.Disabled, QPalette.HighlightedText, disabled_color)
|
||||
|
||||
palette.setColor(QPalette.Link, self.qcolor("link"))
|
||||
|
||||
palette.setColor(QPalette.BrightText, Qt.red)
|
||||
|
||||
app.setPalette(palette)
|
||||
|
||||
|
||||
theme_manager = ThemeManager()
|
|
@ -10,6 +10,7 @@ from anki.lang import _
|
|||
from anki.utils import isLin, isMac, isWin
|
||||
from aqt import gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import openLink
|
||||
|
||||
# Page for debug messages
|
||||
|
@ -308,6 +309,8 @@ div[contenteditable="true"]:focus {
|
|||
|
||||
head = mw.baseHTML() + head + csstxt + jstxt
|
||||
|
||||
body_class = theme_manager.body_class()
|
||||
|
||||
html = """
|
||||
<!doctype html>
|
||||
<html><head>
|
||||
|
@ -321,7 +324,7 @@ body {{ zoom: {}; background: {}; {} }}
|
|||
{}
|
||||
</head>
|
||||
|
||||
<body>{}</body>
|
||||
<body class="{}">{}</body>
|
||||
</html>""".format(
|
||||
self.title,
|
||||
self.zoomFactor(),
|
||||
|
@ -329,6 +332,7 @@ body {{ zoom: {}; background: {}; {} }}
|
|||
fontspec,
|
||||
widgetspec,
|
||||
head,
|
||||
body_class,
|
||||
body,
|
||||
)
|
||||
# print(html)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>422</width>
|
||||
<height>579</height>
|
||||
<height>586</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -111,7 +111,7 @@
|
|||
<item>
|
||||
<widget class="QCheckBox" name="nightMode">
|
||||
<property name="text">
|
||||
<string>Show cards as white on black (night mode)</string>
|
||||
<string>Night mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
23
qt/tools/extract_scss_colors.py
Normal file
23
qt/tools/extract_scss_colors.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
import re
|
||||
|
||||
import json
|
||||
|
||||
colors = {}
|
||||
|
||||
for line in open("ts/scss/_vars.scss"):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
m = re.match(r"^\$(.+): (.+);$", line)
|
||||
if not m:
|
||||
print("failed to match", line)
|
||||
continue
|
||||
|
||||
var = m.group(1)
|
||||
val = m.group(2)
|
||||
|
||||
colors[var] = val
|
||||
|
||||
with open("aqt/colors.py", "w") as buf:
|
||||
buf.write("# this file is auto-generated from _vars.scss\n")
|
||||
buf.write("colors = " + json.dumps(colors))
|
27
qt/ts/scss/_buttons.scss
Normal file
27
qt/ts/scss/_buttons.scss
Normal file
|
@ -0,0 +1,27 @@
|
|||
@use 'vars';
|
||||
|
||||
.nightMode {
|
||||
button {
|
||||
-webkit-appearance: none;
|
||||
color: vars.$night-text-fg;
|
||||
|
||||
/* match the fusion button gradient */
|
||||
background: linear-gradient(0deg,
|
||||
vars.$fusion-button-gradient-start 0%,
|
||||
vars.$fusion-button-gradient-end 100%);
|
||||
box-shadow: 0 0 3px vars.$fusion-button-outline;
|
||||
border: 1px solid vars.$night-faint-border;
|
||||
|
||||
border-radius: 3px;
|
||||
height: 24px;
|
||||
padding: 5px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: vars.$fusion-button-hover-bg;
|
||||
}
|
||||
}
|
||||
|
28
qt/ts/scss/_card_counts.scss
Normal file
28
qt/ts/scss/_card_counts.scss
Normal file
|
@ -0,0 +1,28 @@
|
|||
@use 'vars';
|
||||
|
||||
.review-count {
|
||||
color: vars.$day-review-count;
|
||||
}
|
||||
|
||||
.new-count {
|
||||
color: vars.$day-new-count;
|
||||
}
|
||||
|
||||
.learn-count {
|
||||
color: vars.$day-learn-count;
|
||||
}
|
||||
|
||||
.nightMode {
|
||||
.review-count {
|
||||
color: vars.$night-review-count;
|
||||
}
|
||||
|
||||
.new-count {
|
||||
color: vars.$night-new-count;
|
||||
}
|
||||
|
||||
.learn-count {
|
||||
color: vars.$night-learn-count;
|
||||
}
|
||||
}
|
||||
|
46
qt/ts/scss/_vars.scss
Normal file
46
qt/ts/scss/_vars.scss
Normal file
|
@ -0,0 +1,46 @@
|
|||
$day-text-fg: black;
|
||||
$day-window-bg: white;
|
||||
$day-frame-bg: white;
|
||||
$day-border: #aaa;
|
||||
$day-faint-border: #e7e7e7;
|
||||
$day-link: #00a;
|
||||
$day-review-count: #0a0;
|
||||
$day-new-count: #00a;
|
||||
$day-learn-count: #C35617;
|
||||
$day-zero-count: #aaa;
|
||||
$day-slightly-grey-text: #333;
|
||||
$day-highlight-bg: #77ccff;
|
||||
$day-highlight-fg: black;
|
||||
$day-disabled: #777;
|
||||
$day-flag1-bg: #ffaaaa;
|
||||
$day-flag2-bg: #ffb347;
|
||||
$day-flag3-bg: #82E0AA;
|
||||
$day-flag4-bg: #85C1E9;
|
||||
$day-suspended-bg: #FFFFB2;
|
||||
$day-marked-bg: #cce;
|
||||
|
||||
$night-text-fg: white;
|
||||
$night-window-bg: #2f2f31;
|
||||
$night-frame-bg: #3a3a3a;
|
||||
$night-border: #777;
|
||||
$night-faint-border: #444;
|
||||
$night-link: #aaf;
|
||||
$night-review-count: #7CFC00;
|
||||
$night-new-count: #77ccff;
|
||||
$night-learn-count: #FF935B;
|
||||
$night-zero-count: #777;
|
||||
$night-slightly-grey-text: #ccc;
|
||||
$night-highlight-bg: #77ccff;
|
||||
$night-highlight-fg: white;
|
||||
$night-disabled: #777;
|
||||
$night-flag1-bg: #aa5555;
|
||||
$night-flag2-bg: #aa6337;
|
||||
$night-flag3-bg: #33a055;
|
||||
$night-flag4-bg: #3581a9;
|
||||
$night-suspended-bg: #aaaa33;
|
||||
$night-marked-bg: #77c;
|
||||
|
||||
$fusion-button-gradient-start: #363636;
|
||||
$fusion-button-gradient-end: #404040;
|
||||
$fusion-button-outline: #000;
|
||||
$fusion-button-hover-bg: #454545;
|
|
@ -1,8 +1,11 @@
|
|||
/* Copyright: Ankitects Pty Ltd and contributors
|
||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||
|
||||
@use 'vars';
|
||||
@use 'card_counts';
|
||||
|
||||
a.deck {
|
||||
color: #000;
|
||||
color: vars.$day-text-fg;
|
||||
text-decoration: none;
|
||||
min-width: 5em;
|
||||
display: inline-block;
|
||||
|
@ -13,7 +16,7 @@ a.deck:hover {
|
|||
}
|
||||
|
||||
tr.deck td {
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
border-bottom: 1px solid vars.$day-faint-border;
|
||||
}
|
||||
|
||||
tr.top-level-drag-row td {
|
||||
|
@ -25,7 +28,7 @@ td {
|
|||
}
|
||||
|
||||
tr.drag-hover td {
|
||||
border-bottom: 1px solid #aaa;
|
||||
border-bottom: 1px solid vars.$day-border;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -34,7 +37,7 @@ body {
|
|||
}
|
||||
|
||||
.current {
|
||||
background-color: #e7e7e7;
|
||||
background-color: vars.$day-faint-border;
|
||||
}
|
||||
|
||||
.decktd {
|
||||
|
@ -51,14 +54,14 @@ body {
|
|||
}
|
||||
|
||||
.collapse {
|
||||
color: #000;
|
||||
color: vars.$day-text-fg;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.filtered {
|
||||
color: #00a !important;
|
||||
color: vars.$day-link !important;
|
||||
}
|
||||
|
||||
.gears {
|
||||
|
@ -67,3 +70,29 @@ body {
|
|||
opacity: .5;
|
||||
padding-top: 0.2em;
|
||||
}
|
||||
|
||||
.nightMode {
|
||||
a.deck {
|
||||
color: vars.$night-text-fg;
|
||||
}
|
||||
|
||||
tr.deck td {
|
||||
border-bottom-color: vars.$night-faint-border;
|
||||
}
|
||||
|
||||
tr.drag-hover td {
|
||||
border-bottom-color: vars.$night-border;
|
||||
}
|
||||
|
||||
.current {
|
||||
background-color: vars.$night-faint-border;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
color: vars.$night-text-fg;
|
||||
}
|
||||
|
||||
.gears {
|
||||
filter: invert(180);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
/* Copyright: Ankitects Pty Ltd and contributors
|
||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||
|
||||
@use 'vars';
|
||||
|
||||
.field {
|
||||
border: 1px solid #aaa;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid vars.$day-border;
|
||||
background: vars.$day-frame-bg;
|
||||
color: vars.$day-text-fg;
|
||||
padding: 5px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
@ -49,9 +51,10 @@ body {
|
|||
color-stop(100%, #77f));
|
||||
}
|
||||
|
||||
.linkb {
|
||||
button.linkb {
|
||||
-webkit-appearance: none;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding: 0px 2px;
|
||||
background: transparent;
|
||||
}
|
||||
|
@ -68,3 +71,27 @@ body {
|
|||
#fields {
|
||||
margin-top: 35px;
|
||||
}
|
||||
|
||||
.dupe {
|
||||
background: #fcc;
|
||||
}
|
||||
|
||||
.nightMode {
|
||||
.field {
|
||||
border-color: vars.$night-border;
|
||||
background: vars.$night-frame-bg;
|
||||
color: vars.$night-text-fg;
|
||||
}
|
||||
|
||||
button.linkb > img {
|
||||
filter: invert(180);
|
||||
}
|
||||
|
||||
.dupe {
|
||||
background: #a00;
|
||||
}
|
||||
|
||||
#dupes a {
|
||||
color: vars.$night-link;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
/* Copyright: Ankitects Pty Ltd and contributors
|
||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||
|
||||
@use 'vars';
|
||||
@use 'card_counts';
|
||||
|
||||
.smallLink {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
@ -11,7 +14,7 @@ h3 {
|
|||
|
||||
.descfont {
|
||||
padding: 1em;
|
||||
color: #333;
|
||||
color: vars.$day-slightly-grey-text;
|
||||
}
|
||||
|
||||
.description {
|
||||
|
@ -31,3 +34,9 @@ h3 {
|
|||
.dyn {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nightMode {
|
||||
.descfont {
|
||||
color: vars.$night-slightly-grey-text;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
/* Copyright: Ankitects Pty Ltd and contributors
|
||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||
|
||||
@use 'vars';
|
||||
@use 'card_counts';
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
@ -48,9 +51,15 @@ button {
|
|||
}
|
||||
|
||||
#outer {
|
||||
border-top: 1px solid #aaa;
|
||||
border-top: 1px solid vars.$day-border;
|
||||
}
|
||||
|
||||
#innertable {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.nightMode {
|
||||
#outer {
|
||||
border-top-color: vars.$night-border;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
/* Copyright: Ankitects Pty Ltd and contributors
|
||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||
|
||||
@use 'vars';
|
||||
|
||||
#header {
|
||||
padding: 3px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #aaa;
|
||||
border-bottom: 1px solid vars.$day-border;
|
||||
}
|
||||
|
||||
.tdcenter {
|
||||
|
@ -26,11 +28,7 @@ body {
|
|||
padding-right: 12px;
|
||||
padding-left: 12px;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.nightMode .hitem {
|
||||
color: white;
|
||||
color: vars.$day-text-fg;
|
||||
}
|
||||
|
||||
.hitem:hover {
|
||||
|
@ -38,3 +36,13 @@ body {
|
|||
}
|
||||
|
||||
.hitem:focus { outline: 0; }
|
||||
|
||||
.nightMode {
|
||||
.hitem {
|
||||
color: vars.$night-text-fg;
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom-color: vars.$night-border;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
/* Copyright: Ankitects Pty Ltd and contributors
|
||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||
|
||||
@use 'vars';
|
||||
@use 'buttons';
|
||||
|
||||
body {
|
||||
margin: 2em;
|
||||
}
|
||||
|
@ -10,6 +13,6 @@ h1 {
|
|||
}
|
||||
|
||||
body.nightMode {
|
||||
color: white;
|
||||
background: #2f2f31;
|
||||
color: vars.$night-text-fg;
|
||||
background: vars.$night-window-bg;
|
||||
}
|
||||
|
|
|
@ -352,7 +352,11 @@ function setFields(fields) {
|
|||
|
||||
function setBackgrounds(cols) {
|
||||
for (let i = 0; i < cols.length; i++) {
|
||||
$("#f" + i).css("background", cols[i]);
|
||||
if (cols[i] == "dupe") {
|
||||
$("#f" + i).addClass("dupe");
|
||||
} else {
|
||||
$("#f" + i).removeClass("dupe");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue