diff --git a/source/frontend/carla_skin.py b/source/frontend/carla_skin.py index 2eaba1ccd..b7fbeab61 100644 --- a/source/frontend/carla_skin.py +++ b/source/frontend/carla_skin.py @@ -6,6 +6,7 @@ # Imports (Global) from qt_compat import qt_config +import math if qt_config == 5: from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer @@ -573,19 +574,20 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): paramInfo = self.host.get_parameter_info(self.fPluginId, i) paramData = self.host.get_parameter_data(self.fPluginId, i) paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) + isBoolean = (paramData['hints'] & PARAMETER_IS_BOOLEAN) != 0 isInteger = (paramData['hints'] & PARAMETER_IS_INTEGER) != 0 if paramData['type'] != PARAMETER_INPUT: continue - if paramData['hints'] & PARAMETER_IS_BOOLEAN: - continue + # if paramData['hints'] & PARAMETER_IS_BOOLEAN: + # continue if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0: continue if (paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0 and not isInteger: # NOTE: we assume integer scalepoints are continuous continue - if isInteger and paramRanges['max']-paramRanges['min'] <= 3: - continue + # if isInteger and paramRanges['max']-paramRanges['min'] <= 3: + # continue if paramInfo['name'].startswith("unused"): continue @@ -597,8 +599,26 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): widget.setMaximum(paramRanges['max']) widget.hide() - if isInteger: - widget.setPrecision(paramRanges['max']-paramRanges['min'], True) + delta = paramRanges['max']-paramRanges['min'] + if delta <= 0: + print("ERROR: Parameter "+str(i)+": Max and Min are same or wrong.") + return + + if isBoolean: # Mimic as [0 or 1] integer + widget.setPrecision(1, True) + + elif isInteger: + while delta > 50: + delta = int(math.ceil(delta / 2)) + widget.setPrecision(delta, True) + + else: # Floats are need to be more fine-step smoothed + delta = paramRanges['max']-paramRanges['min'] + while delta > 500: + delta = delta / 2.0 + while delta < 250: + delta = delta * 2.0 + widget.setPrecision(math.ceil(delta), False) setScalableDialStyle(widget, i, parameterCount, whiteLabels, self.fSkinStyle) diff --git a/source/frontend/widgets/commondial.py b/source/frontend/widgets/commondial.py index a615cdeae..645330868 100644 --- a/source/frontend/widgets/commondial.py +++ b/source/frontend/widgets/commondial.py @@ -61,6 +61,7 @@ class CommonDial(QDial): self.fRealValue = 0.0 self.fPrecision = 10000 self.fIsInteger = False + self.fIsButton = False self.fIsHovered = False self.fIsPressed = False @@ -126,7 +127,11 @@ class CommonDial(QDial): def setPrecision(self, value, isInteger): self.fPrecision = value self.fIsInteger = isInteger + # NOTE: Booleans are mimic as isInteger with range [0 or 1]. + if self.fIsInteger and (self.fMinimum == 0) and (self.fMaximum in (1, 2)): + self.fIsButton = True QDial.setMaximum(self, int(value)) + # print("setPrecision "+str(value)+" "+str(isInteger)+" "+str(self.fIsButton)) def setMinimum(self, value): self.fMinimum = value @@ -189,6 +194,9 @@ class CommonDial(QDial): self.realValueChanged.emit(self.fRealValue) def enterEvent(self, event): + + self.setFocus(Qt.MouseFocusReason) + self.fIsHovered = True if self.fHoverStep == self.HOVER_MIN: self.fHoverStep = self.HOVER_MIN + 1 @@ -206,10 +214,16 @@ class CommonDial(QDial): return if event.button() == Qt.LeftButton: - self.fIsPressed = True - self.fLastDragPos = event.pos() - self.fLastDragValue = self.fRealValue - self.dragStateChanged.emit(True) + if self.fIsButton: + value = self.value() + 1; + if (value > self.fMaximum): + value = 0; + self.setValue(value, True) + else: + self.fIsPressed = True + self.fLastDragPos = event.pos() + self.fLastDragValue = self.fRealValue + self.dragStateChanged.emit(True) def mouseMoveEvent(self, event): if self.fDialMode == self.MODE_DEFAULT: @@ -243,6 +257,62 @@ class CommonDial(QDial): self.fIsPressed = False self.dragStateChanged.emit(False) + def wheelEvent(self, event): + direction = event.angleDelta().y() + mod = int(event.modifiers()) + if direction < 0: + delta = -1.0 + elif direction > 0: + delta = 1.0 + else: + return + + if self.fIsButton: + self.setValue(self.rvalue() + delta, True) + return + + if self.fIsInteger: # max. 50 ticks per revolution + if (mod & Qt.ShiftModifier): + delta = delta * 5 + else: # Floats are 250 to 500 ticks per revolution + if (mod & Qt.ControlModifier): + delta = delta * 2 + elif (mod & Qt.ShiftModifier): + delta = delta * 50 + else: + delta = delta * 10 + self.setValue(self.rvalue() + float(self.fMaximum-self.fMinimum)*(float(delta)/float(self.fPrecision)), True) + + return + + def keyPressEvent(self, event): + key = event.key() + mod = int(event.modifiers()) + # print(str(self.value())+" "+str(self.rvalue())+" "+str(self.fMinimum)+" "+str(self.fMaximum)) + + match key: + case key if Qt.Key_0 <= key <= Qt.Key_9: + if self.fIsButton: + self.setValue(key-Qt.Key_0, True) + else: + self.setValue(self.fMinimum + float(self.fMaximum-self.fMinimum)/10.0*(key-Qt.Key_0), True) + case Qt.Key_Home: # NOTE: interferes with Canvas control hotkey + self.setValue(self.fMinimum, True) + case Qt.Key_End: + self.setValue(self.fMaximum, True) + case Qt.Key_PageDown: + if self.fIsButton: + self.setValue(self.rvalue() - 1, True) + else: + self.setValue(self.rvalue() - float(self.fMaximum-self.fMinimum)/10.0, True) + case Qt.Key_PageUp: + if self.fIsButton: + self.setValue(self.rvalue() + 1, True) + else: + self.setValue(self.rvalue() + float(self.fMaximum-self.fMinimum)/10.0, True) + + return + def paintEvent(self, event): painter = QPainter(self) event.accept() @@ -260,7 +330,11 @@ class CommonDial(QDial): painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1]) painter.drawText(self.fLabelPos, self.fLabel) - self.paintDial(painter) + if self.fIsButton: + # Only 0 or 1 values (normal 2-pos. switch) or 0, 1 and 2 (3-pos. one). + self.paintButton(painter, self.fMaximum) + else: + self.paintDial(painter) painter.restore() diff --git a/source/frontend/widgets/scalabledial.py b/source/frontend/widgets/scalabledial.py index 0d842c738..025955d6f 100644 --- a/source/frontend/widgets/scalabledial.py +++ b/source/frontend/widgets/scalabledial.py @@ -5,18 +5,17 @@ # --------------------------------------------------------------------------------------------------------------------- # Imports (Global) -from math import cos, floor, pi, sin +from math import cos, floor, pi, sin, copysign +import numpy as np from qt_compat import qt_config if qt_config == 5: from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize - from PyQt5.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap - from PyQt5.QtSvg import QSvgWidget + from PyQt5.QtGui import QColor, QLinearGradient, QRadialGradient, QConicalGradient, QFontMetrics, QPen elif qt_config == 6: from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize - from PyQt6.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap - from PyQt6.QtSvgWidgets import QSvgWidget + from PyQt6.QtGui import QColor, QLinearGradient, QRadialGradient, QConicalGradient, QFontMetrics, QPen from .commondial import CommonDial from carla_shared import fontMetricsHorizontalAdvance @@ -27,44 +26,14 @@ from carla_shared import fontMetricsHorizontalAdvance class ScalableDial(CommonDial): def __init__(self, parent, index=0): CommonDial.__init__(self, parent, index) - - self.fImage = QSvgWidget(":/scalable/dial_03.svg") - self.fImageNum = "01" - - if self.fImage.sizeHint().width() > self.fImage.sizeHint().height(): - self.fImageOrientation = self.HORIZONTAL - else: - self.fImageOrientation = self.VERTICAL - - self.updateSizes() + self.setImage(0) def getBaseSize(self): return self.fImageBaseSize def updateSizes(self): - if isinstance(self.fImage, QPixmap): - self.fImageWidth = self.fImage.width() - self.fImageHeight = self.fImage.height() - else: - self.fImageWidth = self.fImage.sizeHint().width() - self.fImageHeight = self.fImage.sizeHint().height() - - if self.fImageWidth < 1: - self.fImageWidth = 1 - - if self.fImageHeight < 1: - self.fImageHeight = 1 - - if self.fImageOrientation == self.HORIZONTAL: - self.fImageBaseSize = self.fImageHeight - self.fImageLayersCount = self.fImageWidth / self.fImageHeight - else: - self.fImageBaseSize = self.fImageWidth - self.fImageLayersCount = self.fImageHeight / self.fImageWidth - self.setMinimumSize(self.fImageBaseSize, self.fImageBaseSize + self.fLabelHeight + 5) self.setMaximumSize(self.fImageBaseSize, self.fImageBaseSize + self.fLabelHeight + 5) - if not self.fLabel: self.fLabelHeight = 0 self.fLabelWidth = 0 @@ -76,9 +45,9 @@ class ScalableDial(CommonDial): self.fLabelPos.setX(float(self.fImageBaseSize)/2.0 - float(self.fLabelWidth)/2.0) - if self.fImageNum in ("01", "02", "07", "08", "09", "10"): + if self.fImageId in (1, 2, 7, 8, 9, 10): self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight) - elif self.fImageNum in ("11",): + elif self.fImageId in (11,): self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight*2/3) else: self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight/2) @@ -90,46 +59,33 @@ class ScalableDial(CommonDial): float(self.fImageBaseSize*3)/4.0, self.fImageBaseSize+self.fLabelHeight+5) def setImage(self, imageId): - self.fImageNum = "%02i" % imageId - if imageId in (2,6,7,8,9,10,11,12,13): - img = ":/bitmaps/dial_%s%s.png" % (self.fImageNum, "" if self.isEnabled() else "d") - else: - img = ":/scalable/dial_%s%s.svg" % (self.fImageNum, "" if self.isEnabled() else "d") - - if img.endswith(".png"): - if not isinstance(self.fImage, QPixmap): - self.fImage = QPixmap() - else: - if not isinstance(self.fImage, QSvgWidget): - self.fImage = QSvgWidget() - - self.fImage.load(img) + self.fImageId = imageId + self.fImageBaseSize = 30 # internal + if (imageId == 0): + return - if self.fImage.width() > self.fImage.height(): - self.fImageOrientation = self.HORIZONTAL - else: - self.fImageOrientation = self.VERTICAL + if imageId in (7, 8, 9, 10): # calf + self.fImageBaseSize = 40 + elif imageId in (11, 12, 13): # openav + self.fImageBaseSize = 32 # special svgs if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: # reserved for carla-wet, carla-vol, carla-pan and color - if self.fImageNum == "03": + if self.fImageId == 3: self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_COLOR # reserved for carla-L and carla-R - elif self.fImageNum == "04": + elif self.fImageId == 4: self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_CARLA_L - - # reserved for zita - elif self.fImageNum == "06": - self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_ZITA + self.fImageBaseSize = 26 self.updateSizes() self.update() @pyqtSlot() def slot_updateImage(self): - self.setImage(int(self.fImageNum)) + self.setImage(int(self.fImageId)) def minimumSizeHint(self): return QSize(self.fImageBaseSize, self.fImageBaseSize) @@ -144,149 +100,307 @@ class ScalableDial(CommonDial): if event.type() == QEvent.EnabledChange: self.slot_updateImage() - def paintDial(self, painter): + def checkEnabled(self): if self.isEnabled(): - normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) - curLayer = int((self.fImageLayersCount - 1) * normValue) + if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX: + self.fHoverStep += 1 if self.fIsHovered else -1 + QTimer.singleShot(20, self.update) + return 1 + else: + return 0 + + def normValue(self): + return float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) + + def drawMark(self, painter, X, Y, r1, r2, angle, width, color): + x = X + r1 * cos(angle) + y = Y - r1 * sin(angle) + painter.setPen(QPen(color, width, cap=Qt.RoundCap)) + if not (r1 == r2): # line + x1 = X + r2 * cos(angle) + y1 = Y - r2 * sin(angle) + painter.drawLine(QPointF(x, y), QPointF(x1, y1)) + else: # ball + painter.drawEllipse(QRectF(x-width/2, y-width/2, width, width)) + + def gradMachined(self): + return {5.9, 10.7, 15.7, 20.8, 25.8, 30.6, 40.6, 45.9, + 55.9, 60.7, 65.7, 70.8, 75.8, 80.6, 90.6, 95.9} + + def grayGrad(self, painter, X, Y, a, b, gradPairs): + if b == -1: + grad = QConicalGradient(X, Y, a) + elif b == -2: + grad = QRadialGradient (X, Y, a) + else: + grad = QLinearGradient (X, Y, a, b) + + for i in gradPairs: + grad.setColorAt(int(i)/100.0, QColor.fromHslF(0, 0, (i % 1.0), 1)) + + return grad + + + def paintDial(self, painter): - if self.fImageOrientation == self.HORIZONTAL: - xpos = self.fImageBaseSize * curLayer - ypos = 0.0 + # Replace Qt draw over substrate bitmap or svg to + # all-in-one widget generated from stratch using Qt only, + # make it highly tuneable, and uniformly look like + # using HSL color model to make same brightness of colored things. + # We can also easily have color tinted (themed) knobs. + # Some things were simplified a little, to gain more speed. + # R: knob nib (cap) radius + def createDial(R, hue1, hue2, barWidth, angleSpan, ticks, dialType, value): + + def rectBorder(w): + return QRectF(X-R-w, Y-R-w, (R+w)*2, (R+w)*2) + + def gray(luma): + return QColor.fromHslF(0, 0, luma, 1) + + X = Y = self.fImageBaseSize / 2 # center + + ang0 = int((angleSpan/2+90)*16) + ang1 = -angleSpan*16 + + enabled = self.checkEnabled() + E = enabled * self.fHoverStep / 40 # enlight + S = enabled * 0.9 # saturation + color1 = QColor.fromHslF(hue1, S, 0.5+E, 1) + color2 = QColor.fromHslF(hue2, S, 0.5+E, 1) + + if dialType == 1: # mimic svg dial + # light arc substrate: near black, 0.5 px exposed + painter.setPen(QPen(gray(0.10), barWidth+1, cap=Qt.FlatCap)) + painter.drawArc(rectBorder(barWidth), ang0, ang1) + + # light arc: gray bar + # should be combined with light (value) arc to be a bit faster ? + painter.setPen(QPen(self.grayGrad(painter, X, Y, 270, -1, {0.20, 100.15}), barWidth, cap=Qt.FlatCap)) + painter.drawArc(rectBorder(barWidth), ang0, ang1) + + # cap + painter.setBrush(self.grayGrad(painter, X-R, Y-R, R*2, -2, {0.45+E, 100.15+E})) + painter.setPen(QPen(gray(0.10), 0.5)) + painter.drawEllipse(rectBorder(1)) + + elif dialType == 2: # calf + # outer chamfer & leds substrate + painter.setPen(QPen(self.grayGrad(painter, X, Y, 135, -1, {0.15, 50.50, 100.15}), 1.5, cap=Qt.FlatCap)) + painter.setBrush(QColor.fromHslF(hue1, S, 0.05+E/2, 1)) + painter.drawEllipse(rectBorder(barWidth*2-1)) + + # machined shiny cap with chamfer + painter.setPen(QPen(self.grayGrad(painter, X, Y, -45, -1, {0.15, 50.50, 100.15}), 1, cap=Qt.FlatCap)) + painter.setBrush(self.grayGrad(painter, X, Y, 0, -1, self.gradMachined())) + painter.drawEllipse(rectBorder(1)) + + elif dialType == 3: # openav + # light arc substrate + painter.setPen(QPen(gray(0.20+E), barWidth)) + painter.drawArc(rectBorder(barWidth), ang0, ang1) + + + # draw arc: forward, or reverse (for 'R' ch knob) + startAngle = int((copysign(angleSpan/2, value) + 90)*16) + gradient = QConicalGradient(X, Y, 270) + + if (ticks == 0): + spanAngle = int(-angleSpan*16 * value) + gradient.setColorAt(0.25, color1) + gradient.setColorAt(0.75, color2) else: - xpos = 0.0 - ypos = self.fImageBaseSize * curLayer + # discretize scale: for 10 points, first will lit at 5%, + # then 15%, and last at 95% of normalized value, + # i.e. treshold is: center of point exactly matches knob mark angle + spanAngle = int(-int(angleSpan*value/(360/ticks)+0.5)*(360/ticks)*16) + for i in range(2, ticks-2, 1): + gradient.setColorAt((i+0.5-0.35)/ticks, color1.lighter(100)) + gradient.setColorAt((i+0.5) /ticks, Qt.black) + gradient.setColorAt((i+0.5+0.35)/ticks, color1.lighter(100)) + + painter.setPen(QPen(gradient, barWidth, cap=Qt.RoundCap if dialType == 3 else Qt.FlatCap)) + painter.drawArc(QRectF(rectBorder(barWidth)), startAngle, spanAngle) + + # do not draw marks on disabled items + if enabled == 0: + return + + # Forward or reverse (for 'R' ch knob) + angle = ((0.5-abs(value)) * copysign(angleSpan, value) + 90) * pi/180 + + match dialType: + case 1: # ball + self.drawMark(painter,X,Y, R*0.8, R*0.8, angle, barWidth/2, color1) + case 2: # line for calf + self.drawMark(painter,X,Y, R*0.6, R*0.9, angle, barWidth/2, Qt.black) + case 3: # line for openav + self.drawMark(painter,X,Y, 0, R+barWidth, angle, barWidth, color1) + + + # Custom knobs (Dry/Wet and Volume) + if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL): + hue2 = 0.5 # Blue + hue1 = hue2 if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_VOL else 0.3 # Green + + createDial(10, hue1, hue2, 3, 260, 0, 1, self.normValue()) + + # Custom knobs (L and R) + elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R): + hue = 0.21 # Lime + + # Shift to full negative range (incl. 0) for reversed knob + createDial(8, hue, hue, 2.5, 260, 0, 1, self.normValue()-1.0-np.nextafter(0, 1) if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_R else self.normValue()) + + # Custom knobs (Color) # internal + elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR: + # NOTE: here all incoming color data, except hue, is lost. + hue = (self.fCustomPaintColor.hueF() - 0.05) % 1.0 - source = QRectF(xpos, ypos, self.fImageBaseSize, self.fImageBaseSize) + createDial(10, hue, hue, 3, 260, 0, 1, self.normValue()) - if isinstance(self.fImage, QPixmap): - target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize) - painter.drawPixmap(target, self.fImage, source) + # Custom knobs + elif self.fImageId in (11, 12, 13): # openav + if self.fImageId == 11: + hue1 = hue2 = 0.05 # Orange else: - self.fImage.renderer().render(painter, source) - - # Custom knobs (Dry/Wet and Volume) - if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL): - # knob color - colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6) - colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6) - - # draw small circle - ballRect = QRectF(8.0, 8.0, 15.0, 15.0) - ballPath = QPainterPath() - ballPath.addEllipse(ballRect) - #painter.drawRect(ballRect) - tmpValue = (0.375 + 0.75*normValue) - ballValue = tmpValue - floor(tmpValue) - ballPoint = ballPath.pointAtPercent(ballValue) - - # draw arc - startAngle = 218*16 - spanAngle = -255*16*normValue - - if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET: - painter.setBrush(colorBlue) - painter.setPen(QPen(colorBlue, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) - - gradient = QConicalGradient(15.5, 15.5, -45) - gradient.setColorAt(0.0, colorBlue) - gradient.setColorAt(0.125, colorBlue) - gradient.setColorAt(0.625, colorGreen) - gradient.setColorAt(0.75, colorGreen) - gradient.setColorAt(0.76, colorGreen) - gradient.setColorAt(1.0, colorGreen) - painter.setBrush(gradient) - painter.setPen(QPen(gradient, 3)) + hue2 = 0.5 # Blue + hue1 = hue2 if self.fImageId == 12 else 0.3 # Green + createDial(12, hue1, hue2, 2.5, 270, 0, 3, self.normValue()) + + elif self.fImageId in (1, 2, 7, 8, 9, 10): # calf + hue = 0.52 # Aqua + + createDial(12, hue, hue, 4, (360/36)*29, 36, 2, self.normValue()) + + else: + print("Unknown type "+ str(self.fImageId) + " knob.") + return + + + def paintButton(self, painter, range_): + + # W: button cap half-size ; w: bar width + # positions: can be 2 or 3 + def createButton(W, hue, w, positions, btnType, value): + + def rectBorder(w): + return QRectF(X-W-w, Y-W-w, (W+w)*2, (W+w)*2) + + def gray(luma): + return QColor.fromHslF(0, 0, luma, 1) + + X = Y = self.fImageBaseSize / 2 # center + + enabled = self.checkEnabled() + E = enabled * self.fHoverStep / 40 # enlight + S = enabled * 0.9 # saturation + color = QColor.fromHslF(hue, S, 0.5+E, 1) + + if btnType == 1: # internal + # light bar substrate: near black, 0.5 px exposed + painter.setPen(QPen(gray(0.10), w+1)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) + + # light bar: gray bar + painter.setPen(QPen(gray(0.20), w)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) + + # cap + painter.setBrush(self.grayGrad(painter, X-W/2, Y-W/2, W*2, -2, {0.15+E, 100.28+E})) + painter.setPen(QPen(gray(0.10), 0.5)) + painter.drawRoundedRect(rectBorder(-2), 3, 3) + + elif btnType == 2: # calf + # outer chamfer & leds substrate + painter.setPen(QPen(self.grayGrad(painter, X, Y, 135, -1, {24.25, 26.50, 76.50, 78.25}), 1.5, cap=Qt.FlatCap)) + painter.setBrush(QColor.fromHslF(hue, S, 0.05+E/2, 1)) + painter.drawRoundedRect(QRectF(X-W-1, Y-W-w-0-1, W*2+2, W*2+w+0+2), 4, 4) + + # machined shiny cap with chamfer + painter.setPen(QPen(self.grayGrad(painter, X, Y, -45, -1, {24.25, 26.50, 74.50, 76.25}), 1, cap=Qt.FlatCap)) + painter.setBrush(self.grayGrad(painter, X, Y, -30, -1, self.gradMachined( ))) + painter.drawRoundedRect(rectBorder(-1), 3, 3) + + elif btnType == 3: # openav + # light substrate + pen = QPen(gray(0.20+E), w) + painter.setPen(pen) + painter.drawRoundedRect(rectBorder(0), 3, 3) + + + # draw active lights + if (value > (1/2-(positions-2)*(2/3-1/2))): + if (1/3 < value < 2/3) and (positions == 3): + pos = 1 # Middle else: - painter.setBrush(colorBlue) - painter.setPen(QPen(colorBlue, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) - - painter.setBrush(colorBlue) - painter.setPen(QPen(colorBlue, 3)) - - painter.drawArc(QRectF(4.0, 4.0, 26.0, 26.0), int(startAngle), int(spanAngle)) - - # Custom knobs (L and R) - elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R): - # knob color - color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6) - - # draw small circle - ballRect = QRectF(7.0, 8.0, 11.0, 12.0) - ballPath = QPainterPath() - ballPath.addEllipse(ballRect) - #painter.drawRect(ballRect) - tmpValue = (0.375 + 0.75*normValue) - ballValue = tmpValue - floor(tmpValue) - ballPoint = ballPath.pointAtPercent(ballValue) - - painter.setBrush(color) - painter.setPen(QPen(color, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0)) - - # draw arc - if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L: - startAngle = 218*16 - spanAngle = -255*16*normValue - else: - startAngle = 322.0*16 - spanAngle = 255.0*16*(1.0-normValue) - - painter.setPen(QPen(color, 2.5)) - painter.drawArc(QRectF(3.5, 3.5, 22.0, 22.0), int(startAngle), int(spanAngle)) - - # Custom knobs (Color) - elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR: - # knob color - color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6) - - # draw small circle - ballRect = QRectF(8.0, 8.0, 15.0, 15.0) - ballPath = QPainterPath() - ballPath.addEllipse(ballRect) - tmpValue = (0.375 + 0.75*normValue) - ballValue = tmpValue - floor(tmpValue) - ballPoint = ballPath.pointAtPercent(ballValue) - - # draw arc - startAngle = 218*16 - spanAngle = -255*16*normValue - - painter.setBrush(color) - painter.setPen(QPen(color, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) - - painter.setBrush(color) - painter.setPen(QPen(color, 3)) - painter.drawArc(QRectF(4.0, 4.8, 26.0, 26.0), int(startAngle), int(spanAngle)) - - # Custom knobs (Zita) - elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA: - a = normValue * pi * 1.5 - 2.35 - r = 10.0 - x = 10.5 - y = 10.5 - x += r * sin(a) - y -= r * cos(a) - painter.setBrush(Qt.black) - painter.setPen(QPen(Qt.black, 2)) - painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y)) - - # Custom knobs + pos = 2 # Max else: + pos = 0 + + if btnType == 1: # internal + if (pos > 0): + painter.setPen(QPen(color, w)) + if (pos == 1): + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X-w/2, Y-W-w)) + else: + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) + + elif btnType == 2: # calf + if (pos > 0): + grad = QLinearGradient(X-W, Y, X+W, Y) + for i in ({0.0, 30.6, 40.5, 45.7, 55.7, 60.5, 70.6, 100.0} if (pos>1) + else {20.0, 45.6, 55.6, 80.0}): + grad.setColorAt(int(i)/100.0, QColor.fromHslF(hue, S, (i % 1)+E, 1)) + painter.setPen(QPen(grad, w-0.5, cap=Qt.FlatCap)) + painter.drawLine(QPointF(X-W/4*(pos-1)*3, Y-W-w/2), QPointF(X+W/4*(pos-1)*3, Y-W-w/2)) + + elif btnType == 3: # openav + painter.setPen(QPen(color, w, cap=Qt.RoundCap)) + if (pos > 0): + if (pos == 1): + painter.drawRoundedRect(rectBorder(-W/2), 3, 3) + else: + painter.drawRoundedRect(rectBorder(0), 3, 3) + else: + painter.drawLine(QPointF(X-0.1, Y), QPointF(X+0.1, Y)) + + + # do not draw marks on disabled items + if enabled == 0: return - if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX: - self.fHoverStep += 1 if self.fIsHovered else -1 - QTimer.singleShot(20, self.update) + match btnType: + case 1: # ball at center + self.drawMark(painter,X,Y, 0, 0, 0, w/2, color) + # case 3: # openav + # painter.setPen(QPen(color, w, cap=Qt.RoundCap)) + # painter.drawLine(QPointF(X-0.1, Y), QPointF(X+0.1, Y)) - else: # isEnabled() - target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize) - if isinstance(self.fImage, QPixmap): - painter.drawPixmap(target, self.fImage, target) - else: - self.fImage.renderer().render(painter, target) + + positions = range_ + 1; + + # Custom knobs (Color) # internal + if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR: + # NOTE: here all incoming color data, except hue, is lost. + hue = self.fCustomPaintColor.hueF() + + createButton(10, hue, 3, positions, 1, self.normValue()) + + # Custom knobs + elif self.fImageId in (1, 2, 7, 8, 9, 10): # calf + hue = 0.55 # Sky + + createButton(12, hue, 4, positions, 2, self.normValue()) + + elif self.fImageId in (11, 12, 13): # openav + hue = 0.05 # Orange + + createButton(12, hue, 2.5, positions, 3, self.normValue()) + + else: + print("Unknown type "+ str(self.fImageId) + " button.") + return # ---------------------------------------------------------------------------------------------------------------------