From e5456fe55b1b22c5b8f97dd33201eaf5e28f705e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:40:05 +0200 Subject: [PATCH 1/9] initial commit of "NiceSlide" widget --- openpype/widgets/sliders.py | 138 ++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 openpype/widgets/sliders.py diff --git a/openpype/widgets/sliders.py b/openpype/widgets/sliders.py new file mode 100644 index 0000000000..2f26c3eb97 --- /dev/null +++ b/openpype/widgets/sliders.py @@ -0,0 +1,138 @@ +from Qt import QtWidgets, QtCore, QtGui + + +class NiceSlider(QtWidgets.QSlider): + def __init__(self, *args, **kwargs): + super(NiceSlider, self).__init__(*args, **kwargs) + self._mouse_clicked = False + self._handle_size = 0 + + self._bg_brush = QtGui.QBrush(QtGui.QColor("#21252B")) + self._fill_brush = QtGui.QBrush(QtGui.QColor("#5cadd6")) + + def mousePressEvent(self, event): + self._mouse_clicked = True + if event.button() == QtCore.Qt.LeftButton: + self._set_value_to_pos(event.pos()) + return event.accept() + return super(NiceSlider, self).mousePressEvent(event) + + def mouseMoveEvent(self, event): + if self._mouse_clicked: + self._set_value_to_pos(event.pos()) + + super(NiceSlider, self).mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + self._mouse_clicked = True + super(NiceSlider, self).mouseReleaseEvent(event) + + def _set_value_to_pos(self, pos): + if self.orientation() == QtCore.Qt.Horizontal: + self._set_value_to_pos_x(pos.x()) + else: + self._set_value_to_pos_y(pos.y()) + + def _set_value_to_pos_x(self, pos_x): + _range = self.maximum() - self.minimum() + handle_size = self._handle_size + half_handle = handle_size / 2 + pos_x -= half_handle + width = self.width() - handle_size + value = ((_range * pos_x) / width) + self.minimum() + self.setValue(value) + + def _set_value_to_pos_y(self, pos_y): + _range = self.maximum() - self.minimum() + handle_size = self._handle_size + half_handle = handle_size / 2 + pos_y = self.height() - pos_y - half_handle + height = self.height() - handle_size + value = (_range * pos_y / height) + self.minimum() + self.setValue(value) + + 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.HighQualityAntialiasing) + + horizontal = self.orientation() == QtCore.Qt.Horizontal + + rect = self.style().subControlRect( + QtWidgets.QStyle.CC_Slider, + opt, + QtWidgets.QStyle.SC_SliderGroove, + self + ) + + _range = self.maximum() - self.minimum() + if horizontal: + _handle_half = rect.height() / 2 + _handle_size = _handle_half * 2 + width = rect.width() - _handle_size + pos_x = ((width / _range) * self.value()) + pos_y = rect.center().y() - _handle_half + 1 + else: + _handle_half = rect.width() / 2 + _handle_size = _handle_half * 2 + height = rect.height() - _handle_size + pos_x = rect.center().x() - _handle_half + 1 + pos_y = height - ((height / _range) * self.value()) + + handle_rect = QtCore.QRect( + pos_x, pos_y, _handle_size, _handle_size + ) + + self._handle_size = _handle_size + _offset = 2 + _size = _handle_size - _offset + if horizontal: + if rect.height() > _size: + new_rect = QtCore.QRect(0, 0, rect.width(), _size) + center_point = QtCore.QPoint( + rect.center().x(), handle_rect.center().y() + ) + new_rect.moveCenter(center_point) + rect = new_rect + + ratio = rect.height() / 2 + fill_rect = QtCore.QRect( + rect.x(), + rect.y(), + handle_rect.right() - rect.x(), + rect.height() + ) + + else: + if rect.width() > _size: + new_rect = QtCore.QRect(0, 0, _size, rect.height()) + center_point = QtCore.QPoint( + handle_rect.center().x(), rect.center().y() + ) + new_rect.moveCenter(center_point) + rect = new_rect + + ratio = rect.width() / 2 + fill_rect = QtCore.QRect( + rect.x(), + handle_rect.y(), + rect.width(), + rect.height() - handle_rect.y(), + ) + + painter.save() + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(self._bg_brush) + painter.drawRoundedRect(rect, ratio, ratio) + + painter.setBrush(self._fill_brush) + painter.drawRoundedRect(fill_rect, ratio, ratio) + + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(self._fill_brush) + painter.drawEllipse(handle_rect) + painter.restore() From dd4c4342e0a36dff7ef7366ebf13849a79074aee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Aug 2021 11:33:00 +0200 Subject: [PATCH 2/9] recalculate relativelly offset by value --- openpype/widgets/sliders.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/widgets/sliders.py b/openpype/widgets/sliders.py index 2f26c3eb97..32ade58af5 100644 --- a/openpype/widgets/sliders.py +++ b/openpype/widgets/sliders.py @@ -70,18 +70,19 @@ class NiceSlider(QtWidgets.QSlider): ) _range = self.maximum() - self.minimum() + _offset = self.value() - self.minimum() if horizontal: _handle_half = rect.height() / 2 _handle_size = _handle_half * 2 width = rect.width() - _handle_size - pos_x = ((width / _range) * self.value()) + pos_x = ((width / _range) * _offset) pos_y = rect.center().y() - _handle_half + 1 else: _handle_half = rect.width() / 2 _handle_size = _handle_half * 2 height = rect.height() - _handle_size pos_x = rect.center().x() - _handle_half + 1 - pos_y = height - ((height / _range) * self.value()) + pos_y = height - ((height / _range) * _offset) handle_rect = QtCore.QRect( pos_x, pos_y, _handle_size, _handle_size From d6fc47d1c1ae6914b138f65878052dc4af874705 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 30 Aug 2021 11:34:51 +0200 Subject: [PATCH 3/9] added option to have sliders in number widgets --- openpype/settings/entities/input_entities.py | 3 ++ .../tools/settings/settings/item_widgets.py | 41 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 336d1f5c1e..f7e85294a2 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -369,6 +369,9 @@ class NumberEntity(InputEntity): self.valid_value_types = valid_value_types self.value_on_not_set = value_on_not_set + # UI specific attributes + self.show_slider = self.schema_data.get("show_slider", False) + def _convert_to_valid_type(self, value): if isinstance(value, str): new_value = None diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index d29fa6f42b..6f304a1f88 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -21,6 +21,7 @@ from .base import ( BaseWidget, InputWidget ) +from openpype.widgets.sliders import NiceSlider from openpype.tools.settings import CHILD_OFFSET @@ -377,6 +378,8 @@ class TextWidget(InputWidget): class NumberWidget(InputWidget): + _slider_widget = None + def _add_inputs_to_layout(self): kwargs = { "minimum": self.entity.minimum, @@ -384,14 +387,33 @@ class NumberWidget(InputWidget): "decimal": self.entity.decimal } self.input_field = NumberSpinBox(self.content_widget, **kwargs) + input_field_stretch = 1 + + if self.entity.show_slider: + slider_widget = NiceSlider(QtCore.Qt.Horizontal, self) + slider_widget.setRange( + self.entity.minimum, + self.entity.maximum + ) + + self.content_layout.addWidget(slider_widget, 1) + + slider_widget.valueChanged.connect(self._on_slider_change) + + self._slider_widget = slider_widget + + input_field_stretch = 0 self.setFocusProxy(self.input_field) - self.content_layout.addWidget(self.input_field, 1) + self.content_layout.addWidget(self.input_field, input_field_stretch) self.input_field.valueChanged.connect(self._on_value_change) self.input_field.focused_in.connect(self._on_input_focus) + self._ignore_slider_change = False + self._ignore_input_change = False + def _on_input_focus(self): self.focused_in() @@ -402,10 +424,25 @@ class NumberWidget(InputWidget): def set_entity_value(self): self.input_field.setValue(self.entity.value) + def _on_slider_change(self, new_value): + if self._ignore_slider_change: + return + + self._ignore_input_change = True + self.input_field.setValue(new_value) + self._ignore_input_change = False + def _on_value_change(self): if self.ignore_input_changes: return - self.entity.set(self.input_field.value()) + + value = self.input_field.value() + if self._slider_widget is not None and not self._ignore_input_change: + self._ignore_slider_change = True + self._slider_widget.setValue(value) + self._ignore_slider_change = False + + self.entity.set(value) class RawJsonInput(SettingsPlainTextEdit): From 25478317d619909422a0f16a5b5e792b52ec24ab Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 10:42:47 +0200 Subject: [PATCH 4/9] modified sizes of slider in style --- .../tools/settings/settings/style/style.css | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openpype/tools/settings/settings/style/style.css b/openpype/tools/settings/settings/style/style.css index 250c15063f..d9d85a481e 100644 --- a/openpype/tools/settings/settings/style/style.css +++ b/openpype/tools/settings/settings/style/style.css @@ -114,6 +114,30 @@ QPushButton[btn-type="expand-toggle"] { background: #21252B; } +/* SLider */ +QSlider::groove { + border: 1px solid #464b54; + border-radius: 0.3em; +} +QSlider::groove:horizontal { + height: 8px; +} +QSlider::groove:vertical { + width: 8px; +} +QSlider::handle { + width: 10px; + height: 10px; + + border-radius: 5px; +} +QSlider::handle:horizontal { + margin: -2px 0; +} +QSlider::handle:vertical { + margin: 0 -2px; +} + #GroupWidget { border-bottom: 1px solid #21252B; } From ff4ed83519c89473925999b7d749b31405187365 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 10:58:50 +0200 Subject: [PATCH 5/9] added slider multiplier as slider can't handle decimal places --- openpype/tools/settings/settings/item_widgets.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 6f304a1f88..1f74308211 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -389,11 +389,13 @@ class NumberWidget(InputWidget): self.input_field = NumberSpinBox(self.content_widget, **kwargs) input_field_stretch = 1 + self._slider_multiplier = 10 ** self.entity.decimal if self.entity.show_slider: + slider_widget = NiceSlider(QtCore.Qt.Horizontal, self) slider_widget.setRange( - self.entity.minimum, - self.entity.maximum + int(self.entity.minimum * self._slider_multiplier), + int(self.entity.maximum * self._slider_multiplier) ) self.content_layout.addWidget(slider_widget, 1) @@ -429,7 +431,7 @@ class NumberWidget(InputWidget): return self._ignore_input_change = True - self.input_field.setValue(new_value) + self.input_field.setValue(new_value / self._slider_multiplier) self._ignore_input_change = False def _on_value_change(self): From 00d1ae5d43500116f735522e5db79b8a92ca777b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 10:58:59 +0200 Subject: [PATCH 6/9] added show_slider to examples --- .../entities/schemas/system_schema/example_schema.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index f633d5cb1a..af6a2d49f4 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -183,6 +183,15 @@ "minimum": -10, "maximum": -5 }, + { + "type": "number", + "key": "number_with_slider", + "label": "Number with slider", + "decimal": 2, + "minimum": 0.0, + "maximum": 1.0, + "show_slider": true + }, { "type": "text", "key": "singleline_text", From 8f5254ff234e7bd88298bcb230b1cd44252f1d7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 11:00:44 +0200 Subject: [PATCH 7/9] added show slider to readme --- openpype/settings/entities/schemas/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 2034d4e463..2709f5bed9 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -300,6 +300,7 @@ How output of the schema could look like on save: - key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`) - key `"minimum"` as minimum allowed number to enter (Default: `-99999`) - key `"maxium"` as maximum allowed number to enter (Default: `99999`) +- for UI it is possible to show slider to enable this option set `show_slider` to `true` ``` { "type": "number", @@ -311,6 +312,18 @@ How output of the schema could look like on save: } ``` +``` +{ + "type": "number", + "key": "ratio", + "label": "Ratio" + "decimal": 3, + "minimum": 0, + "maximum": 1, + "show_slider": true +} +``` + ### text - simple text input - key `"multiline"` allows to enter multiple lines of text (Default: `False`) From b8608bccaeaba9810473d6516124ea5d14b04cc6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 11:10:49 +0200 Subject: [PATCH 8/9] added explaining comment to slider multiplier --- openpype/tools/settings/settings/item_widgets.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 1f74308211..a7b1208269 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -389,13 +389,15 @@ class NumberWidget(InputWidget): self.input_field = NumberSpinBox(self.content_widget, **kwargs) input_field_stretch = 1 - self._slider_multiplier = 10 ** self.entity.decimal + slider_multiplier = 1 if self.entity.show_slider: - + # Slider can't handle float numbers so all decimals are converted + # to integer range. + slider_multiplier = 10 ** self.entity.decimal slider_widget = NiceSlider(QtCore.Qt.Horizontal, self) slider_widget.setRange( - int(self.entity.minimum * self._slider_multiplier), - int(self.entity.maximum * self._slider_multiplier) + int(self.entity.minimum * slider_multiplier), + int(self.entity.maximum * slider_multiplier) ) self.content_layout.addWidget(slider_widget, 1) @@ -406,6 +408,8 @@ class NumberWidget(InputWidget): input_field_stretch = 0 + self._slider_multiplier = slider_multiplier + self.setFocusProxy(self.input_field) self.content_layout.addWidget(self.input_field, input_field_stretch) From 3628ab8904b397fb71484e294c0d706bb22c8eda Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 7 Sep 2021 13:51:53 +0200 Subject: [PATCH 9/9] fix changing of slider value from input field --- openpype/tools/settings/settings/item_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index a7b1208269..3b1fc061ec 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -445,7 +445,7 @@ class NumberWidget(InputWidget): value = self.input_field.value() if self._slider_widget is not None and not self._ignore_input_change: self._ignore_slider_change = True - self._slider_widget.setValue(value) + self._slider_widget.setValue(value * self._slider_multiplier) self._ignore_slider_change = False self.entity.set(value)