Browse Source

redraw-Qt-knobs

pull/1969/head
jpka- 1 year ago
parent
commit
638ec3847f
1 changed files with 211 additions and 199 deletions
  1. +211
    -199
      source/frontend/widgets/scalabledial.py

+ 211
- 199
source/frontend/widgets/scalabledial.py View File

@@ -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)
@@ -145,148 +101,204 @@ class ScalableDial(CommonDial):
self.slot_updateImage()

def paintDial(self, painter):

# 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):
# X,Y: center
X = Y = self.fImageBaseSize / 2

ang0 = int((angleSpan/2+90)*16)
ang1 = -angleSpan*16

if (value < -1.1):
value = 0
enabled = 0
enlight = 0
else:
enabled = 1
enlight = self.fHoverStep / 40

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)

S = 0.9 # Saturation
color0 = Qt.black
color1 = QColor.fromHslF(hue1, S, 0.5+enlight, 1)
color2 = QColor.fromHslF(hue2, S, 0.5+enlight, 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. TODO
g1 = QConicalGradient(X, Y, 270)
g1.setColorAt(0.0, gray(0.20)) # right part
g1.setColorAt(1.0, gray(0.15)) # left part
painter.setPen(QPen(g1, barWidth, cap=Qt.FlatCap))
painter.drawArc(rectBorder(barWidth), ang0, ang1)

# cap
g2 = QRadialGradient(X-R, Y-R, R*2)
g2.setColorAt(0.0, gray(0.45+enlight))
g2.setColorAt(1.0, gray(0.15+enlight))
painter.setBrush(g2)
painter.setPen(QPen(g2, 1))
painter.drawEllipse(rectBorder(0.5))

elif dialType == 2: # calf
# outer chamfer
g1 = QConicalGradient(X, Y, 135)
g1.setColorAt(0.0, gray(0.15))
g1.setColorAt(0.5, gray(0.50))
g1.setColorAt(1.0, gray(0.15))
painter.setPen(QPen(g1, 2, cap=Qt.FlatCap))
painter.drawArc(rectBorder(barWidth*2-1), 0, 360*16)

# knob chamfer
g2 = QConicalGradient(X, Y, -45)
g2.setColorAt(0.0, gray(0.15))
g2.setColorAt(0.5, gray(0.50))
g2.setColorAt(1.0, gray(0.15))
painter.setPen(QPen(g2, 2, cap=Qt.FlatCap))
painter.drawArc(rectBorder(1), 0, 360*16)

# leds substrate
# should be combined with light (value) "arc" to be a bit faster. TODO
# painter.setPen(QPen(gray(0.05+enlight), barWidth + 1))
painter.setPen(QPen(QColor.fromHslF(hue1, S, 0.05+enlight/2, 1), barWidth+1))
painter.drawArc(rectBorder(barWidth), 0, 360*16)

# machined shiny cap
g3 = QConicalGradient(X, Y, 0)
for i in (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):
g3.setColorAt(int(i)/100.0, gray(i % 1.0))
painter.setBrush(g3)
painter.setPen(QPen(g3, 1))
painter.drawEllipse(rectBorder(0))

elif dialType == 3: # openav
# light arc substrate
painter.setPen(QPen(gray(0.20+enlight), barWidth))
painter.drawArc(rectBorder(barWidth), ang0, ang1)


# do not draw marks on disabled items
if enabled == 1:
# Forward or reverse (for 'R' ch knob)
a = ((0.5-abs(value)) * copysign(angleSpan, value) + 90) * pi/180

if dialType == 1: # ball
x = X + R * 0.8 * cos(a)
y = Y - R * 0.8 * sin(a)
w = barWidth - 0.5
painter.setBrush(color1)
painter.setPen(QPen(color1, 0))
painter.drawEllipse(QRectF(x-w/2, y-w/2, w, w))

elif dialType == 2: # line for calf
x = X + R * 0.9 * cos(a)
y = Y - R * 0.9 * sin(a)
x1 = X + R * 0.6 * cos(a)
y1 = Y - R * 0.6 * sin(a)
painter.setPen(QPen(Qt.black, 1.5))
painter.drawLine(QPointF(x, y), QPointF(x1, y1))

elif dialType == 3: # line for openav
x = X + (R + barWidth) * cos(a)
y = Y - (R + barWidth) * sin(a)
painter.setPen(QPen(color1, barWidth, cap=Qt.RoundCap))
painter.drawLine(QPointF(x, y), QPointF(X, Y))

# 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:
spanAngle = int(-int(angleSpan*value/(360/ticks)+0.5)*(360/ticks)*16)
n = ticks*2
for i in range(0, n, 2):
gradient.setColorAt((i-0.40)/n, color0)
gradient.setColorAt((i+0.85)/n, color1.lighter(100))

if dialType == 3: # openav
painter.setPen(QPen(gradient, barWidth, cap=Qt.RoundCap))
else:
painter.setPen(QPen(gradient, barWidth, cap=Qt.FlatCap))
painter.drawArc(QRectF(rectBorder(barWidth)), startAngle, spanAngle)

if self.isEnabled():
normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
curLayer = int((self.fImageLayersCount - 1) * normValue)

if self.fImageOrientation == self.HORIZONTAL:
xpos = self.fImageBaseSize * curLayer
ypos = 0.0
else:
normValue = -9.9

# Custom knobs (Dry/Wet and Volume)
if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL):
hue1 = 0.3 # Green
hue2 = 0.5 # Blue
if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_VOL:
hue1 = hue2

createDial(10, hue1, hue2, 3, 260, 0, 1, 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
if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_R:
# Shift to full negative range (incl. 0) for reversed knob
normValue = normValue - 1 - np.nextafter(0, 1)

createDial(8, hue, hue, 2.5, 260, 0, 1, normValue)

# Custom knobs (Color)
elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR:
# FIXME It's better to move it below, because calf is not custom colored.
# (need to change not in this file)
if self.fImageId in (1, 2, 7, 8, 9, 10): # calf
hue = 0.52 # Aqua

createDial(12, hue, hue, 4, 296, 36, 2, normValue)

else: # internal
# NOTE here all incoming color data, except hue, is lost.
hue = self.fCustomPaintColor.hueF()

createDial(10, hue, hue, 3, 260, 0, 1, normValue)

# Custom knobs
elif self.fImageId in (11, 12, 13): # openav
if self.fImageId == 12:
hue1 = hue2 = 0.5 # Blue
elif self.fImageId == 13:
hue1 = 0.3 # Green
hue2 = 0.5 # Blue
else:
xpos = 0.0
ypos = self.fImageBaseSize * curLayer
hue1 = hue2 = 0.05 # Orange

source = QRectF(xpos, ypos, self.fImageBaseSize, self.fImageBaseSize)
createDial(12, hue1, hue2, 2.5, 270, 0, 3, normValue)

if isinstance(self.fImage, QPixmap):
target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize)
painter.drawPixmap(target, self.fImage, source)
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))

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
else:
return
else:
return

if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
self.fHoverStep += 1 if self.fIsHovered else -1
QTimer.singleShot(20, self.update)

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)
# Maybe it's better to be disabled when not self.isEnabled() ? FIXME
if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
self.fHoverStep += 1 if self.fIsHovered else -1
QTimer.singleShot(20, self.update)

# ---------------------------------------------------------------------------------------------------------------------

Loading…
Cancel
Save