Cleanup, bugfixes & C++ backend implementation.

1) {About,Melp?} window: switch to green on black.
2) Assets window: scroll assets list on selected item update w/ <Cursor> or on deletion.
3) Canvas window: change default brush colours to [3, -1].
4) Canvas window: copy canvas cells given transparent cells from tools.
5) Canvas window: don't disable {re,un}do during object tool usage.
6) Canvas window: don't hide cursor during {re,un}do.
7) Canvas window: draw new cells using current brush background colour on resize.
8) Canvas window: fix memory leak on cell size updating.
9) Text tool: process [\r\n] in text pasted from clipboard.

assets/audio/roar{vap0r[1-8],viking[1-5]}.wav: added.
assets/text/README.txt: updated.
assets/tools/AnsiToMiRCART.py: added (for spoke.)
assets/tools/deploy-python.sh: updated.
This commit is contained in:
Lucio Andrés Illanes Albornoz 2019-10-24 21:14:00 +02:00
parent 90840bd0a0
commit 0c66f94797
39 changed files with 1021 additions and 523 deletions

2
.vscode/tasks.json vendored
View File

@ -6,7 +6,7 @@
{ {
"label": "Build libgui/GuiCanvasWxBackendFast.pyd", "label": "Build libgui/GuiCanvasWxBackendFast.pyd",
"type": "shell", "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": [ "problemMatcher": [
"$msCompile" "$msCompile"
] ]

BIN
assets/audio/roarvap0r1.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r2.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r3.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r4.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r5.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r6.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r7.wav Normal file

Binary file not shown.

BIN
assets/audio/roarvap0r8.wav Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,7 @@
# roar.py -- mIRC art editor for Windows & Linux (WIP) # 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` `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:
![Screenshot](https://github.com/lalbornoz/roar/raw/master/assets/images/roar.png "Screenshot") ![Screenshot](https://github.com/lalbornoz/roar/raw/master/assets/images/roar.png "Screenshot")

View File

@ -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} 2) GUI: edit asset in new canvas, import from {canvas,object}
3) GUI: implement GuiCanvasWxBackendFast.c{,c} 3) GUI: implement GuiCanvasWxBackendFast.c{,c}
4) GUI: select all 4) GUI: select all
@ -6,8 +6,10 @@
6) Operators: copy, cut, delete, insert from, paste 6) Operators: copy, cut, delete, insert from, paste
7) Operators: crop, scale, shift, slice operators 7) Operators: crop, scale, shift, slice operators
8) Tools: measure, triangle, unicode block elements tool 8) Tools: measure, triangle, unicode block elements tool
9) Tools: object tool: reimplement cloning correctly outside of object tool 9) Tools: object tool: allow application of arbitrary tool to selection before setting
10) Tools: text tool: finish Arabic/RTL text implementation 10) Tools: object tool: reimplement cloning correctly outside of object tool
11) Tools: text tool: implicitly draw (text) w/ bg -1, toggle drawing actual brushColours[1] mode w/ RMB 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 vim:ff=dos tw=0

View File

@ -1,17 +1,17 @@
 3,6▟6,3▝ 3,6▟6,3▝15
 3,6▟6,3▜▛3,6▟6,3▝ 3,6▟6,3▜▛3,6▟6,3▝15
 3,6▟6,3▝▜3,6▟6,3▝▜▛3,6▟6,3▝ 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▝ 3,6▟6,3▝3,8/\3,6▟6,3▜▛3,6▟6,3▝3,8/\3,6▟6,3▝15
 9,1/\ 9,1/ 6,3▝▜▛3,6▟6,3▝8,8 3,6▟6,3▝▜▛3,6▟6,3▝ 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) 6,1( 6,3▜▛3,6▟6,3▝8,8 3o _ o8 3,6▟6,3▝▜▛3,6▟ 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( \ 9,1) 6,3▝▜▛▜8,8 6(_3Y6_)6,3▛▝▛3,6▟6,3▝ 9,1( \15 9,1)15 6,3▝▜▛▜8,8 6(_3Y6_)6,3▛▝▛3,6▟6,3▝15
 6,1|(__)/ 3,6▟6,3▝▜▛8,8 6\_/6,3▜▛3,6▟6,3▝ 6,1|(__)/15 3,6▟6,3▝▜▛8,8 6\_/6,3▜▛3,6▟6,3▝15
 1,3\\ 1,3.'0s3 6▜▛3,6▟6,3▝ 1,3\\15 1,3.'0s3 6▜▛3,6▟6,3▝15
 1,6\\ 1,6/6 0p6 1\ \ 6,3▜▛ 1,6\\15 1,6/6 0p6 1\ \ 6,3▜▛15
 1,3\\/3 0o3 1\3 1\ \6▜ 1,3\\/3 0o3 1\3 1\ \6▜15
 1,6\6 0k6 1/)___|_|6,3▛ 1,6\6 0k6 1/)___|_|6,3▛15
 1,3(_0e1__/__))) )))6▛ 1,3(_0e1__/__))) )))6▛15
 12,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ 12,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲15
 12,1 12,115
 2,1 2,115
 2,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲ 2,1╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲╲15

31
assets/tools/AnsiToMiRCART.py Executable file
View File

@ -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 <lucio@lucioillanes.de>
# 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: {} <ANSI input file pathname> <mIRC art output file pathname>".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

View File

@ -32,6 +32,9 @@ deploy() {
-not -path '*/__pycache__/*' \ -not -path '*/__pycache__/*' \
-not -path '*/__pycache__' \ -not -path '*/__pycache__' \
-not -path './librtl/ImgurApiKey.py' \ -not -path './librtl/ImgurApiKey.py' \
-not -name '*.exp' \
-not -name '*.lib' \
-not -name '*.obj' \
-not -name '*.sw*' \ -not -name '*.sw*' \
-not -name "${0##*/}" |\ -not -name "${0##*/}" |\
cpio --quiet -dLmp "${_release_dname}"; cpio --quiet -dLmp "${_release_dname}";

View File

@ -6,7 +6,6 @@
from CanvasExportStore import CanvasExportStore from CanvasExportStore import CanvasExportStore
from CanvasImportStore import CanvasImportStore from CanvasImportStore import CanvasImportStore
from CanvasJournal import CanvasJournal
class Canvas(): class Canvas():
def _commitPatch(self, patch): def _commitPatch(self, patch):
@ -18,11 +17,51 @@ class Canvas():
else: else:
patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell]; patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell];
if commitUndo: if commitUndo:
self.journal.updateCurrentDeltas(patch, patchDelta) self.updateCurrentDeltas(patch, patchDelta)
self._commitPatch(patch) self._commitPatch(patch)
return True 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 newSize != self.size:
if self.map == None: if self.map == None:
self.map, oldSize = [], [0, 0] self.map, oldSize = [], [0, 0]
@ -30,41 +69,43 @@ class Canvas():
oldSize = self.size oldSize = self.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)] deltaSize = [b - a for a, b in zip(oldSize, newSize)]
if commitUndo: if commitUndo:
self.journal.begin() self.begin()
undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize] undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize]
self.journal.updateCurrentDeltas(redoPatches, undoPatches) self.updateCurrentDeltas(redoPatches, undoPatches)
if deltaSize[0] < 0: if deltaSize[0] < 0:
for numRow in range(oldSize[1]): for numRow in range(oldSize[1]):
if commitUndo: if commitUndo:
for numCol in range((oldSize[0] + deltaSize[0]), oldSize[0]): 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] del self.map[numRow][-1:(deltaSize[0]-1):-1]
else: else:
for numRow in range(oldSize[1]): 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]): for numNewCol in range(oldSize[0], newSize[0]):
if commitUndo: if commitUndo:
self.journal.updateCurrentDeltas([numNewCol, numRow, 1, 1, 0, " "], None) self.updateCurrentDeltas([numNewCol, numRow, *brushColours, 0, " "], None)
self.applyPatch([numNewCol, numRow, 1, 1, 0, " "], False) newCells += [[numNewCol, numRow, *brushColours, 0, " "]]
self.applyPatch([numNewCol, numRow, *brushColours, 0, " "], False)
if deltaSize[1] < 0: if deltaSize[1] < 0:
if commitUndo: if commitUndo:
for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]): for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]):
for numCol in range(oldSize[0] + deltaSize[0]): 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] del self.map[-1:(deltaSize[1]-1):-1]
else: else:
for numNewRow in range(oldSize[1], newSize[1]): 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]): for numNewCol in range(newSize[0]):
if commitUndo: if commitUndo:
self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None) self.updateCurrentDeltas([numNewCol, numNewRow, *brushColours, 0, " "], None)
self.applyPatch([numNewCol, numNewRow, 1, 1, 0, " "], False) newCells += [[numNewCol, numNewRow, *brushColours, 0, " "]]
self.applyPatch([numNewCol, numNewRow, *brushColours, 0, " "], False)
self.size = newSize self.size = newSize
if commitUndo: if commitUndo:
self.journal.end() self.end()
return True return True, newCells
else: else:
return False return False, newCells
def update(self, newSize, newCanvas=None): def update(self, newSize, newCanvas=None):
for numRow in range(self.size[1]): for numRow in range(self.size[1]):
@ -73,7 +114,15 @@ class Canvas():
and (numRow < len(newCanvas)) and (numCol < len(newCanvas[numRow])): and (numRow < len(newCanvas)) and (numCol < len(newCanvas[numRow])):
self._commitPatch([numCol, numRow, *newCanvas[numRow][numCol]]) 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): 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 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -1,62 +0,0 @@
#!/usr/bin/env python3
#
# CanvasJournal.py
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
#
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

View File

@ -4,10 +4,14 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de> # Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
# #
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 from GuiCanvasColours import Colours
import GuiCanvasWxBackendFast import math, os, platform, Rtl, wx
import math, os, platform, wx
class GuiBufferedDC(wx.MemoryDC): class GuiBufferedDC(wx.MemoryDC):
def __del__(self): def __del__(self):
@ -143,20 +147,7 @@ class GuiCanvasWxBackend():
brush, pen = self._brushesBlend[bg][14], self._pensBlend[bg][14] brush, pen = self._brushesBlend[bg][14], self._pensBlend[bg][14]
return brush, pen 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): def drawPatches(self, canvas, eventDc, patches, isCursor=False):
GuiCanvasWxBackendFast.drawPatches()
patchesRender = [] patchesRender = []
for patch in patches: for patch in patches:
point = patch[:2] point = patch[:2]
@ -166,6 +157,8 @@ class GuiCanvasWxBackend():
patchesRender += [patchReshaped] patchesRender += [patchReshaped]
else: else:
patchesRender += [patch] 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) numPatch, textBg = 0, wx.Colour(0, 0, 0, 0)
rectangles, pens, brushes = [None] * len(patchesRender), [None] * len(patchesRender), [None] * len(patchesRender) rectangles, pens, brushes = [None] * len(patchesRender), [None] * len(patchesRender), [None] * len(patchesRender)
textList, coords, foregrounds, backgrounds = [], [], [], [] textList, coords, foregrounds, backgrounds = [], [], [], []
@ -173,10 +166,10 @@ class GuiCanvasWxBackend():
for patchRender in patchesRender: for patchRender in patchesRender:
if (patchRender[5] == " ") and (patchRender[3] == -1): if (patchRender[5] == " ") and (patchRender[3] == -1):
text, textFg = "", wx.Colour(0, 0, 0, 255) 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:]] 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])) 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:]] 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])) text, textFg = "_", wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3]))
elif patchRender[5] != " ": elif patchRender[5] != " ":
@ -228,9 +221,11 @@ class GuiCanvasWxBackend():
oldDc = wx.MemoryDC(); oldDc.SelectObject(self.canvasBitmap); oldDc = wx.MemoryDC(); oldDc.SelectObject(self.canvasBitmap);
newDc = wx.MemoryDC(); newBitmap = wx.Bitmap(winSize); newDc.SelectObject(newBitmap); newDc = wx.MemoryDC(); newBitmap = wx.Bitmap(winSize); newDc.SelectObject(newBitmap);
newDc.Blit(0, 0, *self.canvasBitmap.GetSize(), oldDc, 0, 0) 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.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): def xlateEventPoint(self, event, eventDc, viewRect):
eventPoint = event.GetLogicalPosition(eventDc) eventPoint = event.GetLogicalPosition(eventDc)
@ -244,6 +239,8 @@ class GuiCanvasWxBackend():
self._finiBrushesAndPens() self._finiBrushesAndPens()
def __init__(self, canvasSize, fontName="Dejavu Sans Mono", fontPathName=os.path.join("assets", "fonts", "DejaVuSansMono.ttf"), fontSize=8): 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._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 self.canvasBitmap, self.cellSize, self.fontName, self.fontPathName, self.fontSize = None, None, fontName, fontPathName, fontSize
if platform.system() == "Windows": if platform.system() == "Windows":

View File

@ -1,25 +0,0 @@
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
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);
}

View File

@ -0,0 +1,577 @@
/*
* GuiCanvasWxBackendFast.cpp
* Copyright (c) 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
*/
#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 <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>
#include <chrono>
#include <cmath>
#include <map>
#include <vector>
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
/*
* 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<std::vector<uint8_t>> COLOUR_LIST;
typedef std::vector<std::vector<COLOUR>> CHAR_MAP_ITEM;
typedef std::map<wchar_t, CHAR_MAP_ITEM> 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<double>)(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;
}

View File

@ -165,7 +165,7 @@ class RoarAssetsWindow(GuiMiniFrame):
def resize(self, canvas, newSize): def resize(self, canvas, newSize):
oldSize = [0, 0] if canvas.map == None else canvas.size oldSize = [0, 0] if canvas.map == None else canvas.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)] 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)] 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); self.panelCanvas.SetMinSize(panelSize); self.panelCanvas.SetSize(wx.DefaultCoord, wx.DefaultCoord, *panelSize);
curWindow = self.panelCanvas curWindow = self.panelCanvas
@ -228,6 +228,7 @@ class RoarAssetsWindow(GuiMiniFrame):
id = +1 if keyCode == wx.WXK_DOWN else -1 id = +1 if keyCode == wx.WXK_DOWN else -1
self.currentIndex = (self.currentIndex + id) % len(self.canvasList) self.currentIndex = (self.currentIndex + id) % len(self.canvasList)
self.listView.Select(self.currentIndex, on=1) self.listView.Select(self.currentIndex, on=1)
self.listView.EnsureVisible(self.currentIndex)
else: else:
index, rc = self.listView.GetFirstSelected(), False index, rc = self.listView.GetFirstSelected(), False
if index != -1: if index != -1:
@ -304,6 +305,8 @@ class RoarAssetsWindow(GuiMiniFrame):
break break
while len(items): while len(items):
self._removeAsset(items[0]); del items[0]; items = [i - 1 for i in 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): def onSaveList(self, event):
rc = True rc = True

View File

@ -5,12 +5,13 @@
# #
from GuiCanvasColours import Colours from GuiCanvasColours import Colours
from GuiFrame import NID_TOOLBAR_HSEP from GuiFrame import NID_MENU_SEP, NID_TOOLBAR_HSEP
from RoarCanvasCommandsEdit import RoarCanvasCommandsEdit from RoarCanvasCommandsEdit import RoarCanvasCommandsEdit
from RoarCanvasCommandsFile import RoarCanvasCommandsFile from RoarCanvasCommandsFile import RoarCanvasCommandsFile
from RoarCanvasCommandsHelp import RoarCanvasCommandsHelp from RoarCanvasCommandsHelp import RoarCanvasCommandsHelp
from RoarCanvasCommandsOperators import RoarCanvasCommandsOperators from RoarCanvasCommandsOperators import RoarCanvasCommandsOperators
from RoarCanvasCommandsTools import RoarCanvasCommandsTools from RoarCanvasCommandsTools import RoarCanvasCommandsTools
from ToolObject import ToolObject
import os, wx import os, wx
class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCanvasCommandsTools, RoarCanvasCommandsOperators, RoarCanvasCommandsHelp): 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.canvasColour, RoarCanvasCommandsEdit.canvasColourAlpha, 1.0)
_initColourBitmaps_(RoarCanvasCommandsEdit.canvasColourBackground, RoarCanvasCommandsEdit.canvasColourAlphaBackground, 1.5) _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): def update(self, **kwargs):
self.lastPanelState.update(kwargs); textItems = []; self.lastPanelState.update(kwargs); textItems = [];
if "cellPos" in self.lastPanelState: 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] 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] toolBarBg = self.parentFrame.toolBarItemsById[self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"]][0]
if self.lastPanelState["colours"][0] != -1: if self.lastPanelState["colours"][0] != -1:
toolBar.ToggleTool(self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"], True) toolBar.ToggleTool(self.canvasColour(self.canvasColour, self.lastPanelState["colours"][0]).attrDict["id"], True); toolBar.Refresh()
toolBar.Refresh()
else: else:
toolBar.ToggleTool(self.canvasColourAlpha(self.canvasColourAlpha, 0).attrDict["id"], True) toolBar.ToggleTool(self.canvasColourAlpha(self.canvasColourAlpha, 0).attrDict["id"], True); toolBar.Refresh()
toolBar.Refresh()
if self.lastPanelState["colours"][1] != -1: if self.lastPanelState["colours"][1] != -1:
toolBarBg.ToggleTool(self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"], True) toolBarBg.ToggleTool(self.canvasColourBackground(self.canvasColourBackground, self.lastPanelState["colours"][1]).attrDict["id"], True); toolBarBg.Refresh()
toolBarBg.Refresh()
else: else:
toolBarBg.ToggleTool(self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0).attrDict["id"], True) toolBarBg.ToggleTool(self.canvasColourAlphaBackground(self.canvasColourAlphaBackground, 0).attrDict["id"], True); toolBarBg.Refresh()
toolBarBg.Refresh()
if "pathName" in self.lastPanelState: if "pathName" in self.lastPanelState:
if self.lastPanelState["pathName"] != None: if self.lastPanelState["pathName"] != None:
basePathName = os.path.basename(self.lastPanelState["pathName"]) basePathName = os.path.basename(self.lastPanelState["pathName"])
@ -71,27 +127,25 @@ class RoarCanvasCommands(RoarCanvasCommandsFile, RoarCanvasCommandsEdit, RoarCan
self.parentFrame.SetTitle("{} - roar".format(basePathName)) self.parentFrame.SetTitle("{} - roar".format(basePathName))
else: else:
self.parentFrame.SetTitle("roar") self.parentFrame.SetTitle("roar")
if "toolName" in self.lastPanelState: if "currentTool" in self.lastPanelState:
textItems.append("T: {}".format(self.lastPanelState["toolName"])) self.parentFrame.menuItemsById[self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"]].Check(True)
if ("operator" in self.lastPanelState) \ toolBar = self.parentFrame.toolBarItemsById[self.canvasTool.attrList[self.lastPanelState["currentToolIdx"]]["id"]][0]
and (self.lastPanelState["operator"] != None): 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"])) textItems.append("O: {}".format(self.lastPanelState["operator"]))
if "dirty" in self.lastPanelState \ if ("dirty" in self.lastPanelState) and self.lastPanelState["dirty"]:
and self.lastPanelState["dirty"]:
textItems.append("*") textItems.append("*")
if "backupStatus" in self.lastPanelState \ if ("backupStatus" in self.lastPanelState) and (self.lastPanelState["backupStatus"] == True):
and self.lastPanelState["backupStatus"] == True:
textItems.append("Saving backup...") textItems.append("Saving backup...")
self.parentFrame.statusBar.SetStatusText(" | ".join(textItems)) self.parentFrame.statusBar.SetStatusText(" | ".join(textItems))
if ("undoInhibit" in self.lastPanelState) \ if "undoLevel" 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 (self.lastPanelState["undoLevel"] >= 0) \ 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) self.parentFrame.menuItemsById[self.canvasUndo.attrDict["id"]].Enable(True)
toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0] toolBar = self.parentFrame.toolBarItemsById[self.canvasUndo.attrDict["id"]][0]
toolBar.EnableTool(self.canvasUndo.attrDict["id"], True); toolBar.Refresh(); 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(); toolBar.EnableTool(self.canvasRedo.attrDict["id"], False); toolBar.Refresh();
def __init__(self, parentCanvas, parentFrame): 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 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 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -4,7 +4,7 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de> # Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
# #
from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator, NID_MENU_SEP from GuiFrame import GuiCommandDecorator, GuiCommandListDecorator, GuiSelectDecorator
import wx import wx
class RoarCanvasCommandsEdit(): class RoarCanvasCommandsEdit():
@ -194,31 +194,10 @@ class RoarCanvasCommandsEdit():
@GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False) @GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False)
def canvasRedo(self, event): 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) @GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False)
def canvasUndo(self, event): def canvasUndo(self, event):
self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel); self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.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 = ()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -5,14 +5,12 @@
# #
try: try:
from ImgurApiKey import ImgurApiKey from ImgurApiKey import ImgurApiKey; haveImgurApiKey = True;
haveImgurApiKey = True
except ImportError: except ImportError:
haveImgurApiKey = False haveImgurApiKey = False
try: try:
import base64, json, requests, urllib.request import base64, json, requests, urllib.request; haveUrllib = True;
haveUrllib = True
except ImportError: except ImportError:
haveUrllib = False haveUrllib = False
@ -35,8 +33,7 @@ class RoarCanvasCommandsFile():
self.canvasPathName = None self.canvasPathName = None
self.parentCanvas._snapshotsReset() self.parentCanvas._snapshotsReset()
self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1) self.update(dirty=self.parentCanvas.dirty, pathName=self.canvasPathName, undoLevel=-1)
self.parentCanvas.canvas.journal.resetCursor() self.parentCanvas.canvas.resetCursor(); self.parentCanvas.canvas.resetUndo();
self.parentCanvas.canvas.journal.resetUndo()
except FileNotFoundError as e: except FileNotFoundError as e:
rc, error, newMap, newPathName, newSize = False, str(e), None, None, None rc, error, newMap, newPathName, newSize = False, str(e), None, None, None
if not rc: if not rc:
@ -52,28 +49,10 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL: if dialog.ShowModal() == wx.ID_CANCEL:
return False, None return False, None
elif self._promptSaveChanges(): 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 self._import(f, newDirty, pathName, emptyPathName=emptyPathName)
return False, None 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): def _promptSaveChanges(self):
if self.parentCanvas.dirty: if self.parentCanvas.dirty:
message = "Do you want to save changes to {}?".format(self.canvasPathName if self.canvasPathName != None else "(Untitled)") message = "Do you want to save changes to {}?".format(self.canvasPathName if self.canvasPathName != None else "(Untitled)")
@ -90,7 +69,25 @@ class RoarCanvasCommandsFile():
else: else:
return True 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() menuItemId = wx.NewId()
if not pathName in [l["pathName"] for l in self.lastFiles]: if not pathName in [l["pathName"] for l in self.lastFiles]:
numLastFiles = len(self.lastFiles) if self.lastFiles != None else 0 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.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasOpenRecent(event, pathName), menuItemWindow)
self.lastFiles += [{"menuItemId":menuItemId, "menuItemWindow":menuItemWindow, "pathName":pathName}] self.lastFiles += [{"menuItemId":menuItemId, "menuItemWindow":menuItemWindow, "pathName":pathName}]
if serialise: 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() menuItemId = wx.NewId()
if not (pathName in self.snapshots.keys()): if not (pathName in self.snapshots.keys()):
label = datetime.datetime.fromtimestamp(os.stat(pathName)[stat.ST_MTIME]).strftime("%c") 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.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasRestore(event, pathName), menuItemWindow)
self.snapshots[pathName] = {"menuItemId":menuItemId, "menuItemWindow":menuItemWindow} self.snapshots[pathName] = {"menuItemId":menuItemId, "menuItemWindow":menuItemWindow}
def _resetSnapshots(self): def _snapshotsReset(self):
for pathName in list(self.snapshots.keys()): for pathName in list(self.snapshots.keys()):
self._popSnapshot(pathName) self._snapshotsPop(pathName)
if self.canvasRestore.attrDict["id"] in self.parentFrame.menuItemsById: if self.canvasRestore.attrDict["id"] in self.parentFrame.menuItemsById:
self.parentFrame.menuItemsById[self.canvasRestore.attrDict["id"]].Enable(False) 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) @GuiCommandDecorator("Clear list", "&Clear list", None, None, False)
def canvasClearRecent(self, event): def canvasClearRecent(self, event):
if self.lastFiles != None: if self.lastFiles != None:
@ -155,7 +152,7 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL: if dialog.ShowModal() == wx.ID_CANCEL:
return False return False
else: 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)) self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
with open(outPathName, "w", encoding="utf-8") as outFile: with open(outPathName, "w", encoding="utf-8") as outFile:
self.parentCanvas.canvas.exportStore.exportAnsiFile(self.parentCanvas.canvas.map, self.parentCanvas.canvas.size, 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: if dialog.ShowModal() == wx.ID_CANCEL:
return False return False
else: 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)) self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) self.parentCanvas.cursorHide()
self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc, reset=False)
self.parentCanvas.canvas.exportStore.exportBitmapToPngFile(self.parentCanvas.backend.canvasBitmap, outPathName, wx.BITMAP_TYPE_PNG) self.parentCanvas.canvas.exportStore.exportBitmapToPngFile(self.parentCanvas.backend.canvasBitmap, outPathName, wx.BITMAP_TYPE_PNG)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); self.parentCanvas.cursorShow()
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.SetCursor(wx.Cursor(wx.NullCursor)) self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return True return True
@GuiCommandDecorator("Export to Imgur...", "Export to I&mgur...", None, None, haveImgurApiKey and haveUrllib) @GuiCommandDecorator("Export to Imgur...", "Export to I&mgur...", None, None, haveImgurApiKey and haveUrllib)
def canvasExportImgur(self, event): def canvasExportImgur(self, event):
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) self.parentCanvas.cursorHide()
self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc, reset=False)
rc, status, result = self.parentCanvas.canvas.exportStore.exportBitmapToImgur(self.imgurApiKey, self.parentCanvas.backend.canvasBitmap, "", "", wx.BITMAP_TYPE_PNG) 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); self.parentCanvas.cursorShow()
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.SetCursor(wx.Cursor(wx.NullCursor)) self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
if rc: if rc:
if not wx.TheClipboard.IsOpened(): if not wx.TheClipboard.IsOpened():
@ -261,7 +248,7 @@ class RoarCanvasCommandsFile():
nonlocal newCanvasSize nonlocal newCanvasSize
if newCanvasSize == None: if newCanvasSize == None:
newCanvasSize = list(self.parentCanvas.canvas.size) 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) return (True, "", newMap, None, newCanvasSize)
if self._promptSaveChanges(): if self._promptSaveChanges():
self._import(canvasImportEmpty, False, None) 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) 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 (*.*)|*.*") rc, newPathName = self._importFile(canvasImportmIRC, False, "mIRC art files (*.txt)|*.txt|All Files (*.*)|*.*")
if rc: if rc:
self._pushRecent(newPathName) self._recentPush(newPathName)
@GuiSubMenuDecorator("Open Recent", "Open &Recent", None, None, False) @GuiSubMenuDecorator("Open Recent", "Open &Recent", None, None, False)
def canvasOpenRecent(self, event, pathName=None): def canvasOpenRecent(self, event, pathName=None):
@ -285,7 +272,7 @@ class RoarCanvasCommandsFile():
if not rc: if not rc:
numLastFile = [i for i in range(len(self.lastFiles)) if self.lastFiles[i]["pathName"] == pathName][0] 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.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) @GuiSubMenuDecorator("Restore Snapshot", "Res&tore Snapshot", None, None, False)
def canvasRestore(self, event, pathName=None): def canvasRestore(self, event, pathName=None):
@ -328,27 +315,16 @@ class RoarCanvasCommandsFile():
if dialog.ShowModal() == wx.ID_CANCEL: if dialog.ShowModal() == wx.ID_CANCEL:
return False return False
else: 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): if self.canvasSave(event, newDirty=False):
self.update(pathName=self.canvasPathName) self.update(pathName=self.canvasPathName)
self._pushRecent(self.canvasPathName) self._recentPush(self.canvasPathName)
return True return True
else: else:
return False return False
def __init__(self): def __init__(self):
self.imgurApiKey, self.lastFiles, self.lastDir, self.snapshots = ImgurApiKey.imgurApiKey if haveImgurApiKey else None, [], None, {} self.exiting, self.lastFiles, self.lastDir, self.snapshots = False, [], None, {}
self.accels = () self.imgurApiKey = ImgurApiKey.imgurApiKey if haveImgurApiKey else None
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
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -4,7 +4,7 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de> # Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
# #
from GuiFrame import GuiCommandDecorator, NID_MENU_SEP from GuiFrame import GuiCommandDecorator
from RoarWindowAbout import RoarWindowAbout from RoarWindowAbout import RoarWindowAbout
from RoarWindowMelp import RoarWindowMelp from RoarWindowMelp import RoarWindowMelp
import webbrowser, wx import webbrowser, wx
@ -26,8 +26,4 @@ class RoarCanvasCommandsHelp():
def canvasVisitGitHub(self, event): def canvasVisitGitHub(self, event):
webbrowser.open("https://www.github.com/lalbornoz/roar") 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 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -4,14 +4,13 @@
# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de> # Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz <lucio@lucioillanes.de>
# #
from GuiFrame import GuiCommandListDecorator
from OperatorFlipHorizontal import OperatorFlipHorizontal from OperatorFlipHorizontal import OperatorFlipHorizontal
from OperatorFlipVertical import OperatorFlipVertical from OperatorFlipVertical import OperatorFlipVertical
from OperatorInvert import OperatorInvert from OperatorInvert import OperatorInvert
from OperatorRotate import OperatorRotate from OperatorRotate import OperatorRotate
from OperatorTile import OperatorTile from OperatorTile import OperatorTile
from GuiFrame import GuiCommandListDecorator
from ToolObject import ToolObject from ToolObject import ToolObject
import copy, wx
class RoarCanvasCommandsOperators(): class RoarCanvasCommandsOperators():
@GuiCommandListDecorator(0, "Flip", "&Flip", None, None, None) @GuiCommandListDecorator(0, "Flip", "&Flip", None, None, None)
@ -28,13 +27,6 @@ class RoarCanvasCommandsOperators():
return canvasOperator_ return canvasOperator_
def __init__(self): 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 self.currentOperator, self.operatorState = None, None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -35,32 +35,13 @@ class RoarCanvasCommandsTools():
if self.currentTool != None: if self.currentTool != None:
self.currentTool = self.currentTool() self.currentTool = self.currentTool()
self.currentOperator, self.operatorState = None, None self.currentOperator, self.operatorState = None, None
self.parentFrame.menuItemsById[self.canvasTool.attrList[idx]["id"]].Check(True) self.update(currentTool=self.currentTool, currentToolIdx=idx, operator=None)
if self.currentTool.__class__ == ToolObject: self.parentCanvas.applyTool(None, True, None, None, None, self.parentCanvas.brushPos, False, False, False, self.currentTool, None, force=True)
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)
setattr(canvasTool_, "attrDict", f.attrList[idx]) setattr(canvasTool_, "attrDict", f.attrList[idx])
setattr(canvasTool_, "isSelect", True) setattr(canvasTool_, "isSelect", True)
return canvasTool_ return canvasTool_
def __init__(self): 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 self.currentTool, self.lastTool = None, None
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=0 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=0

View File

@ -9,7 +9,7 @@ from Rtl import natural_sort
from RtlPlatform import getLocalConfPathName from RtlPlatform import getLocalConfPathName
from ToolObject import ToolObject from ToolObject import ToolObject
from ToolText import ToolText 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): class RoarCanvasWindowDropTarget(wx.TextDropTarget):
def done(self): 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)]; 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.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.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.parent.menuItemsById[self.parent.commands.canvasOperator.attrList[4]["id"]].Enable(True)
self.parent.commands.update(toolName=self.parent.commands.currentTool.name) self.parent.commands.update(currentTool=self.parent.commands.currentTool, currentToolIdx=5)
eventDc = self.parent.backend.getDeviceContext(self.parent.GetClientSize(), self.parent, viewRect) eventDc = self.parent.backend.getDeviceContext(self.parent.GetClientSize(), self.parent, viewRect)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); 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) 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; super().__init__(); self.inProgress, self.parent = False, parent;
class RoarCanvasWindow(GuiWindow): 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: if rc:
if eventDc == None:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
if eventDcResetOrigin:
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
if ((patches != None) and (len(patches) > 0)) \ if hideCursor and (((patches != None) and (len(patches) > 0)) or ((patchesCursor != None) and (len(patchesCursor) > 0))):
or ((patchesCursor != None) and (len(patchesCursor) > 0)): self.cursorHide(eventDc, False, True)
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc)
if (patches != None) and (len(patches) > 0): if (patches != None) and (len(patches) > 0):
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
self.dirty = True if not self.dirty else self.dirty; if dirty and not self.dirty:
self.canvas.journal.begin() self.dirty = True
if commitUndo:
self.canvas.begin()
for patch in patches if patches != None else []: for patch in patches if patches != None else []:
self.canvas.applyPatch(patch, commitUndo=True) self.canvas.applyPatch(patch, commitUndo=commitUndo)
self.canvas.journal.end() if commitUndo:
if patchesCursor != None: self.canvas.end()
self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) if hideCursor and (patchesCursor != None):
if len(patchesCursor) > 0: self.cursorShow(eventDc, False, patchesCursor=patchesCursor)
self.canvas.journal.pushCursor(patchesCursor) if eventDcResetOrigin:
eventDc.SetDeviceOrigin(*eventDcOrigin) eventDc.SetDeviceOrigin(*eventDcOrigin)
self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) self.commands.update(cellPos=self.brushPos, dirty=self.dirty, undoLevel=self.canvas.patchesUndoLevel)
return eventDc
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)
def _snapshotsReset(self): def _snapshotsReset(self):
self._snapshotFiles, self._snapshotsUpdateLast = [], time.time() self._snapshotFiles, self._snapshotsUpdateLast = [], time.time()
self.commands._resetSnapshots() self.commands._snapshotsReset()
if self.commands.canvasPathName != None: if self.commands.canvasPathName != None:
canvasPathName = os.path.abspath(self.commands.canvasPathName) canvasPathName = os.path.abspath(self.commands.canvasPathName)
canvasFileName = os.path.basename(canvasPathName) canvasFileName = os.path.basename(canvasPathName)
@ -90,7 +79,7 @@ class RoarCanvasWindow(GuiWindow):
if os.path.exists(self._snapshotsDirName): if os.path.exists(self._snapshotsDirName):
for snapshotFile in natural_sort([f for f in os.listdir(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))]): 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: else:
self._snapshotsDirName = None self._snapshotsDirName = None
@ -115,14 +104,30 @@ class RoarCanvasWindow(GuiWindow):
self.SetCursor(wx.Cursor(wx.NullCursor)) self.SetCursor(wx.Cursor(wx.NullCursor))
self.commands.update(snapshotStatus=False); self._snapshotsUpdateLast = time.time(); self.commands.update(snapshotStatus=False); self._snapshotsUpdateLast = time.time();
self._snapshotFiles += [os.path.basename(snapshotPathName)]; self._snapshotFiles += [os.path.basename(snapshotPathName)];
self.commands._pushSnapshot(snapshotPathName) self.commands._snapshotsPush(snapshotPathName)
if len(self._snapshotFiles) > 72: if len(self._snapshotFiles) > 72:
for snapshotFile in self._snapshotFiles[:len(self._snapshotFiles) - 8]: 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; os.remove(os.path.join(self._snapshotsDirName, snapshotFile)); snapshotsCount -= 1;
except: except:
print("Exception during _snapshotsUpdate(): {}".format(sys.exc_info()[1])) 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): def applyOperator(self, currentTool, mapPoint, mouseLeftDown, mousePoint, operator, viewRect):
eventDc, patches, patchesCursor, rc = self.backend.getDeviceContext(self.GetClientSize(), self), None, None, True eventDc, patches, patchesCursor, rc = self.backend.getDeviceContext(self.GetClientSize(), self), None, None, True
if (currentTool.__class__ == ToolObject) and (currentTool.toolState >= currentTool.TS_SELECT): 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): def applyTool(self, eventDc, eventMouse, keyChar, keyCode, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect, force=False):
patches, patchesCursor, rc = None, None, 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: if eventMouse:
self.lastCellState = None if force else self.lastCellState self.lastCellState = None if force else self.lastCellState
if ((mapPoint[0] < self.canvas.size[0]) and (mapPoint[1] < self.canvas.size[1])) \ if ((mapPoint[0] < self.canvas.size[0]) and (mapPoint[1] < self.canvas.size[1])) \
@ -173,75 +182,80 @@ class RoarCanvasWindow(GuiWindow):
elif mapPoint != None: elif mapPoint != None:
rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]] rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]]
if rc: 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) self._applyPatches(eventDc, patches, patchesCursor, rc)
if tool.__class__ == ToolObject: if (tool.__class__ == ToolObject) and (tool.external, tool.toolState) == (True, tool.TS_NONE):
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; 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(currentTool=self.commands.currentTool)
self.commands.update(toolName=newToolName, undoInhibit=False)
else:
self.commands.update(undoInhibit=False)
if (patches != None) and (len(patches) > 0): if (patches != None) and (len(patches) > 0):
self._snapshotsUpdate() self._snapshotsUpdate()
return rc return rc
def onKeyboardInput(self, event): def cursorHide(self, eventDc=None, eventDcResetOrigin=True, reset=False):
keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers() if eventDc == None:
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
if (keyCode == wx.WXK_PAUSE) \ if eventDcResetOrigin:
and (keyModifiers == wx.MOD_SHIFT): eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
import pdb; pdb.set_trace() patchesCursor = self.canvas.popCursor(reset=reset); patchesCursor_ = [];
elif keyCode in (wx.WXK_DOWN, wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_UP): for cursorCell in [p[:2] for p in patchesCursor]:
if keyCode == wx.WXK_DOWN: if (cursorCell[0] < self.canvas.size[0]) and (cursorCell[1] < self.canvas.size[1]):
if self.brushPos[1] < (self.canvas.size[1] - 1): patchesCursor_ += [[*cursorCell, *self.canvas.map[cursorCell[1]][cursorCell[0]]]]
self.brushPos = [self.brushPos[0], self.brushPos[1] + 1] if len(patchesCursor_) > 0:
else: self.backend.drawPatches(self.canvas, eventDc, patchesCursor_, False)
self.brushPos = [self.brushPos[0], 0] if eventDcResetOrigin:
elif keyCode == wx.WXK_LEFT: eventDc.SetDeviceOrigin(*eventDcOrigin)
if self.brushPos[0] > 0: return eventDc
self.brushPos = [self.brushPos[0] - 1, self.brushPos[1]]
else: def cursorShow(self, eventDc=None, eventDcResetOrigin=True, patchesCursor=None):
self.brushPos = [self.canvas.size[0] - 1, self.brushPos[1]] if eventDc == None:
elif keyCode == wx.WXK_RIGHT: eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
if self.brushPos[0] < (self.canvas.size[0] - 1): if eventDcResetOrigin:
self.brushPos = [self.brushPos[0] + 1, self.brushPos[1]] eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
else: if patchesCursor == None:
self.brushPos = [0, self.brushPos[1]] patchesCursor = self.canvas.popCursor(reset=False)
elif keyCode == wx.WXK_UP: elif len(patchesCursor) > 0:
if self.brushPos[1] > 0: self.canvas.pushCursor(patchesCursor)
self.brushPos = [self.brushPos[0], self.brushPos[1] - 1] if (patchesCursor != None) and (len(patchesCursor) > 0):
else: self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True)
self.brushPos = [self.brushPos[0], self.canvas.size[1] - 1] if eventDcResetOrigin:
self.commands.update(cellPos=self.brushPos) eventDc.SetDeviceOrigin(*eventDcOrigin)
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 onEnterWindow(self, event): def onEnterWindow(self, event):
self.lastCellState = None self.lastCellState = None
def onErase(self, event): def onKeyboardInput(self, event):
pass 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): def onLeaveWindow(self, event):
if False: if False:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) self.cursorHide()
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc)
self.lastCellState = None self.lastCellState = None
def onMouseInput(self, event): def onMouseInput(self, event):
@ -266,18 +280,19 @@ class RoarCanvasWindow(GuiWindow):
newFontSize = self.backend.fontSize + delta newFontSize = self.backend.fontSize + delta
if newFontSize > 0: if newFontSize > 0:
self.Freeze() self.Freeze()
self.backend.fontSize = newFontSize self.backend.fontSize = newFontSize; self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize;
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)]) 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 = [] patches = []
for numRow in range(self.canvas.size[1]): for numRow in range(self.canvas.size[1]):
for numCol in range(len(self.canvas.map[numRow])): for numCol in range(len(self.canvas.map[numRow])):
patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] 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.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
self.cursorShow(eventDc, False)
eventDc.SetDeviceOrigin(*eventDcOrigin) 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): elif modifiers == (wx.MOD_CONTROL | wx.MOD_SHIFT):
self.commands.canvasCanvasSize(self.commands.canvasCanvasSize, 2, 1 if delta > 0 else 0)(None) self.commands.canvasCanvasSize(self.commands.canvasCanvasSize, 2, 1 if delta > 0 else 0)(None)
elif modifiers == wx.MOD_CONTROL: elif modifiers == wx.MOD_CONTROL:
@ -289,7 +304,7 @@ class RoarCanvasWindow(GuiWindow):
viewRect = self.GetViewStart() viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
self.backend.onPaint(self.GetClientSize(), self, viewRect) 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): def resize(self, newSize, commitUndo=True, dirty=True, freeze=True):
if freeze: if freeze:
@ -297,76 +312,50 @@ class RoarCanvasWindow(GuiWindow):
viewRect = self.GetViewStart() viewRect = self.GetViewStart()
oldSize = [0, 0] if self.canvas.map == None else self.canvas.size oldSize = [0, 0] if self.canvas.map == None else self.canvas.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)] 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; self.backend.resize(newSize); self.scrollStep = self.backend.cellSize;
super().resize([a * b for a, b in zip(newSize, 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.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: if commitUndo:
self._snapshotsUpdate() self._snapshotsUpdate()
if freeze: if freeze:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) self.cursorShow(); self.Thaw(); self._windowEraseBackground(wx.ClientDC(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));
def undo(self, redo=False): def undo(self, redo=False):
freezeFlag = False freezeFlag, patches, patchesDelta = False, [], self.canvas.popUndo(redo)
deltaPatches = self.canvas.journal.popUndo() if not redo else self.canvas.journal.popRedo() for patch in [p for p in patchesDelta if p != None]:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) if patch[0] == "resize":
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":
if not freezeFlag: if not freezeFlag:
self.Freeze(); freezeFlag = True; self.Freeze(); freezeFlag = True;
eventDc = None; self.resize(patch[1:], False, freeze=False); self.resize(patch[1:], False, freeze=False)
else: else:
self.canvas._commitPatch(patch); patches += [patch]; patches += [patch]
if eventDc == None: eventDc = self._applyPatches(None, patches, None, True, commitUndo=False, hideCursor=False)
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) self.cursorShow(eventDc, True, None)
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)
if freezeFlag: if freezeFlag:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) self.Thaw(); self._windowEraseBackground(wx.ClientDC(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));
def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True): def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True):
self.resize(newSize, commitUndo, dirty) self.resize(newSize, commitUndo, dirty); self.canvas.update(newSize, newCanvas);
self.canvas.update(newSize, newCanvas) patches = []
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); patches = [];
for numRow in range(newSize[1]): for numRow in range(newSize[1]):
for numCol in range(newSize[0]): for numCol in range(newSize[0]):
patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]]
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) self._applyPatches(None, patches, None, True, dirty=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
def __init__(self, backend, canvas, commands, parent, pos, size): def __init__(self, backend, canvas, commands, parent, pos, size):
super().__init__(parent, pos) super().__init__(parent, pos); self.parent, self.size = parent, size;
self.lastMouseState, self.size = [False, False, False], size self.backend, self.canvas, self.commands = backend(self.size), canvas, commands(self, parent)
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)
self.bgBrush, self.bgPen = wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID), wx.Pen(self.GetBackgroundColour(), 1) 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.brushColours, self.brushPos, self.brushSize, = [3, -1], [0, 0], [1, 1]
self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel) 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() self._snapshotsReset()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -14,6 +14,7 @@ class RoarWindowAbout(wx.Dialog):
def __init__(self, parent, minSize=(320, 300), title="About roar"): def __init__(self, parent, minSize=(320, 300), title="About roar"):
super().__init__(parent, size=minSize, title=title) 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, 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")) logoPathNames = glob(os.path.join("assets", "images", "logo*.bmp"))
logoPathName = logoPathNames[random.randint(0, len(logoPathNames) - 1)] logoPathName = logoPathNames[random.randint(0, len(logoPathNames) - 1)]

View File

@ -13,6 +13,7 @@ class RoarWindowMelp(wx.Dialog):
def __init__(self, parent, minSize=(320, 300), title="melp?"): def __init__(self, parent, minSize=(320, 300), title="melp?"):
super().__init__(parent, size=minSize, title=title) super().__init__(parent, size=minSize, title=title)
self.panel, self.sizer = wx.Panel(self), wx.BoxSizer(wx.VERTICAL) 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: with open(os.path.join("assets", "text", "melp.txt"), "r") as fileObject:
helpLabel = "".join(fileObject.readlines()) helpLabel = "".join(fileObject.readlines())

View File

@ -34,12 +34,6 @@ class ToolCircle(Tool):
elif ((numRow > 0) and (cells[numRow][numCol][0] > cells[numRow - 1][-1][0])) \ 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])): or ((numRow < len(cells)) and (cells[numRow][numCol][0] > cells[numRow + 1][-1][0])):
patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 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: else:
patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "] patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "]
patches += [patch] patches += [patch]

View File

@ -18,17 +18,9 @@ class ToolRect(Tool):
for brushCol in range((rect[2] - rect[0]) + 1): 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])]): if (brushCol in [0, (rect[2] - rect[0])]) or (brushRow in [0, (rect[3] - rect[1])]):
patchColours = [brushColours[0]] * 2 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: else:
patchColours = [brushColours[1]] * 2 patchColours = [brushColours[1]] * 2
patch = [rect[0] + brushCol, rect[1] + brushRow, *patchColours, 0, " "] patches += [[rect[0] + brushCol, rect[1] + brushRow, *patchColours, 0, " "]]
patches += [patch]
return patches return patches
def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown):

View File

@ -70,12 +70,17 @@ class ToolText(Tool):
rc = True rc = True
elif keyCode == wx.WXK_CONTROL_V: elif keyCode == wx.WXK_CONTROL_V:
rc = True rc = True
if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) \ if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) and wx.TheClipboard.Open():
and wx.TheClipboard.Open():
inBuffer = wx.TextDataObject() inBuffer = wx.TextDataObject()
if wx.TheClipboard.GetData(inBuffer): if wx.TheClipboard.GetData(inBuffer):
brushPosOriginX = brushPos[0]
for inBufferChar in list(inBuffer.GetText()): 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) rc_, patches_ = self._processKeyChar(brushColours, brushPos, canvas, inBufferChar, 0)
patches += patches_ patches += patches_
rc = True if rc_ else rc rc = True if rc_ else rc

View File

@ -20,7 +20,7 @@ def main(*argv):
os.makedirs(localConfirName) os.makedirs(localConfirName)
wxApp, roarClient = wx.App(False), RoarClient(None) wxApp, roarClient = wx.App(False), RoarClient(None)
argv0, argv = argv[0], argv[1:] 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) >= 1:
if (len(argv) >= 2) and (argv[1].endswith(".lst")): if (len(argv) >= 2) and (argv[1].endswith(".lst")):
roarClient.assetsWindow._load_list(argv[1]) roarClient.assetsWindow._load_list(argv[1])
@ -30,7 +30,7 @@ def main(*argv):
if rc: if rc:
roarClient.canvasPanel.update(roarClient.canvasPanel.canvas.importStore.inSize, False, roarClient.canvasPanel.canvas.importStore.outMap, dirty=False) 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.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) roarClient.canvasPanel.commands.canvasTool(roarClient.canvasPanel.commands.canvasTool, 1)(None)
else: else:
print("error: {}".format(error), file=sys.stderr) print("error: {}".format(error), file=sys.stderr)