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:
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

View File

@ -1,6 +1,10 @@
Global hotkeys:
<Ctrl> -/+ Decrease/increase brush size height and width
<Ctrl> <Shift> -/+ 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:
<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> <Shift> 0-5, 6 Set foreground colour to #10-15 or transparent colour, resp.
<Ctrl> <Alt> 0-9 Set background colour to #0-9
@ -15,15 +19,15 @@ Global hotkeys:
<F1> View melp?
<Shift> <Pause> Break into Python debugger
Canvas hotkeys:
Canvas commands:
<Down>, <Left>, <Right>, <Up> Move canvas cursor
<LMB>/<Space> Apply current tool with foreground colour (with exceptions)
<RMB> Apply current tool with background colour (with exceptions)
Tool-specific hotkeys:
(Circle, rectangle) <Ctrl> <LMB>/<RMB> Initiate circle/rectangle dragging irrespective of brush size
Tool-specific commands:
(Circle, rectangle) <Ctrl> <LMB>, <RMB> Initiate circle/rectangle dragging irrespective of brush size
(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
(Object) <Ctrl> <LMB> Move selection instead of cloning
(Pick colour) <LMB>/<Space> Pick current cell's foreground colour

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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 = (

View File

@ -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)

View File

@ -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))