Merge pull request #1542 from pypeclub/feature/color_entity

This commit is contained in:
Milan Kolar 2021-05-24 08:29:34 +02:00 committed by GitHub
commit 20615721da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 2860 additions and 26 deletions

View file

@ -57,6 +57,7 @@ from .exceptions import (
SchemaError,
DefaultsNotDefined,
StudioDefaultsNotDefined,
BaseInvalidValueType,
InvalidValueType,
InvalidKeySymbols,
SchemaMissingFileInfo,
@ -96,7 +97,7 @@ from .input_entities import (
PathInput,
RawJsonEntity
)
from .color_entity import ColorEntity
from .enum_entity import (
BaseEnumEntity,
EnumEntity,
@ -115,6 +116,7 @@ from .anatomy_entities import AnatomyEntity
__all__ = (
"DefaultsNotDefined",
"StudioDefaultsNotDefined",
"BaseInvalidValueType",
"InvalidValueType",
"InvalidKeySymbols",
"SchemaMissingFileInfo",
@ -146,6 +148,8 @@ __all__ = (
"PathInput",
"RawJsonEntity",
"ColorEntity",
"BaseEnumEntity",
"EnumEntity",
"AppsEnumEntity",

View file

@ -9,6 +9,7 @@ from .lib import (
)
from .exceptions import (
BaseInvalidValueType,
InvalidValueType,
SchemeGroupHierarchyBug,
EntitySchemaError
@ -377,7 +378,7 @@ class BaseItemEntity(BaseEntity):
try:
new_value = self.convert_to_valid_type(value)
except InvalidValueType:
except BaseInvalidValueType:
new_value = NOT_SET
if new_value is not NOT_SET:

View file

@ -0,0 +1,54 @@
from .lib import STRING_TYPE
from .input_entities import InputEntity
from .exceptions import (
BaseInvalidValueType,
InvalidValueType
)
class ColorEntity(InputEntity):
schema_types = ["color"]
def _item_initalization(self):
self.valid_value_types = (list, )
self.value_on_not_set = [0, 0, 0, 255]
def convert_to_valid_type(self, value):
"""Conversion to valid type.
Complexity of entity requires to override BaseEntity implementation.
"""
# Convertion to valid value type `list`
if isinstance(value, (set, tuple)):
value = list(value)
# Skip other validations if is not `list`
if not isinstance(value, list):
raise InvalidValueType(
self.valid_value_types, type(value), self.path
)
# Allow list of len 3 (last aplha is set to max)
if len(value) == 3:
value.append(255)
if len(value) != 4:
reason = "Color entity expect 4 items in list got {}".format(
len(value)
)
raise BaseInvalidValueType(reason, self.path)
new_value = []
for item in value:
if not isinstance(item, int):
if isinstance(item, (STRING_TYPE, float)):
item = int(item)
is_valid = isinstance(item, int) and -1 < item < 256
if not is_valid:
reason = (
"Color entity expect 4 integers in range 0-255 got {}"
).format(value)
raise BaseInvalidValueType(reason, self.path)
new_value.append(item)
return new_value

View file

@ -15,20 +15,22 @@ class StudioDefaultsNotDefined(Exception):
super(StudioDefaultsNotDefined, self).__init__(msg)
class InvalidValueType(Exception):
msg_template = "{}"
class BaseInvalidValueType(Exception):
def __init__(self, reason, path):
msg = "Path \"{}\". {}".format(path, reason)
self.msg = msg
super(BaseInvalidValueType, self).__init__(msg)
class InvalidValueType(BaseInvalidValueType):
def __init__(self, valid_types, invalid_type, path):
msg = "Path \"{}\". ".format(path)
joined_types = ", ".join(
[str(valid_type) for valid_type in valid_types]
)
msg += "Got invalid type \"{}\". Expected: {}".format(
msg = "Got invalid type \"{}\". Expected: {}".format(
invalid_type, joined_types
)
self.msg = msg
super(InvalidValueType, self).__init__(msg)
super(InvalidValueType, self).__init__(msg, path)
class RequiredKeyModified(KeyError):

View file

@ -420,6 +420,18 @@
}
```
### color
- preimplemented entity to store and load color values
- entity store and expect list of 4 integers in range 0-255
- integers represents rgba [Red, Green, Blue, Alpha]
```
{
"type": "color",
"key": "bg_color",
"label": "Background Color"
}
```
## Noninteractive widgets
- have nothing to do with data

View file

@ -228,14 +228,9 @@
]
},
{
"type": "schema_template",
"name": "template_rgba_color",
"template_data": [
{
"label": "Fill Color",
"name": "fill_color"
}
]
"type": "color",
"label": "Fill Color",
"key": "fill_color"
},
{
"key": "line_thickness",
@ -245,14 +240,9 @@
"maximum": 1000
},
{
"type": "schema_template",
"name": "template_rgba_color",
"template_data": [
{
"label": "Line Color",
"name": "line_color"
}
]
"type": "color",
"label": "Line Color",
"key": "line_color"
}
]
}

View file

@ -4,6 +4,11 @@
"type": "dict",
"is_file": true,
"children": [
{
"key": "color",
"label": "Color input",
"type": "color"
},
{
"type": "dict",
"key": "schema_template_exaples",

View file

@ -21,6 +21,7 @@ from openpype.settings.entities import (
TextEntity,
PathInput,
RawJsonEntity,
ColorEntity,
DefaultsNotDefined,
StudioDefaultsNotDefined,
@ -44,7 +45,7 @@ from .item_widgets import (
PathWidget,
PathInputWidget
)
from .color_widget import ColorWidget
from avalon.vendor import qtawesome
@ -113,6 +114,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
elif isinstance(entity, RawJsonEntity):
return RawJsonWidget(*args)
elif isinstance(entity, ColorEntity):
return ColorWidget(*args)
elif isinstance(entity, BaseEnumEntity):
return EnumeratorWidget(*args)

View file

@ -0,0 +1,171 @@
from Qt import QtWidgets, QtCore, QtGui
from .item_widgets import InputWidget
from openpype.widgets.color_widgets import (
ColorPickerWidget,
draw_checkerboard_tile
)
class ColorWidget(InputWidget):
def _add_inputs_to_layout(self):
self.input_field = ColorViewer(self.content_widget)
self.setFocusProxy(self.input_field)
self.content_layout.addWidget(self.input_field, 1)
self.input_field.clicked.connect(self._on_click)
self._dialog = None
def _on_click(self):
if self._dialog:
self._dialog.open()
return
dialog = ColorDialog(self.input_field.color(), self)
self._dialog = dialog
dialog.open()
dialog.finished.connect(self._on_dialog_finish)
def _on_dialog_finish(self, *_args):
if not self._dialog:
return
color = self._dialog.result()
if color is not None:
self.input_field.set_color(color)
self._on_value_change()
self._dialog.deleteLater()
self._dialog = None
def _on_entity_change(self):
if self.entity.value != self.input_value():
self.set_entity_value()
def set_entity_value(self):
self.input_field.set_color(*self.entity.value)
def input_value(self):
color = self.input_field.color()
return [color.red(), color.green(), color.blue(), color.alpha()]
def _on_value_change(self):
if self.ignore_input_changes:
return
self.entity.set(self.input_value())
class ColorViewer(QtWidgets.QWidget):
clicked = QtCore.Signal()
def __init__(self, parent=None):
super(ColorViewer, self).__init__(parent)
self.setMinimumSize(10, 10)
self.actual_pen = QtGui.QPen()
self.actual_color = QtGui.QColor()
self._checkerboard = None
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.clicked.emit()
super(ColorViewer, self).mouseReleaseEvent(event)
def checkerboard(self):
if not self._checkerboard:
self._checkerboard = draw_checkerboard_tile(self.height() / 4)
return self._checkerboard
def color(self):
return self.actual_color
def set_color(self, *args):
# Create copy of entered color
self.actual_color = QtGui.QColor(*args)
# Repaint
self.update()
def set_alpha(self, alpha):
# Change alpha of current color
self.actual_color.setAlpha(alpha)
# Repaint
self.update()
def paintEvent(self, event):
rect = event.rect()
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
radius = rect.height() / 2
rounded_rect = QtGui.QPainterPath()
rounded_rect.addRoundedRect(QtCore.QRectF(rect), radius, radius)
painter.setClipPath(rounded_rect)
pen = QtGui.QPen(QtGui.QColor(255, 255, 255, 67))
pen.setWidth(1)
painter.setPen(pen)
painter.drawTiledPixmap(rect, self.checkerboard())
painter.fillRect(rect, self.actual_color)
painter.drawPath(rounded_rect)
painter.end()
class ColorDialog(QtWidgets.QDialog):
def __init__(self, color=None, parent=None):
super(ColorDialog, self).__init__(parent)
self.setWindowTitle("Color picker dialog")
picker_widget = ColorPickerWidget(color, self)
footer_widget = QtWidgets.QWidget(self)
ok_btn = QtWidgets.QPushButton("Ok", footer_widget)
cancel_btn = QtWidgets.QPushButton("Cancel", footer_widget)
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
footer_layout.setContentsMargins(0, 0, 0, 0)
footer_layout.addStretch(1)
footer_layout.addWidget(ok_btn)
footer_layout.addWidget(cancel_btn)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(picker_widget, 1)
layout.addWidget(footer_widget, 0)
ok_btn.clicked.connect(self.on_ok_clicked)
cancel_btn.clicked.connect(self.on_cancel_clicked)
self.picker_widget = picker_widget
self.ok_btn = ok_btn
self.cancel_btn = cancel_btn
self._result = None
def showEvent(self, event):
super(ColorDialog, self).showEvent(event)
btns_width = max(self.ok_btn.width(), self.cancel_btn.width())
self.ok_btn.setFixedWidth(btns_width)
self.cancel_btn.setFixedWidth(btns_width)
def on_ok_clicked(self):
self._result = self.picker_widget.color()
self.close()
def on_cancel_clicked(self):
self._result = None
self.close()
def result(self):
return self._result

View file

@ -0,0 +1,14 @@
from .color_picker_widget import (
ColorPickerWidget
)
from .color_view import (
draw_checkerboard_tile
)
__all__ = (
"ColorPickerWidget",
"draw_checkerboard_tile"
)

View file

@ -0,0 +1,639 @@
import re
from Qt import QtWidgets, QtCore, QtGui
from .color_view import draw_checkerboard_tile
slide_style = """
QSlider::groove:horizontal {
background: qlineargradient(
x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #000, stop: 1 #fff
);
height: 8px;
border-radius: 4px;
}
QSlider::handle:horizontal {
background: qlineargradient(
x1:0, y1:0, x2:1, y2:1, stop:0 #ddd, stop:1 #bbb
);
border: 1px solid #777;
width: 8px;
margin-top: -1px;
margin-bottom: -1px;
border-radius: 4px;
}
QSlider::handle:horizontal:hover {
background: qlineargradient(
x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ddd
);
border: 1px solid #444;ff
border-radius: 4px;
}"""
class AlphaSlider(QtWidgets.QSlider):
def __init__(self, *args, **kwargs):
super(AlphaSlider, self).__init__(*args, **kwargs)
self._mouse_clicked = False
self.setSingleStep(1)
self.setMinimum(0)
self.setMaximum(255)
self.setValue(255)
self._checkerboard = None
def checkerboard(self):
if self._checkerboard is None:
self._checkerboard = draw_checkerboard_tile(
3, QtGui.QColor(173, 173, 173), QtGui.QColor(27, 27, 27)
)
return self._checkerboard
def mousePressEvent(self, event):
self._mouse_clicked = True
if event.button() == QtCore.Qt.LeftButton:
self._set_value_to_pos(event.pos().x())
return event.accept()
return super(AlphaSlider, self).mousePressEvent(event)
def _set_value_to_pos(self, pos_x):
value = (
self.maximum() - self.minimum()
) * pos_x / self.width() + self.minimum()
self.setValue(value)
def mouseMoveEvent(self, event):
if self._mouse_clicked:
self._set_value_to_pos(event.pos().x())
super(AlphaSlider, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self._mouse_clicked = True
super(AlphaSlider, self).mouseReleaseEvent(event)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
opt = QtWidgets.QStyleOptionSlider()
self.initStyleOption(opt)
painter.fillRect(event.rect(), QtCore.Qt.transparent)
painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
rect = self.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderGroove,
self
)
final_height = 9
offset_top = 0
if rect.height() > final_height:
offset_top = int((rect.height() - final_height) / 2)
rect = QtCore.QRect(
rect.x(),
offset_top,
rect.width(),
final_height
)
pix_rect = QtCore.QRect(event.rect())
pix_rect.setX(rect.x())
pix_rect.setWidth(rect.width() - (2 * rect.x()))
pix = QtGui.QPixmap(pix_rect.width(), pix_rect.height())
pix_painter = QtGui.QPainter(pix)
pix_painter.drawTiledPixmap(pix_rect, self.checkerboard())
gradient = QtGui.QLinearGradient(rect.topLeft(), rect.bottomRight())
gradient.setColorAt(0, QtCore.Qt.transparent)
gradient.setColorAt(1, QtCore.Qt.white)
pix_painter.fillRect(pix_rect, gradient)
pix_painter.end()
brush = QtGui.QBrush(pix)
painter.save()
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(brush)
ratio = rect.height() / 2
painter.drawRoundedRect(rect, ratio, ratio)
painter.restore()
_handle_rect = self.style().subControlRect(
QtWidgets.QStyle.CC_Slider,
opt,
QtWidgets.QStyle.SC_SliderHandle,
self
)
handle_rect = QtCore.QRect(rect)
if offset_top > 1:
height = handle_rect.height()
handle_rect.setY(handle_rect.y() - 1)
handle_rect.setHeight(height + 2)
handle_rect.setX(_handle_rect.x())
handle_rect.setWidth(handle_rect.height())
painter.save()
gradient = QtGui.QRadialGradient()
radius = handle_rect.height() / 2
center_x = handle_rect.width() / 2 + handle_rect.x()
center_y = handle_rect.height()
gradient.setCenter(center_x, center_y)
gradient.setCenterRadius(radius)
gradient.setFocalPoint(center_x, center_y)
gradient.setColorAt(0.9, QtGui.QColor(127, 127, 127))
gradient.setColorAt(1, QtCore.Qt.transparent)
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(gradient)
painter.drawEllipse(handle_rect)
painter.restore()
class AlphaInputs(QtWidgets.QWidget):
alpha_changed = QtCore.Signal(int)
def __init__(self, parent=None):
super(AlphaInputs, self).__init__(parent)
self._block_changes = False
self.alpha_value = None
percent_input = QtWidgets.QDoubleSpinBox(self)
percent_input.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
percent_input.setMinimum(0)
percent_input.setMaximum(100)
percent_input.setDecimals(2)
int_input = QtWidgets.QSpinBox(self)
int_input.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
int_input.setMinimum(0)
int_input.setMaximum(255)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(int_input)
layout.addWidget(QtWidgets.QLabel("0-255"))
layout.addWidget(percent_input)
layout.addWidget(QtWidgets.QLabel("%"))
percent_input.valueChanged.connect(self._on_percent_change)
int_input.valueChanged.connect(self._on_int_change)
self.percent_input = percent_input
self.int_input = int_input
self.set_alpha(255)
def set_alpha(self, alpha):
if alpha == self.alpha_value:
return
self.alpha_value = alpha
self.update_alpha()
def _on_percent_change(self):
if self._block_changes:
return
self.alpha_value = int(self.percent_input.value() * 255 / 100)
self.alpha_changed.emit(self.alpha_value)
self.update_alpha()
def _on_int_change(self):
if self._block_changes:
return
self.alpha_value = self.int_input.value()
self.alpha_changed.emit(self.alpha_value)
self.update_alpha()
def update_alpha(self):
self._block_changes = True
if self.int_input.value() != self.alpha_value:
self.int_input.setValue(self.alpha_value)
percent = round(100 * self.alpha_value / 255, 2)
if self.percent_input.value() != percent:
self.percent_input.setValue(percent)
self._block_changes = False
class RGBInputs(QtWidgets.QWidget):
value_changed = QtCore.Signal()
def __init__(self, color, parent=None):
super(RGBInputs, self).__init__(parent)
self._block_changes = False
self.color = color
input_red = QtWidgets.QSpinBox(self)
input_green = QtWidgets.QSpinBox(self)
input_blue = QtWidgets.QSpinBox(self)
input_red.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_green.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_blue.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_red.setMinimum(0)
input_green.setMinimum(0)
input_blue.setMinimum(0)
input_red.setMaximum(255)
input_green.setMaximum(255)
input_blue.setMaximum(255)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(input_red, 1)
layout.addWidget(input_green, 1)
layout.addWidget(input_blue, 1)
input_red.valueChanged.connect(self._on_red_change)
input_green.valueChanged.connect(self._on_green_change)
input_blue.valueChanged.connect(self._on_blue_change)
self.input_red = input_red
self.input_green = input_green
self.input_blue = input_blue
def _on_red_change(self, value):
if self._block_changes:
return
self.color.setRed(value)
self._on_change()
def _on_green_change(self, value):
if self._block_changes:
return
self.color.setGreen(value)
self._on_change()
def _on_blue_change(self, value):
if self._block_changes:
return
self.color.setBlue(value)
self._on_change()
def _on_change(self):
self.value_changed.emit()
def color_changed(self):
if (
self.input_red.value() == self.color.red()
and self.input_green.value() == self.color.green()
and self.input_blue.value() == self.color.blue()
):
return
self._block_changes = True
self.input_red.setValue(self.color.red())
self.input_green.setValue(self.color.green())
self.input_blue.setValue(self.color.blue())
self._block_changes = False
class CMYKInputs(QtWidgets.QWidget):
value_changed = QtCore.Signal()
def __init__(self, color, parent=None):
super(CMYKInputs, self).__init__(parent)
self.color = color
self._block_changes = False
input_cyan = QtWidgets.QSpinBox(self)
input_magenta = QtWidgets.QSpinBox(self)
input_yellow = QtWidgets.QSpinBox(self)
input_black = QtWidgets.QSpinBox(self)
input_cyan.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_magenta.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_yellow.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_black.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_cyan.setMinimum(0)
input_magenta.setMinimum(0)
input_yellow.setMinimum(0)
input_black.setMinimum(0)
input_cyan.setMaximum(255)
input_magenta.setMaximum(255)
input_yellow.setMaximum(255)
input_black.setMaximum(255)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(input_cyan, 1)
layout.addWidget(input_magenta, 1)
layout.addWidget(input_yellow, 1)
layout.addWidget(input_black, 1)
input_cyan.valueChanged.connect(self._on_change)
input_magenta.valueChanged.connect(self._on_change)
input_yellow.valueChanged.connect(self._on_change)
input_black.valueChanged.connect(self._on_change)
self.input_cyan = input_cyan
self.input_magenta = input_magenta
self.input_yellow = input_yellow
self.input_black = input_black
def _on_change(self):
if self._block_changes:
return
self.color.setCmyk(
self.input_cyan.value(),
self.input_magenta.value(),
self.input_yellow.value(),
self.input_black.value()
)
self.value_changed.emit()
def color_changed(self):
if self._block_changes:
return
_cur_color = QtGui.QColor()
_cur_color.setCmyk(
self.input_cyan.value(),
self.input_magenta.value(),
self.input_yellow.value(),
self.input_black.value()
)
if (
_cur_color.red() == self.color.red()
and _cur_color.green() == self.color.green()
and _cur_color.blue() == self.color.blue()
):
return
c, m, y, k, _ = self.color.getCmyk()
self._block_changes = True
self.input_cyan.setValue(c)
self.input_magenta.setValue(m)
self.input_yellow.setValue(y)
self.input_black.setValue(k)
self._block_changes = False
class HEXInputs(QtWidgets.QWidget):
hex_regex = re.compile("^#(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$")
value_changed = QtCore.Signal()
def __init__(self, color, parent=None):
super(HEXInputs, self).__init__(parent)
self.color = color
input_field = QtWidgets.QLineEdit(self)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(input_field, 1)
input_field.textChanged.connect(self._on_change)
self.input_field = input_field
def _on_change(self):
if self._block_changes:
return
input_value = self.input_field.text()
# TODO what if does not match?
if self.hex_regex.match(input_value):
self.color.setNamedColor(input_value)
self.value_changed.emit()
def color_changed(self):
input_value = self.input_field.text()
if self.hex_regex.match(input_value):
_cur_color = QtGui.QColor()
_cur_color.setNamedColor(input_value)
if (
_cur_color.red() == self.color.red()
and _cur_color.green() == self.color.green()
and _cur_color.blue() == self.color.blue()
):
return
self._block_changes = True
self.input_field.setText(self.color.name())
self._block_changes = False
class HSVInputs(QtWidgets.QWidget):
value_changed = QtCore.Signal()
def __init__(self, color, parent=None):
super(HSVInputs, self).__init__(parent)
self._block_changes = False
self.color = color
input_hue = QtWidgets.QSpinBox(self)
input_sat = QtWidgets.QSpinBox(self)
input_val = QtWidgets.QSpinBox(self)
input_hue.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_sat.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_val.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_hue.setMinimum(0)
input_sat.setMinimum(0)
input_val.setMinimum(0)
input_hue.setMaximum(359)
input_sat.setMaximum(255)
input_val.setMaximum(255)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(input_hue, 1)
layout.addWidget(input_sat, 1)
layout.addWidget(input_val, 1)
input_hue.valueChanged.connect(self._on_change)
input_sat.valueChanged.connect(self._on_change)
input_val.valueChanged.connect(self._on_change)
self.input_hue = input_hue
self.input_sat = input_sat
self.input_val = input_val
def _on_change(self):
if self._block_changes:
return
self.color.setHsv(
self.input_hue.value(),
self.input_sat.value(),
self.input_val.value()
)
self.value_changed.emit()
def color_changed(self):
_cur_color = QtGui.QColor()
_cur_color.setHsv(
self.input_hue.value(),
self.input_sat.value(),
self.input_val.value()
)
if (
_cur_color.red() == self.color.red()
and _cur_color.green() == self.color.green()
and _cur_color.blue() == self.color.blue()
):
return
self._block_changes = True
h, s, v, _ = self.color.getHsv()
self.input_hue.setValue(h)
self.input_sat.setValue(s)
self.input_val.setValue(v)
self._block_changes = False
class HSLInputs(QtWidgets.QWidget):
value_changed = QtCore.Signal()
def __init__(self, color, parent=None):
super(HSLInputs, self).__init__(parent)
self._block_changes = False
self.color = color
input_hue = QtWidgets.QSpinBox(self)
input_sat = QtWidgets.QSpinBox(self)
input_light = QtWidgets.QSpinBox(self)
input_hue.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_sat.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_light.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
input_hue.setMinimum(0)
input_sat.setMinimum(0)
input_light.setMinimum(0)
input_hue.setMaximum(359)
input_sat.setMaximum(255)
input_light.setMaximum(255)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(input_hue, 1)
layout.addWidget(input_sat, 1)
layout.addWidget(input_light, 1)
input_hue.valueChanged.connect(self._on_change)
input_sat.valueChanged.connect(self._on_change)
input_light.valueChanged.connect(self._on_change)
self.input_hue = input_hue
self.input_sat = input_sat
self.input_light = input_light
def _on_change(self):
if self._block_changes:
return
self.color.setHsl(
self.input_hue.value(),
self.input_sat.value(),
self.input_light.value()
)
self.value_changed.emit()
def color_changed(self):
_cur_color = QtGui.QColor()
_cur_color.setHsl(
self.input_hue.value(),
self.input_sat.value(),
self.input_light.value()
)
if (
_cur_color.red() == self.color.red()
and _cur_color.green() == self.color.green()
and _cur_color.blue() == self.color.blue()
):
return
self._block_changes = True
h, s, l, _ = self.color.getHsl()
self.input_hue.setValue(h)
self.input_sat.setValue(s)
self.input_light.setValue(l)
self._block_changes = False
class ColorInputsWidget(QtWidgets.QWidget):
color_changed = QtCore.Signal(QtGui.QColor)
def __init__(self, parent=None, **kwargs):
super(ColorInputsWidget, self).__init__(parent)
color = QtGui.QColor()
input_fields = []
if kwargs.get("use_hex", True):
input_fields.append(HEXInputs(color, self))
if kwargs.get("use_rgb", True):
input_fields.append(RGBInputs(color, self))
if kwargs.get("use_hsl", True):
input_fields.append(HSLInputs(color, self))
if kwargs.get("use_hsv", True):
input_fields.append(HSVInputs(color, self))
if kwargs.get("use_cmyk", True):
input_fields.append(CMYKInputs(color, self))
inputs_widget = QtWidgets.QWidget(self)
inputs_layout = QtWidgets.QVBoxLayout(inputs_widget)
for input_field in input_fields:
inputs_layout.addWidget(input_field)
input_field.value_changed.connect(self._on_value_change)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(inputs_widget, 0)
spacer = QtWidgets.QWidget(self)
layout.addWidget(spacer, 1)
self.input_fields = input_fields
self.color = color
def set_color(self, color):
if (
color.red() == self.color.red()
and color.green() == self.color.green()
and color.blue() == self.color.blue()
):
return
self.color.setRed(color.red())
self.color.setGreen(color.green())
self.color.setBlue(color.blue())
self._on_value_change()
def _on_value_change(self):
for input_field in self.input_fields:
input_field.color_changed()
self.color_changed.emit(self.color)

View file

@ -0,0 +1,176 @@
import os
from Qt import QtWidgets, QtCore, QtGui
from .color_triangle import QtColorTriangle
from .color_view import ColorViewer
from .color_screen_pick import PickScreenColorWidget
from .color_inputs import (
AlphaSlider,
AlphaInputs,
HEXInputs,
RGBInputs,
HSLInputs,
HSVInputs
)
class ColorPickerWidget(QtWidgets.QWidget):
color_changed = QtCore.Signal(QtGui.QColor)
def __init__(self, color=None, parent=None):
super(ColorPickerWidget, self).__init__(parent)
# Color triangle
color_triangle = QtColorTriangle(self)
alpha_slider_proxy = QtWidgets.QWidget(self)
alpha_slider = AlphaSlider(QtCore.Qt.Horizontal, alpha_slider_proxy)
alpha_slider_layout = QtWidgets.QHBoxLayout(alpha_slider_proxy)
alpha_slider_layout.setContentsMargins(5, 5, 5, 5)
alpha_slider_layout.addWidget(alpha_slider, 1)
# Eye picked widget
pick_widget = PickScreenColorWidget()
pick_widget.setMaximumHeight(50)
# Color pick button
btn_pick_color = QtWidgets.QPushButton(self)
icon_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"eyedropper.png"
)
btn_pick_color.setIcon(QtGui.QIcon(icon_path))
btn_pick_color.setToolTip("Pick a color")
# Color preview
color_view = ColorViewer(self)
color_view.setMaximumHeight(50)
alpha_inputs = AlphaInputs(self)
color_inputs_color = QtGui.QColor()
col_inputs_by_label = [
("HEX", HEXInputs(color_inputs_color, self)),
("RGB", RGBInputs(color_inputs_color, self)),
("HSL", HSLInputs(color_inputs_color, self)),
("HSV", HSVInputs(color_inputs_color, self))
]
layout = QtWidgets.QGridLayout(self)
empty_col = 1
label_col = empty_col + 1
input_col = label_col + 1
empty_widget = QtWidgets.QWidget(self)
empty_widget.setFixedWidth(10)
layout.addWidget(empty_widget, 0, empty_col)
row = 0
layout.addWidget(btn_pick_color, row, label_col)
layout.addWidget(color_view, row, input_col)
row += 1
color_input_fields = []
for label, input_field in col_inputs_by_label:
layout.addWidget(QtWidgets.QLabel(label, self), row, label_col)
layout.addWidget(input_field, row, input_col)
input_field.value_changed.connect(
self._on_color_input_value_change
)
color_input_fields.append(input_field)
row += 1
layout.addWidget(color_triangle, 0, 0, row + 1, 1)
layout.setRowStretch(row, 1)
row += 1
layout.addWidget(alpha_slider_proxy, row, 0)
layout.addWidget(QtWidgets.QLabel("Alpha", self), row, label_col)
layout.addWidget(alpha_inputs, row, input_col)
row += 1
layout.setRowStretch(row, 1)
color_view.set_color(color_triangle.cur_color)
color_triangle.color_changed.connect(self.triangle_color_changed)
alpha_slider.valueChanged.connect(self._on_alpha_slider_change)
pick_widget.color_selected.connect(self.on_color_change)
alpha_inputs.alpha_changed.connect(self._on_alpha_inputs_changed)
btn_pick_color.released.connect(self.pick_color)
self.color_input_fields = color_input_fields
self.color_inputs_color = color_inputs_color
self.pick_widget = pick_widget
self.color_triangle = color_triangle
self.alpha_slider = alpha_slider
self.color_view = color_view
self.alpha_inputs = alpha_inputs
self.btn_pick_color = btn_pick_color
self._minimum_size_set = False
if color:
self.set_color(color)
self.alpha_changed(color.alpha())
def showEvent(self, event):
super(ColorPickerWidget, self).showEvent(event)
if self._minimum_size_set:
return
triangle_size = max(int(self.width() / 5 * 3), 180)
self.color_triangle.setMinimumWidth(triangle_size)
self.color_triangle.setMinimumHeight(triangle_size)
self._minimum_size_set = True
def color(self):
return self.color_view.color()
def set_color(self, color):
self.alpha_inputs.set_alpha(color.alpha())
self.on_color_change(color)
def pick_color(self):
self.pick_widget.pick_color()
def triangle_color_changed(self, color):
self.color_view.set_color(color)
if self.color_inputs_color != color:
self.color_inputs_color.setRgb(
color.red(), color.green(), color.blue()
)
for color_input in self.color_input_fields:
color_input.color_changed()
def on_color_change(self, color):
self.color_view.set_color(color)
self.color_triangle.set_color(color)
if self.color_inputs_color != color:
self.color_inputs_color.setRgb(
color.red(), color.green(), color.blue()
)
for color_input in self.color_input_fields:
color_input.color_changed()
def _on_color_input_value_change(self):
for input_field in self.color_input_fields:
input_field.color_changed()
self.on_color_change(QtGui.QColor(self.color_inputs_color))
def alpha_changed(self, value):
self.color_view.set_alpha(value)
if self.alpha_slider.value() != value:
self.alpha_slider.setValue(value)
if self.alpha_inputs.alpha_value != value:
self.alpha_inputs.set_alpha(value)
def _on_alpha_inputs_changed(self, value):
self.alpha_changed(value)
def _on_alpha_slider_change(self, value):
self.alpha_changed(value)

View file

@ -0,0 +1,248 @@
import Qt
from Qt import QtWidgets, QtCore, QtGui
class PickScreenColorWidget(QtWidgets.QWidget):
color_selected = QtCore.Signal(QtGui.QColor)
def __init__(self, parent=None):
super(PickScreenColorWidget, self).__init__(parent)
self.labels = []
self.magnification = 2
self._min_magnification = 1
self._max_magnification = 10
def add_magnification_delta(self, delta):
_delta = abs(delta / 1000)
if delta > 0:
self.magnification += _delta
else:
self.magnification -= _delta
if self.magnification > self._max_magnification:
self.magnification = self._max_magnification
elif self.magnification < self._min_magnification:
self.magnification = self._min_magnification
def pick_color(self):
if self.labels:
if self.labels[0].isVisible():
return
self.labels = []
for screen in QtWidgets.QApplication.screens():
label = PickLabel(self)
label.pick_color(screen)
label.color_selected.connect(self.on_color_select)
label.close_session.connect(self.end_pick_session)
self.labels.append(label)
def end_pick_session(self):
for label in self.labels:
label.close()
self.labels = []
def on_color_select(self, color):
self.color_selected.emit(color)
self.end_pick_session()
class PickLabel(QtWidgets.QLabel):
color_selected = QtCore.Signal(QtGui.QColor)
close_session = QtCore.Signal()
def __init__(self, pick_widget):
super(PickLabel, self).__init__()
self.setMouseTracking(True)
self.pick_widget = pick_widget
self.radius_pen = QtGui.QPen(QtGui.QColor(27, 27, 27), 2)
self.text_pen = QtGui.QPen(QtGui.QColor(127, 127, 127), 4)
self.text_bg = QtGui.QBrush(QtGui.QColor(27, 27, 27))
self._mouse_over = False
self.radius = 100
self.radius_ratio = 11
@property
def magnification(self):
return self.pick_widget.magnification
def pick_color(self, screen_obj):
self.show()
self.windowHandle().setScreen(screen_obj)
geo = screen_obj.geometry()
args = (
QtWidgets.QApplication.desktop().winId(),
geo.x(), geo.y(), geo.width(), geo.height()
)
if Qt.__binding__ in ("PyQt4", "PySide"):
pix = QtGui.QPixmap.grabWindow(*args)
else:
pix = screen_obj.grabWindow(*args)
if pix.width() > pix.height():
size = pix.height()
else:
size = pix.width()
self.radius = int(size / self.radius_ratio)
self.setPixmap(pix)
self.showFullScreen()
def wheelEvent(self, event):
y_delta = event.angleDelta().y()
self.pick_widget.add_magnification_delta(y_delta)
self.update()
def enterEvent(self, event):
self._mouse_over = True
super().enterEvent(event)
def leaveEvent(self, event):
self._mouse_over = False
super().leaveEvent(event)
self.update()
def mouseMoveEvent(self, event):
self.update()
def paintEvent(self, event):
super().paintEvent(event)
if not self._mouse_over:
return
mouse_pos_to_widet = self.mapFromGlobal(QtGui.QCursor.pos())
magnified_half_size = self.radius / self.magnification
magnified_size = magnified_half_size * 2
zoom_x_1 = mouse_pos_to_widet.x() - magnified_half_size
zoom_x_2 = mouse_pos_to_widet.x() + magnified_half_size
zoom_y_1 = mouse_pos_to_widet.y() - magnified_half_size
zoom_y_2 = mouse_pos_to_widet.y() + magnified_half_size
pix_width = magnified_size
pix_height = magnified_size
draw_pos_x = 0
draw_pos_y = 0
if zoom_x_1 < 0:
draw_pos_x = abs(zoom_x_1)
pix_width -= draw_pos_x
zoom_x_1 = 1
elif zoom_x_2 > self.pixmap().width():
pix_width -= zoom_x_2 - self.pixmap().width()
if zoom_y_1 < 0:
draw_pos_y = abs(zoom_y_1)
pix_height -= draw_pos_y
zoom_y_1 = 1
elif zoom_y_2 > self.pixmap().height():
pix_height -= zoom_y_2 - self.pixmap().height()
new_pix = QtGui.QPixmap(magnified_size, magnified_size)
new_pix.fill(QtCore.Qt.transparent)
new_pix_painter = QtGui.QPainter(new_pix)
new_pix_painter.drawPixmap(
QtCore.QRect(draw_pos_x, draw_pos_y, pix_width, pix_height),
self.pixmap().copy(zoom_x_1, zoom_y_1, pix_width, pix_height)
)
new_pix_painter.end()
painter = QtGui.QPainter(self)
ellipse_rect = QtCore.QRect(
mouse_pos_to_widet.x() - self.radius,
mouse_pos_to_widet.y() - self.radius,
self.radius * 2,
self.radius * 2
)
ellipse_rect_f = QtCore.QRectF(ellipse_rect)
path = QtGui.QPainterPath()
path.addEllipse(ellipse_rect_f)
painter.setClipPath(path)
new_pix_rect = QtCore.QRect(
mouse_pos_to_widet.x() - self.radius + 1,
mouse_pos_to_widet.y() - self.radius + 1,
new_pix.width() * self.magnification,
new_pix.height() * self.magnification
)
painter.drawPixmap(new_pix_rect, new_pix)
painter.setClipping(False)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setPen(self.radius_pen)
painter.drawEllipse(ellipse_rect_f)
image = self.pixmap().toImage()
if image.valid(mouse_pos_to_widet):
color = QtGui.QColor(image.pixel(mouse_pos_to_widet))
else:
color = QtGui.QColor()
color_text = "Red: {} - Green: {} - Blue: {}".format(
color.red(), color.green(), color.blue()
)
font = painter.font()
font.setPointSize(self.radius / 10)
painter.setFont(font)
text_rect_height = int(painter.fontMetrics().height() + 10)
text_rect = QtCore.QRect(
ellipse_rect.x(),
ellipse_rect.bottom(),
ellipse_rect.width(),
text_rect_height
)
if text_rect.bottom() > self.pixmap().height():
text_rect.moveBottomLeft(ellipse_rect.topLeft())
rect_radius = text_rect_height / 2
path = QtGui.QPainterPath()
path.addRoundedRect(
QtCore.QRectF(text_rect),
rect_radius,
rect_radius
)
painter.fillPath(path, self.text_bg)
painter.setPen(self.text_pen)
painter.drawText(
text_rect,
QtCore.Qt.AlignLeft | QtCore.Qt.AlignCenter,
color_text
)
color_rect_x = ellipse_rect.x() - text_rect_height
if color_rect_x < 0:
color_rect_x += (text_rect_height + ellipse_rect.width())
color_rect = QtCore.QRect(
color_rect_x,
ellipse_rect.y(),
text_rect_height,
ellipse_rect.height()
)
path = QtGui.QPainterPath()
path.addRoundedRect(
QtCore.QRectF(color_rect),
rect_radius,
rect_radius
)
painter.fillPath(path, color)
painter.drawRoundedRect(color_rect, rect_radius, rect_radius)
painter.end()
def mouseReleaseEvent(self, event):
color = QtGui.QColor(self.pixmap().toImage().pixel(event.pos()))
self.color_selected.emit(color)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.close_session.emit()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,83 @@
from Qt import QtWidgets, QtCore, QtGui
def draw_checkerboard_tile(piece_size=None, color_1=None, color_2=None):
if piece_size is None:
piece_size = 7
if color_1 is None:
color_1 = QtGui.QColor(188, 188, 188)
if color_2 is None:
color_2 = QtGui.QColor(90, 90, 90)
pix = QtGui.QPixmap(piece_size * 2, piece_size * 2)
pix_painter = QtGui.QPainter(pix)
rect = QtCore.QRect(
0, 0, piece_size, piece_size
)
pix_painter.fillRect(rect, color_1)
rect.moveTo(piece_size, piece_size)
pix_painter.fillRect(rect, color_1)
rect.moveTo(piece_size, 0)
pix_painter.fillRect(rect, color_2)
rect.moveTo(0, piece_size)
pix_painter.fillRect(rect, color_2)
pix_painter.end()
return pix
class ColorViewer(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ColorViewer, self).__init__(parent)
self.setMinimumSize(10, 10)
self.alpha = 255
self.actual_pen = QtGui.QPen()
self.actual_color = QtGui.QColor()
self._checkerboard = None
def checkerboard(self):
if not self._checkerboard:
self._checkerboard = draw_checkerboard_tile(4)
return self._checkerboard
def color(self):
return self.actual_color
def set_color(self, color):
if color == self.actual_color:
return
# Create copy of entered color
self.actual_color = QtGui.QColor(color)
# Set alpha by current alpha value
self.actual_color.setAlpha(self.alpha)
# Repaint
self.update()
def set_alpha(self, alpha):
if alpha == self.alpha:
return
# Change alpha of current color
self.actual_color.setAlpha(alpha)
# Store the value
self.alpha = alpha
# Repaint
self.update()
def paintEvent(self, event):
clip_rect = event.rect()
rect = clip_rect.adjusted(0, 0, -1, -1)
painter = QtGui.QPainter(self)
painter.setClipRect(clip_rect)
painter.drawTiledPixmap(rect, self.checkerboard())
painter.setBrush(self.actual_color)
pen = QtGui.QPen(QtGui.QColor(255, 255, 255, 67))
painter.setPen(pen)
painter.drawRect(rect)
painter.end()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB