From 30188c779e1b419f6818596a29f7f3c7edad42bb Mon Sep 17 00:00:00 2001 From: Treeki Date: Mon, 21 Nov 2011 15:25:54 +0100 Subject: some refactoring of editorui and co --- src/editorui.py | 1227 ------------------------------------------ src/editorui/__init__.py | 0 src/editorui/doodads.py | 248 +++++++++ src/editorui/editorcommon.py | 77 +++ src/editorui/editormain.py | 419 +++++++++++++++ src/editorui/objects.py | 182 +++++++ src/editorui/paths.py | 319 +++++++++++ src/ui.py | 3 +- 8 files changed, 1247 insertions(+), 1228 deletions(-) delete mode 100644 src/editorui.py create mode 100644 src/editorui/__init__.py create mode 100644 src/editorui/doodads.py create mode 100644 src/editorui/editorcommon.py create mode 100644 src/editorui/editormain.py create mode 100644 src/editorui/objects.py create mode 100644 src/editorui/paths.py diff --git a/src/editorui.py b/src/editorui.py deleted file mode 100644 index 2ab3774..0000000 --- a/src/editorui.py +++ /dev/null @@ -1,1227 +0,0 @@ -from common import * -from math import floor, ceil -import weakref -import math - - -class KPEditorItem(QtGui.QGraphicsItem): - def __init__(self): - QtGui.QGraphicsItem.__init__(self) - self.setFlags( - self.ItemSendsGeometryChanges | - self.ItemIsSelectable | - self.ItemIsMovable - ) - - self.ignoreMovement = False - self.overrideSnap = False - - def itemChange(self, change, value): - if change == self.ItemPositionChange and not self.ignoreMovement: - currentX, currentY = self.x(), self.y() - - newpos = value.toPyObject() - - x, y = newpos.x(), newpos.y() - - if self.overrideSnap: - snapX, snapY = 1, 1 - else: - # snap the item - snapX, snapY = self.SNAP_TO - x = int((x + (snapX/2)) / snapX) * snapX - y = int((y + (snapY/2)) / snapY) * snapY - - if x < 0: x = 0 - if x >= (12288+snapX): x = (12288+snapX-1) - if y < 0: y = 0 - if y >= (12288+snapY): y = (12288+snapY-1) - - 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 resizerPortionAt(self, x, y, originX=0, originY=0): - try: - leftBound, topBound = originX+5, originY+5 - rightBound, bottomBound = self._resizerEndXY - except AttributeError: - rect = self._boundingRect - leftBound, topBound = rect.x() + 5, rect.y() + 5 - rightBound, bottomBound = rect.right() - 5, rect.bottom() - 5 - - if y < topBound: - if x < leftBound: return 1 # TOP_LEFT - elif x >= rightBound: return 2 # TOP_RIGHT - else: return 5 # TOP - - elif y >= bottomBound: - if x < leftBound: return 3 # BOTTOM_LEFT - elif x >= rightBound: return 4 # BOTTOM_RIGHT - else: return 6 # BOTTOM - - else: - if x < leftBound: return 7 # LEFT - elif x >= rightBound: return 8 # RIGHT - else: return None - - - def _itemMoved(self, oldX, oldY, newX, newY): - pass - - -class KPEditorObject(KPEditorItem): - SNAP_TO = (24,24) - - 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 - - if not hasattr(KPEditorObject, 'SELECTION_PEN'): - KPEditorObject.SELECTION_PEN = QtGui.QPen(Qt.white, 1, Qt.DotLine) - - # 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): - self.ignoreMovement = True - x,y = self._objRef().position - self.setPos(x*24, y*24) - self.ignoreMovement = False - - 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(self.SELECTION_PEN) - painter.drawRect(self._selectionRect) - - - 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() - - - def remove(self, withItem=False): - obj = self._objRef() - layer = self._layerRef() - - layer.objects.remove(obj) - layer.updateCache() - - if withItem: - self.scene().removeItem(self) - - -class KPEditorDoodad(KPEditorItem): - SNAP_TO = (12,12) - - def __init__(self, doodad, layer): - KPEditorItem.__init__(self) - - doodad.qtItem = self - self._doodadRef = weakref.ref(doodad) - self._layerRef = weakref.ref(layer) - - # TODO: refactor this to store source doodad data under KP.map - sourceItem = KP.mainWindow.doodadSelector.getDoodad(doodad.index) - self._sourceRef = weakref.ref(sourceItem) - - self.resizing = None - self.rotating = None - - self._updatePixmap() - self._updatePosition() - self._updateSize() - - self.setAcceptHoverEvents(True) - - if not hasattr(KPEditorDoodad, 'SELECTION_PEN'): - KPEditorDoodad.SELECTION_PEN = QtGui.QPen(Qt.red, 1, Qt.DotLine) - - - - def _updatePixmap(self): - source = self._sourceRef() - pixmap = source.icon().pixmap(source.icon().availableSizes()[0]) - - self.prepareGeometryChange() - w, h = pixmap.width(), pixmap.height() - - self.pixmap = pixmap - - - def _updatePosition(self): - # NOTE: EditorDoodads originate at the centre, not the top left like the others - doodad = self._doodadRef() - x,y = doodad.position - w,h = doodad.size - self.setPos(x+floor(w/2.0), y+floor(h/2.0)) - - - def _updateSize(self): - self.prepareGeometryChange() - - w,h = self._doodadRef().size - - self._boundingRect = QtCore.QRectF(-w/2, -h/2, w, h) - self._selectionRect = self._boundingRect.adjusted(0, 0, -1, -1) - - - def _updateTransform(self): - doodad = self._doodadRef() - - self.setRotation(doodad.angle) - - - def paint(self, painter, option, widget): - if self.isSelected(): - painter.setPen(self.SELECTION_PEN) - painter.drawRect(self._selectionRect) - - - def _itemMoved(self, oldX, oldY, newX, newY): - doodad = self._doodadRef() - w,h = doodad.size - doodad.position = [newX-floor(w/2.0), newY-floor(h/2.0)] - - - 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 (event.modifiers() == Qt.ShiftModifier): - - if bit: - self.setCursor(Qt.OpenHandCursor) - else: - self.setCursor(Qt.ArrowCursor) - - else: - - 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() - - if (event.modifiers() & Qt.ShiftModifier): - self.rotating = self.mapToScene(pos), self._doodadRef().angle - self.setCursor(Qt.ClosedHandCursor) - return - - else: - x, xSide, y, ySide = False, None, False, None - - if bit == 1 or bit == 7 or bit == 3: # left - x, xSide = True, 1 - - elif bit == 2 or bit == 4 or bit == 8: # right - x, xSide = True, 0 - - if bit == 1 or bit == 2 or bit == 5: # top - y, ySide = True, 1 - - elif bit == 3 or bit == 4 or bit == 6: # bottom - y, ySide = True, 0 - - self._updateSize() - self.resizing = (x, xSide, y, ySide) - return - - - KPEditorItem.mousePressEvent(self, event) - - - def _tryAndResize(self, obj, axisIndex, mousePosition, stationarySide): - - newSize = abs(mousePosition) * 2 - - if newSize < 10: - return False - - obj.size[axisIndex] = newSize - - return True - - - def _tryAndRotate(self, obj, mouseX, mouseY, originalPos, oldAngle, modifiers): - center = self.mapToScene(self.boundingRect().center()) - - objX = center.x() - objY = center.y() - - - origX = originalPos.x() - origY = originalPos.y() - - dy = origY - objY - dx = origX - objX - rads = math.atan2(dy, dx) - - origAngle = math.degrees(rads) - - dy = mouseY - objY - dx = mouseX - objX - rads = math.atan2(dy, dx) - - angle = math.degrees(rads) - - - # Move this to ItemChange() or something at some point. - finalAngle = angle - origAngle + oldAngle - - if (modifiers & Qt.ControlModifier): - finalAngle = int(finalAngle / 45.0) * 45.0 - - return True, finalAngle - - - def mouseMoveEvent(self, event): - if self.resizing: - obj = self._doodadRef() - - hasChanged = False - resizeX, xSide, resizeY, ySide = self.resizing - - if resizeX: - hasChanged |= self._tryAndResize(obj, 0, event.pos().x(), xSide) - if resizeY: - hasChanged |= self._tryAndResize(obj, 1, event.pos().y(), ySide) - - if hasChanged: - # Doodads aren't supposed to snap, they're all free flowing like the wind. - self._updateSize() - - - elif self.rotating: - obj = self._doodadRef() - scenePos = event.scenePos() - self.setTransformOriginPoint(self.boundingRect().center()) - - hasChanged = False - - hasChanged, angle = self._tryAndRotate(obj, scenePos.x(), scenePos.y(), self.rotating[0], self.rotating[1], event.modifiers()) - - if hasChanged: - obj.angle = angle - self._updateTransform() - - - else: - KPEditorItem.mouseMoveEvent(self, event) - - - def mouseReleaseEvent(self, event): - if self.resizing and event.button() == Qt.LeftButton: - self.resizing = None - # self._doodadRef().position = [self.x(), self.y()] - - elif self.rotating and event.button() == Qt.LeftButton: - self.rotating = None - - else: - KPEditorItem.mouseReleaseEvent(self, event) - - - def remove(self, withItem=False): - doodad = self._doodadRef() - layer = self._layerRef() - - layer.objects.remove(doodad) - - if withItem: - self.scene().removeItem(self) - - -class KPEditorNode(KPEditorItem): - SNAP_TO = (12,12) - - - class toggleButton(QtGui.QPushButton): - - stateToggled = QtCore.pyqtSignal(int) - - - def __init__(self): - QtGui.QPushButton.__init__(self) - - self.setIconSize(QtCore.QSize(24, 24)) - self.setFixedSize(24, 24) - - self.iconList = [QtGui.QIcon("Resources/Through.png"), - QtGui.QIcon("Resources/Level.png"), - QtGui.QIcon("Resources/Stop.png")] - - self.state = 0 - - self.setPalette(QtGui.QPalette(QtGui.QColor(0,0,0,0))) - - self.released.connect(self.toggle) - - - def toggle(self): - - self.state += 1 - if self.state == 3: - self.state = 0 - - self.stateToggled.emit(self.state) - - - def paintEvent(self, event): - - painter = QtGui.QPainter(self) - - painter.setBackgroundMode(Qt.TransparentMode) - painter.setBrush(QtGui.QColor(0,0,0,0)) - painter.setPen(QtGui.QColor(0,0,0,0)) - - if self.isDown(): - self.iconList[self.state].paint(painter, self.contentsRect(), Qt.AlignCenter, QtGui.QIcon.Disabled) - else: - self.iconList[self.state].paint(painter, self.contentsRect()) - painter.end() - - - class hiddenProxy(QtGui.QGraphicsProxyWidget): - def __init__(self, button): - QtGui.QGraphicsProxyWidget.__init__(self) - - self.setWidget(button) - self.setZValue(self.zValue()+1000) - self.hide() - - - class levelSlotSpinner(QtGui.QSpinBox): - - def __init__(self): - QtGui.QSpinBox.__init__(self) - - self.setRange(1, 99) - - self.setPalette(QtGui.QPalette(QtGui.QColor(0,0,0,0))) - - - - def __init__(self, node): - KPEditorItem.__init__(self) - - node.qtItem = self - self._nodeRef = weakref.ref(node) - - self.setZValue(101) - - self._boundingRect = QtCore.QRectF(-24, -24, 48, 48) - self._tinyRect = QtCore.QRectF(-12, -12, 24, 24) - - - if not hasattr(KPEditorNode, 'SELECTION_PEN'): - KPEditorNode.SELECTION_PEN = QtGui.QPen(Qt.blue, 1, Qt.DotLine) - - self.button = self.toggleButton() - self.buttonProxy = self.hiddenProxy(self.button) - self.button.stateToggled.connect(self.stateChange) - - - self.world = self.levelSlotSpinner() - self.worldProxy = self.hiddenProxy(self.world) - self.world.valueChanged.connect(self.worldChange) - - self.stage = self.levelSlotSpinner() - self.stageProxy = self.hiddenProxy(self.stage) - self.stage.valueChanged.connect(self.stageChange) - - - self._updatePosition() - - - - @QtCore.pyqtSlot(int) - def stateChange(self, state): - - node = self._nodeRef() - - if state == 1: - node.level = [1, 1] - self.world.setValue(node.level[0]) - self.stage.setValue(node.level[1]) - - elif state == 2: - node.isStop = True - node.level = [0,0] - - else: - node.isStop = False - node.level = [0,0] - - self.update() - - - @QtCore.pyqtSlot(int) - def worldChange(self, world): - - node = self._nodeRef() - node.level[0] = world - - - @QtCore.pyqtSlot(int) - def stageChange(self, stage): - - node = self._nodeRef() - node.level[1] = stage - - - - def _updatePosition(self): - node = self._nodeRef() - x, y = node.position - self.setPos(x+12, y+12) - self.buttonProxy.setPos(self.x()+12, self.y()-24) - self.worldProxy.setPos(self.x()-42, self.y()+24) - self.stageProxy.setPos(self.x()+6, self.y()+24) - - - def _itemMoved(self, oldX, oldY, newX, newY): - node = self._nodeRef() - node.position = (newX-12, newY-12) - - self.buttonProxy.setPos(newX+12, newY-24) - self.worldProxy.setPos(newX-42, newY+24) - self.stageProxy.setPos(newX+6, newY+24) - - for exit in node.exits: - exit.qtItem.updatePosition() - - - def paint(self, painter, option, widget): - - node = self._nodeRef() - - selectionRect = None - - if node.level != [0,0]: - painter.setBrush(QtGui.QColor(0, 0, 0, 0)) - painter.setPen(QtGui.QColor(0, 0, 0, 0)) - painter.drawPixmap(self._boundingRect.topLeft(), QtGui.QPixmap("Resources/BlackLevel.png")) - selectionRect = self._boundingRect.adjusted(-1,-1,1,1) - - elif node.isStop: - brush = QtGui.QBrush(QtGui.QColor(255, 220, 220)) - painter.setPen(QtGui.QColor(255, 255, 255)) - painter.setBrush(brush) - painter.drawEllipse(self._tinyRect) - selectionRect = self._tinyRect.adjusted(-1,-1,1,1) - - else: - brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) - painter.setPen(QtGui.QColor(255, 255, 255)) - painter.setBrush(brush) - painter.drawEllipse(self._tinyRect) - selectionRect = self._tinyRect.adjusted(-1,-1,1,1) - - - if self.isSelected(): - painter.setPen(self.SELECTION_PEN) - painter.setBrush(QtGui.QColor(0,0,0,0)) - painter.drawEllipse(selectionRect) - - self.buttonProxy.show() - - if node.level != [0,0]: - self.worldProxy.show() - self.stageProxy.show() - - else: - self.worldProxy.hide() - self.stageProxy.hide() - - else: - self.buttonProxy.hide() - self.worldProxy.hide() - self.stageProxy.hide() - - - - - def remove(self, withItem=False): - node = self._nodeRef() - layer = KP.map.pathLayer - - layer.nodes.remove(node) - - self.scene().removeItem(self.buttonProxy) - self.scene().removeItem(self.stageProxy) - self.scene().removeItem(self.worldProxy) - - if len(node.exits) == 2: - # let's try to join the two! - pathOne, pathTwo = node.exits - - start1, end1 = pathOne._startNodeRef(), pathOne._endNodeRef() - start2, end2 = pathTwo._startNodeRef(), pathTwo._endNodeRef() - - if start1 == node: - start = end1 - else: - start = start1 - - if start2 == node: - end = end2 - else: - end = start2 - - # make sure no path already exists between these nodes - nope = False - - for pathToCheck in start.exits: - if pathToCheck._startNodeRef() == end: - nope = True - elif pathToCheck._endNodeRef() == end: - nope = True - - if not nope: - joinedPath = KPPath(start, end, pathOne) - layer.paths.append(joinedPath) - item = KPEditorPath(joinedPath) - self.scene().addItem(item) - - for path in (pathOne, pathTwo): - path.qtItem.remove(True) - else: - # we can't join them so just nuke them - for exit in node.exits[:]: - exit.qtItem.remove(True) - - if withItem: - self.scene().removeItem(self) - - - -class KPEditorPath(QtGui.QGraphicsLineItem): - def __init__(self, path): - QtGui.QGraphicsLineItem.__init__(self) - - self.setFlag(self.ItemIsSelectable, True) - - self.setZValue(100) - - startNode = path._startNodeRef().qtItem - endNode = path._endNodeRef().qtItem - - self._startNodeRef = weakref.ref(startNode) - self._endNodeRef = weakref.ref(endNode) - self._pathRef = weakref.ref(path) - - path.qtItem = self - - self.updatePosition() - - if not hasattr(KPEditorPath, 'PEN'): - KPEditorPath.BRUSH = QtGui.QBrush(QtGui.QColor(255, 255, 255, 140)) - KPEditorPath.PEN = QtGui.QPen(KPEditorPath.BRUSH, 8, Qt.SolidLine, Qt.RoundCap) - self.setPen(KPEditorPath.PEN) - - - def updatePosition(self): - path = self._pathRef() - - x1, y1 = path._startNodeRef().position - x2, y2 = path._endNodeRef().position - - self.setLine(QtCore.QLineF(x1+12, y1+12, x2+12, y2+12)) - - - def remove(self, withItem=False): - path = self._pathRef() - layer = KP.map.pathLayer - - layer.paths.remove(path) - - for ref in (self._startNodeRef, self._endNodeRef): - node = ref()._nodeRef() - try: - node.exits.remove(path) - except ValueError: - pass - - if withItem: - self.scene().removeItem(self) - - - -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, QtGui.QColor(209, 218, 236)) - - 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) - - # compile a list of doodads - visibleDoodadsByLayer = {} - - for obj in self.items(rect): - if not isinstance(obj, KPEditorDoodad): continue - - layer = obj._layerRef() - - try: - doodadList = visibleDoodadsByLayer[layer] - except KeyError: - doodadList = [] - visibleDoodadsByLayer[layer] = doodadList - - doodadList.append(obj) - - # now draw everything! - for layer in reversed(KP.map.layers): - if not layer.visible: continue - - if isinstance(layer, KPDoodadLayer): - try: - toDraw = visibleDoodadsByLayer[layer] - except KeyError: - continue - - for item in reversed(toDraw): - painter.save() - painter.setWorldTransform(item.sceneTransform(), True) - w, h = item._doodadRef().size - p = item._boundingRect - painter.drawPixmap(p.x(), p.y(), p.width(), p.height(), item.pixmap) - painter.restore() - - elif isinstance(layer, KPTileLayer): - 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.currentLayer.setActivated(False) - - self.currentLayer = layer - self.currentLayer.setActivated(True) - - -class KPEditorWidget(QtGui.QGraphicsView): - def __init__(self, scene, parent=None): - QtGui.QGraphicsView.__init__(self, scene, parent) - - self.setRenderHints(QtGui.QPainter.Antialiasing) - - 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''' - - paint = self.paintNext - layer = self.scene().currentLayer - if not layer.visible: return - - if isinstance(layer, KPTileLayer): - if paint is None: return - - 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) - - 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) - - elif isinstance(layer, KPDoodadLayer): - if paint is None: return - - clicked = self.mapToScene(event.x(), event.y()) - x, y = clicked.x(), clicked.y() - if x < 0: x = 0 - if y < 0: y = 0 - - obj = KPDoodad() - obj.position = [x,y] - obj.index = self.paintNextID - obj.setDefaultSize() - layer.objects.append(obj) - - item = KPEditorDoodad(obj, layer) - self.scene().addItem(item) - - self.painting = obj - self.paintingItem = item - self.paintBeginPosition = (x, y) - - elif isinstance(layer, KPPathLayer): - # decide what's under the mouse - clicked = self.mapToScene(event.x(), event.y()) - x, y = clicked.x(), clicked.y() - itemsUnder = self.scene().items(clicked) - - for item in itemsUnder: - if isinstance(item, KPEditorNode): - # Paint a path to this node (if one is selected) - sourceItem, sourceNode = None, None - selected = self.scene().selectedItems() - - for selItem in selected: - if isinstance(item, KPEditorNode) and selItem != item: - sourceItem = selItem - sourceNode = selItem._nodeRef() - break - - if sourceItem is None: return - - # Make sure that no path already exists between these nodes - destNode = item._nodeRef() - - for pathToCheck in sourceNode.exits: - if pathToCheck._startNodeRef() == destNode: - return - if pathToCheck._endNodeRef() == destNode: - return - - # No node can have more than four paths, because there are only - # four directions registered by a Wiimote DPad. - - if len(sourceNode.exits) > 3: - return - - if len(destNode.exits) > 3: - return - - path = KPPath(sourceNode, destNode) - - KP.map.pathLayer.paths.append(path) - - item = KPEditorPath(path) - self.scene().addItem(item) - - return - - elif isinstance(item, KPEditorPath): - # Split this path into two... at this point - - origPath = item._pathRef() - - node = KPNode() - node.position = (x - 12, y - 12) - KP.map.pathLayer.nodes.append(node) - - # Start node => Original path => New node => New path => End node - - endNode = origPath._endNodeRef() - - origPath.setEnd(node) - origPath.qtItem.updatePosition() - - nodeItem = KPEditorNode(node) - self.scene().addItem(nodeItem) - self.scene().addItem(nodeItem.buttonProxy) - self.scene().addItem(nodeItem.stageProxy) - self.scene().addItem(nodeItem.worldProxy) - - self.painting = node - self.paintingItem = item - self.paintBeginPosition = (x - 12, y - 12) - - newPath = KPPath(node, endNode, origPath) - KP.map.pathLayer.paths.append(newPath) - - pathItem = KPEditorPath(newPath) - self.scene().addItem(pathItem) - - return - - # Paint a new node - node = KPNode() - node.position = (x - 12, y - 12) - KP.map.pathLayer.nodes.append(node) - - item = KPEditorNode(node) - self.scene().addItem(item) - self.scene().addItem(item.buttonProxy) - self.scene().addItem(item.stageProxy) - self.scene().addItem(item.worldProxy) - - # Paint a path to this node (if one is selected) - sourceItem, sourceNode = None, None - selected = self.scene().selectedItems() - - for selItem in selected: - if isinstance(item, KPEditorNode) and selItem != item: - sourceItem = selItem - sourceNode = selItem._nodeRef() - break - - # No node can have more than four paths, because there are only - # four directions registered by a Wiimote DPad. - - if not sourceItem is None: - if len(sourceNode.exits) > 3: - return - - # There, now you can draw paths easily in a row. - path = KPPath(sourceNode, node) - - KP.map.pathLayer.paths.append(path) - - pathitem = KPEditorPath(path) - self.scene().addItem(pathitem) - - - # Switch the selection to the recently drawn node, so you can keep on rolling. - self.scene().clearSelection() - item.setSelected(True) - - self.painting = node - self.paintingItem = item - self.paintBeginPosition = (x - 12, y - 12) - - - - 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) - - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace: - scene = self.scene() - - selection = scene.selectedItems() - if len(selection) > 0: - for obj in selection: - obj.setSelected(False) - obj.remove(True) - scene.update() - self.update() - return - - else: - QtGui.QGraphicsView.keyPressEvent(self, event) - - - - diff --git a/src/editorui/__init__.py b/src/editorui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/editorui/doodads.py b/src/editorui/doodads.py new file mode 100644 index 0000000..5a02248 --- /dev/null +++ b/src/editorui/doodads.py @@ -0,0 +1,248 @@ +from common import * +from editorcommon import * +import weakref +from math import floor, ceil +import math + + +class KPEditorDoodad(KPEditorItem): + SNAP_TO = (12,12) + + def __init__(self, doodad, layer): + KPEditorItem.__init__(self) + + doodad.qtItem = self + self._doodadRef = weakref.ref(doodad) + self._layerRef = weakref.ref(layer) + + # TODO: refactor this to store source doodad data under KP.map + sourceItem = KP.mainWindow.doodadSelector.getDoodad(doodad.index) + self._sourceRef = weakref.ref(sourceItem) + + self.resizing = None + self.rotating = None + + self._updatePixmap() + self._updatePosition() + self._updateSize() + + self.setAcceptHoverEvents(True) + + if not hasattr(KPEditorDoodad, 'SELECTION_PEN'): + KPEditorDoodad.SELECTION_PEN = QtGui.QPen(Qt.red, 1, Qt.DotLine) + + + + def _updatePixmap(self): + source = self._sourceRef() + pixmap = source.icon().pixmap(source.icon().availableSizes()[0]) + + self.prepareGeometryChange() + w, h = pixmap.width(), pixmap.height() + + self.pixmap = pixmap + + + def _updatePosition(self): + # NOTE: EditorDoodads originate at the centre, not the top left like the others + doodad = self._doodadRef() + x,y = doodad.position + w,h = doodad.size + self.setPos(x+floor(w/2.0), y+floor(h/2.0)) + + + def _updateSize(self): + self.prepareGeometryChange() + + w,h = self._doodadRef().size + + self._boundingRect = QtCore.QRectF(-w/2, -h/2, w, h) + self._selectionRect = self._boundingRect.adjusted(0, 0, -1, -1) + + + def _updateTransform(self): + doodad = self._doodadRef() + + self.setRotation(doodad.angle) + + + def paint(self, painter, option, widget): + if self.isSelected(): + painter.setPen(self.SELECTION_PEN) + painter.drawRect(self._selectionRect) + + + def _itemMoved(self, oldX, oldY, newX, newY): + doodad = self._doodadRef() + w,h = doodad.size + doodad.position = [newX-floor(w/2.0), newY-floor(h/2.0)] + + + 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 (event.modifiers() == Qt.ShiftModifier): + + if bit: + self.setCursor(Qt.OpenHandCursor) + else: + self.setCursor(Qt.ArrowCursor) + + else: + + 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() + + if (event.modifiers() & Qt.ShiftModifier): + self.rotating = self.mapToScene(pos), self._doodadRef().angle + self.setCursor(Qt.ClosedHandCursor) + return + + else: + x, xSide, y, ySide = False, None, False, None + + if bit == 1 or bit == 7 or bit == 3: # left + x, xSide = True, 1 + + elif bit == 2 or bit == 4 or bit == 8: # right + x, xSide = True, 0 + + if bit == 1 or bit == 2 or bit == 5: # top + y, ySide = True, 1 + + elif bit == 3 or bit == 4 or bit == 6: # bottom + y, ySide = True, 0 + + self._updateSize() + self.resizing = (x, xSide, y, ySide) + return + + + KPEditorItem.mousePressEvent(self, event) + + + def _tryAndResize(self, obj, axisIndex, mousePosition, stationarySide): + + newSize = abs(mousePosition) * 2 + + if newSize < 10: + return False + + obj.size[axisIndex] = newSize + + return True + + + def _tryAndRotate(self, obj, mouseX, mouseY, originalPos, oldAngle, modifiers): + center = self.mapToScene(self.boundingRect().center()) + + objX = center.x() + objY = center.y() + + + origX = originalPos.x() + origY = originalPos.y() + + dy = origY - objY + dx = origX - objX + rads = math.atan2(dy, dx) + + origAngle = math.degrees(rads) + + dy = mouseY - objY + dx = mouseX - objX + rads = math.atan2(dy, dx) + + angle = math.degrees(rads) + + + # Move this to ItemChange() or something at some point. + finalAngle = angle - origAngle + oldAngle + + if (modifiers & Qt.ControlModifier): + finalAngle = int(finalAngle / 45.0) * 45.0 + + return True, finalAngle + + + def mouseMoveEvent(self, event): + if self.resizing: + obj = self._doodadRef() + + hasChanged = False + resizeX, xSide, resizeY, ySide = self.resizing + + if resizeX: + hasChanged |= self._tryAndResize(obj, 0, event.pos().x(), xSide) + if resizeY: + hasChanged |= self._tryAndResize(obj, 1, event.pos().y(), ySide) + + if hasChanged: + # Doodads aren't supposed to snap, they're all free flowing like the wind. + self._updateSize() + + + elif self.rotating: + obj = self._doodadRef() + scenePos = event.scenePos() + self.setTransformOriginPoint(self.boundingRect().center()) + + hasChanged = False + + hasChanged, angle = self._tryAndRotate(obj, scenePos.x(), scenePos.y(), self.rotating[0], self.rotating[1], event.modifiers()) + + if hasChanged: + obj.angle = angle + self._updateTransform() + + + else: + KPEditorItem.mouseMoveEvent(self, event) + + + def mouseReleaseEvent(self, event): + if self.resizing and event.button() == Qt.LeftButton: + self.resizing = None + # self._doodadRef().position = [self.x(), self.y()] + + elif self.rotating and event.button() == Qt.LeftButton: + self.rotating = None + + else: + KPEditorItem.mouseReleaseEvent(self, event) + + + def remove(self, withItem=False): + doodad = self._doodadRef() + layer = self._layerRef() + + layer.objects.remove(doodad) + + if withItem: + self.scene().removeItem(self) + + + diff --git a/src/editorui/editorcommon.py b/src/editorui/editorcommon.py new file mode 100644 index 0000000..945375e --- /dev/null +++ b/src/editorui/editorcommon.py @@ -0,0 +1,77 @@ +from common import * + +class KPEditorItem(QtGui.QGraphicsItem): + def __init__(self): + QtGui.QGraphicsItem.__init__(self) + self.setFlags( + self.ItemSendsGeometryChanges | + self.ItemIsSelectable | + self.ItemIsMovable + ) + + self.ignoreMovement = False + self.overrideSnap = False + + def itemChange(self, change, value): + if change == self.ItemPositionChange and not self.ignoreMovement: + currentX, currentY = self.x(), self.y() + + newpos = value.toPyObject() + + x, y = newpos.x(), newpos.y() + + if self.overrideSnap: + snapX, snapY = 1, 1 + else: + # snap the item + snapX, snapY = self.SNAP_TO + x = int((x + (snapX/2)) / snapX) * snapX + y = int((y + (snapY/2)) / snapY) * snapY + + if x < 0: x = 0 + if x >= (12288+snapX): x = (12288+snapX-1) + if y < 0: y = 0 + if y >= (12288+snapY): y = (12288+snapY-1) + + 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 resizerPortionAt(self, x, y, originX=0, originY=0): + try: + leftBound, topBound = originX+5, originY+5 + rightBound, bottomBound = self._resizerEndXY + except AttributeError: + rect = self._boundingRect + leftBound, topBound = rect.x() + 5, rect.y() + 5 + rightBound, bottomBound = rect.right() - 5, rect.bottom() - 5 + + if y < topBound: + if x < leftBound: return 1 # TOP_LEFT + elif x >= rightBound: return 2 # TOP_RIGHT + else: return 5 # TOP + + elif y >= bottomBound: + if x < leftBound: return 3 # BOTTOM_LEFT + elif x >= rightBound: return 4 # BOTTOM_RIGHT + else: return 6 # BOTTOM + + else: + if x < leftBound: return 7 # LEFT + elif x >= rightBound: return 8 # RIGHT + else: return None + + + def _itemMoved(self, oldX, oldY, newX, newY): + pass + + diff --git a/src/editorui/editormain.py b/src/editorui/editormain.py new file mode 100644 index 0000000..d11642a --- /dev/null +++ b/src/editorui/editormain.py @@ -0,0 +1,419 @@ +from common import * +from math import floor, ceil + +from objects import * +from doodads import * +from paths import * + +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, QtGui.QColor(209, 218, 236)) + + 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) + + # compile a list of doodads + visibleDoodadsByLayer = {} + + for obj in self.items(rect): + if not isinstance(obj, KPEditorDoodad): continue + + layer = obj._layerRef() + + try: + doodadList = visibleDoodadsByLayer[layer] + except KeyError: + doodadList = [] + visibleDoodadsByLayer[layer] = doodadList + + doodadList.append(obj) + + # now draw everything! + for layer in reversed(KP.map.layers): + if not layer.visible: continue + + if isinstance(layer, KPDoodadLayer): + try: + toDraw = visibleDoodadsByLayer[layer] + except KeyError: + continue + + for item in reversed(toDraw): + painter.save() + painter.setWorldTransform(item.sceneTransform(), True) + w, h = item._doodadRef().size + p = item._boundingRect + painter.drawPixmap(p.x(), p.y(), p.width(), p.height(), item.pixmap) + painter.restore() + + elif isinstance(layer, KPTileLayer): + 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.currentLayer.setActivated(False) + + self.currentLayer = layer + self.currentLayer.setActivated(True) + + +class KPEditorWidget(QtGui.QGraphicsView): + def __init__(self, scene, parent=None): + QtGui.QGraphicsView.__init__(self, scene, parent) + + self.setRenderHints(QtGui.QPainter.Antialiasing) + + 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''' + + paint = self.paintNext + layer = self.scene().currentLayer + if not layer.visible: return + + if isinstance(layer, KPTileLayer): + if paint is None: return + + 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) + + 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) + + elif isinstance(layer, KPDoodadLayer): + if paint is None: return + + clicked = self.mapToScene(event.x(), event.y()) + x, y = clicked.x(), clicked.y() + if x < 0: x = 0 + if y < 0: y = 0 + + obj = KPDoodad() + obj.position = [x,y] + obj.index = self.paintNextID + obj.setDefaultSize() + layer.objects.append(obj) + + item = KPEditorDoodad(obj, layer) + self.scene().addItem(item) + + self.painting = obj + self.paintingItem = item + self.paintBeginPosition = (x, y) + + elif isinstance(layer, KPPathLayer): + # decide what's under the mouse + clicked = self.mapToScene(event.x(), event.y()) + x, y = clicked.x(), clicked.y() + itemsUnder = self.scene().items(clicked) + + for item in itemsUnder: + if isinstance(item, KPEditorNode): + # Paint a path to this node (if one is selected) + sourceItem, sourceNode = None, None + selected = self.scene().selectedItems() + + for selItem in selected: + if isinstance(item, KPEditorNode) and selItem != item: + sourceItem = selItem + sourceNode = selItem._nodeRef() + break + + if sourceItem is None: return + + # Make sure that no path already exists between these nodes + destNode = item._nodeRef() + + for pathToCheck in sourceNode.exits: + if pathToCheck._startNodeRef() == destNode: + return + if pathToCheck._endNodeRef() == destNode: + return + + # No node can have more than four paths, because there are only + # four directions registered by a Wiimote DPad. + + if len(sourceNode.exits) > 3: + return + + if len(destNode.exits) > 3: + return + + path = KPPath(sourceNode, destNode) + + KP.map.pathLayer.paths.append(path) + + item = KPEditorPath(path) + self.scene().addItem(item) + + return + + elif isinstance(item, KPEditorPath): + # Split this path into two... at this point + + origPath = item._pathRef() + + node = KPNode() + node.position = (x - 12, y - 12) + KP.map.pathLayer.nodes.append(node) + + # Start node => Original path => New node => New path => End node + + endNode = origPath._endNodeRef() + + origPath.setEnd(node) + origPath.qtItem.updatePosition() + + nodeItem = KPEditorNode(node) + self.scene().addItem(nodeItem) + self.scene().addItem(nodeItem.buttonProxy) + self.scene().addItem(nodeItem.stageProxy) + self.scene().addItem(nodeItem.worldProxy) + + self.painting = node + self.paintingItem = item + self.paintBeginPosition = (x - 12, y - 12) + + newPath = KPPath(node, endNode, origPath) + KP.map.pathLayer.paths.append(newPath) + + pathItem = KPEditorPath(newPath) + self.scene().addItem(pathItem) + + return + + # Paint a new node + node = KPNode() + node.position = (x - 12, y - 12) + KP.map.pathLayer.nodes.append(node) + + item = KPEditorNode(node) + self.scene().addItem(item) + self.scene().addItem(item.buttonProxy) + self.scene().addItem(item.stageProxy) + self.scene().addItem(item.worldProxy) + + # Paint a path to this node (if one is selected) + sourceItem, sourceNode = None, None + selected = self.scene().selectedItems() + + for selItem in selected: + if isinstance(item, KPEditorNode) and selItem != item: + sourceItem = selItem + sourceNode = selItem._nodeRef() + break + + # No node can have more than four paths, because there are only + # four directions registered by a Wiimote DPad. + + if not sourceItem is None: + if len(sourceNode.exits) > 3: + return + + # There, now you can draw paths easily in a row. + path = KPPath(sourceNode, node) + + KP.map.pathLayer.paths.append(path) + + pathitem = KPEditorPath(path) + self.scene().addItem(pathitem) + + + # Switch the selection to the recently drawn node, so you can keep on rolling. + self.scene().clearSelection() + item.setSelected(True) + + self.painting = node + self.paintingItem = item + self.paintBeginPosition = (x - 12, y - 12) + + + + 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) + + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Delete or event.key() == QtCore.Qt.Key_Backspace: + scene = self.scene() + + selection = scene.selectedItems() + if len(selection) > 0: + for obj in selection: + obj.setSelected(False) + obj.remove(True) + scene.update() + self.update() + return + + else: + QtGui.QGraphicsView.keyPressEvent(self, event) + + + + diff --git a/src/editorui/objects.py b/src/editorui/objects.py new file mode 100644 index 0000000..1ae46f7 --- /dev/null +++ b/src/editorui/objects.py @@ -0,0 +1,182 @@ +from common import * +from editorcommon import * +import weakref + +class KPEditorObject(KPEditorItem): + SNAP_TO = (24,24) + + 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 + + if not hasattr(KPEditorObject, 'SELECTION_PEN'): + KPEditorObject.SELECTION_PEN = QtGui.QPen(Qt.white, 1, Qt.DotLine) + + # 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): + self.ignoreMovement = True + x,y = self._objRef().position + self.setPos(x*24, y*24) + self.ignoreMovement = False + + 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(self.SELECTION_PEN) + painter.drawRect(self._selectionRect) + + + 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() + + + def remove(self, withItem=False): + obj = self._objRef() + layer = self._layerRef() + + layer.objects.remove(obj) + layer.updateCache() + + if withItem: + self.scene().removeItem(self) + + diff --git a/src/editorui/paths.py b/src/editorui/paths.py new file mode 100644 index 0000000..4dca31e --- /dev/null +++ b/src/editorui/paths.py @@ -0,0 +1,319 @@ +from common import * +from editorcommon import * +import weakref + +class KPEditorNode(KPEditorItem): + SNAP_TO = (12,12) + + + class toggleButton(QtGui.QPushButton): + + stateToggled = QtCore.pyqtSignal(int) + + + def __init__(self): + QtGui.QPushButton.__init__(self) + + self.setIconSize(QtCore.QSize(24, 24)) + self.setFixedSize(24, 24) + + self.iconList = [QtGui.QIcon("Resources/Through.png"), + QtGui.QIcon("Resources/Level.png"), + QtGui.QIcon("Resources/Stop.png")] + + self.state = 0 + + self.setPalette(QtGui.QPalette(QtGui.QColor(0,0,0,0))) + + self.released.connect(self.toggle) + + + def toggle(self): + + self.state += 1 + if self.state == 3: + self.state = 0 + + self.stateToggled.emit(self.state) + + + def paintEvent(self, event): + + painter = QtGui.QPainter(self) + + painter.setBackgroundMode(Qt.TransparentMode) + painter.setBrush(QtGui.QColor(0,0,0,0)) + painter.setPen(QtGui.QColor(0,0,0,0)) + + if self.isDown(): + self.iconList[self.state].paint(painter, self.contentsRect(), Qt.AlignCenter, QtGui.QIcon.Disabled) + else: + self.iconList[self.state].paint(painter, self.contentsRect()) + painter.end() + + + class hiddenProxy(QtGui.QGraphicsProxyWidget): + def __init__(self, button): + QtGui.QGraphicsProxyWidget.__init__(self) + + self.setWidget(button) + self.setZValue(self.zValue()+1000) + self.hide() + + + class levelSlotSpinner(QtGui.QSpinBox): + + def __init__(self): + QtGui.QSpinBox.__init__(self) + + self.setRange(1, 99) + + self.setPalette(QtGui.QPalette(QtGui.QColor(0,0,0,0))) + + + + def __init__(self, node): + KPEditorItem.__init__(self) + + node.qtItem = self + self._nodeRef = weakref.ref(node) + + self.setZValue(101) + + self._boundingRect = QtCore.QRectF(-24, -24, 48, 48) + self._tinyRect = QtCore.QRectF(-12, -12, 24, 24) + + + if not hasattr(KPEditorNode, 'SELECTION_PEN'): + KPEditorNode.SELECTION_PEN = QtGui.QPen(Qt.blue, 1, Qt.DotLine) + + self.button = self.toggleButton() + self.buttonProxy = self.hiddenProxy(self.button) + self.button.stateToggled.connect(self.stateChange) + + + self.world = self.levelSlotSpinner() + self.worldProxy = self.hiddenProxy(self.world) + self.world.valueChanged.connect(self.worldChange) + + self.stage = self.levelSlotSpinner() + self.stageProxy = self.hiddenProxy(self.stage) + self.stage.valueChanged.connect(self.stageChange) + + + self._updatePosition() + + + + @QtCore.pyqtSlot(int) + def stateChange(self, state): + + node = self._nodeRef() + + if state == 1: + node.level = [1, 1] + self.world.setValue(node.level[0]) + self.stage.setValue(node.level[1]) + + elif state == 2: + node.isStop = True + node.level = [0,0] + + else: + node.isStop = False + node.level = [0,0] + + self.update() + + + @QtCore.pyqtSlot(int) + def worldChange(self, world): + + node = self._nodeRef() + node.level[0] = world + + + @QtCore.pyqtSlot(int) + def stageChange(self, stage): + + node = self._nodeRef() + node.level[1] = stage + + + + def _updatePosition(self): + node = self._nodeRef() + x, y = node.position + self.setPos(x+12, y+12) + self.buttonProxy.setPos(self.x()+12, self.y()-24) + self.worldProxy.setPos(self.x()-42, self.y()+24) + self.stageProxy.setPos(self.x()+6, self.y()+24) + + + def _itemMoved(self, oldX, oldY, newX, newY): + node = self._nodeRef() + node.position = (newX-12, newY-12) + + self.buttonProxy.setPos(newX+12, newY-24) + self.worldProxy.setPos(newX-42, newY+24) + self.stageProxy.setPos(newX+6, newY+24) + + for exit in node.exits: + exit.qtItem.updatePosition() + + + def paint(self, painter, option, widget): + + node = self._nodeRef() + + selectionRect = None + + if node.level != [0,0]: + painter.setBrush(QtGui.QColor(0, 0, 0, 0)) + painter.setPen(QtGui.QColor(0, 0, 0, 0)) + painter.drawPixmap(self._boundingRect.topLeft(), QtGui.QPixmap("Resources/BlackLevel.png")) + selectionRect = self._boundingRect.adjusted(-1,-1,1,1) + + elif node.isStop: + brush = QtGui.QBrush(QtGui.QColor(255, 220, 220)) + painter.setPen(QtGui.QColor(255, 255, 255)) + painter.setBrush(brush) + painter.drawEllipse(self._tinyRect) + selectionRect = self._tinyRect.adjusted(-1,-1,1,1) + + else: + brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) + painter.setPen(QtGui.QColor(255, 255, 255)) + painter.setBrush(brush) + painter.drawEllipse(self._tinyRect) + selectionRect = self._tinyRect.adjusted(-1,-1,1,1) + + + if self.isSelected(): + painter.setPen(self.SELECTION_PEN) + painter.setBrush(QtGui.QColor(0,0,0,0)) + painter.drawEllipse(selectionRect) + + self.buttonProxy.show() + + if node.level != [0,0]: + self.worldProxy.show() + self.stageProxy.show() + + else: + self.worldProxy.hide() + self.stageProxy.hide() + + else: + self.buttonProxy.hide() + self.worldProxy.hide() + self.stageProxy.hide() + + + + + def remove(self, withItem=False): + node = self._nodeRef() + layer = KP.map.pathLayer + + layer.nodes.remove(node) + + self.scene().removeItem(self.buttonProxy) + self.scene().removeItem(self.stageProxy) + self.scene().removeItem(self.worldProxy) + + if len(node.exits) == 2: + # let's try to join the two! + pathOne, pathTwo = node.exits + + start1, end1 = pathOne._startNodeRef(), pathOne._endNodeRef() + start2, end2 = pathTwo._startNodeRef(), pathTwo._endNodeRef() + + if start1 == node: + start = end1 + else: + start = start1 + + if start2 == node: + end = end2 + else: + end = start2 + + # make sure no path already exists between these nodes + nope = False + + for pathToCheck in start.exits: + if pathToCheck._startNodeRef() == end: + nope = True + elif pathToCheck._endNodeRef() == end: + nope = True + + if not nope: + joinedPath = KPPath(start, end, pathOne) + layer.paths.append(joinedPath) + item = KPEditorPath(joinedPath) + self.scene().addItem(item) + + for path in (pathOne, pathTwo): + path.qtItem.remove(True) + else: + # we can't join them so just nuke them + for exit in node.exits[:]: + exit.qtItem.remove(True) + + if withItem: + self.scene().removeItem(self) + + + +class KPEditorPath(QtGui.QGraphicsLineItem): + def __init__(self, path): + QtGui.QGraphicsLineItem.__init__(self) + + self.setFlag(self.ItemIsSelectable, True) + + self.setZValue(100) + + startNode = path._startNodeRef().qtItem + endNode = path._endNodeRef().qtItem + + self._startNodeRef = weakref.ref(startNode) + self._endNodeRef = weakref.ref(endNode) + self._pathRef = weakref.ref(path) + + path.qtItem = self + + self.updatePosition() + + if not hasattr(KPEditorPath, 'PEN'): + KPEditorPath.BRUSH = QtGui.QBrush(QtGui.QColor(255, 255, 255, 140)) + KPEditorPath.PEN = QtGui.QPen(KPEditorPath.BRUSH, 8, Qt.SolidLine, Qt.RoundCap) + self.setPen(KPEditorPath.PEN) + + + def updatePosition(self): + path = self._pathRef() + + x1, y1 = path._startNodeRef().position + x2, y2 = path._endNodeRef().position + + self.setLine(QtCore.QLineF(x1+12, y1+12, x2+12, y2+12)) + + + def remove(self, withItem=False): + path = self._pathRef() + layer = KP.map.pathLayer + + layer.paths.remove(path) + + for ref in (self._startNodeRef, self._endNodeRef): + node = ref()._nodeRef() + try: + node.exits.remove(path) + except ValueError: + pass + + if withItem: + self.scene().removeItem(self) + + diff --git a/src/ui.py b/src/ui.py index 2ba49ba..2d7c1cc 100644 --- a/src/ui.py +++ b/src/ui.py @@ -1,7 +1,8 @@ # -*- coding: UTF-8 -*- from common import * -from editorui import * +from editorui.editorcommon import * +from editorui.editormain import * import os -- cgit v1.2.3