Various bugfixes & usability improvements.

1) Backend: initial optimised cell rendering Python C module implementation skeleton.
2) Backend: raise alpha blending {fore,back}ground colour coefficient to {0.8,1.0 - 0.8}, resp.
3) Backend: reimplement cell rendering using Draw{Rectangle,Text}List().
4) Canvas window: eliminate {canvas,{scroll,tool}bar} flickering during resize.
5) Canvas window: fix cursor artifacts during resizing by masking cursor.
6) Canvas window: restore cursor after executing operations that remove it.
7) Import store: correctly parse non-conforming \u0003,<bg colour> sequences.
8) GUI: correctly save list of recently used files post-update.
This commit is contained in:
Lucio Andrés Illanes Albornoz 2019-10-01 19:03:29 +02:00
parent f5b9e9da7b
commit 3bb4fb0197
15 changed files with 201 additions and 105 deletions

4
.gitignore vendored
View File

@ -1,5 +1,9 @@
*.sw[op]
__pycache__/
build/
libgui/GuiCanvasWxBackendFast.exp
libgui/GuiCanvasWxBackendFast.lib
libgui/GuiCanvasWxBackendFast.obj
libgui/GuiCanvasWxBackendFast.pyd
librtl/ImgurApiKey.py
releases/

23
.vscode/c_cpp_properties.json vendored Executable file
View File

@ -0,0 +1,23 @@
{
"configurations": [
{
"name": "Win32",
"includePath": [
"${workspaceFolder}/**",
"C:/Python37/include/**"
],
"defines": [
"_DEBUG",
"UNICODE",
"_UNICODE"
],
"windowsSdkVersion": "10.0.18362.0",
"compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.23.28105/bin/Hostx64/x64/cl.exe",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "msvc-x64",
"compilerArgs": []
}
],
"version": 4
}

View File

@ -1,4 +1,5 @@
{
"python.analysis.disabled": ["unresolved-import"],
"python.pythonPath": "C:\\Python37\\python.exe",
"python.linting.enabled": false
}

7
.vscode/tasks.json vendored
View File

@ -4,9 +4,12 @@
"version": "2.0.0",
"tasks": [
{
"label": "echo",
"label": "Build libgui/GuiCanvasWxBackendFast.pyd",
"type": "shell",
"command": "echo Hello"
"command": "cd \"${workspaceFolder}/libgui\" && cmd /c cl /LD /IC:/Python37/include GuiCanvasWxBackendFast.c C:/Python37/libs/python37.lib /FeGuiCanvasWxBackendFast.pyd",
"problemMatcher": [
"$msCompile"
]
}
]
}

View File

@ -1,25 +1,20 @@
1) ANSI CSI CU[BDPU] sequences
2) Fix & finish Arabic/RTL text tool support
3) Documentation, instrumentation & unit tests
4) Layers & layout (e.g. for comics, zines, etc.)
5) Open and toggle a reference image in the background
6) Client-Server or Peer-to-Peer realtime collaboration
7) Arbitrary {format,palette}s ({4,8} bit ANSI/mIRC, etc.)
8) {record,replay} {keyboard,mouse,...} events in debugging builds
9) Unit tools: arrow, {cloud,speech bubble}, curve, measure, polygon, triangle
8) Unit tools: arrow, {cloud,speech bubble}, curve, polygon
9) {record,replay} {keyboard,mouse,...} events in debugging builds
10) Integrate ENNTool code in the form of OpenGL-based animation window (see 13) and 14))
11) Composition, parametrisation & keying of tools from higher-order operators (brushes, functions, filters, outlines, patterns & shaders) and unit tools
12) Sprites & scripted (Python?) animation on the basis of asset traits and {composable,parametrised} patterns (metric flow, particle system, rigging, ...)
13) GUI TODO list:
a) switch to Gtk
b) https://material.io/resources/icons/?style=baseline
c) replace logo w/ canvas panel in About dialogue, revisit melp? dialogue
d) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3)
e) Settings/Settings window (e.g. autosave, cursor opacity, hide cursor on leaving window, ...)
Release roadmap:
1) {copy,cut,delete,insert from,paste}, edit asset in new canvas, import from {canvas,object}
2) operators: crop, scale, shift, slice
3) tools: unicode block elements
b) canvas preview in Open dialogue(s)
c) https://material.io/resources/icons/?style=baseline
d) replace logo w/ canvas panel in About dialogue, revisit melp? dialogue
e) replace resize buttons w/ {-,edit box,+} buttons & lock button re: ratio (ty lol3)
f) Settings/Settings window (e.g. autosave, cursor opacity, hide cursor on leaving window, ...)
vim:ff=dos tw=0

13
assets/text/roadmap.txt Normal file
View File

@ -0,0 +1,13 @@
1) canvas: default to transparent background colour
2) GUI: disable tiling items unless current tool is object tool
3) GUI: edit asset in new canvas, import from {canvas,object}
4) GUI: implement GuiCanvasWxBackendFast.c{,c}
5) GUI: select all
6) GUI: show line numbers w/ either ruler or tooltip
7) operators: copy, cut, delete, insert from, paste
8) operators: crop, scale, shift, slice operators
9) tools: measure, triangle, unicode block elements tool
10) tools: text tool: finish Arabic/RTL text implementation
11) tools: text tool: implicitly draw (text) w/ bg -1, toggle drawing actual brushColours[1] mode w/ RMB
vim:ff=dos tw=0

View File

@ -29,7 +29,6 @@ class Canvas():
else:
oldSize = self.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)]
self.journal.resetCursor()
if commitUndo:
self.journal.begin()
undoPatches, redoPatches = ["resize", *oldSize], ["resize", *newSize]

View File

@ -101,6 +101,8 @@ class CanvasImportStore():
inCurColours = (int(m[2]), int(m[3]))
elif (m[2] != None) and (m[3] == None):
inCurColours = (int(m[2]), int(inCurColours[1]))
elif (m[2] == None) and (m[3] != None):
inCurColours = (int(inCurColours[0]), int(m[3]))
else:
inCurColours = (15, -1)
inCurCol += len(m[0])

View File

@ -15,9 +15,11 @@ class CanvasJournal():
if self.patchesUndoLevel > 0:
del self.patchesUndo[:self.patchesUndoLevel]; self.patchesUndoLevel = 0;
def popCursor(self):
def popCursor(self, reset=True):
if len(self.patchesCursor):
patchesCursor = self.patchesCursor; self.patchesCursor = [];
patchesCursor = self.patchesCursor
if reset:
self.resetCursor()
return patchesCursor
else:
return []
@ -40,8 +42,6 @@ class CanvasJournal():
self.patchesCursor = patches
def resetCursor(self):
if self.patchesCursor != None:
self.patchesCursor.clear()
self.patchesCursor = []
def resetUndo(self):
@ -57,7 +57,6 @@ class CanvasJournal():
self.resetCursor(); self.resetUndo();
def __init__(self):
self.patchesCursor, self.patchesUndo, self. patchesUndoLevel = None, None, None
self.resetCursor(); self.resetUndo();
self.patchesUndo = None; self.resetCursor(); self.resetUndo();
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -6,7 +6,8 @@
from ctypes import *
from GuiCanvasColours import Colours
import math, os, platform, Rtl, wx
import GuiCanvasWxBackendFast
import math, os, platform, wx
class GuiBufferedDC(wx.MemoryDC):
def __del__(self):
@ -70,7 +71,7 @@ class GuiCanvasWxBackend():
CS_UNDERLINE = 0x02
def _blendColours(self, bg, fg):
return [int((fg * 0.75) + (bg * (1.0 - 0.75))) for bg, fg in zip(Colours[bg][:3], Colours[fg][:3])]
return [int((fg * 0.8) + (bg * (1.0 - 0.8))) for bg, fg in zip(Colours[bg][:3], Colours[fg][:3])]
def _finiBrushesAndPens(self):
for wxObject in Rtl.flatten([
@ -140,24 +141,23 @@ class GuiCanvasWxBackend():
else:
bg = patchBg[1] if patchBg[1] != -1 else 14
brush, pen = self._brushesBlend[bg][14], self._pensBlend[bg][14]
if self._lastBrush != brush:
dc.SetBrush(brush); self._lastBrush = brush;
if self._lastPen != pen:
dc.SetPen(pen); self._lastPen = pen;
return brush, pen
def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc):
def drawCursorMaskWithJournal(self, canvas, canvasJournal, eventDc, reset=True):
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
cursorCells, patches = canvasJournal.popCursor(), []
for cursorCell in cursorCells:
cursorPatches = canvasJournal.popCursor(reset=reset); patches = [];
for cursorCell in [p[:2] for p in cursorPatches]:
if (cursorCell[0] < canvas.size[0]) \
and (cursorCell[1] < canvas.size[1]):
patches += [[*cursorCell, *canvas.map[cursorCell[1]][cursorCell[0]]]]
if len(patches) > 0:
self.drawPatches(canvas, eventDc, patches, False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
return cursorCells
return cursorPatches
def drawPatches(self, canvas, eventDc, patches, isCursor=False):
patchCursorCells, patchesRender = [], []
GuiCanvasWxBackendFast.drawPatches()
patchesRender = []
for patch in patches:
point = patch[:2]
if [(c >= 0) and (c < s) for c, s in zip(point, self.canvasSize)] == [True, True]:
@ -166,33 +166,33 @@ class GuiCanvasWxBackend():
patchesRender += [patchReshaped]
else:
patchesRender += [patch]
for patchRender in patchesRender:
absPoint, charFlag = [a * b for a, b in zip(self.cellSize, patchRender[:2])], False
if (patchRender[5] == " ") and (patchRender[3] == -1):
charFlag, patchRender, textFg = True, [*patchRender[:-1], ""], wx.Colour(0, 0, 0, 255)
elif isCursor and (patchRender[5] == " ") and ((canvas.map[patchRender[1]][patchRender[0]][3] != " ") or (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE)):
charFlag, patchRender = True, [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]]
textFg = wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3]))
elif (patchRender[5] != " ") or (patchRender[4] & self._CellState.CS_UNDERLINE):
charFlag, textFg = True, wx.Colour(Colours[patchRender[2]][:4])
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()
numPatch, textBg = 0, wx.Colour(0, 0, 0, 0)
rectangles, pens, brushes = [None] * len(patchesRender), [None] * len(patchesRender), [None] * len(patchesRender)
textList, coords, foregrounds, backgrounds = [], [], [], []
eventDc.SetFont(self._font)
eventDc.DestroyClippingRegion(); eventDc.SetClippingRegion(*absPoint, *self.cellSize);
eventDc.SetTextForeground(textFg); eventDc.DrawText(patchRender[5], *absPoint);
eventDc.DestroyClippingRegion()
patchCursorCells += [patchRender[:2]]
return patchCursorCells
for patchRender in patchesRender:
if (patchRender[5] == " ") and (patchRender[3] == -1):
text, textFg = "", wx.Colour(0, 0, 0, 255)
elif isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][3] != " "):
patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]]
text, textFg = canvas.map[patchRender[1]][patchRender[0]][3], wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3]))
elif isCursor and (patchRender[5] == " ") and (canvas.map[patchRender[1]][patchRender[0]][2] & self._CellState.CS_UNDERLINE):
patchRender = [*patchRender[:-2], *canvas.map[patchRender[1]][patchRender[0]][2:]]
text, textFg = "_", wx.Colour(self._blendColours(canvas.map[patchRender[1]][patchRender[0]][0], patchRender[3]))
elif patchRender[5] != " ":
text, textFg = patchRender[5], wx.Colour(Colours[patchRender[2]][:4])
elif patchRender[4] & self._CellState.CS_UNDERLINE:
text, textFg = "_", wx.Colour(Colours[patchRender[2]][:4])
else:
text = None
brush, pen = self._setBrushColours(eventDc, isCursor, patchRender[2:], canvas.map[patchRender[1]][patchRender[0]])
rectangles[numPatch] = [patchRender[:2][0] * self.cellSize[0], patchRender[:2][1] * self.cellSize[1], self.cellSize[0], self.cellSize[1]];
pens[numPatch] = pen; brushes[numPatch] = brush;
if text != None:
textList += [text]; coords += [[patchRender[:2][0] * self.cellSize[0], patchRender[:2][1] * self.cellSize[1]]]; foregrounds += [textFg]; backgrounds += [textBg];
numPatch += 1
eventDc.DrawRectangleList(rectangles, pens, brushes)
eventDc.DrawTextList(textList, coords, foregrounds, backgrounds)
def getDeviceContext(self, clientSize, parentWindow, viewRect=None):
if viewRect == None:

View File

@ -0,0 +1,25 @@
#define PY_SSIZE_T_CLEAN /* Make "s#" use Py_ssize_t rather than int. */
#include <Python.h>
static PyObject *
GuiCanvasWxBackendFast_drawPatches(PyObject *self, PyObject *args)
{
Py_RETURN_NONE;
}
static PyMethodDef
GuiCanvasWxBackendFast_methods[] = {
{"drawPatches", GuiCanvasWxBackendFast_drawPatches, METH_VARARGS, "XXX"},
{NULL, NULL, 0, NULL},
};
static struct PyModuleDef
GuiCanvasWxBackendFastmodule = {
PyModuleDef_HEAD_INIT, "GuiCanvasWxBackendFast", NULL, -1, GuiCanvasWxBackendFast_methods,
};
PyMODINIT_FUNC
PyInit_GuiCanvasWxBackendFast(void)
{
return PyModule_Create(&GuiCanvasWxBackendFastmodule);
}

View File

@ -102,10 +102,7 @@ class RoarCanvasCommandsFile():
self.parentFrame.Bind(wx.EVT_MENU, lambda event: self.canvasOpenRecent(event, pathName), menuItemWindow)
self.lastFiles += [{"menuItemId":menuItemId, "menuItemWindow":menuItemWindow, "pathName":pathName}]
if serialise:
localConfFileName = getLocalConfPathName("Recent.lst")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
for lastFile in [l["pathName"] for l in self.lastFiles]:
print(lastFile, file=outFile)
self._saveRecent()
def _pushSnapshot(self, pathName):
menuItemId = wx.NewId()
@ -122,6 +119,12 @@ class RoarCanvasCommandsFile():
if self.canvasRestore.attrDict["id"] in self.parentFrame.menuItemsById:
self.parentFrame.menuItemsById[self.canvasRestore.attrDict["id"]].Enable(False)
def _saveRecent(self):
localConfFileName = getLocalConfPathName("Recent.lst")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
for lastFile in [l["pathName"] for l in self.lastFiles]:
print(lastFile, file=outFile)
def _storeLastDir(self, pathName):
localConfFileName = getLocalConfPathName("RecentDir.txt")
with open(localConfFileName, "w", encoding="utf-8") as outFile:
@ -170,8 +173,13 @@ class RoarCanvasCommandsFile():
outPathName = dialog.GetPath(); self.lastDir = os.path.dirname(outPathName); self._storeLastDir(self.lastDir);
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas)
self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc)
self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc, reset=False)
self.parentCanvas.canvas.exportStore.exportBitmapToPngFile(self.parentCanvas.backend.canvasBitmap, outPathName, wx.BITMAP_TYPE_PNG)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
cursorPatches = self.parentCanvas.canvas.journal.popCursor(reset=False)
if len(cursorPatches) > 0:
self.parentCanvas.backend.drawPatches(self.parentCanvas.canvas, eventDc, cursorPatches, isCursor=True)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
return True
@ -179,8 +187,13 @@ class RoarCanvasCommandsFile():
def canvasExportImgur(self, event):
self.parentCanvas.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
eventDc = self.parentCanvas.backend.getDeviceContext(self.parentCanvas.GetClientSize(), self.parentCanvas)
self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc)
self.parentCanvas.backend.drawCursorMaskWithJournal(self.parentCanvas.canvas, self.parentCanvas.canvas.journal, eventDc, reset=False)
rc, status, result = self.parentCanvas.canvas.exportStore.exportBitmapToImgur(self.imgurApiKey, self.parentCanvas.backend.canvasBitmap, "", "", wx.BITMAP_TYPE_PNG)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
cursorPatches = self.parentCanvas.canvas.journal.popCursor(reset=False)
if len(cursorPatches) > 0:
self.parentCanvas.backend.drawPatches(self.parentCanvas.canvas, eventDc, cursorPatches, isCursor=True)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.parentCanvas.SetCursor(wx.Cursor(wx.NullCursor))
if rc:
if not wx.TheClipboard.IsOpened():
@ -272,6 +285,7 @@ class RoarCanvasCommandsFile():
if not rc:
numLastFile = [i for i in range(len(self.lastFiles)) if self.lastFiles[i]["pathName"] == pathName][0]
self.canvasOpenRecent.attrDict["menu"].Delete(self.lastFiles[numLastFile]["menuItemId"]); del self.lastFiles[numLastFile];
self._saveRecent()
@GuiSubMenuDecorator("Restore Snapshot", "Res&tore Snapshot", None, None, False)
def canvasRestore(self, event, pathName=None):

View File

@ -56,12 +56,28 @@ class RoarCanvasWindow(GuiWindow):
self.canvas.applyPatch(patch, commitUndo=True)
self.canvas.journal.end()
if patchesCursor != None:
patchesCursorCells = self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True)
if len(patchesCursorCells) > 0:
self.canvas.journal.pushCursor(patchesCursorCells)
self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True)
if len(patchesCursor) > 0:
self.canvas.journal.pushCursor(patchesCursor)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.commands.update(dirty=self.dirty, cellPos=self.brushPos, undoLevel=self.canvas.journal.patchesUndoLevel)
def _eraseBackground(self, eventDc):
viewRect = self.GetViewStart()
canvasSize, panelSize = [a * b for a, b in zip(self.backend.canvasSize, self.backend.cellSize)], self.GetSize()
if viewRect != (0, 0):
viewRect = [a * b for a, b in zip(self.backend.cellSize, viewRect)]
canvasSize = [a - b for a, b in zip(canvasSize, viewRect)]
rectangles, pens, brushes = [], [], []
if panelSize[0] > canvasSize[0]:
brushes += [self.bgBrush]; pens += [self.bgPen];
rectangles += [[canvasSize[0], 0, panelSize[0] - canvasSize[0], panelSize[1]]]
if panelSize[1] > canvasSize[1]:
brushes += [self.bgBrush]; pens += [self.bgPen];
rectangles += [[0, canvasSize[1], panelSize[0], panelSize[1] - canvasSize[1]]]
if len(rectangles) > 0:
eventDc.DrawRectangleList(rectangles, pens, brushes)
def _snapshotsReset(self):
self._snapshotFiles, self._snapshotsUpdateLast = [], time.time()
self.commands._resetSnapshots()
@ -218,6 +234,9 @@ class RoarCanvasWindow(GuiWindow):
def onEnterWindow(self, event):
self.lastCellState = None
def onErase(self, event):
pass
def onLeaveWindow(self, event):
if False:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self, self.GetViewStart())
@ -245,6 +264,7 @@ class RoarCanvasWindow(GuiWindow):
if modifiers == (wx.MOD_CONTROL | wx.MOD_ALT):
newFontSize = self.backend.fontSize + delta
if newFontSize > 0:
self.Freeze()
self.backend.fontSize = newFontSize
self.backend.resize(self.canvas.size); self.scrollStep = self.backend.cellSize;
super().resize([a * b for a, b in zip(self.canvas.size, self.backend.cellSize)])
@ -256,6 +276,7 @@ class RoarCanvasWindow(GuiWindow):
patches += [[numCol, numRow, *self.canvas.map[numRow][numCol]]]
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.Thaw(); del eventDc; self._eraseBackground(wx.ClientDC(self));
elif modifiers == (wx.MOD_CONTROL | wx.MOD_SHIFT):
self.commands.canvasCanvasSize(self.commands.canvasCanvasSize, 2, 1 if delta > 0 else 0)(None)
elif modifiers == wx.MOD_CONTROL:
@ -264,51 +285,64 @@ class RoarCanvasWindow(GuiWindow):
event.Skip()
def onPaint(self, event):
viewRect = self.GetViewStart()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
self.backend.onPaint(self.GetClientSize(), self, self.GetViewStart())
self.backend.onPaint(self.GetClientSize(), self, viewRect)
del eventDc; self._eraseBackground(wx.PaintDC(self));
def resize(self, newSize, commitUndo=True, dirty=True):
def resize(self, newSize, commitUndo=True, dirty=True, freeze=True):
if freeze:
self.Freeze()
viewRect = self.GetViewStart()
oldSize = [0, 0] if self.canvas.map == None else self.canvas.size
deltaSize = [b - a for a, b in zip(oldSize, newSize)]
if self.canvas.resize(newSize, commitUndo):
self.backend.resize(newSize); self.scrollStep = self.backend.cellSize;
super().resize([a * b for a, b in zip(newSize, self.backend.cellSize)])
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
patches = []
if deltaSize[0] > 0:
for numRow in range(oldSize[1]):
for numNewCol in range(oldSize[0], newSize[0]):
patches += [[numNewCol, numRow, 1, 1, 0, " "]]
if deltaSize[1] > 1:
for numNewRow in range(oldSize[1], newSize[1]):
for numNewCol in range(newSize[0]):
patches += [[numNewCol, numNewRow, 1, 1, 0, " "]]
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
eventDc.SetDeviceOrigin(*eventDcOrigin)
self.Scroll(*viewRect); self.dirty = dirty;
self.commands.update(dirty=self.dirty, size=newSize, undoLevel=self.canvas.journal.patchesUndoLevel)
if commitUndo:
self._snapshotsUpdate()
if freeze:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
cursorPatches = self.canvas.journal.popCursor(reset=False)
if len(cursorPatches) > 0:
self.backend.drawPatches(self.canvas, eventDc, cursorPatches, isCursor=True)
eventDc.SetDeviceOrigin(*eventDcOrigin)
del eventDc; self.Thaw(); self._eraseBackground(wx.ClientDC(self));
def undo(self, redo=False):
freezeFlag = False
deltaPatches = self.canvas.journal.popUndo() if not redo else self.canvas.journal.popRedo()
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc)
patchesCursor = self.backend.drawCursorMaskWithJournal(self.canvas, self.canvas.journal, eventDc, reset=False)
patches = []
for patch in deltaPatches:
if patch == None:
continue
elif patch[0] == "resize":
del eventDc; self.resize(patch[1:], False);
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
if not freezeFlag:
self.Freeze(); freezeFlag = True;
eventDc = None; self.resize(patch[1:], False, freeze=False);
else:
self.canvas._commitPatch(patch); patches += [patch];
if eventDc == None:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
self.backend.drawPatches(self.canvas, eventDc, patches, isCursor=False)
if len(patchesCursor):
self.backend.drawPatches(self.canvas, eventDc, patchesCursor, isCursor=True)
eventDc.SetDeviceOrigin(*eventDcOrigin)
if freezeFlag:
eventDc = self.backend.getDeviceContext(self.GetClientSize(), self)
eventDcOrigin = eventDc.GetDeviceOrigin(); eventDc.SetDeviceOrigin(0, 0);
cursorPatches = self.canvas.journal.popCursor(reset=False)
if len(cursorPatches) > 0:
self.backend.drawPatches(self.canvas, eventDc, cursorPatches, isCursor=True)
eventDc.SetDeviceOrigin(*eventDcOrigin)
del eventDc; self.Thaw(); self._eraseBackground(wx.ClientDC(self));
def update(self, newSize, commitUndo=True, newCanvas=None, dirty=True):
self.resize(newSize, commitUndo, dirty)
@ -329,6 +363,8 @@ class RoarCanvasWindow(GuiWindow):
self.popupEventDc = None
self.dropTarget = RoarCanvasWindowDropTarget(self)
self.SetDropTarget(self.dropTarget)
self.bgBrush, self.bgPen = wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID), wx.Pen(self.GetBackgroundColour(), 1)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.onErase)
self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel)
self._snapshotsReset()

View File

@ -5,9 +5,7 @@
# This project is licensed under the terms of the MIT licence.
#
import re, statistics, time
timeState = [None, None, 0, []]
import re
def flatten(l):
return [li for l_ in l for li in l_]
@ -17,19 +15,4 @@ def natural_sort(l):
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
return sorted(l, key=alphanum_key)
def timePrint(pfx):
timeState[0] = time.time() if timeState[0] == None else timeState[0]
t1 = time.time(); td = t1 - timeState[0]
if td > 0:
timeState[1] = td if timeState[1] == None else min(td, timeState[1])
timeState[2] = max(td, timeState[2])
timeState[3] += [td]
print("{} took {:.4f} ms (min: {:.4f} max: {:.4f} avg: {:.4f})".format(pfx, td * 1000, timeState[1] * 1000, timeState[2] * 1000, statistics.mean(timeState[3]) * 1000))
def timeReset():
timeState = [None, None, 0, []]
def timeStart():
timeState[0] = time.time()
# vim:expandtab foldmethod=marker sw=4 ts=4 tw=120

View File

@ -24,8 +24,7 @@ class ToolCircle(Tool):
for numRow in range(len(cells)):
for numCol in range(len(cells[numRow])):
point = cells[numRow][numCol]
if ((point[0] >= 0) and (point[1] >= 0)) \
and (point[0] < canvas.size[0]) and (point[1] < canvas.size[1]):
if (point[0] >= 0) and (point[1] >= 0):
if ((numRow == 0) or (numRow == (len(cells) - 1))) \
or ((numCol == 0) or (numCol == (len(cells[numRow]) - 1))):
patch = [*cells[numRow][numCol], brushColours[0], brushColours[0], 0, " "]