def setUp(self): self.__idlist = [1, 23] self.__assignmentDict = {} self.__repoGrades = Repository() self.__undoRedo = UndoRedo() self.__repoStudents = Repository() #here we populate the students repository self.__groups = {} self.__control = controllerStudent(self.__repoStudents, self.__undoRedo) x = "add 23 Popescu Marin 915" self.__control.addStudent(x, self.__groups, self.__idlist, self.__assignmentDict) x = "add 1 Boc Emil 911" self.__control.addStudent(x, self.__groups, self.__idlist, self.__assignmentDict) self.__repoAssignments = Repository() self.__control2 = controllerAssignmnet(self.__repoAssignments, self.__undoRedo) #here we populate the assignment repo x = "add 2 description prime numbers deadline 12.09" self.__control2.addAssignment(x) x = "add 1 description fibonacci sequence deadline 13.10" self.__control2.addAssignment(x) self.__controllerGrades = ControllerGrades( self.__repoGrades, self.__repoStudents, self.__repoAssignments, self.__idlist, self.__assignmentDict, self.__undoRedo) self.__assign = Assign(self.__idlist, self.__assignmentDict, self.__repoStudents, self.__repoAssignments, self.__groups, self.__undoRedo)
def setUp(self): self.__repo=Repository() self.__groups={} self.__idlist=[] self.__assignmentDict={} self.__undoRedo=UndoRedo() self.__control=controllerStudent(self.__repo,self.__undoRedo)
def __init__(self, app: gui, rom: ROM, palette_editor: PaletteEditor): self.app = app self.rom = rom self.palette_editor = palette_editor self._unsaved_changes = False self._map_address: List[int] = [] # TODO Maybe read these from a customised (optional) text file, or config file section self._map_names: List[str] = [ "Grass", "Brush", "Forest", "Player vs Sea Monsters", "Player vs Pirate Ship", "Door", "Stone Floor 1", "Lava", "Wall", "Table", "Chest", "Stone Floor 2", "Wall Top", "Castle / Dungeon", "Dungeon Entrance", "Town", "Player Ship vs Sea Monsters", "Player Ship vs Pirate Ship", "Player Ship vs Land Enemies" ] # Pattern info self._pattern_info: List[str] = [ "Normal", "Normal", "Normal", "Water", "Blocking", "Normal", "Normal", "Normal", "Blocking", "Blocking", "Normal", "Normal", "Blocking", "Normal", "Normal", "Normal" ] # Map being currently edited. Only modify this in show_window() and close_window(). self._selected_map = 0 # Current pick to put on the map when drawing self._selected_tile = 0 # Index of the last map entry that was modified or selected self._last_edited = 0 # Logical map data (i.e. tile indices 0x0-0xF) self._map_data: List[int] = [] self._grid_colour = "#C0C0C0" # Image cache self._patterns_cache: List[ImageTk.PhotoImage] = [] # Canvas item IDs self._tiles_grid: List[int] = [0] * 16 self._tile_items: List[int] = [0] * 16 self._tile_rectangle: int = 0 self._map_grid: List[int] = [0] * 8 * 12 self._map_items: List[int] = [0] * 9 * 13 self._map_rectangle: int = 0 self._canvas_tiles: Canvas = Canvas() self._canvas_map: Canvas = Canvas() # We keep track of how many tiles are modified with a single operation, for the undo/redo manager self._modified_tiles: int = 0 self._undo_redo: UndoRedo = UndoRedo() # These keep count of how many actions to process in each single undo/redo self._undo_actions: List[int] = [] self._redo_actions: List[int] = []
def setUp(self): self.__repo2 = Repository() self.__undoRedo = UndoRedo() self.__control2 = controllerAssignmnet(self.__repo2, self.__undoRedo)
def show(self, bank: int, address: int, palette: int) -> None: self._bank = bank self._address = address self._palette = palette # Allocate memory for "pixels" and other canvas items in the drawing area self._rectangles = [0] * 64 self._grid = [0] * 14 # Check if window already exists try: self.app.getFrameWidget("TL_Frame_Buttons") self.app.showSubWindow("Tile_Editor") return except appJar.appjar.ItemLookupError: generator = self.app.subWindow("Tile_Editor", size=[286, 220], padding=[2, 2], title="Edit CHR Tile", resizable=False, modal=True, blocking=True, bg=colour.DARK_GREY, fg=colour.WHITE, stopFunction=self.hide) app = self.app with generator: with app.frame("TL_Frame_Buttons", padding=[2, 2], sticky="NEW", row=0, column=0, colspan=3): app.button("TL_Apply", self._input, image="res/floppy.gif", bg=colour.MEDIUM_GREY, row=0, column=1, sticky="W", tooltip="Save all changes") app.button("TL_Reload", self._input, image="res/reload.gif", bg=colour.MEDIUM_GREY, row=0, column=2, sticky="W", tooltip="Reload from ROM") app.button("TL_Close", self._input, image="res/close.gif", bg=colour.MEDIUM_GREY, row=0, column=3, sticky="W", tooltip="Discard changes and close") app.canvas("TL_Separator", width=16, height=1, row=0, column=4) app.button("TL_Undo", self._undo, image="res/undo.gif", width=32, height=32, tooltip="Nothing to Undo", sticky="E", row=0, column=5) app.button("TL_Redo", self._redo, image="res/redo.gif", width=32, height=32, tooltip="Nothing to Redo", sticky="E", row=0, column=6) with app.frame("TL_Frame_Palette", padding=[4, 4], sticky="NW", row=1, column=0): app.canvas("TL_Canvas_Palette", width=32, height=128, bg=colour.BLACK).bind("<ButtonRelease-1>", self._palette_left_click) with app.frame("TL_Frame_Drawing", padding=[4, 4], sticky="NW", row=1, column=1): self._drawing = app.canvas("TL_Canvas_Drawing", width=128, height=128, bg=colour.BLACK) with app.frame("TL_Frame_Tools", padding=[4, 4], sticky="NE", row=1, column=2): app.button("TL_Tool_Draw", self._input, image="res/pencil.gif", sticky="N", bg=colour.WHITE, row=0, column=0, colspan=2) app.button("TL_Tool_Fill", self._input, image="res/bucket.gif", sticky="N", bg=colour.MEDIUM_GREY, row=1, column=0, colspan=2) app.button("TL_Move_Left", self._input, image="res/arrow_left-small.gif", tooltip="Move image left", sticky="NE", row=2, column=0) app.button("TL_Move_Right", self._input, image="res/arrow_right-small.gif", tooltip="Move image right", sticky="NE", row=2, column=1) app.button("TL_Move_Up", self._input, image="res/arrow_up-small.gif", tooltip="Move image up", sticky="NE", row=3, column=0) app.button("TL_Move_Down", self._input, image="res/arrow_down-small.gif", tooltip="Move image down", sticky="NE", row=3, column=1) self._load_pattern() self._select_tool(TileEditor._DRAW) self._undo_redo = UndoRedo() self._drawing.bind("<ButtonPress-1>", self._drawing_left_down) self._drawing.bind("<ButtonRelease-1>", self._drawing_left_up) self._drawing.bind("<B1-Motion>", self._drawing_left_drag) self._drawing.bind("<ButtonPress-3>", self._drawing_right_click) self._show_palette() self._select_colour(0, 1) # This is causing problems at the moment... """ sw = app.openSubWindow("Tile_Editor") sw.bind("<Control-z>", self._undo, add='') sw.bind("<Control-y>", self._redo, add='') """ app.disableButton("TL_Undo") app.disableButton("TL_Redo") app.showSubWindow("Tile_Editor")
class TileEditor: _DRAW: int = 0 _FILL: int = 1 # ------------------------------------------------------------------------------------------------------------------ def __init__(self, app: gui, settings: EditorSettings, rom: ROM, palette_editor: PaletteEditor): self.app = app self.settings = settings self.rom = rom self.palette_editor = palette_editor self._bank: int = 0 self._address: int = 0 self._palette: int = 0 self._colours = [] self._selected_colour: int = 0 self._selected_palette: int = 0 # Canvas references self._drawing: Optional[tkinter.Canvas] = None # Canvas item IDs self._rectangles: List[int] = [ ] # Each filled rectangle will represent a pixel self._grid: List[int] = [] self._pixels: Optional[bytearray] = None self._palette_rectangle: int = 0 # A rectangle surrounding the selected colour self._tool: int = 0 # Undo / Redo # We keep track of how many tiles are modified with a single operation, for the undo/redo manager self._modified_pixels: int = 0 # Last modified pixel, used to prevent unnecessary actions when drag-drawing self._last_edited: int = -1 self._undo_redo: Optional[UndoRedo] = None # These keep count of how many actions to process in each single undo/redo self._undo_actions: List[int] = [] self._redo_actions: List[int] = [] # ------------------------------------------------------------------------------------------------------------------ def error(self, message: str): log(2, f"{self.__class__.__name__}", message) # ------------------------------------------------------------------------------------------------------------------ def warning(self, message: str): log(3, f"{self.__class__.__name__}", message) # ------------------------------------------------------------------------------------------------------------------ def info(self, message: str): log(4, f"{self.__class__.__name__}", message) # ------------------------------------------------------------------------------------------------------------------ def hide(self) -> None: self.app.hideSubWindow("Tile_Editor") self.app.emptySubWindow("Tile_Editor") self._drawing = None self._grid = [] self._rectangles = [] self._pixels = None self._colours = [] self._undo_redo = None self._undo_actions = [] self._redo_actions = [] # ------------------------------------------------------------------------------------------------------------------ def show(self, bank: int, address: int, palette: int) -> None: self._bank = bank self._address = address self._palette = palette # Allocate memory for "pixels" and other canvas items in the drawing area self._rectangles = [0] * 64 self._grid = [0] * 14 # Check if window already exists try: self.app.getFrameWidget("TL_Frame_Buttons") self.app.showSubWindow("Tile_Editor") return except appJar.appjar.ItemLookupError: generator = self.app.subWindow("Tile_Editor", size=[286, 220], padding=[2, 2], title="Edit CHR Tile", resizable=False, modal=True, blocking=True, bg=colour.DARK_GREY, fg=colour.WHITE, stopFunction=self.hide) app = self.app with generator: with app.frame("TL_Frame_Buttons", padding=[2, 2], sticky="NEW", row=0, column=0, colspan=3): app.button("TL_Apply", self._input, image="res/floppy.gif", bg=colour.MEDIUM_GREY, row=0, column=1, sticky="W", tooltip="Save all changes") app.button("TL_Reload", self._input, image="res/reload.gif", bg=colour.MEDIUM_GREY, row=0, column=2, sticky="W", tooltip="Reload from ROM") app.button("TL_Close", self._input, image="res/close.gif", bg=colour.MEDIUM_GREY, row=0, column=3, sticky="W", tooltip="Discard changes and close") app.canvas("TL_Separator", width=16, height=1, row=0, column=4) app.button("TL_Undo", self._undo, image="res/undo.gif", width=32, height=32, tooltip="Nothing to Undo", sticky="E", row=0, column=5) app.button("TL_Redo", self._redo, image="res/redo.gif", width=32, height=32, tooltip="Nothing to Redo", sticky="E", row=0, column=6) with app.frame("TL_Frame_Palette", padding=[4, 4], sticky="NW", row=1, column=0): app.canvas("TL_Canvas_Palette", width=32, height=128, bg=colour.BLACK).bind("<ButtonRelease-1>", self._palette_left_click) with app.frame("TL_Frame_Drawing", padding=[4, 4], sticky="NW", row=1, column=1): self._drawing = app.canvas("TL_Canvas_Drawing", width=128, height=128, bg=colour.BLACK) with app.frame("TL_Frame_Tools", padding=[4, 4], sticky="NE", row=1, column=2): app.button("TL_Tool_Draw", self._input, image="res/pencil.gif", sticky="N", bg=colour.WHITE, row=0, column=0, colspan=2) app.button("TL_Tool_Fill", self._input, image="res/bucket.gif", sticky="N", bg=colour.MEDIUM_GREY, row=1, column=0, colspan=2) app.button("TL_Move_Left", self._input, image="res/arrow_left-small.gif", tooltip="Move image left", sticky="NE", row=2, column=0) app.button("TL_Move_Right", self._input, image="res/arrow_right-small.gif", tooltip="Move image right", sticky="NE", row=2, column=1) app.button("TL_Move_Up", self._input, image="res/arrow_up-small.gif", tooltip="Move image up", sticky="NE", row=3, column=0) app.button("TL_Move_Down", self._input, image="res/arrow_down-small.gif", tooltip="Move image down", sticky="NE", row=3, column=1) self._load_pattern() self._select_tool(TileEditor._DRAW) self._undo_redo = UndoRedo() self._drawing.bind("<ButtonPress-1>", self._drawing_left_down) self._drawing.bind("<ButtonRelease-1>", self._drawing_left_up) self._drawing.bind("<B1-Motion>", self._drawing_left_drag) self._drawing.bind("<ButtonPress-3>", self._drawing_right_click) self._show_palette() self._select_colour(0, 1) # This is causing problems at the moment... """ sw = app.openSubWindow("Tile_Editor") sw.bind("<Control-z>", self._undo, add='') sw.bind("<Control-y>", self._redo, add='') """ app.disableButton("TL_Undo") app.disableButton("TL_Redo") app.showSubWindow("Tile_Editor") # ------------------------------------------------------------------------------------------------------------------ def _input(self, widget: str) -> None: if widget == "TL_Apply": # ---------------------------------------------------------------------------------- self.rom.write_pattern(self._bank, self._address, self._pixels) if self.settings.get("close sub-window after saving"): self.hide() elif widget == "TL_Close": # ---------------------------------------------------------------------------------- self.hide() elif widget == "TL_Tool_Draw": # ------------------------------------------------------------------------------ self._select_tool(TileEditor._DRAW) elif widget == "TL_Tool_Fill": # ------------------------------------------------------------------------------ self._select_tool(TileEditor._FILL) elif widget == "TL_Move_Left": # ------------------------------------------------------------------------------ self._undo_redo(self._move_pixels, (7, 0), (1, 0), text="Move Image Left") self._undo_actions.append(1) self._redo_actions = [] self._update_undo_buttons() elif widget == "TL_Move_Right": # -------------------------------------------------------------------------- self._undo_redo(self._move_pixels, (1, 0), (7, 0), text="Move Image Right") self._undo_actions.append(1) self._redo_actions = [] self._update_undo_buttons() elif widget == "TL_Move_Up": # ------------------------------------------------------------------------------ self._undo_redo(self._move_pixels, (0, 7), (0, 1), text="Move Image Up") self._undo_actions.append(1) self._redo_actions = [] self._update_undo_buttons() elif widget == "TL_Move_Down": # ------------------------------------------------------------------------------ self._undo_redo(self._move_pixels, (0, 1), (0, 7), text="Move Image Down") self._undo_actions.append(1) self._redo_actions = [] self._update_undo_buttons() else: # ------------------------------------------------------------------------------------------------------ self.warning( f"Unimplemented input from Pattern Editor widget '{widget}'.") # ------------------------------------------------------------------------------------------------------------------ def _load_pattern(self) -> None: self._pixels = self.rom.read_pattern(self._bank, self._address) colours = self.palette_editor.sub_palette(self._palette, 1) # Convert our RGB bytearray to strings self._colours = c = [ f"#{colours[n]:02X}{colours[n+1]:02X}{colours[n+2]:02X}" for n in range(0, 12, 3) ] x = y = 0 i = 0 for pixel in self._pixels: if self._rectangles[i] > 0: # Already exists in canvas: update colour self._drawing.itemconfigure(self._rectangles[i], fill=c[pixel]) else: # Create a rectangle in our canvas and make it the colour of this pixel rx = x << 4 ry = y << 4 self._rectangles[i] = self._drawing.create_rectangle( rx, ry, rx + 16, ry + 16, width=0, fill=c[pixel]) x += 1 i += 1 if x > 7: x = 0 y += 1 # Show the grid x = y = 16 for i in range(7): # Horizontal if self._grid[i] > 0: self._drawing.tag_raise(self._grid[i]) else: self._grid[i] = self._drawing.create_line(0, y, 128, y, dash=(4, 4), fill="#C0C0C0") y += 16 # Vertical if self._grid[i + 7] > 0: self._drawing.tag_raise(self._grid[i + 7]) else: self._grid[i + 7] = self._drawing.create_line(x, 0, x, 128, dash=(4, 4), fill="#C0C0C0") x += 16 # ------------------------------------------------------------------------------------------------------------------ def _select_tool(self, tool: int) -> None: self._tool = tool if tool == TileEditor._DRAW: self._drawing.configure(cursor="pencil") self.app.setButtonBg("TL_Tool_Draw", colour.WHITE) self.app.setButtonBg("TL_Tool_Fill", colour.MEDIUM_GREY) elif tool == TileEditor._FILL: self._drawing.configure(cursor="cross") self.app.setButtonBg("TL_Tool_Draw", colour.MEDIUM_GREY) self.app.setButtonBg("TL_Tool_Fill", colour.WHITE) # ------------------------------------------------------------------------------------------------------------------ def _drawing_left_down(self, event) -> None: if self._tool == TileEditor._FILL: return # The image is "zoomed in" x16, so: (x / 16) + ((y / 16) * number of items in a row) pixel_index = (event.x >> 4) + ((event.y >> 4) << 3) self._last_edited = pixel_index old_colour = self._pixels[pixel_index] self._modified_pixels += 1 self._undo_redo(self._change_pixel, (event.x, event.y, self._selected_colour), (event.x, event.y, old_colour), text="Draw") # ------------------------------------------------------------------------------------------------------------------ def _drawing_left_up(self, event) -> None: if self._tool == TileEditor._FILL: self._flood_fill(event.x, event.y, self._selected_colour) return self._undo_actions.append(self._modified_pixels) self._redo_actions = [] self._modified_pixels = 0 self._update_undo_buttons() # ------------------------------------------------------------------------------------------------------------------ def _drawing_left_drag(self, event) -> None: if self._tool == TileEditor._FILL: return # Prevent dragging outside the canvas if event.x < 0 or event.x >= 128 or event.y < 0 or event.y >= 128: return # The image is "zoomed in" x16, so: (x / 16) + ((y / 16) * number of items in a row) pixel_index = (event.x >> 4) + ((event.y >> 4) << 3) self._last_edited = pixel_index old_colour = self._pixels[pixel_index] self._undo_redo(self._change_pixel, (event.x, event.y, self._selected_colour), (event.x, event.y, old_colour), text="Draw") self._modified_pixels += 1 # ------------------------------------------------------------------------------------------------------------------ def _drawing_right_click(self, event) -> None: # The image is "zoomed in" x16, so: (x / 16) + ((y / 16) * number of items in a row) pixel_index = (event.x >> 4) + ((event.y >> 4) << 3) self._select_colour(self._pixels[pixel_index]) # ------------------------------------------------------------------------------------------------------------------ def _palette_left_click(self, event) -> None: # First we need to understand which palette was clicked on # The four palettes are in a 2x2 area of 32x128 pixels, each palette taking 16x64 pixels # ...and each "colour" takes 16x16 pixels x = event.x >> 4 y = event.y >> 6 palette = x + (y << 1) colours = self.palette_editor.sub_palette(self._palette, palette) # Convert our RGB bytearray to strings, this is now the new palette self._colours = [ f"#{colours[n]:02X}{colours[n + 1]:02X}{colours[n + 2]:02X}" for n in range(0, 12, 3) ] # Now get the index of the selected colour within this palette y = event.y >> 4 if palette > 1: y -= 4 self._select_colour(y, palette) # ------------------------------------------------------------------------------------------------------------------ def _select_colour(self, c: int, p: Optional[int] = None) -> None: """ Selects a colour from a palette """ self._selected_colour = c if p is not None: previous_palette = self._selected_palette self._selected_palette = p else: previous_palette = p = self._selected_palette x = (p << 4) % 32 y = c << 4 if p > 1: y += 64 self.app.getCanvasWidget("TL_Canvas_Palette").coords( self._palette_rectangle, x + 1, y + 1, x + 14, y + 14) # Change image colours if a new palette has been selected if p != previous_palette: self._recolour_image() # ------------------------------------------------------------------------------------------------------------------ def _change_pixel(self, x: int, y: int, c: int) -> None: """ Changes the colour of one pixel. Parameters ---------- x: int x coordinate on 128x128 canvas y: int y coordinate on 128x128 canvas c: int Colour value (0-3) """ # The image is "zoomed in" x16, so: (x / 16) + ((y / 16) * number of items in a row) pixel_index = (x >> 4) + ((y >> 4) << 3) self._pixels[pixel_index] = c # Update the canvas if self._rectangles[pixel_index] > 0: self._drawing.itemconfigure(self._rectangles[pixel_index], fill=self._colours[c]) # ------------------------------------------------------------------------------------------------------------------ def _flood_fill(self, x: int, y: int, c: int) -> None: """ Fills an area with the specified colour. Parameters ---------- x: int x coordinate of the starting pixel in the drawing canvas y: int y coordinate of the starting pixel in the drawing canvas c: int Colour index within the current palette """ # Translate the coordinates from 128x128 canvas to 8x8 tile x = x >> 4 y = y >> 4 # List of coordinates that we'll need to visit queue = [(x, y)] index = x + (y << 3) old_clr = self._pixels[index] self._undo_redo(self._change_pixel, (x << 4, y << 4, c), (x << 4, y << 4, old_clr), text="Fill Area") self._modified_pixels += 1 # Four-direction non-recursive flood-fill algorithm in a single non-nested loop while len(queue) > 0: x, y = queue.pop() # 1 Pixel to the left node_x = x - 1 node_y = y index = node_x + (node_y << 3) if node_x >= 0 and self._pixels[index] == old_clr: self._undo_redo(self._change_pixel, (node_x << 4, node_y << 4, c), (node_x << 4, node_y << 4, old_clr), text="Fill Area") self._modified_pixels += 1 queue.append((node_x, node_y)) # 1 Pixel to the right node_x = x + 1 node_y = y index = node_x + (node_y << 3) if node_x < 8 and self._pixels[index] == old_clr: self._undo_redo(self._change_pixel, (node_x << 4, node_y << 4, c), (node_x << 4, node_y << 4, old_clr), text="Fill Area") self._modified_pixels += 1 queue.append((node_x, node_y)) # 1 Pixel down node_x = x node_y = y + 1 index = node_x + (node_y << 3) if node_y < 8 and self._pixels[index] == old_clr: self._undo_redo(self._change_pixel, (node_x << 4, node_y << 4, c), (node_x << 4, node_y << 4, old_clr), text="Fill Area") self._modified_pixels += 1 queue.append((node_x, node_y)) # 1 Pixel up node_x = x node_y = y - 1 index = node_x + (node_y << 3) if node_y >= 0 and self._pixels[index] == old_clr: self._undo_redo(self._change_pixel, (node_x << 4, node_y << 4, c), (node_x << 4, node_y << 4, old_clr), text="Fill Area") self._modified_pixels += 1 queue.append((node_x, node_y)) self._undo_actions.append(self._modified_pixels) self._redo_actions = [] self._modified_pixels = 0 self._update_undo_buttons() # ------------------------------------------------------------------------------------------------------------------ def _undo(self, _event=None) -> None: try: count = self._undo_actions.pop() except IndexError: return self._undo_redo.undo(count) self._redo_actions.append(count) self._update_undo_buttons() # ------------------------------------------------------------------------------------------------------------------ def _redo(self, _event=None) -> None: try: count = self._redo_actions.pop() except IndexError: return self._undo_redo.redo(count) self._undo_actions.append(count) self._update_undo_buttons() # ------------------------------------------------------------------------------------------------------------------ def _show_palette(self) -> None: # Make two columns of eight colours each (two palettes per column) canvas = self.app.getCanvasWidget("TL_Canvas_Palette") colours = self.palette_editor.sub_palette(self._palette, 0) # Convert our RGB bytearray to strings clr = [ f"#{colours[n]:02X}{colours[n + 1]:02X}{colours[n + 2]:02X}" for n in range(0, 12, 3) ] x = 2 y = 2 for c in range(4): canvas.create_rectangle(x, y, x + 12, y + 12, fill=clr[c]) y += 16 # Second palette colours = self.palette_editor.sub_palette(self._palette, 1) clr = [ f"#{colours[n]:02X}{colours[n + 1]:02X}{colours[n + 2]:02X}" for n in range(0, 12, 3) ] x = 16 y = 2 for c in range(4): canvas.create_rectangle(x, y, x + 12, y + 12, fill=clr[c]) y += 16 # Third palette colours = self.palette_editor.sub_palette(self._palette, 2) clr = [ f"#{colours[n]:02X}{colours[n + 1]:02X}{colours[n + 2]:02X}" for n in range(0, 12, 3) ] x = 2 y = 64 for c in range(4): canvas.create_rectangle(x, y, x + 12, y + 12, fill=clr[c]) y += 16 # Last palette colours = self.palette_editor.sub_palette(self._palette, 3) clr = [ f"#{colours[n]:02X}{colours[n + 1]:02X}{colours[n + 2]:02X}" for n in range(0, 12, 3) ] x = 16 y = 64 for c in range(4): canvas.create_rectangle(x, y, x + 12, y + 12, fill=clr[c]) y += 16 # Add a selection marker self._palette_rectangle = canvas.create_rectangle(1, 1, 15, 15, width=2, outline="#F03030") # ------------------------------------------------------------------------------------------------------------------ def _recolour_image(self) -> None: """ Updates the colours of each pixel to reflect a new palette selection """ colours = self.palette_editor.sub_palette(self._palette, self._selected_palette) # Convert our RGB bytearray to strings self._colours = c = [ f"#{colours[n]:02X}{colours[n + 1]:02X}{colours[n + 2]:02X}" for n in range(0, 12, 3) ] x = y = 0 i = 0 for pixel in self._pixels: # We just assume these items already exist: do not call this method before the image has been loaded! self._drawing.itemconfigure(self._rectangles[i], fill=c[pixel]) x += 1 i += 1 if x > 7: x = 0 y += 1 # ------------------------------------------------------------------------------------------------------------------ def _update_undo_buttons(self) -> None: if len(self._undo_actions) < 1: self.app.disableButton("TL_Undo") self.app.setButtonTooltip("TL_Undo", "Nothing to Undo") else: self.app.enableButton("TL_Undo") self.app.setButtonTooltip( "TL_Undo", "Undo " + self._undo_redo.get_undo_text()) if len(self._redo_actions) < 1: self.app.disableButton("TL_Redo") self.app.setButtonTooltip("TL_Redo", "Nothing to Redo") else: self.app.enableButton("TL_Redo") self.app.setButtonTooltip( "TL_Undo", "Undo " + self._undo_redo.get_redo_text()) # ------------------------------------------------------------------------------------------------------------------ def _move_pixels(self, dst_x: int, dst_y: int) -> None: # Create an empty 8x8 array of pixels as our destination moved = bytearray([0] * 64) # Copy all pixels to the destination coordinates, wrapping around for y in range(8): if dst_y > 7: # Wrap around vertically dst_y = 0 for x in range(8): if dst_x > 7: # Wrap around horizontally dst_x = 0 c = self._pixels[x + (y << 3)] moved[dst_x + (dst_y << 3)] = c dst_x += 1 dst_y += 1 self._pixels = moved self._recolour_image()
def __init__(self, app: appjar.gui, rom: ROM, palette_editor: PaletteEditor): self.bank: int = 0 self.nametable_address: int = 0 self.attributes_address: int = 0 self._width: int = 0 self._height: int = 0 self._x: int = 0 self._y: int = 0 self.palette_index: int = 0 self.patterns_address: int = 0 # We cache these for speed and readability self.canvas_cutscene: Canvas = app.getCanvasWidget( "CE_Canvas_Cutscene") self.canvas_patterns: Canvas = app.getCanvasWidget( "CE_Canvas_Patterns") self.canvas_palettes: Canvas = app.getCanvasWidget( "CE_Canvas_Palettes") # Selection from colours self._selected_palette: int = 0 # Selection from patterns (if 2x2, then this is the top-left tile) self._selected_pattern: int = 0 # Selection from nametable (if 2x2, then this is the top-left tile) self._selected_tile: int = 0 self._unsaved_changes: bool = False # Canvas item IDs self._patterns: List[int] = [0] * 256 # Image cache image = Image.new('P', (16, 16), 0) # Empty image just for initialisation self._pattern_cache: List[Image] = [image] * 256 self._pattern_image_cache: List[ ImageTk.PhotoImage] = [ImageTk.PhotoImage(image)] * 256 # Canvas item IDs self._palette_items: List[int] = [0] * 16 # Canvas item ID, PhotoImage cache self._tiles: List[int] = [0] * (32 * 30) self._tile_image_cache: List[ ImageTk.PhotoImage] = [ImageTk.PhotoImage(image)] * (32 * 30) # Nametable entries, same as how they appear in ROM self.nametable: bytearray = bytearray() # Attribute values, one per tile so NOT as they appear in ROM # These can be matched to nametable tiles so that nametables[t] uses palette attributes[t] self.attributes: bytearray = bytearray() # IDs of tkinter items added to canvases indicate current selection self._cutscene_rectangle: int = 0 self._pattern_rectangle: int = 0 self._palette_rectangle: int = 0 self._cutscene_bounds: int = 0 # 0: 1x1 tile, 1: 2x2 tiles self._selection_size: int = 0 # Last modified tile on the cutscene, used for drag-editing self._last_modified: Point2D = Point2D(-1, -1) # We keep track of how many tiles are modified with a single operation, for the undo/redo manager self._modified_tiles: int = 0 self._undo_redo: UndoRedo = UndoRedo() # These keep count of how many actions to process in each single undo/redo self._undo_actions: List[int] = [] self._redo_actions: List[int] = [] self.rom: ROM = rom self.palette_editor = palette_editor self.app: appjar.gui = app