From e9a38a5f53ff48912b9747f113dde56be643f91b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 17:23:13 +0200 Subject: [PATCH 01/32] created base of InvalidValueType (BaseInvalidValueType) to be able pass reason directly --- openpype/settings/entities/__init__.py | 2 ++ openpype/settings/entities/base_entity.py | 3 ++- openpype/settings/entities/exceptions.py | 16 +++++++++------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index 2c71b622ee..5d83a7cde4 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -57,6 +57,7 @@ from .exceptions import ( SchemaError, DefaultsNotDefined, StudioDefaultsNotDefined, + BaseInvalidValueType, InvalidValueType, InvalidKeySymbols, SchemaMissingFileInfo, @@ -115,6 +116,7 @@ from .anatomy_entities import AnatomyEntity __all__ = ( "DefaultsNotDefined", "StudioDefaultsNotDefined", + "BaseInvalidValueType", "InvalidValueType", "InvalidKeySymbols", "SchemaMissingFileInfo", diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 3e73fa8aa6..76150950b5 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -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: diff --git a/openpype/settings/entities/exceptions.py b/openpype/settings/entities/exceptions.py index 3649e63ab7..f352c94f20 100644 --- a/openpype/settings/entities/exceptions.py +++ b/openpype/settings/entities/exceptions.py @@ -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): From b420ea6971c01410ec49b81cde5356cec9f18532 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 17:24:53 +0200 Subject: [PATCH 02/32] base implementation of ColorEntity --- openpype/settings/entities/__init__.py | 4 +- openpype/settings/entities/color_entity.py | 53 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 openpype/settings/entities/color_entity.py diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index 5d83a7cde4..33881a6097 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -97,7 +97,7 @@ from .input_entities import ( PathInput, RawJsonEntity ) - +from .color_entity import ColorEntity from .enum_entity import ( BaseEnumEntity, EnumEntity, @@ -148,6 +148,8 @@ __all__ = ( "PathInput", "RawJsonEntity", + "ColorEntity", + "BaseEnumEntity", "EnumEntity", "AppsEnumEntity", diff --git a/openpype/settings/entities/color_entity.py b/openpype/settings/entities/color_entity.py new file mode 100644 index 0000000000..7d31ba42b9 --- /dev/null +++ b/openpype/settings/entities/color_entity.py @@ -0,0 +1,53 @@ +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 From d3673ae627b4e115cd6574183b33f921e2b84690 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 17:25:54 +0200 Subject: [PATCH 03/32] copy pasted PyQtColorTriangle project --- openpype/widgets/color_widgets/__init__.py | 6 + .../widgets/color_widgets/color_inputs.py | 514 ++++++ .../color_widgets/color_picker_widget.py | 115 ++ .../color_widgets/color_screen_pick.py | 248 +++ .../widgets/color_widgets/color_triangle.py | 1431 +++++++++++++++++ openpype/widgets/color_widgets/color_view.py | 78 + 6 files changed, 2392 insertions(+) create mode 100644 openpype/widgets/color_widgets/__init__.py create mode 100644 openpype/widgets/color_widgets/color_inputs.py create mode 100644 openpype/widgets/color_widgets/color_picker_widget.py create mode 100644 openpype/widgets/color_widgets/color_screen_pick.py create mode 100644 openpype/widgets/color_widgets/color_triangle.py create mode 100644 openpype/widgets/color_widgets/color_view.py diff --git a/openpype/widgets/color_widgets/__init__.py b/openpype/widgets/color_widgets/__init__.py new file mode 100644 index 0000000000..3423e26cf8 --- /dev/null +++ b/openpype/widgets/color_widgets/__init__.py @@ -0,0 +1,6 @@ +from .color_picker_widget import ColorPickerWidget + + +__all__ = ( + "ColorPickerWidget", +) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py new file mode 100644 index 0000000000..ddf8aebd4e --- /dev/null +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -0,0 +1,514 @@ +import re +from Qt import QtWidgets, QtCore, QtGui + + +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 AlphaInputs(QtWidgets.QGroupBox): + alpha_changed = QtCore.Signal(int) + + def __init__(self, parent=None): + super(AlphaInputs, self).__init__("Alpha", parent) + + self._block_changes = False + self.alpha_value = None + + # Opacity slider + alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) + alpha_slider.setSingleStep(1) + alpha_slider.setMinimum(0) + alpha_slider.setMaximum(255) + alpha_slider.setStyleSheet(slide_style) + alpha_slider.setValue(255) + + inputs_widget = QtWidgets.QWidget(self) + inputs_layout = QtWidgets.QHBoxLayout(inputs_widget) + inputs_layout.setContentsMargins(0, 0, 0, 0) + + percent_input = QtWidgets.QDoubleSpinBox(self) + percent_input.setMinimum(0) + percent_input.setMaximum(100) + percent_input.setDecimals(2) + + int_input = QtWidgets.QSpinBox(self) + int_input.setMinimum(0) + int_input.setMaximum(255) + + inputs_layout.addWidget(int_input) + inputs_layout.addWidget(QtWidgets.QLabel("0-255")) + inputs_layout.addWidget(percent_input) + inputs_layout.addWidget(QtWidgets.QLabel("%")) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(alpha_slider) + layout.addWidget(inputs_widget) + + alpha_slider.valueChanged.connect(self._on_slider_change) + percent_input.valueChanged.connect(self._on_percent_change) + int_input.valueChanged.connect(self._on_int_change) + + self.alpha_slider = alpha_slider + 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_slider_change(self): + if self._block_changes: + return + self.alpha_value = self.alpha_slider.value() + self.alpha_changed.emit(self.alpha_value) + 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.alpha_slider.value() != self.alpha_value: + self.alpha_slider.setValue(self.alpha_value) + + 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.QGroupBox): + value_changed = QtCore.Signal() + + def __init__(self, color, parent=None): + super(RGBInputs, self).__init__("RGB", 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.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.addWidget(input_red) + layout.addWidget(input_green) + layout.addWidget(input_blue) + + 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.QGroupBox): + value_changed = QtCore.Signal() + + def __init__(self, color, parent=None): + super(CMYKInputs, self).__init__("CMYK", 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.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.addWidget(input_cyan) + layout.addWidget(input_magenta) + layout.addWidget(input_yellow) + layout.addWidget(input_black) + + 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.QGroupBox): + 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__("HEX", parent) + self.color = color + + input_field = QtWidgets.QLineEdit() + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(input_field) + + 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.QGroupBox): + value_changed = QtCore.Signal() + + def __init__(self, color, parent=None): + super(HSVInputs, self).__init__("HSV", 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.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.addWidget(input_hue) + layout.addWidget(input_sat) + layout.addWidget(input_val) + + 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.QGroupBox): + value_changed = QtCore.Signal() + + def __init__(self, color, parent=None): + super(HSLInputs, self).__init__("HSL", 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.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.addWidget(input_hue) + layout.addWidget(input_sat) + layout.addWidget(input_light) + + 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) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py new file mode 100644 index 0000000000..d06af73cbf --- /dev/null +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -0,0 +1,115 @@ +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 ( + ColorInputsWidget, + AlphaInputs +) + + +class ColorPickerWidget(QtWidgets.QWidget): + color_changed = QtCore.Signal(QtGui.QColor) + + def __init__(self, color=None, parent=None): + super(ColorPickerWidget, self).__init__(parent) + + # Eye picked widget + pick_widget = PickScreenColorWidget() + + # Color utils + utils_widget = QtWidgets.QWidget(self) + utils_layout = QtWidgets.QVBoxLayout(utils_widget) + + bottom_utils_widget = QtWidgets.QWidget(utils_widget) + + # Color triangle + color_triangle = QtColorTriangle(utils_widget) + + # Color preview + color_view = ColorViewer(bottom_utils_widget) + color_view.setMaximumHeight(50) + + # Color pick button + btn_pick_color = QtWidgets.QPushButton( + "Pick a color", bottom_utils_widget + ) + + # Color inputs widget + color_inputs = ColorInputsWidget(self) + + # Alpha inputs + alpha_input_wrapper_widget = QtWidgets.QWidget(self) + alpha_input_wrapper_layout = QtWidgets.QVBoxLayout( + alpha_input_wrapper_widget + ) + + alpha_inputs = AlphaInputs(alpha_input_wrapper_widget) + alpha_input_wrapper_layout.addWidget(alpha_inputs) + alpha_input_wrapper_layout.addWidget(QtWidgets.QWidget(), 1) + + bottom_utils_layout = QtWidgets.QHBoxLayout(bottom_utils_widget) + bottom_utils_layout.setContentsMargins(0, 0, 0, 0) + bottom_utils_layout.addWidget(color_view, 1) + bottom_utils_layout.addWidget(btn_pick_color, 0) + + utils_layout.addWidget(bottom_utils_widget, 0) + utils_layout.addWidget(color_triangle, 1) + + layout = QtWidgets.QHBoxLayout(self) + layout.addWidget(utils_widget, 1) + layout.addWidget(color_inputs, 0) + layout.addWidget(alpha_input_wrapper_widget, 0) + + color_view.set_color(color_triangle.cur_color) + color_inputs.set_color(color_triangle.cur_color) + + color_triangle.color_changed.connect(self.triangle_color_changed) + pick_widget.color_selected.connect(self.on_color_change) + color_inputs.color_changed.connect(self.on_color_change) + alpha_inputs.alpha_changed.connect(self.alpha_changed) + btn_pick_color.released.connect(self.pick_color) + + self.pick_widget = pick_widget + self.utils_widget = utils_widget + self.bottom_utils_widget = bottom_utils_widget + + self.color_triangle = color_triangle + self.color_view = color_view + self.btn_pick_color = btn_pick_color + self.color_inputs = color_inputs + self.alpha_inputs = alpha_inputs + + if color: + self.set_color(color) + self.alpha_changed(color.alpha()) + + def showEvent(self, event): + super(ColorPickerWidget, self).showEvent(event) + triangle_width = int(( + self.utils_widget.height() - self.bottom_utils_widget.height() + ) / 5 * 4) + self.color_triangle.setMinimumWidth(triangle_width) + + 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) + self.color_inputs.set_color(color) + + def on_color_change(self, color): + self.color_view.set_color(color) + self.color_triangle.set_color(color) + self.color_inputs.set_color(color) + + def alpha_changed(self, alpha): + self.color_view.set_alpha(alpha) diff --git a/openpype/widgets/color_widgets/color_screen_pick.py b/openpype/widgets/color_widgets/color_screen_pick.py new file mode 100644 index 0000000000..87f50745eb --- /dev/null +++ b/openpype/widgets/color_widgets/color_screen_pick.py @@ -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() diff --git a/openpype/widgets/color_widgets/color_triangle.py b/openpype/widgets/color_widgets/color_triangle.py new file mode 100644 index 0000000000..d4db175d84 --- /dev/null +++ b/openpype/widgets/color_widgets/color_triangle.py @@ -0,0 +1,1431 @@ +from enum import Enum +from math import floor, sqrt, sin, cos, acos, pi as PI +from Qt import QtWidgets, QtCore, QtGui + +TWOPI = PI * 2 + + +class TriangleState(Enum): + IdleState = object() + SelectingHueState = object() + SelectingSatValueState = object() + + +class DoubleColor: + def __init__(self, r, g=None, b=None): + if g is None: + g = r.g + b = r.b + r = r.r + self.r = float(r) + self.g = float(g) + self.b = float(b) + + +class Vertex: + def __init__(self, color, point): + # Convert GlobalColor to QColor as globals don't have red, green, blue + if isinstance(color, QtCore.Qt.GlobalColor): + color = QtGui.QColor(color) + + # Convert QColor to DoubleColor + if isinstance(color, QtGui.QColor): + color = DoubleColor(color.red(), color.green(), color.blue()) + + self.color = color + self.point = point + + +class QtColorTriangle(QtWidgets.QWidget): + """The QtColorTriangle class provides a triangular color selection widget. + + This widget uses the HSV color model, and is therefore useful for + selecting colors by eye. + + The triangle in the center of the widget is used for selecting + saturation and value, and the surrounding circle is used for + selecting hue. + + Use set_color() and color() to set and get the current color. + """ + color_changed = QtCore.Signal(QtGui.QColor) + + # Thick of color wheel ratio where 1 is fully filled circle + inner_radius_ratio = 5.0 + # Ratio where hue selector on wheel is relative to `inner_radius_ratio` + # - middle of the wheel is twice `inner_radius_ratio` + selector_radius_ratio = inner_radius_ratio * 2 + # Size ratio of selectors on wheel and in triangle + ellipse_size_ratio = 10.0 + # Ration of selectors thickness + ellipse_thick_ratio = 50.0 + # Hue offset on color wheel (0 - 359) + # - red on top if set to "0" + hue_offset = 90 + + def __init__(self, parent=None): + super(QtColorTriangle, self).__init__(parent) + self.setSizePolicy( + QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.Minimum + ) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + self.angle_a = float() + self.angle_b = float() + self.angle_c = float() + + self.bg_image = QtGui.QImage( + self.sizeHint(), QtGui.QImage.Format_RGB32 + ) + self.cur_color = QtGui.QColor() + self.point_a = QtCore.QPointF() + self.point_b = QtCore.QPointF() + self.point_c = QtCore.QPointF() + self.point_d = QtCore.QPointF() + + self.cur_hue = int() + + self.pen_width = int() + self.ellipse_size = int() + self.outer_radius = int() + self.selector_pos = QtCore.QPointF() + + self.sel_mode = TriangleState.IdleState + + self._triangle_outline_pen = QtGui.QPen( + QtGui.QColor(40, 40, 40, 128), + 2 + ) + # Prepare hue numbers for color circle + _hue_circle_range = [] + for idx in range(11): + # Some Qt versions may require: + # hue = int(idx * 360.0) + percent_idx = idx * 0.1 + hue = int(360.0 - (percent_idx * 360.0)) + _hue_circle_range.append((percent_idx, hue)) + self._hue_circle_range = tuple(_hue_circle_range) + + color = QtGui.QColor() + color.setHsv(0, 255, 255) + self.set_color(color) + + def set_color(self, col): + if ( + col.red() == self.cur_color.red() + and col.green() == self.cur_color.green() + and col.blue() == self.cur_color.blue() + ): + return + + self.cur_color = col + + hue, *_ = self.cur_color.getHsv() + + # Never use an invalid hue to display colors + if hue != -1: + self.cur_hue = hue + + angle_with_offset = (360 - self.cur_hue - self.hue_offset) % 360 + self.angle_a = (angle_with_offset * TWOPI) / 360.0 + self.angle_a += PI / 2.0 + if self.angle_a > TWOPI: + self.angle_a -= TWOPI + + self.angle_b = self.angle_a + TWOPI / 3 + self.angle_c = self.angle_b + TWOPI / 3 + + if self.angle_b > TWOPI: + self.angle_b -= TWOPI + if self.angle_c > TWOPI: + self.angle_c -= TWOPI + + cx = float(self.contentsRect().center().x()) + cy = float(self.contentsRect().center().y()) + inner_radius = ( + self.outer_radius + - (self.outer_radius / self.inner_radius_ratio) + ) + selector_radius = ( + self.outer_radius + - (self.outer_radius / self.selector_radius_ratio) + ) + self.point_a = QtCore.QPointF( + cx + (cos(self.angle_a) * inner_radius), + cy - (sin(self.angle_a) * inner_radius) + ) + self.point_b = QtCore.QPointF( + cx + (cos(self.angle_b) * inner_radius), + cy - (sin(self.angle_b) * inner_radius) + ) + self.point_c = QtCore.QPointF( + cx + (cos(self.angle_c) * inner_radius), + cy - (sin(self.angle_c) * inner_radius) + ) + self.point_d = QtCore.QPointF( + cx + (cos(self.angle_a) * selector_radius), + cy - (sin(self.angle_a) * selector_radius) + ) + + self.selector_pos = self._point_from_color(self.cur_color) + self.update() + + self.color_changed.emit(self.cur_color) + + def heightForWidth(self, width): + return width + + def polish(self): + size_w = self.contentsRect().width() + size_h = self.contentsRect().height() + if size_w < size_h: + size = size_w + else: + size = size_h + + self.outer_radius = (size - 1) / 2 + + self.pen_width = int( + floor(self.outer_radius / self.ellipse_thick_ratio) + ) + self.ellipse_size = int( + floor(self.outer_radius / self.ellipse_size_ratio) + ) + + cx = float(self.contentsRect().center().x()) + cy = float(self.contentsRect().center().y()) + + inner_radius = ( + self.outer_radius + - (self.outer_radius / self.inner_radius_ratio) + ) + selector_radius = ( + self.outer_radius + - (self.outer_radius / self.selector_radius_ratio) + ) + self.point_a = QtCore.QPointF( + cx + (cos(self.angle_a) * inner_radius), + cy - (sin(self.angle_a) * inner_radius) + ) + self.point_b = QtCore.QPointF( + cx + (cos(self.angle_b) * inner_radius), + cy - (sin(self.angle_b) * inner_radius) + ) + self.point_c = QtCore.QPointF( + cx + (cos(self.angle_c) * inner_radius), + cy - (sin(self.angle_c) * inner_radius) + ) + self.point_d = QtCore.QPointF( + cx + (cos(self.angle_a) * selector_radius), + cy - (sin(self.angle_a) * selector_radius) + ) + + self.selector_pos = self._point_from_color(self.cur_color) + + self.update() + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + if event.rect().intersects(self.contentsRect()): + event_region = event.region() + if hasattr(event_region, "intersect"): + clip_region = event_region.intersect(self.contentsRect()) + else: + clip_region = event_region.intersected( + self.contentsRect() + ) + painter.setClipRegion(clip_region) + + self.paint_bg() + + # Blit the static generated background with the hue gradient onto + # the double buffer. + buf = QtGui.QImage(self.bg_image.copy()) + + # Draw the trigon + # Find the color with only the hue, and max value and saturation + hue_color = QtGui.QColor() + hue_color.setHsv(self.cur_hue, 255, 255) + + # Draw the triangle + self.drawTrigon( + buf, self.point_a, self.point_b, self.point_c, hue_color + ) + + # Slow step: convert the image to a pixmap + pix = QtGui.QPixmap.fromImage(buf) + pix_painter = QtGui.QPainter(pix) + pix_painter.setRenderHint(QtGui.QPainter.Antialiasing) + + # Draw an outline of the triangle + pix_painter.setPen(self._triangle_outline_pen) + pix_painter.drawLine(self.point_a, self.point_b) + pix_painter.drawLine(self.point_b, self.point_c) + pix_painter.drawLine(self.point_c, self.point_a) + + # Draw the color wheel selector + pix_painter.setPen(QtGui.QPen(QtCore.Qt.white, self.pen_width)) + pix_painter.drawEllipse( + int(self.point_d.x() - self.ellipse_size / 2.0), + int(self.point_d.y() - self.ellipse_size / 2.0), + self.ellipse_size, self.ellipse_size + ) + + # Draw the triangle selector + pix_painter.setBrush(self.cur_color) + pix_painter.drawEllipse( + QtCore.QRectF( + self.selector_pos.x() - self.ellipse_size / 2.0, + self.selector_pos.y() - self.ellipse_size / 2.0, + self.ellipse_size + 0.5, + self.ellipse_size + 0.5 + ) + ) + + pix_painter.end() + # Blit + painter.drawPixmap(self.contentsRect().topLeft(), pix) + painter.end() + + def mouseMoveEvent(self, event): + if (event.buttons() & QtCore.Qt.LeftButton) == 0: + return + + depos = QtCore.QPointF( + event.pos().x(), + event.pos().y() + ) + new_color = False + + if self.sel_mode is TriangleState.SelectingHueState: + self.angle_a = self._angle_at(depos, self.contentsRect()) + self.angle_b = self.angle_a + (TWOPI / 3.0) + self.angle_c = self.angle_b + (TWOPI / 3.0) + if self.angle_b > TWOPI: + self.angle_b -= TWOPI + if self.angle_c > TWOPI: + self.angle_c -= TWOPI + + am = self.angle_a - (PI / 2) + if am < 0: + am += TWOPI + self.cur_hue = ( + 360 - int((am * 360.0) / TWOPI) - self.hue_offset + ) % 360 + hue, sat, val, _ = self.cur_color.getHsv() + + if self.cur_hue != hue: + new_color = True + self.cur_color.setHsv(self.cur_hue, sat, val) + + cx = float(self.contentsRect().center().x()) + cy = float(self.contentsRect().center().y()) + inner_radius = ( + self.outer_radius + - (self.outer_radius / self.inner_radius_ratio) + ) + selector_radius = ( + self.outer_radius + - (self.outer_radius / self.selector_radius_ratio) + ) + self.point_a = QtCore.QPointF( + cx + (cos(self.angle_a) * inner_radius), + cy - (sin(self.angle_a) * inner_radius) + ) + self.point_b = QtCore.QPointF( + cx + (cos(self.angle_b) * inner_radius), + cy - (sin(self.angle_b) * inner_radius) + ) + self.point_c = QtCore.QPointF( + cx + (cos(self.angle_c) * inner_radius), + cy - (sin(self.angle_c) * inner_radius) + ) + self.point_d = QtCore.QPointF( + cx + (cos(self.angle_a) * selector_radius), + cy - (sin(self.angle_a) * selector_radius) + ) + + self.selector_pos = self._point_from_color(self.cur_color) + else: + aa = Vertex(QtCore.Qt.transparent, self.point_a) + bb = Vertex(QtCore.Qt.transparent, self.point_b) + cc = Vertex(QtCore.Qt.transparent, self.point_c) + + self.selector_pos = self._move_point_to_triangle( + depos.x(), depos.y(), aa, bb, cc + ) + col = self._color_from_point(self.selector_pos) + if col != self.cur_color: + # Ensure that hue does not change when selecting + # saturation and value. + _, sat, val, _ = col.getHsv() + self.cur_color.setHsv(self.cur_hue, sat, val) + new_color = True + + if new_color: + self.color_changed.emit(self.cur_color) + + self.update() + + def mousePressEvent(self, event): + # Only respond to the left mouse button. + if event.button() != QtCore.Qt.LeftButton: + return + + depos = QtCore.QPointF( + event.pos().x(), + event.pos().y() + ) + rad = self._radius_at(depos, self.contentsRect()) + new_color = False + + # As in mouseMoveEvent, either find the a, b, c angles or the + # radian position of the selector, then order an update. + inner_radius = ( + self.outer_radius - (self.outer_radius / self.inner_radius_ratio) + ) + if rad > inner_radius: + self.sel_mode = TriangleState.SelectingHueState + + self.angle_a = self._angle_at(depos, self.contentsRect()) + self.angle_b = self.angle_a + TWOPI / 3.0 + self.angle_c = self.angle_b + TWOPI / 3.0 + if self.angle_b > TWOPI: + self.angle_b -= TWOPI + if self.angle_c > TWOPI: + self.angle_c -= TWOPI + + am = self.angle_a - PI / 2 + if am < 0: + am += TWOPI + + self.cur_hue = ( + 360 - int((am * 360.0) / TWOPI) - self.hue_offset + ) % 360 + hue, sat, val, _ = self.cur_color.getHsv() + + if hue != self.cur_hue: + new_color = True + self.cur_color.setHsv(self.cur_hue, sat, val) + + cx = float(self.contentsRect().center().x()) + cy = float(self.contentsRect().center().y()) + + self.point_a = QtCore.QPointF( + cx + (cos(self.angle_a) * inner_radius), + cy - (sin(self.angle_a) * inner_radius) + ) + self.point_b = QtCore.QPointF( + cx + (cos(self.angle_b) * inner_radius), + cy - (sin(self.angle_b) * inner_radius) + ) + self.point_c = QtCore.QPointF( + cx + (cos(self.angle_c) * inner_radius), + cy - (sin(self.angle_c) * inner_radius) + ) + + selector_radius = ( + self.outer_radius + - (self.outer_radius / self.selector_radius_ratio) + ) + self.point_d = QtCore.QPointF( + cx + (cos(self.angle_a) * selector_radius), + cy - (sin(self.angle_a) * selector_radius) + ) + + self.selector_pos = self._point_from_color(self.cur_color) + self.color_changed.emit(self.cur_color) + else: + self.sel_mode = TriangleState.SelectingSatValueState + + aa = Vertex(QtCore.Qt.transparent, self.point_a) + bb = Vertex(QtCore.Qt.transparent, self.point_b) + cc = Vertex(QtCore.Qt.transparent, self.point_c) + + self.selector_pos = self._move_point_to_triangle( + depos.x(), depos.y(), aa, bb, cc + ) + col = self._color_from_point(self.selector_pos) + if col != self.cur_color: + self.cur_color = col + new_color = True + + if new_color: + self.color_changed.emit(self.cur_color) + + self.update() + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.sel_mode = TriangleState.IdleState + + def keyPressEvent(self, event): + key = event.key() + if key == QtCore.Qt.Key_Left: + self.cur_hue -= 1 + if self.cur_hue < 0: + self.cur_hue += 360 + _, sat, val, _ = self.cur_color.getHsv() + + tmp = QtGui.QColor() + tmp.setHsv(self.cur_hue, sat, val) + self.set_color(tmp) + + elif key == QtCore.Qt.Key_Right: + self.cur_hue += 1 + if (self.cur_hue > 359): + self.cur_hue -= 360 + _, sat, val, _ = self.cur_color.getHsv() + tmp = QtGui.QColor() + tmp.setHsv(self.cur_hue, sat, val) + self.set_color(tmp) + + elif key == QtCore.Qt.Key_Up: + _, sat, val, _ = self.cur_color.getHsv() + if event.modifiers() & QtCore.Qt.ShiftModifier: + if sat > 5: + sat -= 5 + else: + sat = 0 + else: + if val > 5: + val -= 5 + else: + val = 0 + + tmp = QtGui.QColor() + tmp.setHsv(self.cur_hue, sat, val) + self.set_color(tmp) + + elif key == QtCore.Qt.Key_Down: + _, sat, val, _ = self.cur_color.getHsv() + if event.modifiers() & QtCore.Qt.ShiftModifier: + if sat < 250: + sat += 5 + else: + sat = 255 + else: + if val < 250: + val += 5 + else: + val = 255 + + tmp = QtGui.QColor() + tmp.setHsv(self.cur_hue, sat, val) + self.set_color(tmp) + + def resizeEvent(self, _event): + size_w = self.contentsRect().width() + size_h = self.contentsRect().height() + if size_w < size_h: + size = size_w + else: + size = size_h + + self.outer_radius = (size - 1) / 2 + + self.pen_width = int( + floor(self.outer_radius / self.ellipse_thick_ratio) + ) + self.ellipse_size = int( + floor(self.outer_radius / self.ellipse_size_ratio) + ) + + cx = float(self.contentsRect().center().x()) + cy = float(self.contentsRect().center().y()) + inner_radius = ( + self.outer_radius + - (self.outer_radius / self.inner_radius_ratio) + ) + selector_radius = ( + self.outer_radius + - (self.outer_radius / self.selector_radius_ratio) + ) + self.point_a = QtCore.QPointF( + cx + (cos(self.angle_a) * inner_radius), + cy - (sin(self.angle_a) * inner_radius) + ) + self.point_b = QtCore.QPointF( + cx + (cos(self.angle_b) * inner_radius), + cy - (sin(self.angle_b) * inner_radius) + ) + self.point_c = QtCore.QPointF( + cx + (cos(self.angle_c) * inner_radius), + cy - (sin(self.angle_c) * inner_radius) + ) + self.point_d = QtCore.QPointF( + cx + (cos(self.angle_a) * selector_radius), + cy - (sin(self.angle_a) * selector_radius) + ) + + # Find the current position of the selector + self.selector_pos = self._point_from_color(self.cur_color) + + self.update() + + def drawTrigon(self, buf, pa, pb, pc, color): + # Create three Vertex objects. A Vertex contains a double-point + # coordinate and a color. + # pa is the tip of the arrow + # pb is the black corner + # pc is the white corner + p1 = Vertex(color, pa) + p2 = Vertex(QtCore.Qt.black, pb) + p3 = Vertex(QtCore.Qt.white, pc) + + # Sort. Make p1 above p2, which is above p3 (using y coordinate). + # Bubble sorting is fastest here. + if p1.point.y() > p2.point.y(): + p1, p2 = p2, p1 + if p1.point.y() > p3.point.y(): + p1, p3 = p3, p1 + if p2.point.y() > p3.point.y(): + p2, p3 = p3, p2 + + # All the three y deltas are >= 0 + p1p2ydist = float(p2.point.y() - p1.point.y()) + p1p3ydist = float(p3.point.y() - p1.point.y()) + p2p3ydist = float(p3.point.y() - p2.point.y()) + p1p2xdist = float(p2.point.x() - p1.point.x()) + p1p3xdist = float(p3.point.x() - p1.point.x()) + p2p3xdist = float(p3.point.x() - p2.point.x()) + + # The first x delta decides wether we have a lefty or a righty + # trigon. + lefty = p1p2xdist < 0 + + # Left and right colors and X values. The key in this map is the + # y values. Our goal is to fill these structures with all the + # information needed to do a single pass top-to-bottom, + # left-to-right drawing of the trigon. + leftColors = {} + rightColors = {} + leftX = {} + rightX = {} + + # Scan longy - find all left and right colors and X-values for + # the tallest edge (p1-p3). + # Initialize with known values + x = p1.point.x() + source = p1.color + dest = p3.color + r = source.r + g = source.g + b = source.b + y1 = int(floor(p1.point.y())) + y2 = int(floor(p3.point.y())) + + # Find slopes (notice that if the y dists are 0, we don't care + # about the slopes) + xdelta = 0.0 + rdelta = 0.0 + gdelta = 0.0 + bdelta = 0.0 + if p1p3ydist != 0.0: + xdelta = p1p3xdist / p1p3ydist + rdelta = (dest.r - r) / p1p3ydist + gdelta = (dest.g - g) / p1p3ydist + bdelta = (dest.b - b) / p1p3ydist + + # Calculate gradients using linear approximation + for y in range(y1, y2): + if lefty: + rightColors[y] = DoubleColor(r, g, b) + rightX[y] = x + else: + leftColors[y] = DoubleColor(r, g, b) + leftX[y] = x + + r += rdelta + g += gdelta + b += bdelta + x += xdelta + + # Scan top shorty - find all left and right colors and x-values + # for the topmost of the two not-tallest short edges. + x = p1.point.x() + source = p1.color + dest = p2.color + r = source.r + g = source.g + b = source.b + y1 = int(floor(p1.point.y())) + y2 = int(floor(p2.point.y())) + + # Find slopes (notice that if the y dists are 0, we don't care + # about the slopes) + xdelta = 0.0 + rdelta = 0.0 + gdelta = 0.0 + bdelta = 0.0 + if p1p2ydist != 0.0: + xdelta = p1p2xdist / p1p2ydist + rdelta = (dest.r - r) / p1p2ydist + gdelta = (dest.g - g) / p1p2ydist + bdelta = (dest.b - b) / p1p2ydist + + # Calculate gradients using linear approximation + for y in range(y1, y2): + if lefty: + leftColors[y] = DoubleColor(r, g, b) + leftX[y] = x + else: + rightColors[y] = DoubleColor(r, g, b) + rightX[y] = x + + r += rdelta + g += gdelta + b += bdelta + x += xdelta + + # Scan bottom shorty - find all left and right colors and + # x-values for the bottommost of the two not-tallest short edges. + x = p2.point.x() + source = p2.color + dest = p3.color + r = source.r + g = source.g + b = source.b + y1 = int(floor(p2.point.y())) + y2 = int(floor(p3.point.y())) + + # Find slopes (notice that if the y dists are 0, we don't care + # about the slopes) + xdelta = 0.0 + rdelta = 0.0 + gdelta = 0.0 + bdelta = 0.0 + if p2p3ydist != 0.0: + xdelta = p2p3xdist / p2p3ydist + rdelta = (dest.r - r) / p2p3ydist + gdelta = (dest.g - g) / p2p3ydist + bdelta = (dest.b - b) / p2p3ydist + + # Calculate gradients using linear approximation + for y in range(y1, y2): + if lefty: + leftColors[y] = DoubleColor(r, g, b) + leftX[y] = x + else: + rightColors[y] = DoubleColor(r, g, b) + rightX[y] = x + + r += rdelta + g += gdelta + b += bdelta + x += xdelta + + # Inner loop. For each y in the left map of x-values, draw one + # line from left to right. + p3yfloor = int(floor(p3.point.y())) + p1yfloor = int(floor(p1.point.y())) + for y in range(p1yfloor, p3yfloor): + lx = leftX[y] + rx = rightX[y] + + lxi = int(floor(lx)) + rxi = int(floor(rx)) + rc = rightColors[y] + lc = leftColors[y] + + # if the xdist is 0, don't draw anything. + xdist = rx - lx + if xdist != 0.0: + r = lc.r + g = lc.g + b = lc.b + rdelta = (rc.r - r) / xdist + gdelta = (rc.g - g) / xdist + bdelta = (rc.b - b) / xdist + + # Inner loop 2. Draws the line from left to right. + for x in range(lxi, rxi + 1): + buf.setPixel(x, y, QtGui.qRgb(int(r), int(g), int(b))) + r += rdelta + g += gdelta + b += bdelta + + def _radius_at(self, pos, rect): + mousexdist = pos.x() - float(rect.center().x()) + mouseydist = pos.y() - float(rect.center().y()) + return sqrt(mousexdist ** 2 + mouseydist ** 2) + + def _angle_at(self, pos, rect): + mousexdist = pos.x() - float(rect.center().x()) + mouseydist = pos.y() - float(rect.center().y()) + mouserad = sqrt(mousexdist ** 2 + mouseydist ** 2) + if mouserad == 0.0: + return 0.0 + + angle = acos(mousexdist / mouserad) + if mouseydist >= 0: + angle = TWOPI - angle + + return angle + + def _point_from_color(self, col): + # Simplifications for the corner cases. + if col == QtCore.Qt.black: + return self.point_b + elif col == QtCore.Qt.white: + return self.point_c + + # Find the x and y slopes + ab_deltax = self.point_b.x() - self.point_a.x() + ab_deltay = self.point_b.y() - self.point_a.y() + bc_deltax = self.point_c.x() - self.point_b.x() + bc_deltay = self.point_c.y() - self.point_b.y() + ac_deltax = self.point_c.x() - self.point_a.x() + ac_deltay = self.point_c.y() - self.point_a.y() + + # Extract the h,s,v values of col. + _, sat, val, _ = col.getHsv() + + # Find the line that passes through the triangle where the value + # is equal to our color's value. + p1 = self.point_a.x() + (ab_deltax * float(255 - val)) / 255.0 + q1 = self.point_a.y() + (ab_deltay * float(255 - val)) / 255.0 + p2 = self.point_b.x() + (bc_deltax * float(val)) / 255.0 + q2 = self.point_b.y() + (bc_deltay * float(val)) / 255.0 + + # Find the line that passes through the triangle where the + # saturation is equal to our color's value. + p3 = self.point_a.x() + (ac_deltax * float(255 - sat)) / 255.0 + q3 = self.point_a.y() + (ac_deltay * float(255 - sat)) / 255.0 + p4 = self.point_b.x() + q4 = self.point_b.y() + + # Find the intersection between these lines. + if p1 != p2: + a = (q2 - q1) / (p2 - p1) + c = (q4 - q3) / (p4 - p3) + b = q1 - a * p1 + d = q3 - c * p3 + + x = (d - b) / (a - c) + y = a * x + b + else: + x = p1 + p4_p3 = p4 - p3 + if p4_p3 == 0: + y = 0 + else: + y = q3 + (x - p3) * (q4 - q3) / p4_p3 + + return QtCore.QPointF(x, y) + + def _color_from_point(self, p): + # Find the outer radius of the hue gradient. + size_w = self.contentsRect().width() + size_h = self.contentsRect().height() + if size_w < size_h: + size = size_w + else: + size = size_h + outer_radius = (size - 1) / 2 + + # Find the center coordinates + cx = float(self.contentsRect().center().x()) + cy = float(self.contentsRect().center().y()) + + # Find the a, b and c from their angles, the center of the rect + # and the radius of the hue gradient donut. + inner_radius = outer_radius - (outer_radius / self.inner_radius_ratio) + pa = QtCore.QPointF( + cx + (cos(self.angle_a) * inner_radius), + cy - (sin(self.angle_a) * inner_radius) + ) + pb = QtCore.QPointF( + cx + (cos(self.angle_b) * inner_radius), + cy - (sin(self.angle_b) * inner_radius) + ) + pc = QtCore.QPointF( + cx + (cos(self.angle_c) * inner_radius), + cy - (sin(self.angle_c) * inner_radius) + ) + + # Find the hue value from the angle of the 'a' point. + angle = self.angle_a - PI / 2.0 + if angle < 0: + angle += TWOPI + hue = ( + 360 + - int(floor((360.0 * angle) / TWOPI)) + - self.hue_offset + ) % 360 + + # Create the color of the 'a' corner point. We know that b is + # black and c is white. + color = QtGui.QColor() + color.setHsv(hue, 255, 255) + + # See also drawTrigon(), which basically does exactly the same to + # determine all colors in the trigon. + p1 = Vertex(color, pa) + p2 = Vertex(QtCore.Qt.black, pb) + p3 = Vertex(QtCore.Qt.white, pc) + + # Make sure p1 is above p2, which is above p3. + if p1.point.y() > p2.point.y(): + p1, p2 = p2, p1 + if p1.point.y() > p3.point.y(): + p1, p3 = p3, p1 + if p2.point.y() > p3.point.y(): + p2, p3 = p3, p2 + + # Find the slopes of all edges in the trigon. All the three y + # deltas here are positive because of the above sorting. + p1p2ydist = p2.point.y() - p1.point.y() + p1p3ydist = p3.point.y() - p1.point.y() + p2p3ydist = p3.point.y() - p2.point.y() + p1p2xdist = p2.point.x() - p1.point.x() + p1p3xdist = p3.point.x() - p1.point.x() + p2p3xdist = p3.point.x() - p2.point.x() + + # The first x delta decides wether we have a lefty or a righty + # trigon. A lefty trigon has its tallest edge on the right hand + # side of the trigon. The righty trigon has it on its left side. + # This property determines wether the left or the right set of x + # coordinates will be continuous. + lefty = p1p2xdist < 0 + + # Find whether the selector's y is in the first or second shorty, + # counting from the top and downwards. This is used to find the + # color at the selector point. + firstshorty = (p.y() >= p1.point.y() and p.y() < p2.point.y()) + + # From the y value of the selector's position, find the left and + # right x values. + if lefty: + if firstshorty: + if (floor(p1p2ydist) != 0.0): + leftx = ( + p1.point.x() + + ((p1p2xdist * (p.y() - p1.point.y())) / p1p2ydist) + ) + else: + leftx = min(p1.point.x(), p2.point.x()) + + else: + if (floor(p2p3ydist) != 0.0): + leftx = ( + p2.point.x() + + (p2p3xdist * (p.y() - p2.point.y())) / p2p3ydist + ) + else: + leftx = min(p2.point.x(), p3.point.x()) + + rightx = ( + p1.point.x() + + ((p1p3xdist * (p.y() - p1.point.y())) / p1p3ydist) + ) + else: + leftx = ( + p1.point.x() + + ((p1p3xdist * (p.y() - p1.point.y())) / p1p3ydist) + ) + if firstshorty: + if floor(p1p2ydist) != 0.0: + rightx = ( + p1.point.x() + + ((p1p2xdist * (p.y() - p1.point.y())) / p1p2ydist) + ) + else: + rightx = max(p1.point.x(), p2.point.x()) + + else: + if floor(p2p3ydist) != 0.0: + rightx = ( + p2.point.x() + + ((p2p3xdist * (p.y() - p2.point.y())) / p2p3ydist) + ) + else: + rightx = max(p2.point.x(), p3.point.x()) + + # Find the r,g,b values of the points on the trigon's edges that + # are to the left and right of the selector. + if firstshorty: + if floor(p1p2ydist) != 0.0: + p_p1_ratio = (p.y() - p1.point.y()) / p1p2ydist + p2_p_ratio = (p2.point.y() - p.y()) / p1p2ydist + rshort = (p2.color.r * p_p1_ratio) + (p1.color.r * p2_p_ratio) + gshort = (p2.color.g * p_p1_ratio) + (p1.color.g * p2_p_ratio) + bshort = (p2.color.b * p_p1_ratio) + (p1.color.b * p2_p_ratio) + elif lefty: + if p1.point.x() <= p2.point.x(): + rshort = p1.color.r + gshort = p1.color.g + bshort = p1.color.b + else: + rshort = p2.color.r + gshort = p2.color.g + bshort = p2.color.b + + else: + if p1.point.x() > p2.point.x(): + rshort = p1.color.r + gshort = p1.color.g + bshort = p1.color.b + else: + rshort = p2.color.r + gshort = p2.color.g + bshort = p2.color.b + + else: + if floor(p2p3ydist) != 0.0: + p_p2_ratio = (p.y() - p2.point.y()) / p2p3ydist + p3_p_ratio = (p3.point.y() - p.y()) / p2p3ydist + rshort = (p3.color.r * p_p2_ratio) + (p2.color.r * p3_p_ratio) + gshort = (p3.color.g * p_p2_ratio) + (p2.color.g * p3_p_ratio) + bshort = (p3.color.b * p_p2_ratio) + (p2.color.b * p3_p_ratio) + elif lefty: + if p2.point.x() <= p3.point.x(): + rshort = p2.color.r + gshort = p2.color.g + bshort = p2.color.b + else: + rshort = p3.color.r + gshort = p3.color.g + bshort = p3.color.b + + else: + if p2.point.x() > p3.point.x(): + rshort = p2.color.r + gshort = p2.color.g + bshort = p2.color.b + else: + rshort = p3.color.r + gshort = p3.color.g + bshort = p3.color.b + + # p1p3ydist is never 0 + p_p1_ratio = (p.y() - p1.point.y()) / p1p3ydist + p3_p_ratio = (p3.point.y() - p.y()) / p1p3ydist + rlong = (p3.color.r * p_p1_ratio) + (p1.color.r * p3_p_ratio) + glong = (p3.color.g * p_p1_ratio) + (p1.color.g * p3_p_ratio) + blong = (p3.color.b * p_p1_ratio) + (p1.color.b * p3_p_ratio) + + # rshort,gshort,bshort is the color on one of the shortys. + # rlong,glong,blong is the color on the longy. So depending on + # wether we have a lefty trigon or not, we can determine which + # colors are on the left and right edge. + if lefty: + rl = rshort + gl = gshort + bl = bshort + rr = rlong + gr = glong + br = blong + else: + rl = rlong + gl = glong + bl = blong + rr = rshort + gr = gshort + br = bshort + + # Find the distance from the left x to the right x (xdist). Then + # find the distances from the selector to each of these (saxdist + # and saxdist2). These distances are used to find the color at + # the selector. + xdist = rightx - leftx + saxdist = p.x() - leftx + saxdist2 = xdist - saxdist + + # Now determine the r,g,b values of the selector using a linear + # approximation. + if xdist != 0.0: + r = (saxdist2 * rl / xdist) + (saxdist * rr / xdist) + g = (saxdist2 * gl / xdist) + (saxdist * gr / xdist) + b = (saxdist2 * bl / xdist) + (saxdist * br / xdist) + else: + # In theory, the left and right color will be equal here. But + # because of the loss of precision, we get an error on both + # colors. The best approximation we can get is from adding + # the two errors, which in theory will eliminate the error + # but in practise will only minimize it. + r = (rl + rr) / 2 + g = (gl + gr) / 2 + b = (bl + br) / 2 + + # Now floor the color components and fit them into proper + # boundaries. This again is to compensate for the error caused by + # loss of precision. + ri = int(floor(r)) + gi = int(floor(g)) + bi = int(floor(b)) + if ri < 0: + ri = 0 + elif ri > 255: + ri = 255 + + if gi < 0: + gi = 0 + elif gi > 255: + gi = 255 + + if bi < 0: + bi = 0 + elif bi > 255: + bi = 255 + + # Voila, we have the color at the point of the selector. + return QtGui.QColor(ri, gi, bi) + + def paint_bg(self): + bg_image = QtGui.QPixmap(self.contentsRect().size()) + bg_image.fill(QtCore.Qt.transparent) + self.bg_image = bg_image + + painter = QtGui.QPainter(self.bg_image) + + painter.setRenderHint(QtGui.QPainter.Antialiasing) + + hue_gradient = QtGui.QConicalGradient( + bg_image.rect().center(), 90 - self.hue_offset + ) + sat_val_gradient = QtGui.QConicalGradient( + bg_image.rect().center(), 90 - self.hue_offset + ) + + hue_color = QtGui.QColor() + sat_val_color = QtGui.QColor() + _, sat, val, _ = self.cur_color.getHsv() + + for idx, hue in self._hue_circle_range: + hue_color.setHsv(hue, 255, 255) + sat_val_color.setHsv(hue, sat, val) + + hue_gradient.setColorAt(idx, hue_color) + sat_val_gradient.setColorAt(idx, sat_val_color) + + inner_radius = self.outer_radius - ( + self.outer_radius / self.inner_radius_ratio + ) + half_radius = self.outer_radius - ( + (self.outer_radius - inner_radius) / 2 + ) + + hue_inner_radius_rect = QtCore.QRectF( + bg_image.rect().center().x() - inner_radius, + bg_image.rect().center().y() - inner_radius, + inner_radius * 2 + 1, + inner_radius * 2 + 1 + ) + hue_outer_radius_rect = QtCore.QRectF( + bg_image.rect().center().x() - half_radius - 1, + bg_image.rect().center().y() - half_radius - 1, + half_radius * 2 + 3, + half_radius * 2 + 3 + ) + sat_val_inner_radius_rect = QtCore.QRectF( + bg_image.rect().center().x() - half_radius, + bg_image.rect().center().y() - half_radius, + half_radius * 2 + 1, + half_radius * 2 + 1 + ) + sat_val_outer_radius_rect = QtCore.QRectF( + bg_image.rect().center().x() - self.outer_radius, + bg_image.rect().center().y() - self.outer_radius, + self.outer_radius * 2 + 1, + self.outer_radius * 2 + 1 + ) + hue_path = QtGui.QPainterPath() + hue_path.addEllipse(hue_inner_radius_rect) + hue_path.addEllipse(hue_outer_radius_rect) + + sat_val_path = QtGui.QPainterPath() + sat_val_path.addEllipse(sat_val_inner_radius_rect) + sat_val_path.addEllipse(sat_val_outer_radius_rect) + + painter.save() + painter.setClipPath(hue_path) + painter.fillRect(self.bg_image.rect(), hue_gradient) + painter.restore() + + painter.save() + painter.setClipPath(sat_val_path) + painter.fillRect(self.bg_image.rect(), sat_val_gradient) + painter.restore() + + painter.end() + + @staticmethod + def vlen(x, y): + return sqrt((x ** 2) + (y ** 2)) + + @staticmethod + def vprod(x1, y1, x2, y2): + return x1 * x2 + y1 * y2 + + @staticmethod + def _angle_between_angles(p, a1, a2): + if a1 > a2: + a2 += TWOPI + if p < PI: + p += TWOPI + + return p >= a1 and p < a2 + + @staticmethod + def _point_above_point(x, y, px, py, ax, ay, bx, by): + floored_ax = floor(ax) + floored_bx = floor(bx) + floored_ay = floor(ay) + floored_by = floor(by) + + if floored_ax == floored_bx: + # line is vertical + if floored_ay < floored_by: + return x < ax + elif floored_ay > floored_by: + return x > ax + return not (x == ax and y == ay) + + if floored_ax > floored_bx: + if floored_ay < floored_by: + # line is draw upright-to-downleft + return (floor(x) < floor(px) or floor(y) < floor(py)) + elif floored_ay > floored_by: + # line is draw downright-to-upleft + return (floor(x) > floor(px) or floor(y) < floor(py)) + # line is flat horizontal + return y < ay + + if floored_ay < floored_by: + # line is draw upleft-to-downright + return (floor(x) < floor(px) or floor(y) > floor(py)) + elif floored_ay > floored_by: + # line is draw downleft-to-upright + return (floor(x) > floor(px) or floor(y) > floor(py)) + # line is flat horizontal + return y > ay + + @staticmethod + def _point_in_line(x, y, ax, ay, bx, by): + if ax > bx: + if ay < by: + # line is draw upright-to-downleft + + # if (x,y) is in on or above the upper right point, + # return -1. + if y <= ay and x >= ax: + return -1 + + # if (x,y) is in on or below the lower left point, + # return 1. + if y >= by and x <= bx: + return 1 + else: + # line is draw downright-to-upleft + + # If the line is flat, only use the x coordinate. + if floor(ay) == floor(by): + # if (x is to the right of the rightmost point, + # return -1. otherwise if x is to the left of the + # leftmost point, return 1. + if x >= ax: + return -1 + elif x <= bx: + return 1 + else: + # if (x,y) is on or below the lower right point, + # return -1. + if y >= ay and x >= ax: + return -1 + + # if (x,y) is on or above the upper left point, return 1. + if y <= by and x <= bx: + return 1 + else: + if ay < by: + # line is draw upleft-to-downright + + # If (x,y) is on or above the upper left point, return -1. + if y <= ay and x <= ax: + return -1 + + # If (x,y) is on or below the lower right point, return 1. + if y >= by and x >= bx: + return 1 + else: + # line is draw downleft-to-upright + + # If the line is flat, only use the x coordinate. + if floor(ay) == floor(by): + if x <= ax: + return -1 + elif x >= bx: + return 1 + else: + # If (x,y) is on or below the lower left point, return -1. + if y >= ay and x <= ax: + return -1 + + # If (x,y) is on or above the upper right point, return 1. + if y <= by and x >= bx: + return 1 + + # No tests proved that (x,y) was outside [(ax,ay),(bx,by)], so we + # assume it's inside the line's bounds. + return 0 + + def _move_point_to_triangle(self, x, y, a, b, c): + # Let v1A be the vector from (x,y) to a. + # Let v2A be the vector from a to b. + # Find the angle alphaA between v1A and v2A. + v1xA = x - a.point.x() + v1yA = y - a.point.y() + v2xA = b.point.x() - a.point.x() + v2yA = b.point.y() - a.point.y() + vpA = self.vprod(v1xA, v1yA, v2xA, v2yA) + cosA = vpA / (self.vlen(v1xA, v1yA) * self.vlen(v2xA, v2yA)) + alphaA = acos(cosA) + + # Let v1B be the vector from x to b. + # Let v2B be the vector from b to c. + v1xB = x - b.point.x() + v1yB = y - b.point.y() + v2xB = c.point.x() - b.point.x() + v2yB = c.point.y() - b.point.y() + vpB = self.vprod(v1xB, v1yB, v2xB, v2yB) + cosB = vpB / (self.vlen(v1xB, v1yB) * self.vlen(v2xB, v2yB)) + alphaB = acos(cosB) + + # Let v1C be the vector from x to c. + # Let v2C be the vector from c back to a. + v1xC = x - c.point.x() + v1yC = y - c.point.y() + v2xC = a.point.x() - c.point.x() + v2yC = a.point.y() - c.point.y() + vpC = self.vprod(v1xC, v1yC, v2xC, v2yC) + cosC = vpC / (self.vlen(v1xC, v1yC) * self.vlen(v2xC, v2yC)) + alphaC = acos(cosC) + + # Find the radian angles between the (1,0) vector and the points + # A, B, C and (x,y). Use this information to determine which of + # the edges we should project (x,y) onto. + angleA = self._angle_at(a.point, self.contentsRect()) + angleB = self._angle_at(b.point, self.contentsRect()) + angleC = self._angle_at(c.point, self.contentsRect()) + angleP = self._angle_at(QtCore.QPointF(x, y), self.contentsRect()) + + # If (x,y) is in the a-b area, project onto the a-b vector. + if self._angle_between_angles(angleP, angleA, angleB): + # Find the distance from (x,y) to a. Then use the slope of + # the a-b vector with this distance and the angle between a-b + # and a-(x,y) to determine the point of intersection of the + # perpendicular projection from (x,y) onto a-b. + pdist = sqrt( + ((x - a.point.x()) ** 2) + ((y - a.point.y()) ** 2) + ) + + # the length of all edges is always > 0 + p0x = ( + a.point.x() + + ((b.point.x() - a.point.x()) / self.vlen(v2xB, v2yB)) + * cos(alphaA) * pdist + ) + p0y = ( + a.point.y() + + ((b.point.y() - a.point.y()) / self.vlen(v2xB, v2yB)) + * cos(alphaA) * pdist + ) + + # If (x,y) is above the a-b line, which basically means it's + # outside the triangle, then return its projection onto a-b. + if self._point_above_point( + x, y, + p0x, p0y, + a.point.x(), a.point.y(), + b.point.x(), b.point.y() + ): + # If the projection is "outside" a, return a. If it is + # outside b, return b. Otherwise return the projection. + n = self._point_in_line( + p0x, p0y, + a.point.x(), a.point.y(), + b.point.x(), b.point.y() + ) + if n < 0: + return a.point + elif n > 0: + return b.point + + return QtCore.QPointF(p0x, p0y) + + elif self._angle_between_angles(angleP, angleB, angleC): + # If (x,y) is in the b-c area, project onto the b-c vector. + pdist = sqrt( + ((x - b.point.x()) ** 2) + ((y - b.point.y()) ** 2) + ) + + # the length of all edges is always > 0 + p0x = ( + b.point.x() + + ((c.point.x() - b.point.x()) / self.vlen(v2xC, v2yC)) + * cos(alphaB) * pdist + ) + p0y = ( + b.point.y() + + ((c.point.y() - b.point.y()) / self.vlen(v2xC, v2yC)) + * cos(alphaB) + * pdist + ) + + if self._point_above_point( + x, y, + p0x, p0y, + b.point.x(), b.point.y(), + c.point.x(), c.point.y() + ): + n = self._point_in_line( + p0x, p0y, + b.point.x(), b.point.y(), + c.point.x(), c.point.y() + ) + if n < 0: + return b.point + elif n > 0: + return c.point + return QtCore.QPointF(p0x, p0y) + + elif self._angle_between_angles(angleP, angleC, angleA): + # If (x,y) is in the c-a area, project onto the c-a vector. + pdist = sqrt( + ((x - c.point.x()) ** 2) + ((y - c.point.y()) ** 2) + ) + + # the length of all edges is always > 0 + p0x = ( + c.point.x() + + ((a.point.x() - c.point.x()) / self.vlen(v2xA, v2yA)) + * cos(alphaC) + * pdist + ) + p0y = ( + c.point.y() + + ((a.point.y() - c.point.y()) / self.vlen(v2xA, v2yA)) + * cos(alphaC) * pdist + ) + + if self._point_above_point( + x, y, + p0x, p0y, + c.point.x(), c.point.y(), + a.point.x(), a.point.y() + ): + n = self._point_in_line( + p0x, p0y, + c.point.x(), c.point.y(), + a.point.x(), a.point.y() + ) + if n < 0: + return c.point + elif n > 0: + return a.point + return QtCore.QPointF(p0x, p0y) + + # (x,y) is inside the triangle (inside a-b, b-c and a-c). + return QtCore.QPointF(x, y) diff --git a/openpype/widgets/color_widgets/color_view.py b/openpype/widgets/color_widgets/color_view.py new file mode 100644 index 0000000000..a4393a6625 --- /dev/null +++ b/openpype/widgets/color_widgets/color_view.py @@ -0,0 +1,78 @@ +from Qt import QtWidgets, QtCore, QtGui + + +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: + checkboard_piece_size = 10 + color_1 = QtGui.QColor(188, 188, 188) + color_2 = QtGui.QColor(90, 90, 90) + + pix = QtGui.QPixmap( + checkboard_piece_size * 2, + checkboard_piece_size * 2 + ) + pix_painter = QtGui.QPainter(pix) + + rect = QtCore.QRect( + 0, 0, checkboard_piece_size, checkboard_piece_size + ) + pix_painter.fillRect(rect, color_1) + rect.moveTo(checkboard_piece_size, checkboard_piece_size) + pix_painter.fillRect(rect, color_1) + rect.moveTo(checkboard_piece_size, 0) + pix_painter.fillRect(rect, color_2) + rect.moveTo(0, checkboard_piece_size) + pix_painter.fillRect(rect, color_2) + pix_painter.end() + self._checkerboard = pix + + 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): + rect = event.rect() + + # Paint everything to pixmap as it has transparency + pix = QtGui.QPixmap(rect.width(), rect.height()) + pix_painter = QtGui.QPainter(pix) + pix_painter.drawTiledPixmap(rect, self.checkerboard()) + pix_painter.fillRect(rect, self.actual_color) + pix_painter.end() + + painter = QtGui.QPainter(self) + painter.drawPixmap(rect, pix) + painter.end() From ac3fbcb7ce1c2ab34ef30f80f44a61acfb922b2f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 17:26:19 +0200 Subject: [PATCH 04/32] base implementation of ColorEntity view --- .../tools/settings/settings/categories.py | 6 +- .../tools/settings/settings/color_widget.py | 174 ++++++++++++++++++ 2 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 openpype/tools/settings/settings/color_widget.py diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 4762aa4b6b..01d4babd0f 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -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) diff --git a/openpype/tools/settings/settings/color_widget.py b/openpype/tools/settings/settings/color_widget.py new file mode 100644 index 0000000000..54545d7450 --- /dev/null +++ b/openpype/tools/settings/settings/color_widget.py @@ -0,0 +1,174 @@ +from Qt import QtWidgets, QtCore, QtGui + +from .item_widgets import InputWidget + +from openpype.widgets.color_widgets import ColorPickerWidget + + +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: + checkboard_piece_size = 10 + color_1 = QtGui.QColor(188, 188, 188) + color_2 = QtGui.QColor(90, 90, 90) + + pix = QtGui.QPixmap( + checkboard_piece_size * 2, + checkboard_piece_size * 2 + ) + pix_painter = QtGui.QPainter(pix) + + rect = QtCore.QRect( + 0, 0, checkboard_piece_size, checkboard_piece_size + ) + pix_painter.fillRect(rect, color_1) + rect.moveTo(checkboard_piece_size, checkboard_piece_size) + pix_painter.fillRect(rect, color_1) + rect.moveTo(checkboard_piece_size, 0) + pix_painter.fillRect(rect, color_2) + rect.moveTo(0, checkboard_piece_size) + pix_painter.fillRect(rect, color_2) + pix_painter.end() + self._checkerboard = pix + + 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() + + # Paint everything to pixmap as it has transparency + pix = QtGui.QPixmap(rect.width(), rect.height()) + pix_painter = QtGui.QPainter(pix) + pix_painter.drawTiledPixmap(rect, self.checkerboard()) + pix_painter.fillRect(rect, self.actual_color) + pix_painter.end() + + painter = QtGui.QPainter(self) + painter.drawPixmap(rect, pix) + 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) + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + ok_btn = QtWidgets.QPushButton("Ok", footer_widget) + cancel_btn = QtWidgets.QPushButton("Cancel", footer_widget) + + footer_layout.addWidget(ok_btn) + footer_layout.addWidget(cancel_btn) + footer_layout.addWidget(QtWidgets.QWidget(self), 1) + + 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._result = None + + 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 From 589dcfd6d0e1e311e240d631e2753532ce2ef10a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 17:56:00 +0200 Subject: [PATCH 05/32] Converted inputs to widgets --- .../widgets/color_widgets/color_inputs.py | 79 ++++++++++++------- 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index ddf8aebd4e..a4409988b2 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -25,11 +25,11 @@ QSlider::handle:horizontal:hover { }""" -class AlphaInputs(QtWidgets.QGroupBox): +class AlphaInputs(QtWidgets.QWidget): alpha_changed = QtCore.Signal(int) def __init__(self, parent=None): - super(AlphaInputs, self).__init__("Alpha", parent) + super(AlphaInputs, self).__init__(parent) self._block_changes = False self.alpha_value = None @@ -47,11 +47,13 @@ class AlphaInputs(QtWidgets.QGroupBox): inputs_layout.setContentsMargins(0, 0, 0, 0) 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) @@ -61,6 +63,7 @@ class AlphaInputs(QtWidgets.QGroupBox): inputs_layout.addWidget(QtWidgets.QLabel("%")) layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(QtWidgets.QLabel("Alpha", self)) layout.addWidget(alpha_slider) layout.addWidget(inputs_widget) @@ -119,11 +122,11 @@ class AlphaInputs(QtWidgets.QGroupBox): self._block_changes = False -class RGBInputs(QtWidgets.QGroupBox): +class RGBInputs(QtWidgets.QWidget): value_changed = QtCore.Signal() def __init__(self, color, parent=None): - super(RGBInputs, self).__init__("RGB", parent) + super(RGBInputs, self).__init__(parent) self._block_changes = False @@ -133,6 +136,10 @@ class RGBInputs(QtWidgets.QGroupBox): 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) @@ -142,9 +149,10 @@ class RGBInputs(QtWidgets.QGroupBox): input_blue.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(input_red) - layout.addWidget(input_green) - layout.addWidget(input_blue) + layout.addWidget(QtWidgets.QLabel("RGB", self), 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) @@ -192,11 +200,11 @@ class RGBInputs(QtWidgets.QGroupBox): self._block_changes = False -class CMYKInputs(QtWidgets.QGroupBox): +class CMYKInputs(QtWidgets.QWidget): value_changed = QtCore.Signal() def __init__(self, color, parent=None): - super(CMYKInputs, self).__init__("CMYK", parent) + super(CMYKInputs, self).__init__(parent) self.color = color @@ -207,6 +215,11 @@ class CMYKInputs(QtWidgets.QGroupBox): 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) @@ -218,10 +231,11 @@ class CMYKInputs(QtWidgets.QGroupBox): input_black.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(input_cyan) - layout.addWidget(input_magenta) - layout.addWidget(input_yellow) - layout.addWidget(input_black) + layout.addWidget(QtWidgets.QLabel("CMYK", self)) + 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) @@ -272,18 +286,19 @@ class CMYKInputs(QtWidgets.QGroupBox): self._block_changes = False -class HEXInputs(QtWidgets.QGroupBox): +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__("HEX", parent) + super(HEXInputs, self).__init__(parent) self.color = color - input_field = QtWidgets.QLineEdit() + input_field = QtWidgets.QLineEdit(self) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(input_field) + layout.addWidget(QtWidgets.QLabel("HEX", self), 0) + layout.addWidget(input_field, 1) input_field.textChanged.connect(self._on_change) @@ -316,11 +331,11 @@ class HEXInputs(QtWidgets.QGroupBox): self._block_changes = False -class HSVInputs(QtWidgets.QGroupBox): +class HSVInputs(QtWidgets.QWidget): value_changed = QtCore.Signal() def __init__(self, color, parent=None): - super(HSVInputs, self).__init__("HSV", parent) + super(HSVInputs, self).__init__(parent) self._block_changes = False @@ -330,6 +345,10 @@ class HSVInputs(QtWidgets.QGroupBox): 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) @@ -339,9 +358,10 @@ class HSVInputs(QtWidgets.QGroupBox): input_val.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(input_hue) - layout.addWidget(input_sat) - layout.addWidget(input_val) + layout.addWidget(QtWidgets.QLabel("HSV", self), 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) @@ -385,11 +405,11 @@ class HSVInputs(QtWidgets.QGroupBox): self._block_changes = False -class HSLInputs(QtWidgets.QGroupBox): +class HSLInputs(QtWidgets.QWidget): value_changed = QtCore.Signal() def __init__(self, color, parent=None): - super(HSLInputs, self).__init__("HSL", parent) + super(HSLInputs, self).__init__(parent) self._block_changes = False @@ -399,6 +419,10 @@ class HSLInputs(QtWidgets.QGroupBox): 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) @@ -408,9 +432,10 @@ class HSLInputs(QtWidgets.QGroupBox): input_light.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(input_hue) - layout.addWidget(input_sat) - layout.addWidget(input_light) + layout.addWidget(QtWidgets.QLabel("HSL", self), 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) From ed6848d9d02e76d3bd05d6f5817eb6c8bce5939a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 18:02:12 +0200 Subject: [PATCH 06/32] used color type at 2 places --- .../schemas/schema_global_publish.json | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 0efe3b8fea..426da4b71e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -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" } ] } From 3e1286531f27ab78441ba2f517e5f50da865bcea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 18:11:31 +0200 Subject: [PATCH 07/32] add empty line --- openpype/settings/entities/color_entity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/settings/entities/color_entity.py b/openpype/settings/entities/color_entity.py index 7d31ba42b9..7a1b1d9848 100644 --- a/openpype/settings/entities/color_entity.py +++ b/openpype/settings/entities/color_entity.py @@ -8,6 +8,7 @@ from .exceptions import ( class ColorEntity(InputEntity): schema_types = ["color"] + def _item_initalization(self): self.valid_value_types = (list, ) self.value_on_not_set = [0, 0, 0, 255] From 2861078620d62cca8fc6d24d72a3c60a05e66ec5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 18:24:13 +0200 Subject: [PATCH 08/32] added small info about color entity --- openpype/settings/entities/schemas/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 18312a8364..6c31b61f59 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -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 From ac2e759afc45e1347bc85994a10f2e33670447e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 18:24:34 +0200 Subject: [PATCH 09/32] added color entity to examples --- .../entities/schemas/system_schema/example_schema.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index 48a21cc0c6..a4ed56df32 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -4,6 +4,11 @@ "type": "dict", "is_file": true, "children": [ + { + "key": "color", + "label": "Color input", + "type": "color" + }, { "type": "dict", "key": "schema_template_exaples", From f8936f5e4038f2516363c645fd04182ec309c9d8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 18:34:29 +0200 Subject: [PATCH 10/32] replaced pick color text with an icon --- .../widgets/color_widgets/color_picker_widget.py | 9 +++++++-- openpype/widgets/color_widgets/eyedropper.png | Bin 0 -> 3178 bytes 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 openpype/widgets/color_widgets/eyedropper.png diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index d06af73cbf..5786c529ed 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -1,3 +1,4 @@ +import os from Qt import QtWidgets, QtCore, QtGui from .color_triangle import QtColorTriangle @@ -32,9 +33,13 @@ class ColorPickerWidget(QtWidgets.QWidget): color_view.setMaximumHeight(50) # Color pick button - btn_pick_color = QtWidgets.QPushButton( - "Pick a color", bottom_utils_widget + btn_pick_color = QtWidgets.QPushButton(bottom_utils_widget) + 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 inputs widget color_inputs = ColorInputsWidget(self) diff --git a/openpype/widgets/color_widgets/eyedropper.png b/openpype/widgets/color_widgets/eyedropper.png new file mode 100644 index 0000000000000000000000000000000000000000..baf6209e0be3d0f32cf76aebbfc0ba5edef7dc3a GIT binary patch literal 3178 zcmai1d0bQ1621W>8d;?5P$W%Q3YdjJKw`pT023sXeRF{jZXiGwk^m8u5L}8?i^L5m zA{G#os8rPAPsO}yL6Ln^Dn;s2K#PJP0(E&eV6m<3dw<*{=X^8YnfYes++TJF2CUOY zm>~cF(5BP8gQ0Iv)w5I+`aH!+9RvW4bDYpfaU{c^#Nu1X)6Y2)Lk_ ziIi|tcp{R7f|}f zg2e>>a>OG1ks~DEKf6enf`2k*F#fkWm;0AAL}H(GNQn2M{w1bA4iJTI5rDX0P{dCY zvOu47kS9jZE1}Yb6v&Z)DUseBF31x>>QYcH&VK`={spA+Q~1IVsHs2-%5mODr5uUQ zWhQ_ToOp5KBG-p~e~_2(cR&?cN}8&)VLT3$3*iG-f-3;;({Sy7ZQPrMuL1f>@D6*F=R!+ z6C5#kBF51z)NwV*$%#Z*?SLnc@C)(GTSK!D&lEHNpS`N1kz~k&!64B%A~9dMWqxa+ z%|pP{h1J3`g)`sZNaTE#NlcbXBnnC(Ab<1Nq-{4`9T%tOyS(4Dcat1)2Zur z>hp60PQMs_dtt63$v!a1fm}VZ%bILM{w{7*hris_p-2)u6Iy?J|8m5AQsxhjjlb;1 zO5IX!d`lhT7))15Qy&;Rm{Ro*AJOkPciV5my>0!KBpy3D?y|(eCdhX;4cBPO&cjo* zZhmS*!~J~jTmS44Tj^I<9@!I~D&lIri?xh4eT!Y678d6x4)Z-6R`pK$d(w%y%%7hO zE)DhQpWXoPN%3NKU-Z!BUKVH^$`=$kJRa69d$(z-Rc>?_exFpb>G1g*IwgG1+uK*V zKJW;xthYUsdq+R9aQlhm>J5Ud@PXl(m2t^0!Xj!f!;cCFzn{^TbSFHc)>qP@7sfIH zElLCc%k@5!L6fwYz58S(`%R?*gCYN_N7=QalRylj z+Ar5YABQJd>6soc)2p63PP_H_Z(hD$MSg=Z@<>xw@Yf<8&!$tW$S2FpQ({61= zmz+fHMQ+0bmwtWMT}sTNv}Y@>zRI1Pk(T>}n6H4hD2RBY`sE{PVhs|oPWCG2u{^uI z>E3VZv20z~Wa`((Yb|Px3Q-*Ccs|=4aJsA&iSXa6oYog?5ICRTd#~0xmVI5u-~B7u zlOG$jzeC}h<9cTpR`>byhuZt63_%3lklm*jsZOTPrr#fPE%pIg6vnltCMyrzEXXc@ z0G&e3!6+{{el_-+t33%|r+Wl4ltYCbef-cApd|%6Zq#7|o5*_OTcfe#$P%Yy^(&hG z0fDUn*Mfpz__{VFV(9RWFw84+Lw&&MDO{nEX{|^y9{=uL`7O0r_Rlh=^0abASqvg6 zSq8W2o!!v`9f*rIdBN~Z<3rVnhBKDuDj z2^97t2ln{O+tjBHPOZ1wyPF{+w^=qGRZC`thrwS*+lx?;K(ct<-_c47dFl1a8}Ayc zdB-Z|f{R$XIm54`3AftyhSKR9U{zL#hM)ZI(Z7n^kZFyUYee z(-S5=;l2QQxP~+G`ta5_*1bTvj0IM|(Ux4$dMkl+b}e}GI3HGS#l<$-jylB}R8}_I z=1jFQo^AWmFWZWXnlo404Olnb(VI0ZQ;?M{bLz()1Z*8DxfasZ zxRiix0#lj%J<~%vYqnH{hi(kCFE4rt>pNh_n1!2Ibk3eB-TPGS(U$rLU#=IFH(OR_ z3G!|9t;g(ppKUxSc1U4U1Tm*;){%Cvk@EA94-v5z{m)_D)>lnNM4xrQS6gv#%aAV& z#?M^zfM;e_M91b<^yB))S57G|#3Kx!$WF=!^N|njOD5m`9&^-|6%}ckiVlU^n{vi|cn+8iqVsoz z#j_(Gmgs(6Y9}AmaX>F~HiH^zxk6KO$;~<|j84ss4bFGeDdaBB0Q&Le;~2(LAN|kH zMlzQ?d)e3YDSZ#3_R)b(-Mh5L+32ALKfy{fOW0Wet#-|rR%{+#f5bYx;N*nck^%JS zby~PhUhciJh<#D|NX#K;j3U;_9HCJADXZc@y%sObZkNr@xDez_Wrn)b4+vsiP(AMX z8mX2^2&auY0cbd#x3xOD6y4Ec1G9t~XH{I270CMzvE$C&GzV}|qXx)r#=eC`j2{g> z&rj3fIm}dxjdIW9dMh^0JXzM$YnWTqdSg?-3|v8}uX!>CMit`CG?ZDb+wh)0S)UxS zK2g*P+l;KMex|vLHs@%wMxWZi71;C>$zK@9xzvR#)$*H~bOuW98}He<44rK5T-Ts< zRr;(v>D!f8;=U=zT$U_YL%pxOAM=2>UB`x<+H%tZhAT*Yt0g<#?y#|R{V!`9wUu{+ zORI`MNr1+ib#wFU%}35(uj!5PFKcwyJtfFcJASd*?}vW7SoXaf*hvl(k!Y$>)6rje4D1yJp+~51Ub!=tr6;&ba4KuKs@9lEOdVi`KTRRQ*Mu M`viEOSsR=CU*aw9$^ZZW literal 0 HcmV?d00001 From 101134bbdfdfe847e647f515296089d43c2e0b09 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 19 May 2021 18:37:20 +0200 Subject: [PATCH 11/32] moved with buttons --- openpype/tools/settings/settings/color_widget.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/settings/settings/color_widget.py b/openpype/tools/settings/settings/color_widget.py index 54545d7450..7b534b749c 100644 --- a/openpype/tools/settings/settings/color_widget.py +++ b/openpype/tools/settings/settings/color_widget.py @@ -141,14 +141,15 @@ class ColorDialog(QtWidgets.QDialog): picker_widget = ColorPickerWidget(color, self) footer_widget = QtWidgets.QWidget(self) - footer_layout = QtWidgets.QHBoxLayout(footer_widget) 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) - footer_layout.addWidget(QtWidgets.QWidget(self), 1) layout = QtWidgets.QVBoxLayout(self) From 9ff9aab0292fd5defb6d49134a730ea5f6bc80ce Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 12:09:15 +0200 Subject: [PATCH 12/32] implemented alpha slider that can move pointer on click --- .../widgets/color_widgets/color_inputs.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index a4409988b2..b8737afd99 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -25,6 +25,40 @@ QSlider::handle:horizontal:hover { }""" +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.setStyleSheet(slide_style) + + 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) + + class AlphaInputs(QtWidgets.QWidget): alpha_changed = QtCore.Signal(int) From ce6c129f9ea27002d9d7b4d4a1a120776b61120a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 12:18:30 +0200 Subject: [PATCH 13/32] removed labels from input widgets and set content's margines --- openpype/widgets/color_widgets/color_inputs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index b8737afd99..f19e28ba1d 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -183,7 +183,7 @@ class RGBInputs(QtWidgets.QWidget): input_blue.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("RGB", self), 0) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(input_red, 1) layout.addWidget(input_green, 1) layout.addWidget(input_blue, 1) @@ -265,7 +265,7 @@ class CMYKInputs(QtWidgets.QWidget): input_black.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("CMYK", self)) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(input_cyan, 1) layout.addWidget(input_magenta, 1) layout.addWidget(input_yellow, 1) @@ -331,7 +331,7 @@ class HEXInputs(QtWidgets.QWidget): input_field = QtWidgets.QLineEdit(self) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("HEX", self), 0) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(input_field, 1) input_field.textChanged.connect(self._on_change) @@ -392,7 +392,7 @@ class HSVInputs(QtWidgets.QWidget): input_val.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("HSV", self), 0) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(input_hue, 1) layout.addWidget(input_sat, 1) layout.addWidget(input_val, 1) @@ -466,7 +466,7 @@ class HSLInputs(QtWidgets.QWidget): input_light.setMaximum(255) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("HSL", self), 0) + layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(input_hue, 1) layout.addWidget(input_sat, 1) layout.addWidget(input_light, 1) From b9b18f34af3eef3d93dfb3f7e75b85385af8797b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 12:19:18 +0200 Subject: [PATCH 14/32] removed alpha slider from alpha inputs --- .../widgets/color_widgets/color_inputs.py | 40 +++---------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index f19e28ba1d..9aa35021c2 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -68,18 +68,6 @@ class AlphaInputs(QtWidgets.QWidget): self._block_changes = False self.alpha_value = None - # Opacity slider - alpha_slider = QtWidgets.QSlider(QtCore.Qt.Horizontal, self) - alpha_slider.setSingleStep(1) - alpha_slider.setMinimum(0) - alpha_slider.setMaximum(255) - alpha_slider.setStyleSheet(slide_style) - alpha_slider.setValue(255) - - inputs_widget = QtWidgets.QWidget(self) - inputs_layout = QtWidgets.QHBoxLayout(inputs_widget) - inputs_layout.setContentsMargins(0, 0, 0, 0) - percent_input = QtWidgets.QDoubleSpinBox(self) percent_input.setButtonSymbols(QtWidgets.QSpinBox.NoButtons) percent_input.setMinimum(0) @@ -91,21 +79,16 @@ class AlphaInputs(QtWidgets.QWidget): int_input.setMinimum(0) int_input.setMaximum(255) - inputs_layout.addWidget(int_input) - inputs_layout.addWidget(QtWidgets.QLabel("0-255")) - inputs_layout.addWidget(percent_input) - inputs_layout.addWidget(QtWidgets.QLabel("%")) + 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("%")) - layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(QtWidgets.QLabel("Alpha", self)) - layout.addWidget(alpha_slider) - layout.addWidget(inputs_widget) - - alpha_slider.valueChanged.connect(self._on_slider_change) percent_input.valueChanged.connect(self._on_percent_change) int_input.valueChanged.connect(self._on_int_change) - self.alpha_slider = alpha_slider self.percent_input = percent_input self.int_input = int_input @@ -118,13 +101,6 @@ class AlphaInputs(QtWidgets.QWidget): self.update_alpha() - def _on_slider_change(self): - if self._block_changes: - return - self.alpha_value = self.alpha_slider.value() - self.alpha_changed.emit(self.alpha_value) - self.update_alpha() - def _on_percent_change(self): if self._block_changes: return @@ -142,10 +118,6 @@ class AlphaInputs(QtWidgets.QWidget): def update_alpha(self): self._block_changes = True - - if self.alpha_slider.value() != self.alpha_value: - self.alpha_slider.setValue(self.alpha_value) - if self.int_input.value() != self.alpha_value: self.int_input.setValue(self.alpha_value) From cbed588a75b8682f0ab3cbc9e4956df6dc29ec12 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 12:28:28 +0200 Subject: [PATCH 15/32] formatting changes --- openpype/widgets/color_widgets/color_inputs.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index 9aa35021c2..ddb832c655 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -4,13 +4,17 @@ from Qt import QtWidgets, QtCore, QtGui slide_style = """ QSlider::groove:horizontal { - background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #000, stop: 1 #fff); + 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); + 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; @@ -19,7 +23,9 @@ QSlider::handle:horizontal { } QSlider::handle:horizontal:hover { - background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ddd); + background: qlineargradient( + x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ddd + ); border: 1px solid #444;ff border-radius: 4px; }""" From 9f86bcff621298f82bdb11eda7d904ad3ada53ad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 12:29:13 +0200 Subject: [PATCH 16/32] inputs are handled inside ColorPickerWidget --- .../color_widgets/color_picker_widget.py | 141 ++++++++++++------ 1 file changed, 93 insertions(+), 48 deletions(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 5786c529ed..7158a426a4 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -6,7 +6,13 @@ from .color_view import ColorViewer from .color_screen_pick import PickScreenColorWidget from .color_inputs import ( ColorInputsWidget, - AlphaInputs + AlphaSlider, + AlphaInputs, + HEXInputs, + RGBInputs, + HSLInputs, + HSVInputs, + CMYKInputs ) @@ -16,24 +22,27 @@ class ColorPickerWidget(QtWidgets.QWidget): def __init__(self, color=None, parent=None): super(ColorPickerWidget, self).__init__(parent) - # Eye picked widget - pick_widget = PickScreenColorWidget() - - # Color utils - utils_widget = QtWidgets.QWidget(self) - utils_layout = QtWidgets.QVBoxLayout(utils_widget) - - bottom_utils_widget = QtWidgets.QWidget(utils_widget) + top_part = QtWidgets.QWidget(self) + left_side = QtWidgets.QWidget(top_part) # Color triangle - color_triangle = QtColorTriangle(utils_widget) + color_triangle = QtColorTriangle(left_side) - # Color preview - color_view = ColorViewer(bottom_utils_widget) - color_view.setMaximumHeight(50) + alpha_slider = AlphaSlider(QtCore.Qt.Horizontal, left_side) + + left_layout = QtWidgets.QVBoxLayout(left_side) + left_layout.setContentsMargins(0, 0, 0, 0) + left_layout.addWidget(color_triangle) + left_layout.addWidget(alpha_slider) + + right_side = QtWidgets.QWidget(top_part) + + # Eye picked widget + pick_widget = PickScreenColorWidget() + pick_widget.setMaximumHeight(50) # Color pick button - btn_pick_color = QtWidgets.QPushButton(bottom_utils_widget) + btn_pick_color = QtWidgets.QPushButton(right_side) icon_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "eyedropper.png" @@ -41,50 +50,62 @@ class ColorPickerWidget(QtWidgets.QWidget): btn_pick_color.setIcon(QtGui.QIcon(icon_path)) btn_pick_color.setToolTip("Pick a color") - # Color inputs widget - color_inputs = ColorInputsWidget(self) + # Color preview + color_view = ColorViewer(right_side) + color_view.setMaximumHeight(50) - # Alpha inputs - alpha_input_wrapper_widget = QtWidgets.QWidget(self) - alpha_input_wrapper_layout = QtWidgets.QVBoxLayout( - alpha_input_wrapper_widget - ) + row = 0 + right_layout = QtWidgets.QGridLayout(right_side) + right_layout.setContentsMargins(0, 0, 0, 0) + right_layout.addWidget(btn_pick_color, row, 0) + right_layout.addWidget(color_view, row, 1) - alpha_inputs = AlphaInputs(alpha_input_wrapper_widget) - alpha_input_wrapper_layout.addWidget(alpha_inputs) - alpha_input_wrapper_layout.addWidget(QtWidgets.QWidget(), 1) + color_inputs_color = QtGui.QColor() + col_inputs_by_label = [ + ("HEX", HEXInputs(color_inputs_color, right_side)), + ("RGB", RGBInputs(color_inputs_color, right_side)), + ("HSL", HSLInputs(color_inputs_color, right_side)), + ("HSV", HSVInputs(color_inputs_color, right_side)) + ] + color_input_fields = [] + for label, input_field in col_inputs_by_label: + row += 1 + right_layout.addWidget(QtWidgets.QLabel(label, right_side), row, 0) + right_layout.addWidget(input_field, row, 1) + input_field.value_changed.connect( + self._on_color_input_value_change + ) + color_input_fields.append(input_field) - bottom_utils_layout = QtWidgets.QHBoxLayout(bottom_utils_widget) - bottom_utils_layout.setContentsMargins(0, 0, 0, 0) - bottom_utils_layout.addWidget(color_view, 1) - bottom_utils_layout.addWidget(btn_pick_color, 0) - - utils_layout.addWidget(bottom_utils_widget, 0) - utils_layout.addWidget(color_triangle, 1) + row += 1 + alpha_inputs = AlphaInputs(right_side) + right_layout.addWidget(QtWidgets.QLabel("Alpha", right_side), row, 0) + right_layout.addWidget(alpha_inputs, row, 1) layout = QtWidgets.QHBoxLayout(self) - layout.addWidget(utils_widget, 1) - layout.addWidget(color_inputs, 0) - layout.addWidget(alpha_input_wrapper_widget, 0) + layout.setSpacing(5) + layout.addWidget(left_side, 1) + layout.addWidget(right_side, 0) color_view.set_color(color_triangle.cur_color) - color_inputs.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) - color_inputs.color_changed.connect(self.on_color_change) - alpha_inputs.alpha_changed.connect(self.alpha_changed) + 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.utils_widget = utils_widget - self.bottom_utils_widget = bottom_utils_widget self.color_triangle = color_triangle + self.alpha_slider = alpha_slider + self.color_view = color_view - self.btn_pick_color = btn_pick_color - self.color_inputs = color_inputs self.alpha_inputs = alpha_inputs + self.btn_pick_color = btn_pick_color if color: self.set_color(color) @@ -92,9 +113,7 @@ class ColorPickerWidget(QtWidgets.QWidget): def showEvent(self, event): super(ColorPickerWidget, self).showEvent(event) - triangle_width = int(( - self.utils_widget.height() - self.bottom_utils_widget.height() - ) / 5 * 4) + triangle_width = int(self.height() / 5 * 4) self.color_triangle.setMinimumWidth(triangle_width) def color(self): @@ -109,12 +128,38 @@ class ColorPickerWidget(QtWidgets.QWidget): def triangle_color_changed(self, color): self.color_view.set_color(color) - self.color_inputs.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) - self.color_inputs.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 alpha_changed(self, alpha): - self.color_view.set_alpha(alpha) + 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) From 3245e97884c724037de5a14c1ba130f5d818857e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 12:29:37 +0200 Subject: [PATCH 17/32] checkerboard can be drawn out of ColorViewer --- openpype/widgets/color_widgets/color_view.py | 52 +++++++++++--------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/openpype/widgets/color_widgets/color_view.py b/openpype/widgets/color_widgets/color_view.py index a4393a6625..d6d7f0a666 100644 --- a/openpype/widgets/color_widgets/color_view.py +++ b/openpype/widgets/color_widgets/color_view.py @@ -1,6 +1,34 @@ 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) @@ -14,29 +42,7 @@ class ColorViewer(QtWidgets.QWidget): def checkerboard(self): if not self._checkerboard: - checkboard_piece_size = 10 - color_1 = QtGui.QColor(188, 188, 188) - color_2 = QtGui.QColor(90, 90, 90) - - pix = QtGui.QPixmap( - checkboard_piece_size * 2, - checkboard_piece_size * 2 - ) - pix_painter = QtGui.QPainter(pix) - - rect = QtCore.QRect( - 0, 0, checkboard_piece_size, checkboard_piece_size - ) - pix_painter.fillRect(rect, color_1) - rect.moveTo(checkboard_piece_size, checkboard_piece_size) - pix_painter.fillRect(rect, color_1) - rect.moveTo(checkboard_piece_size, 0) - pix_painter.fillRect(rect, color_2) - rect.moveTo(0, checkboard_piece_size) - pix_painter.fillRect(rect, color_2) - pix_painter.end() - self._checkerboard = pix - + self._checkerboard = draw_checkerboard_tile() return self._checkerboard def color(self): From c4ec9cb7f1f0d5a2798fe1dc9acd902e2ca84095 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:10:49 +0200 Subject: [PATCH 18/32] use draw_checkerboard_tile to draw checkerboard --- .../tools/settings/settings/color_widget.py | 29 ++++--------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/openpype/tools/settings/settings/color_widget.py b/openpype/tools/settings/settings/color_widget.py index 7b534b749c..b84c8dd9cc 100644 --- a/openpype/tools/settings/settings/color_widget.py +++ b/openpype/tools/settings/settings/color_widget.py @@ -2,7 +2,10 @@ from Qt import QtWidgets, QtCore, QtGui from .item_widgets import InputWidget -from openpype.widgets.color_widgets import ColorPickerWidget +from openpype.widgets.color_widgets import ( + ColorPickerWidget, + draw_checkerboard_tile +) class ColorWidget(InputWidget): @@ -77,29 +80,7 @@ class ColorViewer(QtWidgets.QWidget): def checkerboard(self): if not self._checkerboard: - checkboard_piece_size = 10 - color_1 = QtGui.QColor(188, 188, 188) - color_2 = QtGui.QColor(90, 90, 90) - - pix = QtGui.QPixmap( - checkboard_piece_size * 2, - checkboard_piece_size * 2 - ) - pix_painter = QtGui.QPainter(pix) - - rect = QtCore.QRect( - 0, 0, checkboard_piece_size, checkboard_piece_size - ) - pix_painter.fillRect(rect, color_1) - rect.moveTo(checkboard_piece_size, checkboard_piece_size) - pix_painter.fillRect(rect, color_1) - rect.moveTo(checkboard_piece_size, 0) - pix_painter.fillRect(rect, color_2) - rect.moveTo(0, checkboard_piece_size) - pix_painter.fillRect(rect, color_2) - pix_painter.end() - self._checkerboard = pix - + self._checkerboard = draw_checkerboard_tile() return self._checkerboard def color(self): From 5680f96a499a59844d84b591d39c3b52cb431242 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:13:10 +0200 Subject: [PATCH 19/32] changed how triangle size is set --- openpype/widgets/color_widgets/color_picker_widget.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 7158a426a4..725d0b374e 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -107,14 +107,21 @@ class ColorPickerWidget(QtWidgets.QWidget): 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) - triangle_width = int(self.height() / 5 * 4) - self.color_triangle.setMinimumWidth(triangle_width) + 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() From 20c119f3555ee267c5784ce1e8d4e7db8d65ae9a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:13:20 +0200 Subject: [PATCH 20/32] added draw_checkerboard_tile to init file --- openpype/widgets/color_widgets/__init__.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/color_widgets/__init__.py b/openpype/widgets/color_widgets/__init__.py index 3423e26cf8..324b23543d 100644 --- a/openpype/widgets/color_widgets/__init__.py +++ b/openpype/widgets/color_widgets/__init__.py @@ -1,6 +1,14 @@ -from .color_picker_widget import ColorPickerWidget +from .color_picker_widget import ( + ColorPickerWidget +) + +from .color_view import ( + draw_checkerboard_tile +) __all__ = ( "ColorPickerWidget", + + "draw_checkerboard_tile" ) From 4bb432284fe4dea23bb61e2272d84d952e8ff4a3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:13:39 +0200 Subject: [PATCH 21/32] added stretch at the end of gridlayout --- openpype/widgets/color_widgets/color_picker_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 725d0b374e..3b1eb1aca0 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -82,6 +82,9 @@ class ColorPickerWidget(QtWidgets.QWidget): right_layout.addWidget(QtWidgets.QLabel("Alpha", right_side), row, 0) right_layout.addWidget(alpha_inputs, row, 1) + row += 1 + right_layout.setRowStretch(row, 1) + layout = QtWidgets.QHBoxLayout(self) layout.setSpacing(5) layout.addWidget(left_side, 1) From a688a1510e8dcb439267308112a72e81e61c546c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:13:50 +0200 Subject: [PATCH 22/32] alpha slider has proxy --- openpype/widgets/color_widgets/color_picker_widget.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 3b1eb1aca0..117e1c9aef 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -28,7 +28,12 @@ class ColorPickerWidget(QtWidgets.QWidget): # Color triangle color_triangle = QtColorTriangle(left_side) - alpha_slider = AlphaSlider(QtCore.Qt.Horizontal, left_side) + alpha_slider_proxy = QtWidgets.QWidget(left_side) + 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) left_layout = QtWidgets.QVBoxLayout(left_side) left_layout.setContentsMargins(0, 0, 0, 0) From e40847f61ff49b1ae47bce8b471f87ec98352852 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:14:05 +0200 Subject: [PATCH 23/32] added stretched of triangle and alpha slider --- openpype/widgets/color_widgets/color_picker_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 117e1c9aef..23c06929c0 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -37,8 +37,8 @@ class ColorPickerWidget(QtWidgets.QWidget): left_layout = QtWidgets.QVBoxLayout(left_side) left_layout.setContentsMargins(0, 0, 0, 0) - left_layout.addWidget(color_triangle) - left_layout.addWidget(alpha_slider) + left_layout.addWidget(color_triangle, 1) + left_layout.addWidget(alpha_slider_proxy, 0) right_side = QtWidgets.QWidget(top_part) From 97c1a14de48b48713bee78c1248573bf0f969e7e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 13:14:17 +0200 Subject: [PATCH 24/32] added spacing between left and right widget --- openpype/widgets/color_widgets/color_picker_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 23c06929c0..40e20496f6 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -91,7 +91,7 @@ class ColorPickerWidget(QtWidgets.QWidget): right_layout.setRowStretch(row, 1) layout = QtWidgets.QHBoxLayout(self) - layout.setSpacing(5) + layout.setSpacing(20) layout.addWidget(left_side, 1) layout.addWidget(right_side, 0) From 0579cb2983d32205deed83d778bd4fbbac3c44e7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 15:33:43 +0200 Subject: [PATCH 25/32] nicer alpha slider --- .../widgets/color_widgets/color_inputs.py | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index ddb832c655..d3c801bc0f 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -1,6 +1,8 @@ import re from Qt import QtWidgets, QtCore, QtGui +from .color_view import draw_checkerboard_tile + slide_style = """ QSlider::groove:horizontal { @@ -40,7 +42,14 @@ class AlphaSlider(QtWidgets.QSlider): self.setMaximum(255) self.setValue(255) - self.setStyleSheet(slide_style) + self._checkerboard = None + + def checkerboard(self): + if self._checkerboard is None: + self._checkerboard = draw_checkerboard_tile( + 3, QtGui.QColor(255, 255, 255), QtGui.QColor(27, 27, 27) + ) + return self._checkerboard def mousePressEvent(self, event): self._mouse_clicked = True @@ -64,6 +73,85 @@ class AlphaSlider(QtWidgets.QSlider): 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) From dbf70d34ea039a31ebd3b22dfbdc90d48a8ed61c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 15:38:03 +0200 Subject: [PATCH 26/32] darker white --- openpype/widgets/color_widgets/color_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/widgets/color_widgets/color_inputs.py b/openpype/widgets/color_widgets/color_inputs.py index d3c801bc0f..eda8c618f1 100644 --- a/openpype/widgets/color_widgets/color_inputs.py +++ b/openpype/widgets/color_widgets/color_inputs.py @@ -47,7 +47,7 @@ class AlphaSlider(QtWidgets.QSlider): def checkerboard(self): if self._checkerboard is None: self._checkerboard = draw_checkerboard_tile( - 3, QtGui.QColor(255, 255, 255), QtGui.QColor(27, 27, 27) + 3, QtGui.QColor(173, 173, 173), QtGui.QColor(27, 27, 27) ) return self._checkerboard From faa6fdcd08065e9de928a68f3a82b093f3318795 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 15:39:19 +0200 Subject: [PATCH 27/32] removed unused imports --- openpype/widgets/color_widgets/color_picker_widget.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 40e20496f6..fd0f31e342 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -5,14 +5,12 @@ from .color_triangle import QtColorTriangle from .color_view import ColorViewer from .color_screen_pick import PickScreenColorWidget from .color_inputs import ( - ColorInputsWidget, AlphaSlider, AlphaInputs, HEXInputs, RGBInputs, HSLInputs, - HSVInputs, - CMYKInputs + HSVInputs ) From c496386b47ef126bda0a6623f0411dc4920ac79c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 19:59:54 +0200 Subject: [PATCH 28/32] ok btn has same width as cancel btn --- openpype/tools/settings/settings/color_widget.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/tools/settings/settings/color_widget.py b/openpype/tools/settings/settings/color_widget.py index b84c8dd9cc..7aa3a4bba3 100644 --- a/openpype/tools/settings/settings/color_widget.py +++ b/openpype/tools/settings/settings/color_widget.py @@ -141,9 +141,18 @@ class ColorDialog(QtWidgets.QDialog): 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() From 69ed30f15c5fd8c5f7c19c2a8e34541a56a390e3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 20:23:26 +0200 Subject: [PATCH 29/32] colorpicker widget is one big grid --- .../color_widgets/color_picker_widget.py | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index fd0f31e342..27b9f8fc82 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -21,31 +21,23 @@ class ColorPickerWidget(QtWidgets.QWidget): super(ColorPickerWidget, self).__init__(parent) top_part = QtWidgets.QWidget(self) - left_side = QtWidgets.QWidget(top_part) # Color triangle - color_triangle = QtColorTriangle(left_side) + color_triangle = QtColorTriangle(self) - alpha_slider_proxy = QtWidgets.QWidget(left_side) + 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) - left_layout = QtWidgets.QVBoxLayout(left_side) - left_layout.setContentsMargins(0, 0, 0, 0) - left_layout.addWidget(color_triangle, 1) - left_layout.addWidget(alpha_slider_proxy, 0) - - right_side = QtWidgets.QWidget(top_part) - # Eye picked widget pick_widget = PickScreenColorWidget() pick_widget.setMaximumHeight(50) # Color pick button - btn_pick_color = QtWidgets.QPushButton(right_side) + btn_pick_color = QtWidgets.QPushButton(self) icon_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "eyedropper.png" @@ -54,44 +46,52 @@ class ColorPickerWidget(QtWidgets.QWidget): btn_pick_color.setToolTip("Pick a color") # Color preview - color_view = ColorViewer(right_side) + color_view = ColorViewer(self) color_view.setMaximumHeight(50) - row = 0 - right_layout = QtWidgets.QGridLayout(right_side) - right_layout.setContentsMargins(0, 0, 0, 0) - right_layout.addWidget(btn_pick_color, row, 0) - right_layout.addWidget(color_view, row, 1) + alpha_inputs = AlphaInputs(self) color_inputs_color = QtGui.QColor() col_inputs_by_label = [ - ("HEX", HEXInputs(color_inputs_color, right_side)), - ("RGB", RGBInputs(color_inputs_color, right_side)), - ("HSL", HSLInputs(color_inputs_color, right_side)), - ("HSV", HSVInputs(color_inputs_color, right_side)) + ("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: - row += 1 - right_layout.addWidget(QtWidgets.QLabel(label, right_side), row, 0) - right_layout.addWidget(input_field, row, 1) + 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 - alpha_inputs = AlphaInputs(right_side) - right_layout.addWidget(QtWidgets.QLabel("Alpha", right_side), row, 0) - right_layout.addWidget(alpha_inputs, 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 - right_layout.setRowStretch(row, 1) - - layout = QtWidgets.QHBoxLayout(self) - layout.setSpacing(20) - layout.addWidget(left_side, 1) - layout.addWidget(right_side, 0) + layout.setRowStretch(row, 1) color_view.set_color(color_triangle.cur_color) From c77094aeb6bd5666d791c4486834123f06780362 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 20:52:44 +0200 Subject: [PATCH 30/32] nicer view of color in setting ui --- .../tools/settings/settings/color_widget.py | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/tools/settings/settings/color_widget.py b/openpype/tools/settings/settings/color_widget.py index 7aa3a4bba3..fa0cd2c989 100644 --- a/openpype/tools/settings/settings/color_widget.py +++ b/openpype/tools/settings/settings/color_widget.py @@ -80,7 +80,7 @@ class ColorViewer(QtWidgets.QWidget): def checkerboard(self): if not self._checkerboard: - self._checkerboard = draw_checkerboard_tile() + self._checkerboard = draw_checkerboard_tile(self.height() / 4) return self._checkerboard def color(self): @@ -101,15 +101,21 @@ class ColorViewer(QtWidgets.QWidget): def paintEvent(self, event): rect = event.rect() - # Paint everything to pixmap as it has transparency - pix = QtGui.QPixmap(rect.width(), rect.height()) - pix_painter = QtGui.QPainter(pix) - pix_painter.drawTiledPixmap(rect, self.checkerboard()) - pix_painter.fillRect(rect, self.actual_color) - pix_painter.end() - painter = QtGui.QPainter(self) - painter.drawPixmap(rect, pix) + 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() From c4a7a3ac5013476cab7079c31b6d614422551bea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 21:16:46 +0200 Subject: [PATCH 31/32] added border to color viewer in the dialog --- openpype/widgets/color_widgets/color_view.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/openpype/widgets/color_widgets/color_view.py b/openpype/widgets/color_widgets/color_view.py index d6d7f0a666..8644281a1d 100644 --- a/openpype/widgets/color_widgets/color_view.py +++ b/openpype/widgets/color_widgets/color_view.py @@ -42,7 +42,7 @@ class ColorViewer(QtWidgets.QWidget): def checkerboard(self): if not self._checkerboard: - self._checkerboard = draw_checkerboard_tile() + self._checkerboard = draw_checkerboard_tile(4) return self._checkerboard def color(self): @@ -70,15 +70,14 @@ class ColorViewer(QtWidgets.QWidget): self.update() def paintEvent(self, event): - rect = event.rect() - - # Paint everything to pixmap as it has transparency - pix = QtGui.QPixmap(rect.width(), rect.height()) - pix_painter = QtGui.QPainter(pix) - pix_painter.drawTiledPixmap(rect, self.checkerboard()) - pix_painter.fillRect(rect, self.actual_color) - pix_painter.end() + clip_rect = event.rect() + rect = clip_rect.adjusted(0, 0, -1, -1) painter = QtGui.QPainter(self) - painter.drawPixmap(rect, pix) + 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() From 0d40c89f471f6abccfbe6ae58667bb5304086422 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 20 May 2021 21:18:16 +0200 Subject: [PATCH 32/32] removed unused variable --- openpype/widgets/color_widgets/color_picker_widget.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/widgets/color_widgets/color_picker_widget.py b/openpype/widgets/color_widgets/color_picker_widget.py index 27b9f8fc82..81ec1f87aa 100644 --- a/openpype/widgets/color_widgets/color_picker_widget.py +++ b/openpype/widgets/color_widgets/color_picker_widget.py @@ -20,8 +20,6 @@ class ColorPickerWidget(QtWidgets.QWidget): def __init__(self, color=None, parent=None): super(ColorPickerWidget, self).__init__(parent) - top_part = QtWidgets.QWidget(self) - # Color triangle color_triangle = QtColorTriangle(self)