From 09ef6bb04580b26cb1acb949728f0a34638338d1 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 21 Feb 2021 19:25:49 +0000 Subject: [PATCH] Improved performances (just a little bit) --- .gitignore | 4 ++- README.md | 13 ++++++++- TermTk/TTkCore/canvas.py | 8 ----- TermTk/TTkCore/helper.py | 25 +++++++++------- TermTk/TTkCore/ttk.py | 26 +++++++++++++++-- TermTk/TTkWidgets/__init__.py | 1 + TermTk/TTkWidgets/button.py | 1 + TermTk/TTkWidgets/frame.py | 6 ++-- TermTk/TTkWidgets/layout.py | 4 +-- TermTk/TTkWidgets/logviewer.py | 23 +++++++++++++++ TermTk/TTkWidgets/splitter.py | 52 +++++++++++++++++++++++++++++++++ TermTk/TTkWidgets/testwidget.py | 6 ++-- TermTk/TTkWidgets/widget.py | 52 +++++++++++++++++++++------------ TermTk/TTkWidgets/window.py | 1 + tests/test.ui.004.py | 10 +++++-- 15 files changed, 180 insertions(+), 52 deletions(-) create mode 100644 TermTk/TTkWidgets/logviewer.py create mode 100644 TermTk/TTkWidgets/splitter.py diff --git a/.gitignore b/.gitignore index 56548c06..51652eca 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,10 @@ __pycache__/ *.py[cod] *$py.class -# tmp folder +# other tmp +profiler.txt +.vscode # C extensions *.so diff --git a/README.md b/README.md index 8df13754..08c745b2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # pyTermTk #### Python Terminal Toolkit Text-based user interface library ([TUI](https://en.wikipedia.org/wiki/Text-based_user_interface)) -Evolved from the dead project [pyCuT](https://github.com/ceccopierangiolieugenio/pyCuT) +Evolved from the discontinued project [pyCuT](https://github.com/ceccopierangiolieugenio/pyCuT) and inspired by a mix of [Qt5](https://www.riverbankcomputing.com/static/Docs/PyQt5/),[GTK](https://pygobject.readthedocs.io/en/latest/) and [tkinter](https://docs.python.org/3/library/tkinter.html) api definition with a touch of personal interpretation ## Quick Test/Try @@ -22,4 +22,15 @@ python3 tests/test.input.py # Press CTRL-C to exit # the logs are written to "session.log" python3 tests/test.ui.002.py +python3 tests/test.ui.003.py +python3 tests/test.ui.004.py ``` +#### Profiling +```shell +python3 -m cProfile -o profiler.txt tests/test.ui.004.py + +# install cprofilev: +# pip3 install cprofilev +cprofilev -f profiler.txt +# open http://127.0.0.1:4000 +``` \ No newline at end of file diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 76e7a9bf..1bcc3fd1 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -54,12 +54,6 @@ class TTkCanvas: def getWidget(self): return self._widget - def move(self, x, y): - npos = TTkHelper.absParentPos(self._widget) - # CuTCore.cuDebug("Move: x:"+str(nx+x)+" y:"+str(ny+y)) - # self._bufPaint['move']={'x':npos.x()+x, 'y':npos.y()+y} - TTkHelper.addPaintBuffer(self) - def resize(self, w, h): # TTkLog.debug(f"CanvasResize:{(w,h)}") self._data = [[]]*h @@ -69,7 +63,6 @@ class TTkCanvas: self._colors[i] = [TTkColor.RST]*w self._width = w self._height = h - TTkHelper.addPaintBuffer(self) def clean(self, pos=(0, 0), size=None): x,y = pos @@ -119,7 +112,6 @@ class TTkCanvas: for i in range(y+1,y+h-1): _set(i, x, "║") _set(i, x+w-1, "║") - TTkHelper.addPaintBuffer(self) def execPaint(self, winw, winh): pass diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 5ba3c900..b4a1ea57 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -31,7 +31,7 @@ class TTkHelper: _focusWidget = None _rootCanvas = None _updateWidget = [] - _paintBuffer = [] + _updateBuffer = [] @staticmethod def addUpdateWidget(widget): @@ -39,31 +39,36 @@ class TTkHelper: TTkHelper._updateWidget.append(widget) @staticmethod - def addPaintBuffer(canvas): + def addUpdateBuffer(canvas): if canvas is not TTkHelper._rootCanvas: - if canvas not in TTkHelper._paintBuffer: - TTkHelper._paintBuffer.append(canvas) + if canvas not in TTkHelper._updateBuffer: + TTkHelper._updateBuffer.append(canvas) @staticmethod def registerRootCanvas(canvas): TTkHelper._rootCanvas = canvas - TTkHelper._paintBuffer = [] + TTkHelper._updateBuffer = [] TTkHelper._updateWidget = [] @staticmethod def paintAll(): if TTkHelper._rootCanvas is None: return - processed = [] - for widget in TTkHelper._updateWidget: - processed.append(widget) + #processed = [] + pushToTerminal = False + for widget in TTkHelper._updateBuffer: widget.paintEvent() + TTkHelper._updateBuffer = [] + for widget in TTkHelper._updateWidget: + pushToTerminal = True + #processed.append(widget) widget.paintChildCanvas() - p = widget.parentWidget() + #p = widget.parentWidget() #if p in TTkHelper._updateWidget and p not in processed: widget.paintNotifyParent() TTkHelper._updateWidget = [] - TTkHelper._rootCanvas.pushToTerminal(0, 0, TTkGlbl.term_w, TTkGlbl.term_h) + if pushToTerminal: + TTkHelper._rootCanvas.pushToTerminal(0, 0, TTkGlbl.term_w, TTkGlbl.term_h) @staticmethod def absPos(widget) -> (int,int): diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index 7113ffc7..f37bbf29 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -33,6 +33,19 @@ from TermTk.TTkCore.canvas import * from TermTk.TTkWidgets.layout import * from TermTk.TTkWidgets.widget import * +class TTkTimer(threading.Thread): + def __init__(self, callback): + threading.Thread.__init__(self) + self.stopped = threading.Event() + self._callback = callback + + def quit(self): + self.stopped.set() + + def run(self): + while not self.stopped.wait(0.05): + self._callback() + class TTk(TTkWidget): running: bool = False events = None @@ -46,6 +59,9 @@ class TTk(TTkWidget): QUIT_EVENT = 0x08 TIME_EVENT = 0x10 + HORIZONTAL = 0x01 + VERTICAL = 0x02 + def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) self.events = queue.Queue() @@ -69,7 +85,8 @@ class TTk(TTkWidget): lbt.Term.registerResizeCb(self._win_resize_cb) threading.Thread(target=self._input_thread, daemon=True).start() - # threading.Timer(30.0, hello) + self._timer = TTkTimer(self._time_event) + self._timer.start() self.running = True lbt.Term.init() @@ -91,6 +108,7 @@ class TTk(TTkWidget): # TTkLog.info(f"Key Event: {kevt}") pass elif evt is TTk.TIME_EVENT: + TTkHelper.paintAll() pass elif evt is TTk.SCREEN_EVENT: self.setGeometry(0,0,TTkGlbl.term_w,TTkGlbl.term_h) @@ -102,11 +120,12 @@ class TTk(TTkWidget): TTkLog.error(f"Unhandled Event {evt}") break - TTkHelper.paintAll() - lbt.Term.exit() pass + def _time_event(self): + self.events.put(TTk.TIME_EVENT) + def _win_resize_cb(self, width, height): TTkGlbl.term_w = int(width) TTkGlbl.term_h = int(height) @@ -129,6 +148,7 @@ class TTk(TTkWidget): def quit(self): self.events.put(TTk.QUIT_EVENT) + self._timer.quit() self.running = False def _SIGSTOP(self, signum, frame): diff --git a/TermTk/TTkWidgets/__init__.py b/TermTk/TTkWidgets/__init__.py index b180b170..d19ac83b 100644 --- a/TermTk/TTkWidgets/__init__.py +++ b/TermTk/TTkWidgets/__init__.py @@ -3,4 +3,5 @@ from .button import * from .layout import * from .widget import * from .window import * +from .logviewer import * from .testwidget import * \ No newline at end of file diff --git a/TermTk/TTkWidgets/button.py b/TermTk/TTkWidgets/button.py index 015887f5..96674aca 100644 --- a/TermTk/TTkWidgets/button.py +++ b/TermTk/TTkWidgets/button.py @@ -27,6 +27,7 @@ from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.widget import * class TTkButton(TTkWidget): + __slots__ = ('_text', '_border', '_pressed') def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) self._text = kwargs.get('text', "" ) diff --git a/TermTk/TTkWidgets/frame.py b/TermTk/TTkWidgets/frame.py index 6cf5745f..b906e370 100644 --- a/TermTk/TTkWidgets/frame.py +++ b/TermTk/TTkWidgets/frame.py @@ -26,14 +26,12 @@ from TermTk.TTkCore.log import TTkLog from .widget import * class TTkFrame(TTkWidget): + __slots__ = ('_border') def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) self._border = kwargs.get('border', True ) if self._border: - self._padt = 1 - self._padb = 1 - self._padl = 1 - self._padr = 1 + self.setPadding(1,1,1,1) def paintEvent(self): if self._border: diff --git a/TermTk/TTkWidgets/layout.py b/TermTk/TTkWidgets/layout.py index 1db94b65..883cf63a 100644 --- a/TermTk/TTkWidgets/layout.py +++ b/TermTk/TTkWidgets/layout.py @@ -171,10 +171,10 @@ class TTkHBoxLayout(TTkLayout): for item in self.children(): if item._sMax: item.setGeometry(newx, newy, item._sMaxVal, h) - newy += item._sMaxVal + newx += item._sMaxVal elif item._sMin: item.setGeometry(newx, newy, item._sMinVal, h) - newy += item._sMinVal + newx += item._sMinVal else: sliceSize = freeWidth//leftWidgets item.setGeometry(newx, newy, sliceSize, h) diff --git a/TermTk/TTkWidgets/logviewer.py b/TermTk/TTkWidgets/logviewer.py new file mode 100644 index 00000000..a0033998 --- /dev/null +++ b/TermTk/TTkWidgets/logviewer.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. \ No newline at end of file diff --git a/TermTk/TTkWidgets/splitter.py b/TermTk/TTkWidgets/splitter.py new file mode 100644 index 00000000..62e03235 --- /dev/null +++ b/TermTk/TTkWidgets/splitter.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.ttk import * +from TermTk.TTkWidgets.widget import * +from TermTk.TTkWidgets.frame import * + +class TTkSplitter(TTkFrame): + __slots__ = ('_widgets', '_orientation', '_splitters', '_selected') + def __init__(self, *args, **kwargs): + TTkFrame.__init__(self, *args, **kwargs) + self._widgets = [] + self._splitters = [] + self._orientation = kwargs.get('orientation' , TTk.HORIZONTAL ) + + def addWidget(self, widget): + # NOTE: Check with the max/min size if the new widget can fit + self._widgets.append(widget) + self._splitters.append(0x10000) + self._rearrange() + + def _rearrange(self): + w, h = self.size() + if self._orientation == TTk.HORIZONTAL: + pass + else: + pass + diff --git a/TermTk/TTkWidgets/testwidget.py b/TermTk/TTkWidgets/testwidget.py index f06ed634..b742048c 100644 --- a/TermTk/TTkWidgets/testwidget.py +++ b/TermTk/TTkWidgets/testwidget.py @@ -49,12 +49,14 @@ class _TestContent(TTkWidget): class TTkTestWidget(TTkFrame): ID = 1 + __slots__ = ('_name') def __init__(self, *args, **kwargs): TTkFrame.__init__(self, *args, **kwargs) #self.setLayout(TTkHBoxLayout()) self._name = f"TestWidget-{TTkTestWidget.ID}" - TTkButton(parent=self, x=1, y=1, width=15, height=3, text=' Test Button') - _TestContent(parent=self, x=1, y=4, width=50, height=50, name=f"content-{self._name}") + t,_,l,_ = self.getPadding() + TTkButton(parent=self, x=l, y=t, width=15, height=3, text=' Test Button') + _TestContent(parent=self, x=l, y=3+t, width=50, height=50, name=f"content-{self._name}") TTkTestWidget.ID+=1 def mousePressEvent(self, evt): diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index f455f2bc..28731b80 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -55,8 +55,15 @@ class TTkWidget: │ └─────────────────────────┘ │ └─────────────────────────────────────────┘ ''' + __slots__ = ( + '_name', '_parent', + '_x', '_y', '_width', '_height', + '_padt', '_padb', '_padl', '_padr', + '_maxw', '_maxh', '_minw', '_minh', + '_focus','_focus_policy', + '_layout', '_canvas') + def __init__(self, *args, **kwargs): - self._childs = [] self._name = kwargs.get('name', None ) self._parent = kwargs.get('parent', None ) @@ -67,15 +74,18 @@ class TTkWidget: self._height = kwargs.get('height', 0 ) self._width, self._height = kwargs.get('size', (self._width, self._height)) - self._padt = kwargs.get('paddingTop', 0 ) - self._padb = kwargs.get('paddingBottom', 0 ) - self._padl = kwargs.get('paddingLeft', 0 ) - self._padr = kwargs.get('paddingRight', 0 ) + padding = kwargs.get('padding', 0 ) + self._padt = kwargs.get('paddingTop', padding ) + self._padb = kwargs.get('paddingBottom', padding ) + self._padl = kwargs.get('paddingLeft', padding ) + self._padr = kwargs.get('paddingRight', padding ) - self._maxw = 0x10000 - self._maxh = 0x10000 - self._minw = 0x00000 - self._minh = 0x00000 + self._maxw = kwargs.get('maxWidth', 0x10000) + self._maxh = kwargs.get('maxHeight', 0x10000) + self._maxw, self._maxh = kwargs.get('maxSize', (self._maxw, self._maxh)) + self._minw = kwargs.get('minWidth', 0x00000) + self._minh = kwargs.get('minHeight', 0x00000) + self._minw, self._minh = kwargs.get('minSize', (self._minw, self._minh)) self._focus = False self._focus_policy = TTkWidget.NoFocus @@ -84,10 +94,14 @@ class TTkWidget: widget = self, width = self._width , height = self._height ) + self.setLayout(TTkLayout()) if self._parent is not None and \ self._parent._layout is not None: self._parent._layout.addWidget(self) + self._parent._layout.update() + + self.update(repaint=True, updateLayout=True) def addLayout(self, l): self._layout = l @@ -120,8 +134,7 @@ class TTkWidget: def move(self, x, y): self._x = x self._y = y - self._canvas.move(self._x, self._y) - self.update() + self.update(repaint=False, updateLayout=False) def resize(self, w, h): self._width = w @@ -132,12 +145,15 @@ class TTkWidget: self._padl, self._padt, self._width - self._padl - self._padr, self._height - self._padt - self._padb) - self.update() + self.update(repaint=True, updateLayout=True) def setGeometry(self, x, y, w, h): self.resize(w, h) self.move(x, y) + def getPadding(self): + return self._padt, self._padb, self._padl, self._padr + def setPadding(self, top, bottom, left, right): self._padt = top self._padb = bottom @@ -165,8 +181,6 @@ class TTkWidget: x, y = evt.x, evt.y lx,ly,lw,lh =layout.geometry() # opt of bounds - #x-=lx - #y-=ly if x<0 or x>lw or y<0 or y>lh: return True for i in range(layout.count()): @@ -264,7 +278,7 @@ class TTkWidget: self._padl, self._padt, self._width - self._padl - self._padr, self._height - self._padt - self._padb) - self._layout.update() + self.update(repaint=True, updateLayout=True) def layout(self): return self._layout @@ -324,9 +338,11 @@ class TTkWidget: def setMinimumHeight(self, minh): self._minh = minh def setMinimumWidth(self, minw): self._minw = minw - def update(self): + def update(self, repaint=True, updateLayout=False): + if repaint: + TTkHelper.addUpdateBuffer(self) TTkHelper.addUpdateWidget(self) - if self._layout is not None: + if updateLayout and self._layout is not None: self._layout.update() def setFocus(self): @@ -334,7 +350,7 @@ class TTkWidget: if tmp is not None: tmp.clearFocus() tmp.focusOutEvent() - tmp.update() + tmp.update(repaint=True, updateLayout=False) TTkHelper.setFocus(self) self._focus = True self.focusInEvent() diff --git a/TermTk/TTkWidgets/window.py b/TermTk/TTkWidgets/window.py index 1105aeb1..81881c6b 100644 --- a/TermTk/TTkWidgets/window.py +++ b/TermTk/TTkWidgets/window.py @@ -29,6 +29,7 @@ from TermTk.TTkWidgets.widget import TTkWidget class TTkWindow(TTkWidget): + __slots__ = ('_title', '_mouseDelta', '_draggable', '_resizable') def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) self._title = kwargs.get('title' , 0 ) diff --git a/tests/test.ui.004.py b/tests/test.ui.004.py index 6070952a..77a1d4b2 100755 --- a/tests/test.ui.004.py +++ b/tests/test.ui.004.py @@ -40,11 +40,15 @@ ttk.TTkTestWidget(parent=win2, border=False) win3 = ttk.TTkWindow(parent=root,pos = (20,5), size=(60,20), title="Test Window 3", border=True) win3.setLayout(ttk.TTkHBoxLayout()) -ttk.TTkTestWidget(parent=win3,border=True) +ttk.TTkTestWidget(parent=win3, border=True, maxWidth=30, minWidth=20) rightFrame = ttk.TTkFrame(parent=win3,border=True) rightFrame.setLayout(ttk.TTkVBoxLayout()) -ttk.TTkTestWidget(parent=rightFrame,border=True) -ttk.TTkFrame(parent=rightFrame,border=True) +ttk.TTkTestWidget(parent=rightFrame,border=True, maxHeight=11, minHeight=6) +bottomrightframe = ttk.TTkFrame(parent=rightFrame,border=True) + +win4 = ttk.TTkWindow(parent=bottomrightframe, pos = (3,3), size=(40,20), title="Test Window 4", border=True) +win4.setLayout(ttk.TTkHBoxLayout()) +ttk.TTkTestWidget(parent=win4, border=False) root.mainloop() \ No newline at end of file