From 76e57bd081476538ce77d6ba719ffc06482c324c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Thu, 26 Sep 2019 22:38:28 +0200 Subject: [PATCH] Improve backend latency and throughput via batching. assets/text/TODO: updated. libtools/ToolLine.py: reflect brush width in pre-line dragging cursor. --- assets/text/TODO | 14 +- libgui/GuiCanvasWxBackend.py | 148 ++++++++++----------- liboperators/OperatorRotate.py | 0 liboperators/OperatorTile.py | 0 libroar/RoarAssetsWindow.py | 18 +-- libroar/RoarCanvasCommandsEdit.py | 6 +- libroar/RoarCanvasWindow.py | 210 ++++++++++++++---------------- librtl/Rtl.py | 30 +++++ libtools/Tool.py | 8 +- libtools/ToolCircle.py | 14 +- libtools/ToolErase.py | 19 +-- libtools/ToolFill.py | 18 +-- libtools/ToolLine.py | 36 +++-- libtools/ToolObject.py | 117 ++++++++--------- libtools/ToolPickColour.py | 8 +- libtools/ToolRect.py | 13 +- libtools/ToolText.py | 48 ++++--- 17 files changed, 340 insertions(+), 367 deletions(-) mode change 100755 => 100644 liboperators/OperatorRotate.py mode change 100755 => 100644 liboperators/OperatorTile.py create mode 100644 librtl/Rtl.py diff --git a/assets/text/TODO b/assets/text/TODO index a957828..3436ffd 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -14,14 +14,16 @@ 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) Settings/Settings window (e.g. autosave, hide cursor on leaving window, ...) - e) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3) + 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) auto{load,save} & {backup,restore} -4) tools: unicode block elements -5) floating/dockable toolbar https://wxpython.org/Phoenix/docs/html/wx.aui.AuiManager.html +2) floating/dockable toolbar https://wxpython.org/Phoenix/docs/html/wx.aui.AuiManager.html +3) allow dragging {circle,rect,...} w/ irrespective of current brush size +4) reimplement cursor unmasking w/ simple list of points +5) operators: crop, scale, shift, slice +6) auto{load,save} & {backup,restore} +7) tools: unicode block elements vim:ff=dos tw=0 diff --git a/libgui/GuiCanvasWxBackend.py b/libgui/GuiCanvasWxBackend.py index b410493..e512090 100644 --- a/libgui/GuiCanvasWxBackend.py +++ b/libgui/GuiCanvasWxBackend.py @@ -6,7 +6,7 @@ from ctypes import * from GuiCanvasColours import Colours -import math, os, platform, wx +import math, os, platform, Rtl, wx class GuiBufferedDC(wx.MemoryDC): def __del__(self): @@ -65,70 +65,38 @@ class GuiCanvasWxBackend(): } class _CellState(): - CS_NONE = 0x00 - CS_BOLD = 0x01 - CS_UNDERLINE = 0x02 + CS_NONE = 0x00 + CS_BOLD = 0x01 + 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])] - def _blendColoursBrush(self, bg, fg): - colour = self._blendColours(bg, fg) - return wx.Brush(wx.Colour(colour), wx.BRUSHSTYLE_SOLID), wx.Pen(wx.Colour(colour), 1) - - def _drawPatch(self, eventDc, isCursor, patch, patchBg, point): - absPoint, charFlag = self._xlatePoint(point), False - if (patch[3] == " ") and (patch[1] == -1): - charFlag, patch = True, [*patch[:-1], "░"] - textBg, textFg = wx.Colour(Colours[patch[1]][:4]), wx.Colour(Colours[patch[0]][:4]) - if isCursor and (patch[3] == " ") and ((patchBg[3] != " ") or (patchBg[2] & self._CellState.CS_UNDERLINE)): - charFlag, patch = True, [*patch[:-2], *patchBg[2:]] - textFg = wx.Colour(self._blendColours(patchBg[0], patch[1])) - elif (patch[3] != " ") or (patch[2] & self._CellState.CS_UNDERLINE): - charFlag = True - textBg, textFg = wx.Colour(Colours[patch[1]][:4]), wx.Colour(Colours[patch[0]][:4]) - brush, pen = self._setBrushColours(eventDc, isCursor, patch, patchBg) - eventDc.DrawRectangle(*absPoint, *self.cellSize) - if charFlag: - if (patch[2] & self._CellState.CS_UNDERLINE) or (patch[3] == "_"): - eventDc.SetPen(self._pens[patch[0]]); - 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 patch[3] != "_": - oldClippingRegion = eventDc.GetClippingBox() - eventDc.SetFont(self._font) - eventDc.DestroyClippingRegion(); eventDc.SetClippingRegion(*absPoint, *self.cellSize); - eventDc.SetTextForeground(textFg) - eventDc.DrawText(patch[3], *absPoint) - eventDc.DestroyClippingRegion() - if isCursor: - brush.Destroy(); pen.Destroy(); - if self._lastBrush != None: - eventDc.SetBrush(self._lastBrush) - if self._lastPen != None: - eventDc.SetPen(self._lastPen) - def _finiBrushesAndPens(self): - [brush.Destroy() for brush in self._brushes or []] - [pen.Destroy() for pen in self._pens or []] - self._brushAlpha.Destroy(); self._penAlpha.Destroy(); - self._brushes, self._lastBrush, self._lastPen, self._pens = None, None, None, None + for wxObject in Rtl.flatten([ + (self._brushAlpha,), (*(self._brushes or ()),), (self._penAlpha,), (*(self._pens or ()),), + *[[self._brushesBlend[bg][fg] for fg in self._brushesBlend[bg].keys()] for bg in self._brushesBlend.keys()], + *[[self._pensBlend[bg][fg] for fg in self._pensBlend[bg].keys()] for bg in self._pensBlend.keys()]]): + if wxObject != None: + wxObject.Destroy() + self._brushAlpha, self._brushes, self._brushesBlend, self._lastBrush, self._lastPen, self._penAlpha, self._pens, self._pensBlend = None, [], {}, None, None, None, [], {} def _initBrushesAndPens(self): - self._brushes, self._pens = [None for x in range(len(Colours))], [None for x in range(len(Colours))] + self._brushes, self._brushesBlend, self._lastBrush, self._lastPen, self._pens, self._pensBlend = [], {}, None, None, [], {} + self._brushAlpha, self._penAlpha = wx.Brush(wx.Colour(Colours[14][:4]), wx.BRUSHSTYLE_SOLID), wx.Pen(wx.Colour(Colours[14][:4]), 1) for mircColour in range(len(Colours)): - self._brushes[mircColour] = wx.Brush(wx.Colour(Colours[mircColour][:4]), wx.BRUSHSTYLE_SOLID) - self._pens[mircColour] = wx.Pen(wx.Colour(Colours[mircColour][:4]), 1) - self._brushAlpha = wx.Brush(wx.Colour(Colours[14][:4]), wx.BRUSHSTYLE_SOLID) - self._penAlpha = wx.Pen(wx.Colour(Colours[14][:4]), 1) - self._lastBrush, self._lastPen = None, None + self._brushes += [wx.Brush(wx.Colour(Colours[mircColour][:4]), wx.BRUSHSTYLE_SOLID)]; self._brushesBlend[mircColour] = {}; + self._pens += [wx.Pen(wx.Colour(Colours[mircColour][:4]), 1)]; self._pensBlend[mircColour] = {}; + for mircColourFg in range(len(Colours)): + colourBlend = self._blendColours(mircColour, mircColourFg) + self._brushesBlend[mircColour][mircColourFg] = wx.Brush(wx.Colour(colourBlend), wx.BRUSHSTYLE_SOLID) + self._pensBlend[mircColour][mircColourFg] = wx.Pen(wx.Colour(colourBlend), 1) def _reshapeArabic(self, canvas, eventDc, isCursor, patch, point): - patches = [] - lastCell = point[0] + lastCell, patches = point[0], [] while True: - if ((lastCell + 1) >= (canvas.size[0] - 1)) \ - or (not canvas.map[point[1]][lastCell + 1][3] in self.arabicShapes): + if ((lastCell + 1) >= (canvas.size[0] - 1)) \ + or (not canvas.map[point[1]][lastCell + 1][3] in self.arabicShapes): break else: lastCell += 1 @@ -164,40 +132,64 @@ class GuiCanvasWxBackend(): if not isCursor: brush, pen = self._brushes[patch[1]], self._pens[patch[1]] else: - brush, pen = self._blendColoursBrush(patchBg[1], patch[1]) + bg = patchBg[1] if patchBg[1] != -1 else 14 + brush, pen = self._brushesBlend[bg][patch[1]], self._pensBlend[bg][patch[1]] else: if not isCursor: brush, pen = self._brushAlpha, self._penAlpha else: - brush, pen = self._blendColoursBrush(patchBg[1], 14) - if not isCursor: - if self._lastBrush != brush: - dc.SetBrush(brush); self._lastBrush = brush; - if self._lastPen != pen: - dc.SetPen(pen); self._lastPen = pen; - else: - dc.SetBrush(brush); dc.SetPen(pen); + 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 _xlatePoint(self, point): - return [a * b for a, b in zip(point, self.cellSize)] - def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc): eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - [self.drawPatch(canvas, eventDc, patch) for patch in canvasJournal.popCursor()] + self.drawPatches(canvas, eventDc, canvasJournal.popCursor(), True) eventDc.SetDeviceOrigin(*eventDcOrigin) - def drawPatch(self, canvas, eventDc, patch, isCursor=False): - point = patch[:2] - if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]: - if patch[5] in self.arabicShapes: - for patchReshaped in self._reshapeArabic(canvas, eventDc, isCursor, patch, point): - self._drawPatch(eventDc, isCursor, patchReshaped[2:], canvas.map[patchReshaped[1]][patchReshaped[0]], patchReshaped[:2]) - else: - self._drawPatch(eventDc, isCursor, patch[2:], canvas.map[patch[1]][patch[0]], point) - return True - else: - return False + def drawPatches(self, canvas, eventDc, patches, isCursor=False): + patchDeltaCells, patchesRender = [], [] + for patch in patches: + point = patch[:2] + if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]: + if patch[5] in self.arabicShapes: + for patchReshaped in self._reshapeArabic(canvas, eventDc, isCursor, patch, point): + patchesRender += [patchReshaped] + else: + patchesRender += [patch] + 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 = True, [*patchRender[:-1], "░"] + textBg, textFg = wx.Colour(Colours[patchRender[3]][:4]), wx.Colour(Colours[patchRender[2]][:4]) + 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)): + 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[2])) + elif (patchRender[5] != " ") or (patchRender[4] & self._CellState.CS_UNDERLINE): + charFlag = True + textBg, textFg = wx.Colour(Colours[patchRender[3]][:4]), wx.Colour(Colours[patchRender[2]][:4]) + 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() + patchDeltaCells += [[*patchRender[:2], *canvas.map[patchRender[1]][patchRender[0]]]] + return patchDeltaCells def getDeviceContext(self, clientSize, parentWindow, viewRect=None): if viewRect == None: diff --git a/liboperators/OperatorRotate.py b/liboperators/OperatorRotate.py old mode 100755 new mode 100644 diff --git a/liboperators/OperatorTile.py b/liboperators/OperatorTile.py old mode 100755 new mode 100644 diff --git a/libroar/RoarAssetsWindow.py b/libroar/RoarAssetsWindow.py index 65fdb9e..a22069b 100644 --- a/libroar/RoarAssetsWindow.py +++ b/libroar/RoarAssetsWindow.py @@ -11,10 +11,6 @@ from RtlPlatform import getLocalConfPathName import json, os, sys, wx class RoarAssetsWindow(GuiMiniFrame): - def _drawPatch(self, canvas, eventDc, isCursor, patch): - if not isCursor: - self.backend.drawPatch(canvas, eventDc, patch) - def _import(self, f, pathName): rc = False self.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) @@ -118,9 +114,11 @@ class RoarAssetsWindow(GuiMiniFrame): self.backend.resize(canvas.size, self.cellSize) eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas) eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + patches = [] for numRow in range(canvas.size[1]): for numCol in range(canvas.size[0]): - self.backend.drawPatch(canvas, eventDc, [numCol, numRow, *canvas.map[numRow][numCol]]) + patches += [[numCol, numRow, *canvas.map[numRow][numCol]]] + self.backend.drawPatches(canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) def onPaint(self, event): @@ -154,14 +152,16 @@ class RoarAssetsWindow(GuiMiniFrame): self.backend.resize(newSize, self.cellSize) eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas) 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]): - self._drawPatch(canvas, eventDc, False, [numNewCol, numRow, 1, 1, 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]): - self._drawPatch(canvas, eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "]) + patches += [[numNewCol, numNewRow, 1, 1, 0, " "]] + self.backend.drawPatches(canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) def update(self, canvas, newSize, newCanvas=None): @@ -169,9 +169,11 @@ class RoarAssetsWindow(GuiMiniFrame): canvas.update(newSize, newCanvas); eventDc = self.backend.getDeviceContext(self.panelCanvas.GetClientSize(), self.panelCanvas) eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + patches = [] for numRow in range(canvas.size[1]): for numCol in range(canvas.size[0]): - self.backend.drawPatch(canvas, eventDc, [numCol, numRow, *canvas.map[numRow][numCol]]) + patches += [[numCol, numRow, *canvas.map[numRow][numCol]]] + self.backend.drawPatches(canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) def onImportAnsi(self, event): diff --git a/libroar/RoarCanvasCommandsEdit.py b/libroar/RoarCanvasCommandsEdit.py index 3ce13d1..05f3a25 100644 --- a/libroar/RoarCanvasCommandsEdit.py +++ b/libroar/RoarCanvasCommandsEdit.py @@ -194,14 +194,12 @@ class RoarCanvasCommandsEdit(): @GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False) def canvasRedo(self, event): - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, self.parentCanvas.GetViewStart()) - self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popRedo(), eventDc=eventDc, forceDirtyCursor=True) + self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popRedo(), forceDirtyCursor=True) self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel) @GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False) def canvasUndo(self, event): - eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas, self.parentCanvas.GetViewStart()) - self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popUndo(), eventDc=eventDc, forceDirtyCursor=True) + self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popUndo(), forceDirtyCursor=True) self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel) def __init__(self): diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index 4245549..df97bfa 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -40,97 +40,86 @@ class RoarCanvasWindowDropTarget(wx.TextDropTarget): super().__init__(); self.inProgress, self.parent = False, parent; class RoarCanvasWindow(GuiWindow): - def _drawPatch(self, eventDc, isCursor, patch): - if not self.canvas.dirtyCursor: - self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) - self.canvas.dirtyCursor = True - if self.backend.drawPatch(self.canvas, eventDc, patch, isCursor=isCursor) and isCursor: - patchDeltaCell = self.canvas.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell]; - self.canvas.journal.pushCursor(patchDelta) + def _applyPatches(self, eventDc, patches, patchesCursor, rc): + if rc: + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + if ((patches != None) and (len(patches) > 0)) \ + or ((patchesCursor != None) and (len(patchesCursor) > 0)): + self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc); self.canvas.dirtyCursor = True; + if (patches != None) and (len(patches) > 0): + self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) + self.dirty = True if not self.dirty else self.dirty; + self.canvas.journal.begin() + for patch in patches if patches != None else []: + self.canvas.dispatchPatchSingle(False, patch, commitUndo=True) + self.canvas.journal.end() + if patchesCursor != None: + patchesCursorDeltaCells = self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) + if len(patchesCursorDeltaCells) > 0: + self.canvas.dirtyCursor = False if self.canvas.dirtyCursor else self.canvas.dirtyCursor + for patchCursorDeltaCell in patchesCursorDeltaCells: + self.canvas.journal.pushCursor(patchCursorDeltaCell) + eventDc.SetDeviceOrigin(*eventDcOrigin) + self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) def applyOperator(self, currentTool, mapPoint, mouseLeftDown, mousePoint, operator, viewRect): - self.canvas.dirtyCursor = False - if (currentTool.__class__ == ToolObject) \ - and (currentTool.toolState >= currentTool.TS_SELECT): + eventDc, patches, patchesCursor, rc = self.backend.getDeviceContext(self.GetClientSize(), self), None, None, True + if (currentTool.__class__ == ToolObject) and (currentTool.toolState >= currentTool.TS_SELECT): region = currentTool.getRegion(self.canvas) else: region = self.canvas.map if hasattr(operator, "apply2"): if mouseLeftDown: - if self.commands.operatorState == None: - self.commands.operatorState = True + self.commands.operatorState = True if self.commands.operatorState == None else self.commands.operatorState region = operator.apply2(mapPoint, mousePoint, region, copy.deepcopy(region)) self.commands.update(operator=self.commands.currentOperator.name) elif self.commands.operatorState != None: - self.commands.currentOperator = None - self.commands.update(operator=None) - return + self.commands.currentOperator = None; self.commands.update(operator=None); rc = False; else: - region = operator.apply(copy.deepcopy(region)) - self.commands.currentOperator = None - if (currentTool.__class__ == ToolObject) \ - and (currentTool.toolState >= currentTool.TS_SELECT): - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) if self.popupEventDc == None else self.popupEventDc - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - currentTool.setRegion(self.canvas, None, region, [len(region[0]), len(region)], currentTool.external) - currentTool.onSelectEvent(self.canvas, (0, 0), self.dispatchPatchSingle, eventDc, True, wx.MOD_NONE, None, currentTool.targetRect) - currentTool._drawSelectRect(currentTool.targetRect, self.dispatchPatchSingle, eventDc) - eventDc.SetDeviceOrigin(*eventDcOrigin) - else: - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) if self.popupEventDc == None else self.popupEventDc - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - self.canvas.journal.begin() - for numRow in range(len(region)): - for numCol in range(len(region[numRow])): - self.dirty = True if not self.dirty else self.dirty - self.dispatchPatchSingle(eventDc, False, [numCol, numRow, *region[numRow][numCol]]) - self.canvas.journal.end() - self.commands.update(dirty=self.dirty, undoLevel=self.canvas.journal.patchesUndoLevel) - eventDc.SetDeviceOrigin(*eventDcOrigin) + region = operator.apply(copy.deepcopy(region)); self.commands.currentOperator = None; + if rc: + if (currentTool.__class__ == ToolObject) and (currentTool.toolState >= currentTool.TS_SELECT): + currentTool.setRegion(self.canvas, None, region, [len(region[0]), len(region)], currentTool.external) + rc, patches, patchesCursor = currentTool.onSelectEvent(self.canvas, (0, 0), True, wx.MOD_NONE, None, currentTool.targetRect) + patchesCursor = [] if patchesCursor == None else patchesCursor + patchesCursor += currentTool._drawSelectRect(currentTool.targetRect) + else: + patches = [] + for numRow in range(len(region)): + for numCol in range(len(region[numRow])): + patches += [[numCol, numRow, *region[numRow][numCol]]] + self._applyPatches(eventDc, patches, patchesCursor, rc) + return rc def applyTool(self, eventDc, eventMouse, keyChar, keyCode, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown, tool, viewRect, force=False): - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - dirty, self.canvas.dirtyCursor, rc = False, False, False - self.canvas.journal.begin() + dirty, patches, patchesCursor, rc = False, None, None, False if eventMouse: - if force: - self.lastCellState = None - if ((mapPoint[0] < self.canvas.size[0]) \ - and (mapPoint[1] < self.canvas.size[1])) \ - and ((self.lastCellState == None) \ - or (self.lastCellState != [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)])): - if tool.__class__ != ToolText: - self.brushPos = list(mapPoint) + self.lastCellState = None if force else self.lastCellState + if ((mapPoint[0] < self.canvas.size[0]) and (mapPoint[1] < self.canvas.size[1])) \ + and ((self.lastCellState == None) or (self.lastCellState != [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)])): + self.brushPos = list(mapPoint) if tool.__class__ != ToolText else self.brushPos if tool != None: - rc, dirty = tool.onMouseEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, self.dispatchPatchSingle, eventDc, keyModifiers, self.brushPos, mouseDragging, mouseLeftDown, mouseRightDown) + rc, patches, patchesCursor = tool.onMouseEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, keyModifiers, self.brushPos, mouseDragging, mouseLeftDown, mouseRightDown) else: - self.dispatchPatchSingle(eventDc, True, [*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]) + rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]] self.lastCellState = [list(mapPoint), mouseDragging, mouseLeftDown, mouseRightDown, list(viewRect)] else: if tool != None: - rc, dirty = tool.onKeyboardEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, self.dispatchPatchSingle, eventDc, keyChar, keyCode, keyModifiers, self.brushPos) + rc, patches, patchesCursor = tool.onKeyboardEvent(mapPoint, self.brushColours, self.brushPos, self.brushSize, self.canvas, keyChar, keyCode, keyModifiers, self.brushPos) elif mapPoint != None: - self.dispatchPatchSingle(eventDc, True, [*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]) - self.canvas.journal.end() - if dirty: - self.dirty = True - self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) - else: - self.commands.update(cellPos=self.brushPos) - if rc and (tool.__class__ == ToolObject): - if tool.toolState > tool.TS_NONE: - self.commands.update(undoInhibit=True) - elif tool.toolState == tool.TS_NONE: - if tool.external: - self.dropTarget.done() - self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool - if self.commands.currentTool != None: - self.commands.update(toolName=self.commands.currentTool.name, undoInhibit=False) + rc, patches, patchesCursor = True, None, [[*mapPoint, self.brushColours[0], self.brushColours[0], 0, " "]] + if rc: + self._applyPatches(eventDc, patches, patchesCursor, rc) + if tool.__class__ == ToolObject: + if tool.toolState > tool.TS_NONE: + self.commands.update(undoInhibit=True) + elif tool.toolState == tool.TS_NONE: + if tool.external: + self.dropTarget.done(); self.commands.currentTool, self.commands.lastTool = self.commands.lastTool, self.commands.currentTool; + newToolName = "Cursor" if self.commands.currentTool == None else self.commands.currentTool.name + self.commands.update(toolName=newToolName, undoInhibit=False) else: - self.commands.update(toolName="Cursor", undoInhibit=False) - else: - self.commands.update(undoInhibit=False) - eventDc.SetDeviceOrigin(*eventDcOrigin) + self.commands.update(undoInhibit=False) return rc def dispatchDeltaPatches(self, deltaPatches, eventDc=None, forceDirtyCursor=True): @@ -140,6 +129,7 @@ class RoarCanvasWindow(GuiWindow): if self.canvas.dirtyCursor or forceDirtyCursor: self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) self.canvas.dirtyCursor = False + patches = [] for patch in deltaPatches: if patch == None: continue @@ -148,46 +138,8 @@ class RoarCanvasWindow(GuiWindow): eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); else: - self.canvas._commitPatch(patch); self.backend.drawPatch(self.canvas, eventDc, patch) - eventDc.SetDeviceOrigin(*eventDcOrigin) - - def dispatchPatch(self, eventDc, isCursor, patch): - if self.canvas.dispatchPatch(isCursor, patch, False if isCursor else True): - self._drawPatch(eventDc, isCursor, patch) - - def dispatchPatchSingle(self, eventDc, isCursor, patch): - if self.canvas.dispatchPatchSingle(isCursor, patch, False if isCursor else True): - self._drawPatch(eventDc, isCursor, patch) - - def resize(self, newSize, commitUndo=True, dirty=True): - 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): - super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)]) - self.backend.resize(newSize, self.backend.cellSize) - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - if deltaSize[0] > 0: - for numRow in range(oldSize[1]): - for numNewCol in range(oldSize[0], newSize[0]): - self._drawPatch(eventDc, False, [numNewCol, numRow, 1, 1, 0, " "]) - if deltaSize[1] > 1: - for numNewRow in range(oldSize[1], newSize[1]): - for numNewCol in range(newSize[0]): - self._drawPatch(eventDc, False, [numNewCol, numNewRow, 1, 1, 0, " "]) - eventDc.SetDeviceOrigin(*eventDcOrigin) - self.Scroll(*viewRect); self.dirty = dirty; - self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel) - - def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True): - self.resize(newSize, commitUndo, dirty) - self.canvas.update(newSize, newCanvas) - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - for numRow in range(newSize[1]): - for numCol in range(newSize[0]): - self.backend.drawPatch(self.canvas, eventDc, [numCol, numRow, *self.canvas.map[numRow][numCol]]) + self.canvas._commitPatch(patch); patches += [patch]; + self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) def onKeyboardInput(self, event): @@ -268,9 +220,11 @@ class RoarCanvasWindow(GuiWindow): self.backend.resize(self.canvas.size, self.backend.cellSize) eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); + patches = [] for numRow in range(self.canvas.size[1]): for numCol in range(len(self.canvas.map[numRow])): - self._drawPatch(eventDc, False, [numCol, numRow, *self.canvas.map[numRow][numCol]]) + patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] + self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) else: event.Skip() @@ -280,6 +234,40 @@ class RoarCanvasWindow(GuiWindow): self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart()) + def resize(self, newSize, commitUndo=True, dirty=True): + 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): + super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)]) + self.backend.resize(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) + + def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True): + self.resize(newSize, commitUndo, dirty) + self.canvas.update(newSize, newCanvas) + eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart()) + eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); patches = []; + for numRow in range(newSize[1]): + for numCol in range(newSize[0]): + patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] + self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) + eventDc.SetDeviceOrigin(*eventDcOrigin) + def __init__(self, backend, canvas, cellSize, commands, parent, parentFrame, pos, scrollStep, size): super().__init__(parent, pos, scrollStep) self.size = size diff --git a/librtl/Rtl.py b/librtl/Rtl.py new file mode 100644 index 0000000..63c548f --- /dev/null +++ b/librtl/Rtl.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# +# Rtl.py +# Copyright (c) 2018, 2019 Lucio Andrés Illanes Albornoz +# This project is licensed under the terms of the MIT licence. +# + +import statistics, time + +timeState = [None, None, 0, []] + +def flatten(l): + return [li for l_ in l for li in l_] + +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/Tool.py b/libtools/Tool.py index 0e0c631..5a72878 100644 --- a/libtools/Tool.py +++ b/libtools/Tool.py @@ -5,10 +5,10 @@ # class Tool(object): - def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyChar, keyCode, keyModifiers, mapPoint): - return False, False + def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyChar, keyCode, keyModifiers, mapPoint): + return False, None, None - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - return False, False + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + return False, None, None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolCircle.py b/libtools/ToolCircle.py index 788183b..3317768 100644 --- a/libtools/ToolCircle.py +++ b/libtools/ToolCircle.py @@ -9,8 +9,8 @@ from Tool import Tool class ToolCircle(Tool): name = "Circle" - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - brushColours, brushSize, dirty = list(brushColours), [brushSize[0] * 2, brushSize[1]], False + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + brushColours, brushSize, isCursor = list(brushColours), [brushSize[0] * 2, brushSize[1]], not (mouseLeftDown or mouseRightDown) originPoint, radius = (brushSize[0] / 2, brushSize[0] / 2), brushSize[0] if mouseRightDown: brushColours = [brushColours[1], brushColours[0]] @@ -22,6 +22,7 @@ class ToolCircle(Tool): cells[-1] += [[mapPoint[i] + int(originPoint[i] + o) for i, o in zip((0, 1,), (brushX, brushY,))]] if cells[-1] == []: del cells[-1] + patches = [] for numRow in range(len(cells)): for numCol in range(len(cells[numRow])): if ((numRow == 0) or (numRow == (len(cells) - 1))) \ @@ -41,12 +42,7 @@ class ToolCircle(Tool): patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "] else: patch = [*cells[numRow][numCol], brushColours[1], brushColours[1], 0, " "] - if mouseLeftDown or mouseRightDown: - if not dirty: - dirty = True - dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); - else: - dispatchFn(eventDc, True, patch) - return True, dirty + patches += [patch] + return True, patches if not isCursor else None, patches if isCursor else None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolErase.py b/libtools/ToolErase.py index c5200d0..78541a7 100644 --- a/libtools/ToolErase.py +++ b/libtools/ToolErase.py @@ -9,28 +9,21 @@ from Tool import Tool class ToolErase(Tool): name = "Erase" - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - brushColours, brushSize, dirty = list(brushColours), list(brushSize), False + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + brushColours, brushSize, isCursor, patches, patchesCursor = list(brushColours), list(brushSize), not (mouseLeftDown or mouseRightDown), [], [] if brushSize[0] > 1: brushSize[0] *= 2 for brushRow in range(brushSize[1]): for brushCol in range(brushSize[0]): if mouseLeftDown: - patch = [mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[1], brushColours[1], 0, " "] - if not dirty: - dirty = True - dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); + patches += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[1], brushColours[1], 0, " "]] elif mouseRightDown \ and ((mapPoint[0] + brushCol) < canvas.size[0]) \ and ((mapPoint[1] + brushRow) < canvas.size[1]) \ and (canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][1] == brushColours[1]): - patch = [mapPoint[0] + brushCol, mapPoint[1] + brushRow, canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][0], brushColours[0], *canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][2:]] - if not dirty: - dirty = True - dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); + patches += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][0], brushColours[0], *canvas.map[mapPoint[1] + brushRow][mapPoint[0] + brushCol][2:]]] else: - patch = [mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[1], brushColours[1], 0, " "] - dispatchFn(eventDc, True, patch) - return True, dirty + patchesCursor += [[mapPoint[0] + brushCol, mapPoint[1] + brushRow, brushColours[1], brushColours[1], 0, " "]] + return True, patches if not isCursor else None, patchesCursor # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolFill.py b/libtools/ToolFill.py index b520bb0..eeead3b 100644 --- a/libtools/ToolFill.py +++ b/libtools/ToolFill.py @@ -10,22 +10,17 @@ import wx class ToolFill(Tool): name = "Fill" - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - dirty, pointsDone, pointStack, testChar, testColour = False, [], [list(mapPoint)], canvas.map[mapPoint[1]][mapPoint[0]][3], canvas.map[mapPoint[1]][mapPoint[0]][0:2] + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + isCursor, patches, pointsDone, pointStack, testChar, testColour = not (mouseLeftDown or mouseRightDown), [], [], [list(mapPoint)], canvas.map[mapPoint[1]][mapPoint[0]][3], canvas.map[mapPoint[1]][mapPoint[0]][0:2] if mouseLeftDown or mouseRightDown: - if mouseLeftDown: - fillColour = brushColours[0] - else: - fillColour = brushColours[1] + fillColour = brushColours[0] if mouseLeftDown else brushColours[1] while len(pointStack) > 0: point = pointStack.pop() pointCell = canvas.map[point[1]][point[0]] if ((pointCell[1] == testColour[1]) and ((pointCell[3] == testChar) or (keyModifiers == wx.MOD_CONTROL))) \ or ((pointCell[3] == " ") and (pointCell[1] == testColour[1])): if not point in pointsDone: - if not dirty: - dirty = True - dispatchFn(eventDc, False, [*point, fillColour, fillColour, 0, " "]) + patches += [[*point, fillColour, fillColour, 0, " "]] if point[0] > 0: pointStack.append([point[0] - 1, point[1]]) if point[0] < (canvas.size[0] - 1): @@ -36,8 +31,7 @@ class ToolFill(Tool): pointStack.append([point[0], point[1] + 1]) pointsDone += [point] else: - patch = [mapPoint[0], mapPoint[1], brushColours[0], brushColours[0], 0, " "] - dispatchFn(eventDc, True, patch) - return True, dirty + patches = [[mapPoint[0], mapPoint[1], brushColours[0], brushColours[0], 0, " "]] + return True, patches if not isCursor else None, patches if isCursor else None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolLine.py b/libtools/ToolLine.py index e9a6baa..e506344 100644 --- a/libtools/ToolLine.py +++ b/libtools/ToolLine.py @@ -11,9 +11,8 @@ class ToolLine(Tool): TS_NONE = 0 TS_ORIGIN = 1 - def _getLine(self, brushColours, brushSize, dispatchFn, eventDc, isCursor, originPoint, targetPoint): - dirty = False - originPoint, targetPoint = originPoint.copy(), targetPoint.copy() + def _getLine(self, brushColours, brushSize, isCursor, originPoint, targetPoint): + originPoint, patches, targetPoint = originPoint.copy(), [], targetPoint.copy() pointDelta = self._pointDelta(originPoint, targetPoint) lineXSign = 1 if pointDelta[0] > 0 else -1; lineYSign = 1 if pointDelta[1] > 0 else -1; pointDelta = [abs(a) for a in pointDelta] @@ -27,21 +26,15 @@ class ToolLine(Tool): for lineX in range(pointDelta[0] + 1): for brushStep in range(brushSize[0]): if not ([originPoint[0] + lineX * lineXX + lineY * lineYX + brushStep, originPoint[1] + lineX * lineXY + lineY * lineYY] in pointsDone): - patch = [ \ + patches += [[ \ originPoint[0] + lineX * lineXX + lineY * lineYX + brushStep, \ originPoint[1] + lineX * lineXY + lineY * lineYY, \ - *brushColours, 0, " "] - if not isCursor: - if not dirty: - dirty = True - dispatchFn(eventDc, False, patch) - else: - dispatchFn(eventDc, True, patch) + *brushColours, 0, " "]] pointsDone += [[originPoint[0] + lineX * lineXX + lineY * lineYX + brushStep, originPoint[1] + lineX * lineXY + lineY * lineYY]] if lineD > 0: lineD -= pointDelta[0]; lineY += 1; lineD += pointDelta[1] - return dirty + return patches def _pointDelta(self, a, b): return [a2 - a1 for a1, a2 in zip(a, b)] @@ -49,8 +42,8 @@ class ToolLine(Tool): def _pointSwap(self, a, b): return [b, a] - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - brushColours, dirty = brushColours.copy(), False + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + brushColours, isCursor, patches, rc = brushColours.copy(), not (mouseLeftDown or mouseRightDown), [], False if mouseLeftDown: brushColours[1] = brushColours[0] elif mouseRightDown: @@ -60,17 +53,20 @@ class ToolLine(Tool): if self.toolState == self.TS_NONE: if mouseLeftDown or mouseRightDown: self.toolOriginPoint, self.toolState = list(mapPoint), self.TS_ORIGIN - dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "]) + patches, rc = [], True + for brushCol in range(brushSize[0]): + if ((mapPoint[0] + brushCol) < canvas.size[0]) \ + and (mapPoint[1] < canvas.size[1]): + patches += [[mapPoint[0] + brushCol, mapPoint[1], *brushColours, 0, " "]] elif self.toolState == self.TS_ORIGIN: originPoint, targetPoint = self.toolOriginPoint, list(mapPoint) if mouseLeftDown or mouseRightDown: - dirty = self._getLine(brushColours, brushSize, dispatchFn, eventDc, False, originPoint, targetPoint) + patches = self._getLine(brushColours, brushSize, False, originPoint, targetPoint) self.toolOriginPoint, self.toolState = None, self.TS_NONE else: - dirty = self._getLine(brushColours, brushSize, dispatchFn, eventDc, True, originPoint, targetPoint) - else: - return False, dirty - return True, dirty + patches = self._getLine(brushColours, brushSize, True, originPoint, targetPoint) + rc = True + return rc, patches if not isCursor else None, patches if isCursor else None def __init__(self, *args): super().__init__(*args) diff --git a/libtools/ToolObject.py b/libtools/ToolObject.py index 3e357cd..897c3bb 100644 --- a/libtools/ToolObject.py +++ b/libtools/ToolObject.py @@ -14,19 +14,21 @@ class ToolObject(Tool): TS_SELECT = 2 TS_TARGET = 3 - def _dispatchSelectEvent(self, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, selectRect): + def _dispatchSelectEvent(self, canvas, keyModifiers, mapPoint, mouseLeftDown, selectRect): if mouseLeftDown: disp, isCursor = [mapPoint[m] - self.lastAtPoint[m] for m in [0, 1]], True newTargetRect = [[selectRect[n][m] + disp[m] for m in [0, 1]] for n in [0, 1]] self.lastAtPoint = list(mapPoint) else: disp, isCursor, newTargetRect = [0, 0], True, selectRect.copy() - dirty = self.onSelectEvent(canvas, disp, dispatchFn, eventDc, isCursor, keyModifiers, newTargetRect, selectRect) - self._drawSelectRect(newTargetRect, dispatchFn, eventDc) + rc, patches, patchesCursor = self.onSelectEvent(canvas, disp, isCursor, keyModifiers, newTargetRect, selectRect) + patchesCursor = [] if patchesCursor == None else patchesCursor + patchesCursor += self._drawSelectRect(newTargetRect) self.targetRect = newTargetRect - return dirty + return rc, patches, patchesCursor - def _drawSelectRect(self, rect, dispatchFn, eventDc): + def _drawSelectRect(self, rect): + patches = [] rectFrame = [[rect[m][n] for n in [0, 1]] for m in (0, 1)] if rectFrame[0][0] > rectFrame[1][0]: rectFrame[0][0], rectFrame[1][0] = rectFrame[1][0], rectFrame[0][0] @@ -35,25 +37,19 @@ class ToolObject(Tool): curColours, rectFrame = [0, 0], [[rectFrame[m[0]][n] + m[1] for n in [0, 1]] for m in [[0, -1], [1, +1]]] for rectX in range(rectFrame[0][0], rectFrame[1][0] + 1): curColours = [1, 1] if curColours == [0, 0] else [0, 0] - dispatchFn(eventDc, True, [rectX, rectFrame[0][1], *curColours, 0, " "]) - dispatchFn(eventDc, True, [rectX, rectFrame[1][1], *curColours, 0, " "]) + patches += [[rectX, rectFrame[0][1], *curColours, 0, " "], [rectX, rectFrame[1][1], *curColours, 0, " "]] for rectY in range(rectFrame[0][1], rectFrame[1][1] + 1): curColours = [1, 1] if curColours == [0, 0] else [0, 0] - dispatchFn(eventDc, True, [rectFrame[0][0], rectY, *curColours, 0, " "]) - dispatchFn(eventDc, True, [rectFrame[1][0], rectY, *curColours, 0, " "]) + patches += [[rectFrame[0][0], rectY, *curColours, 0, " "], [rectFrame[1][0], rectY, *curColours, 0, " "]] + return patches - def _mouseEventTsNone(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): - self.substract = False - if self.external: - dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "]) - else: - if mouseLeftDown: - self.targetRect, self.toolState = [list(mapPoint), []], self.TS_ORIGIN - else: - dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, " "]) - return False + def _mouseEventTsNone(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): + patchesCursor = [[*mapPoint, brushColours[0], brushColours[0], 0, " "]]; self.substract = False; + if (not self.external) and mouseLeftDown: + self.targetRect, self.toolState = [list(mapPoint), []], self.TS_ORIGIN + return True, None, patchesCursor - def _mouseEventTsOrigin(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): + def _mouseEventTsOrigin(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): self.targetRect[1] = list(mapPoint) if not mouseLeftDown: if self.targetRect[0][0] > self.targetRect[1][0]: @@ -66,13 +62,10 @@ class ToolObject(Tool): for numCol in range((self.targetRect[1][0] - self.targetRect[0][0]) + 1): rectX, rectY = self.targetRect[0][0] + numCol, self.targetRect[0][1] + numRow self.objectMap[numRow].append(canvas.map[rectY][rectX]) - self._drawSelectRect(self.targetRect, dispatchFn, eventDc) - else: - self._drawSelectRect(self.targetRect, dispatchFn, eventDc) - return False + return True, None, self._drawSelectRect(self.targetRect) - def _mouseEventTsSelect(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): - dirty = False + def _mouseEventTsSelect(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): + rc, patches, patchesCursor = False, None, None if mouseLeftDown: if (mapPoint[0] >= (self.targetRect[0][0] - 1)) \ and (mapPoint[0] <= (self.targetRect[1][0] + 1)) \ @@ -80,80 +73,78 @@ class ToolObject(Tool): and (mapPoint[1] <= (self.targetRect[1][1] + 1)): self.lastAtPoint, self.toolState = list(mapPoint), self.TS_TARGET else: - dirty = self.onSelectEvent(canvas, (0, 0), dispatchFn, eventDc, False, keyModifiers, self.targetRect.copy(), self.targetRect) - self._drawSelectRect(self.targetRect, dispatchFn, eventDc) + rc, patches, patchesCursor = self.onSelectEvent(canvas, (0, 0), False, keyModifiers, self.targetRect.copy(), self.targetRect) + patchesCursor = [] if patchesCursor == None else patchesCursor + patchesCursor += self._drawSelectRect(self.targetRect) self.objectMap, self.objectSize, self.targetRect, self.toolState = None, None, None, self.TS_NONE else: - dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, self.targetRect) - return dirty + rc, patches, patchesCursor = self._dispatchSelectEvent(canvas, keyModifiers, mapPoint, mouseLeftDown, self.targetRect) + return rc, patches, patchesCursor - def _mouseEventTsTarget(self, brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): + def _mouseEventTsTarget(self, brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown): + rc, patches, patchesCursor = False, None, None if (keyModifiers == wx.MOD_CONTROL) and (self.srcRect == self.targetRect): self.substract = True - dirty = False if mouseLeftDown: - dirty = self._dispatchSelectEvent(canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseLeftDown, self.targetRect) + rc, patches, patchesCursor = self._dispatchSelectEvent(canvas, keyModifiers, mapPoint, mouseLeftDown, self.targetRect) else: self.toolState = self.TS_SELECT - return dirty + return rc, patches, patchesCursor def getRegion(self, canvas): return self.objectMap - def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyChar, keyCode, keyModifiers, mapPoint): - rc, dirty = False, False - if (ord(keyChar) == wx.WXK_ESCAPE) \ - and (self.toolState >= self.TS_SELECT): - dirty = self.onSelectEvent(canvas, (0, 0), dispatchFn, eventDc, False, keyModifiers, self.targetRect.copy(), self.targetRect) - self._drawSelectRect(self.targetRect, dispatchFn, eventDc) + def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyChar, keyCode, keyModifiers, mapPoint): + if (ord(keyChar) == wx.WXK_ESCAPE) and (self.toolState >= self.TS_SELECT): + rc, patches, patchesCursor = self.onSelectEvent(canvas, (0, 0), False, keyModifiers, self.targetRect.copy(), self.targetRect) + patchesCursor = [] if patchesCursor == None else patchesCursor + patchesCursor += self._drawSelectRect(self.targetRect) self.objectMap, self.objectSize, self.targetRect, self.toolState = None, None, None, self.TS_NONE - return rc, dirty - - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - dirty = False - if self.toolState == self.TS_NONE: - dirty = self._mouseEventTsNone(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) - elif self.toolState == self.TS_SELECT: - dirty = self._mouseEventTsSelect(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) - elif self.toolState == self.TS_ORIGIN: - dirty = self._mouseEventTsOrigin(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) - elif self.toolState == self.TS_TARGET: - dirty = self._mouseEventTsTarget(brushColours, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) else: - return False, dirty - return True, dirty + rc, patches, patchesCursor = False, None, None + return rc, patches, patchesCursor - def onSelectEvent(self, canvas, disp, dispatchFn, eventDc, isCursor, keyModifiers, newTargetRect, selectRect): - dirty = False + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + if self.toolState == self.TS_NONE: + rc, patches, patchesCursor = self._mouseEventTsNone(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) + elif self.toolState == self.TS_SELECT: + rc, patches, patchesCursor = self._mouseEventTsSelect(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) + elif self.toolState == self.TS_ORIGIN: + rc, patches, patchesCursor = self._mouseEventTsOrigin(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) + elif self.toolState == self.TS_TARGET: + rc, patches, patchesCursor = self._mouseEventTsTarget(brushColours, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown) + else: + rc, patches, patchesCursor = False, None, None + return rc, patches, patchesCursor + + def onSelectEvent(self, canvas, disp, isCursor, keyModifiers, newTargetRect, selectRect): + patches = [] if self.external: for numRow in range(len(self.objectMap)): for numCol in range(len(self.objectMap[numRow])): rectX, rectY = selectRect[0][0] + numCol, selectRect[0][1] + numRow - dirty = False if isCursor else True cellNew = self.objectMap[numRow][numCol] if (cellNew[1] == -1) and (cellNew[3] == " "): if ((rectY + disp[1]) < canvas.size[1]) and ((rectX + disp[0]) < canvas.size[0]): cellNew = canvas.map[rectY + disp[1]][rectX + disp[0]] - dispatchFn(eventDc, isCursor, [rectX + disp[0], rectY + disp[1], *cellNew]) + patches += [[rectX + disp[0], rectY + disp[1], *cellNew]] else: if self.substract: for numRow in range(self.srcRect[0][1], self.srcRect[1][1]): for numCol in range(self.srcRect[0][0], self.srcRect[1][0]): if ((numCol < selectRect[0][0]) or (numCol > selectRect[1][0])) \ or ((numRow < selectRect[0][1]) or (numRow > selectRect[1][1])): - dirty = False if isCursor else True - dispatchFn(eventDc, isCursor, [numCol, numRow, 1, 1, 0, " "]) + patches += [[numCol, numRow, 1, 1, 0, " "]] for numRow in range(len(self.objectMap)): for numCol in range(len(self.objectMap[numRow])): cellOld = self.objectMap[numRow][numCol] rectX, rectY = selectRect[0][0] + numCol, selectRect[0][1] + numRow - dirty = False if isCursor else True cellNew = self.objectMap[numRow][numCol] if (cellNew[1] == -1) and (cellNew[3] == " "): if ((rectY + disp[1]) < canvas.size[1]) and ((rectX + disp[0]) < canvas.size[0]): cellNew = canvas.map[rectY + disp[1]][rectX + disp[0]] - dispatchFn(eventDc, isCursor, [rectX + disp[0], rectY + disp[1], *cellNew]) - return dirty + patches += [[rectX + disp[0], rectY + disp[1], *cellNew]] + return True, patches if not isCursor else None, patches if isCursor else None def setRegion(self, canvas, mapPoint, objectMap, objectSize, external=True): self.external, self.toolState = external, self.TS_SELECT diff --git a/libtools/ToolPickColour.py b/libtools/ToolPickColour.py index c94e70f..42ede31 100644 --- a/libtools/ToolPickColour.py +++ b/libtools/ToolPickColour.py @@ -9,9 +9,8 @@ from Tool import Tool class ToolPickColour(Tool): name = "Pick colour" - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - if (mapPoint[0] < canvas.size[0]) \ - and (mapPoint[1] < canvas.size[1]): + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + if (mapPoint[0] < canvas.size[0]) and (mapPoint[1] < canvas.size[1]): if mouseLeftDown: if canvas.map[mapPoint[1]][mapPoint[0]][3] == " ": brushColours[0] = canvas.map[mapPoint[1]][mapPoint[0]][1] @@ -19,7 +18,6 @@ class ToolPickColour(Tool): brushColours[0] = canvas.map[mapPoint[1]][mapPoint[0]][0] elif mouseRightDown: brushColours[1] = canvas.map[mapPoint[1]][mapPoint[0]][1] - dispatchFn(eventDc, True, [*mapPoint, *brushColours, 0, "░"]) - return True, False + return True, None, [[*mapPoint, *brushColours, 0, "░"]] # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolRect.py b/libtools/ToolRect.py index d673b85..6d7d33f 100644 --- a/libtools/ToolRect.py +++ b/libtools/ToolRect.py @@ -9,8 +9,8 @@ from Tool import Tool class ToolRect(Tool): name = "Rectangle" - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): - brushColours, brushSize, dirty = list(brushColours), list(brushSize), False + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + brushColours, brushSize, isCursor, patches = list(brushColours), list(brushSize), not (mouseLeftDown or mouseRightDown), [] if mouseRightDown: brushColours = [brushColours[1], brushColours[0]] if brushSize[0] > 1: @@ -30,12 +30,7 @@ class ToolRect(Tool): else: patchColours = [brushColours[1]] * 2 patch = [mapPoint[0] + brushCol, mapPoint[1] + brushRow, *patchColours, 0, " "] - if mouseLeftDown or mouseRightDown: - if not dirty: - dirty = True - dispatchFn(eventDc, False, patch); dispatchFn(eventDc, True, patch); - else: - dispatchFn(eventDc, True, patch) - return True, dirty + patches += [patch] + return True, patches if not isCursor else None, patches if isCursor else None # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libtools/ToolText.py b/libtools/ToolText.py index 6566fa1..a47f809 100644 --- a/libtools/ToolText.py +++ b/libtools/ToolText.py @@ -41,11 +41,12 @@ class ToolText(Tool): break return rtlFlag - def _processKeyChar(self, brushColours, brushPos, canvas, dispatchFn, eventDc, keyChar, keyModifiers): + def _processKeyChar(self, brushColours, brushPos, canvas, keyChar, keyModifiers): + patches, rc = [], False if (ord(keyChar) != wx.WXK_NONE) \ and (not keyChar in set("\t\n\v\f\r")) \ and ((ord(keyChar) >= 32) if ord(keyChar) < 127 else True): - dispatchFn(eventDc, False, [*brushPos, *brushColours, 0, keyChar]); + patches += [[*brushPos, *brushColours, 0, keyChar]] if not self._checkRtl(canvas, brushPos, keyChar): if brushPos[0] < (canvas.size[0] - 1): brushPos[0] += 1 @@ -60,38 +61,36 @@ class ToolText(Tool): brushPos[0], brushPos[1] = canvas.size[0] - 1, brushPos[1] - 1 else: brushPos[0], brushPos[1] = canvas.size[0] - 1, canvas.size[1] - 1 - rc, dirty = True, True - else: - rc, dirty = False, False - return rc, dirty + rc = True + return rc, patches - def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyChar, keyCode, keyModifiers, mapPoint): + def onKeyboardEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyChar, keyCode, keyModifiers, mapPoint): + patches, patchesCursor, rc = [], [], False if re.match(self.arabicCombiningRegEx, keyChar): - rc, dirty = True, False + rc = True elif keyCode == wx.WXK_CONTROL_V: - rc, dirty = True, False + rc = True if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)) \ and wx.TheClipboard.Open(): inBuffer = wx.TextDataObject() if wx.TheClipboard.GetData(inBuffer): for inBufferChar in list(inBuffer.GetText()): if not re.match(self.arabicCombiningRegEx, inBufferChar): - rc_, dirty_ = self._processKeyChar(brushColours, brushPos, canvas, dispatchFn, eventDc, inBufferChar, 0) - rc = True if rc_ else rc; dirty = True if dirty_ else dirty; + rc_, patches_ = self._processKeyChar(brushColours, brushPos, canvas, inBufferChar, 0) + patches += patches_ + rc = True if rc_ else rc if rc: - dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"]) + patchesCursor += [[*brushPos, *brushColours, 0, "_"]] wx.TheClipboard.Close() else: rc, error = False, "Clipboard does not contain text data and/or cannot be opened" elif keyCode == wx.WXK_BACK: if ((brushPos[0] + 1) >= canvas.size[0]): - if brushPos[1] > 0: - lastBrushPos = [0, brushPos[1] - 1] - else: - lastBrushPos = [0, 0] + lastBrushPos = [0, brushPos[1] - 1] if brushPos[1] > 0 else [0, 0] else: lastBrushPos = [brushPos[0] + 1, brushPos[1]] if not self._checkRtl(canvas, lastBrushPos, None): + patches += [[*brushPos, *brushColours, 0, " "]] if brushPos[0] > 0: brushPos[0] -= 1 elif brushPos[1] > 0: @@ -105,24 +104,23 @@ class ToolText(Tool): brushPos[0], brushPos[1] = 0, brushPos[1] - 1 else: brushPos[0], brushPos[1] = canvas.size[0] - 1, 0 - rc, dirty = True, False; dispatchFn(eventDc, False, [*brushPos, *brushColours, 0, " "]); - dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"]); + rc = True; patchesCursor += [[*brushPos, *brushColours, 0, "_"]]; elif keyCode == wx.WXK_RETURN: if brushPos[1] < (canvas.size[1] - 1): brushPos[0], brushPos[1] = 0, brushPos[1] + 1 else: brushPos[0], brushPos[1] = 0, 0 - rc, dirty = True, False; dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"]); + rc = True; patchesCursor += [[*brushPos, *brushColours, 0, "_"]]; else: - rc, dirty = self._processKeyChar(brushColours, brushPos, canvas, dispatchFn, eventDc, keyChar, keyModifiers) + rc, patches_ = self._processKeyChar(brushColours, brushPos, canvas, keyChar, keyModifiers) + patches += patches_ if rc: - dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"]) - return rc, dirty + patchesCursor += [[*brushPos, *brushColours, 0, "_"]] + return rc, patches, patchesCursor - def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, dispatchFn, eventDc, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): + def onMouseEvent(self, atPoint, brushColours, brushPos, brushSize, canvas, keyModifiers, mapPoint, mouseDragging, mouseLeftDown, mouseRightDown): if mouseLeftDown or mouseRightDown: brushPos[0], brushPos[1] = atPoint[0], atPoint[1] - dispatchFn(eventDc, True, [*brushPos, *brushColours, 0, "_"]) - return True, False + return True, None, [[*brushPos, *brushColours, 0, "_"]] # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120