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.

523 lines
19KB

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