Browse Source

Merge pull request #244 from ceccopierangiolieugenio/DumbPaintTool

Dumb Paint Tool
pull/252/head
Pier CeccoPierangioliEugenio 2 years ago committed by GitHub
parent
commit
0094de7f87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      Makefile
  2. 59
      TermTk/TTkCore/color.py
  3. 1
      TermTk/TTkCrossTools/__init__.py
  4. 159
      TermTk/TTkCrossTools/savetools.py
  5. 23
      TermTk/TTkGui/clipboard.py
  6. 17
      TermTk/__init__.py
  7. 10
      docs/MDNotes/Fonts/Font2Glyph.md
  8. 2
      docs/MDNotes/webExporter/Javascript Open,DnD Files.md
  9. 10
      tests/sandbox/Makefile
  10. 8
      tests/sandbox/sandbox.NerdFont.html
  11. 8
      tests/sandbox/standalone.fullscreen.NerdFont.html
  12. 4
      tools/check.import.sh
  13. 35
      tools/dumb.paint.tool.py
  14. 10
      tools/dumb_paint_lib/__init__.py
  15. 33
      tools/dumb_paint_lib/about.py
  16. 374
      tools/dumb_paint_lib/canvaslayer.py
  17. 324
      tools/dumb_paint_lib/layers.py
  18. 347
      tools/dumb_paint_lib/maintemplate.py
  19. 593
      tools/dumb_paint_lib/paintarea.py
  20. 123
      tools/dumb_paint_lib/painttoolkit.py
  21. 1
      tools/dumb_paint_lib/palette.py
  22. 918
      tools/dumb_paint_lib/tui/paintToolKit.tui.json
  23. 0
      tools/dumb_paint_lib/tui/quickImport.tui.json
  24. 35
      tools/webExporter/index.html
  25. 116
      tools/webExporter/js/ttkproxy.js
  26. 28
      tools/webExporterInit.sh

4
Makefile

@ -116,6 +116,10 @@ deployTest: .venv
. .venv/bin/activate ; \
python3 -m twine upload --repository testpypi tmp/dist/* --verbose
itchDumbPaintToolexporter:
tools/webExporterInit.sh
python3 -m http.server --directory tmp
test: .venv
# Record a stream
# tests/pytest/test_001_demo.py -r test.input.bin

59
TermTk/TTkCore/color.py

@ -113,7 +113,7 @@ class _TTkColor:
cmax = max(r,g,b)
cmin = min(r,g,b)
lum = (cmax-cmin)/2
lum = (cmax+cmin)/2
if cmax == cmin:
return 0,0,lum
@ -388,10 +388,67 @@ class TTkColor(_TTkColor):
color_1 = color_fg_red + color_bg_blue
color_2 = color_fg_red + TTkColor.bg('#FFFF00')
color_3 = color_2 + TTkColor.UNDERLINE + TTkColor.BOLD
# Use presets
color_4 = TTkColor.RED
color_5 = TTkColor.BG_YELLOW + color_4
color_6 = color_5 + TTkColor.UNDERLINE + TTkColor.BOLD
'''
RST = _TTkColor()
'''Reset to the default terminal color and modifiers'''
BLACK = _TTkColor(fg=( 0, 0, 0))
'''(fg) #000000 - Black'''
WHITE = _TTkColor(fg=(255,255,255))
'''(fg) #FFFFFF - White'''
RED = _TTkColor(fg=(255, 0, 0))
'''(fg) #FF0000 - Red'''
GREEN = _TTkColor(fg=( 0,255, 0))
'''(fg) #00FF00 - Green'''
BLUE = _TTkColor(fg=( 0, 0,255))
'''(fg) #0000FF - Blue'''
CYAN = _TTkColor(fg=( 0,255,255))
'''(fg) #00FFFF - Cyan'''
MAGENTA = _TTkColor(fg=(255, 0,255))
'''(fg) #FF00FF - Magenta'''
YELLOW = _TTkColor(fg=(255,255, 0))
'''(fg) #FFFF00 - Yellow'''
FG_BLACK = BLACK
'''(fg) #000000 - Black'''
FG_WHITE = WHITE
'''(fg) #FFFFFF - White'''
FG_RED = RED
'''(fg) #FF0000 - Red'''
FG_GREEN = GREEN
'''(fg) #00FF00 - Green'''
FG_BLUE = BLUE
'''(fg) #0000FF - Blue'''
FG_CYAN = CYAN
'''(fg) #00FFFF - Cyan'''
FG_MAGENTA = MAGENTA
'''(fg) #FF00FF - Magenta'''
FG_YELLOW = YELLOW
'''(fg) #FFFF00 - Yellow'''
BG_BLACK = BLACK.invertFgBg()
'''(bg) #000000 - Black'''
BG_WHITE = WHITE.invertFgBg()
'''(bg) #FFFFFF - White'''
BG_RED = RED.invertFgBg()
'''(bg) #FF0000 - Red'''
BG_GREEN = GREEN.invertFgBg()
'''(bg) #00FF00 - Green'''
BG_BLUE = BLUE.invertFgBg()
'''(bg) #0000FF - Blue'''
BG_CYAN = CYAN.invertFgBg()
'''(bg) #00FFFF - Cyan'''
BG_MAGENTA = MAGENTA.invertFgBg()
'''(bg) #FF00FF - Magenta'''
BG_YELLOW = YELLOW.invertFgBg()
'''(bg) #FFFF00 - Yellow'''
# Modifiers:
BOLD = _TTkColor(mod=TTkHelper.Color.BOLD)
'''**Bold** modifier'''

1
TermTk/TTkCrossTools/__init__.py

@ -0,0 +1 @@
from .savetools import *

159
TermTk/TTkCrossTools/savetools.py

@ -0,0 +1,159 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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__ = ['ttkCrossOpen', 'ttkCrossSave', 'ttkCrossSaveAs', 'TTkEncoding', 'ttkConnectDragOpen', 'ttkEmitDragOpen', 'ttkEmitFileOpen']
import os
import importlib.util
import json
from TermTk import pyTTkSlot, pyTTkSignal
from TermTk import TTkLog
from TermTk import TTkMessageBox, TTkFileDialogPicker, TTkHelper, TTkString, TTkK, TTkColor
ttkCrossOpen = None
ttkCrossSave = None
ttkCrossSaveAs = None
ttkEmitDragOpen = None
ttkEmitFileOpen = None
ttkConnectDragOpen = None
class TTkEncoding(str):
TEXT = "text"
TEXT_PLAIN = "text/plain"
TEXT_PLAIN_UTF8 = "text/plain;charset=utf-8"
APPLICATION = 'application'
APPLICATION_JSON = 'application/json'
IMAGE = 'image'
IMAGE_PNG = 'image/png'
IMAGE_SVG = 'image/svg+xml'
IMAGE_JPG = 'image/jpeg'
if importlib.util.find_spec('pyodideProxy'):
TTkLog.info("Using 'pyodideProxy' as clipboard manager")
import pyodideProxy
ttkDragOpen = {}
ttkFileOpen = pyTTkSignal(dict)
def _open(path, encoding, filter, cb=None):
if not cb: return
ttkFileOpen.connect(cb)
pyodideProxy.openFile(encoding)
def _save(filePath, content, encoding, filter=None):
pyodideProxy.saveFile(os.path.basename(filePath), content, encoding)
def _connectDragOpen(encoding, cb):
if not encoding in ttkDragOpen:
ttkDragOpen[encoding] = pyTTkSignal(dict)
return ttkDragOpen[encoding].connect(cb)
def _emitDragOpen(encoding, data):
for do in [ttkDragOpen[e] for e in ttkDragOpen if encoding.startswith(e)]:
do.emit(data)
def _emitFileOpen(encoding, data):
ttkFileOpen.emit(data)
ttkFileOpen.clear()
ttkCrossOpen = _open
ttkCrossSave = _save
ttkCrossSaveAs = _save
ttkEmitDragOpen = _emitDragOpen
ttkEmitFileOpen = _emitFileOpen
ttkConnectDragOpen = _connectDragOpen
else:
def _crossDecoder_text(fileName) :
with open(fileName) as fp:
return fp.read()
def _crossDecoder_json(fileName) :
with open(fileName) as fp:
# return json.load(fp)
return fp.read()
def _crossDecoder_image(fileName):
return None
_crossDecoder = {
TTkEncoding.TEXT : _crossDecoder_text ,
TTkEncoding.TEXT_PLAIN : _crossDecoder_text ,
TTkEncoding.TEXT_PLAIN_UTF8 : _crossDecoder_text ,
TTkEncoding.APPLICATION : _crossDecoder_json ,
TTkEncoding.APPLICATION_JSON : _crossDecoder_json ,
TTkEncoding.IMAGE : _crossDecoder_image ,
TTkEncoding.IMAGE_PNG : _crossDecoder_image ,
TTkEncoding.IMAGE_SVG : _crossDecoder_image ,
TTkEncoding.IMAGE_JPG : _crossDecoder_image ,
}
def _open(path, encoding, filter, cb=None):
if not cb: return
def __openFile(fileName):
_decoder = _crossDecoder.get(encoding,lambda _:None)
content = _decoder(fileName)
cb({'name':fileName, 'data':content})
filePicker = TTkFileDialogPicker(pos = (3,3), size=(100,30), caption="Open", path=path, fileMode=TTkK.FileMode.ExistingFile ,filter=filter)
filePicker.pathPicked.connect(__openFile)
TTkHelper.overlay(None, filePicker, 5, 5, True)
def _save(filePath, content, encoding):
TTkLog.info(f"Saving to: {filePath}")
with open(filePath,'w') as fp:
fp.write(content)
def _saveAs(filePath, content, encoding, filter, cb=None):
if not cb: return
def _approveFile(fileName):
if os.path.exists(fileName):
@pyTTkSlot(TTkMessageBox.StandardButton)
def _cb(btn):
if btn == TTkMessageBox.StandardButton.Save:
ttkCrossSave(fileName,content,encoding)
elif btn == TTkMessageBox.StandardButton.Cancel:
return
if cb:
cb()
messageBox = TTkMessageBox(
text= (
TTkString( f'A file named "{os.path.basename(fileName)}" already exists.\nDo you want to replace it?', TTkColor.BOLD) +
TTkString( f'\n\nReplacing it will overwrite its contents.') ),
icon=TTkMessageBox.Icon.Warning,
standardButtons=TTkMessageBox.StandardButton.Discard|TTkMessageBox.StandardButton.Save|TTkMessageBox.StandardButton.Cancel)
messageBox.buttonSelected.connect(_cb)
TTkHelper.overlay(None, messageBox, 5, 5, True)
else:
ttkCrossSave(fileName,content,encoding)
filePicker = TTkFileDialogPicker(
size=(100,30), path=filePath,
acceptMode=TTkK.AcceptMode.AcceptSave,
caption="Save As...",
fileMode=TTkK.FileMode.AnyFile ,
filter=filter)
filePicker.pathPicked.connect(_approveFile)
TTkHelper.overlay(None, filePicker, 5, 5, True)
ttkCrossOpen = _open
ttkCrossSave = _save
ttkCrossSaveAs = _saveAs
ttkEmitDragOpen = lambda a:None
ttkEmitFileOpen = lambda a:None
ttkConnectDragOpen = lambda a,b:None

23
TermTk/TTkGui/clipboard.py

@ -61,10 +61,21 @@ class TTkClipboard():
try:
if importlib.util.find_spec('pyodideProxy'):
TTkLog.info("Using 'pyodideProxy' as clipboard manager")
import pyodideProxy as _c
TTkClipboard._manager = _c
TTkClipboard._setText = _c.copy
TTkClipboard._text = _c.paste
import pyodideProxy
import asyncio
async def _async_co():
text = await pyodideProxy.paste()
TTkLog.debug(f"ttkProxy paste_co: {text}")
return text
def _paste():
loop = asyncio.get_event_loop()
text = loop.run_until_complete(_async_co())
# text = loop.run_until_complete(pyodideProxy.paste())
TTkLog.debug(f"ttkProxy paste: {text=} {_async_co()=}")
return text
TTkClipboard._manager = pyodideProxy
TTkClipboard._setText = pyodideProxy.copy
TTkClipboard._text = pyodideProxy.paste # _paste
elif importlib.util.find_spec('copykitten'):
TTkLog.info("Using 'copykitten' as clipboard manager")
import copykitten as _c
@ -113,13 +124,13 @@ class TTkClipboard():
except Exception as e:
TTkLog.error("Clipboard error, try to export X11 if you are running this UI via SSH")
for line in str(e).split("\n"):
TTkLog.error(line)
TTkLog.error(str(line))
@staticmethod
def text():
'''text'''
if TTkClipboard._text:
txt = ""
txt = None
try:
txt = TTkClipboard._text()
except Exception as e:

17
TermTk/__init__.py

@ -1,11 +1,12 @@
from .TTkCore import *
from .TTkTheme import *
from .TTkGui import *
from .TTkWidgets import *
from .TTkTypes import *
from .TTkLayouts import *
from .TTkCore import *
from .TTkTheme import *
from .TTkGui import *
from .TTkWidgets import *
from .TTkTypes import *
from .TTkLayouts import *
from .TTkTestWidgets import *
from .TTkAbstract import *
from .TTkUiTools import *
from .TTkAbstract import *
from .TTkUiTools import *
from .TTkCrossTools import *
TTkCfg.theme = TTkTheme()

10
docs/MDNotes/Fonts/Font2Glyph.md

@ -13,4 +13,12 @@ You can use this command:
/usr/share/fonts/truetype/unifont/unifont_upper.ttf: Unifont Upper:style=Regular
/usr/share/fonts/opentype/3270/3270Condensed-Regular.otf: IBM 3270 Condensed:style=Condensed
```
```
Check the font rendering in the browser:
https://typezebra.com
OK:
* FreeSans Regular
* FreeSerif Regular
* FreeMono Regular

2
docs/MDNotes/webExporter/Javascript Open,DnD Files.md

@ -0,0 +1,2 @@
https://dev.to/shubhamtiwari909/drag-and-drop-file-using-javascript-2h99
https://www.smashingmagazine.com/2018/01/drag-drop-file-uploader-vanilla-js/

10
tests/sandbox/Makefile

@ -4,7 +4,7 @@ testSandbox:
python3 -m http.server --directory ./
www:
mkdir -p www/pyodide www/xterm/ www/xterm-addon-fit www/codemirror www/codemirror/theme www/codemirror/modes www/fontawesome www/w2ui www/webfonts www/nerdfonts
mkdir -p www/pyodide www/xterm/ www/xterm-addon-fit www/codemirror www/codemirror/theme www/codemirror/modes www/fontawesome www/w2ui www/webfonts www/fonts/nerdfonts
wget -P www/pyodide/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/pyodide/pyodide.js
wget -P www/pyodide/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/pyodide/python_stdlib.zip
@ -25,8 +25,8 @@ www:
wget -P www/fontawesome/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/fontawesome/fontawesome.min.css
wget -P www/webfonts/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/webfonts/fa-regular-400.woff2
wget -P www/nerdfonts/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/nerdfonts/HurmitNerdFontMono-Regular.otf
wget -P www/nerdfonts/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf
wget -P www/fonts/nerdfonts/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/nerdfonts/HurmitNerdFontMono-Regular.otf
wget -P www/fonts/nerdfonts/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf
wget -P www/w2ui/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/w2ui/w2ui-2.0.min.js
wget -P www/w2ui/ https://ceccopierangiolieugenio.github.io/binaryRepo/pyTermTk/www/w2ui/w2ui-2.0.min.css
@ -43,6 +43,7 @@ updateXterm: www
npm install xterm
npm install xterm-addon-fit
npm install xterm-addon-unicode11
npm install xterm-addon-canvas
cp node_modules/xterm/css/xterm.css \
node_modules/xterm/lib/xterm.js \
node_modules/xterm/lib/xterm.js.map \
@ -53,6 +54,9 @@ updateXterm: www
cp node_modules/xterm-addon-fit/lib/xterm-addon-fit.js \
node_modules/xterm-addon-fit/lib/xterm-addon-fit.js.map \
www/xterm-addon-fit/
cp node_modules/xterm-addon-canvas/lib/xterm-addon-canvas.js \
node_modules/xterm-addon-canvas/lib/xterm-addon-canvas.js.map \
www/xterm-addon-fit/
rm -rf node_modules package.json package-lock.json
buildSandbox: www

8
tests/sandbox/sandbox.NerdFont.html

@ -29,9 +29,9 @@
/*
@font-face {
font-family: "NerdFont";
src: url(www/nerdfonts/HurmitNerdFontMono-Regular.otf) format("opentype");
src: url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
src: url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
src: url(www/fonts/nerdfonts/HurmitNerdFontMono-Regular.otf) format("opentype");
src: url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
src: url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
}
*/
</style>
@ -84,7 +84,7 @@
<script type="text/javascript">
// Workaround from: https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API
const font = new FontFace("NerdFont", "url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf)");
const font = new FontFace("NerdFont", "url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf)");
document.fonts.add(font);
font.load();
document.fonts.ready.then(() => {main()});

8
tests/sandbox/standalone.fullscreen.NerdFont.html

@ -23,9 +23,9 @@
/*
@font-face {
font-family: "NerdFont";
src: url(www/nerdfonts/HurmitNerdFontMono-Regular.otf) format("opentype");
src: url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
src: url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
src: url(www/fonts/nerdfonts/HurmitNerdFontMono-Regular.otf) format("opentype");
src: url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
src: url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf) format("truetype");
}
*/
</style>
@ -36,7 +36,7 @@
<script type="text/javascript">
// Workaround from: https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API
const font = new FontFace("NerdFont", "url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf)");
const font = new FontFace("NerdFont", "url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf)");
document.fonts.add(font);
font.load();
document.fonts.ready.then(() => {main()});

4
tools/check.import.sh

@ -36,7 +36,8 @@ __check(){
-e "propertyanimation.py:from inspect import getfullargspec" \
-e "propertyanimation.py:from types import LambdaType" \
-e "propertyanimation.py:import time, math" \
-e "terminal.py:from select import select" |
-e "savetools.py:import importlib.util" \
-e "savetools.py:import json" |
grep -v \
-e "TTkTerm/input_mono.py:from time import time" \
-e "TTkTerm/input_mono.py:import platform" \
@ -71,6 +72,7 @@ __check(){
-e "drivers/__init__.py:import platform" |
grep -v \
-e "TTkTerminal/debugterminal.py:import struct, fcntl, termios" \
-e "TTkTerminal/debugterminal.py:from select import select" \
-e "TTkTerminal/terminalview.py:import struct, fcntl, termios" \
-e "TTkTerminal/terminalview.py:from select import select" \
-e "TTkTerminal/terminalview.py:from .terminalview_CSI_DEC import _TTkTerminal_CSI_DEC" \

35
tools/dumb.paint.tool.py

@ -32,16 +32,25 @@ from dumb_paint_lib import PaintTemplate
ttk.TTkTheme.loadTheme(ttk.TTkTheme.NERD)
root = ttk.TTk(
title="Dumb Paint Tool",
layout=ttk.TTkGridLayout(),
mouseTrack=True,
sigmask=(
ttk.TTkTerm.Sigmask.CTRL_C |
ttk.TTkTerm.Sigmask.CTRL_Q |
ttk.TTkTerm.Sigmask.CTRL_S |
ttk.TTkTerm.Sigmask.CTRL_Z ))
PaintTemplate(parent=root)
root.mainloop()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('filename', type=str, nargs='?', help='the file to open')
# parser.add_argument('-k', '--showkeys', action='store_true', help='display the keypresses and mouse interactions')
args = parser.parse_args()
root = ttk.TTk(
title="Dumb Paint Tool",
layout=ttk.TTkGridLayout(),
mouseTrack=True,
sigmask=(
# ttk.TTkTerm.Sigmask.CTRL_C |
ttk.TTkTerm.Sigmask.CTRL_Q |
ttk.TTkTerm.Sigmask.CTRL_S |
ttk.TTkTerm.Sigmask.CTRL_Z ))
PaintTemplate(parent=root,fileName=args.filename)
root.mainloop()
if __name__ == '__main__':
main()

10
tools/dumb_paint_lib/__init__.py

@ -1,4 +1,8 @@
from .maintemplate import *
from .paintarea import *
from .textarea import *
from .palette import *
from .paintarea import *
from .painttoolkit import *
from .textarea import *
from .palette import *
from .layers import *
from .canvaslayer import *
from .about import *

33
tools/dumb_paint_lib/about.py

@ -0,0 +1,33 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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__ = ['About']
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.string import TTkString
from TermTk import TTkAbout, TTkWindow
# from .cfg import TTkDesignerCfg
class About(TTkAbout):
def paintEvent(self, canvas):
super().paintEvent(canvas)

374
tools/dumb_paint_lib/canvaslayer.py

@ -0,0 +1,374 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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__ = ['CanvasLayer']
import sys, os
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
# Canvas Layer structure
# The data may include more areas than the visible one
# This is helpful in case of resize to not lose the drawn areas
#
# |---| OffsetX
# x
# ╭────────────────╮ - -
# │ │ | OffsetY |
# y │ ┌───────┐ │ \ - | Data
# │ │Visible│ │ | h |
# │ └───────┘ │ / |
# │ │ |
# └────────────────┘ -
# \---w--/
class CanvasLayer():
class Tool(int):
MOVE = 0x01
RESIZE = 0x02
BRUSH = 0x04
RECTFILL = 0x08
RECTEMPTY = 0x10
CLONE = 0x20
__slot__ = ('_pos','_name','_visible','_size','_data','_colors','_preview','_offset')
def __init__(self) -> None:
self._pos = (0,0)
self._size = (0,0)
self._offset = (0,0)
self._name = ""
self._visible = True
self._preview = None
self._data: list[list[str ]] = []
self._colors:list[list[ttk.TTkColor]] = []
def pos(self):
return self._pos
def size(self):
return self._size
def visible(self):
return self._visible
@ttk.pyTTkSlot(bool)
def setVisible(self, visible):
self._visible = visible
def name(self):
return self._name
@ttk.pyTTkSlot(str)
def setName(self, name):
self._name = name
def isOpaque(self,x,y):
if not self._visible: return False
ox,oy = self._offset
w,h = self._size
data = self._data
colors = self._colors
if 0<=x<w and 0<=y<h:
return data[oy+y][ox+x] != ' ' or colors[oy+y][ox+x].background()
return False
def move(self,x,y):
self._pos=(x,y)
def resize(self,w,h):
self._size = (w,h)
self._data = (self._data + [[] for _ in range(h)])[:h]
self._colors = (self._colors + [[] for _ in range(h)])[:h]
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]
def superResize(self,dx,dy,dw,dh):
ox,oy = self._offset
x,y = self.pos()
w,h = self.size()
daw = len(self._data[0])
dah = len(self._data)
diffx = dx-x
diffy = dy-y
self._preview = None
if ox < x-dx: # we need to resize and move ox
_nw = x-dx-ox
ox = x-dx
self._data = [([' ']*_nw ) + _r for _r in self._data]
self._colors = [([ttk.TTkColor.RST]*_nw) + _r for _r in self._colors]
if dw+ox > daw:
_nw = dw+ox-daw
self._data = [_r + ([' ']*_nw ) for _r in self._data]
self._colors = [_r + ([ttk.TTkColor.RST]*_nw) for _r in self._colors]
daw = len(self._data[0])
if oy < y-dy: # we need to resize and move ox
_nh = y-dy-oy
oy = y-dy
self._data = [[' ']*daw for _ in range(_nh)] + self._data
self._colors = [[ttk.TTkColor.RST]*daw for _ in range(_nh)] + self._colors
if dh+oy > dah:
_nh = dh+oy-dah
self._data = self._data + [[' ']*daw for _ in range(_nh)]
self._colors = self._colors + [[ttk.TTkColor.RST]*daw for _ in range(_nh)]
self._offset = (ox+diffx,oy+diffy)
self._pos = (dx,dy)
self._size = (dw,dh)
def clean(self):
w,h = self._size
self._offset = (0,0)
self._preview = None
for i in range(h):
self._data[i] = [' ']*w
self._colors[i] = [ttk.TTkColor.RST]*w
def exportLayer(self, full=False, palette=True, crop=True):
# xa|----------| xb
# px |-----------| = max(px,px+xa-ox)
# Offset |------| pw
# Data |----------------------------|
# daw
# Don't try this at home
ox,oy = self._offset
px,py = self.pos()
pw,ph = self.size()
if full:
data = self._data
colors = self._colors
else:
data = [row[ox:ox+pw] for row in self._data[ oy:oy+ph] ]
colors = [row[ox:ox+pw] for row in self._colors[oy:oy+ph] ]
ox=oy=0
daw = len(data[0])
dah = len(data)
# get the bounding box
if crop:
xa,xb,ya,yb = daw,0,dah,0
for y,(drow,crow) in enumerate(zip(data,colors)):
for x,(d,c) in enumerate(zip(drow,crow)):
if d != ' ' or c.background():
xa = min(x,xa)
xb = max(x,xb)
ya = min(y,ya)
yb = max(y,yb)
if (xa,xb,ya,yb) == (daw,0,dah,0):
xa=xb=ya=yb=0
else:
xa,xb,ya,yb = 0,daw,0,dah
# Visble Area intersecting the bounding box
vxa,vya = max(px,px+xa-ox), max(py,py+ya-oy)
vxb,vyb = min(px+pw,vxa+xb-xa),min(py+ph,vya+yb-ya)
vw,vh = vxb-vxa+1, vyb-vya+1
outData = {
'version':'1.1.0',
'size':[vw,vh],
'pos': (vxa,vya),
'name':str(self.name()),
'data':[], 'colors':[]}
if palette:
palette = outData['palette'] = []
for row in colors:
for c in row:
fg = f"{c.getHex(ttk.TTkK.Foreground)}" if c.foreground() else None
bg = f"{c.getHex(ttk.TTkK.Background)}" if c.background() else None
if (pc:=(fg,bg)) not in palette:
palette.append(pc)
if full:
wslice = slice(xa,xb+1)
hslice = slice(ya,yb+1)
outData['offset'] = (max(0,ox-xa),max(0,oy-ya))
else:
wslice = slice(ox+vxa-px,ox+vxa-px+vw)
hslice = slice(oy+vya-py,oy+vya-py+vh)
for row in data[hslice]:
outData['data'].append(row[wslice])
for row in colors[hslice]:
outData['colors'].append([])
for c in row[wslice]:
fg = f"{c.getHex(ttk.TTkK.Foreground)}" if c.foreground() else None
bg = f"{c.getHex(ttk.TTkK.Background)}" if c.background() else None
if palette:
outData['colors'][-1].append(palette.index((fg,bg)))
else:
outData['colors'][-1].append((fg,bg))
return outData
def _import_v1_1_0(self, dd):
self._import_v1_0_0(dd)
self._offset = dd.get('offset',(0,0))
def _import_v1_0_0(self, dd):
self._pos = dd['pos']
self._size = dd['size']
self._name = dd['name']
self._data = dd['data']
def _getColor(cd):
fg,bg = cd
if fg and bg: return ttk.TTkColor.fg(fg)+ttk.TTkColor.bg(bg)
elif fg: return ttk.TTkColor.fg(fg)
elif bg: return ttk.TTkColor.bg(bg)
else: return ttk.TTkColor.RST
if 'palette' in dd:
palette = [_getColor(c) for c in dd['palette']]
self._colors = [[palette[c] for c in row] for row in dd['colors']]
else:
self._colors = [[_getColor(c) for c in row] for row in dd['colors']]
def _import_v0_0_0(self, dd):
# Legacy old import
w = len(dd['data'][0]) + 10
h = len(dd['data']) + 4
x,y=5,2
self.resize(w,h)
self._pos = (0,0)
for i,rd in enumerate(dd['data']):
for ii,cd in enumerate(rd):
self._data[i+y][ii+x] = cd
for i,rd in enumerate(dd['colors']):
for ii,cd in enumerate(rd):
fg,bg = cd
if fg and bg:
self._colors[i+y][ii+x] = ttk.TTkColor.fg(fg)+ttk.TTkColor.bg(bg)
elif fg:
self._colors[i+y][ii+x] = ttk.TTkColor.fg(fg)
elif bg:
self._colors[i+y][ii+x] = ttk.TTkColor.bg(bg)
else:
self._colors[i+y][ii+x] = ttk.TTkColor.RST
def importLayer(self, dd):
self.clean()
if 'version' in dd:
ver = dd['version']
if ver == ('1.0.0'):
self._import_v1_0_0(dd)
elif ver == ('1.1.0'):
self._import_v1_1_0(dd)
else:
self._import_v0_0_0(dd)
def placeFill(self,geometry,tool,glyph:str,color:ttk.TTkColor,preview=False):
ox,oy = self._offset
w,h = self._size
ax,ay,bx,by = geometry
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))
fax,fay = ox+min(ax,bx), oy+min(ay,by)
fbx,fby = ox+max(ax,bx), oy+max(ay,by)
color = color if glyph != ' ' else color.background()
color = color if color else ttk.TTkColor.RST
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 == CanvasLayer.Tool.RECTFILL:
for row in data[fay:fby+1]:
row[fax:fbx+1] = [glyph]*(fbx-fax+1)
for row in colors[fay:fby+1]:
row[fax:fbx+1] = [color]*(fbx-fax+1)
if tool == CanvasLayer.Tool.RECTEMPTY:
data[fay][fax:fbx+1] = [glyph]*(fbx-fax+1)
data[fby][fax:fbx+1] = [glyph]*(fbx-fax+1)
colors[fay][fax:fbx+1] = [color]*(fbx-fax+1)
colors[fby][fax:fbx+1] = [color]*(fbx-fax+1)
for row in data[fay:fby]:
row[fax]=row[fbx]=glyph
for row in colors[fay:fby]:
row[fax]=row[fbx]=color
return True
def placeGlyph(self,x,y,glyph:str,color:ttk.TTkColor,preview=False):
ox,oy = self._offset
w,h = self._size
color = color if glyph != ' ' else color.background()
color = color if color else ttk.TTkColor.RST
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<w and 0<=y<h:
data[ oy+y][ox+x] = glyph
colors[oy+y][ox+x] = color
return True
return False
def drawInCanvas(self, pos, canvas:ttk.TTkCanvas):
if not self._visible: return
px,py = pos
pw,ph = self._size
cw,ch = canvas.size()
if px+pw<0 or py+ph<0:return
if px>=cw or py>=ch:return
# Data Offset
ox,oy = self._offset
# x,y position in the Canvas
cx = max(0,px)
cy = max(0,py)
# x,y position in the Layer
lx,ly = (cx-px),(cy-py)
# Area to be copyed
dw = min(cw-cx,pw-lx)
dh = min(ch-cy,ph-ly)
if _p := self._preview:
data = _p['data']
colors = _p['colors']
else:
data = self._data
colors = self._colors
for y in range(cy,cy+dh):
for x in range(cx,cx+dw):
gl = data[ oy+y+ly-cy][ox+x+lx-cx]
c = colors[oy+y+ly-cy][ox+x+lx-cx]
if gl==' ' and c._bg:
canvas._data[y][x] = gl
canvas._colors[y][x] = c
elif gl!=' ':
canvas._data[y][x] = gl
cc = canvas._colors[y][x]
newC = c.copy()
newC._bg = c._bg if c._bg else cc._bg
canvas._colors[y][x] = newC

324
tools/dumb_paint_lib/layers.py

@ -0,0 +1,324 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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__ = ['Layers','LayerData']
import sys, os
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
class LayerData():
__slots__ = ('_name','_data',
#signals
'nameChanged','visibilityToggled')
def __init__(self,name:ttk.TTkString=ttk.TTkString('New'),data=None) -> None:
self._name:ttk.TTkString = ttk.TTkString(name) if type(name)==str else name
self.visibilityToggled = ttk.pyTTkSignal(bool)
self._data = data
self.nameChanged = ttk.pyTTkSignal(str)
def name(self):
return self._name
def setName(self,name):
self.nameChanged.emit(name)
self._name = name
def data(self):
return self._data
def setData(self,data):
self._data = data
class _layerButton(ttk.TTkContainer):
classStyle = {
'default': {'color': ttk.TTkColor.fg("#dddd88")+ttk.TTkColor.bg("#000044"),
'borderColor': ttk.TTkColor.fg('#CCDDDD'),
'grid':1},
'disabled': {'color': ttk.TTkColor.fg('#888888'),
'borderColor':ttk.TTkColor.fg('#888888'),
'grid':0},
'hover': {'color': ttk.TTkColor.fg("#dddd88")+ttk.TTkColor.bg("#000050")+ttk.TTkColor.BOLD,
'borderColor': ttk.TTkColor.fg("#FFFFCC")+ttk.TTkColor.BOLD,
'grid':1},
'selected': {'color': ttk.TTkColor.fg("#dddd88")+ttk.TTkColor.bg("#004488"),
'borderColor': ttk.TTkColor.fg("#FFFF00"),
'grid':0},
'unchecked': {'color': ttk.TTkColor.fg("#dddd88")+ttk.TTkColor.bg("#000044"),
'borderColor': ttk.TTkColor.RST,
'grid':3},
'clicked': {'color': ttk.TTkColor.fg("#FFFFDD")+ttk.TTkColor.BOLD,
'borderColor': ttk.TTkColor.fg("#DDDDDD")+ttk.TTkColor.BOLD,
'grid':0},
'focus': {'color': ttk.TTkColor.fg("#dddd88")+ttk.TTkColor.bg("#000044")+ttk.TTkColor.BOLD,
'borderColor': ttk.TTkColor.fg("#ffff00") + ttk.TTkColor.BOLD,
'grid':1},
}
__slots__ = ('_layerData','_first', '_isSelected', '_layerVisible',
'_ledit',
# signals
'clicked', 'visibilityToggled',
)
def __init__(self, layer:LayerData, **kwargs):
self.clicked = ttk.pyTTkSignal(_layerButton)
self._layerData:LayerData = layer
self._isSelected = False
self._first = True
self._layerVisible = True
self.visibilityToggled = layer.visibilityToggled
super().__init__(**kwargs|{'layout':ttk.TTkGridLayout()})
self.setPadding(1,1,7,2)
self._ledit = ttk.TTkLineEdit(parent=self, text=layer.name(),visible=False)
self._ledit.focusChanged.connect(self._ledit.setVisible)
self._ledit.textEdited.connect(self._textEdited)
# self.setFocusPolicy(ttk.TTkK.ClickFocus)
@ttk.pyTTkSlot(str)
def _textEdited(self, text):
self._layerData.setName(text)
def mousePressEvent(self, evt) -> bool:
if evt.x <= 3:
self._layerVisible = not self._layerVisible
self.visibilityToggled.emit(self._layerVisible)
self.setFocus()
self.update()
return True
def mouseReleaseEvent(self, evt) -> bool:
self.clicked.emit(self)
return True
def mouseDoubleClickEvent(self, evt) -> bool:
self._ledit.setVisible(True)
self._ledit.setFocus()
return True
def mouseDragEvent(self, evt) -> bool:
drag = ttk.TTkDrag()
drag.setData(self)
name = self._layerData.name()
pm = ttk.TTkCanvas(width=len(name)+4,height=3)
pm.drawBox(pos=(0,0),size=pm.size())
pm.drawText(pos=(2,1), text=name)
drag.setHotSpot(5, 1)
drag.setPixmap(pm)
drag.exec()
return True
def paintEvent(self, canvas: ttk.TTkCanvas):
if self._isSelected:
style = self.style()['selected']
else:
style = self.currentStyle()
borderColor = style['borderColor']
textColor = style['color']
btnVisible = '' if self._layerVisible else ''
w,h = self.size()
canvas.drawText( pos=(0,0),text=f"{''*(w-7)}",color=borderColor)
canvas.drawText( pos=(0,2),text=f"{''*(w-7)}",color=borderColor)
if self._first:
canvas.drawText(pos=(0,1),text=f" {btnVisible} - ┃{' '*(w-7)}",color=borderColor)
else:
canvas.drawText(pos=(0,1),text=f" {btnVisible} - ╽{' '*(w-7)}",color=borderColor)
canvas.drawTTkString(pos=(7,1),text=self._layerData.name(), width=w-9, color=textColor)
class LayerScrollWidget(ttk.TTkAbstractScrollView):
__slots__ = ('_layers','_selected', '_dropTo',
# Signals
'layerSelected','layerAdded','layerDeleted','layersOrderChanged')
def __init__(self, **kwargs):
self.layerSelected = ttk.pyTTkSignal(LayerData)
self.layerAdded = ttk.pyTTkSignal(LayerData)
self.layerDeleted = ttk.pyTTkSignal(LayerData)
self.layersOrderChanged = ttk.pyTTkSignal(list[LayerData])
self._selected = None
self._dropTo = None
self._layers:list[_layerButton] = []
super().__init__(**kwargs)
self.viewChanged.connect(self._placeTheButtons)
self.viewChanged.connect(self._viewChangedHandler)
@ttk.pyTTkSlot()
def _viewChangedHandler(self):
x,y = self.getViewOffsets()
self.layout().setOffset(-x,-y)
def viewFullAreaSize(self) -> tuple:
_,_,w,h = self.layout().fullWidgetAreaGeometry()
return w,h
def viewDisplayedSize(self) -> tuple:
return self.size()
def maximumWidth(self): return 0x10000
def maximumHeight(self): return 0x10000
def minimumWidth(self): return 0
def minimumHeight(self): return 0
@ttk.pyTTkSlot(_layerButton)
def _clickedLayer(self, layerButton:_layerButton):
if sel:=self._selected:
sel._isSelected = False
sel.update()
self._selected = layerButton
layerButton._isSelected = True
self.layerSelected.emit(layerButton._layerData)
self.update()
def clear(self):
for layBtn in self._layers:
self.layout().removeWidget(layBtn)
layBtn.clicked.clear()
layBtn.visibilityToggled.clear()
layBtn._layerData.nameChanged.clear()
self._layers.clear()
self.update()
@ttk.pyTTkSlot()
def moveUp(self):
return self._moveButton(-1)
@ttk.pyTTkSlot()
def moveDown(self):
return self._moveButton(+1)
def _moveButton(self,direction):
if not self._selected: return
index = self._layers.index(self._selected)
if index+direction < 0: return
l = self._layers.pop(index)
self._layers.insert(index+direction,l)
self._placeTheButtons()
self.layersOrderChanged.emit([_l._layerData for _l in self._layers])
@ttk.pyTTkSlot()
def addLayer(self,name=None, data=None):
name = name if name else f"Layer #{len(self._layers)}"
_l=LayerData(name=name,data=data)
newLayerBtn:_layerButton = _layerButton(parent=self,layer=_l)
self._layers.insert(0,newLayerBtn)
if sel:=self._selected: sel._isSelected = False
self._selected = newLayerBtn
newLayerBtn._isSelected = True
newLayerBtn.clicked.connect(self._clickedLayer)
self.viewChanged.emit()
self._placeTheButtons()
self.layerAdded.emit(newLayerBtn._layerData)
return _l
def _placeTheButtons(self):
w,h = self.size()
for i,l in enumerate(self._layers):
l._first = i==0
l.setGeometry(0,i*2,w,3)
l.lowerWidget()
self.update()
@ttk.pyTTkSlot()
def delLayer(self):
self._layers.remove()
def dragEnterEvent(self, evt) -> bool:
if type(evt.data())!=_layerButton: return False
x,y = self.getViewOffsets()
self._dropTo = max(0,min(len(self._layers),(evt.y-1+y)//2))
self.update()
return True
def dragLeaveEvent(self, evt) -> bool:
if type(evt.data())!=_layerButton: return False
self._dropTo = None
self.update()
return True
def dragMoveEvent(self, evt) -> bool:
if type(evt.data())!=_layerButton: return False
x,y = self.getViewOffsets()
self._dropTo = max(0,min(len(self._layers),(evt.y-1+y)//2))
self.update()
ttk.TTkLog.debug(f"{evt.x},{evt.y-y} - {len(self._layers)} - {self._dropTo}")
return True
def dropEvent(self, evt) -> bool:
if type(evt.data())!=_layerButton: return False
x,y = self.getViewOffsets()
self._dropTo = None
data = evt.data()
# dropPos = len(self._layers)-(evt.y-1)//2
dropPos = max(0,min(len(self._layers),(evt.y-1+y)//2))
ttk.TTkLog.debug(f"{evt.x},{evt.y-y} - {len(self._layers)} - {self._dropTo} {dropPos}")
if dropPos > self._layers.index(data):
dropPos -= 1
self._layers.remove(data)
self._layers.insert(dropPos,data)
self._placeTheButtons()
self.layersOrderChanged.emit([_l._layerData for _l in self._layers])
return True
# Stupid hack to paint on top of the child widgets
def paintChildCanvas(self):
super().paintChildCanvas()
offx, offy = self.getViewOffsets()
if self._dropTo == None: return
canvas = self.getCanvas()
w,h = canvas.size()
color = ttk.TTkColor.YELLOW
canvas.drawText(pos=(0,(self._dropTo)*2-offy),text=f"{''*(w-2)}",color=color)
class Layers(ttk.TTkGridLayout):
__slots__ = ('_scrollWidget',
# Forward Methods
'addLayer','clear',
# Forward Signals
'layerSelected','layerAdded','layerDeleted','layersOrderChanged')
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._scrollWidget = _lsw = LayerScrollWidget()
_sa = ttk.TTkAbstractScrollArea(scrollWidget=self._scrollWidget,minWidth=16)
_sa.setViewport(_lsw)
self.addWidget(_sa,0,0,1,5)
self.addWidget(btnAdd :=ttk.TTkButton(text='add') ,1,0)
self.addWidget(btnUp :=ttk.TTkButton(text='',maxWidth=3) ,1,1)
self.addWidget(btnDown:=ttk.TTkButton(text='',maxWidth=3) ,1,2)
# self.addItem(ttk.TTkLayout(),1,3)
self.addWidget(btnDel :=ttk.TTkButton(text=ttk.TTkString('del',ttk.TTkColor.RED),maxWidth=5),1,4)
btnAdd.setToolTip( "Create a new Layer\nand add it to the image")
btnDel.setToolTip( "Delete the selected Layer")
btnUp.setToolTip( "Raise the selected Layer one step")
btnDown.setToolTip("Lower the selected Layer one step")
btnAdd.clicked.connect( _lsw.addLayer)
btnDel.clicked.connect( _lsw.delLayer)
btnUp.clicked.connect( _lsw.moveUp)
btnDown.clicked.connect(_lsw.moveDown)
# forward signals
self.layerSelected = _lsw.layerSelected
self.layerSelected = _lsw.layerSelected
self.layerAdded = _lsw.layerAdded
self.layerDeleted = _lsw.layerDeleted
self.layersOrderChanged = _lsw.layersOrderChanged
# forward methods
self.addLayer = _lsw.addLayer
self.clear = _lsw.clear

347
tools/dumb_paint_lib/maintemplate.py

@ -22,21 +22,25 @@
__all__ = ['PaintTemplate']
import sys, os
import sys, os, json
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
from .paintarea import PaintArea, PaintToolKit
from .palette import Palette
from .textarea import TextArea
from .paintarea import PaintArea, PaintScrollArea
from .canvaslayer import CanvasLayer
from .painttoolkit import PaintToolKit
from .palette import Palette
from .textarea import TextArea
from .layers import Layers,LayerData
from .about import About
class LeftPanel(ttk.TTkVBoxLayout):
__slots__ = ('_palette',
# Signals
'toolSelected')
def __init__(self, *args, **kwargs):
self.toolSelected = ttk.pyTTkSignal(PaintArea.Tool)
self.toolSelected = ttk.pyTTkSignal(CanvasLayer.Tool)
super().__init__(*args, **kwargs)
self._palette = Palette(maxHeight=12)
self.addWidget(self._palette)
@ -53,43 +57,62 @@ class LeftPanel(ttk.TTkVBoxLayout):
# Toolset
lTools = ttk.TTkGridLayout()
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)
ra_line = ttk.TTkRadioButton(radiogroup="tools", text="Line", enabled=False)
ra_rect = ttk.TTkRadioButton(radiogroup="tools", text="Rect")
ra_oval = ttk.TTkRadioButton(radiogroup="tools", text="Oval", enabled=False)
ra_oval = ttk.TTkRadioButton(radiogroup="tools", text="Oval", enabled=False)
ra_rect_f = ttk.TTkRadioButton(radiogroup="toolsRectFill", text="Fill" , enabled=False, checked=True)
ra_rect_e = ttk.TTkRadioButton(radiogroup="toolsRectFill", text="Empty", enabled=False)
@ttk.pyTTkSlot(bool)
def _emitTool(checked):
if not checked: return
tool = PaintArea.Tool.BRUSH
if ra_brush.isChecked():
tool = PaintArea.Tool.BRUSH
cb_move_r = ttk.TTkCheckbox(text="Resize", enabled=False)
@ttk.pyTTkSlot()
def _checkTools():
tool = CanvasLayer.Tool.BRUSH
if ra_move.isChecked():
tool = CanvasLayer.Tool.MOVE
if cb_move_r.isChecked():
tool |= CanvasLayer.Tool.RESIZE
elif ra_brush.isChecked():
tool = CanvasLayer.Tool.BRUSH
elif ra_rect.isChecked():
if ra_rect_e.isChecked():
tool = PaintArea.Tool.RECTEMPTY
tool = CanvasLayer.Tool.RECTEMPTY
else:
tool = PaintArea.Tool.RECTFILL
tool = CanvasLayer.Tool.RECTFILL
self.toolSelected.emit(tool)
@ttk.pyTTkSlot(bool)
def _emitTool(checked):
if not checked: return
_checkTools()
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)
ra_brush.toggled.connect( _emitTool)
ra_line.toggled.connect( _emitTool)
ra_rect.toggled.connect( _emitTool)
ra_rect_f.toggled.connect( _emitTool)
ra_rect_e.toggled.connect( _emitTool)
ra_oval.toggled.connect( _emitTool)
lTools.addWidget(ra_brush,0,0)
lTools.addWidget(ra_line,1,0)
lTools.addWidget(ra_rect,2,0)
lTools.addWidget(ra_rect_f,2,1)
lTools.addWidget(ra_rect_e,2,2)
lTools.addWidget(ra_oval,3,0)
cb_move_r.toggled.connect( _checkTools)
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)
lTools.addWidget(ra_rect ,4,0)
lTools.addWidget(ra_rect_f,4,1)
lTools.addWidget(ra_rect_e,4,2)
lTools.addWidget(ra_oval ,5,0)
self.addItem(lTools)
# brush
@ -104,69 +127,105 @@ class LeftPanel(ttk.TTkVBoxLayout):
return self._palette
class ExportArea(ttk.TTkGridLayout):
__slots__ = ('_paintArea', '_te')
def __init__(self, paintArea, **kwargs):
self._paintArea = paintArea
__slots__ = ('_paintArea', '_te','_cbCrop', '_cbFull', '_cbPal')
def __init__(self, paintArea:PaintArea, **kwargs):
self._paintArea:PaintArea = paintArea
super().__init__(**kwargs)
self._te = ttk.TTkTextEdit(lineNumber=True, readOnly=False)
btn_ex = ttk.TTkButton(text="Export")
self.addWidget(btn_ex ,0,0)
self.addWidget(self._te,1,0,1,3)
btn_exIm = ttk.TTkButton(text="Export Image")
btn_exLa = ttk.TTkButton(text="Export Layer")
btn_exPr = ttk.TTkButton(text="Export Document")
self._cbCrop = ttk.TTkCheckbox(text="Crop",checked=True)
self._cbFull = ttk.TTkCheckbox(text="Full",checked=True)
self._cbPal = ttk.TTkCheckbox(text="Palette",checked=True)
self.addWidget(btn_exLa ,0,0)
self.addWidget(btn_exIm ,0,1)
self.addWidget(btn_exPr ,0,2)
self.addWidget(self._cbCrop,0,3)
self.addWidget(self._cbFull,0,4)
self.addWidget(self._cbPal ,0,5)
self.addWidget(self._te,1,0,1,7)
btn_exLa.clicked.connect(self._exportLayer)
btn_exPr.clicked.connect(self._exportDocument)
btn_exIm.clicked.connect(self._exportImage)
btn_ex.clicked.connect(self._export)
@ttk.pyTTkSlot()
def _exportImage(self):
crop = self._cbCrop.isChecked()
palette = self._cbPal.isChecked()
full = self._cbFull.isChecked()
image = self._paintArea.exportImage()
self._te.setText(image)
@ttk.pyTTkSlot()
def _export(self):
# Don't try this at home
pw,ph = self._paintArea._canvasSize
data = self._paintArea._canvasArea['data']
colors = self._paintArea._canvasArea['colors']
# get the bounding box
xa,xb,ya,yb = pw,0,ph,0
for y,row in enumerate(data):
for x,d in enumerate(row):
c = colors[y][x]
if d != ' ' or c.background():
xa = min(x,xa)
xb = max(x,xb)
ya = min(y,ya)
yb = max(y,yb)
if xa>xb or ya>yb:
self._te.setText("No Picture Found!!!")
def _exportLayer(self):
crop = self._cbCrop.isChecked()
palette = self._cbPal.isChecked()
full = self._cbFull.isChecked()
dd = self._paintArea.exportLayer(full=full,palette=palette,crop=crop)
if not dd:
self._te.setText('# No Data toi be saved!!!')
return
out = "data = {'data': [\n"
outData = {'data':[], 'colors':[]}
for row in data[ya:yb+1]:
out += " ["
outData['data'].append(row[xa:xb+1])
for c in row[xa:xb+1]:
out += f"'{c}',"
out += "],\n"
out += " ],\n"
out += " 'colors': [\n"
for row in colors[ya:yb+1]:
out += " ["
outData['colors'].append([])
for c in row[xa:xb+1]:
fg = f"{c.getHex(ttk.TTkK.Foreground)}" if c.foreground() else None
bg = f"{c.getHex(ttk.TTkK.Background)}" if c.background() else None
out += f"('{fg}','{bg}'),"
outData['colors'][-1].append((fg,bg))
out += "],\n"
out += " ]}\n"
self._te.setText(out)
self._te.append('\n# Compressed Data:')
self._te.setText('# Compressed Data:')
self._te.append('data = TTkUtil.base64_deflate_2_obj(')
b64str = ttk.TTkUtil.obj_inflate_2_base64(outData)
b64str = ttk.TTkUtil.obj_inflate_2_base64(dd)
b64list = ' "' + '" +\n "'.join([b64str[i:i+128] for i in range(0,len(b64str),128)]) + '")'
self._te.append(b64list)
self._te.append('\n# Uncompressed Data:')
outTxt = '{\n'
for i in dd:
if i in ('data','colors','palette'): continue
if type(dd[i]) == str:
outTxt += f" '{i}':'{dd[i]}',\n"
else:
outTxt += f" '{i}':{dd[i]},\n"
outTxt += " 'data':[\n"
for l in dd['data']:
outTxt += f" {l},\n"
outTxt += " ],'colors':[\n"
for l in dd['colors']:
outTxt += f" {l},\n"
if 'palette' in dd:
outTxt += " ],'palette':["
for i,l in enumerate(dd['palette']):
if not i%10:
outTxt += f"\n "
outTxt += f"{l},"
outTxt += "]}\n"
self._te.append(outTxt)
@ttk.pyTTkSlot()
def _exportDocument(self):
crop = self._cbCrop.isChecked()
palette = self._cbPal.isChecked()
full = self._cbFull.isChecked()
dd = self._paintArea.exportDocument(full=full,palette=palette,crop=crop)
if not dd:
self._te.setText('# No Data to be saved!!!')
return
self._te.setText('# Compressed Data:')
self._te.append('data = TTkUtil.base64_deflate_2_obj(')
b64str = ttk.TTkUtil.obj_inflate_2_base64(dd)
b64list = ' "' + '" +\n "'.join([b64str[i:i+128] for i in range(0,len(b64str),128)]) + '")'
self._te.append(b64list)
self._te.append('\n# Uncompressed Data:')
outTxt = '{\n'
for i in dd:
if i=='layers': continue
if type(dd[i]) == str:
outTxt += f" '{i}':'{dd[i]}',\n"
else:
outTxt += f" '{i}':{dd[i]},\n"
outTxt += " 'layers':[\n"
for l in dd['layers']:
outTxt += f" {l},\n"
outTxt += "]}\n"
self._te.append(outTxt)
# Layout:
#
@ -178,12 +237,14 @@ class ExportArea(ttk.TTkGridLayout):
# Export
#
class PaintTemplate(ttk.TTkAppTemplate):
def __init__(self, border=False, **kwargs):
__slots__ = ('_parea','_layers')
def __init__(self, fileName=None, border=False, **kwargs):
super().__init__(border, **kwargs)
self._parea = PaintArea()
self._parea = parea = PaintArea()
self._layers = layers = Layers()
ptoolkit = PaintToolKit()
tarea = TextArea()
expArea = ExportArea(self._parea)
expArea = ExportArea(parea)
leftPanel = LeftPanel()
palette = leftPanel.palette()
@ -192,21 +253,24 @@ class PaintTemplate(ttk.TTkAppTemplate):
rightPanel.addWidget(tarea)
# rightPanel.addItem(expArea, title="Export")
# rightPanel.setSizes([None,5])
rightPanel.addItem(layers, title='Layers')
rightPanel.setSizes([None,9])
self.setItem(expArea, self.BOTTOM, title="Export")
self.setItem(leftPanel , self.LEFT, size=16*2)
self.setWidget(self._parea , self.MAIN)
self.setItem(ptoolkit , self.TOP, fixed=True)
self.setItem(rightPanel , self.RIGHT, size=50)
self.setWidget(PaintScrollArea(parea) , self.MAIN)
self.setWidget(ptoolkit , self.TOP, fixed=True)
self.setItem(rightPanel , self.RIGHT, size=40)
self.setMenuBar(appMenuBar:=ttk.TTkMenuBarLayout(), self.TOP)
fileMenu = appMenuBar.addMenu("&File")
buttonOpen = fileMenu.addMenu("&Open")
buttonClose = fileMenu.addMenu("&Save")
buttonClose = fileMenu.addMenu("Save &As...")
fileMenu.addMenu("&Open" ).menuButtonClicked.connect(self._open)
fileMenu.addMenu("&Save" ).menuButtonClicked.connect(self._save)
fileMenu.addMenu("Save &As...").menuButtonClicked.connect(self._saveAs)
fileMenu.addSpacer()
fileMenu.addMenu("&Import").menuButtonClicked.connect(self.importDictWin)
menuExport = fileMenu.addMenu("&Export")
fileMenu.addSpacer()
fileMenu.addMenu("Load Palette")
fileMenu.addMenu("Save Palette")
@ -214,13 +278,24 @@ class PaintTemplate(ttk.TTkAppTemplate):
buttonExit = fileMenu.addMenu("E&xit")
buttonExit.menuButtonClicked.connect(ttk.TTkHelper.quit)
menuExport.addMenu("&Ascii/Txt").menuButtonClicked.connect(self._saveAsAscii)
menuExport.addMenu("&Ansi").menuButtonClicked.connect(self._saveAsAnsi)
menuExport.addMenu("&Python")
menuExport.addMenu("&Bash")
# extraMenu = appMenuBar.addMenu("E&xtra")
# extraMenu.addMenu("Scratchpad").menuButtonClicked.connect(self.scratchpad)
# extraMenu.addSpacer()
def _showAbout(btn):
ttk.TTkHelper.overlay(None, About(), 30,10)
def _showAboutTTk(btn):
ttk.TTkHelper.overlay(None, ttk.TTkAbout(), 30,10)
helpMenu = appMenuBar.addMenu("&Help", alignment=ttk.TTkK.RIGHT_ALIGN)
helpMenu.addMenu("About ...").menuButtonClicked
helpMenu.addMenu("About tlogg").menuButtonClicked
helpMenu.addMenu("About ...").menuButtonClicked.connect(_showAboutTTk)
helpMenu.addMenu("About DPT").menuButtonClicked.connect(_showAbout)
palette.colorSelected.connect(self._parea.setGlyphColor)
palette.colorSelected.connect(ptoolkit.setColor)
@ -233,9 +308,107 @@ 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())
layers.layerAdded.connect(self._layerAdded)
layers.layerSelected.connect(_layerSelected)
layers.layersOrderChanged.connect(self._layersOrderChanged)
layers.addLayer(name="Background")
if fileName:
self._openFile(fileName)
ttk.ttkConnectDragOpen(ttk.TTkEncoding.APPLICATION_JSON, self._openDragData)
@ttk.pyTTkSlot()
def _open(self):
ttk.ttkCrossOpen(
path='.',
encoding=ttk.TTkEncoding.APPLICATION_JSON,
filter="DumbPaintTool Files (*.DPT.json);;Json Files (*.json);;All Files (*)",
cb=self._openDragData)
@ttk.pyTTkSlot()
def _save(self):
doc = self._parea.exportDocument()
ttk.ttkCrossSave('untitled.DPT.json', json.dumps(doc, indent=1), ttk.TTkEncoding.APPLICATION_JSON)
@ttk.pyTTkSlot()
def _saveAs(self):
doc = self._parea.exportDocument()
ttk.ttkCrossSaveAs('untitled.DPT.json', json.dumps(doc, indent=1), ttk.TTkEncoding.APPLICATION_JSON,
filter="DumbPaintTool Files (*.DPT.json);;Json Files (*.json);;All Files (*)")
@ttk.pyTTkSlot()
def _saveAsAnsi(self):
image = self._parea.exportImage()
text = ttk.TTkString(image)
ttk.ttkCrossSaveAs('untitled.DPT.Ansi.txt', text.toAnsi(), ttk.TTkEncoding.TEXT_PLAIN_UTF8,
filter="Ansi text Files (*.Ansi.txt);;Text Files (*.txt);;All Files (*)")
@ttk.pyTTkSlot()
def _saveAsAscii(self):
image = self._parea.exportImage()
text = ttk.TTkString(image)
ttk.ttkCrossSaveAs('untitled.DPT.ASCII.txt', text.toAscii(), ttk.TTkEncoding.TEXT_PLAIN_UTF8,
filter="ASCII Text Files (*.ASCII.txt);;Text Files (*.txt);;All Files (*)")
@ttk.pyTTkSlot(dict)
def _openDragData(self, data):
dd = json.loads(data['data'])
if 'layers' in dd:
self.importDocument(dd)
else:
self._layers.addLayer(name="Import")
self._parea.importLayer(dd)
def _openFile(self, fileName):
ttk.TTkLog.info(f"Open: {fileName}")
with open(fileName) as fp:
# dd = json.load(fp)
# text = fp.read()
# dd = eval(text)
dd = json.load(fp)
if 'layers' in dd:
self.importDocument(dd)
else:
self._layers.addLayer(name="Import")
self._parea.importLayer(dd)
# Connect and handle Layers event
@ttk.pyTTkSlot(LayerData)
def _layerAdded(self, l:LayerData):
nl = self._parea.newLayer()
nl.setName(l.name())
l.setData(nl)
l.nameChanged.connect(nl.setName)
l.visibilityToggled.connect(nl.setVisible)
l.visibilityToggled.connect(self._parea.update)
@ttk.pyTTkSlot(list[LayerData])
def _layersOrderChanged(self, layers:list[LayerData]):
self._parea._canvasLayers = [ld.data() for ld in reversed(layers)]
self._parea.update()
def importDocument(self, dd):
self._parea.importDocument(dd)
self._layers.clear()
# Little Hack that I don't know how to overcome
self._layers.layerAdded.disconnect(self._layerAdded)
for l in self._parea.canvasLayers():
ld = self._layers.addLayer(name=l.name(),data=l)
ld.nameChanged.connect(l.setName)
ld.visibilityToggled.connect(l.setVisible)
ld.visibilityToggled.connect(self._parea.update)
self._layers.layerAdded.connect(self._layerAdded)
@ttk.pyTTkSlot()
def importDictWin(self):
newWindow = ttk.TTkUiLoader.loadFile(os.path.join(os.path.dirname(os.path.abspath(__file__)),"quickImport.tui.json"))
newWindow = ttk.TTkUiLoader.loadFile(os.path.join(os.path.dirname(os.path.abspath(__file__)),"tui/quickImport.tui.json"))
te = newWindow.getWidgetByName("TextEdit")
@ttk.pyTTkSlot()
@ -266,7 +439,11 @@ class PaintTemplate(ttk.TTkAppTemplate):
ttk.TTkHelper.overlay(None, messageBox, 5, 5, True)
return
self._parea.importLayer(dd)
if 'layers' in dd:
self.importDocument(dd)
else:
self._layers.addLayer(name="Import")
self._parea.importLayer(dd)
newWindow.close()

593
tools/dumb_paint_lib/paintarea.py

@ -20,227 +20,324 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
__all__ = ['PaintArea','PaintToolKit']
__all__ = ['PaintArea','PaintScrollArea','CanvasLayer']
import sys, os
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
from .canvaslayer import CanvasLayer
class PaintToolKit(ttk.TTkGridLayout):
__slots__ = ('_rSelect', '_rPaint', '_lgliph',
'_cbFg', '_cbBg',
'_bpFg', '_bpBg', '_bpDef',
'_glyph',
#Signals
'updatedColor', 'updatedTrans')
def __init__(self, *args, **kwargs):
self._glyph = 'X'
self.updatedColor = ttk.pyTTkSignal(ttk.TTkColor)
self.updatedTrans = ttk.pyTTkSignal(ttk.TTkColor)
super().__init__(*args, **kwargs)
self._rSelect = ttk.TTkRadioButton(text='Select ' , maxWidth=10)
self._rPaint = ttk.TTkRadioButton(text='Paint ' )
self._lgliph = ttk.TTkLabel(text="" , maxWidth=8)
self._cbFg = ttk.TTkCheckbox(text="Fg" , maxWidth= 6)
self._cbBg = ttk.TTkCheckbox(text="Bg" )
self._bpFg = ttk.TTkColorButtonPicker(enabled=False, maxWidth= 6)
self._bpBg = ttk.TTkColorButtonPicker(enabled=False, )
self._bpDef = ttk.TTkColorButtonPicker(color=ttk.TTkColor.bg('#FF00FF'), maxWidth=6)
self.addWidget(self._rSelect ,0,0)
self.addWidget(self._rPaint ,1,0)
self.addWidget(self._lgliph ,0,1,2,1)
self.addWidget(self._cbFg ,0,2)
self.addWidget(self._cbBg ,1,2)
self.addWidget(self._bpFg ,0,3)
self.addWidget(self._bpBg ,1,3)
self.addWidget(ttk.TTkLabel(text=" Trans:", maxWidth=7) ,1,4)
self.addWidget(self._bpDef ,1,5)
self.addItem(ttk.TTkLayout() ,0,6,2,1)
self._cbFg.toggled.connect(self._bpFg.setEnabled)
self._cbBg.toggled.connect(self._bpBg.setEnabled)
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\n '") +
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 PaintArea(ttk.TTkWidget):
class Tool(int):
BRUSH = 0x01
RECTFILL = 0x02
RECTEMPTY = 0x03
__slots__ = ('_canvasArea', '_canvasSize',
class PaintArea(ttk.TTkAbstractScrollView):
__slots__ = ('_canvasLayers', '_currentLayer',
'_transparentColor',
'_mouseMove', '_mouseFill', '_tool',
'_glyph', '_glyphColor')
'_documentPos','_documentSize',
'_mouseMove', '_mouseDrag', '_mousePress', '_mouseRelease',
'_moveData','_resizeData',
'_tool',
'_glyph', '_glyphColor',
# Signals
'selectedLayer')
def __init__(self, *args, **kwargs):
self._transparentColor = ttk.TTkColor.bg('#FF00FF')
self._canvasSize = (0,0)
self._canvasArea = {'data':[],'colors':[]}
self.selectedLayer = ttk.pyTTkSignal(CanvasLayer)
self._transparentColor = {'base':ttk.TTkColor.RST,'dim':ttk.TTkColor.RST}
self._currentLayer:CanvasLayer = None
self._canvasLayers:list[CanvasLayer] = []
self._glyph = 'X'
self._glyphColor = ttk.TTkColor.RST
self._moveData = None
self._resizeData = None
self._mouseMove = None
self._mouseFill = None
self._tool = self.Tool.BRUSH
self._mouseDrag = None
self._mousePress = None
self._mouseRelease = None
self._tool = CanvasLayer.Tool.BRUSH
self._documentPos = (6,3)
self._documentSize = ( 0, 0)
super().__init__(*args, **kwargs)
self.setTrans(ttk.TTkColor.bg('#FF00FF'))
self.resizeCanvas(80,25)
self.setFocusPolicy(ttk.TTkK.ClickFocus + ttk.TTkK.TabFocus)
def _getGeometry(self):
dx,dy = self._documentPos
dw,dh = self._documentSize
ww,wh = self.size()
x1,y1 = min(0,dx),min(0,dy)
x2,y2 = max(dx+dw,ww),max(dy+dh,wh)
for l in self._canvasLayers:
lx,ly = l.pos()
lw,lh = l.size()
x1 = min(x1,dx+lx)
y1 = min(y1,dy+ly)
x2 = max(x2,dx+lx+lw)
y2 = max(y2,dy+ly+lh)
# ttk.TTkLog.debug(f"{x1=},{y1=},{x2-x1=},{y2-y1=}")
return x1,y1,x2-x1,y2-y1
def _retuneGeometry(self):
ox, oy = self.getViewOffsets()
dx,dy = self._documentPos
x1,y1,_,_ = self._getGeometry()
dx1,dy1 = max(0,dx,dx-x1),max(0,dy,dy-y1)
self._documentPos = dx1,dy1
self.viewChanged.emit()
# dx,dy = self._documentPos
# self.chan
self.viewMoveTo(ox+dx1-dx,oy+dy1-dy)
# If the area move to be adapted to the
# Negative coordinates, the reference values used in
# mouse press, moveData, resizeData need to be
# adapted to the new topology
if mp:=self._mousePress:
mpx,mpy = mp
self._mousePress = (mpx-x1,mpy-y1)
if md:=self._moveData:
mx,my=md['pos']
md['pos']=(mx+dx1-dx,my+dy1-dy)
# if rd:=self._resizeData:
# rx,ry,rw,rh=rd['geometry']
# # rd['geometry']=(rx+dx1-dx,ry+dy1-dy,rw,rh)
def viewFullAreaSize(self) -> tuple[int,int]:
_,_,w,h = self._getGeometry()
return w,h
def viewDisplayedSize(self) -> tuple:
return self.size()
def maximumWidth(self): return 0x10000
def maximumHeight(self): return 0x10000
def minimumWidth(self): return 0
def minimumHeight(self): return 0
def canvasLayers(self):
return self._canvasLayers
def resizeCanvas(self, w, h):
self._canvasSize = (w,h)
self._canvasArea['data'] = (self._canvasArea['data'] + [[] for _ in range(h)])[:h]
self._canvasArea['colors'] = (self._canvasArea['colors'] + [[] for _ in range(h)])[:h]
for i in range(h):
self._canvasArea['data'][i] = (self._canvasArea['data'][i] + [' ' for _ in range(w)])[:w]
self._canvasArea['colors'][i] = (self._canvasArea['colors'][i] + [ttk.TTkColor.RST for _ in range(w)])[:w]
self._documentSize = (w,h)
self._retuneGeometry()
self.update()
def clean(self):
w,h = self._canvasSize
for i in range(h):
self._canvasArea['data'][i] = [' ']*w
self._canvasArea['colors'][i] = [ttk.TTkColor.RST]*w
def superResize(self,x,y,w,h):
dx,dy = self._documentPos
dw,dh = self._documentSize
if (x,y,w,h) == (dx,dy,dw,dh): return
if w<0: x=dx;w=dw
if h<0: y=dy;h=dh
if self._documentPos != (x,y):
diffx = dx-x
diffy = dy-y
for l in self._canvasLayers:
lx,ly = l.pos()
l.move(lx+diffx,ly+diffy)
self._documentPos = (x,y)
self._documentSize = (w,h)
self.update()
def setCurrentLayer(self, layer:CanvasLayer):
self._currentLayer = layer
def newLayer(self) -> CanvasLayer:
newLayer = CanvasLayer()
w,h = self._documentSize
w,h = (w,h) if (w,h)!=(0,0) else (80,24)
newLayer.resize(w,h)
self._currentLayer = newLayer
self._canvasLayers.append(newLayer)
return newLayer
def importLayer(self, dd):
w,h = self._canvasSize
w = len(dd['data'][0]) + 10
h = len(dd['data']) + 4
x,y=5,2
self.resizeCanvas(w,h)
self.clean()
for i,rd in enumerate(dd['data']):
for ii,cd in enumerate(rd):
self._canvasArea['data'][i+y][ii+x] = cd
for i,rd in enumerate(dd['colors']):
for ii,cd in enumerate(rd):
fg,bg = cd
if fg and bg:
self._canvasArea['colors'][i+y][ii+x] = ttk.TTkColor.fg(fg)+ttk.TTkColor.bg(bg)
elif fg:
self._canvasArea['colors'][i+y][ii+x] = ttk.TTkColor.fg(fg)
elif bg:
self._canvasArea['colors'][i+y][ii+x] = ttk.TTkColor.bg(bg)
else:
self._canvasArea['colors'][i+y][ii+x] = ttk.TTkColor.RST
self.update()
self._currentLayer.importLayer(dd)
self._retuneGeometry()
def importDocument(self, dd):
self._canvasLayers = []
if (
( 'version' in dd and dd['version'] == '1.0.0' ) or
( 'version' in dd and dd['version'] == '1.0.1' and dd['type'] == 'DumbPaintTool/Document') ):
self.resizeCanvas(*dd['size'])
for l in dd['layers']:
nl = self.newLayer()
nl.importLayer(l)
else:
ttk.TTkLog.error("File Format not recognised")
self._retuneGeometry()
def exportImage(self):
return {}
def exportLayer(self, full=False, palette=True, crop=True) -> dict:
if self._currentLayer:
return self._currentLayer.exportLayer(full=full,palette=palette,crop=crop)
return {}
def exportDocument(self, full=True, palette=True, crop=True) -> dict:
pw,ph = self._documentSize
outData = {
'type':'DumbPaintTool/Document',
'version':'1.0.1',
'size':(pw,ph),
'layers':[l.exportLayer(full=full,palette=palette,crop=crop) for l in self._canvasLayers]}
return outData
def exportImage(self) -> dict:
pw,ph = self._documentSize
image = ttk.TTkCanvas(width=pw,height=ph)
for l in self._canvasLayers:
lx,ly = l.pos()
l.drawInCanvas(pos=(lx,ly),canvas=image)
return image.toAnsi()
def leaveEvent(self, evt):
self._mouseMove = None
self._moveData = None
self.update()
return super().leaveEvent(evt)
@ttk.pyTTkSlot(Tool)
@ttk.pyTTkSlot(CanvasLayer.Tool)
def setTool(self, tool):
self._tool = tool
self.update()
def mouseMoveEvent(self, evt) -> bool:
# self._mouseFill = None
x,y = evt.x, evt.y
w,h = self._canvasSize
if 0<=x<w and 0<=y<h:
self._mouseMove = (x, y)
self.update()
return True
self._mouseMove = None
def _handleAction(self):
dx,dy = self._documentPos
dw,dh = self._documentSize
ox, oy = self.getViewOffsets()
mp = self._mousePress
mm = self._mouseMove
md = self._mouseDrag
mr = self._mouseRelease
l = self._currentLayer
lx,ly = l.pos()
if self._tool & CanvasLayer.Tool.MOVE and mp and not md:
if self._tool & CanvasLayer.Tool.RESIZE and not md:
mpx,mpy = mp
self._resizeData = None
def _getSelected(_x,_y,_w,_h):
_selected = ttk.TTkK.NONE
if _x <= mpx < _x+_w and mpy == _y: _selected |= ttk.TTkK.TOP
if _x <= mpx < _x+_w and mpy == _y+_h-1: _selected |= ttk.TTkK.BOTTOM
if _y <= mpy < _y+_h and mpx == _x: _selected |= ttk.TTkK.LEFT
if _y <= mpy < _y+_h and mpx == _x+_w-1: _selected |= ttk.TTkK.RIGHT
return _selected
# Main Area Resize Borders
if selected := _getSelected(dx-1,dy-1,dw+2,dh+2):
self._resizeData = {'type':PaintArea,'selected':selected,'cb':self.superResize,'geometry':(dx,dy,dw,dh)}
elif l:
# Selected Layer Resize Borders
lx,ly = l.pos()
lw,lh = l.size()
if selected := _getSelected(dx+lx-1,dy+ly-1,lw+2,lh+2):
self._resizeData = {'type':CanvasLayer,'selected':selected,'cb':l.superResize,'geometry':(lx,ly,lw,lh)}
if not self._resizeData:
# Get The Layer to Move
self._moveData = None
for lm in reversed(self._canvasLayers):
mpx,mpy = mp
lmx,lmy = lm.pos()
self._moveData = {'type':PaintArea,'pos':(dx,dy)}
if lm.isOpaque(mpx-lmx-dx,mpy-lmy-dy):
self._currentLayer = lm
tml = lm
self._moveData = {'type':CanvasLayer,'pos':tml.pos(),'layer':tml}
self.selectedLayer.emit(lm)
break
elif self._tool & CanvasLayer.Tool.MOVE and mp and md:
# Move/Resize Tool
if self._tool & CanvasLayer.Tool.RESIZE and (rData:=self._resizeData):
_rx,_ry,_rw,_rh = rData['geometry']
_rdx,_rdy,_rdw,_rdh=(_rx,_ry,_rw,_rh)
mpx,mpy = mp
mdx,mdy = md
diffx = mdx-mpx
diffy = mdy-mpy
if rData['selected'] & ttk.TTkK.TOP: _rdh-=diffy ; _rdy+=diffy
if rData['selected'] & ttk.TTkK.BOTTOM: _rdh+=diffy
if rData['selected'] & ttk.TTkK.LEFT: _rdw-=diffx ; _rdx+=diffx
if rData['selected'] & ttk.TTkK.RIGHT: _rdw+=diffx
rData['cb'](_rdx,_rdy,_rdw,_rdh)
if not self._resizeData and (mData:=self._moveData):
mpx,mpy = mp
mdx,mdy = md
pdx,pdy = mdx-mpx,mdy-mpy
if mData['type']==CanvasLayer:
px,py = self._moveData['pos']
self._moveData['layer'].move(px+pdx,py+pdy)
self.selectedLayer.emit(self._moveData['layer'])
elif mData['type']==PaintArea:
px,py = self._moveData['pos']
self._documentPos = (px+pdx,py+pdy)
self._retuneGeometry()
elif self._tool == CanvasLayer.Tool.BRUSH:
if mp or md:
if md: mx,my = md
else: mx,my = mp
preview=False
self._currentLayer.placeGlyph(mx-lx-dx,my-ly-dy,self._glyph,self._glyphColor,preview)
elif mm:
mx,my = mm
preview=True
self._currentLayer.placeGlyph(mx-lx-dx,my-ly-dy,self._glyph,self._glyphColor,preview)
elif self._tool in (CanvasLayer.Tool.RECTEMPTY, CanvasLayer.Tool.RECTFILL):
if mr and mp:
mpx,mpy = mp
mrx,mry = mr
preview=False
self._currentLayer.placeFill((mpx-lx-dx,mpy-ly-dy,mrx-lx-dx,mry-ly-dy),self._tool,self._glyph,self._glyphColor,preview)
elif md:
mpx,mpy = mp
mrx,mry = md
preview=True
self._currentLayer.placeFill((mpx-lx-dx,mpy-ly-dy,mrx-lx-dx,mry-ly-dy),self._tool,self._glyph,self._glyphColor,preview)
self.update()
return super().mouseMoveEvent(evt)
def mouseMoveEvent(self, evt) -> bool:
ox, oy = self.getViewOffsets()
self._mouseMove = (evt.x+ox,evt.y+oy)
self._mouseDrag = None
self._handleAction()
return True
def mouseDragEvent(self, evt) -> bool:
x,y = evt.x,evt.y
if self._tool == self.Tool.BRUSH:
if self._placeGlyph(evt.x, evt.y):
return True
if self._tool in (self.Tool.RECTEMPTY, self.Tool.RECTFILL) and self._mouseFill:
mx,my = self._mouseFill[:2]
self._mouseFill = [mx,my,x,y]
self.update()
return True
return super().mouseDragEvent(evt)
ox, oy = self.getViewOffsets()
self._mouseDrag=(evt.x+ox,evt.y+oy)
self._mouseMove= None
self._handleAction()
return True
def mousePressEvent(self, evt) -> bool:
x,y = evt.x,evt.y
if self._tool == self.Tool.BRUSH:
if self._placeGlyph(x,y):
return True
if self._tool in (self.Tool.RECTEMPTY, self.Tool.RECTFILL):
self._mouseFill = [x,y,x,y]
self.update()
return True
return super().mousePressEvent(evt)
ox, oy = self.getViewOffsets()
self._mousePress=(evt.x+ox,evt.y+oy)
self._moveData = None
self._mouseMove = None
self._mouseDrag = None
self._mouseRelease = None
self._handleAction()
return True
def mouseReleaseEvent(self, evt) -> bool:
x,y = evt.x,evt.y
if self._tool in (self.Tool.RECTEMPTY, self.Tool.RECTFILL):
self._placeFill()
self.update()
return True
self._mouseFill = None
ox, oy = self.getViewOffsets()
self._mouseRelease=(evt.x+ox,evt.y+oy)
self._mouseMove = None
self._handleAction()
self._moveData = None
self._resizeData = None
self._mousePress = None
self._mouseDrag = None
self._mouseRelease = None
return super().mousePressEvent(evt)
@ttk.pyTTkSlot(ttk.TTkString)
def glyphFromString(self, ch:ttk.TTkString):
if len(ch)<=0: return
self._glyph = ch.charAt(0)
# self._glyphColor = ch.colorAt(0)
def glyph(self):
return self._glyph
@ -258,85 +355,71 @@ class PaintArea(ttk.TTkWidget):
self._glyphColor = color
@ttk.pyTTkSlot(ttk.TTkColor)
def setTrans(self, color):
self._transparentColor = color
def setTrans(self, color:ttk.TTkColor):
r,g,b = color.bgToRGB()
self._transparentColor = {
'base':color,
'dim': ttk.TTkColor.bg(f'#{int(r*0.3):02x}{int(g*0.3):02x}{int(b*0.3):02x}'),
'layer': ttk.TTkColor.bg(f'#{int(r*0.6):02x}{int(g*0.6):02x}{int(b*0.6):02x}'),
'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._mouseFill: return False
w,h = self._canvasSize
ax,ay,bx,by = self._mouseFill
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))
fax,fay = min(ax,bx), min(ay,by)
fbx,fby = max(ax,bx), max(ay,by)
self._mouseFill = None
self._mouseMove = None
data = self._canvasArea['data']
colors = self._canvasArea['colors']
glyph = self._glyph
color = self._glyphColor
if self._tool == self.Tool.RECTFILL:
for row in data[fay:fby+1]:
row[fax:fbx+1] = [glyph]*(fbx-fax+1)
for row in colors[fay:fby+1]:
row[fax:fbx+1] = [color]*(fbx-fax+1)
if self._tool == self.Tool.RECTEMPTY:
data[fay][fax:fbx+1] = [glyph]*(fbx-fax+1)
data[fby][fax:fbx+1] = [glyph]*(fbx-fax+1)
colors[fay][fax:fbx+1] = [color]*(fbx-fax+1)
colors[fby][fax:fbx+1] = [color]*(fbx-fax+1)
for row in data[fay:fby]:
row[fax]=row[fbx]=glyph
for row in colors[fay:fby]:
row[fax]=row[fbx]=color
self.update()
return True
def _placeGlyph(self,x,y):
self._mouseMove = None
w,h = self._canvasSize
data = self._canvasArea['data']
colors = self._canvasArea['colors']
if 0<=x<w and 0<=y<h:
data[y][x] = self._glyph
colors[y][x] = self._glyphColor
self.update()
return True
return False
def paintEvent(self, canvas: ttk.TTkCanvas):
pw,ph = self._canvasSize
def paintEvent(self, canvas:ttk.TTkCanvas):
dx,dy = self._documentPos
ox, oy = self.getViewOffsets()
dw,dh = self._documentSize
dox,doy = dx-ox,dy-oy
cw,ch = canvas.size()
w=min(cw,pw)
h=min(ch,ph)
data = self._canvasArea['data']
colors = self._canvasArea['colors']
tc = self._transparentColor
for y in range(h):
canvas._data[y][0:w] = data[y][0:w]
for x in range(w):
c = colors[y][x]
canvas._colors[y][x] = c if c._bg else c+tc
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._mouseFill:
ax,ay,bx,by = self._mouseFill
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
gc = self._glyphColor
canvas.fill(pos=(x,y), size=(w,h),
color=gc if gc._bg else gc+tc,
char=self._glyph)
w=min(cw,dw)
h=min(ch,dh)
tcb = self._transparentColor['base']
tcd = self._transparentColor['dim']
if l:=self._currentLayer:
tclb = self._transparentColor['layer']
tcld = self._transparentColor['layerDim']
lx,ly = l.pos()
lw,lh = l.size()
canvas.fill(pos=(0 ,ly+doy),size=(cw,lh),color=tcld)
canvas.fill(pos=(lx+dox,0 ),size=(lw,ch),color=tcld)
canvas.fill(pos=(lx+dox,ly+doy),size=(lw,lh),color=tclb)
canvas.fill(pos=(dx-ox,dy-oy),size=(dw,dh),color=tcb)
canvas.fill(pos=(0 ,dy-oy-1), size=(cw,1),color=tcd)
canvas.fill(pos=(0 ,dy-oy+dh),size=(cw,1),color=tcd)
canvas.fill(pos=(dx-ox-2 ,0 ),size=(2,ch),color=tcd)
canvas.fill(pos=(dx-ox+dw,0 ),size=(2,ch),color=tcd)
for l in self._canvasLayers:
lx,ly = l.pos()
l.drawInCanvas(pos=(lx+dox,ly+doy),canvas=canvas)
if self._tool & CanvasLayer.Tool.RESIZE:
rd = self._resizeData
def _drawResizeBorders(_rx,_ry,_rw,_rh,_sel):
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)
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='')
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 l:=self._currentLayer:
lx,ly = l.pos()
lw,lh = l.size()
_drawResizeBorders(lx+dx-ox-1, ly+dy-oy-1, lw+2, lh+2, sLayer)
class PaintScrollArea(ttk.TTkAbstractScrollArea):
def __init__(self, pwidget:PaintArea, **kwargs):
super().__init__(**kwargs)
self.setViewport(pwidget)

123
tools/dumb_paint_lib/painttoolkit.py

@ -0,0 +1,123 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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__)),"tui/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)

1
tools/dumb_paint_lib/palette.py

@ -55,6 +55,7 @@ class Palette(ttk.TTkWidget):
self._mouseMove = None
self.setPalette(_defaultPalette)
super().__init__(*args, **kwargs)
self.setFocusPolicy(ttk.TTkK.ClickFocus)
def setPalette(self, palette):
self._palette = []

918
tools/dumb_paint_lib/tui/paintToolKit.tui.json

@ -0,0 +1,918 @@
{
"version": "2.0.0",
"tui": {
"class": "TTkContainer",
"params": {
"Name": "MainWindow",
"Position": [
4,
2
],
"Size": [
78,
2
],
"Min Width": 50,
"Min Height": 2,
"Max Width": 65536,
"Max Height": 2,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
0
],
"Layout": "TTkLayout"
},
"layout": {
"class": "TTkLayout",
"params": {
"Geometry": [
0,
0,
78,
2
]
},
"children": [
{
"class": "TTkCheckbox",
"params": {
"Name": "cbFg",
"Position": [
12,
0
],
"Size": [
5,
1
],
"Min Width": 5,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 1,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "Fg",
"Tristate": false,
"Checked": false,
"Check State": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkCheckbox",
"params": {
"Name": "cbBg",
"Position": [
12,
1
],
"Size": [
5,
1
],
"Min Width": 5,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 1,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "Bg",
"Tristate": false,
"Checked": false,
"Check State": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-1",
"Position": [
0,
1
],
"Size": [
5,
1
],
"Min Width": 5,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "Trans",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "lglyph",
"Position": [
0,
0
],
"Size": [
12,
1
],
"Min Width": 6,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "Glyph:",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkColorButtonPicker",
"params": {
"Name": "bpDef",
"Position": [
5,
1
],
"Size": [
6,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 1,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "",
"Border": false,
"Checkable": false,
"Checked": false,
"Color": "\u001b[48;2;0;0;0m"
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-2",
"Position": [
25,
0
],
"Size": [
3,
1
],
"Min Width": 3,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "Doc",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkColorButtonPicker",
"params": {
"Name": "bpBg",
"Position": [
17,
1
],
"Size": [
6,
1
],
"Min Width": 2,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 1,
"Visible": true,
"Enabled": false,
"ToolTip": "",
"Text": "",
"Border": false,
"Checkable": false,
"Checked": false,
"Color": "\u001b[48;2;0;0;0m"
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkColorButtonPicker",
"params": {
"Name": "bpFg",
"Position": [
17,
0
],
"Size": [
6,
1
],
"Min Width": 2,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 1,
"Visible": true,
"Enabled": false,
"ToolTip": "",
"Text": "",
"Border": false,
"Checkable": false,
"Checked": false,
"Color": "\u001b[48;2;0;0;0m"
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-4",
"Position": [
29,
0
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "x",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-5",
"Position": [
29,
1
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "y",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbDx",
"Position": [
31,
0
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": -100,
"Maximum": 100
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbDy",
"Position": [
31,
1
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": -100,
"Maximum": 100
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-6",
"Position": [
38,
0
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "w",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-7",
"Position": [
38,
1
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "h",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbDw",
"Position": [
40,
0
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": 0,
"Maximum": 99
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbDh",
"Position": [
40,
1
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": 0,
"Maximum": 99
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-3",
"Position": [
48,
0
],
"Size": [
5,
1
],
"Min Width": 5,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "Layer",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-8",
"Position": [
54,
0
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "x",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-9",
"Position": [
54,
1
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "y",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbLx",
"Position": [
56,
0
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": -100,
"Maximum": 100
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbLy",
"Position": [
56,
1
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": -100,
"Maximum": 100
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-10",
"Position": [
63,
0
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "w",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkLabel",
"params": {
"Name": "TTkLabel-11",
"Position": [
63,
1
],
"Size": [
1,
1
],
"Min Width": 1,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Text": "h",
"Color": "\u001b[0m",
"Alignment": 0
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbLw",
"Position": [
65,
0
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": 0,
"Maximum": 99
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
},
{
"class": "TTkSpinBox",
"params": {
"Name": "sbLh",
"Position": [
65,
1
],
"Size": [
6,
1
],
"Min Width": 4,
"Min Height": 1,
"Max Width": 65536,
"Max Height": 65536,
"Visible": true,
"Enabled": true,
"ToolTip": "",
"Padding": [
0,
0,
0,
2
],
"Layout": "TTkGridLayout",
"Value": 0,
"Minimum": 0,
"Maximum": 99
},
"layout": {
"class": "TTkGridLayout",
"params": {
"Geometry": [
0,
0,
4,
1
]
},
"children": []
},
"row": 0,
"col": 0,
"rowspan": 1,
"colspan": 1
}
]
}
},
"connections": [
{
"sender": "cbFg",
"receiver": "bpFg",
"signal": "toggled(bool)",
"slot": "setEnabled(bool)"
},
{
"sender": "cbBg",
"receiver": "bpBg",
"signal": "toggled(bool)",
"slot": "setEnabled(bool)"
}
]
}

0
tools/dumb_paint_lib/quickImport.tui.json → tools/dumb_paint_lib/tui/quickImport.tui.json

35
tools/webExporter/index.html

@ -9,16 +9,26 @@
<link href="www/xterm/xterm.css" rel="stylesheet" />
<script src="www/xterm/xterm.js"></script>
<script src="www/xterm-addon-fit/xterm-addon-fit.js"></script>
<!--
<script src="www/xterm-addon-canvas/xterm-addon-canvas.js"></script>
-->
<script src="www/xterm-addon-unicode11/xterm-addon-unicode11.js"></script>
<script src="www/file-saver/FileSaver.js"></script>
<script src="js/ttkproxy.js"></script>
<style>
body {
height: 100%;
overflow-y: hidden;
overflow-x: hidden;
}
.xterm .xterm-viewport {overflow-y: hidden;}
</style>
</head>
<body>
<div id="terminal" contenteditable="true" onpaste="pasteFunction()" oncontextmenu="return false;" style="position: absolute; left: 0px; top: 0px; right: 0px; bottom: 0px"></div>
<input type="file" id="file-input" hidden />
<script type="text/javascript">
@ -28,8 +38,10 @@
}
// Workaround from: https://developer.mozilla.org/en-US/docs/Web/API/CSS_Font_Loading_API
// const font = new FontFace("NerdFont", "url(www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf)");
const font = new FontFace("NerdFont", "url(www/opentype/3270SemiCondensed-Regular.otf)");
// const font = new FontFace("pyTermTkFont", "url(www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf)");
// const font = new FontFace("pyTermTkFont", "url(www/fonts/opentype/3270SemiCondensed-Regular.otf)");
const font = new FontFace("pyTermTkFont", "url(www/fonts/opentype/3270-Regular.otf)");
// const font = new FontFace("pyTermTkFont", "url(www/fonts/unifont/unifont_upper.ttf)");
document.fonts.add(font);
font.load();
document.fonts.ready.then(() => {fetchData()});
@ -45,6 +57,20 @@
main(json)
}
async function paste(){
if(navigator.clipboard){
try {
// let text = null
let text = await navigator.clipboard.readText().then((clipText) => clipText)
console.log('Pasted content: ', text)
return text
} catch (err) {
console.error('Failed to read clipboard contents: ', err);
}
}
return null
}
/* pyodide demo */
async function mainTTk(term,json){
ttkProxy = new TTkProxy(term)
@ -69,7 +95,9 @@
/* xterm.js */
var term = new Terminal({
allowProposedApi: true,
fontFamily: 'NerdFont'});
fontSize: 17,
// fontFamily: 'FreeSerif Regular'});
fontFamily: 'pyTermTkFont'});
/* https://www.npmjs.com/package/xterm-addon-fit */
const fitAddon = new FitAddon.FitAddon();
@ -77,6 +105,7 @@
const unicode11Addon = new Unicode11Addon.Unicode11Addon();
term.loadAddon(fitAddon);
// term.loadAddon(new CanvasAddon.CanvasAddon());
term.loadAddon(unicode11Addon);
term.unicode.activeVersion = '11';

116
tools/webExporter/js/ttkproxy.js

@ -22,19 +22,87 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
async function permissionsCheck() {
if (navigator.clipboard){
const read = await navigator.permissions.query({
name: 'clipboard-read',
});
const write = await navigator.permissions.query({
name: 'clipboard-write',
});
return write.state === 'granted' && read.state !== 'denied';
}
return false
}
// Declaration
class TTkProxy {
constructor(term) {
this.term = term
document.getElementById("file-input").addEventListener("change", (e) => {
e.preventDefault();
const file = e.target.files[0]
let reader = new FileReader()
reader.onload = (event) => {
let data = {
'type':file.type,
'name':file.name,
'size':file.size,
'data':event.target.result}
this.ttk_fileOpen(data)
}
if (file.type.startsWith('application/json') ||
file.type.startsWith('text')){
reader.readAsText(file);
}
})
document.getElementById("terminal").addEventListener("dragover", (e) => {
e.preventDefault(); //preventing from default behaviour
//dropArea.classList.add("active");
//dragFile.textContent = "Release to Upload File";
})
document.getElementById("terminal").addEventListener("dragleave", () => {
//dropArea.classList.remove("active");
// dragFile.textContent = "Drag files here to upload";
})
document.getElementById("terminal").addEventListener("drop", (e) => {
e.preventDefault();
const target = e.dataTransfer;
const file = target.files[0]
let reader = new FileReader()
reader.onload = (event) => {
let data = {
'type':file.type,
'name':file.name,
'size':file.size,
'data':event.target.result}
this.ttk_dragOpen(data)
}
if (file.type.startsWith('application/json') ||
file.type.startsWith('text')){
reader.readAsText(file);
}
})
}
pyodideProxy = {
copy: function(text){
openFile: function(encoding){
let input = document.getElementById("file-input")
input.accept = encoding
input.click();
},
saveFile: function(name, content, encoding){
const blob = new Blob([content], {type: encoding});
saveAs(blob, name);
},
copy: async function(text){
console.log("Copying:",text)
if(navigator.clipboard){
const permission = await permissionsCheck()
if(permission){
navigator.clipboard.writeText(text)
return //codes below wont be executed
}
const active = document.activeElement
const textArea = document.createElement("textarea")
textArea.value = text
@ -46,13 +114,23 @@ class TTkProxy {
document.execCommand('copy')
document.body.removeChild(textArea)
active.focus()
},
paste: function(){
if(navigator.clipboard){
text = navigator.clipboard.readText()
console.log("Pasted:",text)
return text
/*
const permission = await permissionsCheck()
if(permission){
try {
// let text = null
let text = await navigator.clipboard.readText().then((txt) => txt)
// const text = navigator.clipboard.readText()
console.log('Pasted content: ', text)
return text
} catch (err) {
console.error('Failed to read clipboard contents: ', err);
}
}
*/
return null
},
consoleLog: function(m){
@ -135,6 +213,16 @@ class TTkProxy {
from TermTk.TTkCore.TTkTerm.input import TTkInput
import pyodideProxy
def ttk_dragOpen(data):
data = data.to_py()
ttk.ttkEmitDragOpen(data['type'],data)
# ttk_log(f"{type(data.to_py())=}, {str(data.to_py())}")
def ttk_fileOpen(data):
data = data.to_py()
ttk.ttkEmitFileOpen(data['type'],data)
# ttk_log(f"{type(data.to_py())=}, {str(data.to_py())}")
def ttk_input(val):
kevt,mevt,paste = TTkInput.key_process(val)
if kevt or mevt:
@ -155,7 +243,7 @@ class TTkProxy {
def ttk_log(val):
# hex = [f"0x{ord(x):02x}" for x in val]
ttk.TTkLog.debug("---> "+val.replace("\\033","<ESC>") + " - ")
ttk.TTkHelper.paintAll()
# ttk.TTkHelper.paintAll()
def ttk_clean():
if ttk.TTkHelper._rootWidget:
@ -187,12 +275,14 @@ class TTkProxy {
`,{ globals: this.namespace }
);
this.ttk_log = this.namespace.get("ttk_log");
this.ttk_input = this.namespace.get("ttk_input");
this.ttk_timer = this.namespace.get("ttk_timer");
this.ttk_resize = this.namespace.get("ttk_resize");
this.ttk_clean = this.namespace.get("ttk_clean");
this.ttk_init = this.namespace.get("ttk_init");
this.ttk_log = this.namespace.get("ttk_log");
this.ttk_input = this.namespace.get("ttk_input");
this.ttk_timer = this.namespace.get("ttk_timer");
this.ttk_resize = this.namespace.get("ttk_resize");
this.ttk_clean = this.namespace.get("ttk_clean");
this.ttk_init = this.namespace.get("ttk_init");
this.ttk_dragOpen = this.namespace.get("ttk_dragOpen");
this.ttk_fileOpen = this.namespace.get("ttk_fileOpen");
this.pyodideProxy.ttk_timer = this.ttk_timer
this.pyodideProxy.term = this.term

28
tools/webExporterInit.sh

@ -48,10 +48,13 @@ mkdir -p ${_TMP_PATH}/bin \
${_TMP_PATH}/www/pyodide \
${_TMP_PATH}/www/xterm/ \
${_TMP_PATH}/www/xterm-addon-fit \
${_TMP_PATH}/www/xterm-addon-canvas \
${_TMP_PATH}/www/xterm-addon-unicode11 \
${_TMP_PATH}/www/file-saver\
${_TMP_PATH}/www/webfonts \
${_TMP_PATH}/www/nerdfonts \
${_TMP_PATH}/www/opentype
${_TMP_PATH}/www/fonts/nerdfonts \
${_TMP_PATH}/www/fonts/opentype \
${_TMP_PATH}/www/fonts/unifont
function _download {
_P=$1
@ -63,11 +66,12 @@ function _download {
};
_download ${_TMP_PATH}/www/pyodide/ www/pyodide/pyodide.js
# _download ${_TMP_PATH}/www/pyodide/ www/pyodide/pyodide.js.map
# _download ${_TMP_PATH}/www/pyodide/ www/pyodide/pyodide.js
_download ${_TMP_PATH}/www/pyodide/ www/pyodide/pyodide-lock.json
_download ${_TMP_PATH}/www/pyodide/ www/pyodide/python_stdlib.zip
_download ${_TMP_PATH}/www/pyodide/ www/pyodide/pyodide.asm.js
_download ${_TMP_PATH}/www/pyodide/ www/pyodide/repodata.json
# _download ${_TMP_PATH}/www/pyodide/ www/pyodide/repodata.json
_download ${_TMP_PATH}/www/pyodide/ www/pyodide/pyodide.asm.wasm
_download ${_TMP_PATH}/www/xterm/ www/xterm/xterm.css
@ -77,12 +81,20 @@ _download ${_TMP_PATH}/www/xterm/ www/xterm/xterm.js.map
_download ${_TMP_PATH}/www/xterm-addon-fit/ www/xterm-addon-fit/xterm-addon-fit.js
_download ${_TMP_PATH}/www/xterm-addon-fit/ www/xterm-addon-fit/xterm-addon-fit.js.map
_download ${_TMP_PATH}/www/xterm-addon-canvas/ www/xterm-addon-canvas/xterm-addon-canvas.js
_download ${_TMP_PATH}/www/xterm-addon-canvas/ www/xterm-addon-canvas/xterm-addon-canvas.js.map
_download ${_TMP_PATH}/www/xterm-addon-unicode11/ www/xterm-addon-unicode11/xterm-addon-unicode11.js
_download ${_TMP_PATH}/www/xterm-addon-unicode11/ www/xterm-addon-unicode11/xterm-addon-unicode11.js.map
_download ${_TMP_PATH}/www/file-saver/ www/file-saver/FileSaver.js
# _download ${_TMP_PATH}/www/webfonts/ www/webfonts/fa-regular-400.woff2
# _download ${_TMP_PATH}/www/nerdfonts/ www/nerdfonts/HurmitNerdFontMono-Regular.otf
# _download ${_TMP_PATH}/www/nerdfonts/ www/nerdfonts/DejaVuSansMNerdFont-Regular.ttf
_download ${_TMP_PATH}/www/opentype/ www/opentype/3270SemiCondensed-Regular.otf
# _download ${_TMP_PATH}/www/fonts/webfonts/ www/fonts/webfonts/fa-regular-400.woff2
# _download ${_TMP_PATH}/www/fonts/nerdfonts/ www/fonts/nerdfonts/HurmitNerdFontMono-Regular.otf
# _download ${_TMP_PATH}/www/fonts/nerdfonts/ www/fonts/nerdfonts/DejaVuSansMNerdFont-Regular.ttf
# _download ${_TMP_PATH}/www/fonts/opentype/ www/fonts/opentype/3270SemiCondensed-Regular.otf
_download ${_TMP_PATH}/www/fonts/opentype/ www/fonts/opentype/3270-Regular.otf
# _download ${_TMP_PATH}/www/fonts/unifont/ www/fonts/unifont/unifont_upper.ttf
_download ${_TMP_PATH}/www/ www/favicon.ico
@ -112,4 +124,4 @@ echo '{
rm -rf ${_TMP_PATH}/TermTk
pushd ${_TMP_PATH}
zip -r ${_PWD}/itchExport.zip *
popd
popd

Loading…
Cancel
Save