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.

451 lines
17KB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Pixmap Dial, a custom Qt4 widget
  4. # Copyright (C) 2011-2014 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 (Config)
  19. from carla_config import *
  20. # ------------------------------------------------------------------------------------------------------------
  21. # Imports (Global)
  22. from math import cos, floor, pi, sin
  23. if config_UseQt5:
  24. from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize
  25. from PyQt5.QtGui import QColor, QConicalGradient, QFont, QFontMetrics
  26. from PyQt5.QtGui import QLinearGradient, QPainter, QPainterPath, QPen, QPixmap
  27. from PyQt5.QtWidgets import QDial
  28. else:
  29. from PyQt4.QtCore import pyqtSignal, pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize
  30. from PyQt4.QtGui import QColor, QConicalGradient, QFont, QFontMetrics
  31. from PyQt4.QtGui import QDial, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap
  32. # ------------------------------------------------------------------------------------------------------------
  33. # Widget Class
  34. class PixmapDial(QDial):
  35. # enum CustomPaintMode
  36. CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient)
  37. CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3)
  38. CUSTOM_PAINT_MODE_CARLA_VOL = 2 # color blue (reserved #3)
  39. CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4)
  40. CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4)
  41. CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3)
  42. CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3)
  43. CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6)
  44. CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient
  45. # enum Orientation
  46. HORIZONTAL = 0
  47. VERTICAL = 1
  48. HOVER_MIN = 0
  49. HOVER_MAX = 9
  50. # signals
  51. realValueChanged = pyqtSignal(float)
  52. def __init__(self, parent, index=0):
  53. QDial.__init__(self, parent)
  54. self.fMinimum = 0.0
  55. self.fMaximum = 1.0
  56. self.fRealValue = 0.0
  57. self.fIsHovered = False
  58. self.fHoverStep = self.HOVER_MIN
  59. self.fIndex = index
  60. self.fPixmap = QPixmap(":/bitmaps/dial_01d.png")
  61. self.fPixmapNum = "01"
  62. if self.fPixmap.width() > self.fPixmap.height():
  63. self.fPixmapOrientation = self.HORIZONTAL
  64. else:
  65. self.fPixmapOrientation = self.VERTICAL
  66. self.fLabel = ""
  67. self.fLabelPos = QPointF(0.0, 0.0)
  68. self.fLabelFont = QFont(self.font())
  69. self.fLabelFont.setPointSize(6)
  70. self.fLabelWidth = 0
  71. self.fLabelHeight = 0
  72. if self.palette().window().color().lightness() > 100:
  73. # Light background
  74. c = self.palette().dark().color()
  75. self.fLabelGradientColor1 = c
  76. self.fLabelGradientColor2 = QColor(c.red(), c.green(), c.blue(), 0)
  77. self.fLabelGradientColorT = [self.palette().buttonText().color(), self.palette().mid().color()]
  78. else:
  79. # Dark background
  80. self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
  81. self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
  82. self.fLabelGradientColorT = [Qt.white, Qt.darkGray]
  83. self.fLabelGradient = QLinearGradient(0, 0, 0, 1)
  84. self.fLabelGradient.setColorAt(0.0, self.fLabelGradientColor1)
  85. self.fLabelGradient.setColorAt(0.6, self.fLabelGradientColor1)
  86. self.fLabelGradient.setColorAt(1.0, self.fLabelGradientColor2)
  87. self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0)
  88. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL
  89. self.fCustomPaintColor = QColor(0xff, 0xff, 0xff)
  90. self.updateSizes()
  91. # Fake internal value, 10'000 precision
  92. QDial.setMinimum(self, 0)
  93. QDial.setMaximum(self, 10000)
  94. QDial.setValue(self, 0)
  95. self.valueChanged.connect(self.slot_valueChanged)
  96. def getIndex(self):
  97. return self.fIndex
  98. def getBaseSize(self):
  99. return self.fPixmapBaseSize
  100. def forceWhiteLabelGradientText(self):
  101. self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
  102. self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
  103. self.fLabelGradientColorT = [Qt.white, Qt.darkGray]
  104. def updateSizes(self):
  105. self.fPixmapWidth = self.fPixmap.width()
  106. self.fPixmapHeight = self.fPixmap.height()
  107. if self.fPixmapWidth < 1:
  108. self.fPixmapWidth = 1
  109. if self.fPixmapHeight < 1:
  110. self.fPixmapHeight = 1
  111. if self.fPixmapOrientation == self.HORIZONTAL:
  112. self.fPixmapBaseSize = self.fPixmapHeight
  113. self.fPixmapLayersCount = self.fPixmapWidth / self.fPixmapHeight
  114. else:
  115. self.fPixmapBaseSize = self.fPixmapWidth
  116. self.fPixmapLayersCount = self.fPixmapHeight / self.fPixmapWidth
  117. self.setMinimumSize(self.fPixmapBaseSize, self.fPixmapBaseSize + self.fLabelHeight + 5)
  118. self.setMaximumSize(self.fPixmapBaseSize, self.fPixmapBaseSize + self.fLabelHeight + 5)
  119. if not self.fLabel:
  120. self.fLabelHeight = 0
  121. self.fLabelWidth = 0
  122. return
  123. self.fLabelWidth = QFontMetrics(self.fLabelFont).width(self.fLabel)
  124. self.fLabelHeight = QFontMetrics(self.fLabelFont).height()
  125. self.fLabelPos.setX(float(self.fPixmapBaseSize)/2.0 - float(self.fLabelWidth)/2.0)
  126. if self.fPixmapNum in ("01", "02", "07", "08", "09", "10"):
  127. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight)
  128. elif self.fPixmapNum in ("11",):
  129. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight*2/3)
  130. else:
  131. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight/2)
  132. self.fLabelGradient.setStart(0, float(self.fPixmapBaseSize)/2.0)
  133. self.fLabelGradient.setFinalStop(0, self.fPixmapBaseSize + self.fLabelHeight + 5)
  134. self.fLabelGradientRect = QRectF(float(self.fPixmapBaseSize)/8.0, float(self.fPixmapBaseSize)/2.0, float(self.fPixmapBaseSize*3)/4.0, self.fPixmapBaseSize+self.fLabelHeight+5)
  135. def setCustomPaintMode(self, paintMode):
  136. if self.fCustomPaintMode == paintMode:
  137. return
  138. self.fCustomPaintMode = paintMode
  139. self.update()
  140. def setCustomPaintColor(self, color):
  141. if self.fCustomPaintColor == color:
  142. return
  143. self.fCustomPaintColor = color
  144. self.update()
  145. def setLabel(self, label):
  146. if self.fLabel == label:
  147. return
  148. self.fLabel = label
  149. self.updateSizes()
  150. self.update()
  151. def setIndex(self, index):
  152. self.fIndex = index
  153. def setPixmap(self, pixmapId):
  154. self.fPixmapNum = "%02i" % pixmapId
  155. self.fPixmap.load(":/bitmaps/dial_%s%s.png" % (self.fPixmapNum, "" if self.isEnabled() else "d"))
  156. if self.fPixmap.width() > self.fPixmap.height():
  157. self.fPixmapOrientation = self.HORIZONTAL
  158. else:
  159. self.fPixmapOrientation = self.VERTICAL
  160. # special pixmaps
  161. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  162. # reserved for carla-wet, carla-vol, carla-pan and color
  163. if self.fPixmapNum == "03":
  164. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_COLOR
  165. # reserved for carla-L and carla-R
  166. elif self.fPixmapNum == "04":
  167. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_CARLA_L
  168. # reserved for zita
  169. elif self.fPixmapNum == "06":
  170. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_ZITA
  171. self.updateSizes()
  172. self.update()
  173. def setMinimum(self, value):
  174. self.fMinimum = value
  175. def setMaximum(self, value):
  176. self.fMaximum = value
  177. def setValue(self, value):
  178. if self.fRealValue == value:
  179. return
  180. self.fRealValue = value
  181. normValue = float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  182. QDial.setValue(self, int(normValue * 10000))
  183. @pyqtSlot(int)
  184. def slot_valueChanged(self, value):
  185. self.fRealValue = float(value)/10000.0 * (self.fMaximum - self.fMinimum) + self.fMinimum
  186. self.realValueChanged.emit(self.fRealValue)
  187. @pyqtSlot()
  188. def slot_updatePixmap(self):
  189. self.setPixmap(int(self.fPixmapNum))
  190. def minimumSizeHint(self):
  191. return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize)
  192. def sizeHint(self):
  193. return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize)
  194. def changeEvent(self, event):
  195. if event.type() == QEvent.EnabledChange:
  196. QTimer.singleShot(0, self.slot_updatePixmap)
  197. QDial.changeEvent(self, event)
  198. def enterEvent(self, event):
  199. self.fIsHovered = True
  200. if self.fHoverStep == self.HOVER_MIN:
  201. self.fHoverStep = self.HOVER_MIN + 1
  202. QDial.enterEvent(self, event)
  203. def leaveEvent(self, event):
  204. self.fIsHovered = False
  205. if self.fHoverStep == self.HOVER_MAX:
  206. self.fHoverStep = self.HOVER_MAX - 1
  207. QDial.leaveEvent(self, event)
  208. def paintEvent(self, event):
  209. painter = QPainter(self)
  210. event.accept()
  211. painter.save()
  212. painter.setRenderHint(QPainter.Antialiasing, True)
  213. if self.fLabel:
  214. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  215. painter.setPen(self.fLabelGradientColor2)
  216. painter.setBrush(self.fLabelGradient)
  217. painter.drawRect(self.fLabelGradientRect)
  218. painter.setFont(self.fLabelFont)
  219. painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1])
  220. painter.drawText(self.fLabelPos, self.fLabel)
  221. if self.isEnabled():
  222. normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  223. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  224. curLayer = int((self.fPixmapLayersCount - 1) * normValue)
  225. if self.fPixmapOrientation == self.HORIZONTAL:
  226. xpos = self.fPixmapBaseSize * curLayer
  227. ypos = 0.0
  228. else:
  229. xpos = 0.0
  230. ypos = self.fPixmapBaseSize * curLayer
  231. source = QRectF(xpos, ypos, self.fPixmapBaseSize, self.fPixmapBaseSize)
  232. painter.drawPixmap(target, self.fPixmap, source)
  233. # Custom knobs (Dry/Wet and Volume)
  234. if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL):
  235. # knob color
  236. colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6)
  237. colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6)
  238. # draw small circle
  239. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  240. ballPath = QPainterPath()
  241. ballPath.addEllipse(ballRect)
  242. #painter.drawRect(ballRect)
  243. tmpValue = (0.375 + 0.75*normValue)
  244. ballValue = tmpValue - floor(tmpValue)
  245. ballPoint = ballPath.pointAtPercent(ballValue)
  246. # draw arc
  247. startAngle = 216*16
  248. spanAngle = -252*16*normValue
  249. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET:
  250. painter.setBrush(colorBlue)
  251. painter.setPen(QPen(colorBlue, 0))
  252. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  253. gradient = QConicalGradient(15.5, 15.5, -45)
  254. gradient.setColorAt(0.0, colorBlue)
  255. gradient.setColorAt(0.125, colorBlue)
  256. gradient.setColorAt(0.625, colorGreen)
  257. gradient.setColorAt(0.75, colorGreen)
  258. gradient.setColorAt(0.76, colorGreen)
  259. gradient.setColorAt(1.0, colorGreen)
  260. painter.setBrush(gradient)
  261. painter.setPen(QPen(gradient, 3))
  262. else:
  263. painter.setBrush(colorBlue)
  264. painter.setPen(QPen(colorBlue, 0))
  265. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  266. painter.setBrush(colorBlue)
  267. painter.setPen(QPen(colorBlue, 3))
  268. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  269. # Custom knobs (L and R)
  270. elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R):
  271. # knob color
  272. color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6)
  273. # draw small circle
  274. ballRect = QRectF(7.0, 8.0, 11.0, 12.0)
  275. ballPath = QPainterPath()
  276. ballPath.addEllipse(ballRect)
  277. #painter.drawRect(ballRect)
  278. tmpValue = (0.375 + 0.75*normValue)
  279. ballValue = tmpValue - floor(tmpValue)
  280. ballPoint = ballPath.pointAtPercent(ballValue)
  281. painter.setBrush(color)
  282. painter.setPen(QPen(color, 0))
  283. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))
  284. # draw arc
  285. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L:
  286. startAngle = 216*16
  287. spanAngle = -252.0*16*normValue
  288. else:
  289. startAngle = 324.0*16
  290. spanAngle = 252.0*16*(1.0-normValue)
  291. painter.setPen(QPen(color, 2))
  292. painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle)
  293. # Custom knobs (Color)
  294. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR:
  295. # knob color
  296. color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6)
  297. # draw small circle
  298. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  299. ballPath = QPainterPath()
  300. ballPath.addEllipse(ballRect)
  301. tmpValue = (0.375 + 0.75*normValue)
  302. ballValue = tmpValue - floor(tmpValue)
  303. ballPoint = ballPath.pointAtPercent(ballValue)
  304. # draw arc
  305. startAngle = 216*16
  306. spanAngle = -252*16*normValue
  307. painter.setBrush(color)
  308. painter.setPen(QPen(color, 0))
  309. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  310. painter.setBrush(color)
  311. painter.setPen(QPen(color, 3))
  312. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  313. # Custom knobs (Zita)
  314. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA:
  315. a = normValue * pi * 1.5 - 2.35
  316. r = 10.0
  317. x = 10.5
  318. y = 10.5
  319. x += r * sin(a)
  320. y -= r * cos(a)
  321. painter.setBrush(Qt.black)
  322. painter.setPen(QPen(Qt.black, 2))
  323. painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y))
  324. # Custom knobs
  325. else:
  326. painter.restore()
  327. return
  328. if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
  329. self.fHoverStep += 1 if self.fIsHovered else -1
  330. QTimer.singleShot(20, self.update)
  331. else: # isEnabled()
  332. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  333. painter.drawPixmap(target, self.fPixmap, target)
  334. painter.restore()
  335. def resizeEvent(self, event):
  336. QDial.resizeEvent(self, event)
  337. self.updateSizes()
  338. # ------------------------------------------------------------------------------------------------------------
  339. # Main Testing
  340. if __name__ == '__main__':
  341. import sys
  342. from PyQt4.QtGui import QApplication
  343. import resources_rc
  344. app = QApplication(sys.argv)
  345. gui = PixmapDial(None)
  346. #gui.setEnabled(True)
  347. #gui.setEnabled(False)
  348. gui.setPixmap(3)
  349. gui.setLabel("hahaha")
  350. gui.show()
  351. sys.exit(app.exec_())