diff options
| author | tassaron | 2026-01-11 14:29:58 -0500 |
|---|---|---|
| committer | tassaron | 2026-01-11 14:29:58 -0500 |
| commit | 669756b391d26661cf2e2a97a304e73343ef6655 (patch) | |
| tree | 9cf2d4858c209bdab9f44d5c7f95c2a30b37f7a6 /src/components/life.py | |
| parent | 9d45f7f1a986aaa5d3c084c7ae747442b94a61b1 (diff) | |
update to Qt 6 and Pillow 12
and yeah, I accidentally ran black on the codebase. I don't want to spend more free time fixing that. All of these changes are simple renames or removals, nothing too major.
Diffstat (limited to 'src/components/life.py')
| -rw-r--r-- | src/components/life.py | 276 |
1 files changed, 150 insertions, 126 deletions
diff --git a/src/components/life.py b/src/components/life.py index e19ed36..b3c2c58 100644 --- a/src/components/life.py +++ b/src/components/life.py @@ -1,16 +1,21 @@ -from PyQt5 import QtGui, QtCore, QtWidgets -from PyQt5.QtWidgets import QUndoCommand +from PyQt6 import QtGui, QtCore, QtWidgets +from PyQt6.QtGui import QUndoCommand from PIL import Image, ImageDraw, ImageEnhance, ImageChops, ImageFilter import os import math +import logging + from ..component import Component from ..toolkit.frame import BlankFrame, scale +log = logging.getLogger("AVP.Component.Life") + + class Component(Component): - name = 'Conway\'s Game of Life' - version = '1.0.0' + name = "Conway's Game of Life" + version = "1.0.0" def widget(self, *args): super().widget(*args) @@ -18,34 +23,50 @@ class Component(Component): self.updateGridSize() # The initial grid: a "Queen Bee Shuttle" # https://conwaylife.com/wiki/Queen_bee_shuttle - self.startingGrid = set([ - (3, 7), (3, 8), - (4, 7), (4, 8), - (8, 7), - (9, 6), (9, 8), - (10, 5), (10, 9), - (11, 6), (11, 7), (11, 8), - (12, 4), (12, 5), (12, 9), (12, 10), - (23, 6), (23, 7), - (24, 6), (24, 7) - ]) + self.startingGrid = set( + [ + (3, 7), + (3, 8), + (4, 7), + (4, 8), + (8, 7), + (9, 6), + (9, 8), + (10, 5), + (10, 9), + (11, 6), + (11, 7), + (11, 8), + (12, 4), + (12, 5), + (12, 9), + (12, 10), + (23, 6), + (23, 7), + (24, 6), + (24, 7), + ] + ) # Amount of 'bleed' (off-canvas coordinates) on each side of the grid self.bleedSize = 40 self.page.pushButton_pickImage.clicked.connect(self.pickImage) - self.trackWidgets({ - 'tickRate': self.page.spinBox_tickRate, - 'scale': self.page.spinBox_scale, - 'color': self.page.lineEdit_color, - 'shapeType': self.page.comboBox_shapeType, - 'shadow': self.page.checkBox_shadow, - 'customImg': self.page.checkBox_customImg, - 'showGrid': self.page.checkBox_showGrid, - 'image': self.page.lineEdit_image, - }, colorWidgets={ - 'color': self.page.pushButton_color, - }) + self.trackWidgets( + { + "tickRate": self.page.spinBox_tickRate, + "scale": self.page.spinBox_scale, + "color": self.page.lineEdit_color, + "shapeType": self.page.comboBox_shapeType, + "shadow": self.page.checkBox_shadow, + "customImg": self.page.checkBox_customImg, + "showGrid": self.page.checkBox_showGrid, + "image": self.page.lineEdit_image, + }, + colorWidgets={ + "color": self.page.pushButton_color, + }, + ) self.shiftButtons = ( self.page.toolButton_up, self.page.toolButton_down, @@ -56,7 +77,9 @@ class Component(Component): def shiftFunc(i): def shift(): self.shiftGrid(i) + return shift + shiftFuncs = [shiftFunc(i) for i in range(len(self.shiftButtons))] for i, widget in enumerate(self.shiftButtons): widget.clicked.connect(shiftFuncs[i]) @@ -66,8 +89,11 @@ class Component(Component): def pickImage(self): imgDir = self.settings.value("componentDir", os.path.expanduser("~")) filename, _ = QtWidgets.QFileDialog.getOpenFileName( - self.page, "Choose Image", imgDir, - "Image Files (%s)" % " ".join(self.core.imageFormats)) + self.page, + "Choose Image", + imgDir, + "Image Files (%s)" % " ".join(self.core.imageFormats), + ) if filename: self.settings.setValue("componentDir", os.path.dirname(filename)) self.mergeUndo = False @@ -98,20 +124,20 @@ class Component(Component): self.page.label_image.setVisible(False) self.page.lineEdit_image.setVisible(False) self.page.pushButton_pickImage.setVisible(False) - enabled = (len(self.startingGrid) > 0) + enabled = len(self.startingGrid) > 0 for widget in self.shiftButtons: widget.setEnabled(enabled) def previewClickEvent(self, pos, size, button): pos = ( math.ceil((pos[0] / size[0]) * self.gridWidth) - 1, - math.ceil((pos[1] / size[1]) * self.gridHeight) - 1 + math.ceil((pos[1] / size[1]) * self.gridHeight) - 1, ) action = ClickGrid(self, pos, button) self.parent.undoStack.push(action) def updateGridSize(self): - w, h = self.core.resolutions[-1].split('x') + w, h = self.core.resolutions[-1].split("x") self.gridWidth = int(int(w) / self.scale) self.gridHeight = int(int(h) / self.scale) self.pxWidth = math.ceil(self.width / self.gridWidth) @@ -125,10 +151,8 @@ class Component(Component): self.tickGrids = {0: self.startingGrid} def properties(self): - if self.customImg and ( - not self.image or not os.path.exists(self.image) - ): - return ['error'] + if self.customImg and (not self.image or not os.path.exists(self.image)): + return ["error"] return [] def error(self): @@ -162,42 +186,47 @@ class Component(Component): drawer = ImageDraw.Draw(frame) rect = ( (drawPtX, drawPtY), - (drawPtX + self.pxWidth, drawPtY + self.pxHeight) + (drawPtX + self.pxWidth, drawPtY + self.pxHeight), ) shape = self.page.comboBox_shapeType.currentText().lower() # Rectangle - if shape == 'rectangle': + if shape == "rectangle": drawer.rectangle(rect, fill=self.color) # Elliptical - elif shape == 'elliptical': + elif shape == "elliptical": drawer.ellipse(rect, fill=self.color) tenthX, tenthY = scale(10, self.pxWidth, self.pxHeight, int) smallerShape = ( - (drawPtX + tenthX + int(tenthX / 4), - drawPtY + tenthY + int(tenthY / 2)), - (drawPtX + self.pxWidth - tenthX - int(tenthX / 4), - drawPtY + self.pxHeight - (tenthY + int(tenthY / 2))) + ( + drawPtX + tenthX + int(tenthX / 4), + drawPtY + tenthY + int(tenthY / 2), + ), + ( + drawPtX + self.pxWidth - tenthX - int(tenthX / 4), + drawPtY + self.pxHeight - (tenthY + int(tenthY / 2)), + ), ) outlineShape = ( - (drawPtX + int(tenthX / 4), - drawPtY + int(tenthY / 2)), - (drawPtX + self.pxWidth - int(tenthX / 4), - drawPtY + self.pxHeight - int(tenthY / 2)) + (drawPtX + int(tenthX / 4), drawPtY + int(tenthY / 2)), + ( + drawPtX + self.pxWidth - int(tenthX / 4), + drawPtY + self.pxHeight - int(tenthY / 2), + ), ) # Circle - if shape == 'circle': + if shape == "circle": drawer.ellipse(outlineShape, fill=self.color) drawer.ellipse(smallerShape, fill=(0, 0, 0, 0)) # Lilypad - elif shape == 'lilypad': + elif shape == "lilypad": drawer.pieslice(smallerShape, 290, 250, fill=self.color) # Pie - elif shape == 'pie': + elif shape == "pie": drawer.pieslice(outlineShape, 35, 320, fill=self.color) hX, hY = scale(50, self.pxWidth, self.pxHeight, int) # halfline @@ -205,12 +234,15 @@ class Component(Component): qX, qY = scale(20, self.pxWidth, self.pxHeight, int) # quarterline # Path - if shape == 'path': + if shape == "path": drawer.ellipse(rect, fill=self.color) rects = { direction: False for direction in ( - 'up', 'down', 'left', 'right', + "up", + "down", + "left", + "right", ) } for cell in self.nearbyCoords(x, y): @@ -218,60 +250,59 @@ class Component(Component): continue if cell[0] == x: if cell[1] < y: - rects['up'] = True + rects["up"] = True if cell[1] > y: - rects['down'] = True + rects["down"] = True if cell[1] == y: if cell[0] < x: - rects['left'] = True + rects["left"] = True if cell[0] > x: - rects['right'] = True + rects["right"] = True for direction, rect in rects.items(): if rect: - if direction == 'up': + if direction == "up": sect = ( (drawPtX, drawPtY), - (drawPtX + self.pxWidth, drawPtY + hY) + (drawPtX + self.pxWidth, drawPtY + hY), ) - elif direction == 'down': + elif direction == "down": sect = ( (drawPtX, drawPtY + hY), - (drawPtX + self.pxWidth, - drawPtY + self.pxHeight) + ( + drawPtX + self.pxWidth, + drawPtY + self.pxHeight, + ), ) - elif direction == 'left': + elif direction == "left": sect = ( (drawPtX, drawPtY), - (drawPtX + hX, - drawPtY + self.pxHeight) + (drawPtX + hX, drawPtY + self.pxHeight), ) - elif direction == 'right': + elif direction == "right": sect = ( (drawPtX + hX, drawPtY), - (drawPtX + self.pxWidth, - drawPtY + self.pxHeight) + ( + drawPtX + self.pxWidth, + drawPtY + self.pxHeight, + ), ) drawer.rectangle(sect, fill=self.color) # Duck - elif shape == 'duck': + elif shape == "duck": duckHead = ( (drawPtX + qX, drawPtY + qY), - (drawPtX + int(qX * 3), drawPtY + int(tY * 2)) + (drawPtX + int(qX * 3), drawPtY + int(tY * 2)), ) duckBeak = ( (drawPtX + hX, drawPtY + qY), - (drawPtX + self.pxWidth + qX, - drawPtY + int(qY * 3)) - ) - duckWing = ( - (drawPtX, drawPtY + hY), - rect[1] + (drawPtX + self.pxWidth + qX, drawPtY + int(qY * 3)), ) + duckWing = ((drawPtX, drawPtY + hY), rect[1]) duckBody = ( (drawPtX + int(qX / 4), drawPtY + int(qY * 3)), - (drawPtX + int(tX * 2), drawPtY + self.pxHeight) + (drawPtX + int(tX * 2), drawPtY + self.pxHeight), ) drawer.ellipse(duckBody, fill=self.color) drawer.ellipse(duckHead, fill=self.color) @@ -279,11 +310,16 @@ class Component(Component): drawer.pieslice(duckBeak, 145, 200, fill=self.color) # Peace - elif shape == 'peace': - line = (( - drawPtX + hX - int(tenthX / 2), drawPtY + int(tenthY / 2)), - (drawPtX + hX + int(tenthX / 2), - drawPtY + self.pxHeight - int(tenthY / 2)) + elif shape == "peace": + line = ( + ( + drawPtX + hX - int(tenthX / 2), + drawPtY + int(tenthY / 2), + ), + ( + drawPtX + hX + int(tenthX / 2), + drawPtY + self.pxHeight - int(tenthY / 2), + ), ) drawer.ellipse(outlineShape, fill=self.color) drawer.ellipse(smallerShape, fill=(0, 0, 0, 0)) @@ -291,21 +327,15 @@ class Component(Component): def slantLine(difference): return ( - (drawPtX + difference), (drawPtY + self.pxHeight - qY) + (drawPtX + difference), + (drawPtY + self.pxHeight - qY), ), ( - (drawPtX + hX), (drawPtY + hY) + (drawPtX + hX), + (drawPtY + hY), ) - drawer.line( - slantLine(qX), - fill=self.color, - width=tenthX - ) - drawer.line( - slantLine(self.pxWidth - qX), - fill=self.color, - width=tenthX - ) + drawer.line(slantLine(qX), fill=self.color, width=tenthX) + drawer.line(slantLine(self.pxWidth - qX), fill=self.color, width=tenthX) for x, y in grid: drawPtX = x * self.pxWidth @@ -331,44 +361,38 @@ class Component(Component): w, h = scale(0.05, self.width, self.height, int) for x in range(self.pxWidth, self.width, self.pxWidth): drawer.rectangle( - ((x, 0), - (x + w, self.height)), + ((x, 0), (x + w, self.height)), fill=self.color, ) for y in range(self.pxHeight, self.height, self.pxHeight): drawer.rectangle( - ((0, y), - (self.width, y + h)), + ((0, y), (self.width, y + h)), fill=self.color, ) return frame def gridForTick(self, tick): - ''' + """ Given a tick number over 0, returns a new grid (a set of tuples). This must compute the previous ticks' grids if not already computed - ''' + """ if tick - 1 not in self.tickGrids: self.tickGrids[tick - 1] = self.gridForTick(tick - 1) - + lastGrid = self.tickGrids[tick - 1] def neighbours(x, y): - return { - cell for cell in self.nearbyCoords(x, y) - if cell in lastGrid - } + return {cell for cell in self.nearbyCoords(x, y) if cell in lastGrid} newGrid = set() # Copy cells from the previous grid if they have 2 or 3 neighbouring cells # and if they are within the grid or its bleed area (off-canvas area) for x, y in lastGrid: if ( - -self.bleedSize > x > self.gridWidth + self.bleedSize - or - -self.bleedSize > y > self.gridHeight + self.bleedSize - ): + -self.bleedSize > x > self.gridWidth + self.bleedSize + or -self.bleedSize > y > self.gridHeight + self.bleedSize + ): continue surrounding = len(neighbours(x, y)) if surrounding == 2 or surrounding == 3: @@ -376,7 +400,8 @@ class Component(Component): # Find positions around living cells which must be checked for reproduction potentialNewCells = { - coordTup for origin in lastGrid + coordTup + for origin in lastGrid for coordTup in list(self.nearbyCoords(*origin)) } # Check for reproduction @@ -392,11 +417,11 @@ class Component(Component): def savePreset(self): pr = super().savePreset() - pr['GRID'] = sorted(self.startingGrid) + pr["GRID"] = sorted(self.startingGrid) return pr def loadPreset(self, pr, *args): - self.startingGrid = set(pr['GRID']) + self.startingGrid = set(pr["GRID"]) if self.startingGrid: for widget in self.shiftButtons: widget.setEnabled(True) @@ -414,15 +439,17 @@ class Component(Component): class ClickGrid(QUndoCommand): - def __init__(self, comp, pos, id_): - super().__init__( - "click %s component #%s" % (comp.name, comp.compPos)) + def __init__(self, comp, pos, button): + super().__init__("click %s component #%s" % (comp.name, comp.compPos)) self.comp = comp self.pos = [pos] - self.id_ = id_ + if button == QtCore.Qt.MouseButton.RightButton: + self.button = 2 + else: + self.button = 1 def id(self): - return self.id_ + return self.button def mergeWith(self, other): self.pos.extend(other.pos) @@ -439,21 +466,21 @@ class ClickGrid(QUndoCommand): self.comp.update(auto=True) def redo(self): - if self.id_ == 1: # Left-click + if self.button == 1: # Left-click self.add() - elif self.id_ == 2: # Right-click + elif self.button == 2: # Right-click self.remove() def undo(self): - if self.id_ == 1: # Left-click + if self.button == 1: # Left-click self.remove() - elif self.id_ == 2: # Right-click + elif self.button == 2: # Right-click self.add() + class ShiftGrid(QUndoCommand): def __init__(self, comp, direction): - super().__init__( - "change %s component #%s" % (comp.name, comp.compPos)) + super().__init__("change %s component #%s" % (comp.name, comp.compPos)) self.comp = comp self.direction = direction self.distance = 1 @@ -466,10 +493,7 @@ class ShiftGrid(QUndoCommand): return True def newGrid(self, Xchange, Ychange): - return { - (x + Xchange, y + Ychange) - for x, y in self.comp.startingGrid - } + return {(x + Xchange, y + Ychange) for x, y in self.comp.startingGrid} def redo(self): if self.direction == 0: |
