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.

449 lines
16KB

  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"):
  127. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight)
  128. else:
  129. self.fLabelPos.setY(self.fPixmapBaseSize + self.fLabelHeight/2)
  130. self.fLabelGradient.setStart(0, float(self.fPixmapBaseSize)/2.0)
  131. self.fLabelGradient.setFinalStop(0, self.fPixmapBaseSize + self.fLabelHeight + 5)
  132. self.fLabelGradientRect = QRectF(float(self.fPixmapBaseSize)/8.0, float(self.fPixmapBaseSize)/2.0, float(self.fPixmapBaseSize*3)/4.0, self.fPixmapBaseSize+self.fLabelHeight+5)
  133. def setCustomPaintMode(self, paintMode):
  134. if self.fCustomPaintMode == paintMode:
  135. return
  136. self.fCustomPaintMode = paintMode
  137. self.update()
  138. def setCustomPaintColor(self, color):
  139. if self.fCustomPaintColor == color:
  140. return
  141. self.fCustomPaintColor = color
  142. self.update()
  143. def setLabel(self, label):
  144. if self.fLabel == label:
  145. return
  146. self.fLabel = label
  147. self.updateSizes()
  148. self.update()
  149. def setIndex(self, index):
  150. self.fIndex = index
  151. def setPixmap(self, pixmapId):
  152. self.fPixmapNum = "%02i" % pixmapId
  153. self.fPixmap.load(":/bitmaps/dial_%s%s.png" % (self.fPixmapNum, "" if self.isEnabled() else "d"))
  154. if self.fPixmap.width() > self.fPixmap.height():
  155. self.fPixmapOrientation = self.HORIZONTAL
  156. else:
  157. self.fPixmapOrientation = self.VERTICAL
  158. # special pixmaps
  159. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  160. # reserved for carla-wet, carla-vol, carla-pan and color
  161. if self.fPixmapNum == "03":
  162. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_COLOR
  163. # reserved for carla-L and carla-R
  164. elif self.fPixmapNum == "04":
  165. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_CARLA_L
  166. # reserved for zita
  167. elif self.fPixmapNum == "06":
  168. self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_ZITA
  169. self.updateSizes()
  170. self.update()
  171. def setMinimum(self, value):
  172. self.fMinimum = value
  173. def setMaximum(self, value):
  174. self.fMaximum = value
  175. def setValue(self, value):
  176. if self.fRealValue == value:
  177. return
  178. self.fRealValue = value
  179. normValue = float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  180. QDial.setValue(self, int(normValue * 10000))
  181. @pyqtSlot(int)
  182. def slot_valueChanged(self, value):
  183. self.fRealValue = float(value)/10000.0 * (self.fMaximum - self.fMinimum) + self.fMinimum
  184. self.realValueChanged.emit(self.fRealValue)
  185. @pyqtSlot()
  186. def slot_updatePixmap(self):
  187. self.setPixmap(int(self.fPixmapNum))
  188. def minimumSizeHint(self):
  189. return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize)
  190. def sizeHint(self):
  191. return QSize(self.fPixmapBaseSize, self.fPixmapBaseSize)
  192. def changeEvent(self, event):
  193. if event.type() == QEvent.EnabledChange:
  194. QTimer.singleShot(0, self.slot_updatePixmap)
  195. QDial.changeEvent(self, event)
  196. def enterEvent(self, event):
  197. self.fIsHovered = True
  198. if self.fHoverStep == self.HOVER_MIN:
  199. self.fHoverStep = self.HOVER_MIN + 1
  200. QDial.enterEvent(self, event)
  201. def leaveEvent(self, event):
  202. self.fIsHovered = False
  203. if self.fHoverStep == self.HOVER_MAX:
  204. self.fHoverStep = self.HOVER_MAX - 1
  205. QDial.leaveEvent(self, event)
  206. def paintEvent(self, event):
  207. painter = QPainter(self)
  208. event.accept()
  209. painter.save()
  210. painter.setRenderHint(QPainter.Antialiasing, True)
  211. if self.fLabel:
  212. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  213. painter.setPen(self.fLabelGradientColor2)
  214. painter.setBrush(self.fLabelGradient)
  215. painter.drawRect(self.fLabelGradientRect)
  216. painter.setFont(self.fLabelFont)
  217. painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1])
  218. painter.drawText(self.fLabelPos, self.fLabel)
  219. if self.isEnabled():
  220. normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  221. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  222. curLayer = int((self.fPixmapLayersCount - 1) * normValue)
  223. if self.fPixmapOrientation == self.HORIZONTAL:
  224. xpos = self.fPixmapBaseSize * curLayer
  225. ypos = 0.0
  226. else:
  227. xpos = 0.0
  228. ypos = self.fPixmapBaseSize * curLayer
  229. source = QRectF(xpos, ypos, self.fPixmapBaseSize, self.fPixmapBaseSize)
  230. painter.drawPixmap(target, self.fPixmap, source)
  231. # Custom knobs (Dry/Wet and Volume)
  232. if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL):
  233. # knob color
  234. colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6)
  235. colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6)
  236. # draw small circle
  237. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  238. ballPath = QPainterPath()
  239. ballPath.addEllipse(ballRect)
  240. #painter.drawRect(ballRect)
  241. tmpValue = (0.375 + 0.75*normValue)
  242. ballValue = tmpValue - floor(tmpValue)
  243. ballPoint = ballPath.pointAtPercent(ballValue)
  244. # draw arc
  245. startAngle = 216*16
  246. spanAngle = -252*16*normValue
  247. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET:
  248. painter.setBrush(colorBlue)
  249. painter.setPen(QPen(colorBlue, 0))
  250. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  251. gradient = QConicalGradient(15.5, 15.5, -45)
  252. gradient.setColorAt(0.0, colorBlue)
  253. gradient.setColorAt(0.125, colorBlue)
  254. gradient.setColorAt(0.625, colorGreen)
  255. gradient.setColorAt(0.75, colorGreen)
  256. gradient.setColorAt(0.76, colorGreen)
  257. gradient.setColorAt(1.0, colorGreen)
  258. painter.setBrush(gradient)
  259. painter.setPen(QPen(gradient, 3))
  260. else:
  261. painter.setBrush(colorBlue)
  262. painter.setPen(QPen(colorBlue, 0))
  263. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  264. painter.setBrush(colorBlue)
  265. painter.setPen(QPen(colorBlue, 3))
  266. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  267. # Custom knobs (L and R)
  268. elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R):
  269. # knob color
  270. color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6)
  271. # draw small circle
  272. ballRect = QRectF(7.0, 8.0, 11.0, 12.0)
  273. ballPath = QPainterPath()
  274. ballPath.addEllipse(ballRect)
  275. #painter.drawRect(ballRect)
  276. tmpValue = (0.375 + 0.75*normValue)
  277. ballValue = tmpValue - floor(tmpValue)
  278. ballPoint = ballPath.pointAtPercent(ballValue)
  279. painter.setBrush(color)
  280. painter.setPen(QPen(color, 0))
  281. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))
  282. # draw arc
  283. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L:
  284. startAngle = 216*16
  285. spanAngle = -252.0*16*normValue
  286. else:
  287. startAngle = 324.0*16
  288. spanAngle = 252.0*16*(1.0-normValue)
  289. painter.setPen(QPen(color, 2))
  290. painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle)
  291. # Custom knobs (Color)
  292. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR:
  293. # knob color
  294. color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6)
  295. # draw small circle
  296. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  297. ballPath = QPainterPath()
  298. ballPath.addEllipse(ballRect)
  299. tmpValue = (0.375 + 0.75*normValue)
  300. ballValue = tmpValue - floor(tmpValue)
  301. ballPoint = ballPath.pointAtPercent(ballValue)
  302. # draw arc
  303. startAngle = 216*16
  304. spanAngle = -252*16*normValue
  305. painter.setBrush(color)
  306. painter.setPen(QPen(color, 0))
  307. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  308. painter.setBrush(color)
  309. painter.setPen(QPen(color, 3))
  310. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  311. # Custom knobs (Zita)
  312. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA:
  313. a = normValue * pi * 1.5 - 2.35
  314. r = 10.0
  315. x = 10.5
  316. y = 10.5
  317. x += r * sin(a)
  318. y -= r * cos(a)
  319. painter.setBrush(Qt.black)
  320. painter.setPen(QPen(Qt.black, 2))
  321. painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y))
  322. # Custom knobs
  323. else:
  324. painter.restore()
  325. return
  326. if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
  327. self.fHoverStep += 1 if self.fIsHovered else -1
  328. QTimer.singleShot(20, self.update)
  329. else: # isEnabled()
  330. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  331. painter.drawPixmap(target, self.fPixmap, target)
  332. painter.restore()
  333. def resizeEvent(self, event):
  334. QDial.resizeEvent(self, event)
  335. self.updateSizes()
  336. # ------------------------------------------------------------------------------------------------------------
  337. # Main Testing
  338. if __name__ == '__main__':
  339. import sys
  340. from PyQt4.QtGui import QApplication
  341. import resources_rc
  342. app = QApplication(sys.argv)
  343. gui = PixmapDial(None)
  344. #gui.setEnabled(True)
  345. #gui.setEnabled(False)
  346. gui.setPixmap(3)
  347. gui.setLabel("hahaha")
  348. gui.show()
  349. sys.exit(app.exec_())