def __grabRect(self): """ Private method to grab the selected rectangle (i.e. do the snapshot). """ if self.__mode == SnapshotRegionGrabber.Ellipse: ell = QRegion(self.__selection, QRegion.Ellipse) if not ell.isEmpty(): self.__grabbing = True xOffset = self.__pixmap.rect().x() - ell.boundingRect().x() yOffset = self.__pixmap.rect().y() - ell.boundingRect().y() translatedEll = ell.translated(xOffset, yOffset) pixmap2 = QPixmap(ell.boundingRect().size()) pixmap2.fill(Qt.transparent) pt = QPainter() pt.begin(pixmap2) if pt.paintEngine().hasFeature(QPaintEngine.PorterDuff): pt.setRenderHints( QPainter.Antialiasing | QPainter.HighQualityAntialiasing | QPainter.SmoothPixmapTransform, True) pt.setBrush(Qt.black) pt.setPen(QPen(QBrush(Qt.black), 0.5)) pt.drawEllipse(translatedEll.boundingRect()) pt.setCompositionMode(QPainter.CompositionMode_SourceIn) else: pt.setClipRegion(translatedEll) pt.setCompositionMode(QPainter.CompositionMode_Source) pt.drawPixmap(pixmap2.rect(), self.__pixmap, ell.boundingRect()) pt.end() self.grabbed.emit(pixmap2) else: r = QRect(self.__selection) if not r.isNull() and r.isValid(): self.__grabbing = True self.grabbed.emit(self.__pixmap.copy(r))
def doErase(self, continuation): tileLayer = self.currentTileLayer() tilePos = self.tilePosition() eraseRegion = QRegion(tilePos.x(), tilePos.y(), 1, 1) if (continuation): for p in pointsOnLine(self.mLastTilePos, tilePos): eraseRegion |= QRegion(p.x(), p.y(), 1, 1) self.mLastTilePos = self.tilePosition() if (not tileLayer.bounds().intersects(eraseRegion.boundingRect())): return erase = EraseTiles(self.mapDocument(), tileLayer, eraseRegion) erase.setMergeable(continuation) self.mapDocument().undoStack().push(erase) self.mapDocument().emitRegionEdited(eraseRegion, tileLayer)
def paintEvent(self, ev): """Called when paint is needed, finds out which page to magnify.""" view = self.parent().parent() layout = view.pageLayout() scale = max( min(self._scale, view.MAX_ZOOM * self.MAX_EXTRA_ZOOM / layout.zoomFactor), view.MIN_ZOOM / layout.zoomFactor) matrix = QTransform().scale(scale, scale) # the position of our center on the layout c = self.geometry().center() - view.layoutPosition() # make a region scaling back to the view scale rect = matrix.inverted()[0].mapRect(self.rect()) rect.moveCenter(c) region = QRegion(rect, QRegion.Ellipse) # touches the Pages we need to draw # our rect on the enlarged pages our_rect = self.rect() our_rect.moveCenter(matrix.map(c)) # the virtual position of the whole scaled-up layout ev_rect = ev.rect().translated(our_rect.topLeft()) # draw shadow border? shadow = False if hasattr(view, "drawDropShadow") and view.dropShadowEnabled: shadow = True shadow_width = layout.spacing * scale // 2 painter = QPainter(self) for p in layout.pagesAt(region.boundingRect()): # get a (reused) the copy of the page page = p.copy(self, matrix) # now paint it rect = (page.geometry() & ev_rect).translated(-page.pos()) painter.save() painter.translate(page.pos() - our_rect.topLeft()) if shadow: view.drawDropShadow(page, painter, shadow_width) page.paint(painter, rect, self.repaintPage) painter.restore() self.drawBorder(painter)
def copy(self, *args): l = len(args) if l==1: region = args[0] if type(region) != QRegion: region = QRegion(region) area = region.intersected(QRect(0, 0, self.width(), self.height())) bounds = region.boundingRect() areaBounds = area.boundingRect() offsetX = max(0, areaBounds.x() - bounds.x()) offsetY = max(0, areaBounds.y() - bounds.y()) copied = TileLayer(QString(), 0, 0, bounds.width(), bounds.height()) for rect in area.rects(): for x in range(rect.left(), rect.right()+1): for y in range(rect.top(), rect.bottom()+1): copied.setCell(x - areaBounds.x() + offsetX, y - areaBounds.y() + offsetY, self.cellAt(x, y)) return copied elif l==4: x, y, width, height = args return self.copy(QRegion(x, y, width, height))
def copy(self, *args): l = len(args) if l == 1: region = args[0] if type(region) != QRegion: region = QRegion(region) area = region.intersected(QRect(0, 0, self.width(), self.height())) bounds = region.boundingRect() areaBounds = area.boundingRect() offsetX = max(0, areaBounds.x() - bounds.x()) offsetY = max(0, areaBounds.y() - bounds.y()) copied = TileLayer(QString(), 0, 0, bounds.width(), bounds.height()) for rect in area.rects(): for x in range(rect.left(), rect.right() + 1): for y in range(rect.top(), rect.bottom() + 1): copied.setCell(x - areaBounds.x() + offsetX, y - areaBounds.y() + offsetY, self.cellAt(x, y)) return copied elif l == 4: x, y, width, height = args return self.copy(QRegion(x, y, width, height))
class BucketFillTool(AbstractTileTool): def tr(self, sourceText, disambiguation = '', n = -1): return QCoreApplication.translate('BucketFillTool', sourceText, disambiguation, n) def __init__(self, parent = None): super().__init__(self.tr("Bucket Fill Tool"), QIcon(":images/22x22/stock-tool-bucket-fill.png"), QKeySequence(self.tr("F")), parent) self.mStamp = TileStamp() self.mFillOverlay = None self.mFillRegion = QRegion() self.mMissingTilesets = QVector() self.mIsActive = False self.mLastShiftStatus = False ## # Indicates if the tool is using the random mode. ## self.mIsRandom = False ## # Contains the value of mIsRandom at that time, when the latest call of # tilePositionChanged() took place. # This variable is needed to detect if the random mode was changed during # mFillOverlay being brushed at an area. ## self.mLastRandomStatus = False ## # Contains all used random cells to use in random mode. # The same cell can be in the list multiple times to make different # random weights possible. ## self.mRandomCellPicker = RandomPicker() def __del__(self): pass def activate(self, scene): super().activate(scene) self.mIsActive = True self.tilePositionChanged(self.tilePosition()) def deactivate(self, scene): super().deactivate(scene) self.mFillRegion = QRegion() self.mIsActive = False def mousePressed(self, event): if (event.button() != Qt.LeftButton or self.mFillRegion.isEmpty()): return if (not self.brushItem().isVisible()): return preview = self.mFillOverlay if not preview: return paint = PaintTileLayer(self.mapDocument(), self.currentTileLayer(), preview.x(), preview.y(), preview) paint.setText(QCoreApplication.translate("Undo Commands", "Fill Area")) if not self.mMissingTilesets.isEmpty(): for tileset in self.mMissingTilesets: AddTileset(self.mapDocument(), tileset, paint) self.mMissingTilesets.clear() fillRegion = QRegion(self.mFillRegion) self.mapDocument().undoStack().push(paint) self.mapDocument().emitRegionEdited(fillRegion, self.currentTileLayer()) def mouseReleased(self, event): pass def modifiersChanged(self, modifiers): # Don't need to recalculate fill region if there was no fill region if (not self.mFillOverlay): return self.tilePositionChanged(self.tilePosition()) def languageChanged(self): self.setName(self.tr("Bucket Fill Tool")) self.setShortcut(QKeySequence(self.tr("F"))) ## # Sets the stamp that is drawn when filling. The BucketFillTool takes # ownership over the stamp layer. ## def setStamp(self, stamp): # Clear any overlay that we presently have with an old stamp self.clearOverlay() self.mStamp = stamp self.updateRandomListAndMissingTilesets() if (self.mIsActive and self.brushItem().isVisible()): self.tilePositionChanged(self.tilePosition()) ## # This returns the actual tile layer which is used to define the current # state. ## def stamp(self): return TileStamp(self.mStamp) def setRandom(self, value): if (self.mIsRandom == value): return self.mIsRandom = value self.updateRandomListAndMissingTilesets() # Don't need to recalculate fill region if there was no fill region if (not self.mFillOverlay): return self.tilePositionChanged(self.tilePosition()) def tilePositionChanged(self, tilePos): # Skip filling if the stamp is empty if self.mStamp.isEmpty(): return # Make sure that a tile layer is selected tileLayer = self.currentTileLayer() if (not tileLayer): return shiftPressed = QApplication.keyboardModifiers() & Qt.ShiftModifier fillRegionChanged = False regionComputer = TilePainter(self.mapDocument(), tileLayer) # If the stamp is a single tile, ignore it when making the region if (not shiftPressed and self.mStamp.variations().size() == 1): variation = self.mStamp.variations().first() stampLayer = variation.tileLayer() if (stampLayer.size() == QSize(1, 1) and stampLayer.cellAt(0, 0) == regionComputer.cellAt(tilePos)): return # This clears the connections so we don't get callbacks self.clearConnections(self.mapDocument()) # Optimization: we don't need to recalculate the fill area # if the new mouse position is still over the filled region # and the shift modifier hasn't changed. if (not self.mFillRegion.contains(tilePos) or shiftPressed != self.mLastShiftStatus): # Clear overlay to make way for a new one self.clearOverlay() # Cache information about how the fill region was created self.mLastShiftStatus = shiftPressed # Get the new fill region if (not shiftPressed): # If not holding shift, a region is generated from the current pos self.mFillRegion = regionComputer.computePaintableFillRegion(tilePos) else: # If holding shift, the region is the selection bounds self.mFillRegion = self.mapDocument().selectedArea() # Fill region is the whole map if there is no selection if (self.mFillRegion.isEmpty()): self.mFillRegion = tileLayer.bounds() # The mouse needs to be in the region if (not self.mFillRegion.contains(tilePos)): self.mFillRegion = QRegion() fillRegionChanged = True # Ensure that a fill region was created before making an overlay layer if (self.mFillRegion.isEmpty()): return if (self.mLastRandomStatus != self.mIsRandom): self.mLastRandomStatus = self.mIsRandom fillRegionChanged = True if (not self.mFillOverlay): # Create a new overlay region fillBounds = self.mFillRegion.boundingRect() self.mFillOverlay = TileLayer(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height()) # Paint the new overlay if (not self.mIsRandom): if (fillRegionChanged or self.mStamp.variations().size() > 1): fillWithStamp(self.mFillOverlay, self.mStamp, self.mFillRegion.translated(-self.mFillOverlay.position())) fillRegionChanged = True else: self.randomFill(self.mFillOverlay, self.mFillRegion) fillRegionChanged = True if (fillRegionChanged): # Update the brush item to draw the overlay self.brushItem().setTileLayer(self.mFillOverlay) # Create connections to know when the overlay should be cleared self.makeConnections() def mapDocumentChanged(self, oldDocument, newDocument): super().mapDocumentChanged(oldDocument, newDocument) self.clearConnections(oldDocument) # Reset things that are probably invalid now if newDocument: self.updateRandomListAndMissingTilesets() self.clearOverlay() def clearOverlay(self): # Clear connections before clearing overlay so there is no # risk of getting a callback and causing an infinite loop self.clearConnections(self.mapDocument()) self.brushItem().clear() self.mFillOverlay = None self.mFillRegion = QRegion() def makeConnections(self): if (not self.mapDocument()): return # Overlay may need to be cleared if a region changed self.mapDocument().regionChanged.connect(self.clearOverlay) # Overlay needs to be cleared if we switch to another layer self.mapDocument().currentLayerIndexChanged.connect(self.clearOverlay) # Overlay needs be cleared if the selection changes, since # the overlay may be bound or may need to be bound to the selection self.mapDocument().selectedAreaChanged.connect(self.clearOverlay) def clearConnections(self, mapDocument): if (not mapDocument): return try: mapDocument.regionChanged.disconnect(self.clearOverlay) mapDocument.currentLayerIndexChanged.disconnect(self.clearOverlay) mapDocument.selectedAreaChanged.disconnect(self.clearOverlay) except: pass ## # Updates the list of random cells. # This is done by taking all non-null tiles from the original stamp mStamp. ## def updateRandomListAndMissingTilesets(self): self.mRandomCellPicker.clear() self.mMissingTilesets.clear() for variation in self.mStamp.variations(): self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets) if self.mIsRandom: for cell in variation.tileLayer(): if not cell.isEmpty(): self.mRandomCellPicker.add(cell, cell.tile.probability()) ## # Fills the given \a region in the given \a tileLayer with random tiles. ## def randomFill(self, tileLayer, region): if (region.isEmpty() or self.mRandomList.empty()): return for rect in region.translated(-tileLayer.position()).rects(): for _x in range(rect.left(), rect.right()+1): for _y in range(rect.top(), rect.bottom()+1): tileLayer.setCell(_x, _y, self.mRandomCellPicker.pick())
class BucketFillTool(AbstractTileTool): def tr(self, sourceText, disambiguation='', n=-1): return QCoreApplication.translate('BucketFillTool', sourceText, disambiguation, n) def __init__(self, parent=None): super().__init__(self.tr("Bucket Fill Tool"), QIcon(":images/22x22/stock-tool-bucket-fill.png"), QKeySequence(self.tr("F")), parent) self.mStamp = TileStamp() self.mFillOverlay = None self.mFillRegion = QRegion() self.mMissingTilesets = QVector() self.mIsActive = False self.mLastShiftStatus = False ## # Indicates if the tool is using the random mode. ## self.mIsRandom = False ## # Contains the value of mIsRandom at that time, when the latest call of # tilePositionChanged() took place. # This variable is needed to detect if the random mode was changed during # mFillOverlay being brushed at an area. ## self.mLastRandomStatus = False ## # Contains all used random cells to use in random mode. # The same cell can be in the list multiple times to make different # random weights possible. ## self.mRandomCellPicker = RandomPicker() def __del__(self): pass def activate(self, scene): super().activate(scene) self.mIsActive = True self.tilePositionChanged(self.tilePosition()) def deactivate(self, scene): super().deactivate(scene) self.mFillRegion = QRegion() self.mIsActive = False def mousePressed(self, event): if (event.button() != Qt.LeftButton or self.mFillRegion.isEmpty()): return if (not self.brushItem().isVisible()): return preview = self.mFillOverlay if not preview: return paint = PaintTileLayer(self.mapDocument(), self.currentTileLayer(), preview.x(), preview.y(), preview) paint.setText(QCoreApplication.translate("Undo Commands", "Fill Area")) if not self.mMissingTilesets.isEmpty(): for tileset in self.mMissingTilesets: AddTileset(self.mapDocument(), tileset, paint) self.mMissingTilesets.clear() fillRegion = QRegion(self.mFillRegion) self.mapDocument().undoStack().push(paint) self.mapDocument().emitRegionEdited(fillRegion, self.currentTileLayer()) def mouseReleased(self, event): pass def modifiersChanged(self, modifiers): # Don't need to recalculate fill region if there was no fill region if (not self.mFillOverlay): return self.tilePositionChanged(self.tilePosition()) def languageChanged(self): self.setName(self.tr("Bucket Fill Tool")) self.setShortcut(QKeySequence(self.tr("F"))) ## # Sets the stamp that is drawn when filling. The BucketFillTool takes # ownership over the stamp layer. ## def setStamp(self, stamp): # Clear any overlay that we presently have with an old stamp self.clearOverlay() self.mStamp = stamp self.updateRandomListAndMissingTilesets() if (self.mIsActive and self.brushItem().isVisible()): self.tilePositionChanged(self.tilePosition()) ## # This returns the actual tile layer which is used to define the current # state. ## def stamp(self): return TileStamp(self.mStamp) def setRandom(self, value): if (self.mIsRandom == value): return self.mIsRandom = value self.updateRandomListAndMissingTilesets() # Don't need to recalculate fill region if there was no fill region if (not self.mFillOverlay): return self.tilePositionChanged(self.tilePosition()) def tilePositionChanged(self, tilePos): # Skip filling if the stamp is empty if self.mStamp.isEmpty(): return # Make sure that a tile layer is selected tileLayer = self.currentTileLayer() if (not tileLayer): return shiftPressed = QApplication.keyboardModifiers() & Qt.ShiftModifier fillRegionChanged = False regionComputer = TilePainter(self.mapDocument(), tileLayer) # If the stamp is a single tile, ignore it when making the region if (not shiftPressed and self.mStamp.variations().size() == 1): variation = self.mStamp.variations().first() stampLayer = variation.tileLayer() if (stampLayer.size() == QSize(1, 1) and stampLayer.cellAt(0, 0) == regionComputer.cellAt(tilePos)): return # This clears the connections so we don't get callbacks self.clearConnections(self.mapDocument()) # Optimization: we don't need to recalculate the fill area # if the new mouse position is still over the filled region # and the shift modifier hasn't changed. if (not self.mFillRegion.contains(tilePos) or shiftPressed != self.mLastShiftStatus): # Clear overlay to make way for a new one self.clearOverlay() # Cache information about how the fill region was created self.mLastShiftStatus = shiftPressed # Get the new fill region if (not shiftPressed): # If not holding shift, a region is generated from the current pos self.mFillRegion = regionComputer.computePaintableFillRegion( tilePos) else: # If holding shift, the region is the selection bounds self.mFillRegion = self.mapDocument().selectedArea() # Fill region is the whole map if there is no selection if (self.mFillRegion.isEmpty()): self.mFillRegion = tileLayer.bounds() # The mouse needs to be in the region if (not self.mFillRegion.contains(tilePos)): self.mFillRegion = QRegion() fillRegionChanged = True # Ensure that a fill region was created before making an overlay layer if (self.mFillRegion.isEmpty()): return if (self.mLastRandomStatus != self.mIsRandom): self.mLastRandomStatus = self.mIsRandom fillRegionChanged = True if (not self.mFillOverlay): # Create a new overlay region fillBounds = self.mFillRegion.boundingRect() self.mFillOverlay = TileLayer(QString(), fillBounds.x(), fillBounds.y(), fillBounds.width(), fillBounds.height()) # Paint the new overlay if (not self.mIsRandom): if (fillRegionChanged or self.mStamp.variations().size() > 1): fillWithStamp( self.mFillOverlay, self.mStamp, self.mFillRegion.translated(-self.mFillOverlay.position())) fillRegionChanged = True else: self.randomFill(self.mFillOverlay, self.mFillRegion) fillRegionChanged = True if (fillRegionChanged): # Update the brush item to draw the overlay self.brushItem().setTileLayer(self.mFillOverlay) # Create connections to know when the overlay should be cleared self.makeConnections() def mapDocumentChanged(self, oldDocument, newDocument): super().mapDocumentChanged(oldDocument, newDocument) self.clearConnections(oldDocument) # Reset things that are probably invalid now if newDocument: self.updateRandomListAndMissingTilesets() self.clearOverlay() def clearOverlay(self): # Clear connections before clearing overlay so there is no # risk of getting a callback and causing an infinite loop self.clearConnections(self.mapDocument()) self.brushItem().clear() self.mFillOverlay = None self.mFillRegion = QRegion() def makeConnections(self): if (not self.mapDocument()): return # Overlay may need to be cleared if a region changed self.mapDocument().regionChanged.connect(self.clearOverlay) # Overlay needs to be cleared if we switch to another layer self.mapDocument().currentLayerIndexChanged.connect(self.clearOverlay) # Overlay needs be cleared if the selection changes, since # the overlay may be bound or may need to be bound to the selection self.mapDocument().selectedAreaChanged.connect(self.clearOverlay) def clearConnections(self, mapDocument): if (not mapDocument): return try: mapDocument.regionChanged.disconnect(self.clearOverlay) mapDocument.currentLayerIndexChanged.disconnect(self.clearOverlay) mapDocument.selectedAreaChanged.disconnect(self.clearOverlay) except: pass ## # Updates the list of random cells. # This is done by taking all non-null tiles from the original stamp mStamp. ## def updateRandomListAndMissingTilesets(self): self.mRandomCellPicker.clear() self.mMissingTilesets.clear() for variation in self.mStamp.variations(): self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets) if self.mIsRandom: for cell in variation.tileLayer(): if not cell.isEmpty(): self.mRandomCellPicker.add(cell, cell.tile.probability()) ## # Fills the given \a region in the given \a tileLayer with random tiles. ## def randomFill(self, tileLayer, region): if (region.isEmpty() or self.mRandomList.empty()): return for rect in region.translated(-tileLayer.position()).rects(): for _x in range(rect.left(), rect.right() + 1): for _y in range(rect.top(), rect.bottom() + 1): tileLayer.setCell(_x, _y, self.mRandomCellPicker.pick())
class BrushItem(QGraphicsItem): ## # Constructor. ## def __init__(self): super().__init__() self.mMapDocument = None self.mBoundingRect = QRectF() self.mRegion = QRegion() self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption) ## # Sets the map document this brush is operating on. ## def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return self.mMapDocument = mapDocument # The tiles in the stamp may no longer be valid self.clear() ## # Clears the tile layer and region set on this item. ## def clear(self): self.setTileLayer(None) ## # Sets a tile layer representing this brush. When no tile layer is set, # the brush only draws the selection color. # # The BrushItem does not take ownership over the tile layer, but makes a # personal copy of the tile layer. ## def setTileLayer(self, tileLayer): self.mTileLayer = tileLayer if (tileLayer): self.mRegion = tileLayer.region() else: self.mRegion = QRegion() self.updateBoundingRect() self.update() ## # Returns the current tile layer. ## def tileLayer(self): return self.mTileLayer ## # Changes the position of the tile layer, if one is set. ## def setTileLayerPosition(self, pos): if (not self.mTileLayer): return oldPosition = QPoint(self.mTileLayer.x(), self.mTileLayer.y()) if (oldPosition == pos): return self.mRegion.translate(pos - oldPosition) self.mTileLayer.setX(pos.x()) self.mTileLayer.setY(pos.y()) self.updateBoundingRect() ## # Sets the region of tiles that this brush item occupies. ## def setTileRegion(self, region): if type(region) != QRegion: region = QRegion(region) if (self.mRegion == region): return self.mRegion = region self.updateBoundingRect() ## # Sets the layer offset used by the currently active layer. ## def setLayerOffset(self, offset): self.setPos(offset) ## # Returns the region of the current tile layer or the region that was set # using setTileRegion. ## def tileRegion(self): return self.mRegion # QGraphicsItem def boundingRect(self): return self.mBoundingRect def paint(self, painter, option, widget=None): insideMapHighlight = QApplication.palette().highlight().color() insideMapHighlight.setAlpha(64) outsideMapHighlight = QColor(255, 0, 0, 64) mapWidth = self.mMapDocument.map().width() mapHeight = self.mMapDocument.map().height() mapRegion = QRegion(0, 0, mapWidth, mapHeight) insideMapRegion = self.mRegion.intersected(mapRegion) outsideMapRegion = self.mRegion.subtracted(mapRegion) renderer = self.mMapDocument.renderer() if (self.mTileLayer): opacity = painter.opacity() painter.setOpacity(0.75) renderer.drawTileLayer(painter, self.mTileLayer, option.exposedRect) painter.setOpacity(opacity) renderer.drawTileSelection(painter, insideMapRegion, insideMapHighlight, option.exposedRect) renderer.drawTileSelection(painter, outsideMapRegion, outsideMapHighlight, option.exposedRect) def updateBoundingRect(self): self.prepareGeometryChange() if (not self.mMapDocument): self.mBoundingRect = QRectF() return bounds = self.mRegion.boundingRect() self.mBoundingRect = QRectF( self.mMapDocument.renderer().boundingRect(bounds)) # Adjust for amount of pixels tiles extend at the top and to the right if (self.mTileLayer): map = self.mMapDocument.map() drawMargins = self.mTileLayer.drawMargins() drawMargins.setTop(drawMargins.top() - map.tileHeight()) drawMargins.setRight(drawMargins.right() - map.tileWidth()) # Since we're also drawing a tile selection, we should not apply # negative margins self.mBoundingRect.adjust(min(0, -drawMargins.left()), min(0, -drawMargins.top()), max(0, drawMargins.right()), max(0, drawMargins.bottom()))
def drawPreviewLayer(self, _list): self.mPreviewLayer = None if self.mStamp.isEmpty(): return if self.mIsRandom: if self.mRandomCellPicker.isEmpty(): return paintedRegion = QRegion() for p in _list: paintedRegion += QRect(p, QSize(1, 1)) bounds = paintedRegion.boundingRect() preview = TileLayer(QString(), bounds.x(), bounds.y(), bounds.width(), bounds.height()) for p in _list: cell = self.mRandomCellPicker.pick() preview.setCell(p.x() - bounds.left(), p.y() - bounds.top(), cell) self.mPreviewLayer = preview else: self.mMissingTilesets.clear() paintedRegion = QRegion() operations = QVector() regionCache = QHash() for p in _list: variation = self.mStamp.randomVariation() self.mapDocument().unifyTilesets(variation.map, self.mMissingTilesets) stamp = variation.tileLayer() stampRegion = QRegion() if regionCache.contains(stamp): stampRegion = regionCache.value(stamp) else: stampRegion = stamp.region() regionCache.insert(stamp, stampRegion) centered = QPoint(p.x() - int(stamp.width() / 2), p.y() - int(stamp.height() / 2)) region = stampRegion.translated(centered.x(), centered.y()) if not paintedRegion.intersects(region): paintedRegion += region op = PaintOperation(centered, stamp) operations.append(op) bounds = paintedRegion.boundingRect() preview = TileLayer(QString(), bounds.x(), bounds.y(), bounds.width(), bounds.height()) for op in operations: preview.merge(op.pos - bounds.topLeft(), op.stamp) self.mPreviewLayer = preview
class BrushItem(QGraphicsItem): ## # Constructor. ## def __init__(self): super().__init__() self.mMapDocument = None self.mBoundingRect = QRectF() self.mRegion = QRegion() self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption) ## # Sets the map document this brush is operating on. ## def setMapDocument(self, mapDocument): if (self.mMapDocument == mapDocument): return self.mMapDocument = mapDocument # The tiles in the stamp may no longer be valid self.clear() ## # Clears the tile layer and region set on this item. ## def clear(self): self.setTileLayer(None) ## # Sets a tile layer representing this brush. When no tile layer is set, # the brush only draws the selection color. # # The BrushItem does not take ownership over the tile layer, but makes a # personal copy of the tile layer. ## def setTileLayer(self, tileLayer): self.mTileLayer = tileLayer if (tileLayer): self.mRegion = tileLayer.region() else: self.mRegion = QRegion() self.updateBoundingRect() self.update() ## # Returns the current tile layer. ## def tileLayer(self): return self.mTileLayer ## # Changes the position of the tile layer, if one is set. ## def setTileLayerPosition(self, pos): if (not self.mTileLayer): return oldPosition = QPoint(self.mTileLayer.x(), self.mTileLayer.y()) if (oldPosition == pos): return self.mRegion.translate(pos - oldPosition) self.mTileLayer.setX(pos.x()) self.mTileLayer.setY(pos.y()) self.updateBoundingRect() ## # Sets the region of tiles that this brush item occupies. ## def setTileRegion(self, region): if type(region)!=QRegion: region = QRegion(region) if (self.mRegion == region): return self.mRegion = region self.updateBoundingRect() ## # Sets the layer offset used by the currently active layer. ## def setLayerOffset(self, offset): self.setPos(offset) ## # Returns the region of the current tile layer or the region that was set # using setTileRegion. ## def tileRegion(self): return self.mRegion # QGraphicsItem def boundingRect(self): return self.mBoundingRect def paint(self, painter, option, widget = None): insideMapHighlight = QApplication.palette().highlight().color() insideMapHighlight.setAlpha(64) outsideMapHighlight = QColor(255, 0, 0, 64) mapWidth = self.mMapDocument.map().width() mapHeight = self.mMapDocument.map().height() mapRegion = QRegion(0, 0, mapWidth, mapHeight) insideMapRegion = self.mRegion.intersected(mapRegion) outsideMapRegion = self.mRegion.subtracted(mapRegion) renderer = self.mMapDocument.renderer() if (self.mTileLayer): opacity = painter.opacity() painter.setOpacity(0.75) renderer.drawTileLayer(painter, self.mTileLayer, option.exposedRect) painter.setOpacity(opacity) renderer.drawTileSelection(painter, insideMapRegion, insideMapHighlight, option.exposedRect) renderer.drawTileSelection(painter, outsideMapRegion, outsideMapHighlight, option.exposedRect) def updateBoundingRect(self): self.prepareGeometryChange() if (not self.mMapDocument): self.mBoundingRect = QRectF() return bounds = self.mRegion.boundingRect() self.mBoundingRect = QRectF(self.mMapDocument.renderer().boundingRect(bounds)) # Adjust for amount of pixels tiles extend at the top and to the right if (self.mTileLayer): map = self.mMapDocument.map() drawMargins = self.mTileLayer.drawMargins() drawMargins.setTop(drawMargins.top() - map.tileHeight()) drawMargins.setRight(drawMargins.right() - map.tileWidth()) # Since we're also drawing a tile selection, we should not apply # negative margins self.mBoundingRect.adjust(min(0, -drawMargins.left()), min(0, -drawMargins.top()), max(0, drawMargins.right()), max(0, drawMargins.bottom()))
class Mask: def __init__(self, mask_path: Path): mask_pix = QPixmap() if not mask_pix.load(str(mask_path)): raise ValueError(f"Failed to load mask from {mask_path}") self.size = mask_pix.size() logger.debug("size: %s", self.size) self.clip_region = QRegion( mask_pix.createMaskFromColor(QColor("white"), Qt.MaskInColor) ) logger.debug("clip_region: %s", self.clip_region) logger.debug("clip_region bounding rect: %s", self.clip_region.boundingRect()) def mask(self, image: QImage) -> QImage: clip_rect = self._build_clip_rect(image) masked = QImage(self.size, QImage.Format_RGB32) painter = QPainter(masked) painter.setRenderHints(QPainter.Antialiasing, QPainter.SmoothPixmapTransform) painter.setClipRegion(self.clip_region) painter.drawImage(masked.rect(), image, clip_rect) painter.end() return masked def shrink_and_clip_to_mask_size(self, image): clip_rect = self._build_clip_rect(image) shrunk_and_clipped = QImage(self.size, QImage.Format_RGB32) painter = QPainter(shrunk_and_clipped) painter.setRenderHints(QPainter.Antialiasing, QPainter.SmoothPixmapTransform) painter.drawImage(shrunk_and_clipped.rect(), image, clip_rect) painter.end() return shrunk_and_clipped def _build_clip_rect(self, image): logger.debug("Image size: %s", image.size()) image_height_for_width = image.height() / image.width() mask_height_for_width = float(self.size.height()) / self.size.width() logger.debug( "Height for widths - image: %s mask: %s", image_height_for_width, mask_height_for_width, ) if image_height_for_width > mask_height_for_width: # image is taller than mask clip_height = int(image.size().width() * mask_height_for_width) clip_rect = QRect( 0, (image.size().height() - clip_height) / 2, image.size().width(), clip_height, ) else: # image is wider than mask clip_width = int(image.size().height() / mask_height_for_width) clip_rect = QRect( (image.size().width() - clip_width) / 2, 0, clip_width, image.size().height(), ) logger.debug("Clip rect: %s", clip_rect) return clip_rect
class Menu(QMainWindow): # ------------------------- Init ---------------------------------- def __init__(self): super(Menu, self).__init__() self.background_picture = QGraphicsView() self.scene = QGraphicsScene() self.grp = QGroupBox("Mon Groupe") self.palette = QPalette() self.proxy = QGraphicsProxyWidget() self.background_picture2 = QPixmap() self.fenetre_creer_une_partie = None self.bouton_creer = QPushButton(self) self.bouton_rejoindre = QPushButton(self) self.bouton_quitter = QPushButton(self) self.bouton_help = QPushButton(self) self.label = QLabel(self) self.init_ui() self.setWindowTitle("Bienvenue dans Skip-Bo!") self.hauteur = int self.quart_hauteur = int self.longueur = int self.quart_longueur = int self.test_rect = QRect() self.test_region = QRegion() self.rsp = None self.rsp2 = None self.rsp3 = None self.join = QDialog() self.verification = QDialog() self.quitter_icon = QIcon() self.join_icon = QIcon() self.creer_icon = QIcon() self.help_icon = QIcon() self.msg_help = QMessageBox() # self.socket_connexion = None # ---------------------- Fin du Init ------------------------------ # ---------------------- Fonction pour centrer la fenêtre dans l'écran ---------------------- def center(self): frame = self.frameGeometry() ecran_actif = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) point_central = QApplication.desktop().screenGeometry( ecran_actif).center() frame.moveCenter(point_central) self.move(frame.topLeft()) # --------------------- Fin de la fonction -------------------------------------------------- # --------------------- Fonction quitter ---------------------------------------------------- def quitter(self): self.close() # --------------------- Fin de la fonction -------------------------------------------------- # --------------------- Fonction pour créer une partie -------------------------------------- def creer_nouvelle_partie(self): self.fenetre_creer_une_partie = creation_partie.NouvellePartie() self.fenetre_creer_une_partie.setModal(True) self.fenetre_creer_une_partie.show() self.rsp = self.fenetre_creer_une_partie.exec_() if self.rsp == QDialog.Accepted: self.close() else: print("Cancel") # --------------------- Fin de la fonction ------------------------------------------------- # --------------------- Fonction pour joindre une partie ----------------------------------- def joindre_partie(self): self.verification = verification.Verification() self.verification.setModal(True) self.verification.show() self.rsp2 = self.verification.exec_() # print(self.verification.socket_de_connection) if self.rsp2 == QDialog.Accepted: # print(self.socket_connexion) self.join = join.JoinPartie() self.join.setModal(True) self.join.show() self.rsp3 = self.join.exec_() if self.rsp3 == QDialog.Accepted: self.close() else: print("Join canceled") else: print("Cancel") # --------------------- Fin de la fonction ------------------------------------------------- # --------------------- Fonction d'aide ---------------------------------------------------- def help(self): self.msg_help.setIcon(QMessageBox.Information) self.msg_help.setText("Bienvenue dans la rubrique d'aide") self.msg_help.setInformativeText( 'Pour créer une partie, cliquez sur le bouton "Créer une partie".\n \nPour ' 'joindre une partie, ayez d\'abord votre code d\'invitation en main, puis ' 'cliquez sur joindre une partie.') self.msg_help.setWindowTitle("Rubrique d'aide") self.msg_help.setStandardButtons(QMessageBox.Ok) self.msg_help.exec_() # --------------------- Fin de la fonction ------------------------------------------------- # --------------------- Initialisation de la fenêtre et des éléments graphique ------------- def init_ui(self): # --------------- Paramètres de la fenêtre -------------------- self.resize(1280, 720) self.setMaximumSize(1280, 720) self.setMinimumSize(1280, 720) self.center() self.background_picture2.load('backgroundmenu3.png') self.palette.setBrush(self.backgroundRole(), QBrush(self.background_picture2)) self.setPalette(self.palette) # --------------- Éléments présents dans la fenêtre ---------- self.hauteur = int(self.frameGeometry().height()) self.quart_hauteur = int(self.hauteur / 4) self.longueur = int(self.frameGeometry().width()) self.quart_longueur = int(self.longueur / 4) self.label.setText("* Skip-Bo est une marque déposée de Mattel Inc.") self.label.move(20, 690) self.label.adjustSize() self.bouton_creer.setText("") self.creer_icon = QIcon("bouton_creer.png") self.bouton_creer.setIcon(self.creer_icon) self.bouton_creer.setIconSize(QSize(320, 90)) self.bouton_creer.setGeometry((self.quart_longueur - 160), (3 * self.quart_hauteur), self.quart_longueur, int(self.quart_hauteur / 2)) self.bouton_creer.clicked.connect(self.creer_nouvelle_partie) self.bouton_rejoindre.setText("") self.join_icon = QIcon("bouton_join.png") self.bouton_rejoindre.setIcon(self.join_icon) self.bouton_rejoindre.setIconSize(QSize(318, 88)) self.bouton_rejoindre.setGeometry(((2 * self.quart_longueur) - 160), (3 * self.quart_hauteur), self.quart_longueur, int(self.quart_hauteur / 2)) self.bouton_rejoindre.clicked.connect(self.joindre_partie) self.bouton_help.setText("") self.help_icon = QIcon("bouton_help.png") self.bouton_help.setIcon(self.help_icon) self.bouton_help.setIconSize(QSize(65, 65)) self.bouton_help.setFixedHeight(100) self.bouton_help.setFixedWidth(100) self.bouton_help.move( self.longueur - int(self.quart_longueur / (4 / 2)), -10) self.bouton_help.clicked.connect(self.help) self.test_rect = QRect(20, 20, 60, 60) self.test_region = QRegion(self.test_rect, QRegion.Ellipse) self.test_region.boundingRect().size() self.bouton_help.setMask(self.test_region) self.bouton_quitter.setText("") self.quitter_icon = QIcon("bouton_quitter.png") self.bouton_quitter.setIcon(self.quitter_icon) self.bouton_quitter.setIconSize(QSize(318, 88)) self.bouton_quitter.setGeometry(((3 * self.quart_longueur) - 160), (3 * self.quart_hauteur), self.quart_longueur, int(self.quart_hauteur / 2)) self.bouton_quitter.clicked.connect(self.quitter)