From 6ad5bb568de029898ef0f41b0607427bcf4ba986 Mon Sep 17 00:00:00 2001 From: Kolloom <31672176+thompson-vii@users.noreply.github.com> Date: Mon, 9 Jan 2023 01:52:09 -0600 Subject: [PATCH 01/11] Allow different parametrization for each configuration value (#5) * Raise fps limit and set stepsize to 10 * Expose SpinBoxesLayout to more options Added step size and max_value Co-authored-by: Kolloom --- .../spin_boxes_layout.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py b/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py index 94356233..8e370b1c 100644 --- a/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py +++ b/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py @@ -24,22 +24,22 @@ def __init__(self) -> None: self._forms: Dict[Config, SpinBox] = {} self._add_label("Common settings") - self._add_row(Config.SHORT_VS_LONG_PRESS_TIME, is_int=False) - self._add_row(Config.FPS_LIMIT, is_int=True) + self._add_row(Config.SHORT_VS_LONG_PRESS_TIME, step=0.05, max_value=5, is_int=False) + self._add_row(Config.FPS_LIMIT, step=10, max_value=300, is_int=True) self._add_label("Cursor trackers") - self._add_row(Config.TRACKER_SENSITIVITY_SCALE, is_int=False) - self._add_row(Config.TRACKER_DEADZONE, is_int=True) + self._add_row(Config.TRACKER_SENSITIVITY_SCALE, step=0.05, max_value=10, is_int=False) + self._add_row(Config.TRACKER_DEADZONE, step=1, max_value=99, is_int=True) self._add_label("Pie menus display") - self._add_row(Config.PIE_GLOBAL_SCALE, is_int=False) - self._add_row(Config.PIE_ICON_GLOBAL_SCALE, is_int=False) - self._add_row(Config.PIE_DEADZONE_GLOBAL_SCALE, is_int=False) - self._add_row(Config.PIE_ANIMATION_TIME, is_int=False) + self._add_row(Config.PIE_GLOBAL_SCALE, step=0.05, max_value=10, is_int=False) + self._add_row(Config.PIE_ICON_GLOBAL_SCALE, step=0.05, max_value=10, is_int=False) + self._add_row(Config.PIE_DEADZONE_GLOBAL_SCALE, step=0.05, max_value=10, is_int=False) + self._add_row(Config.PIE_ANIMATION_TIME, step=0.05, max_value=10, is_int=False) - def _add_row(self, config: Config, is_int: bool) -> None: - """Add a spin box to the layout along with its desctiption.""" - self.addRow(config.value, self._create_form(config, is_int)) + def _add_row(self, config: Config, step: float, max_value: float, is_int: bool) -> None: + """Add a spin box to the layout along with its description.""" + self.addRow(config.value, self._create_form(config, step, max_value, is_int)) def _add_label(self, text: str): label = QLabel(text) @@ -47,12 +47,13 @@ def _add_label(self, text: str): self.addRow(QSplitter(Qt.Horizontal)) self.addRow(label) - def _create_form(self, config: Config, is_int: bool) -> SpinBox: + def _create_form(self, config: Config, step: float, max_value: float, is_int: bool) -> SpinBox: """Store and return new spin box for required type (int or float).""" form = QSpinBox() if is_int else QDoubleSpinBox() form.setObjectName(config.value) form.setMinimum(0) - form.setSingleStep(1 if is_int else 0.05) # type: ignore + form.setSingleStep(step) # type: ignore + form.setMaximum(max_value) self._forms[config] = form return form From 45bb98e894951045dc77d3a4ea2d6f0a71d432fa Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 09:18:00 +0100 Subject: [PATCH 02/11] Fixed PEP8 mistakes in SpinBoxesLayout --- .../spin_boxes_layout.py | 84 +++++++++++++++---- 1 file changed, 68 insertions(+), 16 deletions(-) diff --git a/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py b/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py index 8e370b1c..90a5cf00 100644 --- a/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py +++ b/shortcut_composer/composer_utils/settings_dialog_utils/spin_boxes_layout.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from typing import Dict, Union +from dataclasses import dataclass from PyQt5.QtCore import Qt from PyQt5.QtWidgets import ( QDoubleSpinBox, @@ -19,27 +20,78 @@ class SpinBoxesLayout(QFormLayout): """Dialog zone consisting of spin boxes.""" + @dataclass + class ConfigParams: + """Adds spinbox parametrization to the config field.""" + config: Config + step: float + max_value: float + is_int: bool + def __init__(self) -> None: super().__init__() self._forms: Dict[Config, SpinBox] = {} self._add_label("Common settings") - self._add_row(Config.SHORT_VS_LONG_PRESS_TIME, step=0.05, max_value=5, is_int=False) - self._add_row(Config.FPS_LIMIT, step=10, max_value=300, is_int=True) + + self._add_row(self.ConfigParams( + Config.SHORT_VS_LONG_PRESS_TIME, + step=0.05, + max_value=4, + is_int=False)) + + self._add_row(self.ConfigParams( + Config.FPS_LIMIT, + step=5, + max_value=300, + is_int=True)) self._add_label("Cursor trackers") - self._add_row(Config.TRACKER_SENSITIVITY_SCALE, step=0.05, max_value=10, is_int=False) - self._add_row(Config.TRACKER_DEADZONE, step=1, max_value=99, is_int=True) + + self._add_row(self.ConfigParams( + Config.TRACKER_SENSITIVITY_SCALE, + step=0.05, + max_value=4, + is_int=False)) + + self._add_row(self.ConfigParams( + Config.TRACKER_DEADZONE, + step=1, + max_value=200, + is_int=True)) self._add_label("Pie menus display") - self._add_row(Config.PIE_GLOBAL_SCALE, step=0.05, max_value=10, is_int=False) - self._add_row(Config.PIE_ICON_GLOBAL_SCALE, step=0.05, max_value=10, is_int=False) - self._add_row(Config.PIE_DEADZONE_GLOBAL_SCALE, step=0.05, max_value=10, is_int=False) - self._add_row(Config.PIE_ANIMATION_TIME, step=0.05, max_value=10, is_int=False) - def _add_row(self, config: Config, step: float, max_value: float, is_int: bool) -> None: + self._add_row(self.ConfigParams( + Config.PIE_GLOBAL_SCALE, + step=0.05, + max_value=4, + is_int=False)) + + self._add_row(self.ConfigParams( + Config.PIE_ICON_GLOBAL_SCALE, + step=0.05, + max_value=4, + is_int=False)) + + self._add_row(self.ConfigParams( + Config.PIE_DEADZONE_GLOBAL_SCALE, + step=0.05, + max_value=4, + is_int=False)) + + self._add_row(self.ConfigParams( + Config.PIE_ANIMATION_TIME, + step=0.01, + max_value=1, + is_int=False)) + + def _add_row(self, config_params: ConfigParams) -> None: """Add a spin box to the layout along with its description.""" - self.addRow(config.value, self._create_form(config, step, max_value, is_int)) + self.addRow( + config_params.config.value, + self._create_form(config_params) + ) def _add_label(self, text: str): label = QLabel(text) @@ -47,15 +99,15 @@ def _add_label(self, text: str): self.addRow(QSplitter(Qt.Horizontal)) self.addRow(label) - def _create_form(self, config: Config, step: float, max_value: float, is_int: bool) -> SpinBox: + def _create_form(self, config_params: ConfigParams) -> SpinBox: """Store and return new spin box for required type (int or float).""" - form = QSpinBox() if is_int else QDoubleSpinBox() - form.setObjectName(config.value) + form = QSpinBox() if config_params.is_int else QDoubleSpinBox() + form.setObjectName(config_params.config.value) form.setMinimum(0) - form.setSingleStep(step) # type: ignore - form.setMaximum(max_value) + form.setMaximum(config_params.max_value) # type: ignore + form.setSingleStep(config_params.step) # type: ignore - self._forms[config] = form + self._forms[config_params.config] = form return form def refresh(self) -> None: From dc1717868e780a5e9eccacc9c4de9d9c7468ec9d Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 09:38:31 +0100 Subject: [PATCH 03/11] Fix displaying inactive presets and tags --- shortcut_composer/api_krita/wrappers/database.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shortcut_composer/api_krita/wrappers/database.py b/shortcut_composer/api_krita/wrappers/database.py index 9a724f94..6fc9730f 100644 --- a/shortcut_composer/api_krita/wrappers/database.py +++ b/shortcut_composer/api_krita/wrappers/database.py @@ -52,7 +52,9 @@ def get_preset_names_from_tag(self, tag_name: str) -> List[str]: ON t.id=rt.tag_id JOIN resources r ON r.id = rt.resource_id - WHERE t.name='{tag_name}' + WHERE + t.name='{tag_name}' + AND rt.active = 1 ''' return self._single_column_query(sql_query, "preset") @@ -61,6 +63,9 @@ def get_brush_tags(self) -> List[str]: sql_query = ''' SELECT t.name AS tag FROM tags t + WHERE + t.active = 1 + AND t.resource_type_id = 5 ''' return self._single_column_query(sql_query, "tag") From 16da9739035ae93505f57345e4efed9f30a09e19 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 10:01:09 +0100 Subject: [PATCH 04/11] PieWidget as popup instead of window --- shortcut_composer/templates/pie_menu_utils/pie_widget.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/pie_widget.py b/shortcut_composer/templates/pie_menu_utils/pie_widget.py index 8ae306e9..969251c2 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_widget.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_widget.py @@ -41,8 +41,11 @@ def __init__( self._style = style self._label_painters = self._create_label_painters() - flags = self.windowFlags() | Qt.FramelessWindowHint # type: ignore - self.setWindowFlags(flags) + self.setWindowFlags(( + self.windowFlags() | # type: ignore + Qt.Popup | + Qt.FramelessWindowHint | + Qt.NoDropShadowWindowHint)) self.setAttribute(Qt.WA_TranslucentBackground) self.setStyleSheet("background: transparent;") self.setCursor(Qt.CrossCursor) From 962a5082b60684e3ab295e7c7e5cffd7a918feee Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 10:06:57 +0100 Subject: [PATCH 05/11] Use default font to fix MacOS helvetica issue --- shortcut_composer/templates/pie_menu_utils/label.py | 11 +++++++++-- .../templates/pie_menu_utils/pie_style.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index 1f5886cf..1b8f35e3 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -6,7 +6,7 @@ from abc import ABC, abstractmethod from PyQt5.QtCore import QPoint, Qt -from PyQt5.QtGui import QFont, QPixmap, QColor, QIcon +from PyQt5.QtGui import QFont, QPixmap, QColor, QIcon, QFontDatabase from PyQt5.QtWidgets import QLabel, QWidget from api_krita.pyqt import Painter, Text, PixmapTransform @@ -95,7 +95,7 @@ def _create_pyqt_label(self) -> QLabel: label = QLabel(self.widget) label.setText(to_display.value) - label.setFont(QFont('Helvetica', self.style.font_size, QFont.Bold)) + label.setFont(self._font) label.setAlignment(Qt.AlignCenter) label.setGeometry(0, 0, round(heigth*2), round(heigth)) label.move(self.label.center.x()-heigth, @@ -108,6 +108,13 @@ def _create_pyqt_label(self) -> QLabel: label.show() return label + @property + def _font(self) -> QFont: + font = QFontDatabase.systemFont(QFontDatabase.TitleFont) + font.setPointSize(self.style.font_size) + font.setBold(True) + return font + @staticmethod def _color_to_str(color: QColor) -> str: return f''' {color.red()}, {color.green()}, {color.blue()}, {color.alpha()}''' diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index ab523b09..3fb9644c 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -78,6 +78,6 @@ def adapt_to_item_amount(self, amount: int) -> None: SYSTEM_FONT_SIZE = { "Linux": 0.40, "Windows": 0.25, - "Darwin": 0.25, + "Darwin": 0.6, "": 0.25, } From 71d750a8a725fa902f1982d957b64c41b1ac635c Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 11:45:15 +0100 Subject: [PATCH 06/11] Detect and support light krita theme --- shortcut_composer/api_krita/core_api.py | 11 ++- shortcut_composer/api_krita/pyqt/colorizer.py | 77 +++++++++++++------ shortcut_composer/templates/pie_menu.py | 2 +- .../templates/pie_menu_utils/pie_style.py | 25 ++++-- 4 files changed, 83 insertions(+), 32 deletions(-) diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index 87187d7b..423b4f88 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -1,11 +1,11 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later -from krita import Krita as Api, Extension +from krita import Krita as Api, Extension, qApp from typing import Callable, Protocol, Any from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QWidgetAction -from PyQt5.QtGui import QKeySequence +from PyQt5.QtGui import QKeySequence, QColor from .wrappers import ( ToolDescriptor, @@ -89,9 +89,16 @@ def add_extension(self, extension: Extension) -> None: """Add extension/plugin/add-on to krita.""" self.instance.addExtension(extension(self.instance)) + @property + def is_light_theme_active(self) -> bool: + main_color: QColor = qApp.palette().window().color() + average = (main_color.red()+main_color.green()+main_color.blue()) // 3 + return average > 128 + class KritaWindow(Protocol): """Krita window received in createActions() of main extension file.""" + def createAction( self, name: str, diff --git a/shortcut_composer/api_krita/pyqt/colorizer.py b/shortcut_composer/api_krita/pyqt/colorizer.py index 03f64956..79f7d649 100644 --- a/shortcut_composer/api_krita/pyqt/colorizer.py +++ b/shortcut_composer/api_krita/pyqt/colorizer.py @@ -1,26 +1,31 @@ # SPDX-FileCopyrightText: © 2022 Wojciech Trybus # SPDX-License-Identifier: GPL-3.0-or-later +from collections import defaultdict from enum import Enum from PyQt5.QtGui import QColor +from api_krita import Krita from ..enums import BlendingMode class Color(Enum): """Named custom colors.""" - ORANGE = QColor(236, 109, 39) + WHITE = QColor(248, 248, 248) LIGHT_GRAY = QColor(170, 170, 170) - BLUE = QColor(110, 217, 224) + DARK_GRAY = QColor(90, 90, 90) + BLACK = QColor(0, 0, 0) + YELLOW = QColor(253, 214, 56) + RED = QColor(245, 49, 116) + ORANGE = QColor(236, 109, 39) GREEN = QColor(169, 224, 69) DARK_GREEN = QColor(119, 184, 55) - RED = QColor(245, 49, 116) - YELLOW = QColor(253, 214, 56) - VIOLET = QColor(173, 133, 251) + BLUE = QColor(110, 217, 224) DARK_BLUE = QColor(42, 160, 251) - WHITE = QColor(248, 248, 242) + VERY_DARK_BLUE = QColor(22, 130, 221) + VIOLET = QColor(173, 133, 251) class Colorizer(QColor): @@ -29,34 +34,58 @@ class Colorizer(QColor): @classmethod def blending_mode(cls, mode: BlendingMode) -> QColor: """Return a QColor associated with blending mode. Gray by default.""" - return cls.BLENDING_MODES_MAP.get(mode, Color.LIGHT_GRAY).value + if Krita.is_light_theme_active: + return cls.BLENDING_MODES_LIGHT[mode].value + return cls.BLENDING_MODES_DARK[mode].value - @classmethod + @ classmethod def percentage(cls, percent: int) -> QColor: """Return a QColor associated with percentage value.""" return cls._percentage(percent).value - @staticmethod + @ staticmethod def _percentage(percent: int) -> Color: """Mapping of percentage values to custom colors.""" - if percent >= 100: - return Color.DARK_GREEN - if percent >= 80: - return Color.GREEN - if percent >= 50: - return Color.WHITE - if percent > 25: - return Color.LIGHT_GRAY - if percent > 10: - return Color.YELLOW - return Color.RED - - BLENDING_MODES_MAP = { + if Krita.is_light_theme_active: + if percent >= 100: + return Color.DARK_GREEN + if percent >= 80: + return Color.GREEN + if percent >= 50: + return Color.BLACK + if percent > 25: + return Color.DARK_GRAY + if percent > 10: + return Color.ORANGE + return Color.RED + else: + if percent >= 100: + return Color.DARK_GREEN + if percent >= 80: + return Color.GREEN + if percent >= 50: + return Color.WHITE + if percent > 25: + return Color.LIGHT_GRAY + if percent > 10: + return Color.YELLOW + return Color.RED + + BLENDING_MODES_DARK = defaultdict(lambda: Color.LIGHT_GRAY, { BlendingMode.NORMAL: Color.WHITE, BlendingMode.OVERLAY: Color.RED, BlendingMode.SCREEN: Color.GREEN, BlendingMode.COLOR: Color.YELLOW, BlendingMode.ADD: Color.DARK_BLUE, + BlendingMode.MULTIPLY: Color.VERY_DARK_BLUE, + }) + """Mapping of blending modes to custom colors in dark theme.""" + BLENDING_MODES_LIGHT = defaultdict(lambda: Color.DARK_GRAY, { + BlendingMode.NORMAL: Color.BLACK, + BlendingMode.OVERLAY: Color.RED, + BlendingMode.SCREEN: Color.ORANGE, + BlendingMode.COLOR: Color.VIOLET, + BlendingMode.ADD: Color.DARK_BLUE, BlendingMode.MULTIPLY: Color.BLUE, - } - """Mapping of blending modes to custom colors.""" + }) + """Mapping of blending modes to custom colors in light theme.""" diff --git a/shortcut_composer/templates/pie_menu.py b/shortcut_composer/templates/pie_menu.py index d13a27f8..501c0472 100644 --- a/shortcut_composer/templates/pie_menu.py +++ b/shortcut_composer/templates/pie_menu.py @@ -83,7 +83,7 @@ def __init__( instructions: List[Instruction] = [], pie_radius_scale: float = 1.0, icon_radius_scale: float = 1.0, - background_color: QColor = QColor(75, 75, 75, 190), + background_color: Optional[QColor] = None, active_color: QColor = QColor(100, 150, 230, 255), short_vs_long_press_time: Optional[float] = None ) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/pie_style.py b/shortcut_composer/templates/pie_menu_utils/pie_style.py index 3fb9644c..4f09ace8 100644 --- a/shortcut_composer/templates/pie_menu_utils/pie_style.py +++ b/shortcut_composer/templates/pie_menu_utils/pie_style.py @@ -3,6 +3,7 @@ import math import platform +from typing import Optional from dataclasses import dataclass from copy import copy @@ -24,12 +25,19 @@ class PieStyle: fit the given amount of labels. """ - pie_radius_scale: float - icon_radius_scale: float - background_color: QColor - active_color: QColor + def __init__( + self, + pie_radius_scale: float, + icon_radius_scale: float, + background_color: Optional[QColor], + active_color: QColor, + ) -> None: + + self.pie_radius_scale = pie_radius_scale + self.icon_radius_scale = icon_radius_scale + self.background_color = self._pick_background_color(background_color) + self.active_color = active_color - def __post_init__(self): base_size = Krita.screen_size/2560 self.pie_radius = round( @@ -75,6 +83,13 @@ def adapt_to_item_amount(self, amount: int) -> None: max_icon_size = round(self.pie_radius * math.pi / amount) self.icon_radius = min(self.icon_radius, max_icon_size) + def _pick_background_color(self, color: Optional[QColor]) -> QColor: + if color is not None: + return color + if Krita.is_light_theme_active: + return QColor(210, 210, 210, 190) + return QColor(75, 75, 75, 190) + SYSTEM_FONT_SIZE = { "Linux": 0.40, "Windows": 0.25, From d78df142b33b7b2057ff4b164042be22cfe88e04 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 12:27:08 +0100 Subject: [PATCH 07/11] Allow using actions when active layer is locked --- shortcut_composer/actions.action | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/shortcut_composer/actions.action b/shortcut_composer/actions.action index 7ff784ca..f2097746 100755 --- a/shortcut_composer/actions.action +++ b/shortcut_composer/actions.action @@ -11,17 +11,14 @@ 1 - 1 1 - 1 1 - 1 @@ -30,12 +27,10 @@ 1 - 1 1 - 1 @@ -44,12 +39,10 @@ 1 - 1 1 - 1 @@ -58,27 +51,22 @@ 1 - 1 1 - 1 1 - 1 1 - 1 1 - 1 @@ -87,32 +75,26 @@ 1 - 1 1 - 1 1 - 1 1 - 1 1 - 1 1 - 1 @@ -121,12 +103,10 @@ 0 - 0 0 - 0 From 713685c85eb8a882591ed088d426235efb9cc230 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 13:01:38 +0100 Subject: [PATCH 08/11] Work around krita zoom-dpi bug --- shortcut_composer/api_krita/wrappers/canvas.py | 18 +++++++++++++++--- .../api_krita/wrappers/document.py | 6 ++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/shortcut_composer/api_krita/wrappers/canvas.py b/shortcut_composer/api_krita/wrappers/canvas.py index 2ac2ef43..bd54e562 100644 --- a/shortcut_composer/api_krita/wrappers/canvas.py +++ b/shortcut_composer/api_krita/wrappers/canvas.py @@ -2,22 +2,30 @@ # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass -from typing import Protocol +from typing import Protocol, Any + +from .document import Document class KritaCanvas(Protocol): """Krita `Canvas` object API.""" + def rotation(self) -> float: ... def setRotation(self, angle_deg: float) -> None: ... def zoomLevel(self) -> float: ... def setZoomLevel(self, zoom: float) -> None: ... + def view(self) -> Any: ... @dataclass class Canvas: + """Wraps krita `Canvas` for typing, docs and PEP8 compatibility.""" canvas: KritaCanvas + def __post_init__(self): + self._zoom_scale = Document(self.canvas.view().document()).dpi/7200 + @property def rotation(self) -> float: """Settable property with rotation in degrees between `0` and `360`.""" @@ -30,8 +38,12 @@ def rotation(self, angle_deg: float) -> None: @property def zoom(self) -> float: - """Settable property with zoom level expressed in %.""" - return self.canvas.zoomLevel()/0.04166666 + """ + Settable property with zoom level expressed in %. + + Add a workaround for zoom detected by krita affected by document dpi. + """ + return self.canvas.zoomLevel() / self._zoom_scale @zoom.setter def zoom(self, zoom: float) -> None: diff --git a/shortcut_composer/api_krita/wrappers/document.py b/shortcut_composer/api_krita/wrappers/document.py index 6d54e641..a12b76a6 100644 --- a/shortcut_composer/api_krita/wrappers/document.py +++ b/shortcut_composer/api_krita/wrappers/document.py @@ -9,9 +9,11 @@ class KritaDocument(Protocol): """Krita `Document` object API.""" + def activeNode(self) -> KritaNode: ... def setActiveNode(self, node: KritaNode): ... def topLevelNodes(self) -> List[KritaNode]: ... + def resolution(self) -> int: ... def currentTime(self) -> int: ... def setCurrentTime(self, time: int) -> None: ... def refreshProjection(self) -> None: ... @@ -57,6 +59,10 @@ def recursive_search(nodes: List[Node], found_so_far: List[Node]): return found_so_far return recursive_search(self.get_top_nodes(), []) + @property + def dpi(self): + return self.document.resolution() + def refresh(self) -> None: """Refresh OpenGL projection of this document.""" self.document.refreshProjection() From 9026d603ed1fabc54f6643200d915ab6c80a6de5 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 14:55:27 +0100 Subject: [PATCH 09/11] Reload plugin on theme change --- shortcut_composer/api_krita/core_api.py | 9 +++++++++ shortcut_composer/shortcut_composer.py | 3 +++ 2 files changed, 12 insertions(+) diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index 423b4f88..b2bac73c 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QMainWindow, QDesktopWidget, QWidgetAction from PyQt5.QtGui import QKeySequence, QColor +from PyQt5.QtCore import QTimer from .wrappers import ( ToolDescriptor, @@ -25,6 +26,7 @@ class KritaInstance: def __init__(self) -> None: self.instance = Api.instance() self.screen_size = QDesktopWidget().screenGeometry(-1).width() + self.main_window: Any = None def get_active_view(self) -> View: """Return wrapper of krita `View`.""" @@ -89,6 +91,13 @@ def add_extension(self, extension: Extension) -> None: """Add extension/plugin/add-on to krita.""" self.instance.addExtension(extension(self.instance)) + def add_theme_change_callback(self, callback: Callable[[], None]) -> Any: + def connect_callback(): + self.main_window = self.instance.activeWindow() + if self.main_window is not None: + self.main_window.themeChanged.connect(callback) + QTimer.singleShot(1000, connect_callback) + @property def is_light_theme_active(self) -> bool: main_color: QColor = qApp.palette().window().color() diff --git a/shortcut_composer/shortcut_composer.py b/shortcut_composer/shortcut_composer.py index 77786a2b..96efc94e 100755 --- a/shortcut_composer/shortcut_composer.py +++ b/shortcut_composer/shortcut_composer.py @@ -26,6 +26,7 @@ def createActions(self, window) -> None: - Create usual actions for transform modes using `TransformModeActions` - Create usual actions for reloading the extension and settings dialog + - Add a callback to reload plugin when krita theme changes - Create complex action manager which holds and binds them to krita """ self._transform_modes = TransformModeActions(window) @@ -34,6 +35,8 @@ def createActions(self, window) -> None: self._reload_action = self._create_reload_action(window) self._settings_action = self._create_settings_action(window) + Krita.add_theme_change_callback(self._reload_composer) + self._manager = ActionManager(window) self._reload_composer() From a10b0a82de2e8b10015331794f86d8412ff7c362 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 15:00:43 +0100 Subject: [PATCH 10/11] Add missing docstrings --- shortcut_composer/api_krita/core_api.py | 7 +++++++ shortcut_composer/api_krita/pyqt/colorizer.py | 4 ++-- shortcut_composer/api_krita/wrappers/document.py | 1 + shortcut_composer/templates/pie_menu_utils/label.py | 1 + 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/shortcut_composer/api_krita/core_api.py b/shortcut_composer/api_krita/core_api.py index b2bac73c..372fab85 100644 --- a/shortcut_composer/api_krita/core_api.py +++ b/shortcut_composer/api_krita/core_api.py @@ -92,6 +92,12 @@ def add_extension(self, extension: Extension) -> None: self.instance.addExtension(extension(self.instance)) def add_theme_change_callback(self, callback: Callable[[], None]) -> Any: + """ + Add method which should be run after the theme is changed. + + Method is delayed with a timer to allow running it on plugin + initialization phase. + """ def connect_callback(): self.main_window = self.instance.activeWindow() if self.main_window is not None: @@ -100,6 +106,7 @@ def connect_callback(): @property def is_light_theme_active(self) -> bool: + """Return if currently set theme is light using it's main color.""" main_color: QColor = qApp.palette().window().color() average = (main_color.red()+main_color.green()+main_color.blue()) // 3 return average > 128 diff --git a/shortcut_composer/api_krita/pyqt/colorizer.py b/shortcut_composer/api_krita/pyqt/colorizer.py index 79f7d649..e22a90b8 100644 --- a/shortcut_composer/api_krita/pyqt/colorizer.py +++ b/shortcut_composer/api_krita/pyqt/colorizer.py @@ -38,12 +38,12 @@ def blending_mode(cls, mode: BlendingMode) -> QColor: return cls.BLENDING_MODES_LIGHT[mode].value return cls.BLENDING_MODES_DARK[mode].value - @ classmethod + @classmethod def percentage(cls, percent: int) -> QColor: """Return a QColor associated with percentage value.""" return cls._percentage(percent).value - @ staticmethod + @staticmethod def _percentage(percent: int) -> Color: """Mapping of percentage values to custom colors.""" if Krita.is_light_theme_active: diff --git a/shortcut_composer/api_krita/wrappers/document.py b/shortcut_composer/api_krita/wrappers/document.py index a12b76a6..7e371896 100644 --- a/shortcut_composer/api_krita/wrappers/document.py +++ b/shortcut_composer/api_krita/wrappers/document.py @@ -61,6 +61,7 @@ def recursive_search(nodes: List[Node], found_so_far: List[Node]): @property def dpi(self): + """Return dpi (dot per inch) of the document.""" return self.document.resolution() def refresh(self) -> None: diff --git a/shortcut_composer/templates/pie_menu_utils/label.py b/shortcut_composer/templates/pie_menu_utils/label.py index 1b8f35e3..a837dcf4 100644 --- a/shortcut_composer/templates/pie_menu_utils/label.py +++ b/shortcut_composer/templates/pie_menu_utils/label.py @@ -110,6 +110,7 @@ def _create_pyqt_label(self) -> QLabel: @property def _font(self) -> QFont: + """Return font which to use in pyqt label.""" font = QFontDatabase.systemFont(QFontDatabase.TitleFont) font.setPointSize(self.style.font_size) font.setBold(True) From 978f9e77676cad58aaa3e400b9fc61179c73a010 Mon Sep 17 00:00:00 2001 From: Wojciech Trybus Date: Mon, 9 Jan 2023 18:14:52 +0100 Subject: [PATCH 11/11] Updated documentation --- README.md | 14 ++++++++++++-- shortcut_composer/api_krita/pyqt/colorizer.py | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 43893075..23191199 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,23 @@ The plugin adds new shortcuts of the following types: [![PIE MENUS - introducing Shortcut Composer](http://img.youtube.com/vi/hrjBycVYFZM/0.jpg)](https://www.youtube.com/watch?v=hrjBycVYFZM "PIE MENUS - introducing Shortcut Composer") +## What's new in v1.0.1 +Fixes following issues: +- Fix displaying inactive presets and tags +- Detect and support light krita theme, switch between them when changed +- PieWidget as popup - pies no longer recognized by OS as windows +- Allow different parameters for each configuration value +- Use default font to fix MacOS helvetica issue +- Allow using actions when active layer is locked +- Work around krita bug: zoom being dependent on document dpi + ## Requirements -Shortcut Composer **v1.0** Requires krita **5.1.0** or later. +Shortcut Composer **v1.0.1** Requires krita **5.1.0** or later. Supported operating systems: - [x] Windows 10/11 - [x] Linux -- [ ] MacOS (Not tested yet - your feedback is very appreciated) +- [ ] MacOS (Known bug of canvas losing focus after using PieMenu) - [ ] Android (Does not support python plugins yet) ## Installation: diff --git a/shortcut_composer/api_krita/pyqt/colorizer.py b/shortcut_composer/api_krita/pyqt/colorizer.py index e22a90b8..ebfbcaa4 100644 --- a/shortcut_composer/api_krita/pyqt/colorizer.py +++ b/shortcut_composer/api_krita/pyqt/colorizer.py @@ -77,7 +77,7 @@ def _percentage(percent: int) -> Color: BlendingMode.SCREEN: Color.GREEN, BlendingMode.COLOR: Color.YELLOW, BlendingMode.ADD: Color.DARK_BLUE, - BlendingMode.MULTIPLY: Color.VERY_DARK_BLUE, + BlendingMode.MULTIPLY: Color.BLUE, }) """Mapping of blending modes to custom colors in dark theme.""" BLENDING_MODES_LIGHT = defaultdict(lambda: Color.DARK_GRAY, { @@ -86,6 +86,6 @@ def _percentage(percent: int) -> Color: BlendingMode.SCREEN: Color.ORANGE, BlendingMode.COLOR: Color.VIOLET, BlendingMode.ADD: Color.DARK_BLUE, - BlendingMode.MULTIPLY: Color.BLUE, + BlendingMode.MULTIPLY: Color.VERY_DARK_BLUE, }) """Mapping of blending modes to custom colors in light theme."""