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.

453 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.setPixelSize(8)
  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. QDial.changeEvent(self, event)
  196. # Force pixmap update if enabled state changes
  197. if event.type() == QEvent.EnabledChange:
  198. self.setPixmap(int(self.fPixmapNum))
  199. def enterEvent(self, event):
  200. self.fIsHovered = True
  201. if self.fHoverStep == self.HOVER_MIN:
  202. self.fHoverStep = self.HOVER_MIN + 1
  203. QDial.enterEvent(self, event)
  204. def leaveEvent(self, event):
  205. self.fIsHovered = False
  206. if self.fHoverStep == self.HOVER_MAX:
  207. self.fHoverStep = self.HOVER_MAX - 1
  208. QDial.leaveEvent(self, event)
  209. def paintEvent(self, event):
  210. painter = QPainter(self)
  211. event.accept()
  212. painter.save()
  213. painter.setRenderHint(QPainter.Antialiasing, True)
  214. if self.fLabel:
  215. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
  216. painter.setPen(self.fLabelGradientColor2)
  217. painter.setBrush(self.fLabelGradient)
  218. painter.drawRect(self.fLabelGradientRect)
  219. painter.setFont(self.fLabelFont)
  220. painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1])
  221. painter.drawText(self.fLabelPos, self.fLabel)
  222. if self.isEnabled():
  223. normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  224. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  225. curLayer = int((self.fPixmapLayersCount - 1) * normValue)
  226. if self.fPixmapOrientation == self.HORIZONTAL:
  227. xpos = self.fPixmapBaseSize * curLayer
  228. ypos = 0.0
  229. else:
  230. xpos = 0.0
  231. ypos = self.fPixmapBaseSize * curLayer
  232. source = QRectF(xpos, ypos, self.fPixmapBaseSize, self.fPixmapBaseSize)
  233. painter.drawPixmap(target, self.fPixmap, source)
  234. # Custom knobs (Dry/Wet and Volume)
  235. if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL):
  236. # knob color
  237. colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6)
  238. colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6)
  239. # draw small circle
  240. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  241. ballPath = QPainterPath()
  242. ballPath.addEllipse(ballRect)
  243. #painter.drawRect(ballRect)
  244. tmpValue = (0.375 + 0.75*normValue)
  245. ballValue = tmpValue - floor(tmpValue)
  246. ballPoint = ballPath.pointAtPercent(ballValue)
  247. # draw arc
  248. startAngle = 216*16
  249. spanAngle = -252*16*normValue
  250. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET:
  251. painter.setBrush(colorBlue)
  252. painter.setPen(QPen(colorBlue, 0))
  253. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  254. gradient = QConicalGradient(15.5, 15.5, -45)
  255. gradient.setColorAt(0.0, colorBlue)
  256. gradient.setColorAt(0.125, colorBlue)
  257. gradient.setColorAt(0.625, colorGreen)
  258. gradient.setColorAt(0.75, colorGreen)
  259. gradient.setColorAt(0.76, colorGreen)
  260. gradient.setColorAt(1.0, colorGreen)
  261. painter.setBrush(gradient)
  262. painter.setPen(QPen(gradient, 3))
  263. else:
  264. painter.setBrush(colorBlue)
  265. painter.setPen(QPen(colorBlue, 0))
  266. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  267. painter.setBrush(colorBlue)
  268. painter.setPen(QPen(colorBlue, 3))
  269. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  270. # Custom knobs (L and R)
  271. elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R):
  272. # knob color
  273. color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6)
  274. # draw small circle
  275. ballRect = QRectF(7.0, 8.0, 11.0, 12.0)
  276. ballPath = QPainterPath()
  277. ballPath.addEllipse(ballRect)
  278. #painter.drawRect(ballRect)
  279. tmpValue = (0.375 + 0.75*normValue)
  280. ballValue = tmpValue - floor(tmpValue)
  281. ballPoint = ballPath.pointAtPercent(ballValue)
  282. painter.setBrush(color)
  283. painter.setPen(QPen(color, 0))
  284. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0))
  285. # draw arc
  286. if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L:
  287. startAngle = 216*16
  288. spanAngle = -252.0*16*normValue
  289. else:
  290. startAngle = 324.0*16
  291. spanAngle = 252.0*16*(1.0-normValue)
  292. painter.setPen(QPen(color, 2))
  293. painter.drawArc(3.5, 4.5, 22.0, 22.0, startAngle, spanAngle)
  294. # Custom knobs (Color)
  295. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR:
  296. # knob color
  297. color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6)
  298. # draw small circle
  299. ballRect = QRectF(8.0, 8.0, 15.0, 15.0)
  300. ballPath = QPainterPath()
  301. ballPath.addEllipse(ballRect)
  302. tmpValue = (0.375 + 0.75*normValue)
  303. ballValue = tmpValue - floor(tmpValue)
  304. ballPoint = ballPath.pointAtPercent(ballValue)
  305. # draw arc
  306. startAngle = 216*16
  307. spanAngle = -252*16*normValue
  308. painter.setBrush(color)
  309. painter.setPen(QPen(color, 0))
  310. painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2))
  311. painter.setBrush(color)
  312. painter.setPen(QPen(color, 3))
  313. painter.drawArc(4.0, 4.0, 26.0, 26.0, startAngle, spanAngle)
  314. # Custom knobs (Zita)
  315. elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA:
  316. a = normValue * pi * 1.5 - 2.35
  317. r = 10.0
  318. x = 10.5
  319. y = 10.5
  320. x += r * sin(a)
  321. y -= r * cos(a)
  322. painter.setBrush(Qt.black)
  323. painter.setPen(QPen(Qt.black, 2))
  324. painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y))
  325. # Custom knobs
  326. else:
  327. painter.restore()
  328. return
  329. if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
  330. self.fHoverStep += 1 if self.fIsHovered else -1
  331. QTimer.singleShot(20, self.update)
  332. else: # isEnabled()
  333. target = QRectF(0.0, 0.0, self.fPixmapBaseSize, self.fPixmapBaseSize)
  334. painter.drawPixmap(target, self.fPixmap, target)
  335. painter.restore()
  336. def resizeEvent(self, event):
  337. QDial.resizeEvent(self, event)
  338. self.updateSizes()
  339. # ------------------------------------------------------------------------------------------------------------
  340. # Main Testing
  341. if __name__ == '__main__':
  342. import sys
  343. from PyQt4.QtGui import QApplication
  344. import resources_rc
  345. app = QApplication(sys.argv)
  346. gui = PixmapDial(None)
  347. #gui.setEnabled(True)
  348. #gui.setEnabled(False)
  349. gui.setPixmap(3)
  350. gui.setLabel("hahaha")
  351. gui.show()
  352. sys.exit(app.exec_())