From bc969295ddaa6eca9480028baa4e41d37cc76867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Tue, 1 Oct 2019 19:03:29 +0200 Subject: [PATCH] Various bugfixes & usability improvements. 1) Backend: initial optimised cell rendering Python C module implementation skeleton. 2) Backend: raise alpha blending {fore,back}ground colour coefficient to {0.8,1.0 - 0.8}, resp. 3) Backend: reimplement cell rendering using Draw{Rectangle,Text}List(). 4) Canvas window: eliminate {canvas,{scroll,tool}bar} flickering during resize. 5) Canvas window: fix cursor artifacts during resizing by masking cursor. 6) Canvas window: restore cursor after executing operations that remove it. 7) Import store: correctly parse non-conforming \u0003, sequences. 8) GUI: correctly save list of recently used files post-update. --- .gitignore | 4 ++ .vscode/c_cpp_properties.json | 23 +++++++++ .vscode/settings.json | 1 + .vscode/tasks.json | 7 ++- assets/text/TODO | 19 +++----- assets/text/roadmap.txt | 13 +++++ libcanvas/Canvas.py | 1 - libcanvas/CanvasImportStore.py | 2 + libcanvas/CanvasJournal.py | 11 ++--- libgui/GuiCanvasWxBackend.py | 72 ++++++++++++++-------------- libgui/GuiCanvasWxBackendFast.c | 25 ++++++++++ libroar/RoarCanvasCommandsFile.py | 26 +++++++--- libroar/RoarCanvasWindow.py | 80 ++++++++++++++++++++++--------- librtl/Rtl.py | 19 +------- libtools/ToolCircle.py | 3 +- 15 files changed, 201 insertions(+), 105 deletions(-) create mode 100755 .vscode/c_cpp_properties.json create mode 100644 assets/text/roadmap.txt create mode 100644 libgui/GuiCanvasWxBackendFast.c diff --git a/.gitignore b/.gitignore index ae7ce3e..c319fe3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ *.sw[op] __pycache__/ build/ +libgui/GuiCanvasWxBackendFast.exp +libgui/GuiCanvasWxBackendFast.lib +libgui/GuiCanvasWxBackendFast.obj +libgui/GuiCanvasWxBackendFast.pyd librtl/ImgurApiKey.py releases/ diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100755 index 0000000..ed3dc79 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**", + "C:/Python37/include/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "windowsSdkVersion": "10.0.18362.0", + "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.23.28105/bin/Hostx64/x64/cl.exe", + "cStandard": "c11", + "cppStandard": "c++17", + "intelliSenseMode": "msvc-x64", + "compilerArgs": [] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4506774..b47f807 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "python.analysis.disabled": ["unresolved-import"], "python.pythonPath": "C:\\Python37\\python.exe", "python.linting.enabled": false } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 5c46c6c..a1be6c3 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,9 +4,12 @@ "version": "2.0.0", "tasks": [ { - "label": "echo", + "label": "Build libgui/GuiCanvasWxBackendFast.pyd", "type": "shell", - "command": "echo Hello" + "command": "cd \"${workspaceFolder}/libgui\" && cmd /c cl /LD /IC:/Python37/include GuiCanvasWxBackendFast.c C:/Python37/libs/python37.lib /FeGuiCanvasWxBackendFast.pyd", + "problemMatcher": [ + "$msCompile" + ] } ] } \ No newline at end of file diff --git a/assets/text/TODO b/assets/text/TODO index 2a9fda0..0d1a760 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -1,25 +1,20 @@ 1) ANSI CSI CU[BDPU] sequences -2) Fix & finish Arabic/RTL text tool support 3) Documentation, instrumentation & unit tests 4) Layers & layout (e.g. for comics, zines, etc.) 5) Open and toggle a reference image in the background 6) Client-Server or Peer-to-Peer realtime collaboration 7) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.) -8) {record,replay} {keyboard,mouse,...} events in debugging builds -9) Unit tools: arrow, {cloud,speech bubble}, curve, measure, polygon, triangle +8) Unit tools: arrow, {cloud,speech bubble}, curve, polygon +9) {record,replay} {keyboard,mouse,...} events in debugging builds 10) Integrate ENNTool code in the form of OpenGL-based animation window (see 13) and 14)) 11) Composition, parametrisation & keying of tools from higher-order operators (brushes, functions, filters, outlines, patterns & shaders) and unit tools 12) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...) 13) GUI TODO list: a) switch to Gtk - b) https://material.io/resources/icons/?style=baseline - c) replace logo w/ canvas panel in About dialogue, revisit melp? dialogue - d) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3) - e) Settings/Settings window (e.g. autosave, cursor opacity, hide cursor on leaving window, ...) - -Release roadmap: -1) {copy,cut,delete,insert from,paste}, edit asset in new canvas, import from {canvas,object} -2) operators: crop, scale, shift, slice -3) tools: unicode block elements + b) canvas preview in Open dialogue(s) + c) https://material.io/resources/icons/?style=baseline + d) replace logo w/ canvas panel in About dialogue, revisit melp? dialogue + e) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3) + f) Settings/Settings window (e.g. autosave, cursor opacity, hide cursor on leaving window, ...) vim:ff=dos tw=0 diff --git a/assets/text/roadmap.txt b/assets/text/roadmap.txt new file mode 100644 index 0000000..b2a78c0 --- /dev/null +++ b/assets/text/roadmap.txt @@ -0,0 +1,13 @@ +1) canvas: default to transparent background colour +2) GUI: disable tiling items unless current tool is object tool +3) GUI: edit asset in new canvas, import from {canvas,object} +4) GUI: implement GuiCanvasWxBackendFast.c{,c} +5) GUI: select all +6) GUI: show line numbers w/ either ruler or tooltip +7) operators: copy, cut, delete, insert from, paste +8) operators: crop, scale, shift, slice operators +9) tools: measure, triangle, unicode block elements tool +10) tools: text tool: finish Arabic/RTL text implementation +11) tools: text tool: implicitly draw (text) w/ bg -1, toggle drawing actual brushColours[1] mode w/ RMB + +vim:ff=dos tw=0 diff --git a/libcanvas/Canvas.py b/libcanvas/Canvas.py index 6064092..aa00fdc 100644 --- a/libcanvas/Canvas.py +++ b/libcanvas/Canvas.py @@ -29,7 +29,6 @@ class Canvas(): else: oldSize = self.size deltaSize = [b - a for a, b in zip(oldSize, newSize)] - self.journal.resetCursor() if commitUndo: self.journal.begin() undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize] diff --git a/libcanvas/CanvasImportStore.py b/libcanvas/CanvasImportStore.py index 7c57157..1ecb28a 100644 --- a/libcanvas/CanvasImportStore.py +++ b/libcanvas/CanvasImportStore.py @@ -101,6 +101,8 @@ class CanvasImportStore(): inCurColours = (int(m[2]), int(m[3])) elif (m[2] != None) and (m[3] == None): inCurColours = (int(m[2]), int(inCurColours[1])) + elif (m[2] == None) and (m[3] != None): + inCurColours = (int(inCurColours[0]), int(m[3])) else: inCurColours = (15, -1) inCurCol += len(m[0]) diff --git a/libcanvas/CanvasJournal.py b/libcanvas/CanvasJournal.py index c0d91ac..2ac1486 100644 --- a/libcanvas/CanvasJournal.py +++ b/libcanvas/CanvasJournal.py @@ -15,9 +15,11 @@ class CanvasJournal(): if self.patchesUndoLevel > 0: del self.patchesUndo[:self.patchesUndoLevel]; self.patchesUndoLevel = 0; - def popCursor(self): + def popCursor(self, reset=True): if len(self.patchesCursor): - patchesCursor = self.patchesCursor; self.patchesCursor = []; + patchesCursor = self.patchesCursor + if reset: + self.resetCursor() return patchesCursor else: return [] @@ -40,8 +42,6 @@ class CanvasJournal(): self.patchesCursor = patches def resetCursor(self): - if self.patchesCursor != None: - self.patchesCursor.clear() self.patchesCursor = [] def resetUndo(self): @@ -57,7 +57,6 @@ class CanvasJournal(): self.resetCursor(); self.resetUndo(); def __init__(self): - self.patchesCursor, self.patchesUndo, self. patchesUndoLevel = None, None, None - self.resetCursor(); self.resetUndo(); + self.patchesUndo = None; self.resetCursor(); self.resetUndo(); # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libgui/GuiCanvasWxBackend.py b/libgui/GuiCanvasWxBackend.py index 2cc9ddd..84bf417 100644 --- a/libgui/GuiCanvasWxBackend.py +++ b/libgui/GuiCanvasWxBackend.py @@ -6,7 +6,8 @@ from ctypes import * from GuiCanvasColours import Colours -import math, os, platform, Rtl, wx +import GuiCanvasWxBackendFast +import math, os, platform, wx class GuiBufferedDC(wx.MemoryDC): def __del__(self): @@ -70,7 +71,7 @@ class GuiCanvasWxBackend(): CS_UNDERLINE = 0x02 def _blendColours(self, bg, fg): - return [int((fg * 0.75) + (bg * (1.0 - 0.75))) for bg, fg in zip(Colours[bg][:3], Colours[fg][:3])] + return [int((fg * 0.8) + (bg * (1.0 - 0.8))) for bg, fg in zip(Colours[bg][:3], Colours[fg][:3])] def _finiBrushesAndPens(self): for wxObject in Rtl.flatten([ @@ -140,24 +141,23 @@ class GuiCanvasWxBackend(): else: bg = patchBg[1] if patchBg[1] != -1 else 14 brush, pen = self._brushesBlend[bg][14], self._pensBlend[bg][14] - if self._lastBrush != brush: - dc.SetBrush(brush); self._lastBrush = brush; - if self._lastPen != pen: - dc.SetPen(pen); self._lastPen = pen; return brush, pen - def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc): + def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc, reset=True): eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - cursorCells, patches = canvasJournal.popCursor(), [] - for cursorCell in cursorCells: - patches += [[*cursorCell, *canvas.map[cursorCell[1]][cursorCell[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 cursorCells + return cursorPatches def drawPatches(self, canvas, eventDc, patches, isCursor=False): - patchCursorCells, patchesRender = [], [] + GuiCanvasWxBackendFast.drawPatches() + patchesRender = [] for patch in patches: point = patch[:2] if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]: @@ -166,33 +166,33 @@ class GuiCanvasWxBackend(): patchesRender += [patchReshaped] else: patchesRender += [patch] + numPatch, textBg = 0, wx.Colour(0, 0, 0, 0) + rectangles, pens, brushes = [None] * len(patchesRender), [None] * len(patchesRender), [None] * len(patchesRender) + textList, coords, foregrounds, backgrounds = [], [], [], [] + eventDc.SetFont(self._font) for patchRender in patchesRender: - absPoint, charFlag = [a * b for a, b in zip(self.cellSize, patchRender[:2])], False if (patchRender[5] == " ") and (patchRender[3] == -1): - charFlag, patchRender, textFg = True, [*patchRender[:-1], "░"], wx.Colour(0, 0, 0, 255) - elif isCursor and (patchRender[5] == " ") and ((canvas.map[patchRender[1]][patchRender[0]][3] != " ") or (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE)): - charFlag, patchRender = True, [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]] - textFg = wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3])) - elif (patchRender[5] != " ") or (patchRender[4] & self._CellState.CS_UNDERLINE): - charFlag, textFg = True, wx.Colour(Colours[patchRender[2]][:4]) + text, textFg = "░", wx.Colour(0, 0, 0, 255) + elif isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][3] != " "): + patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]] + text, textFg = canvas.map[patchRender[1]][patchRender[0]][3], wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3])) + elif isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE): + patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]] + text, textFg = "_", wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3])) + elif patchRender[5] != " ": + text, textFg = patchRender[5], wx.Colour(Colours[patchRender[2]][:4]) + elif patchRender[4] & self._CellState.CS_UNDERLINE: + text, textFg = "_", wx.Colour(Colours[patchRender[2]][:4]) + else: + text = None brush, pen = self._setBrushColours(eventDc, isCursor, patchRender[2:], canvas.map[patchRender[1]][patchRender[0]]) - eventDc.DrawRectangle(*absPoint, *self.cellSize) - if charFlag: - if (patchRender[4] & self._CellState.CS_UNDERLINE) or (patchRender[5] == "_"): - if isCursor and (patchRender[5] == " ") and ((canvas.map[patchRender[1]][patchRender[0]][3] != " ") or (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE)): - eventDc.SetPen(self._pensBlend[patchRender[2]][patchRender[3]]) - else: - eventDc.SetPen(self._pens[patchRender[2]]) - eventDc.DrawLine(absPoint[0], absPoint[1] + self.cellSize[1] - 1, absPoint[0] + self.cellSize[0], absPoint[1] + self.cellSize[1] - 1) - eventDc.SetPen(pen) - if patchRender[5] != "_": - oldClippingRegion = eventDc.GetClippingBox() - eventDc.SetFont(self._font) - eventDc.DestroyClippingRegion(); eventDc.SetClippingRegion(*absPoint, *self.cellSize); - eventDc.SetTextForeground(textFg); eventDc.DrawText(patchRender[5], *absPoint); - eventDc.DestroyClippingRegion() - patchCursorCells += [patchRender[:2]] - return patchCursorCells + rectangles[numPatch] = [patchRender[:2][0] * self.cellSize[0], patchRender[:2][1] * self.cellSize[1], self.cellSize[0], self.cellSize[1]]; + pens[numPatch] = pen; brushes[numPatch] = brush; + if text != None: + textList += [text]; coords += [[patchRender[:2][0] * self.cellSize[0], patchRender[:2][1] * self.cellSize[1]]]; foregrounds += [textFg]; backgrounds += [textBg]; + numPatch += 1 + eventDc.DrawRectangleList(rectangles, pens, brushes) + eventDc.DrawTextList(textList, coords, foregrounds, backgrounds) def getDeviceContext(self, clientSize, parentWindow, viewRect=None): if viewRect == None: diff --git a/libgui/GuiCanvasWxBackendFast.c b/libgui/GuiCanvasWxBackendFast.c new file mode 100644 index 0000000..97c617c --- /dev/null +++ b/libgui/GuiCanvasWxBackendFast.c @@ -0,0 +1,25 @@ +#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */ +#include + +static PyObject * +GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args) +{ + Py_RETURN_NONE; +} + +static PyMethodDef +GuiCanvasWxBackendFast_methods[] = { + {"drawPatches", GuiCanvasWxBackendFast_drawPatches, METH_VARARGS, "XXX"}, + {NULL, NULL, 0, NULL}, +}; + +static struct PyModuleDef +GuiCanvasWxBackendFastmodule = { + PyModuleDef_HEAD_INIT, "GuiCanvasWxBackendFast", NULL, -1, GuiCanvasWxBackendFast_methods, +}; + +PyMODINIT_FUNC +PyInit_GuiCanvasWxBackendFast(void) +{ + return PyModule_Create(&GuiCanvasWxBackendFastmodule); +} \ No newline at end of file diff --git a/libroar/RoarCanvasCommandsFile.py b/libroar/RoarCanvasCommandsFile.py index 23a35c4..4bd97e4 100644 --- a/libroar/RoarCanvasCommandsFile.py +++ b/libroar/RoarCanvasCommandsFile.py @@ -102,10 +102,7 @@ class RoarCanvasCommandsFile(): self.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasOpenRecent(event, pathName), menuItemWindow) self.lastFiles += [{"menuItemId":menuItemId, "menuItemWindow":menuItemWindow, "pathName":pathName}] if serialise: - 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) + self._saveRecent() def _pushSnapshot(self, pathName): menuItemId = wx.NewId() @@ -122,6 +119,12 @@ class RoarCanvasCommandsFile(): if self.canvasRestore.attrDict["id"] in self.parentFrame.menuItemsById: self.parentFrame.menuItemsById[self.canvasRestore.attrDict["id"]].Enable(False) + def _saveRecent(self): + localConfFileName = getLocalConfPathName("Recent.lst") + with open(localConfFileName, "w", encoding="utf-8") as outFile: + for lastFile in [l["pathName"] for l in self.lastFiles]: + print(lastFile, file=outFile) + def _storeLastDir(self, pathName): localConfFileName = getLocalConfPathName("RecentDir.txt") with open(localConfFileName, "w", encoding="utf-8") as outFile: @@ -170,8 +173,13 @@ class RoarCanvasCommandsFile(): outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._storeLastDir(self.lastDir); self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) - self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc) + 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) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + cursorPatches = self.parentCanvas.canvas.journal.popCursor(reset=False) + if len(cursorPatches) > 0: + self.parentCanvas.backend.drawPatches(self.parentCanvas.canvas, eventDc, cursorPatches, isCursor=True) + eventDc.SetDeviceOrigin(*eventDcOrigin) self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) return True @@ -179,8 +187,13 @@ class RoarCanvasCommandsFile(): def canvasExportImgur(self, event): self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas) - self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc) + 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) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + cursorPatches = self.parentCanvas.canvas.journal.popCursor(reset=False) + if len(cursorPatches) > 0: + self.parentCanvas.backend.drawPatches(self.parentCanvas.canvas, eventDc, cursorPatches, isCursor=True) + eventDc.SetDeviceOrigin(*eventDcOrigin) self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor)) if rc: if not wx.TheClipboard.IsOpened(): @@ -272,6 +285,7 @@ class RoarCanvasCommandsFile(): if not rc: numLastFile = [i for i in range(len(self.lastFiles)) if self.lastFiles[i]["pathName"] == pathName][0] self.canvasOpenRecent.attrDict["menu"].Delete(self.lastFiles[numLastFile]["menuItemId"]); del self.lastFiles[numLastFile]; + self._saveRecent() @GuiSubMenuDecorator("Restore Snapshot", "Res&tore Snapshot", None, None, False) def canvasRestore(self, event, pathName=None): diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index 48f62a6..c7b5c5b 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -56,12 +56,28 @@ class RoarCanvasWindow(GuiWindow): self.canvas.applyPatch(patch, commitUndo=True) self.canvas.journal.end() if patchesCursor != None: - patchesCursorCells = self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) - if len(patchesCursorCells) > 0: - self.canvas.journal.pushCursor(patchesCursorCells) + self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) + if len(patchesCursor) > 0: + self.canvas.journal.pushCursor(patchesCursor) eventDc.SetDeviceOrigin(*eventDcOrigin) self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) + def _eraseBackground(self, eventDc): + viewRect = self.GetViewStart() + canvasSize, panelSize = [a * b for a, b in zip(self.backend.canvasSize, self.backend.cellSize)], self.GetSize() + if viewRect != (0, 0): + viewRect = [a * b for a, b in zip(self.backend.cellSize, viewRect)] + canvasSize = [a - b for a, b in zip(canvasSize, viewRect)] + rectangles, pens, brushes = [], [], [] + if panelSize[0] > canvasSize[0]: + brushes += [self.bgBrush]; pens += [self.bgPen]; + rectangles += [[canvasSize[0], 0, panelSize[0] - canvasSize[0], panelSize[1]]] + if panelSize[1] > canvasSize[1]: + brushes += [self.bgBrush]; pens += [self.bgPen]; + rectangles += [[0, canvasSize[1], panelSize[0], panelSize[1] - canvasSize[1]]] + if len(rectangles) > 0: + eventDc.DrawRectangleList(rectangles, pens, brushes) + def _snapshotsReset(self): self._snapshotFiles, self._snapshotsUpdateLast = [], time.time() self.commands._resetSnapshots() @@ -218,6 +234,9 @@ class RoarCanvasWindow(GuiWindow): def onEnterWindow(self, event): self.lastCellState = None + def onErase(self, event): + pass + def onLeaveWindow(self, event): if False: eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) @@ -245,6 +264,7 @@ class RoarCanvasWindow(GuiWindow): if modifiers == (wx.MOD_CONTROL | wx.MOD_ALT): newFontSize = self.backend.fontSize + delta if newFontSize > 0: + self.Freeze() self.backend.fontSize = newFontSize self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize; super().resize([a * b for a, b in zip(self.canvas.size, self.backend.cellSize)]) @@ -256,6 +276,7 @@ class RoarCanvasWindow(GuiWindow): patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) + self.Thaw(); del eventDc; self._eraseBackground(wx.ClientDC(self)); elif modifiers == (wx.MOD_CONTROL | wx.MOD_SHIFT): self.commands.canvasCanvasSize(self.commands.canvasCanvasSize, 2, 1 if delta > 0 else 0)(None) elif modifiers == wx.MOD_CONTROL: @@ -264,51 +285,64 @@ class RoarCanvasWindow(GuiWindow): event.Skip() def onPaint(self, event): + viewRect = self.GetViewStart() eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart()) + self.backend.onPaint(self.GetClientSize(), self, viewRect) + del eventDc; self._eraseBackground(wx.PaintDC(self)); - def resize(self, newSize, commitUndo=True, dirty=True): + def resize(self, newSize, commitUndo=True, dirty=True, freeze=True): + if freeze: + self.Freeze() viewRect = self.GetViewStart() oldSize = [0, 0] if self.canvas.map == None else self.canvas.size deltaSize = [b - a for a, b in zip(oldSize, newSize)] if self.canvas.resize(newSize, commitUndo): self.backend.resize(newSize); self.scrollStep = self.backend.cellSize; super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)]) - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - patches = [] - if deltaSize[0] > 0: - for numRow in range(oldSize[1]): - for numNewCol in range(oldSize[0], newSize[0]): - patches += [[numNewCol, numRow, 1, 1, 0, " "]] - if deltaSize[1] > 1: - for numNewRow in range(oldSize[1], newSize[1]): - for numNewCol in range(newSize[0]): - patches += [[numNewCol, numNewRow, 1, 1, 0, " "]] - self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) - eventDc.SetDeviceOrigin(*eventDcOrigin) self.Scroll(*viewRect); self.dirty = dirty; self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel) if commitUndo: self._snapshotsUpdate() + if freeze: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + cursorPatches = self.canvas.journal.popCursor(reset=False) + if len(cursorPatches) > 0: + self.backend.drawPatches(self.canvas, eventDc, cursorPatches, isCursor=True) + eventDc.SetDeviceOrigin(*eventDcOrigin) + del eventDc; self.Thaw(); self._eraseBackground(wx.ClientDC(self)); def undo(self, redo=False): + freezeFlag = False deltaPatches = self.canvas.journal.popUndo() if not redo else self.canvas.journal.popRedo() eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) + 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": - del eventDc; self.resize(patch[1:], False); - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + if not freezeFlag: + self.Freeze(); freezeFlag = True; + eventDc = None; self.resize(patch[1:], False, freeze=False); else: self.canvas._commitPatch(patch); patches += [patch]; + if eventDc == None: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) + if len(patchesCursor): + self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) eventDc.SetDeviceOrigin(*eventDcOrigin) + if freezeFlag: + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + cursorPatches = self.canvas.journal.popCursor(reset=False) + if len(cursorPatches) > 0: + self.backend.drawPatches(self.canvas, eventDc, cursorPatches, isCursor=True) + eventDc.SetDeviceOrigin(*eventDcOrigin) + del eventDc; self.Thaw(); self._eraseBackground(wx.ClientDC(self)); def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True): self.resize(newSize, commitUndo, dirty) @@ -329,6 +363,8 @@ class RoarCanvasWindow(GuiWindow): 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.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase) self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel) self._snapshotsReset() diff --git a/librtl/Rtl.py b/librtl/Rtl.py index e7abbe2..fb2e6a9 100644 --- a/librtl/Rtl.py +++ b/librtl/Rtl.py @@ -5,9 +5,7 @@ # This project is licensed under the terms of the MIT licence. # -import re, statistics, time - -timeState = [None, None, 0, []] +import re def flatten(l): return [li for l_ in l for li in l_] @@ -17,19 +15,4 @@ def natural_sort(l): alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] return sorted(l, key=alphanum_key) -def timePrint(pfx): - timeState[0] = time.time() if timeState[0] == None else timeState[0] - t1 = time.time(); td = t1 - timeState[0] - if td > 0: - timeState[1] = td if timeState[1] == None else min(td, timeState[1]) - timeState[2] = max(td, timeState[2]) - timeState[3] += [td] - print("{} took {:.4f} ms (min: {:.4f} max: {:.4f} avg: {:.4f})".format(pfx, td * 1000, timeState[1] * 1000, timeState[2] * 1000, statistics.mean(timeState[3]) * 1000)) - -def timeReset(): - timeState = [None, None, 0, []] - -def timeStart(): - timeState[0] = time.time() - # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolCircle.py b/libtools/ToolCircle.py index c231faf..67b6e2c 100644 --- a/libtools/ToolCircle.py +++ b/libtools/ToolCircle.py @@ -24,8 +24,7 @@ class ToolCircle(Tool): for numRow in range(len(cells)): for numCol in range(len(cells[numRow])): point = cells[numRow][numCol] - if ((point[0] >= 0) and (point[1] >= 0)) \ - and (point[0] < canvas.size[0]) and (point[1] < canvas.size[1]): + if (point[0] >= 0) and (point[1] >= 0): if ((numRow == 0) or (numRow == (len(cells) - 1))) \ or ((numCol == 0) or (numCol == (len(cells[numRow]) - 1))): patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 0, " "]