def keyPressEvent(self, e: QtGui.QKeyEvent): if not self.m_isEditing: return if e.key() == QtCore.Qt.Key_Delete: self.deleteLater() # Moving container with arrows if QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier: newPos = QPoint(self.x(), self.y()) if e.key() == QtCore.Qt.Key_Up: newPos.setY(newPos.y() - 1) if e.key() == QtCore.Qt.Key_Down: newPos.setY(newPos.y() + 1) if e.key() == QtCore.Qt.Key_Left: newPos.setX(newPos.x() - 1) if e.key() == QtCore.Qt.Key_Right: newPos.setX(newPos.x() + 1) self.move(newPos) if QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: if e.key() == QtCore.Qt.Key_Up: self.resize(self.width(), self.height() - 1) if e.key() == QtCore.Qt.Key_Down: self.resize(self.width(), self.height() + 1) if e.key() == QtCore.Qt.Key_Left: self.resize(self.width() - 1, self.height()) if e.key() == QtCore.Qt.Key_Right: self.resize(self.width() + 1, self.height()) self.newGeometry.emit(self.geometry())
def is_inside_limit(limit: QRect, pos: QPoint): if pos.x() < limit.x() or pos.x() > limit.width(): return False elif pos.y() < limit.y() or pos.y() > limit.height(): return False return True
def download(self): """ Download tile :return: Nothing """ grab = None rx = range(self._tiles_rectangle.width()) ry = range(self._tiles_rectangle.height()) for x, y in product(rx, ry): tp = Point(self._tiles_rectangle.topLeft() + QPoint(x, y)) if tp not in self._tile_pixmaps: grab = QPoint(tp) break if grab is None: self._url = QUrl() return path = 'http://tile.openstreetmap.org/%d/%d/%d.png' % ( self.zoom, grab.x(), grab.y()) self._url = QUrl(path) self.request = QNetworkRequest() self.request.setUrl(self._url) self.request.setRawHeader(b'User-Agent', b'Nokia (PyQt) Graphics Dojo 1.0') self.request.setAttribute(QNetworkRequest.User, grab) self._manager.get(self.request) print('downloading z:', self.zoom, 'x:', grab.x(), 'y:', grab.y())
def show_node_choice_widget(self, pos, nodes=None): """Opens the node choice dialog in the scene.""" # calculating position self.node_place_pos = self.mapToScene(pos) dialog_pos = QPoint(pos.x() + 1, pos.y() + 1) # ensure that the node_choice_widget stays in the viewport if dialog_pos.x() + self.node_choice_widget.width( ) / self.total_scale_div > self.viewport().width(): dialog_pos.setX(dialog_pos.x() - (dialog_pos.x() + self.node_choice_widget.width() / self.total_scale_div - self.viewport().width())) if dialog_pos.y() + self.node_choice_widget.height( ) / self.total_scale_div > self.viewport().height(): dialog_pos.setY(dialog_pos.y() - (dialog_pos.y() + self.node_choice_widget.height() / self.total_scale_div - self.viewport().height())) dialog_pos = self.mapToScene(dialog_pos) # open nodes dialog # the dialog emits 'node_chosen' which is connected to self.place_node, # so this all continues at self.place_node below self.node_choice_widget.update_list( nodes if nodes is not None else self.all_nodes) self.node_choice_widget.update_view() self.node_choice_proxy.setPos(dialog_pos) self.node_choice_proxy.show() self.node_choice_widget.refocus()
def setCursorShape(self, e_pos: QPoint): diff = 3 # Left - Bottom if (((e_pos.y() > self.y() + self.height() - diff) and # Bottom (e_pos.x() < self.x() + diff)) or # Left # Right-Bottom ((e_pos.y() > self.y() + self.height() - diff) and # Bottom (e_pos.x() > self.x() + self.width() - diff)) or # Right # Left-Top ((e_pos.y() < self.y() + diff) and # Top (e_pos.x() < self.x() + diff)) or # Left # Right-Top (e_pos.y() < self.y() + diff) and # Top (e_pos.x() > self.x() + self.width() - diff)): # Right # Left - Bottom if ((e_pos.y() > self.y() + self.height() - diff) and # Bottom (e_pos.x() < self.x() + diff)): # Left self.mode = Mode.RESIZEBL self.setCursor(QCursor(QtCore.Qt.SizeBDiagCursor)) # Right - Bottom if ((e_pos.y() > self.y() + self.height() - diff) and # Bottom (e_pos.x() > self.x() + self.width() - diff)): # Right self.mode = Mode.RESIZEBR self.setCursor(QCursor(QtCore.Qt.SizeFDiagCursor)) # Left - Top if ((e_pos.y() < self.y() + diff) and # Top (e_pos.x() < self.x() + diff)): # Left self.mode = Mode.RESIZETL self.setCursor(QCursor(QtCore.Qt.SizeFDiagCursor)) # Right - Top if ((e_pos.y() < self.y() + diff) and # Top (e_pos.x() > self.x() + self.width() - diff)): # Right self.mode = Mode.RESIZETR self.setCursor(QCursor(QtCore.Qt.SizeBDiagCursor)) # check cursor horizontal position elif ((e_pos.x() < self.x() + diff) or # Left (e_pos.x() > self.x() + self.width() - diff)): # Right if e_pos.x() < self.x() + diff: # Left self.setCursor(QCursor(QtCore.Qt.SizeHorCursor)) self.mode = Mode.RESIZEL else: # Right self.setCursor(QCursor(QtCore.Qt.SizeHorCursor)) self.mode = Mode.RESIZER # check cursor vertical position elif ((e_pos.y() > self.y() + self.height() - diff) or # Bottom (e_pos.y() < self.y() + diff)): # Top if e_pos.y() < self.y() + diff: # Top self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) self.mode = Mode.RESIZET else: # Bottom self.setCursor(QCursor(QtCore.Qt.SizeVerCursor)) self.mode = Mode.RESIZEB else: self.setCursor(QCursor(QtCore.Qt.ArrowCursor)) self.mode = Mode.MOVE
def center_on_ui(self): self.expand.toggle_expand(immediate=True) r: QRect = self.ui.frameGeometry() width, height = self.default_width, self.size().height() center = QPoint(r.x() + r.width() / 2, r.y() + r.height() / 2) top_left = QPoint(center.x() - width / 2, center.y() - height / 2) self.setGeometry(QRect(top_left.x(), top_left.y(), width, height)) self.first_expand = True
def pos_inside_handle(self, pos: QtCore.QPoint): opt = QtWidgets.QStyleOptionSlider() # Populate opt with the widget's style self.initStyleOption(opt) handle = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderHandle, self) topleft = handle.topLeft() bottomright = handle.bottomRight() return (pos.x() >= topleft.x() and pos.x() <= bottomright.x() and pos.y() >= topleft.y() and pos.y() <= bottomright.y())
def pixel_pos_to_range_value(self, pos: QtCore.QPoint): opt = QtWidgets.QStyleOptionSlider() # Populate opt with the widget's style self.initStyleOption(opt) groove = self.style().subControlRect(QStyle.CC_Slider, opt, QStyle.SC_SliderGroove, self) if self.orientation() == QtCore.Qt.Horizontal: # sliderLength = handle.width() slider_min = groove.x() slider_max = groove.right() else: # sliderLength = handle.height() slider_min = groove.y() slider_max = groove.bottom() new_pos_scalar = pos.x() if self.orientation( ) == QtCore.Qt.Horizontal else pos.y() return QStyle.sliderValueFromPosition( self.minimum(), # min self.maximum(), # max new_pos_scalar, # pos (int) slider_max - slider_min, # span (int) opt.upsideDown # upside down (bool) )
def mouseMoveEvent(self, e): """ move the whole window to mouse position """ if self.toolbar_selected: delta = QPoint(e.globalPos() - self.old_pos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.old_pos = e.globalPos()
def mouseMoveEvent(self, event): """ Handles move during Drag-and-Drop """ delta = QPoint(event.globalPos() - self._old_position) self.move(self.x() + delta.x(), self.y() + delta.y()) self._old_position = event.globalPos()
def moveRotatingTool(self): """ Moves the tool buttons to the vertices of the displayed image. Should be called every time that posRelImg is changed or the image zooming coeff. or position in widget are modified (cf. blue.mouseEvent and blue.wheelEvent) """ # get parent widget parent = self.parent() r = parent.img.resize_coeff(parent) topLeft = self.btnDict['topLeft'] topRight = self.btnDict['topRight'] bottomLeft = self.btnDict['bottomLeft'] bottomRight = self.btnDict['bottomRight'] # move buttons : coordinates are relative to parent widget p = QPoint(self.img.xOffset, self.img.yOffset) x, y = p.x(), p.y() bottomLeft.move(x + bottomLeft.posRelImg.x() * r, y - bottomLeft.height() + bottomLeft.posRelImg.y() * r) bottomRight.move( x - bottomRight.width() + bottomRight.posRelImg.x() * r, y - bottomRight.height() + bottomRight.posRelImg.y() * r) topLeft.move(x + topLeft.posRelImg.x() * r, y + topLeft.posRelImg.y() * r) topRight.move(x - topRight.width() + topRight.posRelImg.x() * r, y + topRight.posRelImg.y() * r)
def mousePressEvent(self, event): mouse = QPoint(event.pos()) if event.buttons() & QtCore.Qt.LeftButton: # Click on hues? if self._hue_rect.contains(mouse.x(), mouse.y()): y = mouse.y() c = QtGui.QColor.fromHsvF(float(y) / self.height(), self._saturation, self._value) self.color = c # Click on colors? elif self._shades_rect.contains(mouse.x(), mouse.y()): # calculate saturation and value x = mouse.x() y = mouse.y() c = QtGui.QColor.fromHsvF(self._hue, 1 - float(y) / self._shades_rect.height(), float(x) / self._shades_rect.width()) self.color = c
def showRightClickMenu(self, pos: QtCore.QPoint) -> None: x = 0 x = 0 x += self.treeWidget.pos().x() x += self.window.pos().x() x += pos.x() y = 0 y += 90 # Tab widget + menubar y += self.treeWidget.pos().y() y += self.window.pos().y() y += pos.y() log(f"[ ] Showing menu at {x}x{y}") menu = QtWidgets.QMenu(self) menu.move(x, y) menu.addAction(self.addFileAction) menu.addSeparator() menu.addAction(self.openFilesAction) menu.addSeparator() menu.addAction(self.showCheckboxesAction) menu.addAction(self.selectNoneAction) menu.addAction(self.selectAllAction) menu.addAction(self.invertSelectionAction) menu.addSeparator() menu.addAction(self.magicAction) menu.exec_()
def zoomWithDegrees(self, numDegrees: QtCore.QPoint): zoomPercentage = numDegrees.y() / self.height() if zoomPercentage < 0: self.zoomOut(0.1) else: self.zoomIn(0.1)
def eventFilter(self, obj, event): if event.type() == QEvent.MouseButtonPress: self.oldPos = event.globalPos() elif event.type() == QEvent.MouseMove: delta = QPoint(event.globalPos() - self.oldPos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPos() return QMainWindow.eventFilter(self, obj, event)
def _draw_nodes(self, painter, topleft_point, bottomright_point): # draw nodes for block in self.blocks: # safety check if block.x is None or block.y is None: l.warning("Failed to assign coordinates to block %s.", block) continue # optimization: don't paint blocks that are outside of the current range block_topleft_point = QPoint(block.x, block.y) block_bottomright_point = QPoint(block.x + block.width, block.y + block.height) if block_topleft_point.x() > bottomright_point.x() or block_topleft_point.y() > bottomright_point.y(): continue elif block_bottomright_point.x() < topleft_point.x() or block_bottomright_point.y() < topleft_point.y(): continue block.paint(painter)
def create_view_context_menu(self, pos: QPoint) -> QMenu: """Create and return a context menu to use with the playlist view.""" menu = QMenu() remove_action = QAction("Remove", self.ui.playlist_view) menu.addAction(remove_action) row = self.ui.playlist_view.rowAt(pos.y()) remove_action.triggered.connect(lambda: self.remove_triggered(row)) return menu
def map_event_pos_to_target_scene_pos(self, pos: QPoint) -> QPointF: """ Map a point `pos` from view (e.g. mouse event) to target scene. """ scene_rect = self._target_view.scene().sceneRect() pos = self.mapToScene(pos) x, y = pos.x(), pos.y() x = x / self._scale + scene_rect.x() y = y / self._scale + scene_rect.y() return QPointF(x, y)
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Assignment") self.scene = QGraphicsScene() self.resize(800, 600) self.button = QPushButton("Draw Text Boxes") self.scene.addWidget(self.button) self.view = QGraphicsView() self.view.setScene(self.scene) self.setCentralWidget(self.view) self.button.clicked.connect(self.buttonClicked) self.view.viewport().installEventFilter(self) self.drawing = False self.lastPoint = QPoint() self.startPoint = QPoint() def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self.startPoint = self.view.mapToScene(event.pos()) self.drawing = True def mouseReleaseEvent(self, event): if Qt.LeftButton and self.drawing: self.lastPoint = self.view.mapToScene(event.pos()) self.update() def paintEvent(self, event): if self.drawing : self.view.le = QPlainTextEdit() width = QtCore.QRectF(self.startPoint, self.lastPoint).size().width() height = QtCore.QRectF(self.startPoint, self.lastPoint).size().height() x = self.startPoint.x() y = self.startPoint.y() if width > 1 and height > 1: self.view.le.setGeometry(x, y, width, height) self.qsizegrip = QSizeGrip(self.view.le) self.scene.addWidget(self.view.le) def buttonClicked(self): self.button.hide() def eventFilter(self, obj, event): if obj is self.view.viewport(): if event.type() == QEvent.MouseButtonPress: pass # self.mousePressEvent(event) elif event.type() == QEvent.MouseButtonRelease: self.mouseReleaseEvent(event) return QWidget.eventFilter(self, obj, event)
def paint(self, painter, option, index): """ Paints the comment element specified by `index`. """ if option.state & QStyle.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) idx = index.row() comment = index.model().data(index, Qt.DisplayRole) # top y coordinate at which drawing will begin downwards leftX = option.rect.x() topY = option.rect.y() # save painter state, since color and font are changed painter.save() # whole area that can be drawn onto boundingRect = painter.viewport() # extract pen and font from painter pen = painter.pen() self.updateFonts(painter.font()) fontMetric = painter.fontMetrics() # draw comment brush = index.model().data(index, Qt.ForegroundRole) (commentStart, commentTextHeight) = self.drawComment(comment, painter, option, fontMetric, leftX, topY, brush) # draw separation line lineStart = QPoint(commentStart.x(), commentStart.y()) self.drawSeparationLine(painter, pen, lineStart, self.width - 20) # draw author authorStart = QPoint(lineStart.x(), lineStart.y() + fontMetric.height()) authorWidth = fontMetric.width(comment[1]) authorEnd = QPoint(authorStart.x() + authorWidth, authorStart.y()) self.drawAuthorDate(comment, painter, pen, authorStart, authorEnd) painter.restore()
def paintEvent(self, event): painter = QPainter() painter.begin(self) center = QPoint(self.width() / 2, self.height() / 2) painter.fillRect(self.rect(), QBrush(QColor(255, 255, 255, 160))) margin = 20 painter.fillRect( QRect(center.x() - self.prop.dx - margin, center.y() - self.prop.dy - margin, (self.prop.dx + margin) * 2, (self.prop.dy + margin) * 2), QBrush(QColor(0, 0, 0, 255))) self.prop.draw(painter, QPoint(center.x(), center.y()), True if self.counter == 0 else False) painter.end() self.counter += 1 self.counter %= 2
def set_mask_pixels(self, center_point: QPoint, value=255): it = np.nditer(self.mask[:, :, self.image.get_slice_index()], flags=['multi_index']) size = int(self.tools.get_size() / 2) - 1 x = center_point.x() y = center_point.y() for pixel in it: if x - size <= it.multi_index[ 1] <= x + size and y + size >= it.multi_index[ 0] >= y - size: self.mask[it.multi_index[0]][it.multi_index[1]][ self.image.get_slice_index()] = value
def indexAt(self, pos: QtCore.QPoint): """ The index under a given point """ # Safety check: these must be lists containing at least a [0] if not self.tops or self.lefts: self.resultantTopLefts(UserRoles.FullResImage) # If the point is in negative space, we don't # have any indexes that would use negative space if pos.x() < 0 or pos.y() < 0: return None # If we don't find the row or the column, # we cannot return an index rowFound = False colFound = False # Find the row that this point belongs to row = 0 for y in self.tops[1:]: if pos.y() < y: rowFound = True continue row += 1 # Find the column that this point belongs to col = 0 for x in self.lefts[1:]: if pos.x() < x: colFound = True continue col += 1 if rowFound and colFound: return self.relativeIndexes[row][col] else: return None
def drawAuthorDate(self, comment, painter, pen, start: QPoint, end: QPoint): """ Helper function for `self.paint()`, drawing author and date """ fontMetric = QFontMetrics(self.authorFont) pen.setColor(QColor("#666699")) painter.setPen(pen) painter.setFont(self.authorFont) painter.drawText(start, comment[1]) dateStart = QPoint(end.x(), end.y()) if comment[1] is not None and len(comment[1]) > 0: dateStart.setX(dateStart.x() + self._authorDateQSeparation) painter.drawText(dateStart, comment[2])
def drawCircle(self, point: QPoint): a = point.x() b = point.y() angle_step_size = 64 radius = 90 initPoint = QPoint(-1, -1) finalPoint = initPoint firstPoint = finalPoint self.image.fill('#000000ff') painter = QPainter(self.image) lidarPoints = [] painter.setPen('#00ff00') painter.drawRect(point.x() - 1, point.y() - 1, 2, 2) front = QPoint(math.cos(self.angle), math.sin(self.angle)) self.image.setPixelColor(point + front, QColor('#0000ff')) painter.setPen('#ff0000') for step in range(0, angle_step_size - 1): angle = 2 * math.pi * step / angle_step_size + self.angle initPoint = finalPoint for r in range(1, radius): x = point.x() + r * math.cos(angle) y = point.y() + r * math.sin(angle) finalPoint = QPoint(x, y) if not self.map.pixel(x, y): break if initPoint != QPoint(-1, -1): painter.drawLine(initPoint, finalPoint) else: firstPoint = finalPoint lidarPoints.append(finalPoint - point) painter.drawLine(finalPoint, firstPoint) painter.end() self.update() self.runObstacleAvoidance(point, lidarPoints)
def popTableMenu(self, pos): pos = QPoint(pos.x() + 3, pos.y() - 4) menu = QMenu() exportAudio = menu.addAction('导出音频文件') action = menu.exec_(self.graph.mapToGlobal(pos)) if action == exportAudio: if not self.mp3Path: QMessageBox.warning(self, '未检测到音轨文件', '请导入视频并确保视频音轨正常', QMessageBox.Ok) else: mp3Path = QFileDialog.getSaveFileName(self, "选择音频输出文件夹", None, "(*.acc)")[0] if mp3Path: shutil.copy(self.mp3Path, mp3Path) QMessageBox.information(self, '导出音轨文件', '导出完成', QMessageBox.Ok)
def index_for_dropping_pos(self, pos: QPoint) -> QModelIndex: """dropした場所のindexを返す。ただし、要素の高さ半分より下にある場合は、下の要素を返す。 :param pos: :return: posから導き出されたindex 挿入や移動のために、要素の間を意識している。 """ index = self.indexAt(pos) if index.row() < 0: new_index = self.proxy_model.sourceModel().index( self.proxy_model.sourceModel().rowCount(), 0) return new_index item_rect = self.visualRect(index) pos_in_rect = pos.y() - item_rect.top() if pos_in_rect < (item_rect.height() / 2): return index else: return self.proxy_model.sourceModel().index(index.row() + 1, 0)
def popTableMenu(self, pos): pos = QPoint(pos.x() + 3, pos.y() - 4) menu = QMenu() exportAudio = menu.addAction('导出音频文件') action = menu.exec_(self.graph.mapToGlobal(pos)) if action == exportAudio: if not self.mp3Path: QMessageBox.warning(self, '未检测到音轨文件', '请先完成AI打轴以生成人声音轨和背景音轨', QMessageBox.Ok) else: mp3Path = QFileDialog.getSaveFileName(self, "选择音频输出文件夹", None, "(*.mp3)")[0] if mp3Path: vocalPath = self.mp3Path + r'\vocals.mp3' bgmPath = self.mp3Path + r'\bgm.mp3' shutil.copy(vocalPath, mp3Path[:-4] + '_人声.mp3') shutil.copy(bgmPath, mp3Path[:-4] + '_背景声.mp3') QMessageBox.information(self, '导出音轨文件', '导出完成', QMessageBox.Ok)
def cursor_on_edge_of_object(self, level_object: Union[LevelObject, EnemyObject], pos: QPoint, edge_width: int = 4): right = (level_object.get_rect().left() + level_object.get_rect().width()) * self.block_length bottom = (level_object.get_rect().top() + level_object.get_rect().height()) * self.block_length on_right_edge = pos.x() in range(right - edge_width, right) on_bottom_edge = pos.y() in range(bottom - edge_width, bottom) edges = 0 if on_right_edge: edges |= Qt.RightEdge if on_bottom_edge: edges |= Qt.BottomEdge return edges
def download(self): grab = None for x in range(self._tilesRect.width()): for y in range(self._tilesRect.height()): tp = Point(self._tilesRect.topLeft() + QPoint(x, y)) if tp not in self._tilePixmaps: grab = QPoint(tp) break if grab is None: self._url = QUrl() return path = 'http://tile.openstreetmap.org/%d/%d/%d.png' % ( self.zoom, grab.x(), grab.y()) self._url = QUrl(path) self.request = QNetworkRequest() self.request.setUrl(self._url) self.request.setRawHeader(b'User-Agent', b'Nokia (PyQt) Graphics Dojo 1.0') self.request.setAttribute(QNetworkRequest.User, grab) self._manager.get(self.request)
class GLWidget(QOpenGLWidget, QOpenGLFunctions): xRotationChanged = Signal(int) yRotationChanged = Signal(int) zRotationChanged = Signal(int) def __init__(self, parent=None): QOpenGLWidget.__init__(self, parent) QOpenGLFunctions.__init__(self) self.core = "--coreprofile" in QCoreApplication.arguments() self.xRot = 0 self.yRot = 0 self.zRot = 0 self.lastPos = 0 self.logo = Logo() self.vao = QOpenGLVertexArrayObject() self.logoVbo = QOpenGLBuffer() self.program = QOpenGLShaderProgram() self.projMatrixLoc = 0 self.mvMatrixLoc = 0 self.normalMatrixLoc = 0 self.lightPosLoc = 0 self.proj = QMatrix4x4() self.camera = QMatrix4x4() self.world = QMatrix4x4() self.transparent = "--transparent" in QCoreApplication.arguments() if self.transparent: fmt = self.format() fmt.setAlphaBufferSize(8) self.setFormat(fmt) def xRotation(self): return self.xRot def yRotation(self): return self.yRot def zRotation(self): return self.zRot def minimumSizeHint(self): return QSize(50, 50) def sizeHint(self): return QSize(400, 400) def normalizeAngle(self, angle): while angle < 0: angle += 360 * 16 while angle > 360 * 16: angle -= 360 * 16 return angle def setXRotation(self, angle): angle = self.normalizeAngle(angle) if angle != self.xRot: self.xRot = angle self.emit(SIGNAL("xRotationChanged(int)"), angle) self.update() def setYRotation(self, angle): angle = self.normalizeAngle(angle) if angle != self.yRot: self.yRot = angle self.emit(SIGNAL("yRotationChanged(int)"), angle) self.update() def setZRotation(self, angle): angle = self.normalizeAngle(angle) if angle != self.zRot: self.zRot = angle self.emit(SIGNAL("zRotationChanged(int)"), angle) self.update() def cleanup(self): self.makeCurrent() self.logoVbo.destroy() del self.program self.program = None self.doneCurrent() def vertexShaderSourceCore(self): return """#version 150 in vec4 vertex; in vec3 normal; out vec3 vert; out vec3 vertNormal; uniform mat4 projMatrix; uniform mat4 mvMatrix; uniform mat3 normalMatrix; void main() { vert = vertex.xyz; vertNormal = normalMatrix * normal; gl_Position = projMatrix * mvMatrix * vertex; }""" def fragmentShaderSourceCore(self): return """#version 150 in highp vec3 vert; in highp vec3 vertNormal; out highp vec4 fragColor; uniform highp vec3 lightPos; void main() { highp vec3 L = normalize(lightPos - vert); highp float NL = max(dot(normalize(vertNormal), L), 0.0); highp vec3 color = vec3(0.39, 1.0, 0.0); highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); fragColor = vec4(col, 1.0); }""" def vertexShaderSource(self): return """attribute vec4 vertex; attribute vec3 normal; varying vec3 vert; varying vec3 vertNormal; uniform mat4 projMatrix; uniform mat4 mvMatrix; uniform mat3 normalMatrix; void main() { vert = vertex.xyz; vertNormal = normalMatrix * normal; gl_Position = projMatrix * mvMatrix * vertex; }""" def fragmentShaderSource(self): return """varying highp vec3 vert; varying highp vec3 vertNormal; uniform highp vec3 lightPos; void main() { highp vec3 L = normalize(lightPos - vert); highp float NL = max(dot(normalize(vertNormal), L), 0.0); highp vec3 color = vec3(0.39, 1.0, 0.0); highp vec3 col = clamp(color * 0.2 + color * 0.8 * NL, 0.0, 1.0); gl_FragColor = vec4(col, 1.0); }""" def initializeGL(self): self.context().aboutToBeDestroyed.connect(self.cleanup) self.initializeOpenGLFunctions() self.glClearColor(0, 0, 0, 1) self.program = QOpenGLShaderProgram() if self.core: self.vertexShader = self.vertexShaderSourceCore() self.fragmentShader = self.fragmentShaderSourceCore() else: self.vertexShader = self.vertexShaderSource() self.fragmentShader = self.fragmentShaderSource() self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, self.vertexShader) self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, self.fragmentShader) self.program.bindAttributeLocation("vertex", 0) self.program.bindAttributeLocation("normal", 1) self.program.link() self.program.bind() self.projMatrixLoc = self.program.uniformLocation("projMatrix") self.mvMatrixLoc = self.program.uniformLocation("mvMatrix") self.normalMatrixLoc = self.program.uniformLocation("normalMatrix") self.lightPosLoc = self.program.uniformLocation("lightPos") self.vao.create() vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) self.logoVbo.create() self.logoVbo.bind() float_size = ctypes.sizeof(ctypes.c_float) self.logoVbo.allocate(self.logo.constData(), self.logo.count() * float_size) self.setupVertexAttribs() self.camera.setToIdentity() self.camera.translate(0, 0, -1) self.program.setUniformValue(self.lightPosLoc, QVector3D(0, 0, 70)) self.program.release() vaoBinder = None def setupVertexAttribs(self): self.logoVbo.bind() f = QOpenGLContext.currentContext().functions() f.glEnableVertexAttribArray(0) f.glEnableVertexAttribArray(1) float_size = ctypes.sizeof(ctypes.c_float) null = VoidPtr(0) pointer = VoidPtr(3 * float_size) f.glVertexAttribPointer(0, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, null) f.glVertexAttribPointer(1, 3, int(GL.GL_FLOAT), int(GL.GL_FALSE), 6 * float_size, pointer) self.logoVbo.release() def paintGL(self): self.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) self.glEnable(GL.GL_DEPTH_TEST) self.glEnable(GL.GL_CULL_FACE) self.world.setToIdentity() self.world.rotate(180 - (self.xRot / 16), 1, 0, 0) self.world.rotate(self.yRot / 16, 0, 1, 0) self.world.rotate(self.zRot / 16, 0, 0, 1) vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao) self.program.bind() self.program.setUniformValue(self.projMatrixLoc, self.proj) self.program.setUniformValue(self.mvMatrixLoc, self.camera * self.world) normalMatrix = self.world.normalMatrix() self.program.setUniformValue(self.normalMatrixLoc, normalMatrix) self.glDrawArrays(GL.GL_TRIANGLES, 0, self.logo.vertexCount()) self.program.release() vaoBinder = None def resizeGL(self, width, height): self.proj.setToIdentity() self.proj.perspective(45, width / height, 0.01, 100) def mousePressEvent(self, event): self.lastPos = QPoint(event.pos()) def mouseMoveEvent(self, event): dx = event.x() - self.lastPos.x() dy = event.y() - self.lastPos.y() if event.buttons() & Qt.LeftButton: self.setXRotation(self.xRot + 8 * dy) self.setYRotation(self.yRot + 8 * dx) elif event.buttons() & Qt.RightButton: self.setXRotation(self.xRot + 8 * dy) self.setZRotation(self.zRot + 8 * dx) self.lastPos = QPoint(event.pos())
class MandelbrotWidget(QWidget): def __init__(self, parent=None): super(MandelbrotWidget, self).__init__(parent) self.thread = RenderThread() self.pixmap = QPixmap() self.pixmapOffset = QPoint() self.lastDragPos = QPoint() self.centerX = DefaultCenterX self.centerY = DefaultCenterY self.pixmapScale = DefaultScale self.curScale = DefaultScale self.thread.renderedImage.connect(self.updatePixmap) self.setWindowTitle("Mandelbrot") self.setCursor(Qt.CrossCursor) self.resize(550, 400) def paintEvent(self, event): painter = QPainter(self) painter.fillRect(self.rect(), Qt.black) if self.pixmap.isNull(): painter.setPen(Qt.white) painter.drawText(self.rect(), Qt.AlignCenter, "Rendering initial image, please wait...") return if self.curScale == self.pixmapScale: painter.drawPixmap(self.pixmapOffset, self.pixmap) else: scaleFactor = self.pixmapScale / self.curScale newWidth = int(self.pixmap.width() * scaleFactor) newHeight = int(self.pixmap.height() * scaleFactor) newX = self.pixmapOffset.x() + (self.pixmap.width() - newWidth) / 2 newY = self.pixmapOffset.y() + (self.pixmap.height() - newHeight) / 2 painter.save() painter.translate(newX, newY) painter.scale(scaleFactor, scaleFactor) exposed, _ = painter.matrix().inverted() exposed = exposed.mapRect(self.rect()).adjusted(-1, -1, 1, 1) painter.drawPixmap(exposed, self.pixmap, exposed) painter.restore() text = "Use mouse wheel or the '+' and '-' keys to zoom. Press and " \ "hold left mouse button to scroll." metrics = painter.fontMetrics() textWidth = metrics.width(text) painter.setPen(Qt.NoPen) painter.setBrush(QColor(0, 0, 0, 127)) painter.drawRect((self.width() - textWidth) / 2 - 5, 0, textWidth + 10, metrics.lineSpacing() + 5) painter.setPen(Qt.white) painter.drawText((self.width() - textWidth) / 2, metrics.leading() + metrics.ascent(), text) def resizeEvent(self, event): self.thread.render(self.centerX, self.centerY, self.curScale, self.size()) def keyPressEvent(self, event): if event.key() == Qt.Key_Plus: self.zoom(ZoomInFactor) elif event.key() == Qt.Key_Minus: self.zoom(ZoomOutFactor) elif event.key() == Qt.Key_Left: self.scroll(-ScrollStep, 0) elif event.key() == Qt.Key_Right: self.scroll(+ScrollStep, 0) elif event.key() == Qt.Key_Down: self.scroll(0, -ScrollStep) elif event.key() == Qt.Key_Up: self.scroll(0, +ScrollStep) else: super(MandelbrotWidget, self).keyPressEvent(event) def wheelEvent(self, event): numDegrees = event.angleDelta().y() / 8 numSteps = numDegrees / 15.0 self.zoom(pow(ZoomInFactor, numSteps)) def mousePressEvent(self, event): if event.buttons() == Qt.LeftButton: self.lastDragPos = QPoint(event.pos()) def mouseMoveEvent(self, event): if event.buttons() & Qt.LeftButton: self.pixmapOffset += event.pos() - self.lastDragPos self.lastDragPos = QPoint(event.pos()) self.update() def mouseReleaseEvent(self, event): if event.button() == Qt.LeftButton: self.pixmapOffset += event.pos() - self.lastDragPos self.lastDragPos = QPoint() deltaX = (self.width() - self.pixmap.width()) / 2 - self.pixmapOffset.x() deltaY = (self.height() - self.pixmap.height()) / 2 - self.pixmapOffset.y() self.scroll(deltaX, deltaY) def updatePixmap(self, image, scaleFactor): if not self.lastDragPos.isNull(): return self.pixmap = QPixmap.fromImage(image) self.pixmapOffset = QPoint() self.lastDragPosition = QPoint() self.pixmapScale = scaleFactor self.update() def zoom(self, zoomFactor): self.curScale *= zoomFactor self.update() self.thread.render(self.centerX, self.centerY, self.curScale, self.size()) def scroll(self, deltaX, deltaY): self.centerX += deltaX * self.curScale self.centerY += deltaY * self.curScale self.update() self.thread.render(self.centerX, self.centerY, self.curScale, self.size())