From 7dcbc7efec08ec2928523408dd9bc7bdaae03fd8 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Thu, 23 Jan 2020 15:08:10 +1000 Subject: [PATCH] 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. --- pylib/anki/utils.py | 9 ++- qt/Makefile | 3 +- qt/aqt/.gitignore | 1 + qt/aqt/browser.py | 49 ++++--------- qt/aqt/clayout.py | 5 +- qt/aqt/deckbrowser.py | 10 +-- qt/aqt/editor.py | 4 +- qt/aqt/main.py | 34 ++------- qt/aqt/overview.py | 6 +- qt/aqt/preferences.py | 14 +++- qt/aqt/profiles.py | 6 ++ qt/aqt/reviewer.py | 11 +-- qt/aqt/theme.py | 126 ++++++++++++++++++++++++++++++++ qt/aqt/webview.py | 6 +- qt/designer/preferences.ui | 4 +- qt/tools/extract_scss_colors.py | 23 ++++++ qt/ts/scss/_buttons.scss | 27 +++++++ qt/ts/scss/_card_counts.scss | 28 +++++++ qt/ts/scss/_vars.scss | 46 ++++++++++++ qt/ts/scss/deckbrowser.scss | 41 +++++++++-- qt/ts/scss/editor.scss | 35 ++++++++- qt/ts/scss/overview.scss | 11 ++- qt/ts/scss/reviewer-bottom.scss | 13 +++- qt/ts/scss/toolbar.scss | 20 +++-- qt/ts/scss/webview.scss | 7 +- qt/ts/src/editor.ts | 6 +- 26 files changed, 430 insertions(+), 115 deletions(-) create mode 100644 qt/aqt/theme.py create mode 100644 qt/tools/extract_scss_colors.py create mode 100644 qt/ts/scss/_buttons.scss create mode 100644 qt/ts/scss/_card_counts.scss create mode 100644 qt/ts/scss/_vars.scss diff --git a/pylib/anki/utils.py b/pylib/anki/utils.py index 9566a027c..2a6fcc361 100644 --- a/pylib/anki/utils.py +++ b/pylib/anki/utils.py @@ -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 diff --git a/qt/Makefile b/qt/Makefile index 8079c4763..9f66d58b6 100644 --- a/qt/Makefile +++ b/qt/Makefile @@ -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 diff --git a/qt/aqt/.gitignore b/qt/aqt/.gitignore index c1b440396..b56d01f0a 100644 --- a/qt/aqt/.gitignore +++ b/qt/aqt/.gitignore @@ -1 +1,2 @@ buildinfo.py +colors.py diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 3808f137e..7063480ff 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.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 diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index aa4ef8253..5ab4bafe2 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -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") diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py index a152d75f0..8b81d4d2f 100644 --- a/qt/aqt/deckbrowser.py +++ b/qt/aqt/deckbrowser.py @@ -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 "%s" % (colour, cnt) + return f'{cnt}' buf += "%s%s" % ( - nonzeroColour(due, "#007700"), - nonzeroColour(new, "#000099"), + nonzeroColour(due, "review-count"), + nonzeroColour(new, "new-count"), ) # options buf += ( diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index b7795bf7f..cfd6d0545 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -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();") diff --git a/qt/aqt/main.py b/qt/aqt/main.py index a4e2824d2..27e1c0255 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -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""" % ( 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 ########################################################################## diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index e5a9c9891..3a75eb8a7 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -189,9 +189,9 @@ to their original deck."""
- - - + + +
%s:%s
%s:%s
%s:%s
%s:%s
%s:%s
%s:%s
%s
""" % ( diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 669c828ad..53714d911 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -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.")) diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 092e1cabe..51ee3c8de 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -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 diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index ebaef9038..c7c7371c5 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -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] = "%s" % (counts[idx]) space = " + " - ctxt = '%s' % counts[0] - ctxt += space + '%s' % counts[1] - ctxt += space + '%s' % counts[2] + ctxt = "%s" % counts[0] + ctxt += space + "%s" % counts[1] + ctxt += space + "%s" % counts[2] return ctxt def _defaultEase(self): diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py new file mode 100644 index 000000000..3d0e4e9f9 --- /dev/null +++ b/qt/aqt/theme.py @@ -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() diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index f40ed8ce8..a095c9c41 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -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 = """ @@ -321,7 +324,7 @@ body {{ zoom: {}; background: {}; {} }} {} -{} +{} """.format( self.title, self.zoomFactor(), @@ -329,6 +332,7 @@ body {{ zoom: {}; background: {}; {} }} fontspec, widgetspec, head, + body_class, body, ) # print(html) diff --git a/qt/designer/preferences.ui b/qt/designer/preferences.ui index 2f638ceb3..87e80b9bb 100644 --- a/qt/designer/preferences.ui +++ b/qt/designer/preferences.ui @@ -7,7 +7,7 @@ 0 0 422 - 579 + 586 @@ -111,7 +111,7 @@ - Show cards as white on black (night mode) + Night mode diff --git a/qt/tools/extract_scss_colors.py b/qt/tools/extract_scss_colors.py new file mode 100644 index 000000000..6202689f3 --- /dev/null +++ b/qt/tools/extract_scss_colors.py @@ -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)) diff --git a/qt/ts/scss/_buttons.scss b/qt/ts/scss/_buttons.scss new file mode 100644 index 000000000..a8c58d154 --- /dev/null +++ b/qt/ts/scss/_buttons.scss @@ -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; + } +} + diff --git a/qt/ts/scss/_card_counts.scss b/qt/ts/scss/_card_counts.scss new file mode 100644 index 000000000..e72e764ad --- /dev/null +++ b/qt/ts/scss/_card_counts.scss @@ -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; + } +} + diff --git a/qt/ts/scss/_vars.scss b/qt/ts/scss/_vars.scss new file mode 100644 index 000000000..9aa60ab36 --- /dev/null +++ b/qt/ts/scss/_vars.scss @@ -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; diff --git a/qt/ts/scss/deckbrowser.scss b/qt/ts/scss/deckbrowser.scss index 57887de2b..13b186a89 100644 --- a/qt/ts/scss/deckbrowser.scss +++ b/qt/ts/scss/deckbrowser.scss @@ -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); + } +} \ No newline at end of file diff --git a/qt/ts/scss/editor.scss b/qt/ts/scss/editor.scss index 6a51a19d6..a21d40f8b 100644 --- a/qt/ts/scss/editor.scss +++ b/qt/ts/scss/editor.scss @@ -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; + } +} \ No newline at end of file diff --git a/qt/ts/scss/overview.scss b/qt/ts/scss/overview.scss index d11b18e5f..82c3f1557 100644 --- a/qt/ts/scss/overview.scss +++ b/qt/ts/scss/overview.scss @@ -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; + } +} diff --git a/qt/ts/scss/reviewer-bottom.scss b/qt/ts/scss/reviewer-bottom.scss index 29c1c94fc..95810fc2c 100644 --- a/qt/ts/scss/reviewer-bottom.scss +++ b/qt/ts/scss/reviewer-bottom.scss @@ -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; + } +} diff --git a/qt/ts/scss/toolbar.scss b/qt/ts/scss/toolbar.scss index 8c842b564..970a81535 100644 --- a/qt/ts/scss/toolbar.scss +++ b/qt/ts/scss/toolbar.scss @@ -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; + } +} \ No newline at end of file diff --git a/qt/ts/scss/webview.scss b/qt/ts/scss/webview.scss index b824b591a..b729411fc 100644 --- a/qt/ts/scss/webview.scss +++ b/qt/ts/scss/webview.scss @@ -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; } diff --git a/qt/ts/src/editor.ts b/qt/ts/src/editor.ts index 8efe4bf23..3336eef8e 100644 --- a/qt/ts/src/editor.ts +++ b/qt/ts/src/editor.ts @@ -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"); + } } }