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.

507 lines
18KB

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