Skip to content

Commit

Permalink
Add state persistence
Browse files Browse the repository at this point in the history
  • Loading branch information
evnlme committed Apr 4, 2024
1 parent c4cf030 commit e7114de
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 32 deletions.
78 changes: 52 additions & 26 deletions half_tone_selector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Qt,
QColor,
QWidget,
QLayout,
QHBoxLayout,
QVBoxLayout,
QPushButton,
Expand All @@ -20,6 +21,7 @@
QScrollArea,
)
from .lib import (
AppState,
getFGColor,
getBGColor,
setFGColor,
Expand All @@ -31,11 +33,18 @@
addLayout,
)

# Data store so state isn't lost when Krita closes.
dataPath = Path(__file__).resolve().parent / '_data.json'
S = AppState()
if dataPath.exists():
S = AppState.from_file(dataPath)
Krita.instance().notifier().applicationClosing.connect(lambda: S.to_file(dataPath))

def createSelectToneWidget(
name: str,
options: list,
defaultTone: QColor,
updateTone: Callable[[QColor], None],
getTone: Callable[[], QColor],
setTone: Callable[[QColor], None],
style: dict,
) -> QWidget:
def setLabelStyle(widget):
Expand All @@ -50,7 +59,7 @@ def setLabelStyle(widget):
''')

def setColor(widget, color):
updateTone(color)
setTone(color)
fontColor = scaleColor(color, 1, 96 if color.value() < 128 else -96)
widget.setStyleSheet(f'''
QPushButton {{
Expand All @@ -67,10 +76,10 @@ def setColor(widget, color):

def setColorStyle(widget):
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
setColor(widget, defaultTone)
setColor(widget, getTone())

def handleColorDialog(widget):
color = QColorDialog.getColor(options=QColorDialog.ShowAlphaChannel)
color = QColorDialog.getColor(getTone())
if color.isValid():
setColor(widget, color)

Expand Down Expand Up @@ -117,10 +126,15 @@ def createOptionButton(widget, opt):

return widget

def createColorBarWidget(colors: List[QColor], style: dict) -> QWidget:
def createColorBarWidget(
colors: List[QColor],
style: dict,
parentLayout: QLayout,
) -> QWidget:
def createColorPatch(color):
patch = QPushButton()
patch.setMinimumSize(16, 16)
patch.setToolTip(str(color.getRgb()[:3]))
patch.setStyleSheet(f'''
QPushButton {{
border: 0px solid transparent;
Expand All @@ -142,18 +156,21 @@ def createColorPatch(color):
patch = createColorPatch(color)
layout.addWidget(patch)

def delete():
i = parentLayout.indexOf(widget)
S.halfTones.pop(i)
widget.deleteLater()

deleteButton = QPushButton()
deleteButton.setIcon(Krita.instance().icon('deletelayer'))
deleteButton.clicked.connect(lambda: widget.deleteLater())
deleteButton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
deleteButton.clicked.connect(delete)
layout.addWidget(deleteButton)

parentLayout.addWidget(widget)
return widget

def createHalfToneSelectorWidget() -> QWidget:
state = {
'light': QColor(128, 128, 128),
'dark': QColor(64, 64, 64),
}
windowColor = getWindowColor()
isDark = windowColor.value() < 128
style = {
Expand All @@ -171,8 +188,8 @@ def createHalfToneSelectorWidget() -> QWidget:
{'name': 'FG', 'getColor': getFGColor},
{'name': 'BG', 'getColor': getBGColor},
],
state['light'],
lambda tone: state.update(light=tone),
lambda: S.light,
lambda tone: setattr(S, 'light', tone),
style,
)

Expand All @@ -181,20 +198,21 @@ def createHalfToneSelectorWidget() -> QWidget:
darkToneWidget = createSelectToneWidget(
'Dark tone',
[
{'name': 'Half', 'getColor': lambda: halfLight(state)},
{'name': 'Half', 'getColor': lambda: halfLight(S)},
{'name': 'FG', 'getColor': getFGColor},
{'name': 'BG', 'getColor': getBGColor},
],
state['dark'],
lambda tone: state.update(dark=tone),
lambda: S.dark,
lambda tone: setattr(S, 'dark', tone),
style,
)

# Select half tone count - Positive integer
countLabel = QLabel('Count:')
spinBox = QSpinBox()
spinBox.setRange(1, 10)
spinBox.setValue(5)
spinBox.setValue(S.count)
spinBox.valueChanged.connect(lambda i: setattr(S, 'count', i))
countWidget, countLayout = addLayout(
qlayout=QHBoxLayout,
childWidgets=[countLabel, spinBox])
Expand All @@ -203,7 +221,8 @@ def createHalfToneSelectorWidget() -> QWidget:
# Cosine - Toggle
cosineLabel = QLabel('Cosine:')
cosineCheckBox = QCheckBox()
cosineCheckBox.setChecked(True)
cosineCheckBox.setChecked(S.cos)
cosineCheckBox.toggled.connect(lambda checked: setattr(S, 'cos', checked))
cosineWidget, cosineLayout = addLayout(
qlayout=QHBoxLayout,
childWidgets=[cosineLabel, cosineCheckBox])
Expand All @@ -212,7 +231,8 @@ def createHalfToneSelectorWidget() -> QWidget:
# Exponent - Float
exponentLabel = QLabel('Exponent:')
doubleSpinBox = QDoubleSpinBox()
doubleSpinBox.setValue(0.45)
doubleSpinBox.setValue(S.exp)
doubleSpinBox.valueChanged.connect(lambda d: setattr(S, 'exp', d))
exponentWidget, exponentLayout = addLayout(
qlayout=QHBoxLayout,
childWidgets=[exponentLabel, doubleSpinBox])
Expand All @@ -221,23 +241,29 @@ def createHalfToneSelectorWidget() -> QWidget:
# Tones
# Create half tones - Button
# Color[0] Color[1] ...
toneWidget, toneLayout = addLayout(QVBoxLayout)
toneLayout.setContentsMargins(0, 0, 0, 0)
toneLayout.setSpacing(5)

def create():
intervals = computeIntervals(
getN=spinBox.value,
getExp=doubleSpinBox.value,
getCos=cosineCheckBox.isChecked)
colors = [interpolateColors(state['light'], state['dark'], i) for i in intervals]
layout.addWidget(createColorBarWidget(colors, style))
intervals = computeIntervals(S.count, S.cos, S.exp)
colors = [interpolateColors(S.light, S.dark, i) for i in intervals]
S.halfTones.append(colors)
createColorBarWidget(colors, style, toneLayout)

createButton = QPushButton('Create half tones')
createButton.clicked.connect(create)

for tones in S.halfTones:
createColorBarWidget(tones, style, toneLayout)

# Main layout
widget, layout = addLayout(
qlayout=QVBoxLayout,
childWidgets=[
lightToneWidget, darkToneWidget,
countWidget, cosineWidget, exponentWidget,
createButton,
createButton, toneWidget,
])
layout.setSpacing(5)
layout.setAlignment(Qt.AlignTop)
Expand Down
52 changes: 46 additions & 6 deletions half_tone_selector/lib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import json
import math
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable, Optional, List, Tuple
from krita import (
Krita,
Expand All @@ -11,6 +14,45 @@
QWidget,
)

@dataclass
class AppState:
light: QColor = field(default_factory=lambda: QColor(128, 128, 128))
dark: QColor = field(default_factory=lambda: QColor(64, 64, 64))
count: int = 5
cos: bool = True
exp: float = 0.45
halfTones: List[List[QColor]] = field(default_factory=list)

def to_dict(self) -> dict:
return {
'light': self.light.name(),
'dark': self.dark.name(),
'count': self.count,
'cos': self.cos,
'exp': self.exp,
'halfTones': [[tone.name() for tone in ht] for ht in self.halfTones]
}

def to_file(self, path: Path) -> None:
with path.open('w') as f:
json.dump(self.to_dict(), f)

@staticmethod
def from_dict(d: dict) -> 'AppState':
return AppState(
light=QColor(d['light']),
dark=QColor(d['dark']),
count=d['count'],
cos=d['cos'],
exp=d['exp'],
halfTones=[[QColor(tone) for tone in ht] for ht in d['halfTones']]
)

@staticmethod
def from_file(path: Path) -> 'AppState':
with path.open() as f:
return AppState.from_dict(json.load(f))

def getActiveView() -> View:
instance = Krita.instance()
window = instance.activeWindow() if instance else None
Expand All @@ -37,16 +79,14 @@ def setFGColor(color: QColor) -> None:
def getWindowColor() -> QColor:
return QApplication.palette().color(QPalette.Window)

def halfLight(state: dict) -> QColor:
rgba = state['light'].getRgb()
def halfLight(state: AppState) -> QColor:
rgba = state.light.getRgb()
halfRgb = [round(x / 2) for x in rgba[:3]]
return QColor(*halfRgb, rgba[3])

def computeIntervals(getN, getExp, getCos) -> List[float]:
n = getN()
exp = getExp()
def computeIntervals(n: int, cos: bool, exp: float) -> List[float]:
intervals = [i / (n+1) for i in range(n+2)]
if getCos():
if cos:
intervals = [math.cos(x * math.pi / 2) for x in intervals]
intervals = [round(x**exp, 2) for x in intervals]
return intervals
Expand Down

0 comments on commit e7114de

Please sign in to comment.