Skip to content

Commit

Permalink
Merge pull request #7 from wojtryb/development
Browse files Browse the repository at this point in the history
Deploy v1.0.1
  • Loading branch information
wojtryb authored Jan 9, 2023
2 parents 4059726 + 978f9e7 commit 02034f1
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 77 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
20 changes: 0 additions & 20 deletions shortcut_composer/actions.action
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,14 @@

<Action name="Temporary move tool">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Temporary eraser">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Temporary preserve alpha">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

</Actions>
Expand All @@ -30,12 +27,10 @@

<Action name="Preview current layer visibility">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Preview projection below">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

</Actions>
Expand All @@ -44,12 +39,10 @@

<Action name="Cycle painting opacity">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Cycle selection tools">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

</Actions>
Expand All @@ -58,27 +51,22 @@

<Action name="Scroll undo stack">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Scroll isolated layers">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Scroll timeline or animated layers">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Scroll brush size or opacity">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Scroll canvas zoom or rotation">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

</Actions>
Expand All @@ -87,32 +75,26 @@

<Action name="Pick misc tools">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Pick painting blending modes">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Pick transform tool modes">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Pick brush presets (red)">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Pick brush presets (green)">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

<Action name="Pick brush presets (blue)">
<activationFlags>1</activationFlags>
<activationConditions>1</activationConditions>
</Action>

</Actions>
Expand All @@ -121,12 +103,10 @@

<Action name="Configure Shortcut Composer">
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
</Action>

<Action name="Reload Shortcut Composer">
<activationFlags>0</activationFlags>
<activationConditions>0</activationConditions>
</Action>

<Action name="Transform tool: free">
Expand Down
27 changes: 25 additions & 2 deletions shortcut_composer/api_krita/core_api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# SPDX-FileCopyrightText: © 2022 Wojciech Trybus <[email protected]>
# 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 PyQt5.QtCore import QTimer

from .wrappers import (
ToolDescriptor,
Expand All @@ -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`."""
Expand Down Expand Up @@ -89,9 +91,30 @@ 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:
"""
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:
self.main_window.themeChanged.connect(callback)
QTimer.singleShot(1000, 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


class KritaWindow(Protocol):
"""Krita window received in createActions() of main extension file."""

def createAction(
self,
name: str,
Expand Down
73 changes: 51 additions & 22 deletions shortcut_composer/api_krita/pyqt/colorizer.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
# SPDX-FileCopyrightText: © 2022 Wojciech Trybus <[email protected]>
# 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):
Expand All @@ -29,7 +34,9 @@ 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
def percentage(cls, percent: int) -> QColor:
Expand All @@ -39,24 +46,46 @@ def percentage(cls, percent: int) -> QColor:
@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.BLUE,
}
"""Mapping of blending modes to custom colors."""
})
"""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.VERY_DARK_BLUE,
})
"""Mapping of blending modes to custom colors in light theme."""
18 changes: 15 additions & 3 deletions shortcut_composer/api_krita/wrappers/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`."""
Expand All @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion shortcut_composer/api_krita/wrappers/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand All @@ -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")

Expand Down
Loading

0 comments on commit 02034f1

Please sign in to comment.