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)
|
return reEnts.sub(fixup, html)
|
||||||
|
|
||||||
|
|
||||||
|
# legacy function
|
||||||
def bodyClass(col, card) -> str:
|
def bodyClass(col, card) -> str:
|
||||||
bodyclass = "card card%d" % (card.ord + 1)
|
from aqt.theme import theme_manager
|
||||||
if col.conf.get("nightMode"):
|
|
||||||
bodyclass += " nightMode"
|
print("bodyClass() deprecated")
|
||||||
return bodyclass
|
return theme_manager.body_classes_for_card_ord(card.ord)
|
||||||
|
|
||||||
|
|
||||||
# IDs
|
# IDs
|
||||||
|
|
|
@ -5,7 +5,7 @@ MAKEFLAGS += --warn-undefined-variables
|
||||||
MAKEFLAGS += --no-builtin-rules
|
MAKEFLAGS += --no-builtin-rules
|
||||||
.SUFFIXES:
|
.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
|
ISORTARGS := aqt tests setup.py
|
||||||
|
|
||||||
$(shell mkdir -p .build ../dist)
|
$(shell mkdir -p .build ../dist)
|
||||||
|
@ -33,6 +33,7 @@ TSDEPS := $(wildcard ts/src/*.ts) $(wildcard ts/scss/*.scss)
|
||||||
|
|
||||||
.build/js: $(TSDEPS)
|
.build/js: $(TSDEPS)
|
||||||
(cd ts && npm i && npm run build)
|
(cd ts && npm i && npm run build)
|
||||||
|
python ./tools/extract_scss_colors.py
|
||||||
@touch $@
|
@touch $@
|
||||||
|
|
||||||
.build/hooks: tools/genhooks_gui.py ../pylib/tools/hookslib.py
|
.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
|
buildinfo.py
|
||||||
|
colors.py
|
||||||
|
|
|
@ -20,19 +20,12 @@ from anki.consts import *
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.utils import (
|
from anki.utils import fmtTimeSpan, htmlToTextLine, ids2str, intTime, isMac, isWin
|
||||||
bodyClass,
|
|
||||||
fmtTimeSpan,
|
|
||||||
htmlToTextLine,
|
|
||||||
ids2str,
|
|
||||||
intTime,
|
|
||||||
isMac,
|
|
||||||
isWin,
|
|
||||||
)
|
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.editor import Editor
|
from aqt.editor import Editor
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
MenuList,
|
MenuList,
|
||||||
SubMenu,
|
SubMenu,
|
||||||
|
@ -366,16 +359,6 @@ class DataModel(QAbstractTableModel):
|
||||||
# Line painter
|
# Line painter
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
COLOUR_SUSPENDED = "#FFFFB2"
|
|
||||||
COLOUR_MARKED = "#ccc"
|
|
||||||
|
|
||||||
flagColours = {
|
|
||||||
1: "#ffaaaa",
|
|
||||||
2: "#ffb347",
|
|
||||||
3: "#82E0AA",
|
|
||||||
4: "#85C1E9",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class StatusDelegate(QItemDelegate):
|
class StatusDelegate(QItemDelegate):
|
||||||
def __init__(self, browser, model):
|
def __init__(self, browser, model):
|
||||||
|
@ -399,13 +382,13 @@ class StatusDelegate(QItemDelegate):
|
||||||
|
|
||||||
col = None
|
col = None
|
||||||
if c.userFlag() > 0:
|
if c.userFlag() > 0:
|
||||||
col = flagColours[c.userFlag()]
|
col = theme_manager.qcolor(f"flag{c.userFlag()}-bg")
|
||||||
elif c.note().hasTag("Marked"):
|
elif c.note().hasTag("Marked"):
|
||||||
col = COLOUR_MARKED
|
col = theme_manager.qcolor("marked-bg")
|
||||||
elif c.queue == -1:
|
elif c.queue == -1:
|
||||||
col = COLOUR_SUSPENDED
|
col = theme_manager.qcolor("suspended-bg")
|
||||||
if col:
|
if col:
|
||||||
brush = QBrush(QColor(col))
|
brush = QBrush(col)
|
||||||
painter.save()
|
painter.save()
|
||||||
painter.fillRect(option.rect, brush)
|
painter.fillRect(option.rect, brush)
|
||||||
painter.restore()
|
painter.restore()
|
||||||
|
@ -450,7 +433,6 @@ class SidebarModel(QAbstractItemModel):
|
||||||
def __init__(self, root: SidebarItem) -> None:
|
def __init__(self, root: SidebarItem) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.root = root
|
self.root = root
|
||||||
self.iconCache: Dict[str, QIcon] = {}
|
|
||||||
|
|
||||||
# Qt API
|
# Qt API
|
||||||
######################################################################
|
######################################################################
|
||||||
|
@ -510,18 +492,11 @@ class SidebarModel(QAbstractItemModel):
|
||||||
elif role == Qt.ToolTipRole:
|
elif role == Qt.ToolTipRole:
|
||||||
return QVariant(item.tooltip)
|
return QVariant(item.tooltip)
|
||||||
else:
|
else:
|
||||||
return QVariant(self.iconFromRef(item.icon))
|
return QVariant(theme_manager.icon_from_resources(item.icon))
|
||||||
|
|
||||||
# Helpers
|
# 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:
|
def expandWhereNeccessary(self, tree: QTreeView) -> None:
|
||||||
for row, child in enumerate(self.root.children):
|
for row, child in enumerate(self.root.children):
|
||||||
if child.expanded:
|
if child.expanded:
|
||||||
|
@ -821,8 +796,10 @@ class Browser(QMainWindow):
|
||||||
self.form.tableView.selectionModel()
|
self.form.tableView.selectionModel()
|
||||||
self.form.tableView.setItemDelegate(StatusDelegate(self, self.model))
|
self.form.tableView.setItemDelegate(StatusDelegate(self, self.model))
|
||||||
self.form.tableView.selectionModel().selectionChanged.connect(self.onRowChanged)
|
self.form.tableView.selectionModel().selectionChanged.connect(self.onRowChanged)
|
||||||
|
if not theme_manager.night_mode:
|
||||||
self.form.tableView.setStyleSheet(
|
self.form.tableView.setStyleSheet(
|
||||||
"QTableView{ selection-background-color: rgba(127, 127, 127, 50); }"
|
"QTableView{ selection-background-color: rgba(150, 150, 150, 50); "
|
||||||
|
"selection-color: black; }"
|
||||||
)
|
)
|
||||||
self.singleCard = False
|
self.singleCard = False
|
||||||
|
|
||||||
|
@ -1709,7 +1686,7 @@ where id in %s"""
|
||||||
txt = c.a()
|
txt = c.a()
|
||||||
txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt)
|
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 self.mw.reviewer.autoplay(c):
|
||||||
# if we're showing both sides at once, play question audio first
|
# if we're showing both sides at once, play question audio first
|
||||||
|
|
|
@ -9,10 +9,11 @@ import re
|
||||||
import aqt
|
import aqt
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.lang import _, ngettext
|
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 import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
askUser,
|
askUser,
|
||||||
downArrow,
|
downArrow,
|
||||||
|
@ -336,7 +337,7 @@ Please create a new card type first."""
|
||||||
c = self.card
|
c = self.card
|
||||||
ti = self.maybeTextInput
|
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 = ti(mungeQA(self.mw.col, c.q(reload=True)))
|
||||||
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
|
q = gui_hooks.card_will_show(q, c, "clayoutQuestion")
|
||||||
|
|
|
@ -209,16 +209,16 @@ where id > ?""",
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
# due counts
|
# due counts
|
||||||
def nonzeroColour(cnt, colour):
|
def nonzeroColour(cnt, klass):
|
||||||
if not cnt:
|
if not cnt:
|
||||||
colour = "#e0e0e0"
|
klass = "zero-count"
|
||||||
if cnt >= 1000:
|
if cnt >= 1000:
|
||||||
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>" % (
|
buf += "<td align=right>%s</td><td align=right>%s</td>" % (
|
||||||
nonzeroColour(due, "#007700"),
|
nonzeroColour(due, "review-count"),
|
||||||
nonzeroColour(new, "#000099"),
|
nonzeroColour(new, "new-count"),
|
||||||
)
|
)
|
||||||
# options
|
# options
|
||||||
buf += (
|
buf += (
|
||||||
|
|
|
@ -442,10 +442,10 @@ class Editor:
|
||||||
self.web.evalWithCallback("saveNow(%d)" % keepFocus, lambda res: callback())
|
self.web.evalWithCallback("saveNow(%d)" % keepFocus, lambda res: callback())
|
||||||
|
|
||||||
def checkValid(self):
|
def checkValid(self):
|
||||||
cols = ["#fff"] * len(self.note.fields)
|
cols = [""] * len(self.note.fields)
|
||||||
err = self.note.dupeOrEmpty()
|
err = self.note.dupeOrEmpty()
|
||||||
if err == 2:
|
if err == 2:
|
||||||
cols[0] = "#fcc"
|
cols[0] = "dupe"
|
||||||
self.web.eval("showDupes();")
|
self.web.eval("showDupes();")
|
||||||
else:
|
else:
|
||||||
self.web.eval("hideDupes();")
|
self.web.eval("hideDupes();")
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import gc
|
import gc
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
@ -37,6 +36,7 @@ from aqt.profiles import ProfileManager as ProfileManagerType
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.qt import sip
|
from aqt.qt import sip
|
||||||
from aqt.taskman import TaskManager
|
from aqt.taskman import TaskManager
|
||||||
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
askUser,
|
askUser,
|
||||||
checkInvalidFilename,
|
checkInvalidFilename,
|
||||||
|
@ -58,6 +58,7 @@ class AnkiQt(QMainWindow):
|
||||||
col: _Collection
|
col: _Collection
|
||||||
pm: ProfileManagerType
|
pm: ProfileManagerType
|
||||||
web: aqt.webview.AnkiWebView
|
web: aqt.webview.AnkiWebView
|
||||||
|
bottomWeb: aqt.webview.AnkiWebView
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -111,9 +112,9 @@ class AnkiQt(QMainWindow):
|
||||||
self.setupMediaServer()
|
self.setupMediaServer()
|
||||||
self.setupSound()
|
self.setupSound()
|
||||||
self.setupSpellCheck()
|
self.setupSpellCheck()
|
||||||
|
self.setupStyle()
|
||||||
self.setupMainWindow()
|
self.setupMainWindow()
|
||||||
self.setupSystemSpecific()
|
self.setupSystemSpecific()
|
||||||
self.setupStyle()
|
|
||||||
self.setupMenus()
|
self.setupMenus()
|
||||||
self.setupProgress()
|
self.setupProgress()
|
||||||
self.setupErrorHandler()
|
self.setupErrorHandler()
|
||||||
|
@ -850,33 +851,8 @@ title="%s" %s>%s</button>""" % (
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setupStyle(self) -> None:
|
def setupStyle(self) -> None:
|
||||||
buf = ""
|
theme_manager.night_mode = self.pm.night_mode()
|
||||||
|
theme_manager.apply_style(self.app)
|
||||||
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)
|
|
||||||
|
|
||||||
# Key handling
|
# Key handling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -189,9 +189,9 @@ to their original deck."""
|
||||||
<table width=400 cellpadding=5>
|
<table width=400 cellpadding=5>
|
||||||
<tr><td align=center valign=top>
|
<tr><td align=center valign=top>
|
||||||
<table cellspacing=5>
|
<table cellspacing=5>
|
||||||
<tr><td>%s:</td><td><b><font color=#00a>%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 color=#C35617>%s</font></b></td></tr>
|
<tr><td>%s:</td><td><b><font class=learn-count>%s</span></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=review-count>%s</span></b></td></tr>
|
||||||
</table>
|
</table>
|
||||||
</td><td align=center>
|
</td><td align=center>
|
||||||
%s</td></tr></table>""" % (
|
%s</td></tr></table>""" % (
|
||||||
|
|
|
@ -85,7 +85,6 @@ class Preferences(QDialog):
|
||||||
f.timeLimit.setValue(qc["timeLim"] / 60.0)
|
f.timeLimit.setValue(qc["timeLim"] / 60.0)
|
||||||
f.showEstimates.setChecked(qc["estTimes"])
|
f.showEstimates.setChecked(qc["estTimes"])
|
||||||
f.showProgress.setChecked(qc["dueCounts"])
|
f.showProgress.setChecked(qc["dueCounts"])
|
||||||
f.nightMode.setChecked(qc.get("nightMode", False))
|
|
||||||
f.newSpread.addItems(list(c.newCardSchedulingLabels().values()))
|
f.newSpread.addItems(list(c.newCardSchedulingLabels().values()))
|
||||||
f.newSpread.setCurrentIndex(qc["newSpread"])
|
f.newSpread.setCurrentIndex(qc["newSpread"])
|
||||||
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
|
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
|
||||||
|
@ -113,7 +112,6 @@ class Preferences(QDialog):
|
||||||
qc["dueCounts"] = f.showProgress.isChecked()
|
qc["dueCounts"] = f.showProgress.isChecked()
|
||||||
qc["estTimes"] = f.showEstimates.isChecked()
|
qc["estTimes"] = f.showEstimates.isChecked()
|
||||||
qc["newSpread"] = f.newSpread.currentIndex()
|
qc["newSpread"] = f.newSpread.currentIndex()
|
||||||
qc["nightMode"] = f.nightMode.isChecked()
|
|
||||||
qc["timeLim"] = f.timeLimit.value() * 60
|
qc["timeLim"] = f.timeLimit.value() * 60
|
||||||
qc["collapseTime"] = f.lrnCutoff.value() * 60
|
qc["collapseTime"] = f.lrnCutoff.value() * 60
|
||||||
qc["addToCur"] = not f.useCurrent.currentIndex()
|
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.uiScale.setValue(self.mw.pm.uiScale() * 100)
|
||||||
self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False))
|
self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False))
|
||||||
self.form.showPlayButtons.setChecked(self.prof.get("showPlayButtons", True))
|
self.form.showPlayButtons.setChecked(self.prof.get("showPlayButtons", True))
|
||||||
|
self.form.nightMode.setChecked(self.mw.pm.night_mode())
|
||||||
|
|
||||||
def updateOptions(self):
|
def updateOptions(self):
|
||||||
|
restart_required = False
|
||||||
|
|
||||||
self.prof["pastePNG"] = self.form.pastePNG.isChecked()
|
self.prof["pastePNG"] = self.form.pastePNG.isChecked()
|
||||||
self.prof["pasteInvert"] = self.form.pasteInvert.isChecked()
|
self.prof["pasteInvert"] = self.form.pasteInvert.isChecked()
|
||||||
newScale = self.form.uiScale.value() / 100
|
newScale = self.form.uiScale.value() / 100
|
||||||
if newScale != self.mw.pm.uiScale():
|
if newScale != self.mw.pm.uiScale():
|
||||||
self.mw.pm.setUiScale(newScale)
|
self.mw.pm.setUiScale(newScale)
|
||||||
showInfo(_("Changes will take effect when you restart Anki."))
|
restart_required = True
|
||||||
self.prof["showPlayButtons"] = self.form.showPlayButtons.isChecked()
|
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):
|
def set_last_addon_update_check(self, secs):
|
||||||
self.meta["last_addon_update_check"] = 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.cards import Card
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from anki.sound import AVTag
|
from anki.sound import AVTag
|
||||||
from anki.utils import bodyClass, stripHTML
|
from anki.utils import stripHTML
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player, getAudio
|
from aqt.sound import av_player, getAudio
|
||||||
|
from aqt.theme import theme_manager
|
||||||
from aqt.toolbar import BottomBar
|
from aqt.toolbar import BottomBar
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
askUserDialog,
|
askUserDialog,
|
||||||
|
@ -199,7 +200,7 @@ The front of this card is empty. Please run Tools>Empty Cards."""
|
||||||
q = self._mungeQA(q)
|
q = self._mungeQA(q)
|
||||||
q = gui_hooks.card_will_show(q, c, "reviewQuestion")
|
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.web.eval("_showQuestion(%s,'%s');" % (json.dumps(q), bodyclass))
|
||||||
self._drawFlag()
|
self._drawFlag()
|
||||||
|
@ -600,9 +601,9 @@ time = %(time)d;
|
||||||
idx = self.mw.col.sched.countIdx(self.card)
|
idx = self.mw.col.sched.countIdx(self.card)
|
||||||
counts[idx] = "<u>%s</u>" % (counts[idx])
|
counts[idx] = "<u>%s</u>" % (counts[idx])
|
||||||
space = " + "
|
space = " + "
|
||||||
ctxt = '<font color="#000099">%s</font>' % counts[0]
|
ctxt = "<span class=new-count>%s</span>" % counts[0]
|
||||||
ctxt += space + '<font color="#C35617">%s</font>' % counts[1]
|
ctxt += space + "<span class=learn-count>%s</span>" % counts[1]
|
||||||
ctxt += space + '<font color="#007700">%s</font>' % counts[2]
|
ctxt += space + "<span class=review-count>%s</span>" % counts[2]
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
def _defaultEase(self):
|
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 anki.utils import isLin, isMac, isWin
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
from aqt.theme import theme_manager
|
||||||
from aqt.utils import openLink
|
from aqt.utils import openLink
|
||||||
|
|
||||||
# Page for debug messages
|
# Page for debug messages
|
||||||
|
@ -308,6 +309,8 @@ div[contenteditable="true"]:focus {
|
||||||
|
|
||||||
head = mw.baseHTML() + head + csstxt + jstxt
|
head = mw.baseHTML() + head + csstxt + jstxt
|
||||||
|
|
||||||
|
body_class = theme_manager.body_class()
|
||||||
|
|
||||||
html = """
|
html = """
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html><head>
|
<html><head>
|
||||||
|
@ -321,7 +324,7 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
{}
|
{}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>{}</body>
|
<body class="{}">{}</body>
|
||||||
</html>""".format(
|
</html>""".format(
|
||||||
self.title,
|
self.title,
|
||||||
self.zoomFactor(),
|
self.zoomFactor(),
|
||||||
|
@ -329,6 +332,7 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
fontspec,
|
fontspec,
|
||||||
widgetspec,
|
widgetspec,
|
||||||
head,
|
head,
|
||||||
|
body_class,
|
||||||
body,
|
body,
|
||||||
)
|
)
|
||||||
# print(html)
|
# print(html)
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>422</width>
|
<width>422</width>
|
||||||
<height>579</height>
|
<height>586</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="nightMode">
|
<widget class="QCheckBox" name="nightMode">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Show cards as white on black (night mode)</string>
|
<string>Night mode</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
@use 'vars';
|
||||||
|
@use 'card_counts';
|
||||||
|
|
||||||
a.deck {
|
a.deck {
|
||||||
color: #000;
|
color: vars.$day-text-fg;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
min-width: 5em;
|
min-width: 5em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -13,7 +16,7 @@ a.deck:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.deck td {
|
tr.deck td {
|
||||||
border-bottom: 1px solid #e7e7e7;
|
border-bottom: 1px solid vars.$day-faint-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.top-level-drag-row td {
|
tr.top-level-drag-row td {
|
||||||
|
@ -25,7 +28,7 @@ td {
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.drag-hover td {
|
tr.drag-hover td {
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid vars.$day-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -34,7 +37,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.current {
|
.current {
|
||||||
background-color: #e7e7e7;
|
background-color: vars.$day-faint-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.decktd {
|
.decktd {
|
||||||
|
@ -51,14 +54,14 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapse {
|
.collapse {
|
||||||
color: #000;
|
color: vars.$day-text-fg;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filtered {
|
.filtered {
|
||||||
color: #00a !important;
|
color: vars.$day-link !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gears {
|
.gears {
|
||||||
|
@ -67,3 +70,29 @@ body {
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
padding-top: 0.2em;
|
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
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
@use 'vars';
|
||||||
|
|
||||||
.field {
|
.field {
|
||||||
border: 1px solid #aaa;
|
border: 1px solid vars.$day-border;
|
||||||
background: #fff;
|
background: vars.$day-frame-bg;
|
||||||
color: #000;
|
color: vars.$day-text-fg;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
@ -49,9 +51,10 @@ body {
|
||||||
color-stop(100%, #77f));
|
color-stop(100%, #77f));
|
||||||
}
|
}
|
||||||
|
|
||||||
.linkb {
|
button.linkb {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
box-shadow: none;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
@ -68,3 +71,27 @@ body {
|
||||||
#fields {
|
#fields {
|
||||||
margin-top: 35px;
|
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
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
@use 'vars';
|
||||||
|
@use 'card_counts';
|
||||||
|
|
||||||
.smallLink {
|
.smallLink {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +14,7 @@ h3 {
|
||||||
|
|
||||||
.descfont {
|
.descfont {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
color: #333;
|
color: vars.$day-slightly-grey-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
@ -31,3 +34,9 @@ h3 {
|
||||||
.dyn {
|
.dyn {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nightMode {
|
||||||
|
.descfont {
|
||||||
|
color: vars.$night-slightly-grey-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
@use 'vars';
|
||||||
|
@use 'card_counts';
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +51,15 @@ button {
|
||||||
}
|
}
|
||||||
|
|
||||||
#outer {
|
#outer {
|
||||||
border-top: 1px solid #aaa;
|
border-top: 1px solid vars.$day-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
#innertable {
|
#innertable {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nightMode {
|
||||||
|
#outer {
|
||||||
|
border-top-color: vars.$night-border;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
/* Copyright: Ankitects Pty Ltd and contributors
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
@use 'vars';
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid vars.$day-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tdcenter {
|
.tdcenter {
|
||||||
|
@ -26,11 +28,7 @@ body {
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #000;
|
color: vars.$day-text-fg;
|
||||||
}
|
|
||||||
|
|
||||||
.nightMode .hitem {
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hitem:hover {
|
.hitem:hover {
|
||||||
|
@ -38,3 +36,13 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.hitem:focus { outline: 0; }
|
.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
|
/* Copyright: Ankitects Pty Ltd and contributors
|
||||||
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
* License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html */
|
||||||
|
|
||||||
|
@use 'vars';
|
||||||
|
@use 'buttons';
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 2em;
|
margin: 2em;
|
||||||
}
|
}
|
||||||
|
@ -10,6 +13,6 @@ h1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
body.nightMode {
|
body.nightMode {
|
||||||
color: white;
|
color: vars.$night-text-fg;
|
||||||
background: #2f2f31;
|
background: vars.$night-window-bg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -352,7 +352,11 @@ function setFields(fields) {
|
||||||
|
|
||||||
function setBackgrounds(cols) {
|
function setBackgrounds(cols) {
|
||||||
for (let i = 0; i < cols.length; i++) {
|
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