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.

426 lines
16KB

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