From f428daa9e0414f86f815bdf50f4ac66a56303d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucio=20Andr=C3=A9s=20Illanes=20Albornoz?= Date: Sat, 28 Sep 2019 12:08:52 +0200 Subject: [PATCH] Minor cleanup & usability improvements. 1) Add format clarification to commands documentation. 2) Allow changing {canvas,brush} size w/ { ,} . 3) Backend: {draw,produce,remove} cursor w/ list of coordinates instead of cells. 4) Update changing rendered cell size hotkey to . --- assets/text/TODO | 7 ++- assets/text/{hotkeys.txt => melp.txt} | 18 +++++--- libcanvas/Canvas.py | 36 ++++------------ libcanvas/CanvasJournal.py | 2 +- libgui/GuiCanvasWxBackend.py | 13 ++++-- libroar/RoarCanvasCommandsEdit.py | 6 +-- libroar/RoarCanvasWindow.py | 61 +++++++++++++-------------- libroar/RoarWindowMelp.py | 2 +- 8 files changed, 66 insertions(+), 79 deletions(-) rename assets/text/{hotkeys.txt => melp.txt} (67%) diff --git a/assets/text/TODO b/assets/text/TODO index 93b9c6a..1cb4cb2 100644 --- a/assets/text/TODO +++ b/assets/text/TODO @@ -19,9 +19,8 @@ Release roadmap: 1) {copy,cut,delete,insert from,paste}, edit asset in new canvas, import from {canvas,object} -2) reimplement cursor unmasking w/ simple list of points -3) operators: crop, scale, shift, slice -4) auto{load,save} & {backup,restore} -5) tools: unicode block elements +2) operators: crop, scale, shift, slice +3) auto{load,save} & {backup,restore} +4) tools: unicode block elements vim:ff=dos tw=0 diff --git a/assets/text/hotkeys.txt b/assets/text/melp.txt similarity index 67% rename from assets/text/hotkeys.txt rename to assets/text/melp.txt index 6b06225..035961c 100644 --- a/assets/text/hotkeys.txt +++ b/assets/text/melp.txt @@ -1,6 +1,10 @@ -Global hotkeys: - -/+ Decrease/increase brush size height and width - -/+ Decrease/increase canvas size height and width +Keys or mouse actions separated by forward slashes (`/') indicate alternatives. +Keys or mouse actions separated by commas indicate separate commands. + +Global commands: + -, +/ Decrease/increase brush size height and width + Decrease/increase rendered cell size + -, +/ Decrease/increase canvas size height and width 0-9 Set foreground colour to #0-9 0-5, 6 Set foreground colour to #10-15 or transparent colour, resp. 0-9 Set background colour to #0-9 @@ -15,15 +19,15 @@ Global hotkeys: View melp? Break into Python debugger -Canvas hotkeys: +Canvas commands: , , , Move canvas cursor / Apply current tool with foreground colour (with exceptions) Apply current tool with background colour (with exceptions) -Tool-specific hotkeys: -(Circle, rectangle) / Initiate circle/rectangle dragging irrespective of brush size +Tool-specific commands: +(Circle, rectangle) , Initiate circle/rectangle dragging irrespective of brush size (Erase) Erase background colour with foreground colour -(Fill) // Fill entire region with foreground/background colour ignoring character cells +(Fill) , / Fill entire region with foreground/background colour ignoring character cells (Line, object) / Initiate line drawing/selection (Object) Move selection instead of cloning (Pick colour) / Pick current cell's foreground colour diff --git a/libcanvas/Canvas.py b/libcanvas/Canvas.py index bd88c44..6064092 100644 --- a/libcanvas/Canvas.py +++ b/libcanvas/Canvas.py @@ -12,30 +12,14 @@ class Canvas(): def _commitPatch(self, patch): self.map[patch[1]][patch[0]] = patch[2:] - def dispatchPatch(self, isCursor, patch, commitUndo=True): + def applyPatch(self, patch, commitUndo=True): if (patch[0] >= self.size[0]) or (patch[1] >= self.size[1]): return False else: patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell]; - if isCursor: - self.journal.pushCursor(patchDelta) - else: - if commitUndo: - self.journal.begin(); self.journal.updateCurrentDeltas(patch, patchDelta); self.journal.end(); - self._commitPatch(patch) - return True - - def dispatchPatchSingle(self, isCursor, patch, commitUndo=True): - if (patch[0] >= self.size[0]) or (patch[1] >= self.size[1]): - return False - else: - patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell]; - if isCursor: - self.journal.pushCursor(patchDelta) - else: - if commitUndo: - self.journal.updateCurrentDeltas(patch, patchDelta) - self._commitPatch(patch) + if commitUndo: + self.journal.updateCurrentDeltas(patch, patchDelta) + self._commitPatch(patch) return True def resize(self, newSize, commitUndo=True): @@ -62,7 +46,7 @@ class Canvas(): for numNewCol in range(oldSize[0], newSize[0]): if commitUndo: self.journal.updateCurrentDeltas([numNewCol, numRow, 1, 1, 0, " "], None) - self.dispatchPatch(False, [numNewCol, numRow, 1, 1, 0, " "], False) + self.applyPatch([numNewCol, numRow, 1, 1, 0, " "], False) if deltaSize[1] < 0: if commitUndo: for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]): @@ -75,7 +59,7 @@ class Canvas(): for numNewCol in range(newSize[0]): if commitUndo: self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None) - self.dispatchPatch(False, [numNewCol, numNewRow, 1, 1, 0, " "], False) + self.applyPatch([numNewCol, numNewRow, 1, 1, 0, " "], False) self.size = newSize if commitUndo: self.journal.end() @@ -86,13 +70,11 @@ class Canvas(): def update(self, newSize, newCanvas=None): for numRow in range(self.size[1]): for numCol in range(self.size[0]): - if (newCanvas != None) \ - and (numRow < len(newCanvas)) \ - and (numCol < len(newCanvas[numRow])): + if (newCanvas != None) \ + and (numRow < len(newCanvas)) and (numCol < len(newCanvas[numRow])): self._commitPatch([numCol, numRow, *newCanvas[numRow][numCol]]) def __init__(self, size): - self.dirtyCursor, self.map, self.size = False, None, size - self.exportStore, self.importStore, self.journal = CanvasExportStore(), CanvasImportStore(), CanvasJournal() + self.exportStore, self.importStore, self.journal, self.map, self.size = CanvasExportStore(), CanvasImportStore(), CanvasJournal(), None, size # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 diff --git a/libcanvas/CanvasJournal.py b/libcanvas/CanvasJournal.py index 5f464e5..c0d91ac 100644 --- a/libcanvas/CanvasJournal.py +++ b/libcanvas/CanvasJournal.py @@ -37,7 +37,7 @@ class CanvasJournal(): return [] def pushCursor(self, patches): - self.patchesCursor.append(patches) + self.patchesCursor = patches def resetCursor(self): if self.patchesCursor != None: diff --git a/libgui/GuiCanvasWxBackend.py b/libgui/GuiCanvasWxBackend.py index 7bf94c8..2cc9ddd 100644 --- a/libgui/GuiCanvasWxBackend.py +++ b/libgui/GuiCanvasWxBackend.py @@ -148,11 +148,16 @@ class GuiCanvasWxBackend(): def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc): eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - self.drawPatches(canvas, eventDc, canvasJournal.popCursor(), False) + cursorCells, patches = canvasJournal.popCursor(), [] + for cursorCell in cursorCells: + patches += [[*cursorCell, *canvas.map[cursorCell[1]][cursorCell[0]]]] + if len(patches) > 0: + self.drawPatches(canvas, eventDc, patches, False) eventDc.SetDeviceOrigin(*eventDcOrigin) + return cursorCells def drawPatches(self, canvas, eventDc, patches, isCursor=False): - patchDeltaCells, patchesRender = [], [] + patchCursorCells, patchesRender = [], [] for patch in patches: point = patch[:2] if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]: @@ -186,8 +191,8 @@ class GuiCanvasWxBackend(): 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 + patchCursorCells += [patchRender[:2]] + return patchCursorCells def getDeviceContext(self, clientSize, parentWindow, viewRect=None): if viewRect == None: diff --git a/libroar/RoarCanvasCommandsEdit.py b/libroar/RoarCanvasCommandsEdit.py index 05f3a25..4ea854b 100644 --- a/libroar/RoarCanvasCommandsEdit.py +++ b/libroar/RoarCanvasCommandsEdit.py @@ -194,13 +194,11 @@ class RoarCanvasCommandsEdit(): @GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False) def canvasRedo(self, event): - self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popRedo(), forceDirtyCursor=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.journal.patchesUndoLevel); @GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False) def canvasUndo(self, event): - self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popUndo(), forceDirtyCursor=True) - 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.journal.patchesUndoLevel); def __init__(self): self.accels = ( diff --git a/libroar/RoarCanvasWindow.py b/libroar/RoarCanvasWindow.py index dd88e94..cea64cf 100644 --- a/libroar/RoarCanvasWindow.py +++ b/libroar/RoarCanvasWindow.py @@ -45,20 +45,18 @@ class RoarCanvasWindow(GuiWindow): 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; + self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc) 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.applyPatch(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) + patchesCursorCells = self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) + if len(patchesCursorCells) > 0: + self.canvas.journal.pushCursor(patchesCursorCells) eventDc.SetDeviceOrigin(*eventDcOrigin) self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) @@ -122,26 +120,6 @@ class RoarCanvasWindow(GuiWindow): self.commands.update(undoInhibit=False) return rc - def dispatchDeltaPatches(self, deltaPatches, eventDc=None, forceDirtyCursor=True): - if eventDc == None: - eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); - 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 - 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); - else: - self.canvas._commitPatch(patch); patches += [patch]; - self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) - eventDc.SetDeviceOrigin(*eventDcOrigin) - def onKeyboardInput(self, event): keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers() viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect); @@ -211,9 +189,9 @@ class RoarCanvasWindow(GuiWindow): event.Skip() def onMouseWheel(self, event): - if event.GetModifiers() == wx.MOD_CONTROL: - fd = +1 if event.GetWheelRotation() >= event.GetWheelDelta() else -1 - newFontSize = self.backend.fontSize + fd + delta, modifiers = +1 if event.GetWheelRotation() >= event.GetWheelDelta() else -1, event.GetModifiers() + if modifiers == (wx.MOD_CONTROL | wx.MOD_ALT): + newFontSize = self.backend.fontSize + delta if newFontSize > 0: self.backend.fontSize = newFontSize self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize; @@ -226,12 +204,15 @@ class RoarCanvasWindow(GuiWindow): patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) eventDc.SetDeviceOrigin(*eventDcOrigin) + 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: + self.commands.canvasBrushSize(self.commands.canvasBrushSize, 2, 1 if delta > 0 else 0)(None) else: event.Skip() def onPaint(self, event): eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) - 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): @@ -257,6 +238,24 @@ class RoarCanvasWindow(GuiWindow): self.Scroll(*viewRect); self.dirty = dirty; self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel) + def undo(self, redo=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) + 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); + else: + self.canvas._commitPatch(patch); patches += [patch]; + self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) + eventDc.SetDeviceOrigin(*eventDcOrigin) + def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True): self.resize(newSize, commitUndo, dirty) self.canvas.update(newSize, newCanvas) diff --git a/libroar/RoarWindowMelp.py b/libroar/RoarWindowMelp.py index 702996e..5ef84aa 100644 --- a/libroar/RoarWindowMelp.py +++ b/libroar/RoarWindowMelp.py @@ -14,7 +14,7 @@ class RoarWindowMelp(wx.Dialog): super().__init__(parent, size=minSize, title=title) self.panel, self.sizer = wx.Panel(self), wx.BoxSizer(wx.VERTICAL) - with open(os.path.join("assets", "text", "hotkeys.txt"), "r") as fileObject: + with open(os.path.join("assets", "text", "melp.txt"), "r") as fileObject: helpLabel = "".join(fileObject.readlines()) self.title = wx.StaticText(self.panel, label=helpLabel, style=wx.ALIGN_LEFT) self.title.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, underline=False))