diff options
author | Colin Noga <Tempus@Spectrum-Song.local> | 2011-11-08 03:43:59 -0600 |
---|---|---|
committer | Colin Noga <Tempus@Spectrum-Song.local> | 2011-11-08 03:43:59 -0600 |
commit | d7984abd4fd0efda7684bc5a48750483ed9261a8 (patch) | |
tree | 3d8af1ddaeb9aff3887e9ce47bde3e2d3c03a352 | |
parent | f54d177364dbfacce45ec6768462bc81e837a274 (diff) | |
download | koopatlas-d7984abd4fd0efda7684bc5a48750483ed9261a8.tar.gz koopatlas-d7984abd4fd0efda7684bc5a48750483ed9261a8.zip |
Added a bunch more stuff for the eventual Gourped Object Selector Widget. Its a complicated bugger, especially when I am in no position to test
-rw-r--r-- | src/tileset.py | 434 | ||||
-rw-r--r-- | src/ui.py | 87 |
2 files changed, 452 insertions, 69 deletions
diff --git a/src/tileset.py b/src/tileset.py index a193a7f..8d0c3d3 100644 --- a/src/tileset.py +++ b/src/tileset.py @@ -1,26 +1,59 @@ from common import * -class KPObjectDefinition(object): - def __init__(self): - self.rows = [] - + + +class KPTileObject(object): + def __init__(self, tilelist, height, width, wrapmode, icon): + + # A list of lists of tile indices + self.tiles = tilelist + + # Dimensions + self.height = height + self.width = width + + # QPixmap of Rendered 1:1 Object + self.icon = icon + + # Wrapmode + self.wrap = wrapmode + + # --- Wrapmodes --- + # 'Repeat' + # 'Stretch Center' + # 'Stretch X' + # 'Stretch Y' + # 'Repeat Bottom' + # 'Repeat Top' + # 'Repeat Left' + # 'Repeat Right' + # 'Upward slope' (LL to UR) + # 'Downward slope' (UL to LR) + + self.itemsize = QtCore.QSize(self.width * 24 + 4, self.height * 24 + 4) + + + def render(self, size): + '''Returns a tilemap of the object at a certain size as a list of lists.''' + + if self.wrap > 7: + self._renderSlope(size) + + + # size is a tuple of (width, height) + buf = [] beforeRepeat = [] - inRepeat = [] + inRepeat = self.tiles.copy() afterRepeat = [] - foundRepeat = False - for row in self.rows: - if (row[0][0] & 2) != 0: - inRepeat.append(row) - foundRepeat = True - else: - if foundRepeat: - afterRepeat.append(row) - else: - beforeRepeat.append(row) + if (self.wrap == 1) or (self.wrap == 3) or (self.wrap == 5): + beforeRepeat = inRepeat.pop(0) + + if (self.wrap == 1) or (self.wrap == 3) or (self.wrap == 4): + afterRepeat = inRepeat.pop() bC, iC, aC = len(beforeRepeat), len(inRepeat), len(afterRepeat) @@ -44,19 +77,14 @@ class KPObjectDefinition(object): buf = [-1 for i in xrange(width)] beforeRepeat = [] - inRepeat = [] + inRepeat = row afterRepeat = [] - foundRepeat = False - for tile in row: - if (tile[0] & 1) != 0: - inRepeat.append(tile[1]) - foundRepeat = True - else: - if foundRepeat: - afterRepeat.append(tile[1]) - else: - beforeRepeat.append(tile[1]) + if (self.wrap == 1) or (self.wrap == 2) or (self.wrap == 6): + beforeRepeat = inRepeat.pop(0) + + if (self.wrap == 1) or (self.wrap == 2) or (self.wrap == 7): + afterRepeat = inRepeat.pop() bC, iC, aC = len(beforeRepeat), len(inRepeat), len(afterRepeat) @@ -77,14 +105,170 @@ class KPObjectDefinition(object): return buf + def _renderSlope(self, size): + # Slopes are annoying + + buf = [] + w = xrange(size[0]) + h = xrange(size[1]) + + # Koopuzzle really only does slopes that are two blocks tall. + # Suck it, multi height slopes. + + mainblock = self.tiles[0] + subblock = self.tiles[1] + + if self.wrap == 8: # Upward (LL to UR) + + # List Comprehension to make a palette + buf = [[-1 for x in w] for x in h] + + offset = 0 + + # Paint the mainblock + for row in h: + for tile in w: + if (tile == ((size[1] - row - 1) * self.width) + offset): + buf[size[1] - row - 2][tile] = mainblock[offset] + offset += 1 + offset = 0 + + # Paint the subblock + for row in h: + for tile in w: + if (tile == ((size[1] - row) * self.width) + offset): + buf[size[1] - row - 1][tile] = mainblock[offset] + offset += 1 + offset = 0 + + + elif self.wrap == 9: # Downward (UL to LR) + + # List Comprehension to make a palette + buf = [[-1 for x in w] for x in h] + + offset = 0 + + # Paint the mainblock + for row in h: + for tile in w: + if (tile == (row * self.width) + offset): + buf[row][tile] = mainblock[offset] + offset += 1 + offset = 0 + + # Paint the subblock + for row in h: + for tile in w: + if (tile == ((row - 1) * self.width) + offset): + buf[row][tile] = mainblock[offset] + offset += 1 + offset = 0 + + + + + + +class KPGroupModel(QtCore.QAbstractListModel): + """Model containing all the grouped objects in a tileset""" + + def __init__(self, groupItem): + self.container = groupItem + + QtCore.QAbstractListModel.__init__(self) + + + def rowCount(self, parent=None): + return self.container.objectCount() + + + def data(self, index, role=QtCore.Qt.DisplayRole): + # Should return the contents of a row when asked for the index + + if not index.isValid(): return None + n = index.row() + + if n < 0: return None + if n >= self.container.objectCount(): return None + + item = self.container.getItem(n) + + if role == QtCore.Qt.DecorationRole: + if isInstance(item, KPTileObject): + return item.icon + + if role == QtCore.Qt.BackgroundRole: + return QtGui.qApp.palette().base() + + if role == QtCore.Qt.UserRole: + if isInstance(item, KPTileObject): + return item.itemsize + + if role == QtCore.Qt.DisplayRole: + if isInstance(item, KPGroupItem): + return item.name + + if role == QtCore.Qt.TextAlignmentRole: + if isInstance(item, KPGroupItem): + return item.alignment + + + return None + + + +class KPGroupItem(object): + """Hierarchal object for making a 2D hierarchy recursively using classes""" + + def __init__(self, name): + + self.objects = [] + self.groups = [] + self.startIndex = 0 + self.endIndex = 0 + + self.name = name + self.alignment = QtCore.Qt.AlignCenter + + + def objectCount(self): + ''' Retrieves the total number of items in this group and all it's children ''' + + objCount = 0 + + objCount += len(self.objects) + objCount += len(self.groups) + for group in groups: + objCount += group.objectCount() + + return objCount + + + def getItem(index): + ''' Retrieves an item of a specific index. The index is already checked for validity ''' + + if index == self.startIndex: + return self + + if (index < self.startIndex + len(self.objects)): + return self.objects[index - self.startIndex - 1] + + else: + + for group in groups: + + if (group.startIndex < index) and (index < group.endIndex): + return group.getItem(index) + class KPTileset(object): - def __init__(self, imageBuffer, objectBuffer, groupBuffer): - '''A Koopatlas Tileset class. To initialize, pass it an image buffer, an - object buffer, and a group buffer from a Koopatlas tileset file. + def __init__(self, imageBuffer, objectBuffer, objectMetadata, groupBuffer): + '''A Koopatlas Tileset class. To initialize, pass it image data, + object data, and group data as read from a Koopatlas tileset file. tiles -> has a list of all 512 tiles. objects -> has a list of objects in the tileset @@ -94,46 +278,58 @@ class KPTileset(object): Methods of Note: - overrideTile(Tile Index, QPixmap) # Takes a 24x24 QPixmap and a tile index + getTile(TileIndex) + # Returns a tile image as a QPixmap + + getObject(ObjectIndex) + # Returns a KPTileObject + getObjectIcon(ObjectIndex) + getObjectIcon(KPTileObject) + # Returns a QPixmap for the Object + getObjectIcon(ObjectIndex, (width, height)) + getObjectIcon(KPTileObject, (width, height)) + # Returns a render map for the Object at the given size + overrideTile(Tile Index, QPixmap) + # Takes a 24x24 QPixmap and a tile index ''' self.tiles = [] self.objects = [] - self.groupModel = QtGui.QTreeModel + self.groupItem = KPGroupItem("All Groups") self.processImage(imageBuffer) - self.processObjects(objectBuffer) + self.processObjects(objectBuffer, objectMetadata) self.processGroup(groupBuffer) - + self.groupModel = KPGroupModel(self.groupItem) def processImage(self, imageBuffer): '''Takes an imageBuffer from a Koopatlas Tileset and converts it into 24x24 tiles, which get put into KPTileset.tiles.''' - dest = self.RGB4A3Decode(Image) - - self.tileImage = QtGui.QPixmap.fromImage(dest) - - # Makes us some nice Tiles! - Xoffset = 4 - Yoffset = 4 - for i in range(512): - self.tiles.append(self.tileImage.copy(Xoffset,Yoffset,24,24)) - Xoffset += 32 - if Xoffset >= 1024: - Xoffset = 4 - Yoffset += 32 + dest = self.RGB4A3Decode(Image) + + self.tileImage = QtGui.QPixmap.fromImage(dest) + + # Makes us some nice Tiles! + Xoffset = 4 + Yoffset = 4 + for i in range(512): + self.tiles.append(self.tileImage.copy(Xoffset,Yoffset,24,24)) + Xoffset += 32 + if Xoffset >= 1024: + Xoffset = 4 + Yoffset += 32 - def RGB4A3Decode(tex): + def RGB4A3Decode (tex): dest = QtGui.QImage(1024,512,QtGui.QImage.Format_ARGB32) dest.fill(QtCore.Qt.transparent) @@ -168,44 +364,141 @@ class KPTileset(object): return dest - def processObjects(self, objectBuffer): + def processObjects(self, objstrings, metadata): + '''Takes the object files from a Koopatlas Tileset and converts it into + KPTileObject classes, which get put into KPTileset.objects.''' + + # Load Objects + + meta = [] + for i in xrange(len(metadata)/5): + meta.append(struct.unpack_from('>H3B', metadata, i * 5)) + + tilelist = [] + rowlist = [] + + for entry in meta: + offset = entry[0] + row = 0 + + tex = QtGui.QPixmap(entry[2] * 24, entry[1] * 24) + tex.fill(QtCore.Qt.transparent) + painter = QtGui.QPainter(tex) + + + for tilesA in xrange(entry[2]): + for tilesB in xrange(entry[1]): + untile = struct.unpack_from('>h', objstrings, offset)[0] + + if untile != -1: + painter.drawPixmap(tilesA*24, tilesB*24, self.tiles[untile]) + + rowlist.append(untile) + offset += 2 + + tilelist.append(rowlist) + rowlist = [] + + painter.end() + + + self.objects.append(KPTileObject(tilelist, entry[2], entry[1], entry[3], tex)) + + tilelist = [] def processGroup(self, groupBuffer): - grovyle = cPickle.loads(group) - makeTree(grovyle, treecko) + grovyle = cPickle.loads(group) + self.makeTree(grovyle, self.groupItem) + + def makeTree(self, grovyle, treecko): - - for razorLeaf in grovyle: + + indexNum = treecko.startIndex + + for razorLeaf in grovyle: + + if (type(razorLeaf) is str) and (razorLeaf[:6] == "Object"): + + objnum = int(razorLeaf[7:]) + obj = self.objects(objnum) - if (type(razorLeaf) is str) and (razorLeaf[:6] == "Object"): + treecko.objects.append(obj) - pix = QtGui.QPixmap(24, 24) - pix.fill(QtCore.Qt.transparent) - painter = QtGui.QPainter(pix) - painter.drawPixmap(0, 0, pix) - painter.end() + else: + + a = KPGroupItem(razorLeaf[0]) + treecko.groups.append(a) + + makeTree(razorLeaf[1], a) + + treecko.endIndex = treecko.startIndex + treecko.objCount() - a = QtGui.QTreeWidgetItem(treecko) - a.setText(0, razorLeaf) - a.setFlags(QtCore.Qt.ItemFlags(0x25)) - a.setIcon(1, QtGui.QIcon(pix)) - else: - a = QtGui.QTreeWidgetItem(treecko) - a.setText(0, razorLeaf[0]) - a.setFlags(QtCore.Qt.ItemFlags(0x2F)) - a.setChildIndicatorPolicy(QtGui.QTreeWidgetItem.ShowIndicator) - a.setExpanded(True) + def getTile(index): + '''Takes a tile index and returns a tile image as a QPixmap, or -1 if failed.''' + if index > 511: + return false + + if index < 0: + return false + + return self.tiles[index] + + + def getObject(index): + '''Takes an object index and returns a KPTileObject, or false if failed.''' + + if index < 0: + return false + + return self.tiles[index] + + + def getObjectIcon(index): + '''Takes an object index or a KPTileObject and returns a QPixmap for the + object, or false if failed.''' + + if isInstance(index, KPTileObject): + return index.icon + + if isInstance(index, int): + try: + return self.objects[index].icon + except: + return false + + return false + + + def getObjectRender(index, size): + '''Takes an object index or a KPTileObject and returns a render map for the + object, or false if failed.''' + + if isInstance(index, KPTileObject): + return index.render(size) + + if isInstance(index, int): + try: + return self.objects[index].render(size) + except: + return false + + return false + + + def getMode(): + '''Returns the Group Model''' + + return self.groupModel - makeTree(razorLeaf[1], a) def overrideTile(index, pixmap): @@ -214,6 +507,9 @@ class KPTileset(object): if index > 511: return false + if index < 0: + return false + if not isInstance(pixmap, QtGui.QPixmap): return false @@ -53,6 +53,87 @@ class KPLayerList(QtGui.QWidget): KP.map.moveLayer(index, index + 2) + + +class KPObjectSelector(QtGui.QListView): + + def __init__(self): + """Initialises the widget. Remember to call setModel() on it with a KPGroupModel + whenever the layer changes.""" + + QtGui.QListView.__init__(self) + self.setFlow(QtGui.QListView.LeftToRight) + self.setLayoutMode(QtGui.QListView.SinglePass) + self.setMovement(QtGui.QListView.Static) + self.setResizeMode(QtGui.QListView.Adjust) + self.setWrapping(True) + + # self.setItemDelegate(KPObjectSelector.ObjectItemDelegate()) + + self.clicked.connect(self.HandleObjReplace) + + + def toggleTopLevel(self, id): + """Changes the top level group in the list view.""" + + # Not quite sure how to implement this yet. Basically, the model is hierarchal, + # and it'll return items from whatever the top level KPGroupItem is. But removing + # and adding stuff isn't possible, since I need to retain my recursive object. + # + # So here's the structure. Above this QListView, there will be a QComboBox. + # + # The QComboBox will list all groups in my hierarchy. + # Selecting a group will cause the QListView to only show items for those rows, + # even though all rows are retained. + # + # It's kind of like a custom QSortFilterProxyModel. Maybe I should subclass that. + + + self.setCurrentIndex(self.model().index(sel, 0, QtCore.QModelIndex())) + + @QtCore.pyqtSlot(QtCore.QModelIndex, QtCore.QModelIndex) + def currentChanged(self, current, previous): + """Throws a signal when the selected object changed""" + self.ObjChanged.emit(current.row()) + + def HandleObjReplace(self, index): + """Throws a signal when the selected object is used as a replacement""" + if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.AltModifier: + self.ObjReplace.emit(index.row()) + + ObjChanged = QtCore.pyqtSignal(int) + ObjReplace = QtCore.pyqtSignal(int) + + + class KPGroupItemDelegate(QtGui.QAbstractItemDelegate): + """Handles tileset groups, objects and their rendering""" + + def __init__(self): + """Initialises the delegate""" + QtGui.QAbstractItemDelegate.__init__(self) + + def paint(self, painter, option, index): + """Paints an object""" + if option.state & QtGui.QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + + p = index.model().data(index, QtCore.Qt.DecorationRole) + painter.drawPixmap(option.rect.x()+2, option.rect.y()+2, p) + #painter.drawText(option.rect, str(index.row())) + + def sizeHint(self, option, index): + """Returns the size for the object""" + p = index.model().data(index, QtCore.Qt.UserRole) + return p + + + + + + + + + class KPMainWindow(QtGui.QMainWindow): def __init__(self): QtGui.QMainWindow.__init__(self) @@ -77,6 +158,12 @@ class KPMainWindow(QtGui.QMainWindow): self.layerListDock = QtGui.QDockWidget('Layers') self.layerListDock.setWidget(self.layerList) + self.objectSelector = KPObjectSelector() + # self.objectSelector.setModel(SomeTileset.getModel()) + self.objectSelectorDock = QtGui.QDockWidget('Layers') + self.objectSelectorDock.setWidget(self.layerList) + + self.addDockWidget(Qt.RightDockWidgetArea, self.layerListDock) |