mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
540 lines
17 KiB
Python
540 lines
17 KiB
Python
from math import floor, sqrt, ceil
|
|
from qtpy import QtWidgets, QtCore, QtGui
|
|
|
|
from openpype.style import get_objected_colors
|
|
|
|
|
|
class NiceCheckbox(QtWidgets.QFrame):
|
|
stateChanged = QtCore.Signal(int)
|
|
clicked = QtCore.Signal()
|
|
|
|
_checked_bg_color = None
|
|
_unchecked_bg_color = None
|
|
_checker_color = None
|
|
_checker_hover_color = None
|
|
|
|
def __init__(self, checked=False, draw_icons=False, parent=None):
|
|
super(NiceCheckbox, self).__init__(parent)
|
|
|
|
self.setObjectName("NiceCheckbox")
|
|
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
|
self.setSizePolicy(
|
|
QtWidgets.QSizePolicy.Fixed,
|
|
QtWidgets.QSizePolicy.Fixed
|
|
)
|
|
self._checked = checked
|
|
if checked:
|
|
checkstate = QtCore.Qt.Checked
|
|
else:
|
|
checkstate = QtCore.Qt.Unchecked
|
|
self._checkstate = checkstate
|
|
self._is_tristate = False
|
|
|
|
self._draw_icons = draw_icons
|
|
|
|
self._animation_timer = QtCore.QTimer(self)
|
|
self._animation_timeout = 6
|
|
|
|
self._fixed_width_set = False
|
|
self._fixed_height_set = False
|
|
|
|
self._current_step = None
|
|
self._steps = 21
|
|
self._middle_step = 11
|
|
self.set_steps(self._steps)
|
|
|
|
self._checker_margins_divider = 0
|
|
|
|
self._pressed = False
|
|
self._under_mouse = False
|
|
|
|
self.icon_scale_factor = sqrt(2) / 2
|
|
|
|
icon_path_stroker = QtGui.QPainterPathStroker()
|
|
icon_path_stroker.setCapStyle(QtCore.Qt.RoundCap)
|
|
icon_path_stroker.setJoinStyle(QtCore.Qt.RoundJoin)
|
|
|
|
self.icon_path_stroker = icon_path_stroker
|
|
|
|
self._animation_timer.timeout.connect(self._on_animation_timeout)
|
|
|
|
self._base_size = QtCore.QSize(90, 50)
|
|
self._load_colors()
|
|
|
|
@classmethod
|
|
def _load_colors(cls):
|
|
if cls._checked_bg_color is not None:
|
|
return
|
|
|
|
colors_info = get_objected_colors("nice-checkbox")
|
|
|
|
cls._checked_bg_color = colors_info["bg-checked"].get_qcolor()
|
|
cls._unchecked_bg_color = colors_info["bg-unchecked"].get_qcolor()
|
|
|
|
cls._checker_color = colors_info["bg-checker"].get_qcolor()
|
|
cls._checker_hover_color = colors_info["bg-checker-hover"].get_qcolor()
|
|
|
|
@property
|
|
def checked_bg_color(self):
|
|
return self._checked_bg_color
|
|
|
|
@property
|
|
def unchecked_bg_color(self):
|
|
return self._unchecked_bg_color
|
|
|
|
@property
|
|
def checker_color(self):
|
|
return self._checker_color
|
|
|
|
@property
|
|
def checker_hover_color(self):
|
|
return self._checker_hover_color
|
|
|
|
def setTristate(self, tristate=True):
|
|
if self._is_tristate != tristate:
|
|
self._is_tristate = tristate
|
|
|
|
def set_draw_icons(self, draw_icons=None):
|
|
if draw_icons is None:
|
|
draw_icons = not self._draw_icons
|
|
|
|
if draw_icons == self._draw_icons:
|
|
return
|
|
|
|
self._draw_icons = draw_icons
|
|
self.repaint()
|
|
|
|
def sizeHint(self):
|
|
height = self.fontMetrics().height()
|
|
width = self.get_width_hint_by_height(height)
|
|
return QtCore.QSize(width, height)
|
|
|
|
def get_width_hint_by_height(self, height):
|
|
return int((
|
|
float(height) / self._base_size.height()
|
|
) * self._base_size.width())
|
|
|
|
def get_height_hint_by_width(self, width):
|
|
return int((
|
|
float(width) / self._base_size.width()
|
|
) * self._base_size.height())
|
|
|
|
def setFixedHeight(self, *args, **kwargs):
|
|
self._fixed_height_set = True
|
|
super(NiceCheckbox, self).setFixedHeight(*args, **kwargs)
|
|
if not self._fixed_width_set:
|
|
width = self.get_width_hint_by_height(self.height())
|
|
self.setFixedWidth(width)
|
|
|
|
def setFixedWidth(self, *args, **kwargs):
|
|
self._fixed_width_set = True
|
|
super(NiceCheckbox, self).setFixedWidth(*args, **kwargs)
|
|
if not self._fixed_height_set:
|
|
height = self.get_height_hint_by_width(self.width())
|
|
self.setFixedHeight(height)
|
|
|
|
def setFixedSize(self, *args, **kwargs):
|
|
self._fixed_height_set = True
|
|
self._fixed_width_set = True
|
|
super(NiceCheckbox, self).setFixedSize(*args, **kwargs)
|
|
|
|
def steps(self):
|
|
return self._steps
|
|
|
|
def set_steps(self, steps):
|
|
if steps < 2:
|
|
steps = 2
|
|
|
|
# Make sure animation is stopped
|
|
if self._animation_timer.isActive():
|
|
self._animation_timer.stop()
|
|
|
|
# Set steps and set current step by current checkstate
|
|
self._steps = steps
|
|
diff = steps % 2
|
|
self._middle_step = (int(steps - diff) / 2) + diff
|
|
if self._checkstate == QtCore.Qt.Checked:
|
|
self._current_step = self._steps
|
|
elif self._checkstate == QtCore.Qt.Unchecked:
|
|
self._current_step = 0
|
|
else:
|
|
self._current_step = self._middle_step
|
|
|
|
def checkState(self):
|
|
return self._checkstate
|
|
|
|
def isChecked(self):
|
|
return self._checked
|
|
|
|
def _checkstate_int_to_enum(self, state):
|
|
if not isinstance(state, int):
|
|
return state
|
|
|
|
if state == 2:
|
|
return QtCore.Qt.Checked
|
|
if state == 1:
|
|
return QtCore.Qt.PartiallyChecked
|
|
return QtCore.Qt.Unchecked
|
|
|
|
def _checkstate_enum_to_int(self, state):
|
|
if isinstance(state, int):
|
|
return state
|
|
if state == QtCore.Qt.Checked:
|
|
return 2
|
|
if state == QtCore.Qt.PartiallyChecked:
|
|
return 1
|
|
return 0
|
|
|
|
def setCheckState(self, state):
|
|
state = self._checkstate_int_to_enum(state)
|
|
if self._checkstate == state:
|
|
return
|
|
|
|
self._checkstate = state
|
|
if state == QtCore.Qt.Checked:
|
|
self._checked = True
|
|
elif state == QtCore.Qt.Unchecked:
|
|
self._checked = False
|
|
|
|
self.stateChanged.emit(self._checkstate_enum_to_int(self.checkState()))
|
|
|
|
if self._animation_timer.isActive():
|
|
self._animation_timer.stop()
|
|
|
|
if self.isVisible() and self.isEnabled():
|
|
# Start animation
|
|
self._animation_timer.start(self._animation_timeout)
|
|
else:
|
|
# Do not animate change if is disabled
|
|
if state == QtCore.Qt.Checked:
|
|
self._current_step = self._steps
|
|
elif state == QtCore.Qt.Unchecked:
|
|
self._current_step = 0
|
|
else:
|
|
self._current_step = self._middle_step
|
|
self.repaint()
|
|
|
|
def setChecked(self, checked):
|
|
if checked == self._checked:
|
|
return
|
|
|
|
if checked:
|
|
checkstate = QtCore.Qt.Checked
|
|
else:
|
|
checkstate = QtCore.Qt.Unchecked
|
|
|
|
self.setCheckState(checkstate)
|
|
|
|
def nextCheckState(self):
|
|
if self._checkstate == QtCore.Qt.Unchecked:
|
|
if self._is_tristate:
|
|
return QtCore.Qt.PartiallyChecked
|
|
return QtCore.Qt.Checked
|
|
|
|
if self._checkstate == QtCore.Qt.Checked:
|
|
return QtCore.Qt.Unchecked
|
|
|
|
if self._checked:
|
|
return QtCore.Qt.Unchecked
|
|
return QtCore.Qt.Checked
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.buttons() & QtCore.Qt.LeftButton:
|
|
self._pressed = True
|
|
self.repaint()
|
|
super(NiceCheckbox, self).mousePressEvent(event)
|
|
|
|
def mouseReleaseEvent(self, event):
|
|
if self._pressed and not event.buttons() & QtCore.Qt.LeftButton:
|
|
self._pressed = False
|
|
if self.rect().contains(event.pos()):
|
|
self.setCheckState(self.nextCheckState())
|
|
self.clicked.emit()
|
|
event.accept()
|
|
return
|
|
super(NiceCheckbox, self).mouseReleaseEvent(event)
|
|
|
|
def mouseMoveEvent(self, event):
|
|
if self._pressed:
|
|
under_mouse = self.rect().contains(event.pos())
|
|
if under_mouse != self._under_mouse:
|
|
self._under_mouse = under_mouse
|
|
self.repaint()
|
|
|
|
super(NiceCheckbox, self).mouseMoveEvent(event)
|
|
|
|
def enterEvent(self, event):
|
|
self._under_mouse = True
|
|
if self.isEnabled():
|
|
self.repaint()
|
|
super(NiceCheckbox, self).enterEvent(event)
|
|
|
|
def leaveEvent(self, event):
|
|
self._under_mouse = False
|
|
if self.isEnabled():
|
|
self.repaint()
|
|
super(NiceCheckbox, self).leaveEvent(event)
|
|
|
|
def _on_animation_timeout(self):
|
|
if self._checkstate == QtCore.Qt.Checked:
|
|
if self._current_step == self._steps:
|
|
self._animation_timer.stop()
|
|
return
|
|
self._current_step += 1
|
|
|
|
elif self._checkstate == QtCore.Qt.Unchecked:
|
|
if self._current_step == 0:
|
|
self._animation_timer.stop()
|
|
return
|
|
self._current_step -= 1
|
|
|
|
else:
|
|
if self._current_step < self._middle_step:
|
|
self._current_step += 1
|
|
|
|
elif self._current_step > self._middle_step:
|
|
self._current_step -= 1
|
|
|
|
if self._current_step == self._middle_step:
|
|
self._animation_timer.stop()
|
|
|
|
self.repaint()
|
|
|
|
@staticmethod
|
|
def steped_color(color1, color2, offset_ratio):
|
|
red_dif = (
|
|
color1.red() - color2.red()
|
|
)
|
|
green_dif = (
|
|
color1.green() - color2.green()
|
|
)
|
|
blue_dif = (
|
|
color1.blue() - color2.blue()
|
|
)
|
|
red = int(color2.red() + (
|
|
red_dif * offset_ratio
|
|
))
|
|
green = int(color2.green() + (
|
|
green_dif * offset_ratio
|
|
))
|
|
blue = int(color2.blue() + (
|
|
blue_dif * offset_ratio
|
|
))
|
|
|
|
return QtGui.QColor(red, green, blue)
|
|
|
|
def paintEvent(self, event):
|
|
frame_rect = QtCore.QRect(self.rect())
|
|
if frame_rect.width() < 0 or frame_rect.height() < 0:
|
|
return
|
|
|
|
painter = QtGui.QPainter(self)
|
|
|
|
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
|
|
|
# Draw inner background
|
|
if self._current_step == self._steps:
|
|
bg_color = self.checked_bg_color
|
|
|
|
elif self._current_step == 0:
|
|
bg_color = self.unchecked_bg_color
|
|
|
|
else:
|
|
offset_ratio = float(self._current_step) / self._steps
|
|
# Animation bg
|
|
bg_color = self.steped_color(
|
|
self.checked_bg_color,
|
|
self.unchecked_bg_color,
|
|
offset_ratio
|
|
)
|
|
|
|
margins_ratio = float(self._checker_margins_divider)
|
|
if margins_ratio > 0:
|
|
size_without_margins = int(
|
|
(float(frame_rect.height()) / margins_ratio)
|
|
* (margins_ratio - 2)
|
|
)
|
|
size_without_margins -= size_without_margins % 2
|
|
margin_size_c = ceil(
|
|
frame_rect.height() - size_without_margins
|
|
) / 2
|
|
|
|
else:
|
|
size_without_margins = frame_rect.height()
|
|
margin_size_c = 0
|
|
|
|
checkbox_rect = QtCore.QRect(
|
|
frame_rect.x() + margin_size_c,
|
|
frame_rect.y() + margin_size_c,
|
|
frame_rect.width() - (margin_size_c * 2),
|
|
frame_rect.height() - (margin_size_c * 2)
|
|
)
|
|
|
|
if checkbox_rect.width() > checkbox_rect.height():
|
|
radius = floor(checkbox_rect.height() * 0.5)
|
|
else:
|
|
radius = floor(checkbox_rect.width() * 0.5)
|
|
|
|
painter.setPen(QtCore.Qt.transparent)
|
|
painter.setBrush(bg_color)
|
|
painter.drawRoundedRect(checkbox_rect, radius, radius)
|
|
|
|
# Draw checker
|
|
checker_size = size_without_margins - (margin_size_c * 2)
|
|
area_width = (
|
|
checkbox_rect.width()
|
|
- (margin_size_c * 2)
|
|
- checker_size
|
|
)
|
|
if self._current_step == 0:
|
|
x_offset = 0
|
|
else:
|
|
x_offset = (float(area_width) / self._steps) * self._current_step
|
|
|
|
pos_x = checkbox_rect.x() + x_offset + margin_size_c
|
|
pos_y = checkbox_rect.y() + margin_size_c
|
|
|
|
checker_rect = QtCore.QRect(pos_x, pos_y, checker_size, checker_size)
|
|
|
|
under_mouse = self.isEnabled() and self._under_mouse
|
|
if under_mouse:
|
|
checker_color = self.checker_hover_color
|
|
else:
|
|
checker_color = self.checker_color
|
|
|
|
painter.setBrush(checker_color)
|
|
painter.drawEllipse(checker_rect)
|
|
|
|
if self._draw_icons:
|
|
painter.setBrush(bg_color)
|
|
icon_path = self._get_icon_path(painter, checker_rect)
|
|
painter.drawPath(icon_path)
|
|
|
|
# Draw shadow overlay
|
|
if not self.isEnabled():
|
|
level = 33
|
|
alpha = 127
|
|
painter.setPen(QtCore.Qt.transparent)
|
|
painter.setBrush(QtGui.QColor(level, level, level, alpha))
|
|
painter.drawRoundedRect(checkbox_rect, radius, radius)
|
|
|
|
painter.end()
|
|
|
|
def _get_icon_path(self, painter, checker_rect):
|
|
self.icon_path_stroker.setWidth(checker_rect.height() / 5)
|
|
|
|
if self._current_step == self._steps:
|
|
return self._get_enabled_icon_path(painter, checker_rect)
|
|
|
|
if self._current_step == 0:
|
|
return self._get_disabled_icon_path(painter, checker_rect)
|
|
|
|
if self._current_step == self._middle_step:
|
|
return self._get_middle_circle_path(painter, checker_rect)
|
|
|
|
disabled_step = self._steps - self._current_step
|
|
enabled_step = self._steps - disabled_step
|
|
half_steps = self._steps + 1 - ((self._steps + 1) % 2)
|
|
if enabled_step > disabled_step:
|
|
return self._get_enabled_icon_path(
|
|
painter, checker_rect, enabled_step, half_steps
|
|
)
|
|
else:
|
|
return self._get_disabled_icon_path(
|
|
painter, checker_rect, disabled_step, half_steps
|
|
)
|
|
|
|
def _get_middle_circle_path(self, painter, checker_rect):
|
|
width = self.icon_path_stroker.width()
|
|
path = QtGui.QPainterPath()
|
|
path.addEllipse(checker_rect.center(), width, width)
|
|
|
|
return path
|
|
|
|
def _get_enabled_icon_path(
|
|
self, painter, checker_rect, step=None, half_steps=None
|
|
):
|
|
fifteenth = float(checker_rect.height()) / 15
|
|
# Left point
|
|
p1 = QtCore.QPoint(
|
|
int(checker_rect.x() + (5 * fifteenth)),
|
|
int(checker_rect.y() + (9 * fifteenth))
|
|
)
|
|
# Middle bottom point
|
|
p2 = QtCore.QPoint(
|
|
checker_rect.center().x(),
|
|
int(checker_rect.y() + (11 * fifteenth))
|
|
)
|
|
# Top right point
|
|
p3 = QtCore.QPoint(
|
|
int(checker_rect.x() + (10 * fifteenth)),
|
|
int(checker_rect.y() + (5 * fifteenth))
|
|
)
|
|
if step is not None:
|
|
multiplier = (half_steps - step)
|
|
|
|
p1c = p1 - checker_rect.center()
|
|
p2c = p2 - checker_rect.center()
|
|
p3c = p3 - checker_rect.center()
|
|
|
|
p1o = QtCore.QPoint(
|
|
int((float(p1c.x()) / half_steps) * multiplier),
|
|
int((float(p1c.y()) / half_steps) * multiplier)
|
|
)
|
|
p2o = QtCore.QPoint(
|
|
int((float(p2c.x()) / half_steps) * multiplier),
|
|
int((float(p2c.y()) / half_steps) * multiplier)
|
|
)
|
|
p3o = QtCore.QPoint(
|
|
int((float(p3c.x()) / half_steps) * multiplier),
|
|
int((float(p3c.y()) / half_steps) * multiplier)
|
|
)
|
|
|
|
p1 -= p1o
|
|
p2 -= p2o
|
|
p3 -= p3o
|
|
|
|
path = QtGui.QPainterPath(p1)
|
|
path.lineTo(p2)
|
|
path.lineTo(p3)
|
|
|
|
return self.icon_path_stroker.createStroke(path)
|
|
|
|
def _get_disabled_icon_path(
|
|
self, painter, checker_rect, step=None, half_steps=None
|
|
):
|
|
center_point = QtCore.QPointF(
|
|
float(checker_rect.width()) / 2,
|
|
float(checker_rect.height()) / 2
|
|
)
|
|
offset = float((
|
|
(center_point + QtCore.QPointF(0, 0)) / 2
|
|
).x()) / 4 * 5
|
|
if step is not None:
|
|
diff = center_point.x() - offset
|
|
diff_offset = (diff / half_steps) * (half_steps - step)
|
|
offset += diff_offset
|
|
|
|
line1_p1 = QtCore.QPointF(
|
|
checker_rect.topLeft().x() + offset,
|
|
checker_rect.topLeft().y() + offset,
|
|
)
|
|
line1_p2 = QtCore.QPointF(
|
|
checker_rect.bottomRight().x() - offset,
|
|
checker_rect.bottomRight().y() - offset
|
|
)
|
|
line2_p1 = QtCore.QPointF(
|
|
checker_rect.bottomLeft().x() + offset,
|
|
checker_rect.bottomLeft().y() - offset
|
|
)
|
|
line2_p2 = QtCore.QPointF(
|
|
checker_rect.topRight().x() - offset,
|
|
checker_rect.topRight().y() + offset
|
|
)
|
|
path = QtGui.QPainterPath()
|
|
path.moveTo(line1_p1)
|
|
path.lineTo(line1_p2)
|
|
path.moveTo(line2_p1)
|
|
path.lineTo(line2_p2)
|
|
|
|
return self.icon_path_stroker.createStroke(path)
|