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.

416 lines
15KB

  1. #!/usr/bin/env python3
  2. # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
  3. # SPDX-License-Identifier: GPL-2.0-or-later
  4. # ---------------------------------------------------------------------------------------------------------------------
  5. # Imports (Global)
  6. from math import floor, ceil
  7. from qt_compat import qt_config
  8. if qt_config == 5:
  9. from PyQt5.QtCore import pyqtSignal, QT_VERSION, Qt, QRectF, QTimer, QEvent, QPoint
  10. from PyQt5.QtGui import QBrush, QColor, QCursor, QPainter, QPainterPath, QPen, QPixmap
  11. from PyQt5.QtWidgets import QFrame, QGraphicsScene
  12. elif qt_config == 6:
  13. from PyQt6.QtCore import pyqtSignal, QT_VERSION, Qt, QRectF, QTimer, QEvent, QPoint
  14. from PyQt6.QtGui import QBrush, QColor, QCursor, QPainter, QPainterPath, QPen, QPixmap
  15. from PyQt6.QtWidgets import QFrame, QGraphicsScene
  16. # ---------------------------------------------------------------------------------------------------------------------
  17. # Antialiasing settings
  18. from patchcanvas import options, ANTIALIASING_FULL
  19. # ---------------------------------------------------------------------------------------------------------------------
  20. # Widget Class
  21. class CanvasPreviewFrame(QFrame):
  22. miniCanvasMoved = pyqtSignal(float, float)
  23. # x = 2
  24. # y = 2
  25. # w = width - 4
  26. # h = height - 3
  27. # bounds -1 px
  28. _kRectX = 0
  29. _kRectY = 1
  30. _kRectWidth = 2
  31. _kRectHeight = 3
  32. _kCursorName = 0
  33. _kCursorZoom = 1
  34. _kCursorZoomIn = 2
  35. _kCursorZoomOut = 3
  36. _kCursorZoomCount = 4
  37. _MOUSE_MODE_NONE = 0
  38. _MOUSE_MODE_MOVE = 1
  39. _MOUSE_MODE_SCALE = 2
  40. _RUBBERBAND_BLENDING_DEFAULT = 0
  41. _RUBBERBAND_BLENDING_PLUS = 1
  42. _RUBBERBAND_BLENDING_DIFF = 2
  43. # -----------------------------------------------------------------------------------------------------------------
  44. def __init__(self, parent):
  45. QFrame.__init__(self, parent)
  46. #self.setMinimumWidth(210)
  47. #self.setMinimumHeight(162)
  48. #self.setMaximumHeight(162)
  49. self.fRealParent = None
  50. self.fInternalWidth = 210-6 # -4 for width + -1px*2 bounds
  51. self.fInternalHeight = 162-5 # -3 for height + -1px*2 bounds
  52. self.fInternalRatio = self.fInternalWidth / self.fInternalHeight
  53. self.fScene = None
  54. self.fRenderSource = QRectF(0.0, 0.0, 0.0, 0.0)
  55. self.fRenderTarget = QRectF(0.0, 0.0, 0.0, 0.0)
  56. self.fUseCustomPaint = False
  57. self.fFrameWidth = 0.0
  58. self.fInitialX = 0.0
  59. self.fInitialY = 0.0
  60. self.fScale = 1.0
  61. self.fViewBg = QColor(0, 0, 0)
  62. self.fViewBrush = QBrush(QColor(75, 75, 255, 30))
  63. self.fViewPen = QPen(Qt.blue, 1)
  64. self.fViewRect = [3.0, 3.0, 10.0, 10.0]
  65. self.fMouseMode = self._MOUSE_MODE_NONE
  66. self.fMouseLeftDown = False
  67. self.fMouseRightDown = False
  68. self.fMouseInitialZoomPos = None
  69. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DEFAULT
  70. self.fZoomCursors = [None for _ in range(self._kCursorZoomCount)]
  71. self.fZoomCursors[self._kCursorName] = "black"
  72. self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap(":/cursors/zoom_black.png"), 8, 7)
  73. self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap(":/cursors/zoom-in_black.png"), 8, 7)
  74. self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap(":/cursors/zoom-out_black.png"), 8, 7)
  75. def init(self, scene: QGraphicsScene, realWidth: float, realHeight: float, useCustomPaint: bool = False):
  76. self.fScene = scene
  77. self.fRenderSource = QRectF(0.0, 0.0, realWidth, realHeight)
  78. self.fInternalRatio = realWidth / realHeight
  79. self._updateStyle()
  80. if self.fUseCustomPaint != useCustomPaint:
  81. self.fUseCustomPaint = useCustomPaint
  82. self.repaint()
  83. def setRealParent(self, parent):
  84. self.fRealParent = parent
  85. # -----------------------------------------------------------------------------------------------------------------
  86. def setViewPosX(self, xp: float):
  87. self.fViewRect[self._kRectX] = xp * (self.fInternalWidth - self.fViewRect[self._kRectWidth]/self.fScale)
  88. self.update()
  89. def setViewPosY(self, yp: float):
  90. self.fViewRect[self._kRectY] = yp * (self.fInternalHeight - self.fViewRect[self._kRectHeight]/self.fScale)
  91. self.update()
  92. def setViewScale(self, scale: float):
  93. self.fScale = scale
  94. if self.fRealParent is not None:
  95. QTimer.singleShot(0, self.fRealParent.slot_miniCanvasCheckAll)
  96. def setViewSize(self, width: float, height: float):
  97. self.fViewRect[self._kRectWidth] = width * self.fInternalWidth
  98. self.fViewRect[self._kRectHeight] = height * self.fInternalHeight
  99. self.update()
  100. def setViewTheme(self, bgColor, brushColor, penColor):
  101. bg_black = bgColor.blackF()
  102. brush_black = brushColor.blackF()
  103. r0, g0, b0, _ = bgColor.getRgb()
  104. r1, g1, b1, _ = brushColor.getRgb()
  105. if brush_black < bg_black:
  106. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_PLUS
  107. brushColor = QColor(r1-r0, g1-g0, b1-b0, 40)
  108. elif bg_black < brush_black:
  109. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DIFF
  110. brushColor = QColor(r0-r1, g0-g1, b0-b1, 40)
  111. else:
  112. bgColor.setAlpha(40)
  113. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DEFAULT
  114. penColor.setAlpha(100)
  115. self.fViewBg = bgColor
  116. self.fViewBrush = QBrush(brushColor)
  117. self.fViewPen = QPen(penColor, 1)
  118. cursorName = "black" if bg_black < 0.5 else "white"
  119. if self.fZoomCursors[self._kCursorName] != cursorName:
  120. prefix = ":/cursors/zoom"
  121. self.fZoomCursors[self._kCursorName] = cursorName
  122. self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap(f"{prefix}_{cursorName}.png"), 8, 7)
  123. self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap(f"{prefix}-in_{cursorName}.png"), 8, 7)
  124. self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap(f"{prefix}-out_{cursorName}.png"), 8, 7)
  125. # -----------------------------------------------------------------------------------------------------------------
  126. def changeEvent(self, event):
  127. if event.type() in (QEvent.StyleChange, QEvent.PaletteChange):
  128. self._updateStyle()
  129. QFrame.changeEvent(self, event)
  130. def mousePressEvent(self, event):
  131. if event.button() == Qt.LeftButton:
  132. event.accept()
  133. self.fMouseLeftDown = True
  134. self._updateMouseMode(event)
  135. return
  136. if event.button() == Qt.RightButton:
  137. event.accept()
  138. self.fMouseRightDown = True
  139. self._updateMouseMode(event)
  140. return
  141. if event.button() == Qt.MiddleButton:
  142. event.accept()
  143. self.fMouseLeftDown = True
  144. self.fMouseRightDown = True
  145. self._updateMouseMode(event)
  146. return
  147. QFrame.mouseMoveEvent(self, event)
  148. def mouseMoveEvent(self, event):
  149. if self.fMouseMode == self._MOUSE_MODE_MOVE:
  150. event.accept()
  151. if QT_VERSION >= 0x60000:
  152. pos = event.position()
  153. x = pos.x()
  154. y = pos.y()
  155. else:
  156. x = event.x()
  157. y = event.y()
  158. self._moveViewRect(x, y)
  159. return
  160. if self.fMouseMode == self._MOUSE_MODE_SCALE:
  161. event.accept()
  162. self._scaleViewRect(event.globalY())
  163. return
  164. QFrame.mouseMoveEvent(self, event)
  165. def mouseReleaseEvent(self, event):
  166. if event.button() == Qt.LeftButton:
  167. event.accept()
  168. self.fMouseLeftDown = False
  169. self._updateMouseMode(event)
  170. return
  171. if event.button() == Qt.RightButton:
  172. event.accept()
  173. self.fMouseRightDown = False
  174. self._updateMouseMode(event)
  175. return
  176. if event.button() == Qt.MiddleButton:
  177. event.accept()
  178. self.fMouseLeftDown = event.buttons() & Qt.LeftButton
  179. self.fMouseRightDown = event.buttons() & Qt.RightButton
  180. self._updateMouseMode(event)
  181. return
  182. QFrame.mouseReleaseEvent(self, event)
  183. def wheelEvent(self, event):
  184. if self.fScene is None:
  185. QFrame.wheelEvent(self, event)
  186. return
  187. event.accept()
  188. self.fScene.zoom_wheel(event.angleDelta().y())
  189. def paintEvent(self, event):
  190. if self.fScene is None:
  191. QFrame.paintEvent(self, event)
  192. return
  193. painter = QPainter(self)
  194. painter.setRenderHint(QPainter.Antialiasing, bool(options.antialiasing == ANTIALIASING_FULL))
  195. # Brightness-aware out-of-canvas shading
  196. bg_color = self.fViewBg
  197. bg_black = bg_color.black()
  198. bg_shade = -12 if bg_black < 127 else 12
  199. r,g,b,_ = bg_color.getRgb()
  200. bg_color = QColor(r+bg_shade, g+bg_shade, b+bg_shade)
  201. frameWidth = self.fFrameWidth
  202. if self.fUseCustomPaint:
  203. # Inner shadow
  204. color = QColor.fromHsv(40, 0, 255-max(210, bg_color.black(), bg_black))
  205. painter.setBrush(Qt.transparent)
  206. painter.setPen(color)
  207. painter.drawRect(QRectF(0.5, 0.5, self.width()-1, self.height()-1))
  208. # Background
  209. painter.setBrush(bg_color)
  210. painter.setPen(bg_color)
  211. painter.drawRect(QRectF(1.5, 1.5, self.width()-3, self.height()-3))
  212. else:
  213. use_rounding = int(frameWidth > 1)
  214. rounding = 0.5 * use_rounding
  215. painter.setBrush(bg_color)
  216. painter.setPen(bg_color)
  217. painter.drawRoundedRect(QRectF(0.5+frameWidth,
  218. 0.5+frameWidth,
  219. self.width()-1-frameWidth*2,
  220. self.height()-1-frameWidth*2), rounding, rounding)
  221. clipPath = QPainterPath()
  222. rounding = 1.0 * use_rounding
  223. clipPath.addRoundedRect(QRectF(frameWidth,
  224. frameWidth,
  225. self.width()-frameWidth*2,
  226. self.height()-frameWidth*2), rounding, rounding)
  227. painter.setClipPath(clipPath)
  228. self.fScene.render(painter, self.fRenderTarget, self.fRenderSource, Qt.KeepAspectRatio)
  229. # Allow cursor frame to look joined with minicanvas frame
  230. painter.setClipping(False)
  231. width = self.fViewRect[self._kRectWidth]/self.fScale
  232. height = self.fViewRect[self._kRectHeight]/self.fScale
  233. # cursor
  234. lineHinting = self.fViewPen.widthF() / 2
  235. x = self.fViewRect[self._kRectX]+self.fInitialX
  236. y = self.fViewRect[self._kRectY]+self.fInitialY
  237. scr_x = floor(x)
  238. scr_y = floor(y)
  239. rect = QRectF(
  240. scr_x+lineHinting,
  241. scr_y+lineHinting,
  242. ceil(width+x-scr_x)-lineHinting*2,
  243. ceil(height+y-scr_y)-lineHinting*2 )
  244. if self.fRubberBandBlending == self._RUBBERBAND_BLENDING_PLUS:
  245. painter.setCompositionMode(QPainter.CompositionMode_Plus)
  246. elif self.fRubberBandBlending == self._RUBBERBAND_BLENDING_DIFF:
  247. painter.setCompositionMode(QPainter.CompositionMode_Difference)
  248. painter.setBrush(self.fViewBrush)
  249. painter.setPen(Qt.NoPen)
  250. painter.drawRect(rect)
  251. painter.setCompositionMode(QPainter.CompositionMode_SourceOver)
  252. painter.setBrush(Qt.NoBrush)
  253. painter.setPen(self.fViewPen)
  254. painter.drawRect(rect)
  255. if self.fUseCustomPaint:
  256. event.accept()
  257. else:
  258. QFrame.paintEvent(self, event)
  259. def resizeEvent(self, event):
  260. size = event.size()
  261. width = size.width()
  262. height = size.height()
  263. extRatio = (width - self.fFrameWidth * 2) / (height - self.fFrameWidth * 2)
  264. if extRatio >= self.fInternalRatio:
  265. self.fInternalHeight = floor(height - self.fFrameWidth * 2)
  266. self.fInternalWidth = floor(self.fInternalHeight * self.fInternalRatio)
  267. self.fInitialX = floor(float(width - self.fInternalWidth) / 2.0)
  268. self.fInitialY = self.fFrameWidth
  269. else:
  270. self.fInternalWidth = floor(width - self.fFrameWidth * 2)
  271. self.fInternalHeight = floor(self.fInternalWidth / self.fInternalRatio)
  272. self.fInitialX = self.fFrameWidth
  273. self.fInitialY = floor(float(height - self.fInternalHeight) / 2.0)
  274. self.fRenderTarget = QRectF(self.fInitialX,
  275. self.fInitialY,
  276. float(self.fInternalWidth),
  277. float(self.fInternalHeight))
  278. if self.fRealParent is not None:
  279. QTimer.singleShot(0, self.fRealParent.slot_miniCanvasCheckAll)
  280. QFrame.resizeEvent(self, event)
  281. # -----------------------------------------------------------------------------------------------------------------
  282. def _moveViewRect(self, x: float, y: float):
  283. if self.fScene is None:
  284. return
  285. x -= self.fInitialX
  286. y -= self.fInitialY
  287. fixPos = False
  288. rCentX = self.fViewRect[self._kRectWidth] / self.fScale / 2
  289. rCentY = self.fViewRect[self._kRectHeight] / self.fScale / 2
  290. if x < rCentX:
  291. x = rCentX
  292. fixPos = True
  293. elif x > self.fInternalWidth - rCentX:
  294. x = self.fInternalWidth - rCentX
  295. fixPos = True
  296. if y < rCentY:
  297. y = rCentY
  298. fixPos = True
  299. elif y > self.fInternalHeight - rCentY:
  300. y = self.fInternalHeight - rCentY
  301. fixPos = True
  302. if fixPos:
  303. globalPos = self.mapToGlobal(QPoint(int(self.fInitialX + x), int(self.fInitialY + y)))
  304. self.cursor().setPos(globalPos)
  305. x = self.fRenderSource.width() * x / self.fInternalWidth
  306. y = self.fRenderSource.height() * y / self.fInternalHeight
  307. self.fScene.m_view.centerOn(x, y)
  308. def _scaleViewRect(self, globalY: int):
  309. if self.fScene is None:
  310. return
  311. dy = self.fMouseInitialZoomPos.y() - globalY
  312. if dy != 0:
  313. self.setCursor(self.fZoomCursors[self._kCursorZoomIn if dy > 0 else self._kCursorZoomOut])
  314. self.fScene.zoom_wheel(dy)
  315. self.cursor().setPos(self.fMouseInitialZoomPos)
  316. def _updateMouseMode(self, event):
  317. if self.fMouseLeftDown and self.fMouseRightDown:
  318. self.fMouseInitialZoomPos = event.globalPos()
  319. self.setCursor(self.fZoomCursors[self._kCursorZoom])
  320. self.fMouseMode = self._MOUSE_MODE_SCALE
  321. elif self.fMouseLeftDown:
  322. self.setCursor(QCursor(Qt.SizeAllCursor))
  323. if self.fMouseMode == self._MOUSE_MODE_NONE:
  324. if QT_VERSION >= 0x60000:
  325. pos = event.position()
  326. x = pos.x()
  327. y = pos.y()
  328. else:
  329. x = event.x()
  330. y = event.y()
  331. self._moveViewRect(x, y)
  332. self.fMouseMode = self._MOUSE_MODE_MOVE
  333. else:
  334. self.unsetCursor()
  335. self.fMouseMode = self._MOUSE_MODE_NONE
  336. def _updateStyle(self):
  337. self.fFrameWidth = 1 if self.fUseCustomPaint else self.frameWidth()
  338. # ---------------------------------------------------------------------------------------------------------------------