Minor cleanup & usability improvements.

1) Add format clarification to commands documentation.
2) Allow changing {canvas,brush} size w/ {<Ctrl> <Shift>,<Ctrl>} <Mouse wheel>.
3) Backend: {draw,produce,remove} cursor w/ list of coordinates instead of cells.
4) Update changing rendered cell size hotkey to <Ctrl> <Alt> <Mouse wheel>.
This commit is contained in:
Lucio Andrés Illanes Albornoz 2019-09-28 12:08:52 +02:00
parent c84ef3a47e
commit f428daa9e0
8 changed files with 66 additions and 79 deletions

View File

@ -19,9 +19,8 @@
Release roadmap: Release roadmap:
1) {copy,cut,delete,insert from,paste}, edit asset in new canvas, import from {canvas,object} 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 2) operators: crop, scale, shift, slice
3) operators: crop, scale, shift, slice 3) auto{load,save} & {backup,restore}
4) auto{load,save} & {backup,restore} 4) tools: unicode block elements
5) tools: unicode block elements
vim:ff=dos tw=0 vim:ff=dos tw=0

View File

@ -1,6 +1,10 @@
Global hotkeys: Keys or mouse actions separated by forward slashes (`/') indicate alternatives.
<Ctrl> -/+ Decrease/increase brush size height and width Keys or mouse actions separated by commas indicate separate commands.
<Ctrl> <Shift> -/+ Decrease/increase canvas size height and width
Global commands:
<Ctrl> -, +/<Mouse wheel> Decrease/increase brush size height and width
<Ctrl> <Alt> <Mouse wheel> Decrease/increase rendered cell size
<Ctrl> <Shift> -, +/<Mouse wheel> Decrease/increase canvas size height and width
<Ctrl> 0-9 Set foreground colour to #0-9 <Ctrl> 0-9 Set foreground colour to #0-9
<Ctrl> <Shift> 0-5, 6 Set foreground colour to #10-15 or transparent colour, resp. <Ctrl> <Shift> 0-5, 6 Set foreground colour to #10-15 or transparent colour, resp.
<Ctrl> <Alt> 0-9 Set background colour to #0-9 <Ctrl> <Alt> 0-9 Set background colour to #0-9
@ -15,15 +19,15 @@ Global hotkeys:
<F1> View melp? <F1> View melp?
<Shift> <Pause> Break into Python debugger <Shift> <Pause> Break into Python debugger
Canvas hotkeys: Canvas commands:
<Down>, <Left>, <Right>, <Up> Move canvas cursor <Down>, <Left>, <Right>, <Up> Move canvas cursor
<LMB>/<Space> Apply current tool with foreground colour (with exceptions) <LMB>/<Space> Apply current tool with foreground colour (with exceptions)
<RMB> Apply current tool with background colour (with exceptions) <RMB> Apply current tool with background colour (with exceptions)
Tool-specific hotkeys: Tool-specific commands:
(Circle, rectangle) <Ctrl> <LMB>/<RMB> Initiate circle/rectangle dragging irrespective of brush size (Circle, rectangle) <Ctrl> <LMB>, <RMB> Initiate circle/rectangle dragging irrespective of brush size
(Erase) <RMB> Erase background colour with foreground colour (Erase) <RMB> Erase background colour with foreground colour
(Fill) <Ctrl> <LMB>/<Space>/<RMB> Fill entire region with foreground/background colour ignoring character cells (Fill) <Ctrl> <LMB>, <Space>/<RMB> Fill entire region with foreground/background colour ignoring character cells
(Line, object) <LMB>/<Space> Initiate line drawing/selection (Line, object) <LMB>/<Space> Initiate line drawing/selection
(Object) <Ctrl> <LMB> Move selection instead of cloning (Object) <Ctrl> <LMB> Move selection instead of cloning
(Pick colour) <LMB>/<Space> Pick current cell's foreground colour (Pick colour) <LMB>/<Space> Pick current cell's foreground colour

View File

@ -12,27 +12,11 @@ class Canvas():
def _commitPatch(self, patch): def _commitPatch(self, patch):
self.map[patch[1]][patch[0]] = patch[2:] 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]): if (patch[0] >= self.size[0]) or (patch[1] >= self.size[1]):
return False return False
else: else:
patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell]; patchDeltaCell = self.map[patch[1]][patch[0]]; patchDelta = [*patch[0:2], *patchDeltaCell];
if 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: if commitUndo:
self.journal.updateCurrentDeltas(patch, patchDelta) self.journal.updateCurrentDeltas(patch, patchDelta)
self._commitPatch(patch) self._commitPatch(patch)
@ -62,7 +46,7 @@ class Canvas():
for numNewCol in range(oldSize[0], newSize[0]): for numNewCol in range(oldSize[0], newSize[0]):
if commitUndo: if commitUndo:
self.journal.updateCurrentDeltas([numNewCol, numRow, 1, 1, 0, " "], None) self.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 deltaSize[1] < 0:
if commitUndo: if commitUndo:
for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]): for numRow in range((oldSize[1] + deltaSize[1]), oldSize[1]):
@ -75,7 +59,7 @@ class Canvas():
for numNewCol in range(newSize[0]): for numNewCol in range(newSize[0]):
if commitUndo: if commitUndo:
self.journal.updateCurrentDeltas([numNewCol, numNewRow, 1, 1, 0, " "], None) self.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 self.size = newSize
if commitUndo: if commitUndo:
self.journal.end() self.journal.end()
@ -87,12 +71,10 @@ class Canvas():
for numRow in range(self.size[1]): for numRow in range(self.size[1]):
for numCol in range(self.size[0]): for numCol in range(self.size[0]):
if (newCanvas != None) \ if (newCanvas != None) \
and (numRow < len(newCanvas)) \ and (numRow < len(newCanvas)) and (numCol < len(newCanvas[numRow])):
and (numCol < len(newCanvas[numRow])):
self._commitPatch([numCol, numRow, *newCanvas[numRow][numCol]]) self._commitPatch([numCol, numRow, *newCanvas[numRow][numCol]])
def __init__(self, size): def __init__(self, size):
self.dirtyCursor, self.map, self.size = False, None, size self.exportStore, self.importStore, self.journal, self.map, self.size = CanvasExportStore(), CanvasImportStore(), CanvasJournal(), None, size
self.exportStore, self.importStore, self.journal = CanvasExportStore(), CanvasImportStore(), CanvasJournal()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120 # vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -37,7 +37,7 @@ class CanvasJournal():
return [] return []
def pushCursor(self, patches): def pushCursor(self, patches):
self.patchesCursor.append(patches) self.patchesCursor = patches
def resetCursor(self): def resetCursor(self):
if self.patchesCursor != None: if self.patchesCursor != None:

View File

@ -148,11 +148,16 @@ class GuiCanvasWxBackend():
def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc): def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc):
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); 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) eventDc.SetDeviceOrigin(*eventDcOrigin)
return cursorCells
def drawPatches(self, canvas, eventDc, patches, isCursor=False): def drawPatches(self, canvas, eventDc, patches, isCursor=False):
patchDeltaCells, patchesRender = [], [] patchCursorCells, patchesRender = [], []
for patch in patches: for patch in patches:
point = patch[:2] point = patch[:2]
if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]: 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.DestroyClippingRegion(); eventDc.SetClippingRegion(*absPoint, *self.cellSize);
eventDc.SetTextForeground(textFg); eventDc.DrawText(patchRender[5], *absPoint); eventDc.SetTextForeground(textFg); eventDc.DrawText(patchRender[5], *absPoint);
eventDc.DestroyClippingRegion() eventDc.DestroyClippingRegion()
patchDeltaCells += [[*patchRender[:2], *canvas.map[patchRender[1]][patchRender[0]]]] patchCursorCells += [patchRender[:2]]
return patchDeltaCells return patchCursorCells
def getDeviceContext(self, clientSize, parentWindow, viewRect=None): def getDeviceContext(self, clientSize, parentWindow, viewRect=None):
if viewRect == None: if viewRect == None:

View File

@ -194,13 +194,11 @@ class RoarCanvasCommandsEdit():
@GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False) @GuiCommandDecorator("Redo", "&Redo", ["", wx.ART_REDO], [wx.ACCEL_CTRL, ord("Y")], False)
def canvasRedo(self, event): def canvasRedo(self, event):
self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popRedo(), forceDirtyCursor=True) self.parentCanvas.undo(redo=True); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel);
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) @GuiCommandDecorator("Undo", "&Undo", ["", wx.ART_UNDO], [wx.ACCEL_CTRL, ord("Z")], False)
def canvasUndo(self, event): def canvasUndo(self, event):
self.parentCanvas.dispatchDeltaPatches(self.parentCanvas.canvas.journal.popUndo(), forceDirtyCursor=True) self.parentCanvas.undo(); self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel);
self.update(size=self.parentCanvas.canvas.size, undoLevel=self.parentCanvas.canvas.journal.patchesUndoLevel)
def __init__(self): def __init__(self):
self.accels = ( self.accels = (

View File

@ -45,20 +45,18 @@ class RoarCanvasWindow(GuiWindow):
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0); eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
if ((patches != None) and (len(patches) > 0)) \ if ((patches != None) and (len(patches) > 0)) \
or ((patchesCursor != None) and (len(patchesCursor) > 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): if (patches != None) and (len(patches) > 0):
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
self.dirty = True if not self.dirty else self.dirty; self.dirty = True if not self.dirty else self.dirty;
self.canvas.journal.begin() self.canvas.journal.begin()
for patch in patches if patches != None else []: 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() self.canvas.journal.end()
if patchesCursor != None: if patchesCursor != None:
patchesCursorDeltaCells = self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True) patchesCursorCells = self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True)
if len(patchesCursorDeltaCells) > 0: if len(patchesCursorCells) > 0:
self.canvas.dirtyCursor = False if self.canvas.dirtyCursor else self.canvas.dirtyCursor self.canvas.journal.pushCursor(patchesCursorCells)
for patchCursorDeltaCell in patchesCursorDeltaCells:
self.canvas.journal.pushCursor(patchCursorDeltaCell)
eventDc.SetDeviceOrigin(*eventDcOrigin) eventDc.SetDeviceOrigin(*eventDcOrigin)
self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel) 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) self.commands.update(undoInhibit=False)
return rc 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): def onKeyboardInput(self, event):
keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers() keyCode, keyModifiers = event.GetKeyCode(), event.GetModifiers()
viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect); viewRect = self.GetViewStart(); eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, viewRect);
@ -211,9 +189,9 @@ class RoarCanvasWindow(GuiWindow):
event.Skip() event.Skip()
def onMouseWheel(self, event): def onMouseWheel(self, event):
if event.GetModifiers() == wx.MOD_CONTROL: delta, modifiers = +1 if event.GetWheelRotation() >= event.GetWheelDelta() else -1, event.GetModifiers()
fd = +1 if event.GetWheelRotation() >= event.GetWheelDelta() else -1 if modifiers == (wx.MOD_CONTROL | wx.MOD_ALT):
newFontSize = self.backend.fontSize + fd newFontSize = self.backend.fontSize + delta
if newFontSize > 0: if newFontSize > 0:
self.backend.fontSize = newFontSize self.backend.fontSize = newFontSize
self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize; self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize;
@ -226,12 +204,15 @@ class RoarCanvasWindow(GuiWindow):
patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]] patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]]
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False) self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin) 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: else:
event.Skip() event.Skip()
def onPaint(self, event): def onPaint(self, event):
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self) eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc)
self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart()) self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart())
def resize(self, newSize, commitUndo=True, dirty=True): def resize(self, newSize, commitUndo=True, dirty=True):
@ -257,6 +238,24 @@ class RoarCanvasWindow(GuiWindow):
self.Scroll(*viewRect); self.dirty = dirty; self.Scroll(*viewRect); self.dirty = dirty;
self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel) self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.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): def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True):
self.resize(newSize, commitUndo, dirty) self.resize(newSize, commitUndo, dirty)
self.canvas.update(newSize, newCanvas) self.canvas.update(newSize, newCanvas)

View File

@ -14,7 +14,7 @@ class RoarWindowMelp(wx.Dialog):
super().__init__(parent, size=minSize, title=title) super().__init__(parent, size=minSize, title=title)
self.panel, self.sizer = wx.Panel(self), wx.BoxSizer(wx.VERTICAL) self.panel, self.sizer = wx.Panel(self), wx.BoxSizer(wx.VERTICAL)
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()) helpLabel = "".join(fileObject.readlines())
self.title = wx.StaticText(self.panel, label=helpLabel, style=wx.ALIGN_LEFT) 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)) self.title.SetFont(wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, underline=False))