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.

425 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. self.fScene = scene
  84. self.fRenderSource = QRectF(0.0, 0.0, realWidth, realHeight)
  85. self.fInternalRatio = realWidth / realHeight
  86. self._updateStyle()
  87. if self.fUseCustomPaint != useCustomPaint:
  88. self.fUseCustomPaint = useCustomPaint
  89. self.repaint()
  90. def setRealParent(self, parent):
  91. self.fRealParent = parent
  92. # -----------------------------------------------------------------------------------------------------------------
  93. def setViewPosX(self, xp: float):
  94. self.fViewRect[self._kRectX] = xp * (self.fInternalWidth - self.fViewRect[self._kRectWidth]/self.fScale)
  95. self.update()
  96. def setViewPosY(self, yp: float):
  97. self.fViewRect[self._kRectY] = yp * (self.fInternalHeight - self.fViewRect[self._kRectHeight]/self.fScale)
  98. self.update()
  99. def setViewScale(self, scale: float):
  100. self.fScale = scale
  101. if self.fRealParent is not None:
  102. QTimer.singleShot(0, self.fRealParent.slot_miniCanvasCheckAll)
  103. def setViewSize(self, width: float, height: float):
  104. self.fViewRect[self._kRectWidth] = width * self.fInternalWidth
  105. self.fViewRect[self._kRectHeight] = height * self.fInternalHeight
  106. self.update()
  107. def setViewTheme(self, bgColor, brushColor, penColor):
  108. bg_black = bgColor.blackF()
  109. brush_black = brushColor.blackF()
  110. r0, g0, b0, _ = bgColor.getRgb()
  111. r1, g1, b1, _ = brushColor.getRgb()
  112. if brush_black < bg_black:
  113. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_PLUS
  114. brushColor = QColor(r1-r0, g1-g0, b1-b0, 40)
  115. elif bg_black < brush_black:
  116. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DIFF
  117. brushColor = QColor(r0-r1, g0-g1, b0-b1, 40)
  118. else:
  119. bgColor.setAlpha(40)
  120. self.fRubberBandBlending = self._RUBBERBAND_BLENDING_DEFAULT
  121. penColor.setAlpha(100)
  122. self.fViewBg = bgColor
  123. self.fViewBrush = QBrush(brushColor)
  124. self.fViewPen = QPen(penColor, 1)
  125. cursorName = "black" if bg_black < 0.5 else "white"
  126. if self.fZoomCursors[self._kCursorName] != cursorName:
  127. prefix = ":/cursors/zoom"
  128. self.fZoomCursors[self._kCursorName] = cursorName
  129. self.fZoomCursors[self._kCursorZoom] = QCursor(QPixmap("{}_{}.png".format(prefix, cursorName)), 8, 7)
  130. self.fZoomCursors[self._kCursorZoomIn] = QCursor(QPixmap("{}-in_{}.png".format(prefix, cursorName)), 8, 7)
  131. self.fZoomCursors[self._kCursorZoomOut] = QCursor(QPixmap("{}-out_{}.png".format(prefix, cursorName)), 8, 7)
  132. # -----------------------------------------------------------------------------------------------------------------
  133. def changeEvent(self, event):
  134. if event.type() in (QEvent.StyleChange, QEvent.PaletteChange):
  135. self._updateStyle()
  136. QFrame.changeEvent(self, event)
  137. def mousePressEvent(self, event):
  138. if event.button() == Qt.LeftButton:
  139. event.accept()
  140. self.fMouseLeftDown = True
  141. self._updateMouseMode(event)
  142. return
  143. if event.button() == Qt.RightButton:
  144. event.accept()
  145. self.fMouseRightDown = True
  146. self._updateMouseMode(event)
  147. return
  148. if event.button() == Qt.MidButton:
  149. event.accept()
  150. self.fMouseLeftDown = True
  151. self.fMouseRightDown = True
  152. self._updateMouseMode(event)
  153. return
  154. QFrame.mouseMoveEvent(self, event)
  155. def mouseMoveEvent(self, event):
  156. if self.fMouseMode == self._MOUSE_MODE_MOVE:
  157. event.accept()
  158. self._moveViewRect(event.x(), event.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()
  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.MidButton:
  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(self.fInitialX + x, 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 = None):
  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. self._moveViewRect(event.x(), event.y())
  325. self.fMouseMode = self._MOUSE_MODE_MOVE
  326. else:
  327. self.unsetCursor()
  328. self.fMouseMode = self._MOUSE_MODE_NONE
  329. def _updateStyle(self):
  330. self.fFrameWidth = 1 if self.fUseCustomPaint else self.frameWidth()
  331. # ---------------------------------------------------------------------------------------------------------------------
  332. if __name__ == '__main__':
  333. # pylint: disable=unused-import
  334. # pylint: disable=ungrouped-imports
  335. import sys
  336. import resources_rc
  337. from PyQt5.QtWidgets import QApplication
  338. # pylint: enable=unused-import
  339. # pylint: enable=ungrouped-imports
  340. app = QApplication(sys.argv)
  341. gui = CanvasPreviewFrame(None)
  342. gui.show()
  343. sys.exit(app.exec_())