diff --git a/beeref/__main__.py b/beeref/__main__.py index 4899c83..1e8a679 100755 --- a/beeref/__main__.py +++ b/beeref/__main__.py @@ -25,8 +25,12 @@ from beeref import constants from beeref.assets import BeeAssets -from beeref.config import CommandlineArgs, BeeSettings, logfile_name -from beeref.utils import create_palette_from_dict +from beeref.config import ( + BeeSettings, + CommandlineArgs, + BeeStyleSheet, + logfile_name, +) from beeref.view import BeeGraphicsView logger = logging.getLogger(__name__) @@ -110,8 +114,7 @@ def main(): os.environ["QT_DEBUG_PLUGINS"] = "1" app = BeeRefApplication(sys.argv) - palette = create_palette_from_dict(constants.COLORS) - app.setPalette(palette) + app.setStyleSheet(BeeStyleSheet().qss) bee = BeeRefMainWindow(app) # NOQA:F841 signal.signal(signal.SIGINT, handle_sigint) diff --git a/beeref/config.py b/beeref/config.py index 9368c33..df2d4d3 100644 --- a/beeref/config.py +++ b/beeref/config.py @@ -16,11 +16,16 @@ """Handling of command line args and Qt settings.""" import argparse +from functools import lru_cache, cached_property import logging import logging.config import os.path +import shutil -from PyQt6 import QtCore +import tinycss2 +import tinycss2.color3 + +from PyQt6 import QtCore, QtGui from beeref import constants from beeref.logging import qt_message_handler @@ -241,6 +246,70 @@ def restore_defaults(self): settings_events.restore_keyboard_defaults.emit() +class BeeStyleSheet: + _instance = None + DEFAULT_PATH = os.path.join(os.path.dirname(__file__), + 'default_stylesheet.qss') + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super().__new__(cls, *args, **kwargs) + cls._instance.on_new() + return cls._instance + + def on_new(self): + dest = os.path.join( + os.path.dirname(BeeSettings().fileName()), + 'stylesheet.qss.example') + shutil.copy(self.DEFAULT_PATH, dest) + + path = os.path.join( + os.path.dirname(BeeSettings().fileName()), 'stylesheet.qss') + + if os.path.exists(path): + logger.info(f'Found custom stylesheet: {path}') + else: + logger.info(f'Using default stylesheet') + path = self.DEFAULT_PATH + + with open(path) as f: + self.qss = f.read() + + @cached_property + def parsed(self): + return tinycss2.parse_stylesheet( + self.qss, skip_comments=True, skip_whitespace=True) + + def _get_color_from_rule(self, rule, attribute): + found = False + for dec in rule.content: + if found: + color = tinycss2.color3.parse_color(dec.serialize()) + if color: + return QtGui.QColor( + int(color.red * 255), + int(color.green * 255), + int(color.blue * 255), + int(color.alpha * 255)) + else: + if getattr(dec, 'value', None) == attribute: + found = True + + @lru_cache + def get_color(self, element, attribute): + for rule in self.parsed: + for pre in rule.prelude: + if getattr(pre, 'value', None) == element: + return self._get_color_from_rule(rule, attribute) + + +def get_stylesheet(create_example=False): + """Read custom stylesheet from settings dir if it exists, else read + default style sheet from source code. + """ + + + def logfile_name(): return os.path.join( os.path.dirname(BeeSettings().fileName()), f'{constants.APPNAME}.log') diff --git a/beeref/constants.py b/beeref/constants.py index 24cbd81..7d0df65 100644 --- a/beeref/constants.py +++ b/beeref/constants.py @@ -19,24 +19,4 @@ WEBSITE = 'https://github.com/rbreu/beeref' COPYRIGHT = 'Copyright © 2021-2023 Rebecca Breu' -COLORS = { - # Qt: - 'Active:Base': (60, 60, 60), - 'Active:Window': (40, 40, 40), - 'Active:Button': (40, 40, 40), - 'Active:Text': (200, 200, 200), - 'Active:HighlightedText': (255, 255, 255), - 'Active:WindowText': (200, 200, 200), - 'Active:ButtonText': (200, 200, 200), - 'Active:Highlight': (83, 167, 165), - 'Active:Link': (90, 181, 179), - 'Disabled:Light': (0, 0, 0, 0), - 'Disabled:Text': (140, 140, 140), - - # BeeRef specific: - 'Scene:Selection': (116, 234, 231), - 'Scene:Canvas': (60, 60, 60), - 'Scene:Text': (200, 200, 200), - 'Table:AlternativeRow': (70, 70, 70), - -} +DEFAULT_SELECTION_COLOR = (116, 234, 231) diff --git a/beeref/default_stylesheet.qss b/beeref/default_stylesheet.qss new file mode 100644 index 0000000..2fe266e --- /dev/null +++ b/beeref/default_stylesheet.qss @@ -0,0 +1,115 @@ +/* This is BeeRef's default stylesheet provided as an example. + +Save your own stylesheet as stylesheet.qss +*/ + +* { + color: rgb(200, 200, 200); + background: rgb(50, 50, 50); +} + +QSpinBox::focus, +QKeySequenceEdit::focus { + border: 1px solid rgb(83, 167, 165); + border-radius: 3px; +} + +*::item:selected, +*::item:pressed +{ + color: rgb(255, 255, 255); + background: rgb(83, 167, 165); +} + +*::item:disabled { + color: rgb(140, 140, 140); +} + +QMenu { + border: 1px solid rgb(40, 40, 40); +} + +QPushButton { + border: 1px solid rgb(140, 140, 140); + border-radius: 3px; + padding: 3px; + background-color: rgb(50, 50, 50); +} + +QPushButton { + border: 1px solid rgb(140, 140, 140); + border-radius: 3px; + padding: 3px; + background-color: rgb(60, 60, 60); +} + +QPushButton::hover { + background-color: rgb(70, 70, 70); +} + +QPushButton::default { + border: 1px solid rgb(83, 167, 165); +} + +QPushButton::pressed { + background-color: rgb(50, 50, 50); +} + +QProgressBar { + border: 1px solid rgb(40, 40, 40); + border-radius: 3px; + text-align: center; +} + +QProgressBar::chunk { + background-color: rgb(83, 167, 165); + border-radius: 3px; +} + +QTableView { + alternate-background-color: rgb(60, 60, 60); + selection-background-color: rgb(83, 167, 165); +} + +QScrollBar::handle:horizontal, +QScrollBar::handle:vertical { + background: rgb(60, 60, 60); + border: 1px solid rgb(40, 40, 40); + border-radius: 3px; +} + +QScrollBar::handle:horizontal:hover, +QScrollBar::handle:vertical:hover { + background: rgb(70, 70, 70); + border: 1px solid rgb(40, 40, 40); + border-radius: 3px; +} + +QScrollBar::handle:horizontal:pressed, +QScrollBar::handle:vertical:pressed { + background: rgb(83, 167, 165); + border: 1px solid rgb(40, 40, 40); + border-radius: 3px; +} + +/* QScrollBar::add-line:horizontal { */ +/* border: 1px solid rgb(40, 40, 40); */ +/* border-radius: 3px; */ +/* } */ + + +QGraphicsView { + background: rgb(60, 60, 60); +} + +BeeTextItem { + color: rgb(200, 200, 200); +} + +BeeSelectionItem { + color: rgb(116, 234, 231); +} + +BeeRubberbandItem { + color: rgba(116, 234, 231, 0.16); +} diff --git a/beeref/fileio/export.py b/beeref/fileio/export.py index 30febe1..35eef35 100644 --- a/beeref/fileio/export.py +++ b/beeref/fileio/export.py @@ -21,6 +21,7 @@ from .errors import BeeFileIOError from beeref import constants, widgets +from beeref.config import BeeStyleSheet logger = logging.getLogger(__name__) @@ -92,7 +93,9 @@ def render_to_image(self): logger.debug(f'Final export margin: {margin}') image = QtGui.QImage(self.size, QtGui.QImage.Format.Format_RGB32) - image.fill(QtGui.QColor(*constants.COLORS['Scene:Canvas'])) + color = BeeStyleSheet().get_color('QGraphicsView', 'background') + if color: + image.fill(color) painter = QtGui.QPainter(image) target_rect = QtCore.QRectF( margin, diff --git a/beeref/items.py b/beeref/items.py index 207184b..07f1430 100644 --- a/beeref/items.py +++ b/beeref/items.py @@ -25,8 +25,7 @@ from PyQt6.QtCore import Qt from beeref import commands -from beeref.config import BeeSettings -from beeref.constants import COLORS +from beeref.config import BeeSettings, BeeStyleSheet from beeref.selection import SelectableMixin @@ -568,7 +567,9 @@ def __init__(self, text=None): self.init_selectable() self.is_editable = True self.edit_mode = False - self.setDefaultTextColor(QtGui.QColor(*COLORS['Scene:Text'])) + color = BeeStyleSheet().get_color('BeeTextItem', 'color') + if color: + self.setDefaultTextColor(color) @classmethod def create_from_data(cls, **kwargs): diff --git a/beeref/selection.py b/beeref/selection.py index a3a72f1..56a7e2a 100644 --- a/beeref/selection.py +++ b/beeref/selection.py @@ -15,6 +15,7 @@ """Classes that draw and handle selection stuff for items.""" +from functools import cached_property import logging import math @@ -23,15 +24,13 @@ from PyQt6.QtWidgets import QGraphicsItem from beeref.assets import BeeAssets -from beeref import commands -from beeref.config import CommandlineArgs -from beeref.constants import COLORS +from beeref import commands, constants +from beeref.config import CommandlineArgs, BeeStyleSheet from beeref import utils commandline_args = CommandlineArgs() logger = logging.getLogger(__name__) -SELECT_COLOR = QtGui.QColor(*COLORS['Scene:Selection']) def with_anchor(func): @@ -217,13 +216,20 @@ def paint_debug(self, painter, option, widget): self.draw_debug_shape( painter, self.select_handle_free_center(), 255, 0, 255) + @cached_property + def _select_color(self): + color = BeeStyleSheet().get_color('BeeSelectionItem', 'color') + if not color: + color = QtGui.QColor(*constants.DEFAULT_SELECTION_COLOR) + return color + def paint_selectable(self, painter, option, widget): self.paint_debug(painter, option, widget) if not self.has_selection_outline(): return - pen = QtGui.QPen(SELECT_COLOR) + pen = QtGui.QPen(self._select_color) pen.setWidth(self.SELECT_LINE_WIDTH) pen.setCosmetic(True) painter.setPen(pen) @@ -685,8 +691,10 @@ class RubberbandItem(BaseItemMixin, QtWidgets.QGraphicsRectItem): def __init__(self): super().__init__() - color = QtGui.QColor(SELECT_COLOR) - color.setAlpha(40) + color = BeeStyleSheet().get_color('BeeRubberbandItem', 'color') + if not color: + color = QtGui.QColor(*constants.DEFAULT_SELECTION_COLOR) + color.setAlpha(40) self.setBrush(QtGui.QBrush(color)) pen = QtGui.QPen(QtGui.QColor(0, 0, 0)) pen.setWidth(1) diff --git a/beeref/utils.py b/beeref/utils.py index 1ff8113..b6f31ca 100644 --- a/beeref/utils.py +++ b/beeref/utils.py @@ -18,36 +18,6 @@ from PyQt6 import QtCore, QtGui -def create_palette_from_dict(conf): - """Create a palette from a config dictionary. Keys are a string of - 'ColourGroup:ColorRole' and values are a (r, g, b) tuple. E.g: - { - 'Active:WindowText': (80, 100, 0), - ... - } - - Colors from the Active group will automatically be applied to the - Inactive group as well. Unknown color groups will be ignored. - """ - - palette = QtGui.QPalette() - for key, value in conf.items(): - group, role = key.split(':') - if hasattr(QtGui.QPalette.ColorGroup, group): - palette.setColor( - getattr(QtGui.QPalette.ColorGroup, group), - getattr(QtGui.QPalette.ColorRole, role), - QtGui.QColor(*value)) - if group == 'Active': - # Also set the Inactive colour group. - palette.setColor( - QtGui.QPalette.ColorGroup.Inactive, - getattr(QtGui.QPalette.ColorRole, role), - QtGui.QColor(*value)) - - return palette - - def get_rect_from_points(point1, point2): """Constructs a QRectF from the given QPointF. The points can be *any* two opposing corners of the rectangle.""" diff --git a/beeref/view.py b/beeref/view.py index a1bd242..46ccaee 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -48,9 +48,6 @@ def __init__(self, app, parent=None): self.parent = parent self.settings = BeeSettings() self.welcome_overlay = widgets.welcome_overlay.WelcomeOverlay(self) - - self.setBackgroundBrush( - QtGui.QBrush(QtGui.QColor(*constants.COLORS['Scene:Canvas']))) self.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing) self.setFrameShape(QtWidgets.QFrame.Shape.NoFrame) diff --git a/beeref/widgets/settings.py b/beeref/widgets/settings.py index 1c3d44f..1087756 100644 --- a/beeref/widgets/settings.py +++ b/beeref/widgets/settings.py @@ -184,6 +184,8 @@ class KeyboardShortcutsEditor(QtWidgets.QKeySequenceEdit): def __init__(self, parent, index): super().__init__(parent) + #self.setObjectName('QKeySequenceEdit') + print('***', self.objectName()) self.action = actions[index.row()] try: self.old_value = self.action.get_shortcuts()[index.column() - 2] @@ -322,13 +324,6 @@ def __init__(self): self.setFilterCaseSensitivity( QtCore.Qt.CaseSensitivity.CaseInsensitive) - def data(self, index, role): - if (role == QtCore.Qt.ItemDataRole.BackgroundRole - and index.row() % 2): - return QtGui.QColor(*constants.COLORS['Table:AlternativeRow']) - else: - return super().data(index, role) - def setData(self, index, value, role, remove_from_other=None): result = self.sourceModel().setData( self.mapToSource(index), @@ -352,6 +347,7 @@ def __init__(self, parent): 1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.setSelectionMode( QtWidgets.QHeaderView.SelectionMode.SingleSelection) + self.setAlternatingRowColors(True) settings_events.restore_keyboard_defaults.connect( self.on_restore_defaults) diff --git a/beeref/widgets/welcome_overlay.py b/beeref/widgets/welcome_overlay.py index ec5fc4b..a656a7e 100644 --- a/beeref/widgets/welcome_overlay.py +++ b/beeref/widgets/welcome_overlay.py @@ -106,12 +106,14 @@ def __init__(self, parent): files_layout.addWidget(self.files_view) files_layout.addStretch(50) self.files_widget.setLayout(files_layout) + self.files_widget.setStyleSheet('background: transparent;') self.files_widget.hide() # Help text self.label = QtWidgets.QLabel(self.txt, self) self.label.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignCenter) + self.label.setStyleSheet('background: transparent;') self.layout = QtWidgets.QHBoxLayout() self.layout.addStretch(50) self.layout.addWidget(self.label) diff --git a/setup.py b/setup.py index ddd9ac0..ebc290a 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ 'pyQt6-Qt6>=6.5.0,<=6.6.1', 'rectangle-packer>=2.0.1,<=2.0.2', 'exif>=1.3.5,<=1.6.0', + 'tinycss2==1.2.1', ], packages=[ 'beeref',