From 11be48bc8e65e40e9eb827e6c633dfd028708133 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 17 Mar 2024 21:44:53 +0000 Subject: [PATCH] Added PaintPreview --- tools/dumb_paint_lib/__init__.py | 9 +- tools/dumb_paint_lib/maintemplate.py | 28 ++- tools/dumb_paint_lib/paintarea.py | 286 +++++++++++++-------------- tools/dumb_paint_lib/painttoolkit.py | 123 ++++++++++++ 4 files changed, 293 insertions(+), 153 deletions(-) create mode 100644 tools/dumb_paint_lib/painttoolkit.py diff --git a/tools/dumb_paint_lib/__init__.py b/tools/dumb_paint_lib/__init__.py index f45cf51e..79da5866 100644 --- a/tools/dumb_paint_lib/__init__.py +++ b/tools/dumb_paint_lib/__init__.py @@ -1,5 +1,6 @@ from .maintemplate import * -from .paintarea import * -from .textarea import * -from .palette import * -from .layers import * \ No newline at end of file +from .paintarea import * +from .painttoolkit import * +from .textarea import * +from .palette import * +from .layers import * \ No newline at end of file diff --git a/tools/dumb_paint_lib/maintemplate.py b/tools/dumb_paint_lib/maintemplate.py index 3474addc..7ba24cb0 100644 --- a/tools/dumb_paint_lib/maintemplate.py +++ b/tools/dumb_paint_lib/maintemplate.py @@ -27,10 +27,11 @@ import sys, os, json sys.path.append(os.path.join(sys.path[0],'../..')) import TermTk as ttk -from .paintarea import PaintArea, PaintToolKit, PaintScrollArea -from .palette import Palette -from .textarea import TextArea -from .layers import Layers,LayerData +from .paintarea import PaintArea, PaintScrollArea +from .painttoolkit import PaintToolKit +from .palette import Palette +from .textarea import TextArea +from .layers import Layers,LayerData class LeftPanel(ttk.TTkVBoxLayout): __slots__ = ('_palette', @@ -54,7 +55,7 @@ class LeftPanel(ttk.TTkVBoxLayout): # Toolset lTools = ttk.TTkGridLayout() - ra_move = ttk.TTkRadioButton(radiogroup="tools", text="Move", enabled=True) + ra_move = ttk.TTkRadioButton(radiogroup="tools", text="Select/Move", enabled=True) ra_select = ttk.TTkRadioButton(radiogroup="tools", text="Select",enabled=False) ra_brush = ttk.TTkRadioButton(radiogroup="tools", text="Brush", checked=True) ra_line = ttk.TTkRadioButton(radiogroup="tools", text="Line", enabled=False) @@ -64,12 +65,16 @@ class LeftPanel(ttk.TTkVBoxLayout): ra_rect_f = ttk.TTkRadioButton(radiogroup="toolsRectFill", text="Fill" , enabled=False, checked=True) ra_rect_e = ttk.TTkRadioButton(radiogroup="toolsRectFill", text="Empty", enabled=False) + cb_move_r = ttk.TTkCheckbox(text="Resize", enabled=False) + @ttk.pyTTkSlot(bool) def _emitTool(checked): if not checked: return tool = PaintArea.Tool.BRUSH if ra_move.isChecked(): - tool = PaintArea.Tool.MOVE + tool = PaintArea.Tool.MOVE + if cb_move_r.isChecked(): + tool |= PaintArea.Tool.RESIZE elif ra_brush.isChecked(): tool = PaintArea.Tool.BRUSH elif ra_rect.isChecked(): @@ -81,6 +86,7 @@ class LeftPanel(ttk.TTkVBoxLayout): ra_rect.toggled.connect(ra_rect_f.setEnabled) ra_rect.toggled.connect(ra_rect_e.setEnabled) + ra_move.toggled.connect(cb_move_r.setEnabled) ra_move.toggled.connect( _emitTool) ra_select.toggled.connect( _emitTool) @@ -92,6 +98,7 @@ class LeftPanel(ttk.TTkVBoxLayout): ra_oval.toggled.connect( _emitTool) lTools.addWidget(ra_move ,0,0) + lTools.addWidget(cb_move_r,0,1) lTools.addWidget(ra_select,1,0) lTools.addWidget(ra_brush ,2,0) lTools.addWidget(ra_line ,3,0) @@ -232,6 +239,7 @@ class PaintTemplate(ttk.TTkAppTemplate): buttonClose = fileMenu.addMenu("Save &As...") fileMenu.addSpacer() fileMenu.addMenu("&Import").menuButtonClicked.connect(self.importDictWin) + menuExport = fileMenu.addMenu("&Export") fileMenu.addSpacer() fileMenu.addMenu("Load Palette") fileMenu.addMenu("Save Palette") @@ -239,6 +247,12 @@ class PaintTemplate(ttk.TTkAppTemplate): buttonExit = fileMenu.addMenu("E&xit") buttonExit.menuButtonClicked.connect(ttk.TTkHelper.quit) + menuExport.addMenu("&Ascii/Txt") + menuExport.addMenu("&Ansi") + menuExport.addMenu("&Python") + menuExport.addMenu("&Bash") + + # extraMenu = appMenuBar.addMenu("E&xtra") # extraMenu.addMenu("Scratchpad").menuButtonClicked.connect(self.scratchpad) # extraMenu.addSpacer() @@ -258,6 +272,8 @@ class PaintTemplate(ttk.TTkAppTemplate): self._parea.setGlyphColor(palette.color()) ptoolkit.setColor(palette.color()) + parea.selectedLayer.connect(ptoolkit.updateLayer) + @ttk.pyTTkSlot(LayerData) def _layerSelected(l:LayerData): parea.setCurrentLayer(l.data()) diff --git a/tools/dumb_paint_lib/paintarea.py b/tools/dumb_paint_lib/paintarea.py index 1fe85abe..92255635 100644 --- a/tools/dumb_paint_lib/paintarea.py +++ b/tools/dumb_paint_lib/paintarea.py @@ -28,86 +28,15 @@ sys.path.append(os.path.join(sys.path[0],'../..')) import TermTk as ttk -class PaintToolKit(ttk.TTkContainer): - __slots__ = ('_rSelect', '_rPaint', '_lgliph', - '_cbFg', '_cbBg', - '_bpFg', '_bpBg', '_bpDef', - '_glyph', - #Signals - 'updatedColor', 'updatedTrans') - def __init__(self, *args, **kwargs): - ttk.TTkUiLoader.loadFile(os.path.join(os.path.dirname(os.path.abspath(__file__)),"paintToolKit.tui.json"),self) - self._glyph = 'X' - self.updatedColor = ttk.pyTTkSignal(ttk.TTkColor) - self.updatedTrans = ttk.pyTTkSignal(ttk.TTkColor) - self._lgliph = self.getWidgetByName("lglyph") - self._cbFg = self.getWidgetByName("cbFg") - self._cbBg = self.getWidgetByName("cbBg") - self._bpFg = self.getWidgetByName("bpFg") - self._bpBg = self.getWidgetByName("bpBg") - self._bpDef = self.getWidgetByName("bpDef") - - self._bpDef.setColor(ttk.TTkColor.bg('#FF00FF')) - self._cbFg.toggled.connect(self._refreshColor) - self._cbBg.toggled.connect(self._refreshColor) - - self._bpFg.colorSelected.connect(self._refreshColor) - self._bpBg.colorSelected.connect(self._refreshColor) - self._bpDef.colorSelected.connect(self.updatedTrans.emit) - - self._refreshColor(emit=False) - - @ttk.pyTTkSlot() - def _refreshColor(self, emit=True): - color =self.color() - self._lgliph.setText( - ttk.TTkString("Glyph: '") + - ttk.TTkString(self._glyph,color) + - ttk.TTkString("'")) - if emit: - self.updatedColor.emit(color) - - @ttk.pyTTkSlot(ttk.TTkString) - def glyphFromString(self, ch:ttk.TTkString): - if len(ch)<=0: return - self._glyph = ch.charAt(0) - self._refreshColor() - # self.setColor(ch.colorAt(0)) - - def color(self): - color = ttk.TTkColor() - if self._cbFg.checkState() == ttk.TTkK.Checked: - color += self._bpFg.color().invertFgBg() - if self._cbBg.checkState() == ttk.TTkK.Checked: - color += self._bpBg.color() - return color - - @ttk.pyTTkSlot(ttk.TTkColor) - def setColor(self, color:ttk.TTkColor): - if fg := color.foreground(): - self._cbFg.setCheckState(ttk.TTkK.Checked) - self._bpFg.setEnabled() - self._bpFg.setColor(fg.invertFgBg()) - else: - self._cbFg.setCheckState(ttk.TTkK.Unchecked) - self._bpFg.setDisabled() - - if bg := color.background(): - self._cbBg.setCheckState(ttk.TTkK.Checked) - self._bpBg.setEnabled() - self._bpBg.setColor(bg) - else: - self._cbBg.setCheckState(ttk.TTkK.Unchecked) - self._bpBg.setDisabled() - self._refreshColor(emit=False) class CanvasLayer(): - __slot__ = ('_pos','_name','_visible','_size','_data','_colors') + __slot__ = ('_pos','_name','_visible','_size','_data','_colors','_preview') def __init__(self) -> None: self._pos = (0,0) self._size = (0,0) self._name = "" self._visible = True + self._preview = None self._data: list[list[str ]] = [] self._colors:list[list[ttk.TTkColor]] = [] @@ -251,7 +180,7 @@ class CanvasLayer(): else: self._colors[i+y][ii+x] = ttk.TTkColor.RST - def placeFill(self,geometry,tool,glyph,color): + def placeFill(self,geometry,tool,glyph,color,preview=False): w,h = self._size ax,ay,bx,by = geometry ax = max(0,min(w-1,ax)) @@ -262,8 +191,14 @@ class CanvasLayer(): fbx,fby = max(ax,bx), max(ay,by) color = color if glyph != ' ' else color.background() - data = self._data - colors = self._colors + if preview: + data = [_r.copy() for _r in self._data] + colors = [_r.copy() for _r in self._colors] + self._preview = {'data':data,'colors':colors} + else: + self._preview = None + data = self._data + colors = self._colors if tool == PaintArea.Tool.RECTFILL: for row in data[fay:fby+1]: @@ -281,11 +216,17 @@ class CanvasLayer(): row[fax]=row[fbx]=color return True - def placeGlyph(self,x,y,glyph,color): + def placeGlyph(self,x,y,glyph,color,preview=False): w,h = self._size color = color if glyph != ' ' else color.background() - data = self._data - colors = self._colors + if preview: + data = [_r.copy() for _r in self._data] + colors = [_r.copy() for _r in self._colors] + self._preview = {'data':data,'colors':colors} + else: + self._preview = None + data = self._data + colors = self._colors if 0<=x bool: @@ -511,7 +554,7 @@ class PaintArea(ttk.TTkAbstractScrollView): ox, oy = self.getViewOffsets() self._mouseMove=(evt.x+ox-dx,evt.y+oy-dy) self._mouseDrag = None - self.update() + self._handleAction() return True def mouseDragEvent(self, evt) -> bool: @@ -584,23 +627,6 @@ class PaintArea(ttk.TTkAbstractScrollView): 'layerDim':ttk.TTkColor.bg(f'#{int(r*0.2):02x}{int(g*0.2):02x}{int(b*0.2):02x}')} self.update() - def _placeFill(self): - if not self._mouseDrag: return False - mfill = self._mouseDrag - self._mouseDrag = None - self._mouseMove = None - self._moveData = None - ret = self._currentLayer.placeFill(mfill,self._tool,self._glyph,self._glyphColor) - self.update() - return ret - - def _placeGlyph(self,x,y): - self._mouseMove = None - self._moveData = None - ret = self._currentLayer.placeGlyph(x,y,self._glyph,self._glyphColor) - self.update() - return ret - def paintEvent(self, canvas:ttk.TTkCanvas): dx,dy = self._documentPos doffx,doffy = self._documentOffset @@ -633,7 +659,7 @@ class PaintArea(ttk.TTkAbstractScrollView): # Draw canvas/currentLayout ruler - ruleColor = ttk.TTkColor.fg("#444444") + # ruleColor = ttk.TTkColor.fg("#444444") # # canvas.drawText(pos=((0,dy-oy-1 )),text="═"*cw,color=ruleColor) # # canvas.drawText(pos=((0,dy-oy+dh)),text="═"*cw,color=ruleColor) # # canvas.drawText(pos=((0,dy-oy-1 )),text="▁"*cw,color=ruleColor) @@ -651,33 +677,7 @@ class PaintArea(ttk.TTkAbstractScrollView): for l in self._canvasLayers: lx,ly = l.pos() l.drawInCanvas(pos=(lx+dox,ly+doy),canvas=canvas) - return - if self._mouseMove: - x,y = self._mouseMove - gc = self._glyphColor - canvas._data[y][x] = self._glyph - canvas._colors[y][x] = gc if gc._bg else gc+tc - if self._mouseDrag and self._mousePress: - ax,ay = self._mousePress - bx,by = self._mouseDrag - ax = max(0,min(w-1,ax)) - ay = max(0,min(h-1,ay)) - bx = max(0,min(w-1,bx)) - by = max(0,min(h-1,by)) - x,y = min(ax,bx), min(ay,by) - w,h = max(ax-x,bx-x)+1, max(ay-y,by-y)+1 - gl = self._glyph - gc = self._glyphColor - if self._tool == PaintArea.Tool.RECTFILL: - canvas.fill(pos=(x,y), size=(w,h), - color=gc if gc._bg else gc+tc, - char=gl) - elif self._tool == PaintArea.Tool.RECTEMPTY: - canvas.drawText(pos=(x,y ),text=gl*w,color=gc) - canvas.drawText(pos=(x,y+h-1),text=gl*w,color=gc) - for y in range(y+1,y+h-1): - canvas.drawChar(pos=(x ,y),char=gl,color=gc) - canvas.drawChar(pos=(x+w-1,y),char=gl,color=gc) + # Draw Preview for mouse move/drag class PaintScrollArea(ttk.TTkAbstractScrollArea): def __init__(self, pwidget:PaintArea, **kwargs): diff --git a/tools/dumb_paint_lib/painttoolkit.py b/tools/dumb_paint_lib/painttoolkit.py new file mode 100644 index 00000000..834b6f12 --- /dev/null +++ b/tools/dumb_paint_lib/painttoolkit.py @@ -0,0 +1,123 @@ +# MIT License +# +# Copyright (c) 2024 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +__all__ = ['PaintToolKit'] + +import sys, os + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +from .paintarea import * + +class PaintToolKit(ttk.TTkContainer): + __slots__ = ('_rSelect', '_rPaint', '_lgliph', + '_cbFg', '_cbBg', + '_bpFg', '_bpBg', '_bpDef', + '_sbDx','_sbDy','_sbDw','_sbDh', + '_sbLx','_sbLy','_sbLw','_sbLh', + '_glyph', + #Signals + 'updatedColor', 'updatedTrans') + def __init__(self, *args, **kwargs): + ttk.TTkUiLoader.loadFile(os.path.join(os.path.dirname(os.path.abspath(__file__)),"paintToolKit.tui.json"),self) + self._glyph = 'X' + self.updatedColor = ttk.pyTTkSignal(ttk.TTkColor) + self.updatedTrans = ttk.pyTTkSignal(ttk.TTkColor) + self._lgliph = self.getWidgetByName("lglyph") + self._cbFg = self.getWidgetByName("cbFg") + self._cbBg = self.getWidgetByName("cbBg") + self._bpFg = self.getWidgetByName("bpFg") + self._bpBg = self.getWidgetByName("bpBg") + self._bpDef = self.getWidgetByName("bpDef") + + self._sbDx = self.getWidgetByName("sbDx") + self._sbDy = self.getWidgetByName("sbDy") + self._sbDw = self.getWidgetByName("sbDw") + self._sbDh = self.getWidgetByName("sbDh") + self._sbLx = self.getWidgetByName("sbLx") + self._sbLy = self.getWidgetByName("sbLy") + self._sbLw = self.getWidgetByName("sbLw") + self._sbLh = self.getWidgetByName("sbLh") + + self._bpDef.setColor(ttk.TTkColor.bg('#FF00FF')) + self._cbFg.toggled.connect(self._refreshColor) + self._cbBg.toggled.connect(self._refreshColor) + + self._bpFg.colorSelected.connect(self._refreshColor) + self._bpBg.colorSelected.connect(self._refreshColor) + self._bpDef.colorSelected.connect(self.updatedTrans.emit) + + self._refreshColor(emit=False) + + @ttk.pyTTkSlot(CanvasLayer) + def updateLayer(self, layer:CanvasLayer): + lx,ly = layer.pos() + lw,lh = layer.size() + self._sbLx.setValue(lx) + self._sbLy.setValue(ly) + self._sbLw.setValue(lw) + self._sbLh.setValue(lh) + + @ttk.pyTTkSlot() + def _refreshColor(self, emit=True): + color =self.color() + self._lgliph.setText( + ttk.TTkString("Glyph: '") + + ttk.TTkString(self._glyph,color) + + ttk.TTkString("'")) + if emit: + self.updatedColor.emit(color) + + @ttk.pyTTkSlot(ttk.TTkString) + def glyphFromString(self, ch:ttk.TTkString): + if len(ch)<=0: return + self._glyph = ch.charAt(0) + self._refreshColor() + # self.setColor(ch.colorAt(0)) + + def color(self): + color = ttk.TTkColor() + if self._cbFg.checkState() == ttk.TTkK.Checked: + color += self._bpFg.color().invertFgBg() + if self._cbBg.checkState() == ttk.TTkK.Checked: + color += self._bpBg.color() + return color + + @ttk.pyTTkSlot(ttk.TTkColor) + def setColor(self, color:ttk.TTkColor): + if fg := color.foreground(): + self._cbFg.setCheckState(ttk.TTkK.Checked) + self._bpFg.setEnabled() + self._bpFg.setColor(fg.invertFgBg()) + else: + self._cbFg.setCheckState(ttk.TTkK.Unchecked) + self._bpFg.setDisabled() + + if bg := color.background(): + self._cbBg.setCheckState(ttk.TTkK.Checked) + self._bpBg.setEnabled() + self._bpBg.setColor(bg) + else: + self._cbBg.setCheckState(ttk.TTkK.Unchecked) + self._bpBg.setDisabled() + self._refreshColor(emit=False)