diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a1be6c3..22e01f8 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,7 +6,7 @@ { "label": "Build libgui/GuiCanvasWxBackendFast.pyd", "type": "shell", - "command": "cd \"${workspaceFolder}/libgui\" && cmd /c cl /LD /IC:/Python37/include GuiCanvasWxBackendFast.c C:/Python37/libs/python37.lib /FeGuiCanvasWxBackendFast.pyd", + "command": "cd \"${workspaceFolder}/libgui\" && cmd /c cl /EHsc /LD /Ox /Wall /WX /IC:/Python37/include GuiCanvasWxBackendFast.cpp C:/Python37/libs/python37.lib /FeGuiCanvasWxBackendFast.pyd", "problemMatcher": [ "$msCompile" ] diff --git a/assets/audio/roarvap0r1.wav b/assets/audio/roarvap0r1.wav new file mode 100644 index 0000000..93ffc78 Binary files /dev/null and b/assets/audio/roarvap0r1.wav differ diff --git a/assets/audio/roarvap0r2.wav b/assets/audio/roarvap0r2.wav new file mode 100644 index 0000000..d1b65a1 Binary files /dev/null and b/assets/audio/roarvap0r2.wav differ diff --git a/assets/audio/roarvap0r3.wav b/assets/audio/roarvap0r3.wav new file mode 100644 index 0000000..40c529b Binary files /dev/null and b/assets/audio/roarvap0r3.wav differ diff --git a/assets/audio/roarvap0r4.wav b/assets/audio/roarvap0r4.wav new file mode 100644 index 0000000..47d2d4a Binary files /dev/null and b/assets/audio/roarvap0r4.wav differ diff --git a/assets/audio/roarvap0r5.wav b/assets/audio/roarvap0r5.wav new file mode 100644 index 0000000..235634a Binary files /dev/null and b/assets/audio/roarvap0r5.wav differ diff --git a/assets/audio/roarvap0r6.wav b/assets/audio/roarvap0r6.wav new file mode 100644 index 0000000..16e943a Binary files /dev/null and b/assets/audio/roarvap0r6.wav differ diff --git a/assets/audio/roarvap0r7.wav b/assets/audio/roarvap0r7.wav new file mode 100644 index 0000000..8bb8cc3 Binary files /dev/null and b/assets/audio/roarvap0r7.wav differ diff --git a/assets/audio/roarvap0r8.wav b/assets/audio/roarvap0r8.wav new file mode 100644 index 0000000..969d1dd Binary files /dev/null and b/assets/audio/roarvap0r8.wav differ diff --git a/assets/audio/roarviking1.wav b/assets/audio/roarviking1.wav new file mode 100644 index 0000000..94715d1 Binary files /dev/null and b/assets/audio/roarviking1.wav differ diff --git a/assets/audio/roarviking2.wav b/assets/audio/roarviking2.wav new file mode 100644 index 0000000..ee6d111 Binary files /dev/null and b/assets/audio/roarviking2.wav differ diff --git a/assets/audio/roarviking3.wav b/assets/audio/roarviking3.wav new file mode 100644 index 0000000..edce8e3 Binary files /dev/null and b/assets/audio/roarviking3.wav differ diff --git a/assets/audio/roarviking4.wav b/assets/audio/roarviking4.wav new file mode 100644 index 0000000..e9a4c07 Binary files /dev/null and b/assets/audio/roarviking4.wav differ diff --git a/assets/audio/roarviking5.wav b/assets/audio/roarviking5.wav new file mode 100644 index 0000000..6d383cf Binary files /dev/null and b/assets/audio/roarviking5.wav differ diff --git a/assets/text/README.md b/assets/text/README.md index 4b14c9c..20a9f60 100644 --- a/assets/text/README.md +++ b/assets/text/README.md @@ -1,7 +1,7 @@ # roar.py -- mIRC art editor for Windows & Linux (WIP) -* Prerequisites on Windows: install Python v3.6.x[1] and script dependencies w/ the following elevated command prompt command line: +* Prerequisites on Windows: install Python v3.7.x[1] and script dependencies w/ the following elevated command prompt command line: `pip install requests urllib3 wxPython` -* Prerequisites on Linux: python3 && python-wx{gtk2.8,tools} on Debian-family Linux distributions +* Prerequisites on Linux: python3 (v3.7.x) && python-wx{gtk2.8,tools} on Debian-family Linux distributions * Screenshot: ![Screenshot](https://github.com/lalbornoz/roar/raw/master/assets/images/roar.png "Screenshot") diff --git a/assets/text/roadmap.txt b/assets/text/roadmap.txt index fcb785b..a49c738 100644 --- a/assets/text/roadmap.txt +++ b/assets/text/roadmap.txt @@ -1,4 +1,4 @@ -1) Backend: correctly refresh transparent cursor cells when drawing cursor patches +1) GUI: drag & drop file outside of canvas to open, into canvas as object w/ select tool 2) GUI: edit asset in new canvas, import from {canvas,object} 3) GUI: implement GuiCanvasWxBackendFast.c{,c} 4) GUI: select all @@ -6,8 +6,10 @@ 6) Operators: copy, cut, delete, insert from, paste 7) Operators: crop, scale, shift, slice operators 8) Tools: measure, triangle, unicode block elements tool -9) Tools: object tool: reimplement cloning correctly outside of object tool -10) Tools: text tool: finish Arabic/RTL text implementation -11) Tools: text tool: implicitly draw (text) w/ bg -1, toggle drawing actual brushColours[1] mode w/ RMB +9) Tools: object tool: allow application of arbitrary tool to selection before setting +10) Tools: object tool: reimplement cloning correctly outside of object tool +11) Tools: reimplement in C++ +12) Tools: text tool: finish Arabic/RTL text implementation +13) Tools: text tool: implicitly draw (text) w/ bg -1, toggle drawing actual brushColours[1] mode w/ RMB vim:ff=dos tw=0 diff --git a/assets/text/spoke-arablion.txt b/assets/text/spoke-arablion.txt index 9121265..469cf6c 100644 --- a/assets/text/spoke-arablion.txt +++ b/assets/text/spoke-arablion.txt @@ -1,17 +1,17 @@ - 3,6β–Ÿ6,3▝ - 3,6β–Ÿ6,3β–œβ–›3,6β–Ÿ6,3▝ - 3,6β–Ÿ6,3β–β–œ3,6β–Ÿ6,3β–β–œβ–›3,6β–Ÿ6,3▝ - 3,6β–Ÿ6,3▝3,8/\3,6β–Ÿ6,3β–œβ–›3,6β–Ÿ6,3▝3,8/\3,6β–Ÿ6,3▝ - 9,1/\ 9,1/ 6,3β–β–œβ–›3,6β–Ÿ6,3▝8,8 3,6β–Ÿ6,3β–β–œβ–›3,6β–Ÿ6,3▝ -6,1(0o9 6) 6,1( 6,3β–œβ–›3,6β–Ÿ6,3▝8,8 3o _ o8 3,6β–Ÿ6,3β–β–œβ–›3,6β–Ÿ - 9,1( \ 9,1) 6,3β–β–œβ–›β–œ8,8 6(_3Y6_)6,3▛▝▛3,6β–Ÿ6,3▝ - 6,1|(__)/ 3,6β–Ÿ6,3β–β–œβ–›8,8 6\_/6,3β–œβ–›3,6β–Ÿ6,3▝ - 1,3\\ 1,3.'0s3 6β–œβ–›3,6β–Ÿ6,3▝ - 1,6\\ 1,6/6 0p6 1\ \ 6,3β–œβ–› - 1,3\\/3 0o3 1\3 1\ \6β–œ - 1,6\6 0k6 1/)___|_|6,3β–› - 1,3(_0e1__/__))) )))6β–› - 12,1β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•² - 12,1β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•± - 2,1β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•± - 2,1β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•² + 3,6β–Ÿ6,3▝15 + 3,6β–Ÿ6,3β–œβ–›3,6β–Ÿ6,3▝15 + 3,6β–Ÿ6,3β–β–œ3,6β–Ÿ6,3β–β–œβ–›3,6β–Ÿ6,3▝15 + 3,6β–Ÿ6,3▝3,8/\3,6β–Ÿ6,3β–œβ–›3,6β–Ÿ6,3▝3,8/\3,6β–Ÿ6,3▝15 + 9,1/\15 9,1/15 6,3β–β–œβ–›3,6β–Ÿ6,3▝8,8 3,6β–Ÿ6,3β–β–œβ–›3,6β–Ÿ6,3▝15 +6,1(0o9 6)15 6,1(15 6,3β–œβ–›3,6β–Ÿ6,3▝8,8 3o _ o8 3,6β–Ÿ6,3β–β–œβ–›3,6β–Ÿ + 9,1( \15 9,1)15 6,3β–β–œβ–›β–œ8,8 6(_3Y6_)6,3▛▝▛3,6β–Ÿ6,3▝15 + 6,1|(__)/15 3,6β–Ÿ6,3β–β–œβ–›8,8 6\_/6,3β–œβ–›3,6β–Ÿ6,3▝15 + 1,3\\15 1,3.'0s3 6β–œβ–›3,6β–Ÿ6,3▝15 + 1,6\\15 1,6/6 0p6 1\ \ 6,3β–œβ–›15 + 1,3\\/3 0o3 1\3 1\ \6β–œ15 + 1,6\6 0k6 1/)___|_|6,3β–›15 + 1,3(_0e1__/__))) )))6β–›15 + 12,1β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²15 + 12,1β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±15 + 2,1β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±β•±15 + 2,1β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²β•²15 diff --git a/assets/tools/AnsiToMiRCART.py b/assets/tools/AnsiToMiRCART.py new file mode 100755 index 0000000..fe6d5a7 --- /dev/null +++ b/assets/tools/AnsiToMiRCART.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# +# AnsiToMiRCART.py -- convert ANSI to mIRC art file (for spoke) +# Copyright (c) 2019 Lucio AndrΓ©s Illanes Albornoz +# This project is licensed under the terms of the MIT licence. +# + +import os, sys +[sys.path.append(os.path.join(os.getcwd(), "..", "..", path)) for path in ["libcanvas", "librtl"]] + +from CanvasExportStore import CanvasExportStore +from CanvasImportStore import CanvasImportStore + +# +# Entry point +def main(*argv): + if (len(sys.argv) - 1) != 2: + print("usage: {} ".format(sys.argv[0]), file=sys.stderr) + else: + canvasImportStore = CanvasImportStore() + rc, error = canvasImportStore.importAnsiFile(argv[1]) + if rc: + canvasExportStore = CanvasExportStore() + with open(argv[2], "w", encoding="utf-8") as outFile: + canvasExportStore.exportTextFile(canvasImportStore.outMap, canvasImportStore.inSize, outFile) + else: + print("error: {}".format(error), file=sys.stderr) +if __name__ == "__main__": + main(*sys.argv) + +# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/assets/tools/deploy-python.sh b/assets/tools/deploy-python.sh index 3195699..c161dc4 100755 --- a/assets/tools/deploy-python.sh +++ b/assets/tools/deploy-python.sh @@ -32,6 +32,9 @@ deploy() { -not -path '*/__pycache__/*' \ -not -path '*/__pycache__' \ -not -path './librtl/ImgurApiKey.py' \ + -not -name '*.exp' \ + -not -name '*.lib' \ + -not -name '*.obj' \ -not -name '*.sw*' \ -not -name "${0##*/}" |\ cpio --quiet -dLmp "${_release_dname}"; diff --git a/libcanvas/Canvas.py b/libcanvas/Canvas.py index aa00fdc..5a790ca 100644 --- a/libcanvas/Canvas.py +++ b/libcanvas/Canvas.py @@ -6,7 +6,6 @@ from CanvasExportStore import CanvasExportStore from CanvasImportStore import CanvasImportStore -from CanvasJournal import CanvasJournal class Canvas(): def _commitPatch(self, patch): @@ -18,11 +17,51 @@ class Canvas(): else: patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell]; if commitUndo: - self.journal.updateCurrentDeltas(patch, patchDelta) + self.updateCurrentDeltas(patch, patchDelta) self._commitPatch(patch) return True - def resize(self, newSize, commitUndo=True): + def begin(self): + deltaItem = [[], []]; self.patchesUndo.insert(self.patchesUndoLevel, deltaItem); + + def end(self): + if self.patchesUndo[self.patchesUndoLevel] == [[], []]: + del self.patchesUndo[self.patchesUndoLevel] + else: + if self.patchesUndoLevel > 0: + del self.patchesUndo[:self.patchesUndoLevel]; self.patchesUndoLevel = 0; + + def popCursor(self, reset=True): + patchesCursor = [] + if len(self.patchesCursor): + patchesCursor = self.patchesCursor + if reset: + self.resetCursor() + return patchesCursor + + def popUndo(self, redo=False): + patches = [] + if not redo: + if self.patchesUndo[self.patchesUndoLevel] != None: + patches = self.patchesUndo[self.patchesUndoLevel][0]; self.patchesUndoLevel += 1; + else: + if self.patchesUndoLevel > 0: + self.patchesUndoLevel -= 1; patches = self.patchesUndo[self.patchesUndoLevel][1]; + return patches + + def pushCursor(self, patches): + self.patchesCursor = patches + + def resetCursor(self): + self.patchesCursor = [] + + def resetUndo(self): + if self.patchesUndo != None: + self.patchesUndo.clear() + self.patchesUndo = [None]; self.patchesUndoLevel = 0; + + def resize(self, brushColours, newSize, commitUndo=True): + newCells = [] if newSize != self.size: if self.map == None: self.map, oldSize = [], [0, 0] @@ -30,41 +69,43 @@ class Canvas(): oldSize = self.size deltaSize = [b - a for a, b in zip(oldSize, newSize)] if commitUndo: - self.journal.begin() + self.begin() undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize] - self.journal.updateCurrentDeltas(redoPatches, undoPatches) + self.updateCurrentDeltas(redoPatches, undoPatches) if deltaSize[0] < 0: for numRow in range(oldSize[1]): if commitUndo: for numCol in range((oldSize[0] + deltaSize[0]), oldSize[0]): - self.journal.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]]) + self.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]]) del self.map[numRow][-1:(deltaSize[0]-1):-1] else: for numRow in range(oldSize[1]): - self.map[numRow].extend([[1, 1, 0, " "]] * deltaSize[0]) + self.map[numRow].extend([[*brushColours, 0, " "]] * deltaSize[0]) for numNewCol in range(oldSize[0], newSize[0]): if commitUndo: - self.journal.updateCurrentDeltas([numNewCol, numRow, 1, 1, 0, " "], None) - self.applyPatch([numNewCol, numRow, 1, 1, 0, " "], False) + self.updateCurrentDeltas([numNewCol, numRow, *brushColours, 0, " "], None) + newCells += [[numNewCol, numRow, *brushColours, 0, " "]] + self.applyPatch([numNewCol, numRow, *brushColours, 0, " "], False) if deltaSize[1] < 0: if commitUndo: for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]): for numCol in range(oldSize[0] + deltaSize[0]): - self.journal.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]]) + self.updateCurrentDeltas(None, [numCol, numRow, *self.map[numRow][numCol]]) del self.map[-1:(deltaSize[1]-1):-1] else: for numNewRow in range(oldSize[1], newSize[1]): - self.map.extend([[[1, 1, 0, " "]] * newSize[0]]) + self.map.extend([[[*brushColours, 0, " "]] * newSize[0]]) for numNewCol in range(newSize[0]): if commitUndo: - self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None) - self.applyPatch([numNewCol, numNewRow, 1, 1, 0, " "], False) + self.updateCurrentDeltas([numNewCol, numNewRow, *brushColours, 0, " "], None) + newCells += [[numNewCol, numNewRow, *brushColours, 0, " "]] + self.applyPatch([numNewCol, numNewRow, *brushColours, 0, " "], False) self.size = newSize if commitUndo: - self.journal.end() - return True + self.end() + return True, newCells else: - return False + return False, newCells def update(self, newSize, newCanvas=None): for numRow in range(self.size[1]): @@ -73,7 +114,15 @@ class Canvas(): and (numRow < len(newCanvas)) and (numCol < len(newCanvas[numRow])): self._commitPatch([numCol, numRow, *newCanvas[numRow][numCol]]) + def updateCurrentDeltas(self, redoPatches, undoPatches): + self.patchesUndo[self.patchesUndoLevel][0].append(undoPatches) + self.patchesUndo[self.patchesUndoLevel][1].append(redoPatches) + + def __del__(self): + self.resetCursor(); self.resetUndo(); + def __init__(self, size): - self.exportStore, self.importStore, self.journal, self.map, self.size = CanvasExportStore(), CanvasImportStore(), CanvasJournal(), None, size + self.exportStore, self.importStore, self.map, self.size = CanvasExportStore(), CanvasImportStore(), None, size + self.patchesCursor, self.patchesUndo, self.patchesUndoLevel = [], [None], 0 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libcanvas/CanvasJournal.py b/libcanvas/CanvasJournal.py deleted file mode 100644 index 2ac1486..0000000 --- a/libcanvas/CanvasJournal.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -# -# CanvasJournal.py -# Copyright (c) 2018, 2019 Lucio AndrΓ©s Illanes Albornoz -# - -class CanvasJournal(): - def begin(self): - deltaItem = [[], []]; self.patchesUndo.insert(self.patchesUndoLevel, deltaItem); - - def end(self): - if self.patchesUndo[self.patchesUndoLevel] == [[], []]: - del self.patchesUndo[self.patchesUndoLevel] - else: - if self.patchesUndoLevel > 0: - del self.patchesUndo[:self.patchesUndoLevel]; self.patchesUndoLevel = 0; - - def popCursor(self, reset=True): - if len(self.patchesCursor): - patchesCursor = self.patchesCursor - if reset: - self.resetCursor() - return patchesCursor - else: - return [] - - def popRedo(self): - if self.patchesUndoLevel > 0: - self.patchesUndoLevel -= 1; patches = self.patchesUndo[self.patchesUndoLevel]; - return patches[1] - else: - return [] - - def popUndo(self): - if self.patchesUndo[self.patchesUndoLevel] != None: - patches = self.patchesUndo[self.patchesUndoLevel]; self.patchesUndoLevel += 1; - return patches[0] - else: - return [] - - def pushCursor(self, patches): - self.patchesCursor = patches - - def resetCursor(self): - self.patchesCursor = [] - - def resetUndo(self): - if self.patchesUndo != None: - self.patchesUndo.clear() - self.patchesUndo = [None]; self.patchesUndoLevel = 0; - - def updateCurrentDeltas(self, redoPatches, undoPatches): - self.patchesUndo[self.patchesUndoLevel][0].append(undoPatches) - self.patchesUndo[self.patchesUndoLevel][1].append(redoPatches) - - def __del__(self): - self.resetCursor(); self.resetUndo(); - - def __init__(self): - self.patchesUndo = None; self.resetCursor(); self.resetUndo(); - -# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libgui/GuiCanvasWxBackend.py b/libgui/GuiCanvasWxBackend.py index 84bf417..31b5eda 100644 --- a/libgui/GuiCanvasWxBackend.py +++ b/libgui/GuiCanvasWxBackend.py @@ -4,10 +4,14 @@ # Copyright (c) 2018, 2019 Lucio AndrΓ©s Illanes Albornoz # -from ctypes import * +try: + import GuiCanvasWxBackendFast; haveGuiCanvasWxBackendFast = True; +except ImportError as e: + print("Failed to import GuiCanvasWxBackendFast: {}".format(e)); haveGuiCanvasWxBackendFast = False; + +from ctypes import WinDLL from GuiCanvasColours import Colours -import GuiCanvasWxBackendFast -import math, os, platform, wx +import math, os, platform, Rtl, wx class GuiBufferedDC(wx.MemoryDC): def __del__(self): @@ -143,20 +147,7 @@ class GuiCanvasWxBackend(): brush, pen = self._brushesBlend[bg][14], self._pensBlend[bg][14] return brush, pen - def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc, reset=True): - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - cursorPatches = canvasJournal.popCursor(reset=reset); patches = []; - for cursorCell in [p[:2] for p in cursorPatches]: - if (cursorCell[0] < canvas.size[0]) \ - and (cursorCell[1] < canvas.size[1]): - patches += [[*cursorCell, *canvas.map[cursorCell[1]][cursorCell[0]]]] - if len(patches) > 0: - self.drawPatches(canvas, eventDc, patches, False) - eventDc.SetDeviceOrigin(*eventDcOrigin) - return cursorPatches - def drawPatches(self, canvas, eventDc, patches, isCursor=False): - GuiCanvasWxBackendFast.drawPatches() patchesRender = [] for patch in patches: point = patch[:2] @@ -166,6 +157,8 @@ class GuiCanvasWxBackend(): patchesRender += [patchReshaped] else: patchesRender += [patch] + if haveGuiCanvasWxBackendFast: + GuiCanvasWxBackendFast.drawPatches(self.canvasBitmap, canvas.map, canvas.size, eventDc, isCursor, patchesRender); return; numPatch, textBg = 0, wx.Colour(0, 0, 0, 0) rectangles, pens, brushes = [None] * len(patchesRender), [None] * len(patchesRender), [None] * len(patchesRender) textList, coords, foregrounds, backgrounds = [], [], [], [] @@ -173,10 +166,10 @@ class GuiCanvasWxBackend(): for patchRender in patchesRender: if (patchRender[5] == " ") and (patchRender[3] == -1): text, textFg = "β–‘", wx.Colour(0, 0, 0, 255) - elif isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][3] != " "): + elif False and isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][3] != " "): patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]] text, textFg = canvas.map[patchRender[1]][patchRender[0]][3], wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3])) - elif isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE): + elif False and isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE): patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]] text, textFg = "_", wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3])) elif patchRender[5] != " ": @@ -228,9 +221,11 @@ class GuiCanvasWxBackend(): oldDc = wx.MemoryDC(); oldDc.SelectObject(self.canvasBitmap); newDc = wx.MemoryDC(); newBitmap = wx.Bitmap(winSize); newDc.SelectObject(newBitmap); newDc.Blit(0, 0, *self.canvasBitmap.GetSize(), oldDc, 0, 0) - oldDc.SelectObject(wx.NullBitmap) + oldDc.SelectObject(wx.NullBitmap); newDc.SelectObject(wx.NullBitmap); self.canvasBitmap.Destroy(); self.canvasBitmap = newBitmap; - self.canvasSize = canvasSize + self.canvasSize = canvasSize; + if haveGuiCanvasWxBackendFast: + GuiCanvasWxBackendFast.resize(tuple(self.cellSize), self._font, tuple(winSize)) def xlateEventPoint(self, event, eventDc, viewRect): eventPoint = event.GetLogicalPosition(eventDc) @@ -244,6 +239,8 @@ class GuiCanvasWxBackend(): self._finiBrushesAndPens() def __init__(self, canvasSize, fontName="Dejavu Sans Mono", fontPathName=os.path.join("assets", "fonts", "DejaVuSansMono.ttf"), fontSize=8): + if haveGuiCanvasWxBackendFast: + GuiCanvasWxBackendFast.init(wx) self._brushes, self._font, self._lastBrush, self._lastPen, self._pens = None, None, None, None, None self.canvasBitmap, self.cellSize, self.fontName, self.fontPathName, self.fontSize = None, None, fontName, fontPathName, fontSize if platform.system() == "Windows": diff --git a/libgui/GuiCanvasWxBackendFast.c b/libgui/GuiCanvasWxBackendFast.c deleted file mode 100644 index 97c617c..0000000 --- a/libgui/GuiCanvasWxBackendFast.c +++ /dev/null @@ -1,25 +0,0 @@ -#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ -#include - -static PyObject * -GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args) -{ - Py_RETURN_NONE; -} - -static PyMethodDef -GuiCanvasWxBackendFast_methods[] = { - {"drawPatches", GuiCanvasWxBackendFast_drawPatches, METH_VARARGS, "XXX"}, - {NULL, NULL, 0, NULL}, -}; - -static struct PyModuleDef -GuiCanvasWxBackendFastmodule = { - PyModuleDef_HEAD_INIT, "GuiCanvasWxBackendFast", NULL, -1, GuiCanvasWxBackendFast_methods, -}; - -PyMODINIT_FUNC -PyInit_GuiCanvasWxBackendFast(void) -{ - return PyModule_Create(&GuiCanvasWxBackendFastmodule); -} \ No newline at end of file diff --git a/libgui/GuiCanvasWxBackendFast.cpp b/libgui/GuiCanvasWxBackendFast.cpp new file mode 100644 index 0000000..c82f6ec --- /dev/null +++ b/libgui/GuiCanvasWxBackendFast.cpp @@ -0,0 +1,577 @@ +/* + * GuiCanvasWxBackendFast.cpp + * Copyright (c) 2019 Lucio AndrΓ©s Illanes Albornoz + */ + +#ifdef _MSC_VER +#pragma warning(disable : 4514) +#pragma warning(disable : 4530) +#pragma warning(disable : 4577) +#pragma warning(disable : 4706) +#pragma warning(disable : 4710) +#pragma warning(disable : 4711) +#pragma warning(disable : 4820) +#pragma warning(disable : 5045) +#endif /* _MSC_VER_ */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ +#include + +/* + * Private types + */ + +typedef uint32_t COLOUR; +#define COLOUR_ALPHA(colour) (((colour) >> 24) & 0xff) + +typedef uint64_t COORD; + +typedef struct point_s { + COORD x, y; +} POINT; +#define POINT_EMPTY {0ULL, 0ULL} + +typedef enum cell_attrs_e { + CATTR_NONE = 0x00, + CATTR_BOLD = 0x01, + CATTR_UNDERLINE = 0x02, +} CELL_ATTRS; + +typedef struct cell_s { + CELL_ATTRS attrs; + COLOUR bg, fg; + POINT p; + wchar_t txt[8]; +} CELL; +#define CELL_EMPTY {CATTR_NONE, 0UL, 0UL, POINT_EMPTY, {L'\0',}} + +typedef struct rect_s { + POINT p0, p1; +} RECT; +#define RECT_EMPTY {POINT_EMPTY, POINT_EMPTY} +#define RECT_HEIGHT(r) ((r).p1.y - (r).p0.y) +#define RECT_WIDTH(r) ((r).p1.x - (r).p0.x) + +typedef struct size_s { + uint64_t h, w; +} SIZE; +#define SIZE_EMPTY {0ULL, 0ULL} + +typedef std::vector> COLOUR_LIST; +typedef std::vector> CHAR_MAP_ITEM; +typedef std::map CHAR_MAP; + +/* + * Private constants and variables + */ + +#define BITMAP_BPS 24 +#define BITMAP_BPS_BYTES 3 +#define BLEND_ALPHA_COEFFICIENT 0.8 + +static PyObject *s_bitmap = NULL, *s_dc = NULL, *s_dc_tmp = NULL, *s_font = NULL, *s_wx = NULL, *s_wx_NullBitmap = NULL; +static uint8_t *s_bitmap_buffer = NULL; +static size_t s_bitmap_buffer_size = 0; +static SIZE s_bitmap_size = SIZE_EMPTY, s_cell_size = SIZE_EMPTY; +static CHAR_MAP s_char_map; +static PyObject *s_colour_black = NULL, *s_colour_white = NULL; +static PyObject *s_error = NULL; + +static COLOUR_LIST s_colours = { + {255, 255, 255}, // Bright White + {0, 0, 0}, // Black + {0, 0, 187}, // Light Blue + {0, 187, 0}, // Green + {255, 85, 85}, // Red + {187, 0, 0}, // Light Red + {187, 0, 187}, // Pink + {187, 187, 0}, // Yellow + {255, 255, 85}, // Light Yellow + {85, 255, 85}, // Light Green + {0, 187, 187}, // Cyan + {85, 255, 255}, // Light Cyan + {85, 85, 255}, // Blue + {255, 85, 255}, // Light Pink + {85, 85, 85}, // Grey + {187, 187, 187}, // Light Grey +}; + +static COLOUR_LIST s_colours_bold = { + {255, 255, 255}, // Bright White + {85, 85, 85}, // Black + {85, 85, 255}, // Light Blue + {85, 255, 85}, // Green + {255, 85, 85}, // Red + {255, 85, 85}, // Light Red + {255, 85, 255}, // Pink + {255, 255, 85}, // Yellow + {255, 255, 85}, // Light Yellow + {85, 255, 85}, // Light Green + {85, 255, 255}, // Cyan + {85, 255, 255}, // Light Cyan + {85, 85, 255}, // Blue + {255, 85, 255}, // Light Pink + {85, 85, 85}, // Grey + {255, 255, 255}, // Light Grey +}; + +/* + * Private preprocessor macros + */ + +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) + +#define PYTHON_TRY(expr, msg) \ + [&](){bool rc = (bool)(expr); if (!rc) { PyErr_SetString(s_error, msg); }; return rc;}() +#define PYTHON_TRY_NOMEMORY(expr, msg) \ + [&](){bool rc = (bool)(expr); if (!rc) { PyErr_SetString(PyExc_MemoryError, msg); }; return rc;}() + +/* + * N.B. required due to absence of Python_CallMethodV() + */ +#define COMMA , +#define PYTHON_WRAP_METHOD(fn, fmt, args1, args2) \ + static bool \ + python_##fn(PyObject *obj, const char *default_error, PyObject **presult, args1) \ + { \ + bool rc = true; \ + PyObject *result; \ + \ + if ((result = PyObject_CallMethod(obj, #fn, fmt, args2))) { \ + if (!presult) { \ + Py_XDECREF(result); \ + } else { \ + *presult = result; \ + } \ + } else { \ + rc = false; \ + setErrorFromLast(default_error ? default_error \ + : "Failed to call " # fn "()"); \ + } \ + return rc; \ + } +#define PYTHON_WRAP_METHOD0(fn) \ + static bool \ + python_##fn(PyObject *obj, const char *default_error, PyObject **presult) \ + { \ + bool rc = true; \ + PyObject *result; \ + \ + if ((result = PyObject_CallMethod(obj, #fn, ""))) { \ + if (!presult) { \ + Py_XDECREF(result); \ + } else { \ + *presult = result; \ + } \ + } else { \ + rc = false; \ + setErrorFromLast(default_error ? default_error \ + : "Failed to call " # fn "()"); \ + } \ + return rc; \ + } + +/* + * Static private subroutine prototypes + */ + +static COLOUR blendColours(COLOUR bg, COLOUR fg); +static void cellDraw(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, RECT *prect); +static bool cellDrawPixel(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, COLOUR colour, RECT *prect, COORD rx, COORD ry); +static bool cellDrawText(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, CHAR_MAP& char_map, RECT *prect); +static bool cellFetch(const COLOUR_LIST& colours, const COLOUR_LIST& colours_bold, PyObject *object, bool fromCanvas, POINT canvasPoint, CELL *pcell); +static void setErrorFromLast(const char *default_fmt, ...); +#ifdef TIMING +static std::chrono::system_clock::time_point timeBegin(); +static double timeDelta(std::chrono::system_clock::time_point t0); +#endif /* TIMING */ +static bool updateCharMap(SIZE cell_size, CHAR_MAP& char_map, wchar_t wch); + +PYTHON_WRAP_METHOD(Bitmap, "lll", unsigned long long width COMMA unsigned long long height COMMA unsigned long long bits, width COMMA height COMMA bits); +PYTHON_WRAP_METHOD(Blit, "OllllOll", PyObject *self COMMA unsigned long long xdest COMMA unsigned long long ydest COMMA unsigned long long width COMMA unsigned long long height COMMA PyObject *source COMMA unsigned long long xsrc COMMA unsigned long long ysrc, self COMMA xdest COMMA ydest COMMA width COMMA height COMMA source COMMA xsrc COMMA ysrc); +PYTHON_WRAP_METHOD(Colour, "lll", unsigned long long red COMMA unsigned long long green COMMA unsigned long long blue, red COMMA green COMMA blue); +PYTHON_WRAP_METHOD(CopyFromBuffer, "Ol", PyObject *data COMMA unsigned long long format, data COMMA format); +PYTHON_WRAP_METHOD(CopyToBuffer, "Ol", PyObject *data COMMA unsigned long long format, data COMMA format); +PYTHON_WRAP_METHOD(DrawText, "u#ll", wchar_t *text COMMA size_t text_size COMMA unsigned long long x COMMA unsigned long long y, text COMMA text_size COMMA x COMMA y); +PYTHON_WRAP_METHOD0(MemoryDC); +PYTHON_WRAP_METHOD(SelectObject, "O", PyObject *bitmap, bitmap); +PYTHON_WRAP_METHOD(SetFont, "O", PyObject *font, font); +PYTHON_WRAP_METHOD(SetTextBackground, "O", PyObject *colour, colour); +PYTHON_WRAP_METHOD(SetTextForeground, "O", PyObject *colour, colour); + +/* + * Static private subroutines + */ + +static COLOUR +blendColours(COLOUR bg, COLOUR fg) +{ + return (COLOUR) + (std::llround(((fg & 0xff) * BLEND_ALPHA_COEFFICIENT) + ((bg & 0xff) * (1.0 - BLEND_ALPHA_COEFFICIENT))) | + (std::llround((((fg >> 8) & 0xff) * BLEND_ALPHA_COEFFICIENT) + (((bg >> 8) & 0xff) * (1.0 - BLEND_ALPHA_COEFFICIENT))) << 8) | + (std::llround((((fg >> 16) & 0xff) * BLEND_ALPHA_COEFFICIENT) + (((bg >> 16) & 0xff) * (1.0 - BLEND_ALPHA_COEFFICIENT))) << 16)); +} + +static void +cellDraw(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, RECT *prect) +{ + for (COORD ry = 0; ry < cell_size.h; ry++) { + for (COORD rx = 0; rx < cell_size.w; rx++) + cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, cell.bg, prect, rx, ry); + } + if (cell.attrs & CATTR_UNDERLINE) { + for (COORD rx = 0, ry = (cell_size.h - 1); rx < cell_size.w; rx++) + cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, cell.fg, prect, rx, ry); + } +} + +static bool +cellDrawPixel(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, COLOUR colour, RECT *prect, COORD rx, COORD ry) +{ + COORD offset, x_, y_; + bool rc = false; + + x_ = (cell.p.x * cell_size.w) + rx, y_ = (cell.p.y * cell_size.h) + ry; + offset = ((y_ * bitmap_size.w) + x_) * bitmap_bps_bytes; + if ((x_ < bitmap_size.w) && (y_ < bitmap_size.h)) { + prect->p0.x = MIN(prect->p0.x > 0 ? prect->p0.x : x_, x_); + prect->p0.y = MIN(prect->p0.y > 0 ? prect->p0.y : y_, y_); + prect->p1.x = MAX(prect->p1.x, x_+ 1); prect->p1.y = MAX(prect->p1.y, y_+ 1); + bitmap_buffer[offset] = colour & 0xff; + bitmap_buffer[offset + 1] = (colour >> 8) & 0xff; + bitmap_buffer[offset + 2] = (colour >> 16) & 0xff; + rc = true; + } + return rc; +} + +static bool +cellDrawText(size_t bitmap_bps_bytes, uint8_t *bitmap_buffer, SIZE bitmap_size, CELL cell, SIZE cell_size, CHAR_MAP& char_map, RECT *prect) +{ + CHAR_MAP::iterator char_map_item; + COLOUR colour; + bool rc = true; + + for (size_t nch = 0; (nch < (sizeof(cell.txt) / sizeof(cell.txt[0]))) && (cell.txt[nch]); nch++) { + if ((char_map_item = char_map.find(cell.txt[nch])) == char_map.end()) { + if (updateCharMap(cell_size, char_map, cell.txt[nch])) + char_map_item = char_map.find(cell.txt[nch]); + else { + rc = false; break; + } + } + for (COORD ry = 0; ry < cell_size.h; ry++) { + for (COORD rx = 0; rx < cell_size.w; rx++) { + if ((char_map_item != char_map.end()) + && (char_map_item->second[ry][rx] != (COLOUR)0x0L)) + colour = cell.fg; + else + colour = cell.bg; + cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, colour, prect, rx, ry); + } + } + } + if (cell.attrs & CATTR_UNDERLINE) { + for (COORD rx = 0, ry = (cell_size.h - 1); rx < cell_size.w; rx++) + cellDrawPixel(bitmap_bps_bytes, bitmap_buffer, bitmap_size, cell, cell_size, cell.fg, prect, rx, ry); + } + return rc; +} + +static bool +cellFetch(const COLOUR_LIST& colours, const COLOUR_LIST& colours_bold, PyObject *object, bool fromCanvas, POINT canvasPoint, CELL *pcell) +{ + long bg, fg; + PyObject *canvasMapRow, *cellObject = NULL, *txt; + Py_ssize_t offset, txt_len; + const COLOUR_LIST *pcolours; + bool rc = false; + + if (fromCanvas) { + offset = -2; + if ((canvasMapRow = PyList_GetItem(object, (Py_ssize_t)canvasPoint.y))) + cellObject = PyList_GetItem(canvasMapRow, (Py_ssize_t)canvasPoint.x); + } else + cellObject = object, offset = 0; + if (cellObject && PyList_Check(cellObject) && (PyList_Size(cellObject) == 6 + offset)) { + if (!fromCanvas) { + pcell->p.x = PyLong_AsUnsignedLongLong(PyList_GetItem(cellObject, 0)); + pcell->p.y = PyLong_AsUnsignedLongLong(PyList_GetItem(cellObject, 1)); + } + fg = PyLong_AsLong(PyList_GetItem(cellObject, 2 + offset)); + bg = PyLong_AsLong(PyList_GetItem(cellObject, 3 + offset)); + pcell->attrs = (CELL_ATTRS)PyLong_AsUnsignedLong(PyList_GetItem(cellObject, 4 + offset)); + if (pcell->attrs & CATTR_BOLD) + pcolours = &colours_bold; + else + pcolours = &colours; + pcell->bg = (bg == -1) ? 0xff000000 : (COLOUR)((colours[(uint8_t)bg][0]) | ((colours[(uint8_t)bg][1] << 8) & 0xff00) | ((colours[(uint8_t)bg][2] << 16) & 0xff0000)); + pcell->fg = (fg == -1) ? pcell->bg : (COLOUR)(((*pcolours)[(uint8_t)fg][0]) | (((*pcolours)[(uint8_t)fg][1] << 8) & 0xff00) | (((*pcolours)[(uint8_t)fg][2] << 16) & 0xff0000)); + txt = PyList_GetItem(cellObject, 5 + offset); + if ((txt_len = PyUnicode_AsWideChar(txt, pcell->txt, sizeof(pcell->txt) / sizeof(pcell->txt[0]))) > 0) { + if (txt_len < (sizeof(pcell->txt) / sizeof(pcell->txt[0]))) + pcell->txt[txt_len] = L'\0'; + rc = true; + } + } + return rc; +} + +static void +setErrorFromLast(const char *default_fmt, ...) +{ + va_list ap; + static char default_buf[1024]; + PyObject *exc_traceback, *exc_type, *exc_value = NULL; + + PyErr_Fetch(&exc_type, &exc_value, &exc_traceback); + if (exc_value) + PyErr_SetObject(s_error, exc_value); + else { + va_start(ap, default_fmt); + vsnprintf_s(default_buf, sizeof(default_buf), default_fmt, ap); + va_end(ap); + PyErr_SetString(s_error, default_buf); + } +} + +#ifdef TIMING +static std::chrono::system_clock::time_point +timeBegin() +{ + return std::chrono::system_clock::now(); +} + +static double +timeDelta(std::chrono::system_clock::time_point t0) +{ + return ((std::chrono::duration)(std::chrono::system_clock::now() - t0)).count(); +} +#endif /* TIMING */ + +static bool +updateCharMap(SIZE cell_size, CHAR_MAP& char_map, wchar_t wch) +{ + PyObject *bitmap, *mv = NULL; + Py_buffer buffer; + uint8_t *char_buffer = NULL; + size_t char_buffer_size; + bool rc = false; + + PyBuffer_FillInfo(&buffer, 0, NULL, 0, false, PyBUF_WRITABLE); + char_buffer_size = s_cell_size.w * s_cell_size.h * BITMAP_BPS_BYTES; + if (python_Bitmap(s_wx, NULL, &bitmap, s_cell_size.w, s_cell_size.h, BITMAP_BPS) + && python_SelectObject(s_dc_tmp, NULL, NULL, bitmap) + && python_SetFont(s_dc_tmp, NULL, NULL, s_font) + && python_SetTextBackground(s_dc_tmp, NULL, NULL, s_colour_black) + && python_SetTextForeground(s_dc_tmp, NULL, NULL, s_colour_white) + && python_DrawText(s_dc_tmp, NULL, NULL, &wch, 1, 0, 0) + && PYTHON_TRY_NOMEMORY((char_buffer = (uint8_t *)malloc(char_buffer_size)), "Failed to allocate character bitmap buffer") + && PYTHON_TRY(PyBuffer_FillInfo(&buffer, 0, char_buffer, (Py_ssize_t)char_buffer_size, false, PyBUF_WRITABLE) == 0, "Failed to create Py_buffer") + && PYTHON_TRY((mv = PyMemoryView_FromBuffer(&buffer)), "Failed to create Py_buffer memory view") + && python_CopyToBuffer(bitmap, NULL, NULL, mv, 0)) { + char_map[wch] = CHAR_MAP_ITEM(cell_size.h); + for (COORD ry = 0; ry < cell_size.h; ry++) { + for (COORD rx = 0; rx < cell_size.w; rx++) + char_map[wch][ry].push_back( + (((COLOUR)char_buffer[(((ry * cell_size.w) + rx) * BITMAP_BPS_BYTES)]) & 0xff) | + (((COLOUR)char_buffer[(((ry * cell_size.w) + rx) * BITMAP_BPS_BYTES) + 1] << 8) & 0xff00) | + (((COLOUR)char_buffer[(((ry * cell_size.w) + rx) * BITMAP_BPS_BYTES) + 2] << 16) & 0xff0000)); + } + rc = true; + } + if (s_dc_tmp) + python_SelectObject(s_dc_tmp, NULL, NULL, s_wx_NullBitmap); + Py_XDECREF(bitmap); + if (char_buffer) { + free(char_buffer); + } + Py_XDECREF(mv); PyBuffer_Release(&buffer); + return rc; +} + +/* + * Private Python module subroutine prototypes + */ + +static PyObject *GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args); +static PyObject *GuiCanvasWxBackendFast_init(PyObject *self, PyObject *args); +static PyObject *GuiCanvasWxBackendFast_resize(PyObject *self, PyObject *args); + +/* + * Private Python module subroutines + */ + +static PyObject * +GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args) +{ + PyObject *bitmap, *canvas_map, *canvas_size_obj, *eventDc, *patches; + Py_buffer buffer; + SIZE canvas_size; + CELL cell, cell_canvas; + bool isCursor, skip, status = true; + PyObject *iter, *iter_cur, *mv = NULL, *rc = NULL; + RECT rect = RECT_EMPTY; + + (void)self; + PyBuffer_FillInfo(&buffer, 0, NULL, 0, false, PyBUF_WRITABLE); +#ifdef TIMING + auto t0 = timeBegin(); +#endif /* TIMING */ + if (PYTHON_TRY(PyArg_ParseTuple(args, "OOOOpO", &bitmap, &canvas_map, &canvas_size_obj, &eventDc, &isCursor, &patches), "Invalid arguments") + && PYTHON_TRY((iter = PyObject_GetIter(patches)), "Failed to get patches iterator object")) { + canvas_size.w = PyLong_AsUnsignedLong(PyList_GetItem(canvas_size_obj, 0)); + canvas_size.h = PyLong_AsUnsignedLong(PyList_GetItem(canvas_size_obj, 1)); + while (iter_cur = PyIter_Next(iter)) { + skip = false, status = true; + if (PYTHON_TRY(cellFetch(s_colours, s_colours_bold, iter_cur, false, POINT_EMPTY, &cell), "Failed to get patch cell")) { + if (isCursor) { + if (!(skip = !cellFetch(s_colours, s_colours_bold, canvas_map, true, cell.p, &cell_canvas))) { + cell.attrs = cell_canvas.attrs; + if (COLOUR_ALPHA(cell.bg) != 0xff) { + cell.fg = blendColours(cell_canvas.fg, cell.bg); cell.bg = blendColours(cell_canvas.bg, cell.bg); + if ((cell_canvas.txt[0] == L' ') && (COLOUR_ALPHA(cell_canvas.bg) == 0xff)) + cell.txt[0] = L'\u2591', cell.txt[1] = L'\0'; + else + memcpy(cell.txt, cell_canvas.txt, sizeof(cell.txt)); + } else if (COLOUR_ALPHA(cell_canvas.bg) == 0xff) { + cell.fg = cell_canvas.fg, cell.bg = cell_canvas.bg; + if (cell_canvas.txt[0] == L' ') + cell.txt[0] = L'\u2591', cell.txt[1] = L'\0'; + else + memcpy(cell.txt, cell_canvas.txt, sizeof(cell.txt)); + } else { + cell.fg = cell_canvas.fg, cell.bg = cell_canvas.bg; + memcpy(cell.txt, cell_canvas.txt, sizeof(cell.txt)); + } + } + } else if ((cell.txt[0] == L' ') && (COLOUR_ALPHA(cell.bg) == 0xff)) + cell.bg = 0x00000000, cell.txt[0] = L'\u2591', cell.txt[1] = L'\0'; + if (status && !skip) { + if (cell.txt[0] != L' ') + status = cellDrawText(BITMAP_BPS_BYTES, s_bitmap_buffer, s_bitmap_size, cell, s_cell_size, s_char_map, &rect); + else + cellDraw(BITMAP_BPS_BYTES, s_bitmap_buffer, s_bitmap_size, cell, s_cell_size, &rect); + } + } + Py_XDECREF(iter_cur); + } + Py_XDECREF(iter); + if (status + && PYTHON_TRY(PyBuffer_FillInfo(&buffer, 0, s_bitmap_buffer, (Py_ssize_t)s_bitmap_buffer_size, false, PyBUF_WRITABLE) == 0, "Failed to create Py_buffer") + && PYTHON_TRY((mv = PyMemoryView_FromBuffer(&buffer)), "Failed to create Py_buffer memory view") + && python_CopyFromBuffer(s_bitmap, NULL, NULL, mv, 0) + && python_Blit((PyObject *)eventDc->ob_type, NULL, NULL, eventDc, rect.p0.x, rect.p0.y, RECT_WIDTH(rect), RECT_HEIGHT(rect), s_dc, rect.p0.x, rect.p0.y)) { + Py_INCREF(Py_True), rc = Py_True; + } + } +#ifdef TIMING + printf("drawing took %.2f ms\n", timeDelta(t0) * 1000); +#endif /* TIMING */ + Py_XDECREF(mv); PyBuffer_Release(&buffer); + return rc; +} + +static PyObject * +GuiCanvasWxBackendFast_init(PyObject *self, PyObject *args) +{ + PyObject *colour_black = NULL, *colour_white = NULL, *dc = NULL, *dc_tmp = NULL, *wx = NULL, *wx_NullBitmap = NULL; + PyObject *rc = NULL, *wx_dict; + + (void)self; + if (PYTHON_TRY(PyArg_ParseTuple(args, "O", &wx), "Invalid arguments") + && PYTHON_TRY((wx_dict = PyModule_GetDict(wx)), "Failed to get wx module dictionary") + && python_Colour(wx, NULL, &colour_black, 0, 0, 0) + && python_Colour(wx, NULL, &colour_white, 255, 255, 255) + && python_MemoryDC(wx, NULL, &dc) + && python_MemoryDC(wx, NULL, &dc_tmp) + && PYTHON_TRY((wx_NullBitmap = PyObject_GetAttrString(wx, "NullBitmap")), "Failed to get wx.NullBitmap attribute")) { + s_colour_black = colour_black, s_colour_white = colour_white, s_dc = dc, s_dc_tmp = dc_tmp; + s_wx = wx; Py_INCREF(wx_NullBitmap), s_wx_NullBitmap = wx_NullBitmap; + Py_INCREF(Py_True), rc = Py_True; + } + if (!rc) { + Py_XDECREF(colour_black); Py_XDECREF(colour_white); Py_XDECREF(dc); Py_XDECREF(dc_tmp); Py_XDECREF(wx_NullBitmap); + } + return rc; +} + +static PyObject * +GuiCanvasWxBackendFast_resize(PyObject *self, PyObject *args) +{ + uint8_t *bitmap_buffer_new = NULL; + size_t bitmap_buffer_size_new; + PyObject *bitmap_new = NULL; + SIZE bitmap_size_new, cell_size_new; + PyObject *cellSize, *cellSizeHeightObj, *cellSizeWidthObj, *font, *winSize, *winSizeHeightObj, *winSizeWidthObj, *rc = NULL; + + (void)self; + if (PYTHON_TRY(PyArg_ParseTuple(args, "OOO", &cellSize, &font, &winSize) && PyTuple_Check(cellSize) && PyTuple_Check(winSize), "Invalid arguments")) { + cellSizeWidthObj = PyTuple_GetItem(cellSize, 0); cellSizeHeightObj = PyTuple_GetItem(cellSize, 1); + cell_size_new.w = PyLong_AsUnsignedLong(cellSizeWidthObj); cell_size_new.h = PyLong_AsUnsignedLong(cellSizeHeightObj); + winSizeWidthObj = PyTuple_GetItem(winSize, 0); bitmap_size_new.w = PyLong_AsUnsignedLong(winSizeWidthObj); + winSizeHeightObj = PyTuple_GetItem(winSize, 1); bitmap_size_new.h = PyLong_AsUnsignedLong(winSizeHeightObj); + bitmap_buffer_size_new = bitmap_size_new.h * bitmap_size_new.w * BITMAP_BPS_BYTES; + if (python_Bitmap(s_wx, NULL, &bitmap_new, bitmap_size_new.w, bitmap_size_new.h, BITMAP_BPS) + && (s_bitmap ? python_SelectObject(s_dc, NULL, NULL, s_wx_NullBitmap) : true) + && python_SelectObject(s_dc, NULL, NULL, bitmap_new) + && PYTHON_TRY_NOMEMORY((bitmap_buffer_new = (uint8_t *)malloc(bitmap_buffer_size_new)), "Failed to allocate bitmap buffer")) { + if (s_bitmap_buffer) + free(s_bitmap_buffer); + s_bitmap_buffer = bitmap_buffer_new; + Py_XDECREF(s_bitmap); s_bitmap = bitmap_new; + s_bitmap_buffer_size = bitmap_buffer_size_new, s_bitmap_size = bitmap_size_new; + if ((cell_size_new.h != s_cell_size.h) || (cell_size_new.w != s_cell_size.w)) + s_char_map.clear(); + s_cell_size = cell_size_new; Py_INCREF(font), s_font = font; Py_INCREF(Py_True), rc = Py_True; + } + } + if (!rc) { + if (bitmap_buffer_new) + free(bitmap_buffer_new); + Py_XDECREF(bitmap_new); Py_XDECREF(font); + } + return rc; +} + +/* + * Python C/C++ extension footer + */ + +static PyMethodDef +GuiCanvasWxBackendFast_methods[] = { + {"drawPatches", GuiCanvasWxBackendFast_drawPatches, METH_VARARGS, "drawPatches"}, + {"init", GuiCanvasWxBackendFast_init, METH_VARARGS, "init"}, + {"resize", GuiCanvasWxBackendFast_resize, METH_VARARGS, "resize"}, + {NULL, NULL, 0, NULL}, +}; + +static struct PyModuleDef +GuiCanvasWxBackendFastmodule = { + PyModuleDef_HEAD_INIT, "GuiCanvasWxBackendFast", NULL, -1, GuiCanvasWxBackendFast_methods, +}; + +PyMODINIT_FUNC +PyInit_GuiCanvasWxBackendFast(void) +{ + PyObject *m = NULL; + + m = PyModule_Create(&GuiCanvasWxBackendFastmodule); + s_error = PyErr_NewException("GuiCanvasWxBackendFast.error", NULL, NULL); + Py_XINCREF(s_error); + if (PyModule_AddObject(m, "error", s_error) < 0) { + Py_XDECREF(s_error); Py_CLEAR(s_error); Py_DECREF(m); m = NULL; + } + return m; +} diff --git a/libroar/RoarAssetsWindow.py b/libroar/RoarAssetsWindow.py index 8905997..ad6ffc6 100644 --- a/libroar/RoarAssetsWindow.py +++ b/libroar/RoarAssetsWindow.py @@ -165,7 +165,7 @@ class RoarAssetsWindow(GuiMiniFrame): def resize(self, canvas, newSize): oldSize = [0, 0] if canvas.map == None else canvas.size deltaSize = [b - a for a, b in zip(oldSize, newSize)] - if canvas.resize(newSize, False): + if canvas.resize((1, 1,), newSize, False): panelSize = [a * b for a, b in zip(canvas.size, self.backend.cellSize)] self.panelCanvas.SetMinSize(panelSize); self.panelCanvas.SetSize(wx.DefaultCoord, wx.DefaultCoord, *panelSize); curWindow = self.panelCanvas @@ -228,6 +228,7 @@ class RoarAssetsWindow(GuiMiniFrame): id = +1 if keyCode == wx.WXK_DOWN else -1 self.currentIndex = (self.currentIndex + id) % len(self.canvasList) self.listView.Select(self.currentIndex, on=1) + self.listView.EnsureVisible(self.currentIndex) else: index, rc = self.listView.GetFirstSelected(), False if index != -1: @@ -304,6 +305,8 @@ class RoarAssetsWindow(GuiMiniFrame): break while len(items): self._removeAsset(items[0]); del items[0]; items = [i - 1 for i in items]; + if self.currentIndex != None: + self.listView.EnsureVisible(self.currentIndex) def onSaveList(self, event): rc = True diff --git a/libroar/RoarCanvasCommands.py b/libroar/RoarCanvasCommands.py index 0c87211..ac1a623 100644 --- a/libroar/RoarCanvasCommands.py +++ b/libroar/RoarCanvasCommands.py @@ -5,12 +5,13 @@ # from GuiCanvasColours import Colours -from GuiFrame import NID_TOOLBAR_HSEP +from GuiFrame import NID_MENU_SEP, NID_TOOLBAR_HSEP from RoarCanvasCommandsEdit import RoarCanvasCommandsEdit from RoarCanvasCommandsFile import RoarCanvasCommandsFile from RoarCanvasCommandsHelp import RoarCanvasCommandsHelp from RoarCanvasCommandsOperators import RoarCanvasCommandsOperators from RoarCanvasCommandsTools import RoarCanvasCommandsTools +from ToolObject import ToolObject import os, wx class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCanvasCommandsTools, RoarCanvasCommandsOperators, RoarCanvasCommandsHelp): @@ -40,6 +41,65 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan _initColourBitmaps_(RoarCanvasCommandsEdit.canvasColour, RoarCanvasCommandsEdit.canvasColourAlpha, 1.0) _initColourBitmaps_(RoarCanvasCommandsEdit.canvasColourBackground, RoarCanvasCommandsEdit.canvasColourAlphaBackground, 1.5) + def _initInterface(self): + accels = () + menus = ( + ("&File", + self.canvasNew, self.canvasOpen, self.canvasOpenRecent, self.canvasRestore, self.canvasSave, self.canvasSaveAs, NID_MENU_SEP, + ("&Export...", self.canvasExportAsAnsi, self.canvasExportToClipboard, self.canvasExportImgur, self.canvasExportPastebin, self.canvasExportAsPng,), + ("&Import...", self.canvasImportAnsi, self.canvasImportFromClipboard, self.canvasImportSauce,), + NID_MENU_SEP, + self.canvasExit, + ), + ("&Edit", + self.canvasUndo, self.canvasRedo, NID_MENU_SEP, + self.canvasCut, self.canvasCopy, self.canvasPaste, + self.canvasDelete, NID_MENU_SEP, + ("Brush size", self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), NID_MENU_SEP, + self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False),), + ("Canvas size", self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_MENU_SEP, + self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False),), + self.canvasColoursFlip, + NID_MENU_SEP, + self.canvasBrush(self.canvasBrush, 0), NID_MENU_SEP, + self.canvasAssetsWindowHide, self.canvasAssetsWindowShow, + ), + ("&Tools", + self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 7), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 4), self.canvasTool(self.canvasTool, 8), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 6), + ), + ("&Operators", + self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2), self.canvasOperator(self.canvasOperator, 3), self.canvasOperator(self.canvasOperator, 4), + ), + ("&Help", + self.canvasMelp, NID_MENU_SEP, self.canvasNewIssueGitHub, self.canvasVisitGitHub, NID_MENU_SEP, self.canvasAbout, + ), + ) + toolBars = ( + (self.canvasNew, self.canvasOpen, self.canvasSave, self.canvasSaveAs, NID_TOOLBAR_HSEP, + self.canvasUndo, self.canvasRedo, NID_TOOLBAR_HSEP, + self.canvasCut, self.canvasCopy, self.canvasPaste, self.canvasDelete, NID_TOOLBAR_HSEP, + self.canvasAssetsWindowHide, self.canvasAssetsWindowShow, NID_TOOLBAR_HSEP, + ), + (self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 7), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 4), self.canvasTool(self.canvasTool, 8), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 6),), + (self.canvasColour(self.canvasColour, 0), self.canvasColour(self.canvasColour, 1), self.canvasColour(self.canvasColour, 2), self.canvasColour(self.canvasColour, 3), + self.canvasColour(self.canvasColour, 4), self.canvasColour(self.canvasColour, 5), self.canvasColour(self.canvasColour, 6), self.canvasColour(self.canvasColour, 7), + self.canvasColour(self.canvasColour, 8), self.canvasColour(self.canvasColour, 9), self.canvasColour(self.canvasColour, 10), self.canvasColour(self.canvasColour, 11), + self.canvasColour(self.canvasColour, 12), self.canvasColour(self.canvasColour, 13), self.canvasColour(self.canvasColour, 14), self.canvasColour(self.canvasColour, 15), + self.canvasColourAlpha(self.canvasColourAlpha, 0), self.canvasColoursFlip, NID_TOOLBAR_HSEP, + self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), NID_TOOLBAR_HSEP, + self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False), + ), + (self.canvasColourBackground(self.canvasColourBackground, 0), self.canvasColourBackground(self.canvasColourBackground, 1), self.canvasColourBackground(self.canvasColourBackground, 2), self.canvasColourBackground(self.canvasColourBackground, 3), + self.canvasColourBackground(self.canvasColourBackground, 4), self.canvasColourBackground(self.canvasColourBackground, 5), self.canvasColourBackground(self.canvasColourBackground, 6), self.canvasColourBackground(self.canvasColourBackground, 7), + self.canvasColourBackground(self.canvasColourBackground, 8), self.canvasColourBackground(self.canvasColourBackground, 9), self.canvasColourBackground(self.canvasColourBackground, 10), self.canvasColourBackground(self.canvasColourBackground, 11), + self.canvasColourBackground(self.canvasColourBackground, 12), self.canvasColourBackground(self.canvasColourBackground, 13), self.canvasColourBackground(self.canvasColourBackground, 14), self.canvasColourBackground(self.canvasColourBackground, 15), + self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0), self.canvasColoursFlip, NID_TOOLBAR_HSEP, + self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_TOOLBAR_HSEP, + self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False), + ), + ) + return accels, menus, toolBars + def update(self, **kwargs): self.lastPanelState.update(kwargs); textItems = []; if "cellPos" in self.lastPanelState: @@ -53,17 +113,13 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan toolBar = self.parentFrame.toolBarItemsById[self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"]][0] toolBarBg = self.parentFrame.toolBarItemsById[self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"]][0] if self.lastPanelState["colours"][0] != -1: - toolBar.ToggleTool(self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"], True) - toolBar.Refresh() + toolBar.ToggleTool(self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"], True); toolBar.Refresh() else: - toolBar.ToggleTool(self.canvasColourAlpha(self.canvasColourAlpha, 0).attrDict["id"], True) - toolBar.Refresh() + toolBar.ToggleTool(self.canvasColourAlpha(self.canvasColourAlpha, 0).attrDict["id"], True); toolBar.Refresh() if self.lastPanelState["colours"][1] != -1: - toolBarBg.ToggleTool(self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"], True) - toolBarBg.Refresh() + toolBarBg.ToggleTool(self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"], True); toolBarBg.Refresh() else: - toolBarBg.ToggleTool(self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0).attrDict["id"], True) - toolBarBg.Refresh() + toolBarBg.ToggleTool(self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0).attrDict["id"], True); toolBarBg.Refresh() if "pathName" in self.lastPanelState: if self.lastPanelState["pathName"] != None: basePathName = os.path.basename(self.lastPanelState["pathName"]) @@ -71,27 +127,25 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan self.parentFrame.SetTitle("{} - roar".format(basePathName)) else: self.parentFrame.SetTitle("roar") - if "toolName" in self.lastPanelState: - textItems.append("T: {}".format(self.lastPanelState["toolName"])) - if ("operator" in self.lastPanelState) \ - and (self.lastPanelState["operator"] != None): + if "currentTool" in self.lastPanelState: + self.parentFrame.menuItemsById[self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"]].Check(True) + toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"]][0] + toolBar.ToggleTool(self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"], True); toolBar.Refresh(); + if (self.lastPanelState["currentTool"] != None) and (self.lastPanelState["currentTool"].__class__ == ToolObject): + self.parentFrame.menuItemsById[self.canvasOperator.attrList[4]["id"]].Enable(True) + else: + self.parentFrame.menuItemsById[self.canvasOperator.attrList[4]["id"]].Enable(False) + textItems.append("T: {}".format(self.lastPanelState["currentTool"].name if (self.lastPanelState["currentTool"] != None) else "Cursor")) + if ("operator" in self.lastPanelState) and (self.lastPanelState["operator"] != None): textItems.append("O: {}".format(self.lastPanelState["operator"])) - if "dirty" in self.lastPanelState \ - and self.lastPanelState["dirty"]: + if ("dirty" in self.lastPanelState) and self.lastPanelState["dirty"]: textItems.append("*") - if "backupStatus" in self.lastPanelState \ - and self.lastPanelState["backupStatus"] == True: + if ("backupStatus" in self.lastPanelState) and (self.lastPanelState["backupStatus"] == True): textItems.append("Saving backup...") self.parentFrame.statusBar.SetStatusText(" | ".join(textItems)) - if ("undoInhibit" in self.lastPanelState) \ - and (self.lastPanelState["undoInhibit"]): - for item in (self.canvasRedo, self.canvasUndo): - self.parentFrame.menuItemsById[item.attrDict["id"]].Enable(False) - toolBar = self.parentFrame.toolBarItemsById[item.attrDict["id"]][0] - toolBar.EnableTool(item.attrDict["id"], False); toolBar.Refresh(); - elif "undoLevel" in self.lastPanelState: + if "undoLevel" in self.lastPanelState: if (self.lastPanelState["undoLevel"] >= 0) \ - and (self.lastPanelState["undoLevel"] < (len(self.parentCanvas.canvas.journal.patchesUndo) - 1)): + and (self.lastPanelState["undoLevel"] < (len(self.parentCanvas.canvas.patchesUndo) - 1)): self.parentFrame.menuItemsById[self.canvasUndo.attrDict["id"]].Enable(True) toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0] toolBar.EnableTool(self.canvasUndo.attrDict["id"], True); toolBar.Refresh(); @@ -109,45 +163,8 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan toolBar.EnableTool(self.canvasRedo.attrDict["id"], False); toolBar.Refresh(); def __init__(self, parentCanvas, parentFrame): - accels, menus, toolBars = [], [], [] + [classObject.__init__(self) for classObject in self.__class__.__bases__] + self._initColourBitmaps(); self.accels, self.menus, self.toolBars = self._initInterface(); self.canvasPathName, self.lastPanelState, self.parentCanvas, self.parentFrame = None, {}, parentCanvas, parentFrame - for classObject in self.__class__.__bases__: - classObject.__init__(self) - if len(self.accels): - accels += self.accels - if len(self.menus): - menus += self.menus - if len(self.toolBars): - toolBars += self.toolBars - self._initColourBitmaps() - - toolBars.append( - [self.canvasNew, self.canvasOpen, self.canvasSave, self.canvasSaveAs, NID_TOOLBAR_HSEP, - self.canvasUndo, self.canvasRedo, NID_TOOLBAR_HSEP, - self.canvasCut, self.canvasCopy, self.canvasPaste, self.canvasDelete, NID_TOOLBAR_HSEP, - self.canvasAssetsWindowHide, self.canvasAssetsWindowShow, NID_TOOLBAR_HSEP, - ]) - toolBars.append( - [self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 7), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 4), self.canvasTool(self.canvasTool, 8), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 6), - ]) - toolBars.append( - [self.canvasColour(self.canvasColour, 0), self.canvasColour(self.canvasColour, 1), self.canvasColour(self.canvasColour, 2), self.canvasColour(self.canvasColour, 3), - self.canvasColour(self.canvasColour, 4), self.canvasColour(self.canvasColour, 5), self.canvasColour(self.canvasColour, 6), self.canvasColour(self.canvasColour, 7), - self.canvasColour(self.canvasColour, 8), self.canvasColour(self.canvasColour, 9), self.canvasColour(self.canvasColour, 10), self.canvasColour(self.canvasColour, 11), - self.canvasColour(self.canvasColour, 12), self.canvasColour(self.canvasColour, 13), self.canvasColour(self.canvasColour, 14), self.canvasColour(self.canvasColour, 15), - self.canvasColourAlpha(self.canvasColourAlpha, 0), self.canvasColoursFlip, NID_TOOLBAR_HSEP, - self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), NID_TOOLBAR_HSEP, - self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False), - ]) - toolBars.append( - [self.canvasColourBackground(self.canvasColourBackground, 0), self.canvasColourBackground(self.canvasColourBackground, 1), self.canvasColourBackground(self.canvasColourBackground, 2), self.canvasColourBackground(self.canvasColourBackground, 3), - self.canvasColourBackground(self.canvasColourBackground, 4), self.canvasColourBackground(self.canvasColourBackground, 5), self.canvasColourBackground(self.canvasColourBackground, 6), self.canvasColourBackground(self.canvasColourBackground, 7), - self.canvasColourBackground(self.canvasColourBackground, 8), self.canvasColourBackground(self.canvasColourBackground, 9), self.canvasColourBackground(self.canvasColourBackground, 10), self.canvasColourBackground(self.canvasColourBackground, 11), - self.canvasColourBackground(self.canvasColourBackground, 12), self.canvasColourBackground(self.canvasColourBackground, 13), self.canvasColourBackground(self.canvasColourBackground, 14), self.canvasColourBackground(self.canvasColourBackground, 15), - self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0), self.canvasColoursFlip, NID_TOOLBAR_HSEP, - self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_TOOLBAR_HSEP, - self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False), - ]) - self.accels, self.menus, self.toolBars = accels, menus, toolBars # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasCommandsEdit.py b/libroar/RoarCanvasCommandsEdit.py index 21e6945..0eb19dc 100644 --- a/libroar/RoarCanvasCommandsEdit.py +++ b/libroar/RoarCanvasCommandsEdit.py @@ -4,7 +4,7 @@ # Copyright (c) 2018, 2019 Lucio AndrΓ©s Illanes Albornoz # -from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator, NID_MENU_SEP +from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator import wx class RoarCanvasCommandsEdit(): @@ -194,31 +194,10 @@ class RoarCanvasCommandsEdit(): @GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False) def canvasRedo(self, event): - self.parentCanvas.undo(redo=True); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel); + self.parentCanvas.undo(redo=True); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.patchesUndoLevel); @GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False) def canvasUndo(self, event): - self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel); - - def __init__(self): - self.accels = ( - (self.canvasColourBackground(self.canvasColourBackground, 0), self.canvasColourBackground(self.canvasColourBackground, 1), self.canvasColourBackground(self.canvasColourBackground, 2), self.canvasColourBackground(self.canvasColourBackground, 3), self.canvasColourBackground(self.canvasColourBackground, 4), self.canvasColourBackground(self.canvasColourBackground, 5), self.canvasColourBackground(self.canvasColourBackground, 6), self.canvasColourBackground(self.canvasColourBackground, 7), self.canvasColourBackground(self.canvasColourBackground, 8), self.canvasColourBackground(self.canvasColourBackground, 9), self.canvasColourBackground(self.canvasColourBackground, 10), self.canvasColourBackground(self.canvasColourBackground, 11), self.canvasColourBackground(self.canvasColourBackground, 12), self.canvasColourBackground(self.canvasColourBackground, 13), self.canvasColourBackground(self.canvasColourBackground, 14), self.canvasColourBackground(self.canvasColourBackground, 15), self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0),), - ) - self.menus = ( - ("&Edit", - self.canvasUndo, self.canvasRedo, NID_MENU_SEP, - self.canvasCut, self.canvasCopy, self.canvasPaste, - self.canvasDelete, NID_MENU_SEP, - ("Brush size", self.canvasBrushSize(self.canvasBrushSize, 0, True), self.canvasBrushSize(self.canvasBrushSize, 0, False), self.canvasBrushSize(self.canvasBrushSize, 1, True), self.canvasBrushSize(self.canvasBrushSize, 1, False), NID_MENU_SEP, - self.canvasBrushSize(self.canvasBrushSize, 2, True), self.canvasBrushSize(self.canvasBrushSize, 2, False),), - ("Canvas size", self.canvasCanvasSize(self.canvasCanvasSize, 1, True), self.canvasCanvasSize(self.canvasCanvasSize, 1, False), self.canvasCanvasSize(self.canvasCanvasSize, 0, True), self.canvasCanvasSize(self.canvasCanvasSize, 0, False), NID_MENU_SEP, - self.canvasCanvasSize(self.canvasCanvasSize, 2, True), self.canvasCanvasSize(self.canvasCanvasSize, 2, False),), - self.canvasColoursFlip, - NID_MENU_SEP, - self.canvasBrush(self.canvasBrush, 0), NID_MENU_SEP, - self.canvasAssetsWindowHide, self.canvasAssetsWindowShow, - ), - ) - self.toolBars = () + self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.patchesUndoLevel); # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasCommandsFile.py b/libroar/RoarCanvasCommandsFile.py index 446ac34..09bb3a3 100644 --- a/libroar/RoarCanvasCommandsFile.py +++ b/libroar/RoarCanvasCommandsFile.py @@ -5,14 +5,12 @@ # try: - from ImgurApiKey import ImgurApiKey - haveImgurApiKey = True + from ImgurApiKey import ImgurApiKey; haveImgurApiKey = True; except ImportError: haveImgurApiKey = False try: - import base64, json, requests, urllib.request - haveUrllib = True + import base64, json, requests, urllib.request; haveUrllib = True; except ImportError: haveUrllib = False @@ -35,8 +33,7 @@ class RoarCanvasCommandsFile(): self.canvasPathName = None self.parentCanvas._snapshotsReset() self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1) - self.parentCanvas.canvas.journal.resetCursor() - self.parentCanvas.canvas.journal.resetUndo() + self.parentCanvas.canvas.resetCursor(); self.parentCanvas.canvas.resetUndo(); except FileNotFoundError as e: rc, error, newMap, newPathName, newSize = False, str(e), None, None, None if not rc: @@ -52,28 +49,10 @@ class RoarCanvasCommandsFile(): if dialog.ShowModal() == wx.ID_CANCEL: return False, None elif self._promptSaveChanges(): - pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName); self._storeLastDir(self.lastDir); + pathName = dialog.GetPath(); self.lastDir = os.path.dirname(pathName); self._recentDirSave(self.lastDir); return self._import(f, newDirty, pathName, emptyPathName=emptyPathName) return False, None - def _loadLastDir(self): - localConfFileName = getLocalConfPathName("RecentDir.txt") - if os.path.exists(localConfFileName): - with open(localConfFileName, "r", encoding="utf-8") as inFile: - self.lastDir = inFile.read().rstrip("\r\n") - - def _loadRecent(self): - localConfFileName = getLocalConfPathName("Recent.lst") - if os.path.exists(localConfFileName): - with open(localConfFileName, "r", encoding="utf-8") as inFile: - for lastFile in inFile.readlines(): - self._pushRecent(lastFile.rstrip("\r\n"), False) - - def _popSnapshot(self, pathName): - if pathName in self.snapshots.keys(): - self.canvasRestore.attrDict["menu"].Delete(self.snapshots[pathName]["menuItemId"]) - del self.snapshots[pathName] - def _promptSaveChanges(self): if self.parentCanvas.dirty: message = "Do you want to save changes to {}?".format(self.canvasPathName if self.canvasPathName != None else "(Untitled)") @@ -90,7 +69,25 @@ class RoarCanvasCommandsFile(): else: return True - def _pushRecent(self, pathName, serialise=True): + def _recentDirLoad(self): + localConfFileName = getLocalConfPathName("RecentDir.txt") + if os.path.exists(localConfFileName): + with open(localConfFileName, "r", encoding="utf-8") as inFile: + self.lastDir = inFile.read().rstrip("\r\n") + + def _recentDirSave(self, pathName): + localConfFileName = getLocalConfPathName("RecentDir.txt") + with open(localConfFileName, "w", encoding="utf-8") as outFile: + print(pathName, file=outFile) + + def _recentLoad(self): + localConfFileName = getLocalConfPathName("Recent.lst") + if os.path.exists(localConfFileName): + with open(localConfFileName, "r", encoding="utf-8") as inFile: + for lastFile in inFile.readlines(): + self._recentPush(lastFile.rstrip("\r\n"), False) + + def _recentPush(self, pathName, serialise=True): menuItemId = wx.NewId() if not pathName in [l["pathName"] for l in self.lastFiles]: numLastFiles = len(self.lastFiles) if self.lastFiles != None else 0 @@ -102,9 +99,20 @@ class RoarCanvasCommandsFile(): self.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasOpenRecent(event, pathName), menuItemWindow) self.lastFiles += [{"menuItemId":menuItemId, "menuItemWindow":menuItemWindow, "pathName":pathName}] if serialise: - self._saveRecent() + self._recentSave() - def _pushSnapshot(self, pathName): + def _recentSave(self): + localConfFileName = getLocalConfPathName("Recent.lst") + with open(localConfFileName, "w", encoding="utf-8") as outFile: + for lastFile in [l["pathName"] for l in self.lastFiles]: + print(lastFile, file=outFile) + + def _snapshotsPop(self, pathName): + if pathName in self.snapshots.keys(): + self.canvasRestore.attrDict["menu"].Delete(self.snapshots[pathName]["menuItemId"]) + del self.snapshots[pathName] + + def _snapshotsPush(self, pathName): menuItemId = wx.NewId() if not (pathName in self.snapshots.keys()): label = datetime.datetime.fromtimestamp(os.stat(pathName)[stat.ST_MTIME]).strftime("%c") @@ -113,23 +121,12 @@ class RoarCanvasCommandsFile(): self.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasRestore(event, pathName), menuItemWindow) self.snapshots[pathName] = {"menuItemId":menuItemId, "menuItemWindow":menuItemWindow} - def _resetSnapshots(self): + def _snapshotsReset(self): for pathName in list(self.snapshots.keys()): - self._popSnapshot(pathName) + self._snapshotsPop(pathName) if self.canvasRestore.attrDict["id"] in self.parentFrame.menuItemsById: self.parentFrame.menuItemsById[self.canvasRestore.attrDict["id"]].Enable(False) - def _saveRecent(self): - localConfFileName = getLocalConfPathName("Recent.lst") - with open(localConfFileName, "w", encoding="utf-8") as outFile: - for lastFile in [l["pathName"] for l in self.lastFiles]: - print(lastFile, file=outFile) - - def _storeLastDir(self, pathName): - localConfFileName = getLocalConfPathName("RecentDir.txt") - with open(localConfFileName, "w", encoding="utf-8") as outFile: - print(pathName, file=outFile) - @GuiCommandDecorator("Clear list", "&Clear list", None, None, False) def canvasClearRecent(self, event): if self.lastFiles != None: @@ -155,7 +152,7 @@ class RoarCanvasCommandsFile(): if dialog.ShowModal() == wx.ID_CANCEL: return False else: - outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._storeLastDir(self.lastDir); + outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._recentDirSave(self.lastDir); self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) with open(outPathName, "w", encoding="utf-8") as outFile: self.parentCanvas.canvas.exportStore.exportAnsiFile(self.parentCanvas.canvas.map, self.parentCanvas.canvas.size, outFile) @@ -170,30 +167,20 @@ class RoarCanvasCommandsFile(): if dialog.ShowModal() == wx.ID_CANCEL: return False else: - outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._storeLastDir(self.lastDir); + outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._recentDirSave(self.lastDir); self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) - self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc, reset=False) + self.parentCanvas.cursorHide() self.parentCanvas.canvas.exportStore.exportBitmapToPngFile(self.parentCanvas.backend.canvasBitmap, outPathName, wx.BITMAP_TYPE_PNG) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - cursorPatches = self.parentCanvas.canvas.journal.popCursor(reset=False) - if len(cursorPatches) > 0: - self.parentCanvas.backend.drawPatches(self.parentCanvas.canvas, eventDc, cursorPatches, isCursor=True) - eventDc.SetDeviceOrigin(*eventDcOrigin) + self.parentCanvas.cursorShow() self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) return True @GuiCommandDecorator("Export to Imgur...", "Export to I&mgur...", None, None, haveImgurApiKey and haveUrllib) def canvasExportImgur(self, event): self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) - self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc, reset=False) + self.parentCanvas.cursorHide() rc, status, result = self.parentCanvas.canvas.exportStore.exportBitmapToImgur(self.imgurApiKey, self.parentCanvas.backend.canvasBitmap, "", "", wx.BITMAP_TYPE_PNG) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - cursorPatches = self.parentCanvas.canvas.journal.popCursor(reset=False) - if len(cursorPatches) > 0: - self.parentCanvas.backend.drawPatches(self.parentCanvas.canvas, eventDc, cursorPatches, isCursor=True) - eventDc.SetDeviceOrigin(*eventDcOrigin) + self.parentCanvas.cursorShow() self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) if rc: if not wx.TheClipboard.IsOpened(): @@ -261,7 +248,7 @@ class RoarCanvasCommandsFile(): nonlocal newCanvasSize if newCanvasSize == None: newCanvasSize = list(self.parentCanvas.canvas.size) - newMap = [[[-1, -1, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])] + newMap = [[[*self.parentCanvas.brushColours, 0, " "] for x in range(newCanvasSize[0])] for y in range(newCanvasSize[1])] return (True, "", newMap, None, newCanvasSize) if self._promptSaveChanges(): self._import(canvasImportEmpty, False, None) @@ -273,7 +260,7 @@ class RoarCanvasCommandsFile(): return (rc, error, self.parentCanvas.canvas.importStore.outMap, pathName, self.parentCanvas.canvas.importStore.inSize) rc, newPathName = self._importFile(canvasImportmIRC, False, "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*") if rc: - self._pushRecent(newPathName) + self._recentPush(newPathName) @GuiSubMenuDecorator("Open Recent", "Open &Recent", None, None, False) def canvasOpenRecent(self, event, pathName=None): @@ -285,7 +272,7 @@ class RoarCanvasCommandsFile(): if not rc: numLastFile = [i for i in range(len(self.lastFiles)) if self.lastFiles[i]["pathName"] == pathName][0] self.canvasOpenRecent.attrDict["menu"].Delete(self.lastFiles[numLastFile]["menuItemId"]); del self.lastFiles[numLastFile]; - self._saveRecent() + self._recentSave() @GuiSubMenuDecorator("Restore Snapshot", "Res&tore Snapshot", None, None, False) def canvasRestore(self, event, pathName=None): @@ -328,27 +315,16 @@ class RoarCanvasCommandsFile(): if dialog.ShowModal() == wx.ID_CANCEL: return False else: - self.canvasPathName = dialog.GetPath(); self.lastDir = os.path.dirname(self.canvasPathName); self._storeLastDir(self.lastDir); + self.canvasPathName = dialog.GetPath(); self.lastDir = os.path.dirname(self.canvasPathName); self._recentDirSave(self.lastDir); if self.canvasSave(event, newDirty=False): self.update(pathName=self.canvasPathName) - self._pushRecent(self.canvasPathName) + self._recentPush(self.canvasPathName) return True else: return False def __init__(self): - self.imgurApiKey, self.lastFiles, self.lastDir, self.snapshots = ImgurApiKey.imgurApiKey if haveImgurApiKey else None, [], None, {} - self.accels = () - self.menus = ( - ("&File", - self.canvasNew, self.canvasOpen, self.canvasOpenRecent, self.canvasRestore, self.canvasSave, self.canvasSaveAs, NID_MENU_SEP, - ("&Export...", self.canvasExportAsAnsi, self.canvasExportToClipboard, self.canvasExportImgur, self.canvasExportPastebin, self.canvasExportAsPng,), - ("&Import...", self.canvasImportAnsi, self.canvasImportFromClipboard, self.canvasImportSauce,), - NID_MENU_SEP, - self.canvasExit, - ), - ) - self.toolBars = () - self.exiting = False + self.exiting, self.lastFiles, self.lastDir, self.snapshots = False, [], None, {} + self.imgurApiKey = ImgurApiKey.imgurApiKey if haveImgurApiKey else None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasCommandsHelp.py b/libroar/RoarCanvasCommandsHelp.py index 435c458..564deb4 100644 --- a/libroar/RoarCanvasCommandsHelp.py +++ b/libroar/RoarCanvasCommandsHelp.py @@ -4,7 +4,7 @@ # Copyright (c) 2018, 2019 Lucio AndrΓ©s Illanes Albornoz # -from GuiFrame import GuiCommandDecorator, NID_MENU_SEP +from GuiFrame import GuiCommandDecorator from RoarWindowAbout import RoarWindowAbout from RoarWindowMelp import RoarWindowMelp import webbrowser, wx @@ -26,8 +26,4 @@ class RoarCanvasCommandsHelp(): def canvasVisitGitHub(self, event): webbrowser.open("https://www.github.com/lalbornoz/roar") - def __init__(self): - self.accels = () - self.menus, self.toolBars = (("&Help", self.canvasMelp, NID_MENU_SEP, self.canvasNewIssueGitHub, self.canvasVisitGitHub, NID_MENU_SEP, self.canvasAbout,),), () - # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasCommandsOperators.py b/libroar/RoarCanvasCommandsOperators.py index b9113fc..5373814 100644 --- a/libroar/RoarCanvasCommandsOperators.py +++ b/libroar/RoarCanvasCommandsOperators.py @@ -4,14 +4,13 @@ # Copyright (c) 2018, 2019 Lucio AndrΓ©s Illanes Albornoz # +from GuiFrame import GuiCommandListDecorator from OperatorFlipHorizontal import OperatorFlipHorizontal from OperatorFlipVertical import OperatorFlipVertical from OperatorInvert import OperatorInvert from OperatorRotate import OperatorRotate from OperatorTile import OperatorTile -from GuiFrame import GuiCommandListDecorator from ToolObject import ToolObject -import copy, wx class RoarCanvasCommandsOperators(): @GuiCommandListDecorator(0, "Flip", "&Flip", None, None, None) @@ -28,13 +27,6 @@ class RoarCanvasCommandsOperators(): return canvasOperator_ def __init__(self): - self.accels = () - self.menus = ( - ("&Operators", - self.canvasOperator(self.canvasOperator, 0), self.canvasOperator(self.canvasOperator, 1), self.canvasOperator(self.canvasOperator, 2), self.canvasOperator(self.canvasOperator, 3), self.canvasOperator(self.canvasOperator, 4), - ), - ) - self.toolBars = () self.currentOperator, self.operatorState = None, None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasCommandsTools.py b/libroar/RoarCanvasCommandsTools.py index b6a40a1..be33996 100644 --- a/libroar/RoarCanvasCommandsTools.py +++ b/libroar/RoarCanvasCommandsTools.py @@ -27,40 +27,21 @@ class RoarCanvasCommandsTools(): @GuiSelectDecorator(8, "Text", "&Text", ["toolText.png"], [wx.MOD_NONE, wx.WXK_F7], False) def canvasTool(self, f, idx): def canvasTool_(event): - if (self.currentTool.__class__ == ToolObject) \ - and (self.currentTool.toolState > self.currentTool.TS_NONE) \ + if (self.currentTool.__class__ == ToolObject) \ + and (self.currentTool.toolState > self.currentTool.TS_NONE) \ and self.currentTool.external: self.parentCanvas.dropTarget.done() self.lastTool, self.currentTool = self.currentTool, [ToolCircle, None, ToolErase, ToolFill, ToolLine, ToolObject, ToolPickColour, ToolRect, ToolText][idx] if self.currentTool != None: self.currentTool = self.currentTool() self.currentOperator, self.operatorState = None, None - self.parentFrame.menuItemsById[self.canvasTool.attrList[idx]["id"]].Check(True) - if self.currentTool.__class__ == ToolObject: - self.parentFrame.menuItemsById[self.canvasOperator.attrList[4]["id"]].Enable(True) - else: - self.parentFrame.menuItemsById[self.canvasOperator.attrList[4]["id"]].Enable(False) - toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[idx]["id"]][0] - toolBar.ToggleTool(self.canvasTool.attrList[idx]["id"], True); toolBar.Refresh(); - if self.currentTool != None: - self.update(operator=None, toolName=self.currentTool.name) - else: - self.update(operator=None, toolName="Cursor") - viewRect = self.parentCanvas.GetViewStart() - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, viewRect) - self.parentCanvas.applyTool(eventDc, True, None, None, None, self.parentCanvas.brushPos, False, False, False, self.currentTool, viewRect, force=True) + self.update(currentTool=self.currentTool, currentToolIdx=idx, operator=None) + self.parentCanvas.applyTool(None, True, None, None, None, self.parentCanvas.brushPos, False, False, False, self.currentTool, None, force=True) setattr(canvasTool_, "attrDict", f.attrList[idx]) setattr(canvasTool_, "isSelect", True) return canvasTool_ def __init__(self): - self.accels = () - self.menus = ( - ("&Tools", - self.canvasTool(self.canvasTool, 1), self.canvasTool(self.canvasTool, 7), self.canvasTool(self.canvasTool, 0), self.canvasTool(self.canvasTool, 3), self.canvasTool(self.canvasTool, 4), self.canvasTool(self.canvasTool, 8), self.canvasTool(self.canvasTool, 5), self.canvasTool(self.canvasTool, 2), self.canvasTool(self.canvasTool, 6), - ), - ) - self.toolBars = () self.currentTool, self.lastTool = None, None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index 74ed680..3415919 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -9,7 +9,7 @@ from Rtl import natural_sort from RtlPlatform import getLocalConfPathName from ToolObject import ToolObject from ToolText import ToolText -import copy, hashlib, json, os, re, time, wx, sys +import copy, hashlib, json, os, pdb, re, time, wx, sys class RoarCanvasWindowDropTarget(wx.TextDropTarget): def done(self): @@ -27,8 +27,8 @@ class RoarCanvasWindowDropTarget(wx.TextDropTarget): viewRect = self.parent.GetViewStart(); mapPoint = [m + n for m, n in zip((mapX, mapY), viewRect)]; self.parent.commands.lastTool, self.parent.commands.currentTool = self.parent.commands.currentTool, ToolObject() self.parent.commands.currentTool.setRegion(self.parent.canvas, mapPoint, dropMap, dropSize, external=True) - self.parent.parentFrame.menuItemsById[self.parent.commands.canvasOperator.attrList[4]["id"]].Enable(True) - self.parent.commands.update(toolName=self.parent.commands.currentTool.name) + self.parent.parent.menuItemsById[self.parent.commands.canvasOperator.attrList[4]["id"]].Enable(True) + self.parent.commands.update(currentTool=self.parent.commands.currentTool, currentToolIdx=5) eventDc = self.parent.backend.getDeviceContext(self.parent.GetClientSize(), self.parent, viewRect) eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); self.parent.applyTool(eventDc, True, None, None, None, self.parent.brushPos, False, False, False, self.parent.commands.currentTool, viewRect) @@ -43,45 +43,34 @@ class RoarCanvasWindowDropTarget(wx.TextDropTarget): super().__init__(); self.inProgress, self.parent = False, parent; class RoarCanvasWindow(GuiWindow): - def _applyPatches(self, eventDc, patches, patchesCursor, rc): + def _applyPatches(self, eventDc, patches, patchesCursor, rc, commitUndo=True, dirty=True, eventDcResetOrigin=True, hideCursor=True): if rc: - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - if ((patches != None) and (len(patches) > 0)) \ - or ((patchesCursor != None) and (len(patchesCursor) > 0)): - self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) + if eventDc == None: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) + if eventDcResetOrigin: + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + if hideCursor and (((patches != None) and (len(patches) > 0)) or ((patchesCursor != None) and (len(patchesCursor) > 0))): + self.cursorHide(eventDc, False, True) if (patches != None) and (len(patches) > 0): self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) - self.dirty = True if not self.dirty else self.dirty; - self.canvas.journal.begin() + if dirty and not self.dirty: + self.dirty = True + if commitUndo: + self.canvas.begin() for patch in patches if patches != None else []: - self.canvas.applyPatch(patch, commitUndo=True) - self.canvas.journal.end() - if patchesCursor != None: - self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) - if len(patchesCursor) > 0: - self.canvas.journal.pushCursor(patchesCursor) - eventDc.SetDeviceOrigin(*eventDcOrigin) - self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) - - def _eraseBackground(self, eventDc): - viewRect = self.GetViewStart() - canvasSize, panelSize = [a * b for a, b in zip(self.backend.canvasSize, self.backend.cellSize)], self.GetSize() - if viewRect != (0, 0): - viewRect = [a * b for a, b in zip(self.backend.cellSize, viewRect)] - canvasSize = [a - b for a, b in zip(canvasSize, viewRect)] - rectangles, pens, brushes = [], [], [] - if panelSize[0] > canvasSize[0]: - brushes += [self.bgBrush]; pens += [self.bgPen]; - rectangles += [[canvasSize[0], 0, panelSize[0] - canvasSize[0], panelSize[1]]] - if panelSize[1] > canvasSize[1]: - brushes += [self.bgBrush]; pens += [self.bgPen]; - rectangles += [[0, canvasSize[1], panelSize[0], panelSize[1] - canvasSize[1]]] - if len(rectangles) > 0: - eventDc.DrawRectangleList(rectangles, pens, brushes) + self.canvas.applyPatch(patch, commitUndo=commitUndo) + if commitUndo: + self.canvas.end() + if hideCursor and (patchesCursor != None): + self.cursorShow(eventDc, False, patchesCursor=patchesCursor) + if eventDcResetOrigin: + eventDc.SetDeviceOrigin(*eventDcOrigin) + self.commands.update(cellPos=self.brushPos, dirty=self.dirty, undoLevel=self.canvas.patchesUndoLevel) + return eventDc def _snapshotsReset(self): self._snapshotFiles, self._snapshotsUpdateLast = [], time.time() - self.commands._resetSnapshots() + self.commands._snapshotsReset() if self.commands.canvasPathName != None: canvasPathName = os.path.abspath(self.commands.canvasPathName) canvasFileName = os.path.basename(canvasPathName) @@ -90,7 +79,7 @@ class RoarCanvasWindow(GuiWindow): if os.path.exists(self._snapshotsDirName): for snapshotFile in natural_sort([f for f in os.listdir(self._snapshotsDirName) \ if (re.match(r'snapshot\d+\.txt$', f)) and os.path.isfile(os.path.join(self._snapshotsDirName, f))]): - self.commands._pushSnapshot(os.path.join(self._snapshotsDirName, snapshotFile)) + self.commands._snapshotsPush(os.path.join(self._snapshotsDirName, snapshotFile)) else: self._snapshotsDirName = None @@ -115,14 +104,30 @@ class RoarCanvasWindow(GuiWindow): self.SetCursor(wx.Cursor(wx.NullCursor)) self.commands.update(snapshotStatus=False); self._snapshotsUpdateLast = time.time(); self._snapshotFiles += [os.path.basename(snapshotPathName)]; - self.commands._pushSnapshot(snapshotPathName) + self.commands._snapshotsPush(snapshotPathName) if len(self._snapshotFiles) > 72: for snapshotFile in self._snapshotFiles[:len(self._snapshotFiles) - 8]: - self.commands._popSnapshot(os.path.join(self._snapshotsDirName, snapshotFile)) + self.commands._snapshotsPop(os.path.join(self._snapshotsDirName, snapshotFile)) os.remove(os.path.join(self._snapshotsDirName, snapshotFile)); snapshotsCount -= 1; except: print("Exception during _snapshotsUpdate(): {}".format(sys.exc_info()[1])) + def _windowEraseBackground(self, eventDc): + viewRect = self.GetViewStart() + canvasSize, panelSize = [a * b for a, b in zip(self.backend.canvasSize, self.backend.cellSize)], self.GetSize() + if viewRect != (0, 0): + viewRect = [a * b for a, b in zip(self.backend.cellSize, viewRect)] + canvasSize = [a - b for a, b in zip(canvasSize, viewRect)] + rectangles, pens, brushes = [], [], [] + if panelSize[0] > canvasSize[0]: + brushes += [self.bgBrush]; pens += [self.bgPen]; + rectangles += [[canvasSize[0], 0, panelSize[0] - canvasSize[0], panelSize[1]]] + if panelSize[1] > canvasSize[1]: + brushes += [self.bgBrush]; pens += [self.bgPen]; + rectangles += [[0, canvasSize[1], panelSize[0], panelSize[1] - canvasSize[1]]] + if len(rectangles) > 0: + eventDc.DrawRectangleList(rectangles, pens, brushes) + def applyOperator(self, currentTool, mapPoint, mouseLeftDown, mousePoint, operator, viewRect): eventDc, patches, patchesCursor, rc = self.backend.getDeviceContext(self.GetClientSize(), self), None, None, True if (currentTool.__class__ == ToolObject) and (currentTool.toolState >= currentTool.TS_SELECT): @@ -157,6 +162,10 @@ class RoarCanvasWindow(GuiWindow): def applyTool(self, eventDc, eventMouse, keyChar, keyCode, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect, force=False): patches, patchesCursor, rc = None, None, False + if viewRect == None: + viewRect = self.GetViewStart() + if eventDc == None: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect) if eventMouse: self.lastCellState = None if force else self.lastCellState if ((mapPoint[0] < self.canvas.size[0]) and (mapPoint[1] < self.canvas.size[1])) \ @@ -173,75 +182,80 @@ class RoarCanvasWindow(GuiWindow): elif mapPoint != None: rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]] if rc: + for patch in patches if patches != None else []: + if ((patch[2] == -1) and (patch[3] == -1)) \ + and (patch[0] < self.canvas.size[0]) \ + and (patch[1] < self.canvas.size[1]): + patch[2:] = self.canvas.map[patch[1]][patch[0]] self._applyPatches(eventDc, patches, patchesCursor, rc) - if tool.__class__ == ToolObject: - if tool.toolState > tool.TS_NONE: - self.commands.update(undoInhibit=True) - elif tool.toolState == tool.TS_NONE: - if tool.external: - self.dropTarget.done(); self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool; - newToolName = "Cursor" if self.commands.currentTool == None else self.commands.currentTool.name - self.commands.update(toolName=newToolName, undoInhibit=False) - else: - self.commands.update(undoInhibit=False) + if (tool.__class__ == ToolObject) and (tool.external, tool.toolState) == (True, tool.TS_NONE): + self.dropTarget.done(); self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool; + self.commands.update(currentTool=self.commands.currentTool) if (patches != None) and (len(patches) > 0): self._snapshotsUpdate() return rc - def onKeyboardInput(self, event): - keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers() - viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect); - if (keyCode == wx.WXK_PAUSE) \ - and (keyModifiers == wx.MOD_SHIFT): - import pdb; pdb.set_trace() - elif keyCode in (wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP): - if keyCode == wx.WXK_DOWN: - if self.brushPos[1] < (self.canvas.size[1] - 1): - self.brushPos = [self.brushPos[0], self.brushPos[1] + 1] - else: - self.brushPos = [self.brushPos[0], 0] - elif keyCode == wx.WXK_LEFT: - if self.brushPos[0] > 0: - self.brushPos = [self.brushPos[0] - 1, self.brushPos[1]] - else: - self.brushPos = [self.canvas.size[0] - 1, self.brushPos[1]] - elif keyCode == wx.WXK_RIGHT: - if self.brushPos[0] < (self.canvas.size[0] - 1): - self.brushPos = [self.brushPos[0] + 1, self.brushPos[1]] - else: - self.brushPos = [0, self.brushPos[1]] - elif keyCode == wx.WXK_UP: - if self.brushPos[1] > 0: - self.brushPos = [self.brushPos[0], self.brushPos[1] - 1] - else: - self.brushPos = [self.brushPos[0], self.canvas.size[1] - 1] - self.commands.update(cellPos=self.brushPos) - self.applyTool(eventDc, True, None, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect) - elif (chr(event.GetUnicodeKey()) == " ") \ - and (self.commands.currentTool.__class__ != ToolText): - if not self.applyTool(eventDc, True, None, None, event.GetModifiers(), self.brushPos, False, True, False, self.commands.currentTool, viewRect): - event.Skip() - else: - if self.brushPos[0] < (self.canvas.size[0] - 1): - self.brushPos = [self.brushPos[0] + 1, self.brushPos[1]] - else: - self.brushPos = [0, self.brushPos[1]] - self.commands.update(cellPos=self.brushPos) - self.applyTool(eventDc, True, None, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect) - else: - if not self.applyTool(eventDc, False, chr(event.GetUnicodeKey()), keyCode, keyModifiers, None, None, None, None, self.commands.currentTool, viewRect): - event.Skip() + def cursorHide(self, eventDc=None, eventDcResetOrigin=True, reset=False): + if eventDc == None: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) + if eventDcResetOrigin: + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + patchesCursor = self.canvas.popCursor(reset=reset); patchesCursor_ = []; + for cursorCell in [p[:2] for p in patchesCursor]: + if (cursorCell[0] < self.canvas.size[0]) and (cursorCell[1] < self.canvas.size[1]): + patchesCursor_ += [[*cursorCell, *self.canvas.map[cursorCell[1]][cursorCell[0]]]] + if len(patchesCursor_) > 0: + self.backend.drawPatches(self.canvas, eventDc, patchesCursor_, False) + if eventDcResetOrigin: + eventDc.SetDeviceOrigin(*eventDcOrigin) + return eventDc + + def cursorShow(self, eventDc=None, eventDcResetOrigin=True, patchesCursor=None): + if eventDc == None: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) + if eventDcResetOrigin: + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + if patchesCursor == None: + patchesCursor = self.canvas.popCursor(reset=False) + elif len(patchesCursor) > 0: + self.canvas.pushCursor(patchesCursor) + if (patchesCursor != None) and (len(patchesCursor) > 0): + self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) + if eventDcResetOrigin: + eventDc.SetDeviceOrigin(*eventDcOrigin) def onEnterWindow(self, event): self.lastCellState = None - def onErase(self, event): - pass + def onKeyboardInput(self, event): + keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers() + viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect); + if (keyCode, keyModifiers,) == (wx.WXK_PAUSE, wx.MOD_SHIFT,): + pdb.set_trace() + elif keyCode in (wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP): + if keyCode == wx.WXK_DOWN: + self.brushPos[1] = (self.brushPos[1] + 1) % self.canvas.size[1] + elif keyCode == wx.WXK_LEFT: + self.brushPos[0] = (self.brushPos[0] - 1) if (self.brushPos[0] > 0) else (self.canvas.size[0] - 1) + elif keyCode == wx.WXK_RIGHT: + self.brushPos[0] = (self.brushPos[0] + 1) % self.canvas.size[0] + elif keyCode == wx.WXK_UP: + self.brushPos[1] = (self.brushPos[1] - 1) if (self.brushPos[1] > 0) else (self.canvas.size[1] - 1) + self.commands.update(cellPos=self.brushPos) + self.applyTool(eventDc, True, None, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect) + elif (chr(event.GetUnicodeKey()) == " ") and (self.commands.currentTool.__class__ != ToolText): + if not self.applyTool(eventDc, True, None, None, event.GetModifiers(), self.brushPos, False, True, False, self.commands.currentTool, viewRect): + event.Skip() + else: + self.brushPos[0] = (self.brushPos[0] + 1) if (self.brushPos[0] < (self.canvas.size[0] - 1)) else 0 + self.commands.update(cellPos=self.brushPos) + self.applyTool(eventDc, True, None, None, None, self.brushPos, False, False, False, self.commands.currentTool, viewRect) + elif not self.applyTool(eventDc, False, chr(event.GetUnicodeKey()), keyCode, keyModifiers, None, None, None, None, self.commands.currentTool, viewRect): + event.Skip() def onLeaveWindow(self, event): if False: - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) - self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) + self.cursorHide() self.lastCellState = None def onMouseInput(self, event): @@ -266,18 +280,19 @@ class RoarCanvasWindow(GuiWindow): newFontSize = self.backend.fontSize + delta if newFontSize > 0: self.Freeze() - self.backend.fontSize = newFontSize - self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize; + self.backend.fontSize = newFontSize; self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize; super().resize([a * b for a, b in zip(self.canvas.size, self.backend.cellSize)]) - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); patches = [] for numRow in range(self.canvas.size[1]): for numCol in range(len(self.canvas.map[numRow])): patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + self.cursorHide(eventDc, False, False) self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) + self.cursorShow(eventDc, False) eventDc.SetDeviceOrigin(*eventDcOrigin) - self.Thaw(); del eventDc; self._eraseBackground(wx.ClientDC(self)); + self.Thaw(); self._windowEraseBackground(wx.ClientDC(self)); elif modifiers == (wx.MOD_CONTROL | wx.MOD_SHIFT): self.commands.canvasCanvasSize(self.commands.canvasCanvasSize, 2, 1 if delta > 0 else 0)(None) elif modifiers == wx.MOD_CONTROL: @@ -289,7 +304,7 @@ class RoarCanvasWindow(GuiWindow): viewRect = self.GetViewStart() eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) self.backend.onPaint(self.GetClientSize(), self, viewRect) - del eventDc; self._eraseBackground(wx.PaintDC(self)); + del eventDc; self._windowEraseBackground(wx.PaintDC(self)); def resize(self, newSize, commitUndo=True, dirty=True, freeze=True): if freeze: @@ -297,76 +312,50 @@ class RoarCanvasWindow(GuiWindow): viewRect = self.GetViewStart() oldSize = [0, 0] if self.canvas.map == None else self.canvas.size deltaSize = [b - a for a, b in zip(oldSize, newSize)] - if self.canvas.resize(newSize, commitUndo): + rc, newCells = self.canvas.resize(self.brushColours, newSize, commitUndo) + if rc: self.backend.resize(newSize); self.scrollStep = self.backend.cellSize; super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)]) + self._applyPatches(None, newCells, None, True, commitUndo=False, dirty=True, hideCursor=False) self.Scroll(*viewRect); self.dirty = dirty; - self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel) + self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.patchesUndoLevel) if commitUndo: self._snapshotsUpdate() if freeze: - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - cursorPatches = self.canvas.journal.popCursor(reset=False) - if len(cursorPatches) > 0: - self.backend.drawPatches(self.canvas, eventDc, cursorPatches, isCursor=True) - eventDc.SetDeviceOrigin(*eventDcOrigin) - del eventDc; self.Thaw(); self._eraseBackground(wx.ClientDC(self)); + self.cursorShow(); self.Thaw(); self._windowEraseBackground(wx.ClientDC(self)); def undo(self, redo=False): - freezeFlag = False - deltaPatches = self.canvas.journal.popUndo() if not redo else self.canvas.journal.popRedo() - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - patchesCursor = self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc, reset=False) - patches = [] - for patch in deltaPatches: - if patch == None: - continue - elif patch[0] == "resize": + freezeFlag, patches, patchesDelta = False, [], self.canvas.popUndo(redo) + for patch in [p for p in patchesDelta if p != None]: + if patch[0] == "resize": if not freezeFlag: self.Freeze(); freezeFlag = True; - eventDc = None; self.resize(patch[1:], False, freeze=False); + self.resize(patch[1:], False, freeze=False) else: - self.canvas._commitPatch(patch); patches += [patch]; - if eventDc == None: - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) - if len(patchesCursor): - self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) - eventDc.SetDeviceOrigin(*eventDcOrigin) + patches += [patch] + eventDc = self._applyPatches(None, patches, None, True, commitUndo=False, hideCursor=False) + self.cursorShow(eventDc, True, None) if freezeFlag: - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - cursorPatches = self.canvas.journal.popCursor(reset=False) - if len(cursorPatches) > 0: - self.backend.drawPatches(self.canvas, eventDc, cursorPatches, isCursor=True) - eventDc.SetDeviceOrigin(*eventDcOrigin) - del eventDc; self.Thaw(); self._eraseBackground(wx.ClientDC(self)); + self.Thaw(); self._windowEraseBackground(wx.ClientDC(self)); def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True): - self.resize(newSize, commitUndo, dirty) - self.canvas.update(newSize, newCanvas) - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); patches = []; + self.resize(newSize, commitUndo, dirty); self.canvas.update(newSize, newCanvas); + patches = [] for numRow in range(newSize[1]): for numCol in range(newSize[0]): patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] - self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) - eventDc.SetDeviceOrigin(*eventDcOrigin) + self._applyPatches(None, patches, None, True, dirty=False) def __init__(self, backend, canvas, commands, parent, pos, size): - super().__init__(parent, pos) - self.lastMouseState, self.size = [False, False, False], size - self.backend, self.canvas, self.commands, self.parentFrame = backend(self.size), canvas, commands(self, parent), parent - self.brushColours, self.brushPos, self.brushSize, self.dirty, self.lastCellState = [3, 9], [0, 0], [1, 1], False, None - self.popupEventDc = None - self.dropTarget = RoarCanvasWindowDropTarget(self) - self.SetDropTarget(self.dropTarget) + super().__init__(parent, pos); self.parent, self.size = parent, size; + self.backend, self.canvas, self.commands = backend(self.size), canvas, commands(self, parent) self.bgBrush, self.bgPen = wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID), wx.Pen(self.GetBackgroundColour(), 1) - self.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase) - self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel) + self.brushColours, self.brushPos, self.brushSize, = [3, -1], [0, 0], [1, 1] + self.dirty, self.lastCellState, self.lastMouseState = False, None, [False, False, False] + self.dropTarget, self.popupEventDc = RoarCanvasWindowDropTarget(self), None + for event, handler in ((wx.EVT_ERASE_BACKGROUND, lambda event: None,), (wx.EVT_MOUSEWHEEL, self.onMouseWheel,),): + self.Bind(event, handler) + self.SetDropTarget(self.dropTarget) self._snapshotsReset() # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libroar/RoarWindowAbout.py b/libroar/RoarWindowAbout.py index fa725e8..33d2f9d 100644 --- a/libroar/RoarWindowAbout.py +++ b/libroar/RoarWindowAbout.py @@ -14,6 +14,7 @@ class RoarWindowAbout(wx.Dialog): def __init__(self, parent, minSize=(320, 300), title="About roar"): super().__init__(parent, size=minSize, title=title) self.panel, self.sizer, self.sizerV = wx.Panel(self), wx.FlexGridSizer(2, 2, 4, 4), wx.BoxSizer(wx.VERTICAL) + self.panel.SetBackgroundColour(wx.Colour(0, 0, 0)); self.panel.SetForegroundColour(wx.Colour(0, 187, 0)); logoPathNames = glob(os.path.join("assets", "images", "logo*.bmp")) logoPathName = logoPathNames[random.randint(0, len(logoPathNames) - 1)] diff --git a/libroar/RoarWindowMelp.py b/libroar/RoarWindowMelp.py index d2e84a6..37605d0 100644 --- a/libroar/RoarWindowMelp.py +++ b/libroar/RoarWindowMelp.py @@ -13,6 +13,7 @@ class RoarWindowMelp(wx.Dialog): def __init__(self, parent, minSize=(320, 300), title="melp?"): super().__init__(parent, size=minSize, title=title) self.panel, self.sizer = wx.Panel(self), wx.BoxSizer(wx.VERTICAL) + self.panel.SetBackgroundColour(wx.Colour(0, 0, 0)); self.panel.SetForegroundColour(wx.Colour(0, 187, 0)); with open(os.path.join("assets", "text", "melp.txt"), "r") as fileObject: helpLabel = "".join(fileObject.readlines()) diff --git a/librtl/Rtl.py b/librtl/Rtl.py index fb2e6a9..11465ee 100644 --- a/librtl/Rtl.py +++ b/librtl/Rtl.py @@ -10,9 +10,9 @@ import re def flatten(l): return [li for l_ in l for li in l_] -def natural_sort(l): - convert = lambda text: int(text) if text.isdigit() else text.lower() - alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] +def natural_sort(l): + convert = lambda text: int(text) if text.isdigit() else text.lower() + alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] return sorted(l, key=alphanum_key) # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolCircle.py b/libtools/ToolCircle.py index 67b6e2c..e3ee459 100644 --- a/libtools/ToolCircle.py +++ b/libtools/ToolCircle.py @@ -34,12 +34,6 @@ class ToolCircle(Tool): elif ((numRow > 0) and (cells[numRow][numCol][0] > cells[numRow - 1][-1][0])) \ or ((numRow < len(cells)) and (cells[numRow][numCol][0] > cells[numRow + 1][-1][0])): patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 0, " "] - elif brushColours[1] == -1: - if (cells[numRow][numCol][0] < canvas.size[0]) \ - and (cells[numRow][numCol][1] < canvas.size[1]): - patch = [cells[numRow][numCol][0], cells[numRow][numCol][1], *canvas.map[cells[numRow][numCol][1]][cells[numRow][numCol][0]]] - else: - patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "] else: patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "] patches += [patch] diff --git a/libtools/ToolRect.py b/libtools/ToolRect.py index 75539a0..ea59af8 100644 --- a/libtools/ToolRect.py +++ b/libtools/ToolRect.py @@ -18,17 +18,9 @@ class ToolRect(Tool): for brushCol in range((rect[2] - rect[0]) + 1): if (brushCol in [0, (rect[2] - rect[0])]) or (brushRow in [0, (rect[3] - rect[1])]): patchColours = [brushColours[0]] * 2 - patch = [rect[0] + brushCol, rect[1] + brushRow, *patchColours, 0, " "] - elif brushColours[1] == -1: - if ((rect[0] + brushCol) < canvas.size[0]) \ - and ((rect[1] + brushRow) < canvas.size[1]): - patch = [rect[0] + brushCol, rect[1] + brushRow, *canvas.map[rect[1] + brushRow][rect[0] + brushCol]] - else: - patch = [rect[0] + brushCol, rect[1] + brushRow, -1, -1, 0, " "] else: patchColours = [brushColours[1]] * 2 - patch = [rect[0] + brushCol, rect[1] + brushRow, *patchColours, 0, " "] - patches += [patch] + patches += [[rect[0] + brushCol, rect[1] + brushRow, *patchColours, 0, " "]] return patches def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): diff --git a/libtools/ToolText.py b/libtools/ToolText.py index 47c833e..e662444 100644 --- a/libtools/ToolText.py +++ b/libtools/ToolText.py @@ -70,12 +70,17 @@ class ToolText(Tool): rc = True elif keyCode == wx.WXK_CONTROL_V: rc = True - if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) \ - and wx.TheClipboard.Open(): + if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) and wx.TheClipboard.Open(): inBuffer = wx.TextDataObject() if wx.TheClipboard.GetData(inBuffer): + brushPosOriginX = brushPos[0] for inBufferChar in list(inBuffer.GetText()): - if not re.match(self.arabicCombiningRegEx, inBufferChar): + if inBufferChar in set("\r\n"): + if brushPos[1] < (canvas.size[1] - 1): + brushPos[0], brushPos[1] = brushPosOriginX, brushPos[1] + 1 + else: + brushPos[0], brushPos[1] = brushPosOriginX, 0 + elif not re.match(self.arabicCombiningRegEx, inBufferChar): rc_, patches_ = self._processKeyChar(brushColours, brushPos, canvas, inBufferChar, 0) patches += patches_ rc = True if rc_ else rc diff --git a/roar.py b/roar.py index e306aa4..88b0782 100755 --- a/roar.py +++ b/roar.py @@ -20,7 +20,7 @@ def main(*argv): os.makedirs(localConfirName) wxApp, roarClient = wx.App(False), RoarClient(None) argv0, argv = argv[0], argv[1:] - roarClient.canvasPanel.commands._loadLastDir(); roarClient.canvasPanel.commands._loadRecent(); + roarClient.canvasPanel.commands._recentDirLoad(); roarClient.canvasPanel.commands._recentLoad(); if len(argv) >= 1: if (len(argv) >= 2) and (argv[1].endswith(".lst")): roarClient.assetsWindow._load_list(argv[1]) @@ -30,7 +30,7 @@ def main(*argv): if rc: roarClient.canvasPanel.update(roarClient.canvasPanel.canvas.importStore.inSize, False, roarClient.canvasPanel.canvas.importStore.outMap, dirty=False) roarClient.canvasPanel.commands.update(pathName=argv[0], undoLevel=-1) - roarClient.canvasPanel.commands._pushRecent(argv[0]) + roarClient.canvasPanel.commands._recentPush(argv[0]) roarClient.canvasPanel.commands.canvasTool(roarClient.canvasPanel.commands.canvasTool, 1)(None) else: print("error: {}".format(error), file=sys.stderr)