|
- #!/usr/bin/env python3
- # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
- # SPDX-License-Identifier: GPL-2.0-or-later
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Imports (Global)
-
- from math import cos, floor, pi, sin
- import ast
-
- from qt_compat import qt_config
-
- if qt_config == 5:
- from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QPointF, QRectF, QTimer, QSize
- from PyQt5.QtGui import QColor, QLinearGradient, QRadialGradient, QConicalGradient, QFontMetrics, QPen, QPolygonF
- from PyQt5.QtWidgets import QToolTip
- elif qt_config == 6:
- from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QPoint, QPointF, QRectF, QTimer, QSize
- from PyQt6.QtGui import QColor, QLinearGradient, QRadialGradient, QConicalGradient, QFontMetrics, QPen, QPolygonF
- from PyQt6.QtWidgets import QToolTip
-
- from .commondial import CommonDial
- from carla_shared import fontMetricsHorizontalAdvance, RACK_KNOB_GAP
- from carla_backend import (
- PARAMETER_NULL,
- PARAMETER_DRYWET,
- PARAMETER_VOLUME,
- PARAMETER_BALANCE_LEFT,
- PARAMETER_BALANCE_RIGHT,
- PARAMETER_PANNING,
- PARAMETER_FORTH,
- PARAMETER_MAX )
-
- # ---------------------------------------------------------------------------------------------------------------------
- # Widget Class
-
- class ScalableDial(CommonDial):
- def __init__(self, parent, index,
- precision,
- default,
- minimum,
- maximum,
- label,
- paintMode,
- colorHint = -1, # Hue & Sat, -1 = NotColorable
- unit = "%", # Measurement Unit
- skinStyle = "default", # Full name (from full list)
- whiteLabels = 1, # Is light/white theme?
- tweaks = {},
- isInteger = 0, # Input is Integer
- isButton = 0, # Integer i/o is Button or LED
- isOutput = 0,
- isVuOutput = 0, # Output is analog VU meter
- isVisible = 1 ):
-
- # self.fWidth = self.fHeight = 32 # aka fImageBaseSize, not includes label.
- CommonDial.__init__(self, parent, index, precision, default, minimum, maximum, label, paintMode, colorHint, unit, skinStyle, whiteLabels, tweaks, isInteger, isButton, isOutput, isVuOutput, isVisible)
-
- # FIXME not every repaint need to re-calculate geometry?
- def updateSizes(self):
- knownModes = [
- # default
- self.CUSTOM_PAINT_MODE_NULL , # 0
- self.CUSTOM_PAINT_MODE_CARLA_WET , # 1
- self.CUSTOM_PAINT_MODE_CARLA_VOL , # 2
- self.CUSTOM_PAINT_MODE_CARLA_L , # 3
- self.CUSTOM_PAINT_MODE_CARLA_R , # 4
- self.CUSTOM_PAINT_MODE_CARLA_PAN , # 5
- self.CUSTOM_PAINT_MODE_CARLA_FORTH , # 6
- self.CUSTOM_PAINT_MODE_CARLA_WET_MINI, # 9
- self.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, # 10
- # calf
- 16,
- # openav
- 32, 33, 34, 37, 38,
- # zynfx
- 48, 49, 50, 53, 54,
- # tube
- 64, 65, 66, 69, 70,
- ]
-
- index = -1
- for i in range(len(knownModes)):
- if knownModes[i] == self.fCustomPaintMode:
- index = i
- break
-
- if (index == -1):
- print("Unknown paint mode "+ str(self.fCustomPaintMode))
- return
-
- self.skin = int(self.fCustomPaintMode / 16)
- self.subSkin = int(self.fCustomPaintMode % 16)
-
- width, hueA, hueB, travel, radius, size, point, labelLift = [
- # default Aqua
- [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ],
- [ 32, 0.3 , 0.50, 260, 10, 10, 3 , 1/2, ], # WET
- [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], # VOL
- [ 26, 0.21, 0.21, 260, 8, 10, 2.5, 1/2, ], # L
- [ 26, 0.21, 0.21, 260, 8, 10, 2.5, 1/2, ], # R
- [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], # PAN
- [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], # FORTH
- [ 28, 0.3 , 0.50, 260, 9, 10, 2.5, 1/2, ], # WET_MINI
- [ 28, 0.50, 0.50, 260, 9, 10, 2.5, 1/2, ], # VOL_MINI
- # calf Blue
- [ 40, 0.53, 0.53, 290, 12, 12, 4 , 1 , ], # calf absent any wet/vol knobs
- # openav Orange
- [ 32, 0.05, 0.05, 270, 12, 12, 2.5, 2/3, ],
- [ 32, 0.30, 0.5, 270, 12, 12, 2.5, 2/3, ], # WET
- [ 32, 0.5, 0.5, 270, 12, 12, 2.5, 2/3, ], # VOL
- [ 32, 0.5, 0.5, 270, 12, 12, 2.5, 2/3, ],
- [ 32, 0.5, 0.5, 270, 12, 12, 2.5, 2/3, ],
- # zynfx Teal
- [ 38, 0.55, 0.55, 264, 12, 12, 4 , 1/4, ],
- [ 38, 0.30, 0.5, 264, 12, 12, 4 , 1/4, ], # WET
- [ 38, 0.5, 0.5, 264, 12, 12, 4 , 1/4, ], # VOL
- [ 38, 0.5, 0.5, 264, 12, 12, 4 , 1/4, ],
- [ 38, 0.5, 0.5, 264, 12, 12, 4 , 1/4, ],
- # tube VFD
- [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ],
- [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], # WET
- [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], # VOL
- [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ],
- [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ],
- ] [index]
-
- # Geometry & Color of controls & displays, some are tweakable:
- # 1. Try to get value from per-skin tweak;
- # 2. Then try to get value from common tweak;
- # 3. Then use default value from array.
- self.fWidth = self.fHeight = width
-
- # Angle span (travel)
- # calf must be 360/36*29=290
- # tube must be 360/14*10=257.14 or 360/12*10=300
- self.fTravel = int(self.getTweak('KnobTravel', travel))
-
- # Radius of some notable element of Knob (not exactly the largest)
- self.fRadius = int(self.getTweak('KnobRadius', radius))
-
- # Size of Button (half of it, similar to "raduis")
- self.fSize = int(self.getTweak('ButtonSize', size))
-
- # Point, line or other accent on knob
- self.fPointSize = point
-
- # Colouring, either only one or both values can be used for skin.
- if (self.subSkin > 0) or (self.skin in (1, 3, 4,)) :
- self.fHueA = hueA
- self.fHueB = hueB
- # default and openav can be re-colored
- elif self.colorFollow:
- self.fHueA = self.fHueB = int(self.fColorHint) / 100.0 # we use hue only yet
- else:
- # NOTE: here all incoming color data, except hue, is lost.
- self.fHueA = self.fHueB = self.fCustomPaintColor.hueF()
-
-
- metrics = QFontMetrics(self.fLabelFont)
-
- if not self.fLabel:
- self.fLabelWidth = 0
- else:
- self.fLabelWidth = fontMetricsHorizontalAdvance(metrics, self.fLabel)
-
- extraWidthAuto = max((self.fLabelWidth - self.fWidth), 0)
-
- self.fLabelHeight = metrics.height()
-
- if (self.fCustomPaintMode % 16) == 0: # exclude: DryWet, Volume, etc.
- extraWidth = int(self.getTweak('GapMin', 0))
- extraWidthLimit = int(self.getTweak('GapMax', 0))
-
- if self.getTweak('GapAuto', 0):
- extraWidth = max(extraWidth, extraWidthAuto)
-
- extraWidth = min(extraWidth, extraWidthLimit)
-
- self.fWidth = self.fWidth + extraWidth
-
- self.setMinimumSize(self.fWidth, self.fHeight + self.fLabelHeight + RACK_KNOB_GAP)
- self.setMaximumSize(self.fWidth, self.fHeight + self.fLabelHeight + RACK_KNOB_GAP)
-
- if not self.fLabel:
- self.fLabelHeight = 0
- # self.fLabelWidth = 0
- return
-
- self.fLabelPos.setX(float(self.fWidth)/2.0 - float(self.fLabelWidth)/2.0)
-
- # labelLift = (1/2, 1, 2/3, 1/4, 1/2, 1, 1, 1)[skin % 8]
- self.fLabelPos.setY(self.fHeight + self.fLabelHeight * labelLift)
-
- # jpka: TODO Can't see how gradients work, looks like it's never triggered.
- self.fLabelGradient.setStart(0, float(self.fHeight)/2.0)
- self.fLabelGradient.setFinalStop(0, self.fHeight + self.fLabelHeight + 5)
-
- self.fLabelGradientRect = QRectF(float(self.fHeight)/8.0, float(self.fHeight)/2.0,
- float(self.fHeight*3)/4.0, self.fHeight+self.fLabelHeight+5)
-
- def setImage(self, imageId):
- print("Loopback for self.setupZynFxParams(), FIXME!")
- return
-
- def minimumSizeHint(self):
- return QSize(self.fWidth, self.fHeight)
-
- def sizeHint(self):
- return QSize(self.fWidth, self.fHeight)
-
- # def changeEvent(self, event):
- # CommonDial.changeEvent(self, event)
- #
- # # Force svg update if enabled state changes
- # if event.type() == QEvent.EnabledChange:
- # self.slot_updateImage()
-
- def drawMark(self, painter, X, Y, r1, r2, angle, width, color):
- A = angle * pi/180
- x = X + r1 * cos(A)
- y = Y - r1 * sin(A)
- painter.setPen(QPen(color, width, cap=Qt.RoundCap))
- if not (r1 == r2): # line
- x1 = X + r2 * cos(A)
- y1 = Y - r2 * sin(A)
- painter.drawLine(QPointF(x, y), QPointF(x1, y1))
- else: # ball
- painter.drawEllipse(QRectF(x-width/2, y-width/2, width, width))
-
- gradMachined = {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, alpha = 1.0):
- 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), alpha))
-
- return grad
-
- # Pen is always full opacity (alpha = 1)
- def grayGradPen(self, painter, X, Y, a, b, gradPairs = {0.10, 50.30, 100.10}, width = 1.0):
- painter.setPen(QPen(self.grayGrad(painter, X, Y, a, b, gradPairs, 1), width, Qt.SolidLine, Qt.FlatCap))
-
- def grayGradBrush(self, painter, X, Y, a, b, gradPairs, alpha = 1.0):
- painter.setBrush(self.grayGrad(painter, X, Y, a, b, gradPairs, alpha))
-
-
- # 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 paintDial(self, painter, X, Y, H, S, L, E, normValue, enabled):
- R = self.fRadius
- barWidth = self.fPointSize
- angleSpan = self.fTravel
-
- hueA = self.fHueA
- hueB = self.fHueB
-
- color0 = QColor.fromHslF(hueA, S, L, 1)
- color0a = QColor.fromHslF(hueA, S, L/2-0.25, 1)
- color1 = QColor.fromHslF(hueB, S, L, 1)
-
- skin = self.skin
-
- def ang(value):
- return angleSpan * (0.5 - value) + 90
-
- def drawArcV(rect, valFrom, valTo, ticks = 0):
- # 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
- if ticks:
- valTo = int(valTo * (ticks * angleSpan / 360) + 0.5) / (ticks * angleSpan / 360)
-
- painter.drawArc(rect, int(ang(valFrom) * 16), int((ang(valTo) - ang(valFrom)) * 16))
-
- def squareBorder(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)
-
-
- # Knob light arc "base" (starting) value/angle.
- if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L:
- refValue = 0
- elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_R:
- refValue = 1
- elif (self.fMinimum == -self.fMaximum) and (skin == 0):
- refValue = 0.5
- else:
- refValue = 0
-
- knobMuted = (self.knobPusheable and (normValue == refValue))
-
- haveLed = self.getTweak('WetVolPushLed', 1) and self.fCustomPaintMode in (1, 2, 5, 6, 9, 10,)
-
- if self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9
- if knobMuted:
- if self.fIndex == PARAMETER_DRYWET:
- self.pushLabel("Thru")
- elif self.fIndex == PARAMETER_VOLUME:
- self.pushLabel("Mute")
- elif self.fIndex == PARAMETER_PANNING:
- self.pushLabel("Center")
- else:
- self.pushLabel("Midway")
- else:
- self.popLabel()
-
- if skin == 0: # mimic svg dial
- # if not knobMuted:
- if not (knobMuted and haveLed):
- # light arc substrate: near black, 0.5 px exposed
- painter.setPen(QPen(gray(0.10), barWidth+1, cap=Qt.FlatCap))
- drawArcV(squareBorder(barWidth), 0, 1)
-
- # light arc: gray bar
- # should be combined with light (value) arc to be a bit faster ?
- self.grayGradPen(painter, X, Y, 270, -1, {0.20, 100.15}, barWidth)
- drawArcV(squareBorder(barWidth), 0, 1)
-
- # cap
- self.grayGradBrush(painter, X-R, Y-R, R*2, -2, {0.45+E, 100.15+E})
- painter.setPen(QPen(gray(0.10), 0.5))
- painter.drawEllipse(squareBorder(1))
-
- elif skin == 1: # calf
- # outer chamfer & leds substrate
- self.grayGradPen(painter, X, Y, 135, -1, {0.15, 50.50, 100.15}, 1.5)
- painter.setBrush(color0a)
- painter.drawEllipse(squareBorder(barWidth*2-1))
-
- # machined shiny cap with chamfer
- self.grayGradPen(painter, X, Y, -45, -1, {0.15, 50.50, 100.15})
- self.grayGradBrush(painter, X, Y, 0, -1, self.gradMachined)
- painter.drawEllipse(squareBorder(1))
-
- elif skin == 2: # openav
- # light arc substrate
- painter.setPen(QPen(gray(0.20+E), barWidth))
- drawArcV(squareBorder(barWidth), 0, 1)
-
- elif skin == 3: # zynfx
- # light arc substrate
- painter.setPen(QPen(QColor.fromHslF(0.57, 0.8, 0.25, 1), barWidth+2, cap=Qt.FlatCap))
- drawArcV(squareBorder(barWidth), 0, 1)
-
- # cap
- painter.setPen(QPen(gray(0.0), 1))
- painter.setBrush(gray(0.3 + E))
- painter.drawEllipse(squareBorder(-2))
-
- # These knobs are different for integers and for floats.
- elif skin == 4: # tube / bakelite
- chamfer = 1.5 # It is best when 1.5 at normal zoom, and 1.0 for >2x HiDpi
- # base
- self.grayGradPen(painter, X, Y, -45, -1, width=chamfer)
- self.grayGradBrush(painter, X-5-ang(normValue)/36, -20, 83, -2, {0.2, 50.2, 51.00, 100.00})
- if self.fIsInteger: # chickenhead knob: small base
- painter.drawEllipse(squareBorder(1))
- else: # round knob: larger base
- painter.drawEllipse(squareBorder(R*0.7))
-
- polygon = QPolygonF()
- # "chickenhead" pointer
- if self.fIsInteger:
- for i in range(17):
- A = ((0.01, 0.02, 0.03, 0.06, 0.2, 0.3, 0.44, 0.455, -0.455, -0.44, -0.3, -0.2, -0.06, -0.03, -0.02, -0.01, 0.01)[i] * 360 - ang(normValue)) * pi/180
- r = (1, 0.97, 0.91, 0.7, 0.38, 0.39, 0.87, 0.9, 0.9, 0.87, 0.39, 0.38, 0.7, 0.91, 0.97, 1, 1)[i] * R
- polygon.append(QPointF(X + r * 1.75 * cos(A), Y + r * 1.75 * sin(A)))
- # 8-teeth round knob outline
- else:
- for i in range(64):
- A = (i / 64 * 360 - ang(normValue)) * pi/180
- r = R * (1, 0.95, 0.91, 0.89, 0.88, 0.89, 0.91, 0.95)[i % 8]
- polygon.append(QPointF(X + r * 1.5 * cos(A), Y + r * 1.5 * sin(A)))
-
- self.grayGradPen(painter, X, Y, -45, -1, {0.10, 50.50, 100.10}, chamfer)
- self.grayGradBrush(painter, X-5-ang(normValue)/36, -20, 75, -2, {0.2, 50.2, 51.00, 100.00})
- painter.drawPolygon(polygon)
-
- # machined shiny penny with chamfer
- self.grayGradPen(painter, X, Y, 135, -1, {0.15, 50.50, 100.15})
- self.grayGradBrush(painter, X, Y, -ang(normValue)/36, -1, self.gradMachined, 0.75)
- if self.fIsInteger: # chickenhead knob: small circle
- painter.drawEllipse(squareBorder(-R*0.65))
- else: # round knob: large one
- painter.drawEllipse(squareBorder(-1))
-
- # Outer scale marks
- for i in range(0, 11):
- angle = ((0.5-i/10) * angleSpan + 90)
- self.drawMark(painter, X, Y, R*2, R*2, angle, barWidth/12 * (4 + 1 * int((i % 10) == 0)), gray(0.5 + E))
-
- # if knobMuted:
- if (knobMuted and haveLed):
- # if self.getTweak('WetVolPushLed', 1):
- self.drawMark(painter, X, Y, 0, 0, 0, barWidth, color0)
- return
-
- # draw arc: forward, or reverse (for 'R' ch knob)
- if (not (normValue == refValue)) and (not (skin == 4)):
-
- gradient = QConicalGradient(X, Y, 270)
- cap=Qt.FlatCap
-
- if not (skin == 1): # any, except calf
- ticks = 0
- gradient.setColorAt(0.75, color0)
- gradient.setColorAt(0.25, color1)
- if skin == 3: # zynfx
- # light arc partial (angled) black substrate
- painter.setPen(QPen(gray(0.0), barWidth+2, cap=Qt.FlatCap))
- drawArcV(squareBorder(barWidth), refValue-0.013, normValue+0.013)
- elif skin == 2: # openav
- cap=Qt.RoundCap
- else: # calf
- ticks = 36
- for i in range(2, ticks-2, 1):
- gradient.setColorAt((i+0.5-0.35)/ticks, color0)
- gradient.setColorAt((i+0.5) /ticks, Qt.black)
- gradient.setColorAt((i+0.5+0.35)/ticks, color0)
-
- painter.setPen(QPen(gradient, barWidth, Qt.SolidLine, cap))
- drawArcV(QRectF(squareBorder(barWidth)), refValue, normValue, ticks)
-
- # do not draw marks on disabled items
- if not enabled:
- return
-
- A = ang(normValue)
-
- match skin:
- case 0: # ball
- self.drawMark(painter, X, Y, R*0.8, R*0.8, A, barWidth/2+0.5, color0)
- case 1: # line for calf
- self.drawMark(painter, X, Y, R*0.6, R*0.9, A, barWidth/2, Qt.black)
- case 2: # line for openav
- self.drawMark(painter, X, Y, 0, R+barWidth, A, barWidth, color0)
- case 3: # line for zynfx
- self.drawMark(painter, X, Y, 2, R-3, A, barWidth/2+0.5, Qt.white)
- case 4: # ball
- r = R * (int(self.fIsInteger) * 0.25 + 1.2)
- self.drawMark(painter, X, Y, r, r, A, barWidth/2+0.5, Qt.white)
-
-
- def paintButton(self, painter, X, Y, H, S, L, E, normValue, enabled):
- # W: button cap half-size ; w: bar width
- W = self.fRadius
- w = self.fPointSize
-
- hue = self.fHueA
-
- skin = int(self.fCustomPaintMode / 16)
-
- def squareBorder(w, dw=0):
- return QRectF(X-W-w-dw, Y-W-w, (W+w+dw)*2, (W+w)*2)
-
- def gray(luma):
- return QColor.fromHslF(0, 0, luma, 1)
-
- color = QColor.fromHslF(hue, S, L, 1)
-
- centerLed = self.getTweak('ButtonHaveLed', 0) # LED itself & size increase
- coloredNeon = self.getTweak('ColoredNeon', 1) # But worse when HighContrast.
-
- if skin == 0: # internal
- if not centerLed:
- # 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
- self.grayGradBrush(painter, X-W/2, Y-W/2, W*2, -2, {0.13+E, 50.18+E, 100.35+E})
- painter.setPen(QPen(gray(0.05), 1))
- # A bit larger buttons when no top LED, but centered one.
- painter.drawRoundedRect(squareBorder(-1+centerLed), 3, 3)
-
- elif skin == 1: # calf
- # outer chamfer & leds substrate
- self.grayGradPen(painter, X, Y, 135, -1, {24.25, 26.50, 76.50, 78.25}, 1.5)
- 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
- self.grayGradPen(painter, X, Y, -45, -1, {24.25, 26.50, 74.50, 76.25})
- self.grayGradBrush(painter, X, Y, -30, -1, self.gradMachined)
- painter.drawRoundedRect(squareBorder(-1), 3, 3)
-
- elif skin == 2: # openav
- # light substrate
- pen = QPen(gray(0.20+E), w)
- painter.setPen(pen)
- painter.drawRoundedRect(squareBorder(0), 3, 3)
-
- elif skin == 3: # zynfx
- if not centerLed:
- # light bar substrate: teal, 1 px exposed
- painter.setPen(QPen(QColor.fromHslF(hue, 0.8, 0.25, 1), w+2))
- painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w))
-
- # button
- painter.setPen(QPen(gray(0.0), 1))
- painter.setBrush(gray(0.3 + E))
- painter.drawRoundedRect(squareBorder(-2, 4), 3, 3)
-
- elif skin == 4: # tube
- # bakelite cap
- self.grayGradPen(painter, X, Y, -45, -1)
- self.grayGradBrush(painter, X-10, -40, 120, -2, {0.2, 50.2, 51.00, 100.00})
- painter.drawRoundedRect(squareBorder(W*0.2), 3, 3)
-
- # neon lamp
- if (normValue > 0):
- grad = QRadialGradient(X, Y, 10)
- for i in ({0.6, 20.6, 70.4, 100.0}):
- if coloredNeon:
- grad.setColorAt(int(i)/100.0, QColor.fromHslF((0.05 - normValue) % 1.0, S, (i % 1.0), 1))
- else:
- grad.setColorAt(int(i)/100.0, QColor.fromHslF(0.05, S, (i % 1.0) * normValue, 1))
-
- painter.setPen(QPen(Qt.NoPen))
- painter.setBrush(grad)
- painter.drawRoundedRect(squareBorder(-W*0.4), 1.5, 1.5)
-
- # glass over neon lamp
- self.grayGradPen(painter, X, Y, 135, -1)
- self.grayGradBrush(painter, X-10, -40, 124, -2, {0.9, 50.9, 51.4, 100.4}, 0.25)
- painter.drawRoundedRect(squareBorder(-W*0.4), 1.5, 1.5)
-
- # draw active lights
- if skin == 0: # internal
- if not centerLed:
- if (normValue > 0):
- painter.setPen(QPen(color, w))
- if (normValue < 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))
- else:
- painter.setPen(QPen(gray(0.05), 0.5))
- painter.setBrush(color.darker(90 + int(300*(1-normValue))))
- painter.drawRoundedRect(squareBorder(w-W), 1, 1)
-
- elif skin == 1: # calf
- if (normValue > 0):
- grad = QLinearGradient(X-W, Y, X+W, Y)
- for i in ({20.0, 45.6, 55.6, 80.0} if (normValue < 1)
- else {0.0, 30.6, 40.5, 45.7, 55.7, 60.5, 70.6, 100.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, Y-W-w/2), QPointF(X+W, Y-W-w/2))
-
- elif skin == 2: # openav
- painter.setPen(QPen(color, w, cap=Qt.RoundCap))
- if (normValue > 0):
- painter.drawRoundedRect(squareBorder(-W * (1 - normValue)), 3, 3)
- else:
- painter.drawLine(QPointF(X-0.1, Y), QPointF(X+0.1, Y))
-
- elif skin == 3: # zynfx
- if not centerLed:
- if (normValue > 0):
- dx = (W - w) if (normValue < 1) else 0
- painter.setPen(QPen(gray(0), w+2))
- painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2-dx, Y-W-w))
- painter.setPen(QPen(color, w))
- painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2-dx, Y-W-w))
- else:
- painter.setPen(QPen(gray(0), 1))
- painter.setBrush(color.darker(90 + int(300*(1-normValue))))
- painter.drawEllipse(squareBorder(w-W+1))
-
- # do not draw marks on disabled items
- if not enabled:
- return
-
- match skin:
- case 0: # internal: ball at center
- if not centerLed:
- self.drawMark(painter, X, Y, 0, 0, 0, w/2+0.5, color)
- # case 3: # openav
- # painter.setPen(QPen(color, w, cap=Qt.RoundCap))
- # painter.drawLine(QPointF(X-0.1, Y), QPointF(X+0.1, Y))
- case 3: # zynfx: ball at center
- if not centerLed:
- self.drawMark(painter, X, Y, 0, 0, 0, w/2, gray(1))
-
-
- # Just a text label not so good for fast updated display, see issue #1934.
- # NOTE Work in progress.
- def paintDisplay(self, painter, X, Y, H, S, L, E, normValue, enabled):
-
- # X, Y: Center of label.
- def plotStr(self, painter, X, Y, st, fontSize, aspectRatio, skew):
-
- # Due to CPU/speed gain, we use simplest possible 7-segmented digits.
- # Shape to Speed balance: Speed
- h = ["KYNKNY ROZUZ RVKVY ROJUJ", # 0 NJ RJ VJ
- "KYVJVQ RVSVZ", # 1 NR RR VR
- "KYNJVJVRNRNZVZ", # 2 NZ RZ VZ
- "KYNJVJVZNZ RVRNR", # 3 P]
- "KYNJNRVR RVJVZ", # 4
- "KYVJNJNRVRVZNZ", # 5
- "KYVJNJNZVZVRNR", # 6
- "KYNJVJVZ", # 7
- "KYNJNZVZVJNJ RNRVR", # 8
- "KYNZVZVJNJNRVR", # 9
- "KYNRVR", # -
- "OTRZP]", # .
- "KYNNNX RVPNTVX", # k
- "KYNXNLRRVLVX" ] # M
-
- def plotHersheyChar(painter, X, Y, c, fontSize, aspectRatio, skew, justGetWidth):
- lm = (ord(h[c][0]) - ord('R')) * fontSize * aspectRatio
- rm = (ord(h[c][1]) - ord('R')) * fontSize * aspectRatio
- if justGetWidth:
- return X + rm - lm
-
- points = []
- X = X - lm
- # The speed and CPU load is critical here.
- # I try to make it as efficient as possible, but can it be even faster?
- for i in range(1, int(len(h[c])/2)):
- a = (h[c][i*2])
- b = (h[c][i*2+1])
- if (a == ' ') and (b == 'R'):
- painter.drawPolyline(points)
- points = []
- else:
- y = (ord(b) - ord('R')) * fontSize
- x = (ord(a) - ord('R')) * fontSize * aspectRatio + skew * y
- points.append(QPointF(X+x, Y+y))
-
- painter.drawPolyline(points)
- X = X + rm
- return X
-
- def plotDecodedChar(painter, X, Y, st, fontSize, aspectRatio, skew, justGetWidth):
- for i in range(len(st)):
- digit = "0123456789-.kM".find(st[i])
- if digit < 0:
- print("ERROR: Illegal char at " + str(i) + " in " + st)
- else:
- X = plotHersheyChar(painter, X, Y, digit, fontSize, aspectRatio, skew, justGetWidth)
- return X
-
- widthPx = plotDecodedChar(painter, 0, Y, st, fontSize, aspectRatio, skew, 1)
- plotDecodedChar(painter, X-widthPx/2, Y, st, fontSize, aspectRatio, skew, 0)
- return
-
- def strLimDigits(x):
- s = str(x)
- ret = lambda x: float(x) if '.' in s else int(x)
- return str(ret(s[:max(s.find('.'), 4+1)].strip('.')))
- # return str(ret(s[:max(s.find('.'), num+1 + ('-' in s))].strip('.')))
-
- def plotNixie(n):
- painter.setPen(QPen(QColor.fromHslF(0.05, S, L, 1), 2.5, cap=Qt.RoundCap))
-
- # We use true arcs instead of polyline/Bezier.
- # Arcs are perfectly matched with original tube.
- # x = 0..20, y = 0..32
- digits = [[[ 2,00,18,16, 480,2400],[ 00,-8,48,40,2400,3360],
- [ 2,16,18,32,3360,5280],[ -28,-8,20,40,5280,6240]], # 0
- [[10,00,10,32, 0, 0]], # 1
- [[ 1,-0.5,19,17.5,4608,8640],[1,17,30,46,1680,2880],
- [1,31.5,19,31.5, 0, 0]], # 2
- [[-2,10,20,32,3500,7300],[ 2,00,19,00, 0, 0],
- [19,00, 8,10, 0, 0]], # 3
- [[ 1,22,17,00, 0, 0],[ 17,00,17,32, 0, 0],
- [1,22,17,22, 0, 0]], # 4
- [[-1,12,19,32,3500,7920],[ 4,00,18,00, 0, 0],
- [4,00, 2,14, 0, 0]], # 5
- [[00,12,20,32, 0,5760],[ 0,-10,64,54,2150,2880]], # 6
- [[ 1,00,19,00, 0, 0],[ 19,00, 8,32, 0, 0]], # 7
- [[ 1,14,19,32, 0,5760],[ 3,00,17,14, 0,5760]], # 8
- [[00,00,20,20, 0,5760],[ 20,42,-44,-22,5030,5760]]] # 9
-
- for x0, y0, x1, y1, a0, a1 in digits[n]:
- if a0 == a1 == 0:
- painter.drawLine(QPointF(x0+X-10, y0+Y-16), QPointF(x1+X-10, y1+Y-16))
- else:
- rect = QRectF(x0+X-10, y0+Y-16, x1-x0, y1-y0)
- painter.drawArc(rect, a0, a1-a0)
-
- def squareBorder(w, dw=0):
- return QRectF(X-W-w-dw, Y-W-w, (W+w+dw)*2, (W+w)*2)
-
- W = Y-1 # "radius"
- value = self.fRealValue
- hue = self.fHueA
-
- # if self.fIsButton: # TODO make it separate paintLED
- if (self.fIsInteger and (self.fMinimum == 0) and (self.fMaximum == 1)): # TODO
- # Neon lamp
- if (self.fCustomPaintMode == 64): # tube
- # bakelite lamp holder
- self.grayGradPen(painter, X, Y, -45, -1)
- self.grayGradBrush(painter, X-10, -40, 120, -2, {0.2, 50.2, 51.00, 100.00})
- painter.drawRoundedRect(squareBorder(-W*0.45), 3, 3)
-
- # neon lamp
- if (normValue > 0):
- grad = QRadialGradient(X, Y, 13)
- for i in ({0.6, 20.6, 70.4, 100.0}):
- grad.setColorAt(int(i)/100.0, QColor.fromHslF(0.05, 1.0, (i % 1.0) * normValue, 1))
- painter.setPen(QPen(Qt.NoPen))
- painter.setBrush(grad)
- painter.drawRoundedRect(squareBorder(-W*0.6), 1.5, 1.5)
-
- # glass over neon lamp
- self.grayGradPen(painter, X, Y, 135, -1)
- self.grayGradBrush(painter, X-10, -40, 124, -2, {0.9, 50.9, 51.4, 100.4}, 0.25)
- painter.drawRoundedRect(squareBorder(-W*0.6), 1.5, 1.5)
- return
-
- painter.setPen(QPen(QColor.fromHslF(0, 0, 0.3-0.15*normValue+E, 1), 1.5))
- painter.setBrush(QColor(QColor.fromHslF(hue, S, L*(normValue*0.8+0.1), 1)))
- painter.drawRoundedRect(squareBorder(-W/2-2), 1.5, 1.5)
- return
-
- if (self.fCustomPaintMode == 64) and \
- (self.fIsInteger and (self.fMinimum >= 0) and (self.fMaximum < 20)):
- #Nixie tube for 0..9, or 1 1/2 tubes for 11..19
- Y = Y - 2
- self.grayGradPen(painter, X, Y, 135, -1)
- self.grayGradBrush(painter, X-10, -40, 120, -2, {0.2, 50.2, 51.00, 100.00})
- painter.drawRoundedRect(squareBorder(-2, -W*0.2), 3, 3)
-
- if (value < 10):
- plotNixie(int(value % 10))
- else:
- if (value == 11):
- X = X + 8
- plotNixie(1)
- else:
- X = X + 4
- plotNixie(int(value % 10))
- X = X - 16
- plotNixie(1)
- return
-
- # Will it be analog display, or digital 7-segment scale?
- if not self.fIsVuOutput:
- unit = ""
- if abs(value) >= 10000.0:
- value = value / 1000.0
- unit = "k"
- if abs(value) >= 10000.0:
- value = value / 1000.0
- unit = "M"
- # Remove trailing decimal zero and decimal point also.
- if (value % 1.0) == 0:
- value = int(value)
-
- valueStr = strLimDigits(value) + unit
- valueLen = len(valueStr)
- if valueLen == 0:
- print("Zero length string from " + str(self.fRealValue) + " value.")
- return
-
- autoFontsize = int(self.getTweak('Auto7segSize', 0)) # Full auto
- autoFontwidth = int(self.getTweak('Auto7segWidth', 1)) # Width only
-
- skew = -0.2
- substrate = 1
- dY = 3 # Work in progress here. NOTE
- fntSize = 0.9
- fntAspect = 0.5
- bgLuma = 0.05
- R = 10 # Replace to W
- width = 4
- lineWidth = 2.0
-
- if self.fCustomPaintMode == 0: # default / internal
- fntSize = 0.75
- Y = Y - 2
-
- elif self.fCustomPaintMode == 16: # calf
- autoFontsize = 1
- skew = 0
- dY = 4
- bgLuma = 0.1
- R = 12
-
- elif self.fCustomPaintMode == 32: # openav
- autoFontsize = 1
- substrate = 0
- R = 13
- lineWidth = 3.0
-
- elif self.fCustomPaintMode == 48: # zynfx
- fntSize = 0.99
- Y = Y - 2
- dY = 4
- bgLuma = 0.12
- R = 12
-
- elif self.fCustomPaintMode == 64: # tube
- autoFontsize = 1
- R = 13
- lineWidth = 3.0
-
- else:
- print("Unknown paint mode "+ str(self.fCustomPaintMode) + " display.")
- return
-
- if not self.fIsVuOutput:
- if autoFontwidth and (valueLen < 4):
- if autoFontsize:
- fntSize = fntSize * 4/3
- fntAspect = 1.0 - (valueLen-1) * 0.2
-
- lineWidth = fntSize + 0.5 # Work in progress here. NOTE
-
- # substrate
- if substrate:
- substratePen = QPen(QColor.fromHslF(0, 0, 0.4+E, 1), 0.5)
- if (self.fCustomPaintMode == 64): # tube
- if not self.fIsVuOutput:
- self.grayGradPen(painter, X, Y, 135, -1)
- self.grayGradBrush(painter, X-10, -80, 200, -2, {0.2, 50.2, 51.00, 100.00})
- painter.drawRoundedRect(squareBorder(-W*0.4, W*0.3), 3, 3)
-
- else:
- if self.fCustomPaintMode == 16: # calf
- if self.fIsVuOutput:
- dY = 9
- painter.setPen(substratePen)
- painter.setBrush(QColor(QColor.fromHslF(0, 0, bgLuma, 1)))
- painter.drawRoundedRect(squareBorder(-dY, dY), 3, 3)
-
-
- color = QColor.fromHslF(hue, S, L, 1)
- painter.setPen(QPen(color, lineWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
- if not self.fIsVuOutput:
- plotStr(self, painter, X, Y, valueStr, fntSize, fntAspect, skew);
- else:
- if self.fCustomPaintMode == 16: # calf # Work in progress here. NOTE
- for i in range(0, 10+1):
- if normValue > ((i+0.5)/11):
- painter.drawLine(QPointF(X-15+i*3, Y-R/2), QPointF(X-15+i*3, Y+R/2))
- elif self.fCustomPaintMode == 64: # tube # Work in progress here. NOTE
- # Draw Cat eye.
- chamfer = 1.5 # It is best when 1.5 at normal zoom, and 1.0 for >2x HiDpi
-
- # base
- self.grayGradPen(painter, X, Y, -45, -1, width=chamfer)
- self.grayGradBrush(painter, X-10, -20, 83, -2, {0.2, 50.2, 51.00, 100.00})
- painter.drawEllipse(squareBorder(-W*0.2))
-
- # green sectors
- rays = 4 # There are 4- or 8-rays (2 or 4 notches) tubes
- gradient = QConicalGradient(X, Y, 0)
- for i in range(rays):
- sign = 1 - (i % 2) * 2
- v = min(normValue, 1) * 1.2 + 0.05 # For output, it can be > max.
- a = (i % 2) + v * sign
- b = a + 0.02 * sign
- if v > 0.99 :
- # did you notice overlapping sectors?
- gradient.setColorAt((i+a)/rays, color)
- gradient.setColorAt((i+b)/rays, color.darker(130))
- else:
- b = max(-1, min(1, b))
- gradient.setColorAt((i+a)/rays, color.darker(130))
- gradient.setColorAt((i+b)/rays, color.darker(300))
-
- self.grayGradPen(painter, X, Y, 0, -1, width=chamfer)
- painter.setBrush(gradient)
- painter.drawEllipse(squareBorder(-W*0.4))
-
- # cap is black itself, but looks dark green on working tube.
- self.drawMark(painter, X, Y, 0, 0, 0, W/4, color.darker(800))
-
- else:
- # VU scale points
- for i in range(0, 5+1):
- angle = ((0.5-i/5) * 110 + 90)
- self.drawMark(painter, X, Y + R*0.6, R*1.3, R*1.5, angle, lineWidth-0.5, color.darker(150))
-
- # VU pointer
- angle = ((0.5-normValue) * 110 + 90)
- self.drawMark(painter, X, Y + R*0.6, 0, R*1.3, angle, lineWidth, color)
-
- # Draw "settling screw" of VU meter # Work in progress here. NOTE
- if substrate:
- painter.setPen(substratePen)
- painter.drawEllipse(QRectF(X-width, Y+R*0.6-width, width*2, width*2))
-
- # ---------------------------------------------------------------------------------------------------------------------
|