from common import * from math import floor, ceil import weakref class KPEditorItem(QtGui.QGraphicsItem): def __init__(self): QtGui.QGraphicsItem.__init__(self) self.setFlags( self.ItemSendsGeometryChanges | self.ItemIsSelectable | self.ItemIsMovable ) def itemChange(self, change, value): if change == self.ItemPositionChange: currentX, currentY = self.x(), self.y() # snap the item to 24x24 newpos = value.toPyObject() x, y = newpos.x(), newpos.y() x = int((x + 12) / 24) * 24 y = int((y + 12) / 24) * 24 if x < 0: x = 0 if x > 12264: x = 12264 if y < 0: y = 0 if y > 12264: y = 12264 if x != currentX or y != currentY: self._itemMoved(currentX, currentY, x, y) newpos.setX(x) newpos.setY(y) return newpos return QtGui.QGraphicsItem.itemChange(self, change, value) def boundingRect(self): return self._boundingRect def _itemMoved(self, oldX, oldY, newX, newY): pass class KPEditorObject(KPEditorItem): def __init__(self, obj, layer): KPEditorItem.__init__(self) obj.qtItem = self self._objRef = weakref.ref(obj) self._layerRef = weakref.ref(layer) self._updatePosition() self._updateSize() self.setAcceptHoverEvents(True) self.resizing = None # I don't bother setting the ZValue because it doesn't quite matter: # only one layer's objects are ever clickable, and drawBackground takes # care of the layered drawing def _updatePosition(self): obj = self._objRef() x,y = obj.position self.setPos(x*24, y*24) def _updateSize(self): self.prepareGeometryChange() obj = self._objRef() w,h = obj.size self._boundingRect = QtCore.QRectF(0, 0, w*24, h*24) self._selectionRect = QtCore.QRectF(0, 0, w*24-1, h*24-1) self._resizerEndXY = (w*24-5, h*24-5) def paint(self, painter, option, widget): if self.isSelected(): painter.setPen(QtGui.QPen(Qt.white, 1, Qt.DotLine)) painter.drawRect(self._selectionRect) RESIZER_TOP_LEFT = 1 RESIZER_TOP_RIGHT = 2 RESIZER_BOTTOM_LEFT = 3 RESIZER_BOTTOM_RIGHT = 4 RESIZER_TOP = 5 RESIZER_BOTTOM = 6 RESIZER_LEFT = 7 RESIZER_RIGHT = 8 def resizerPortionAt(self, x, y): rightBound, bottomBound = self._resizerEndXY if y < 5: if x < 5: return 1 # TOP_LEFT elif x >= rightBound: return 2 # TOP_RIGHT else: return 5 # TOP elif y >= bottomBound: if x < 5: return 3 # BOTTOM_LEFT elif x >= rightBound: return 4 # BOTTOM_RIGHT else: return 6 # BOTTOM else: if x < 5: return 7 # LEFT elif x >= rightBound: return 8 # RIGHT else: return None def hoverMoveEvent(self, event): if self._layerRef() != KP.mapScene.currentLayer: self.setCursor(Qt.ArrowCursor) return pos = event.pos() bit = self.resizerPortionAt(pos.x(), pos.y()) if bit == 1 or bit == 4: self.setCursor(Qt.SizeFDiagCursor) elif bit == 2 or bit == 3: self.setCursor(Qt.SizeBDiagCursor) elif bit == 7 or bit == 8: self.setCursor(Qt.SizeHorCursor) elif bit == 5 or bit == 6: self.setCursor(Qt.SizeVerCursor) else: self.setCursor(Qt.ArrowCursor) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: pos = event.pos() bit = self.resizerPortionAt(pos.x(), pos.y()) if self._layerRef() == KP.mapScene.currentLayer and bit: event.accept() x, xSide, y, ySide = False, None, False, None if bit == 1 or bit == 7 or bit == 3: x, xSide = True, 1 elif bit == 2 or bit == 4 or bit == 8: x, xSide = True, 0 if bit == 1 or bit == 2 or bit == 5: y, ySide = True, 1 elif bit == 3 or bit == 4 or bit == 6: y, ySide = True, 0 self.resizing = (x, xSide, y, ySide) return KPEditorItem.mousePressEvent(self, event) def _tryAndResize(self, obj, axisIndex, mousePosition, stationarySide): objPosition = obj.position[axisIndex] objSize = obj.size[axisIndex] if stationarySide == 0: # Resize the right/bottom side relativeMousePosition = mousePosition - objPosition newSize = relativeMousePosition + 1 if newSize == objSize or newSize < 1: return False if axisIndex == 1: obj.size = (obj.size[0], newSize) else: obj.size = (newSize, obj.size[1]) else: # Resize the left/top side rightSide = objPosition + objSize - 1 newLeftSide = mousePosition newPosition = newLeftSide newSize = rightSide - newLeftSide + 1 if newSize < 1: return False if newPosition == objPosition and newSize == objSize: return False if axisIndex == 1: obj.position = (obj.position[0], newPosition) obj.size = (obj.size[0], newSize) else: obj.position = (newPosition, obj.position[1]) obj.size = (newSize, obj.size[1]) return True def mouseMoveEvent(self, event): if self.resizing: obj = self._objRef() scenePos = event.scenePos() hasChanged = False resizeX, xSide, resizeY, ySide = self.resizing if resizeX: hasChanged |= self._tryAndResize(obj, 0, int(scenePos.x() / 24), xSide) if resizeY: hasChanged |= self._tryAndResize(obj, 1, int(scenePos.y() / 24), ySide) if hasChanged: obj.updateCache() self._layerRef().updateCache() self._updatePosition() self._updateSize() else: KPEditorItem.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event): if self.resizing and event.button() == Qt.LeftButton: self.resizing = None else: KPEditorItem.mouseReleaseEvent(self, event) def _itemMoved(self, oldX, oldY, newX, newY): obj = self._objRef() obj.position = (newX/24, newY/24) self._layerRef().updateCache() class KPMapScene(QtGui.QGraphicsScene): def __init__(self): QtGui.QGraphicsScene.__init__(self, 0, 0, 512*24, 512*24) # todo: handle selectionChanged # todo: look up why I used setItemIndexMethod(self.NoIndex) in Reggie self.currentLayer = None KP.mapScene = self def drawBackground(self, painter, rect): painter.fillRect(rect, Qt.white) areaLeft, areaTop = rect.x(), rect.y() areaWidth, areaHeight = rect.width(), rect.height() areaRight, areaBottom = areaLeft+areaWidth, areaTop+areaHeight areaLeftT = floor(areaLeft / 24) areaTopT = floor(areaTop / 24) areaRightT = ceil(areaRight / 24) areaBottomT = ceil(areaBottom / 24) for layer in reversed(KP.map.layers): left, top = layer.cacheBasePos width, height = layer.cacheSize right, bottom = left+width, top+height if width == 0 and height == 0: continue if right < areaLeftT: continue if left > areaRightT: continue if bottom < areaTopT: continue if top > areaBottomT: continue # decide how much of the layer we'll actually draw drawLeft = int(max(areaLeftT, left)) drawRight = int(min(areaRightT, right)) drawTop = int(max(areaTopT, top)) drawBottom = int(min(areaBottomT, bottom)) srcY = drawTop - top destY = drawTop * 24 baseSrcX = drawLeft - left baseDestX = drawLeft * 24 rows = layer.cache tileset = KP.map.loadedTilesets[layer.tileset] tileList = tileset.tiles for y in xrange(drawTop, drawBottom): srcX = baseSrcX destX = baseDestX row = rows[srcY] for x in xrange(drawLeft, drawRight): tile = row[srcX] if tile != -1: painter.drawPixmap(destX, destY, tileList[tile]) srcX += 1 destX += 24 srcY += 1 destY += 24 def setCurrentLayer(self, layer): if self.currentLayer is not None: self.setLayerObjectsFlag(self.currentLayer, QtGui.QGraphicsItem.ItemIsSelectable, False) self.setLayerObjectsFlag(self.currentLayer, QtGui.QGraphicsItem.ItemIsMovable, False) self.currentLayer = layer self.setLayerObjectsFlag(layer, QtGui.QGraphicsItem.ItemIsSelectable, True) self.setLayerObjectsFlag(layer, QtGui.QGraphicsItem.ItemIsMovable, True) def setLayerObjectsFlag(self, layer, flag, value): for obj in layer.objects: item = obj.qtItem if item: item.setFlag(flag, value) class KPEditorWidget(QtGui.QGraphicsView): def __init__(self, scene, parent=None): QtGui.QGraphicsView.__init__(self, scene, parent) self.setAlignment(Qt.AlignLeft | Qt.AlignTop) self.setDragMode(self.RubberBandDrag) self.xScrollBar = QtGui.QScrollBar(Qt.Horizontal, parent) self.setHorizontalScrollBar(self.xScrollBar) self.yScrollBar = QtGui.QScrollBar(Qt.Vertical, parent) self.setVerticalScrollBar(self.yScrollBar) self.centerOn(0,0) # set up stuff for painting self.paintNext = None self.paintNextID = None self._resetPaintVars() def _resetPaintVars(self): self.painting = None self.paintingItem = None self.paintBeginPosition = None def _tryToPaint(self, event): '''Called when a paint attempt is initiated''' if self.paintNext is None: return paint = self.paintNext if isinstance(paint, KPTileObject): clicked = self.mapToScene(event.x(), event.y()) x, y = clicked.x(), clicked.y() if x < 0: x = 0 if y < 0: y = 0 x = int(x / 24) y = int(y / 24) layer = self.scene().currentLayer obj = KPObject() obj.position = (x,y) obj.size = (1,1) obj.tileset = layer.tileset obj.kind = paint obj.updateCache() layer.objects.append(obj) layer.updateCache() item = KPEditorObject(obj, layer) self.scene().addItem(item) self.painting = obj self.paintingItem = item self.paintBeginPosition = (x, y) def _movedWhilePainting(self, event): '''Called when the mouse is moved while painting something''' obj = self.painting item = self.paintingItem if isinstance(obj, KPObject): clicked = self.mapToScene(event.x(), event.y()) x, y = clicked.x(), clicked.y() if x < 0: x = 0 if y < 0: y = 0 x = int(x / 24) y = int(y / 24) beginX, beginY = self.paintBeginPosition if x >= beginX: objX = beginX width = x - beginX + 1 else: objX = x width = beginX - x + 1 if y >= beginY: objY = beginY height = y - beginY + 1 else: objY = y height = beginY - y + 1 currentX, currentY = obj.position currentWidth, currentHeight = obj.size # update everything if changed changed = False if currentX != objX or currentY != objY: obj.position = (objX, objY) item._updatePosition() changed = True if currentWidth != width or currentHeight != height: obj.size = (width, height) obj.updateCache() item._updateSize() changed = True if not changed: return item._layerRef().updateCache() def mousePressEvent(self, event): if event.button() == Qt.RightButton: self._tryToPaint(event) event.accept() else: QtGui.QGraphicsView.mousePressEvent(self, event) def mouseMoveEvent(self, event): if event.buttons() == Qt.RightButton and self.painting: self._movedWhilePainting(event) event.accept() else: QtGui.QGraphicsView.mouseMoveEvent(self, event)