Add dark/light toggle button to sidebar
This commit is contained in:
@@ -28,7 +28,8 @@ from typing import Final
|
||||
from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
|
||||
|
||||
from novelwriter.enum import (
|
||||
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape
|
||||
nwBuildFmt, nwComment, nwItemClass, nwItemLayout, nwOutline, nwStatusShape,
|
||||
nwTheme
|
||||
)
|
||||
|
||||
|
||||
@@ -453,6 +454,16 @@ class nwLabels:
|
||||
"blue": QT_TRANSLATE_NOOP("Constant", "Blue"),
|
||||
"purple": QT_TRANSLATE_NOOP("Constant", "Purple"),
|
||||
}
|
||||
THEME_MODE_ICON: Final[dict[nwTheme, str]] = {
|
||||
nwTheme.AUTO: "theme_auto",
|
||||
nwTheme.LIGHT: "theme_light",
|
||||
nwTheme.DARK: "theme_dark",
|
||||
}
|
||||
THEME_MODE_LABEL: Final[dict[nwTheme, str]] = {
|
||||
nwTheme.AUTO: QT_TRANSLATE_NOOP("Constant", "System Theme"),
|
||||
nwTheme.LIGHT: QT_TRANSLATE_NOOP("Constant", "Light Theme"),
|
||||
nwTheme.DARK: QT_TRANSLATE_NOOP("Constant", "Dark Theme"),
|
||||
}
|
||||
|
||||
|
||||
class nwHeadFmt:
|
||||
|
||||
@@ -27,12 +27,13 @@ import logging
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal
|
||||
from PyQt6.QtCore import QEvent, QPoint, QSize, pyqtSignal, pyqtSlot
|
||||
from PyQt6.QtWidgets import QMenu, QVBoxLayout, QWidget
|
||||
|
||||
from novelwriter import SHARED
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import qtLambda
|
||||
from novelwriter.enum import nwView
|
||||
from novelwriter.constants import nwLabels, trConst
|
||||
from novelwriter.enum import nwTheme, nwView
|
||||
from novelwriter.extensions.eventfilters import StatusTipFilter
|
||||
from novelwriter.extensions.modified import NIconToolButton
|
||||
from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
|
||||
@@ -77,6 +78,10 @@ class GuiSideBar(QWidget):
|
||||
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
||||
self.tbOutline.clicked.connect(qtLambda(self.requestViewChange.emit, nwView.OUTLINE))
|
||||
|
||||
self.tbTheme = NIconToolButton(self, iSz)
|
||||
self.tbTheme.setToolTip(self.tr("Switch Colour Theme"))
|
||||
self.tbTheme.clicked.connect(self._cycleColurTheme)
|
||||
|
||||
self.tbBuild = NIconToolButton(self, iSz)
|
||||
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
|
||||
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
||||
@@ -109,6 +114,7 @@ class GuiSideBar(QWidget):
|
||||
self.outerBox.addWidget(self.tbOutline)
|
||||
self.outerBox.addWidget(self.tbBuild)
|
||||
self.outerBox.addStretch(1)
|
||||
self.outerBox.addWidget(self.tbTheme)
|
||||
self.outerBox.addWidget(self.tbDetails)
|
||||
self.outerBox.addWidget(self.tbStats)
|
||||
self.outerBox.addWidget(self.tbSettings)
|
||||
@@ -131,6 +137,7 @@ class GuiSideBar(QWidget):
|
||||
self.tbSearch.setStyleSheet(buttonStyle)
|
||||
self.tbOutline.setStyleSheet(buttonStyle)
|
||||
self.tbBuild.setStyleSheet(buttonStyle)
|
||||
self.tbTheme.setStyleSheet(buttonStyle)
|
||||
self.tbDetails.setStyleSheet(buttonStyle)
|
||||
self.tbStats.setStyleSheet(buttonStyle)
|
||||
self.tbSettings.setStyleSheet(buttonStyle)
|
||||
@@ -144,6 +151,36 @@ class GuiSideBar(QWidget):
|
||||
self.tbStats.setThemeIcon("sb_stats")
|
||||
self.tbSettings.setThemeIcon("settings")
|
||||
|
||||
self._setThemeModeIcon()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _cycleColurTheme(self) -> None:
|
||||
"""Go to nex colour theme."""
|
||||
match CONFIG.themeMode:
|
||||
case nwTheme.AUTO:
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
case nwTheme.LIGHT:
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
case nwTheme.DARK:
|
||||
CONFIG.themeMode = nwTheme.AUTO
|
||||
self.mainGui.checkThemeUpdate()
|
||||
self._setThemeModeIcon()
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setThemeModeIcon(self) -> None:
|
||||
"""Set the theme button icon."""
|
||||
self.tbTheme.setThemeIcon(nwLabels.THEME_MODE_ICON[CONFIG.themeMode])
|
||||
self.tbTheme.setToolTip(trConst(nwLabels.THEME_MODE_LABEL[CONFIG.themeMode]))
|
||||
return
|
||||
|
||||
|
||||
|
||||
+17
-16
@@ -100,22 +100,22 @@ class GuiTheme:
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"_availSyntax", "_availThemes", "_darkThemes", "_guiPalette", "_lightThemes", "_qColors",
|
||||
"_styleSheets", "_svgColors", "_syntaxList", "_themeList", "baseButtonHeight",
|
||||
"baseIconHeight", "baseIconSize", "buttonIconSize", "errorText", "fadedText",
|
||||
"fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
|
||||
"_availSyntax", "_availThemes", "_currentTheme", "_darkThemes", "_guiPalette",
|
||||
"_lightThemes", "_qColors", "_styleSheets", "_svgColors", "_syntaxList", "_themeList",
|
||||
"baseButtonHeight", "baseIconHeight", "baseIconSize", "buttonIconSize", "errorText",
|
||||
"fadedText", "fontPixelSize", "fontPointSize", "getDecoration", "getHeaderDecoration",
|
||||
"getHeaderDecorationNarrow", "getIcon", "getItemIcon", "getPixmap", "getToggleIcon",
|
||||
"guiFont", "guiFontB", "guiFontBU", "guiFontFixed", "guiFontSmall", "helpText",
|
||||
"iconCache", "isDarkTheme", "syntaxMeta", "syntaxTheme", "textNHeight", "textNWidth",
|
||||
"iconCache", "isDarkTheme", "syntaxTheme", "textNHeight", "textNWidth",
|
||||
"themeMeta",
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self.iconCache = GuiIcons(self)
|
||||
|
||||
# GUI Theme
|
||||
# Theme Objects
|
||||
self.themeMeta = ThemeMeta()
|
||||
self.iconCache = GuiIcons(self)
|
||||
self.syntaxTheme = SyntaxColors()
|
||||
self.isDarkTheme = False
|
||||
|
||||
# Special Text Colours
|
||||
@@ -123,11 +123,8 @@ class GuiTheme:
|
||||
self.fadedText = QColor(0, 0, 0)
|
||||
self.errorText = QColor(255, 0, 0)
|
||||
|
||||
# Syntax Theme
|
||||
self.syntaxMeta = ThemeMeta()
|
||||
self.syntaxTheme = SyntaxColors()
|
||||
|
||||
# Load Themes
|
||||
self._currentTheme = ""
|
||||
self._guiPalette = QPalette()
|
||||
self._themeList: list[T_ThemeEntry] = []
|
||||
self._availThemes: dict[str, Path] = {}
|
||||
@@ -275,6 +272,10 @@ class GuiTheme:
|
||||
theme = DEF_GUI_LIGHT
|
||||
CONFIG.lightTheme = DEF_GUI_LIGHT
|
||||
|
||||
if theme == self._currentTheme:
|
||||
logger.info("Theme '%s' is already loaded", theme)
|
||||
return False
|
||||
|
||||
if not (file := self._availThemes.get(theme)):
|
||||
logger.error("Could not load GUI theme")
|
||||
return False
|
||||
@@ -387,7 +388,6 @@ class GuiTheme:
|
||||
text = self._guiPalette.text().color()
|
||||
window = self._guiPalette.window().color()
|
||||
highlight = self._guiPalette.highlight().color()
|
||||
isDark = text.lightnessF() > window.lightnessF()
|
||||
|
||||
QtColActive = QPalette.ColorGroup.Active
|
||||
QtColInactive = QPalette.ColorGroup.Inactive
|
||||
@@ -407,8 +407,8 @@ class GuiTheme:
|
||||
darkOff = dark.darker(150)
|
||||
shadowOff = ref.darker(150)
|
||||
|
||||
grey = QColor(120, 120, 120) if isDark else QColor(140, 140, 140)
|
||||
dimmed = QColor(130, 130, 130) if isDark else QColor(190, 190, 190)
|
||||
grey = QColor(120, 120, 120) if darkMode else QColor(140, 140, 140)
|
||||
dimmed = QColor(130, 130, 130) if darkMode else QColor(190, 190, 190)
|
||||
|
||||
placeholder = QColor(text)
|
||||
placeholder.setAlpha(128)
|
||||
@@ -453,10 +453,11 @@ class GuiTheme:
|
||||
self.iconCache.loadTheme(CONFIG.iconTheme)
|
||||
|
||||
# Finalise
|
||||
self.isDarkTheme = isDark
|
||||
self.isDarkTheme = darkMode
|
||||
QApplication.setPalette(self._guiPalette)
|
||||
self._buildStyleSheets(self._guiPalette)
|
||||
|
||||
self._currentTheme = theme
|
||||
CONFIG.splashMessage(f"Loaded GUI theme: {meta.name}")
|
||||
|
||||
return True
|
||||
|
||||
+38
-17
@@ -30,7 +30,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSlot
|
||||
from PyQt6.QtCore import QEvent, Qt, QTimer, pyqtSlot
|
||||
from PyQt6.QtGui import QCloseEvent, QCursor, QIcon, QShortcut
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox,
|
||||
@@ -902,10 +902,46 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return not self.splitView.isVisible()
|
||||
|
||||
def checkThemeUpdate(self) -> None:
|
||||
"""Load theme if mode changed."""
|
||||
if SHARED.theme.loadTheme():
|
||||
self.refreshThemeColors(syntax=True)
|
||||
self.docEditor.initEditor()
|
||||
self.docViewer.initViewer()
|
||||
return
|
||||
|
||||
def refreshThemeColors(self, syntax: bool) -> None:
|
||||
"""Refresh the GUI theme."""
|
||||
SHARED.theme.loadTheme()
|
||||
self.setPalette(QApplication.palette())
|
||||
self.docEditor.updateTheme()
|
||||
self.docViewer.updateTheme()
|
||||
self.docViewerPanel.updateTheme()
|
||||
self.sideBar.updateTheme()
|
||||
self.projView.updateTheme()
|
||||
self.novelView.updateTheme()
|
||||
self.projSearch.updateTheme()
|
||||
self.outlineView.updateTheme()
|
||||
self.itemDetails.updateTheme()
|
||||
self.mainStatus.updateTheme()
|
||||
SHARED.project.tree.refreshAllItems()
|
||||
|
||||
if syntax:
|
||||
self.docEditor.updateSyntaxColors()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
|
||||
def changeEvent(self, event: QEvent) -> None:
|
||||
"""Capture application change events."""
|
||||
if int(event.type()) == 210:
|
||||
# ThemeChange
|
||||
self.checkThemeUpdate()
|
||||
return
|
||||
|
||||
def closeEvent(self, event: QCloseEvent) -> None:
|
||||
"""Capture the closing event of the GUI and call the close
|
||||
function to handle all the close process steps.
|
||||
@@ -1054,22 +1090,7 @@ class GuiMain(QMainWindow):
|
||||
self.novelView.refreshCurrentTree()
|
||||
|
||||
if theme:
|
||||
SHARED.theme.loadTheme()
|
||||
self.setPalette(QApplication.palette())
|
||||
self.docEditor.updateTheme()
|
||||
self.docViewer.updateTheme()
|
||||
self.docViewerPanel.updateTheme()
|
||||
self.sideBar.updateTheme()
|
||||
self.projView.updateTheme()
|
||||
self.novelView.updateTheme()
|
||||
self.projSearch.updateTheme()
|
||||
self.outlineView.updateTheme()
|
||||
self.itemDetails.updateTheme()
|
||||
self.mainStatus.updateTheme()
|
||||
SHARED.project.tree.refreshAllItems()
|
||||
|
||||
if syntax:
|
||||
self.docEditor.updateSyntaxColors()
|
||||
self.refreshThemeColors(syntax=syntax)
|
||||
|
||||
self.docEditor.initEditor()
|
||||
self.docViewer.initViewer()
|
||||
|
||||
Reference in New Issue
Block a user