Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scalabledial.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Scalable Dial, a custom Qt widget
  4. # Copyright (C) 2011-2022 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ---------------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from math import cos, floor, pi, sin
  20. from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize
  21. from PyQt5.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap
  22. from PyQt5.QtSvg import QSvgWidget
  23. from .commondial import CommonDial
  24. # ---------------------------------------------------------------------------------------------------------------------
  25. # Widget Class
  26. class ScalableDial(CommonDial):
  27. def __init__(self, parent, index=0):
  28. CommonDial.__init__(self, parent, index)
  29. self.fImage = QSvgWidget(":/scalable/dial_03.svg")
  30. self.fImageNum = "01"
  31. if self.fImage.sizeHint().width() > self.fImage.sizeHint().height():
  32. self.fImageOrientation = self.HORIZONTAL
  33. else:
  34. self.fImageOrientation = self.VERTICAL
  35. self.updateSizes()
  36. def getBaseSize(self):
  37. return self.fImageBaseSize
  38. def updateSizes(self):
  39. if isinstance(self.fImage, QPixmap):
  40. self.fImageWidth = self.fImage.width()
  41. self.fImageHeight = self.fImage.height()
  42. else:
  43. self.fImageWidth = self.fImage.sizeHint().width()
  44. self.fImageHeight = self.fImage.sizeHint().height()
  45. if self.fImageWidth < 1:
  46. self.fImageWidth = 1
  47. if self.fImageHeight < 1:
  48. self.fImageHeight = 1
  49. if self.fImageOrientation == self.HORIZONTAL:
  50. self.fImageBaseSize = self.fImageHeight
  51. self.fImageLayersCount = self.fImageWidth / self.fImageHeight
  52. else:
  53. self.fImageBaseSize = self.fImageWidth
  54. self.fImageLayersCount = self.fImageHeight / self.fImageWidth
  55. self.setMinimumSize(self.fImageBaseSize, self.fImageBaseSize + self.fLabelHeight + 5)
  56. self.setMaximumSize(self.fImageBaseSize, self.fImageBaseSize + self.fLabelHeight + 5)
  57. if not self.fLabel:
  58. self.fLabelHeight = 0
  59. self.fLabelWidth = 0
  60. return
  61. self.fLabelWidth = QFontMetrics(self.fLabelFont).width(self.fLabel)
  62. self.fLabelHeight = QFontMetrics(self.fLabelFont).height()
  63. self.fLabelPos.setX(float(self.fImageBaseSize)/2.0 - float(self.fLabelWidth)/2.0)
  64. if self.fImageNum in ("01", "02", "07", "08", "09", "10"):
  65. self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight)
  66. elif self.fImageNum in ("11",):
  67. self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight*2/3)
  68. else:
  69. self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight/2)
  70. self.fLabelGradient.setStart(0, float(self.fImageBaseSize)/2.0)
  71. self.fLabelGradient.setFinalStop(0, self.fImageBaseSize + self.fLabelHeight + 5)
  72. self.fLabelGradientRect = QRectF(float(self.fImageBaseSize)/8.0, float(self.fImageBaseSize)/2.0,
  73. float(self.fImageBaseSize*3)/4.0, self.fImageBaseSize+self.fLabelHeight+5)
  74. def setImage(self, imageId):
  75. self.fImageNum = "%02i" % imageId
  76. if imageId in (2,6,7,8,9,10,11,12,13):
  77. img = ":/bitmaps/dial_%s%s.png" % (self.fImageNum, "" if self.isEnabled() else "d")
  78. else:
  79. img = ":/scalable/dial_%s%s.svg" % (self.fImageNum, "" if self.isEnabled() else "d")
  80. if img.endswith(".png"):
  81. if not isinstance(self.fImage, QPixmap):
  82. self.fImage = QPixmap()
  83. else:
  84. if not isinstance(self.fImage, QSvgWidget):
  85. self.fImage = QSvgWidget()
  86. self.fImage.load(img)
  87. if self.fImage.width() > self.fImage.height():
  88. self.fImageOrientation = self.HORIZONTAL
  89. else:
  90. self.fImageOrientation = self.VERTICAL
  91. # special svgs
  92. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  93. # reserved for carla-wet, carla-vol, carla-pan and color
  94. if self.fImageNum == "03":
  95. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_COLOR
  96. # reserved for carla-L and carla-R
  97. elif self.fImageNum == "04":
  98. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_CARLA_L
  99. # reserved for zita
  100. elif self.fImageNum == "06":
  101. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_ZITA
  102. self.updateSizes()
  103. self.update()
  104. @pyqtSlot()
  105. def slot_updateImage(self):
  106. self.setImage(int(self.fImageNum))
  107. def minimumSizeHint(self):
  108. return QSize(self.fImageBaseSize, self.fImageBaseSize)
  109. def sizeHint(self):
  110. return QSize(self.fImageBaseSize, self.fImageBaseSize)
  111. def changeEvent(self, event):
  112. CommonDial.changeEvent(self, event)
  113. # Force svg update if enabled state changes
  114. if event.type() == QEvent.EnabledChange:
  115. self.slot_updateImage()
  116. def paintDial(self, painter):
  117. if self.isEnabled():
  118. normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  119. curLayer = int((self.fImageLayersCount - 1) * normValue)
  120. if self.fImageOrientation == self.HORIZONTAL:
  121. xpos = self.fImageBaseSize * curLayer
  122. ypos = 0.0
  123. else:
  124. xpos = 0.0
  125. ypos = self.fImageBaseSize * curLayer
  126. source = QRectF(xpos, ypos, self.fImageBaseSize, self.fImageBaseSize)
  127. if isinstance(self.fImage, QPixmap):
  128. target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize)
  129. painter.drawPixmap(target, self.fImage, source)
  130. else:
  131. self.fImage.renderer().render(painter, source)
  132. # Custom knobs (Dry/Wet and Volume)
  133. if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL):
  134. # knob color
  135. colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6)
  136. colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6)
  137. # draw small circle
  138. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  139. ballPath = QPainterPath()
  140. ballPath.addEllipse(ballRect)
  141. #painter.drawRect(ballRect)
  142. tmpValue = (0.375 + 0.75*normValue)
  143. ballValue = tmpValue - floor(tmpValue)
  144. ballPoint = ballPath.pointAtPercent(ballValue)
  145. # draw arc
  146. startAngle = 218*16
  147. spanAngle = -255*16*normValue
  148. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET:
  149. painter.setBrush(colorBlue)
  150. painter.setPen(QPen(colorBlue, 0))
  151. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  152. gradient = QConicalGradient(15.5, 15.5, -45)
  153. gradient.setColorAt(0.0, colorBlue)
  154. gradient.setColorAt(0.125, colorBlue)
  155. gradient.setColorAt(0.625, colorGreen)
  156. gradient.setColorAt(0.75, colorGreen)
  157. gradient.setColorAt(0.76, colorGreen)
  158. gradient.setColorAt(1.0, colorGreen)
  159. painter.setBrush(gradient)
  160. painter.setPen(QPen(gradient, 3))
  161. else:
  162. painter.setBrush(colorBlue)
  163. painter.setPen(QPen(colorBlue, 0))
  164. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  165. painter.setBrush(colorBlue)
  166. painter.setPen(QPen(colorBlue, 3))
  167. painter.drawArc(QRectF(4.0, 4.0, 26.0, 26.0), int(startAngle), int(spanAngle))
  168. # Custom knobs (L and R)
  169. elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R):
  170. # knob color
  171. color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6)
  172. # draw small circle
  173. ballRect = QRectF(7.0, 8.0, 11.0, 12.0)
  174. ballPath = QPainterPath()
  175. ballPath.addEllipse(ballRect)
  176. #painter.drawRect(ballRect)
  177. tmpValue = (0.375 + 0.75*normValue)
  178. ballValue = tmpValue - floor(tmpValue)
  179. ballPoint = ballPath.pointAtPercent(ballValue)
  180. painter.setBrush(color)
  181. painter.setPen(QPen(color, 0))
  182. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))
  183. # draw arc
  184. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L:
  185. startAngle = 218*16
  186. spanAngle = -255*16*normValue
  187. else:
  188. startAngle = 322.0*16
  189. spanAngle = 255.0*16*(1.0-normValue)
  190. painter.setPen(QPen(color, 2.5))
  191. painter.drawArc(QRectF(3.5, 3.5, 22.0, 22.0), int(startAngle), int(spanAngle))
  192. # Custom knobs (Color)
  193. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR:
  194. # knob color
  195. color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6)
  196. # draw small circle
  197. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  198. ballPath = QPainterPath()
  199. ballPath.addEllipse(ballRect)
  200. tmpValue = (0.375 + 0.75*normValue)
  201. ballValue = tmpValue - floor(tmpValue)
  202. ballPoint = ballPath.pointAtPercent(ballValue)
  203. # draw arc
  204. startAngle = 218*16
  205. spanAngle = -255*16*normValue
  206. painter.setBrush(color)
  207. painter.setPen(QPen(color, 0))
  208. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  209. painter.setBrush(color)
  210. painter.setPen(QPen(color, 3))
  211. painter.drawArc(QRectF(4.0, 4.8, 26.0, 26.0), int(startAngle), int(spanAngle))
  212. # Custom knobs (Zita)
  213. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA:
  214. a = normValue * pi * 1.5 - 2.35
  215. r = 10.0
  216. x = 10.5
  217. y = 10.5
  218. x += r * sin(a)
  219. y -= r * cos(a)
  220. painter.setBrush(Qt.black)
  221. painter.setPen(QPen(Qt.black, 2))
  222. painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y))
  223. # Custom knobs
  224. else:
  225. painter.restore()
  226. return
  227. if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
  228. self.fHoverStep += 1 if self.fIsHovered else -1
  229. QTimer.singleShot(20, self.update)
  230. else: # isEnabled()
  231. target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize)
  232. if isinstance(self.fImage, QPixmap):
  233. painter.drawPixmap(target, self.fImage, target)
  234. else:
  235. self.fImage.renderer().render(painter, target)
  236. # ---------------------------------------------------------------------------------------------------------------------