diff --git a/tools/dumbPaintTool/app/canvaslayer.py b/tools/dumbPaintTool/app/canvaslayer.py index f0670be0..8240f230 100644 --- a/tools/dumbPaintTool/app/canvaslayer.py +++ b/tools/dumbPaintTool/app/canvaslayer.py @@ -42,14 +42,16 @@ from .const import ToolType # \---w--/ class CanvasLayer(): - __slot__ = ('_pos','_name','_visible','_size','_data','_colors','_preview','_offset','_modified' + __slot__ = ('_pos','_name','_visible','_size','_data','_colors','_preview','_offset', + '_snapVersion', '_snapshots', #signals 'nameChanged','changed') def __init__(self,name:ttk.TTkString=ttk.TTkString('New')) -> None: - self._modified = False self.changed = ttk.pyTTkSignal() self._name:ttk.TTkString = ttk.TTkString(name) if isinstance(name,str) else name self.nameChanged = ttk.pyTTkSignal(ttk.TTkString) + self._snapVersion = 0 + self._snapshots = {} self._pos = (0,0) self._size = (0,0) self._offset = (0,0) @@ -58,9 +60,9 @@ class CanvasLayer(): self._data: list[list[str ]] = [] self._colors:list[list[ttk.TTkColor]] = [] - def clone(self) -> None: + def clone(self) -> object: cl = CanvasLayer() - cl._modified = False + cl._snapVersion = self._snapVersion cl._pos = self._pos cl._size = self._size cl._offset = self._offset @@ -69,8 +71,9 @@ class CanvasLayer(): cl._colors = [row.copy() for row in self._colors] return cl - def restore(self, cl) -> None: - self._modified = False + def restore(self, cl: object) -> None: + self._preview = None + self._snapVersion = cl._snapVersion self._pos = cl._pos self._size = cl._size self._offset = cl._offset @@ -79,6 +82,24 @@ class CanvasLayer(): self._colors = [row.copy() for row in cl._colors] self.changed.emit() + def restoreSnapshot(self, id:int) -> None: + if id == self._snapVersion: + return + ttk.TTkLog.debug(f"restore {id=}") + if id in self._snapshots: + self.restore(self._snapshots[id]) + + def saveSnapshot(self) -> int: + self._snapshots = {key:self._snapshots[key] for key in self._snapshots if key <= self._snapVersion} + if self._snapVersion not in self._snapshots: + ttk.TTkLog.debug(f"{self._snapVersion=}") + self._snapshots[self._snapVersion] = self.clone() + return self._snapVersion + + def clearSnapshot(self) -> None: + self._snapshots = {} + self.saveSnapshot() + def __eq__(self, value: object) -> bool: return ( issubclass(type(value),CanvasLayer) and @@ -102,7 +123,7 @@ class CanvasLayer(): @ttk.pyTTkSlot(bool) def setVisible(self, visible): if visible == self._visible: return - self._modified = True + self._snapVersion += 1 self._visible = visible self.changed.emit() @@ -110,7 +131,7 @@ class CanvasLayer(): return self._name @ttk.pyTTkSlot(str) def setName(self, name): - self._modified = True + self._snapVersion += 1 self._name = name def isOpaque(self,x,y): @@ -125,7 +146,7 @@ class CanvasLayer(): def move(self,x,y): self._pos=(x,y) - self._modified = True + self._snapVersion += 1 self.changed.emit() def resize(self,w,h): @@ -135,7 +156,7 @@ class CanvasLayer(): for i in range(h): self._data[i] = (self._data[i] + [' ' for _ in range(w)])[:w] self._colors[i] = (self._colors[i] + [ttk.TTkColor.RST for _ in range(w)])[:w] - self._modified = True + self._snapVersion += 1 self.changed.emit() def superResize(self,dx,dy,dw,dh): @@ -169,7 +190,7 @@ class CanvasLayer(): self._offset = (ox+diffx,oy+diffy) self._pos = (dx,dy) self._size = (dw,dh) - self._modified = True + self._snapVersion += 1 self.changed.emit() def clean(self): @@ -179,7 +200,7 @@ class CanvasLayer(): for i in range(h): self._data[i] = [' ']*w self._colors[i] = [ttk.TTkColor.RST]*w - self._modified = True + self._snapVersion += 1 self.changed.emit() def toTTkString(self): @@ -324,7 +345,7 @@ class CanvasLayer(): self._import_v1_1_0(dd) else: self._import_v0_0_0(dd) - self._modified = True + self._snapVersion += 1 self.changed.emit() def trim(self): @@ -384,7 +405,7 @@ class CanvasLayer(): self._size = (w,h) self._name = ttk.TTkString("Pasted") - self._modified = True + self._snapVersion += 1 def placeFill(self,geometry,tool,glyph:str,color:ttk.TTkColor,glyphEnabled=True,preview=False): ox,oy = self._offset @@ -402,6 +423,7 @@ class CanvasLayer(): colors = [_r.copy() for _r in self._colors] self._preview = {'data':data,'colors':colors} else: + self._snapVersion += 1 self._preview = None data = self._data colors = self._colors @@ -417,7 +439,6 @@ class CanvasLayer(): for y in range(fay,fby+1): self._placeGlyph(data,colors,fax,y,glyph,color,glyphEnabled,preview) self._placeGlyph(data,colors,fbx,y,glyph,color,glyphEnabled,preview) - self._modified = True self.changed.emit() return True @@ -427,11 +448,11 @@ class CanvasLayer(): colors = [_r.copy() for _r in self._colors] self._preview = {'data':data,'colors':colors} else: + self._snapVersion += 1 self._preview = None data = self._data colors = self._colors - self._modified = True self.changed.emit() return self._placeGlyph(data,colors,x,y,glyph,color,glyphEnabled,preview) @@ -478,6 +499,7 @@ class CanvasLayer(): colors = [_r.copy() for _r in self._colors] self._preview = {'data':data,'colors':colors} else: + self._snapVersion += 1 self._preview = None data = self._data colors = self._colors @@ -495,7 +517,6 @@ class CanvasLayer(): newC._bg = ca._bg if ca._bg else cc._bg colors[_y][_x] = newC - self._modified = True self.changed.emit() def drawInCanvas(self, pos, canvas:ttk.TTkCanvas): diff --git a/tools/dumbPaintTool/app/glbls.py b/tools/dumbPaintTool/app/glbls.py index da015496..822f6fef 100644 --- a/tools/dumbPaintTool/app/glbls.py +++ b/tools/dumbPaintTool/app/glbls.py @@ -33,31 +33,19 @@ from .state.layers import Layers class Snapshot(): __slots__ = ('_layer','_canvasLayers') def __init__(self) -> None: - self._layer = None - self._canvasLayers = [] - if glbls.layers._modified: - self._layer = glbls.layers.clone() - glbls.layers._modified = False - for cl in glbls.layers.layers(): - if cl._modified: - cl._modified = False - self._canvasLayers.append((cl,cl.clone())) - - def valid(self) -> None: - if self._layer or self._canvasLayers: - return True - return False + self._layer = glbls.layers.clone() + self._canvasLayers = [cl.saveSnapshot() for cl in glbls.layers.layers()] def restore(self) -> None: if self._layer: glbls.layers.restore(self._layer) - for cl,clone in self._canvasLayers: - cl.restore(clone) + for cl,snapId in zip(glbls.layers.layers(),self._canvasLayers): + cl.restoreSnapshot(snapId) def __eq__(self, value: object) -> bool: return ( self._layer == value._layer and - all(a==b for a,b in self._canvasLayers)) + self._canvasLayers == value._canvasLayers ) @dataclass() class Glbls: @@ -75,13 +63,17 @@ class Glbls: def saveSnapshot(self): # TODO: Dispose properly of the unused clones snapshot = Snapshot() - if not snapshot.valid(): - return + # if not snapshot.valid(): + # return + if 0 <= self._snapId < len(self._snaphots): + if self._snaphots[self._snapId] == snapshot: + return self._snaphots = self._snaphots[:self._snapId+1] + [snapshot] self._snapId = len(self._snaphots)-1 @ttk.pyTTkSlot() def undo(self): + # ttk.TTkLog.debug(f"{self._snapId=} - {len(self._snaphots)=}") if self._snapId: self._snapId -= 1 self._snaphots[self._snapId].restore() diff --git a/tools/dumbPaintTool/app/paintarea.py b/tools/dumbPaintTool/app/paintarea.py index cc84b717..f183ddd1 100644 --- a/tools/dumbPaintTool/app/paintarea.py +++ b/tools/dumbPaintTool/app/paintarea.py @@ -370,6 +370,9 @@ class PaintArea(ttk.TTkAbstractScrollView): text = glbls.layers.selected().toTTkString() self.copy(text) ret = True + elif cl and evt.key == ttk.TTkK.Key_Delete: + glbls.layers.delLayer() + glbls.saveSnapshot() else: return super().keyEvent(evt) self._retuneGeometry() @@ -389,6 +392,7 @@ class PaintArea(ttk.TTkAbstractScrollView): def pasteEvent(self, txt:str): glbls.layers.addLayer().importTTkString(ttk.TTkString(txt)) + glbls.saveSnapshot() self.update() return True @@ -462,29 +466,29 @@ class PaintArea(ttk.TTkAbstractScrollView): if self._tool & ToolType.RESIZE: rd = self._resizeData - def _drawResizeBorders(_rx,_ry,_rw,_rh,_sel): + def _drawResizeBorders(_rx,_ry,_rw,_rh,_sel,_color=ttk.TTkColor.RST): selColor = ttk.TTkColor.YELLOW + ttk.TTkColor.BG_BLUE # canvas.drawBox(pos=_pos,size=_size) - canvas.drawText(pos=(_rx ,_ry ),text='─'*_rw, color=selColor if _sel & ttk.TTkK.TOP else ttk.TTkColor.RST) - canvas.drawText(pos=(_rx ,_ry+_rh-1),text='─'*_rw, color=selColor if _sel & ttk.TTkK.BOTTOM else ttk.TTkColor.RST) + canvas.drawText(pos=(_rx ,_ry ),text='─'*_rw, color=selColor if _sel & ttk.TTkK.TOP else _color) + canvas.drawText(pos=(_rx ,_ry+_rh-1),text='─'*_rw, color=selColor if _sel & ttk.TTkK.BOTTOM else _color) for _y in range(_ry,_ry+_rh): - canvas.drawText(pos=(_rx ,_y),text='│',color=selColor if _sel & ttk.TTkK.LEFT else ttk.TTkColor.RST) - canvas.drawText(pos=(_rx+_rw-1,_y),text='│',color=selColor if _sel & ttk.TTkK.RIGHT else ttk.TTkColor.RST) - canvas.drawChar(pos=(_rx ,_ry ), char='▛') - canvas.drawChar(pos=(_rx+_rw-1,_ry ), char='▜') - canvas.drawChar(pos=(_rx ,_ry+_rh-1), char='▙') - canvas.drawChar(pos=(_rx+_rw-1,_ry+_rh-1), char='▟') + canvas.drawText(pos=(_rx ,_y),text='│',color=selColor if _sel & ttk.TTkK.LEFT else _color) + canvas.drawText(pos=(_rx+_rw-1,_y),text='│',color=selColor if _sel & ttk.TTkK.RIGHT else _color) + canvas.drawChar(pos=(_rx ,_ry ), char='▛', color=_color) + canvas.drawChar(pos=(_rx+_rw-1,_ry ), char='▜', color=_color) + canvas.drawChar(pos=(_rx ,_ry+_rh-1), char='▙', color=_color) + canvas.drawChar(pos=(_rx+_rw-1,_ry+_rh-1), char='▟', color=_color) sMain = rd['selected'] if rd and rd['type'] == PaintArea else ttk.TTkK.NONE sLayer = rd['selected'] if rd and rd['type'] == CanvasLayer else ttk.TTkK.NONE - _drawResizeBorders(dx-ox-1, dy-oy-1, dw+2, dh+2, sMain) - if cl:=glbls.layers.selected(): lx,ly = cl.pos() lw,lh = cl.size() _drawResizeBorders(lx+dx-ox-1, ly+dy-oy-1, lw+2, lh+2, sLayer) + _drawResizeBorders(dx-ox-1, dy-oy-1, dw+2, dh+2, sMain, _color=ttk.TTkColor.YELLOW) + class PaintScrollArea(ttk.TTkAbstractScrollArea): def __init__(self, pwidget:PaintArea, **kwargs): diff --git a/tools/dumbPaintTool/app/state/layers.py b/tools/dumbPaintTool/app/state/layers.py index 0a384ed1..c9f6de43 100644 --- a/tools/dumbPaintTool/app/state/layers.py +++ b/tools/dumbPaintTool/app/state/layers.py @@ -54,6 +54,7 @@ class Layers(): self._layers = la._layers.copy() self.layersOrderChanged.emit(self._layers) self.layerSelected.emit(self._selected) + self.changed.emit() def __eq__(self, value: object) -> bool: return self._layers == value._layers @@ -99,8 +100,8 @@ class Layers(): la = self._layers dl = la.pop(la.index(self._selected)) self._selected = la[0] if la else None - self.layerDeleted.emit(dl) self._modified = True + self.layerDeleted.emit(dl) self.layersOrderChanged.emit(self._layers) return dl diff --git a/tools/ttkDesigner/app/signalsloteditor.py b/tools/ttkDesigner/app/signalsloteditor.py index 4417b6bb..262884d7 100644 --- a/tools/ttkDesigner/app/signalsloteditor.py +++ b/tools/ttkDesigner/app/signalsloteditor.py @@ -119,7 +119,7 @@ class _SignalSlotItem(ttk.TTkTreeWidgetItem): curSlot = str(self._slot.currentText()) filter = None for c in self._slotData: - if not str(curSlot) in self._slotData[c]: continue + if str(curSlot) not in self._slotData[c]: continue filter = self._slotData[c][curSlot]['type'] break @@ -151,7 +151,7 @@ class _SignalSlotItem(ttk.TTkTreeWidgetItem): curSlot = self._slot.currentText() filter = 'ALL' for c in self._signalData: - if not str(curSignal) in self._signalData[c]: continue + if str(curSignal) not in self._signalData[c]: continue filter = self._signalData[c][curSignal]['type'] break