# 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__ = ['TTkAppTemplate']
from dataclasses import dataclass
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.canvas import TTkCanvas
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
from TermTk.TTkLayouts import TTkLayout, TTkGridLayout
from TermTk.TTkWidgets.container import TTkWidget, TTkContainer
from TermTk.TTkWidgets.menubar import TTkMenuBarLayout
[docs]
class TTkAppTemplate(TTkContainer):
''' TTkAppTemplate Layout:
::
App Template Layout
┌─────────────────────────────────┐
│ Header │
├─────────┬──────────────┬────────┤ H
│ │ Top │ │
│ ├──────────────┤ │ T
│ │ │ │
│ Right │ Main │ Left │
│ │ Center │ │
│ │ │ │
│ ├──────────────┤ │ B
│ │ Bottom │ │
├─────────┴──────────────┴────────┤ F
│ Footer │
└─────────────────────────────────┘
R L
'''
class Position(int):
''' This Class enumerate the different panels available in the layout of :py:class:`TTkAppTemplate`
.. autosummary::
MAIN
TOP
BOTTOM
LEFT
RIGHT
CENTER
HEADER
FOOTER
'''
MAIN = TTkK.CENTER
'''Main'''
TOP = TTkK.TOP
'''Top'''
BOTTOM = TTkK.BOTTOM
'''Bottom'''
LEFT = TTkK.LEFT
'''Left'''
RIGHT = TTkK.RIGHT
'''Right'''
CENTER = TTkK.CENTER
'''Center'''
HEADER = TTkK.HEADER
'''Header'''
FOOTER = TTkK.FOOTER
'''Footer'''
MAIN = Position.MAIN
''':py:class:`Position.MAIN`'''
TOP = Position.TOP
''':py:class:`Position.TOP`'''
BOTTOM = Position.BOTTOM
''':py:class:`Position.BOTTOM`'''
LEFT = Position.LEFT
''':py:class:`Position.LEFT`'''
RIGHT = Position.RIGHT
''':py:class:`Position.RIGHT`'''
CENTER = Position.CENTER
''':py:class:`Position.CENTER`'''
HEADER = Position.HEADER
''':py:class:`Position.HEADER`'''
FOOTER = Position.FOOTER
''':py:class:`Position.FOOTER`'''
@dataclass(frozen=False)
class _Panel:
# It's either item or widget
item: TTkLayout = None
widget: TTkWidget = None
title: TTkString = None
menubar: TTkMenuBarLayout = None
size: int = 0
border: bool = True
fixed: bool = False
def setGeometry(self,x,y,w,h):
if it := self.item:
it.setGeometry(x,y,w,h)
elif wid := self.widget:
wid.setGeometry(x,y,w,h)
def isVisible(self):
if self.widget:
return self.widget.isVisible()
return True
def geometry(self):
if it := self.item:
return it.geometry()
if wid := self.widget:
return wid.geometry()
return (0,0,0,0)
def getSize(self):
if it := self.item:
return it.size()
if wid := self.widget:
return wid.size()
return (0,0)
def minimumWidth(self):
if it := self.item:
return it.minimumWidth()
if wid := self.widget:
return wid.minimumWidth()
return 0
def minimumHeight(self):
if it := self.item:
return it.minimumHeight()
if wid := self.widget:
return wid.minimumHeight()
return 0
def maximumWidth(self):
if it := self.item:
return it.maximumWidth()
if wid := self.widget:
return wid.maximumWidth()
return 0x10000
def maximumHeight(self):
if it := self.item:
return it.maximumHeight()
if wid := self.widget:
return wid.maximumHeight()
return 0x10000
__slots__ = ('_panels', '_splitters', '_menubarLines', '_selected'
#Signals
)
def __init__(self,
border=False,
**kwargs) -> None:
self._panels = {
TTkAppTemplate.MAIN : TTkAppTemplate._Panel(item=TTkLayout(), border=border) ,
TTkAppTemplate.TOP : None ,
TTkAppTemplate.BOTTOM : None ,
TTkAppTemplate.LEFT : None ,
TTkAppTemplate.RIGHT : None ,
TTkAppTemplate.HEADER : None ,
TTkAppTemplate.FOOTER : None }
self._splitters = {
TTkAppTemplate.TOP : None ,
TTkAppTemplate.BOTTOM : None ,
TTkAppTemplate.LEFT : None ,
TTkAppTemplate.RIGHT : None ,
TTkAppTemplate.HEADER : None ,
TTkAppTemplate.FOOTER : None }
self._menubarLines = {
TTkAppTemplate.MAIN : None ,
TTkAppTemplate.TOP : None ,
TTkAppTemplate.BOTTOM : None ,
TTkAppTemplate.LEFT : None ,
TTkAppTemplate.RIGHT : None ,
TTkAppTemplate.HEADER : None ,
TTkAppTemplate.FOOTER : None }
self._selected = None
super().__init__( **kwargs)
self.layout().addItem(self._panels[TTkAppTemplate.MAIN].item)
self._updateGeometries(force=True)
self.setFocusPolicy(TTkK.ClickFocus)
[docs]
def setItem(self,
item:TTkLayout, position:Position=Position.MAIN,
size:int=None, title:TTkString="",
border:bool=None, fixed:bool=None) -> None:
'''
Place the :py:class:`TTkLayout` in the :py:class:`TTkAppTemplate`'s panel identified by its :py:class:`Position`
:param item:
:type item: :py:class:`TTkLayout`
:param position: defaults to :py:class:`Position.MAIN`
:type position: :py:class:`Position`, optional
:param size: defaults to None
:type size: int, optional
:param title: defaults to ""
:type title: :py:class:`TTkString`, optional
:param border: defaults to True
:type border: bool, optional
:param fixed: defaults to False
:type fixed: bool, optional
'''
if not self._panels[position]:
self._panels[position] = TTkAppTemplate._Panel()
if wid:=self._panels[position].widget:
self.layout().removeWidget(wid)
self._panels[position].widget = None
if it:=self._panels[position].item:
self.layout().removeItem(it)
self._panels[position].item = None
if item:
self._panels[position].item = item
self.layout().addItem(item)
if border!=None:
self._panels[position].border = border
if fixed != None:
self._panels[position].fixed = fixed
self._panels[position].title = TTkString(title)
self._panels[position].size = ( size if size is not None else
item.minimumWidth() if position in (TTkAppTemplate.LEFT,TTkAppTemplate.RIGHT) else
item.minimumHeight() )
self._updateGeometries(force=True)
[docs]
def setTitle(self, position:Position=Position.MAIN, title:str=""):
'''Set the title of the panel identified by the "position"
:param position: defaults to :py:class:`Position.MAIN`
:type position: :py:class:`Position`, optional
:param title: defaults to ""
:type title: :py:class:`TTkString`, optional
'''
if not self._panels[position]: return
self._panels[position].title = TTkString(title) if title else ""
self._updateGeometries(force=True)
[docs]
def setBorder(self, border=True, position=MAIN) -> None:
if not self._panels[position]:
self._panels[position] = TTkAppTemplate._Panel()
self._panels[position].border = border
self._updateGeometries(force=True)
[docs]
def setFixed(self, fixed=False, position=MAIN) -> None:
if not self._panels[position]:
self._panels[position] = TTkAppTemplate._Panel()
self._panels[position].fixed = fixed
self._updateGeometries(force=True)
def resizeEvent(self, width: int, height: int) -> None:
self._updateGeometries()
def focusOutEvent(self) -> None:
self._selected = None
self.update()
def mouseReleaseEvent(self, evt: TTkMouseEvent) -> bool:
self._selected = None
self.update()
return True
def mousePressEvent(self, evt: TTkMouseEvent) -> bool:
self._selected = []
self._updateGeometries()
spl = self._splitters
pns = self._panels
for loc in (TTkAppTemplate.TOP, TTkAppTemplate.BOTTOM, TTkAppTemplate.HEADER, TTkAppTemplate.FOOTER):
if (s:=spl[loc]) and not pns[loc].fixed and (p:=s['pos'])[1]==evt.y and p[0] <= evt.x <=p[0]+s['size']:
self._selected.append(loc)
for loc in (TTkAppTemplate.LEFT, TTkAppTemplate.RIGHT):
if (s:=spl[loc]) and not pns[loc].fixed and (p:=s['pos'])[0]==evt.x and p[1] <= evt.y <=p[1]+s['size']:
self._selected.append(loc)
return True
def mouseDragEvent(self, evt: TTkMouseEvent) -> bool:
if not self._selected: return False
pns = self._panels
for loc in self._selected:
x,y,w,h = (p:=pns[loc]).geometry()
if loc == TTkAppTemplate.LEFT:
p.size = evt.x-x
elif loc == TTkAppTemplate.RIGHT:
p.size = w+x-evt.x
elif loc in (TTkAppTemplate.HEADER, TTkAppTemplate.TOP):
p.size = evt.y-y
else:
p.size = h+y-evt.y-1
self._updateGeometries()
return True
def minimumWidth(self) -> int:
pns = self._panels
# Header and Footer sizes
mh=mf=0
if (p:=pns[TTkAppTemplate.HEADER]) and p.isVisible(): mh = p.minimumWidth()
if (p:=pns[TTkAppTemplate.FOOTER]) and p.isVisible(): mf = p.minimumWidth()
# Center Right,Left sizes
mcr=mcl=0
if (p:=pns[TTkAppTemplate.RIGHT]) and p.isVisible(): mcr = p.minimumWidth() + ( 1 if p.border else 0 )
if (p:=pns[TTkAppTemplate.LEFT ]) and p.isVisible(): mcl = p.minimumWidth() + ( 1 if p.border else 0 )
# Center Top,Bottom sizes
mct=mcb=0
if (p:=pns[TTkAppTemplate.TOP ]) and p.isVisible(): mct = p.minimumWidth()
if (p:=pns[TTkAppTemplate.BOTTOM]) and p.isVisible(): mcb = p.minimumWidth()
mcm = (p:=pns[TTkAppTemplate.MAIN]).minimumWidth()
return max(mh, mf, mcr+mcl+max(mct, mcb, mcm)) + (2 if p.border else 0)
def maximumWidth(self) -> int:
pns = self._panels
# Header and Footer sizes
mh=mf=0x10000
if (p:=pns[TTkAppTemplate.HEADER]) and p.isVisible(): mh = p.maximumWidth()
if (p:=pns[TTkAppTemplate.FOOTER]) and p.isVisible(): mf = p.maximumWidth()
# Center Right,Left sizes
mcr=mcl=0
if (p:=pns[TTkAppTemplate.RIGHT]) and p.isVisible(): mcr = p.maximumWidth() + ( 1 if p.border else 0 )
if (p:=pns[TTkAppTemplate.LEFT ]) and p.isVisible(): mcl = p.maximumWidth() + ( 1 if p.border else 0 )
# Center Top,Bottom sizes
mct=mcb=0x10000
if (p:=pns[TTkAppTemplate.TOP ]) and p.isVisible(): mct = p.maximumWidth()
if (p:=pns[TTkAppTemplate.BOTTOM]) and p.isVisible(): mcb = p.maximumWidth()
mcm = (p:=pns[TTkAppTemplate.MAIN]).maximumWidth()
return min(mh, mf, mcr+mcl+min(mct, mcb, mcm)) + (2 if p.border else 0)
def minimumHeight(self) -> int:
pns = self._panels
# Retrieve all the panels parameters and hide the menubar if required
def _processPanel(_loc):
_p = pns[_loc]
if not (_p and _p.isVisible()):
return None, 0, False, False
return _p, _p.minimumHeight(), _p.border, _p.menubar
ph,mh,bh,menh = _processPanel(TTkAppTemplate.HEADER)
pl,ml,bl,menl = _processPanel(TTkAppTemplate.LEFT)
pr,mr,br,menr = _processPanel(TTkAppTemplate.RIGHT)
pt,mt,bt,ment = _processPanel(TTkAppTemplate.TOP)
pm,mm,bm,menm = _processPanel(TTkAppTemplate.MAIN)
pb,mb,bb,menb = _processPanel(TTkAppTemplate.BOTTOM)
pf,mf,bf,menf = _processPanel(TTkAppTemplate.FOOTER)
# Adjust the sizes based on the menubar and the borders
if menh and not (bm): mh += 1
if menl and not (bh or (bm and ph==None)): ml += 1
if menr and not (bh or (bm and ph==None)): mr += 1
if ment and not (bh or (bm and ph==None)): mt += 1
if menm and not (bt or (bh and pt==None) or (bm and pt==ph==None)): mm += 1
if menb and not (bb): mb += 1
if menf and not (bf): mf += 1
return mh+mf+max(mr ,ml, mm+mt+mb ) + ( 2 if bm else 0 )
def maximumHeight(self) -> int:
pns = self._panels
# Retrieve all the panels parameters and hide the menubar if required
def _processPanel(_loc):
_p = pns[_loc]
if not (_p and _p.isVisible()):
return None, 0, False, False
return _p, _p.maximumHeight(), _p.border, _p.menubar
ph,mh,bh,menh = _processPanel(TTkAppTemplate.HEADER)
pl,ml,bl,menl = _processPanel(TTkAppTemplate.LEFT)
pr,mr,br,menr = _processPanel(TTkAppTemplate.RIGHT)
pt,mt,bt,ment = _processPanel(TTkAppTemplate.TOP)
pm,mm,bm,menm = _processPanel(TTkAppTemplate.MAIN)
pb,mb,bb,menb = _processPanel(TTkAppTemplate.BOTTOM)
pf,mf,bf,menf = _processPanel(TTkAppTemplate.FOOTER)
# Adjust the sizes based on the menubar and the borders
if menh and not (bm): mh += 1
if menl and not (bh or (bm and ph==None)): ml += 1
if menr and not (bh or (bm and ph==None)): mr += 1
if ment and not (bh or (bm and ph==None)): mt += 1
if menm and not (bt or (bh and pt==None) or (bm and pt==ph==None)): mm += 1
if menb and not (bb): mb += 1
if menf and not (bf): mf += 1
# Those panels cannot have null size
if not mm: mm=0x10000
if not ml: ml=0x10000
if not mr: mr=0x10000
return mh+mf+min(mr ,ml, mm+mt+mb ) + ( 2 if bm else 0 )
def _updateGeometries(self, force:bool=False) -> None:
w,h = self.size()
if w<=0 or h<=0 or (not force and not self.isVisibleAndParent()): return
pns = self._panels
spl = self._splitters
mbl = self._menubarLines
# Retrieve all the panels parameters and hide the menubar if required
def _processPanel(_loc):
_p = pns[_loc]
if not (_p and _p.isVisible()):
if _p and (_menu:=_p.menubar):
_menu.setGeometry(-1,-1,0,0)
return None, 0, 0x1000, 0, True, 0, None
_min = _p.minimumHeight()
_max = _p.maximumHeight()
_size = min(max(_p.size,_min),_max)
_fixed = _p.fixed
_border = 1 if _p.border else 0
return _p, _min, _max, _size, _fixed, _border, _p.menubar
pt,ptmin,ptmax,st,ft,bt,mt = _processPanel(TTkAppTemplate.TOP)
pb,pbmin,pbmax,sb,fb,bb,mb = _processPanel(TTkAppTemplate.BOTTOM)
ph,phmin,phmax,sh,fh,bh,mh = _processPanel(TTkAppTemplate.HEADER)
pf,pfmin,pfmax,sf,ff,bf,mf = _processPanel(TTkAppTemplate.FOOTER)
pl,plmin,plmax,sl,fl,bl,ml = _processPanel(TTkAppTemplate.LEFT)
pr,prmin,prmax,sr,fr,br,mr = _processPanel(TTkAppTemplate.RIGHT)
# Main Boundaries
pm=pns[TTkAppTemplate.MAIN]
mm=pm.menubar
mmaxw = pm.maximumWidth()
mminw = pm.minimumWidth()
mmaxh = pm.maximumHeight()
mminh = pm.minimumHeight()
bm = 1 if pm.border else 0
w-=(bm<<1)+bl+br
h-=(bm<<1)+bt+bb+bh+bf
adjm =adjt =adjb =adjh =adjf =adjl =adjr =0 # Adjustment if we need extra space for the menubar
adjtf=adjbf=adjhf=adjff=adjlf=adjrf=adjmf=0 # 1 if the menu is on a single line bar
# Retune the max/min sizes and adjustment based on the menubar,border and visible widgets
# Check if there is a splitter to be used for the menubar
# Fix bar status if the menu is on the closest splitter
if pt and mt: adjt,adjtf = ( 0, fh if _phbh else True ) if (_phbh:=(ph and bh)) or (not ph and bm) else (1,True) ; st+=adjt ; ptmin+=adjt ; ptmax+=adjt
if pb and mb: adjb,adjbf = ( 0, fb ) if bb else (1,True) ; sb+=adjb ; pbmin+=adjb ; pbmax+=adjb
if ph and mh: adjh,adjhf = ( 0, 0 ) if bm else (1,True) ; sh+=adjh ; phmin+=adjh ; phmax+=adjh
if pf and mf: adjf,adjff = ( 0, ff ) if bf else (1,True) ; sf+=adjf ; pfmin+=adjf ; pfmax+=adjf
if pl and ml: adjl,adjlf = ( 0, fh if _phbh else True ) if (_phbh:=(ph and bh)) or (not ph and bm) else (1,True) ; plmin+=adjl ; plmax+=adjl
if pr and mr: adjr,adjrf = ( 0, fh if _phbh else True ) if (_phbh:=(ph and bh)) or (not ph and bm) else (1,True) ; prmin+=adjr ; prmax+=adjr
if mm: adjm,adjmf = ( 0, ft if (pt and bt) else fh if _phbh else True) if (_phbh:=(ph and bh)) or (not pt and ph and bh) or (not pt and not ph and bm) else (1,True) ; mminh+=adjm ; mmaxh+=adjm
# check horizontal sizes
if not (mminw <= (newszw:=(w-sl-sr)) <= mmaxw):
# the main width does not fit
# we need to move the (R,L) splitters
# * to avoid extra complexity,
# Let's resize the right panel first
# and adjust the left one to allows the
# main panel to fit again
if newszw < mminw:
if pr: pr.size = sr = max(prmin, w-mminw-sl) ; newszw=w-sl-sr
if newszw < mminw and pl: pl.size = sl = max(plmin, w-mminw-sr) ; newszw=w-sl-sr
else:
if pr: pr.size = sr = min(prmax, w-mmaxw-sl) ; newszw=w-sl-sr
if newszw > mmaxw and pl: pl.size = sl = min(plmax, w-mmaxw-sr) ; newszw=w-sl-sr
# check vertical sizes
if not (mminh <= (newszh:=(h-st-sb-sh-sf)) <= mmaxh):
# almost same as before except that there are 4 panels to be considered instead of 2
if newszh < mminh:
if pf: pf.size = sf = max(pfmin, h-mminh-sb-st-sh) ; newszh=h-st-sb-sh-sf
if newszh < mminh and pb: pb.size = sb = max(pbmin, h-mminh-sf-st-sh) ; newszh=h-st-sb-sh-sf
if newszh < mminh and pt: pt.size = st = max(ptmin, h-mminh-sf-sb-sh) ; newszh=h-st-sb-sh-sf
if newszh < mminh and ph: ph.size = sh = max(phmin, h-mminh-sf-sb-st) ; newszh=h-st-sb-sh-sf
else:
if pf: pf.size = sf = min(pfmax, h-mmaxh-sb-st-sh) ; newszh=h-st-sb-sh-sf
if newszh > mmaxh and pb: pb.size = sb = min(pbmax, h-mmaxh-sf-st-sh) ; newszh=h-st-sb-sh-sf
if newszh > mmaxh and pt: pt.size = st = min(ptmax, h-mmaxh-sf-sb-sh) ; newszh=h-st-sb-sh-sf
if newszh > mmaxh and ph: ph.size = sh = min(phmax, h-mmaxh-sf-sb-st) ; newszh=h-st-sb-sh-sf
# Resize any panel to the proper dimension
w+=bl+br
h+=bt+bb+bh+bf
def _setGeometries(_loc, _p, _x,_y,_w,_h,_mb,_adj,_fix):
if _mb:
if _fix: # Fixed
styleToMerge = {'default':{'glyphs':('├','─','┤','┄','┄','▶')}}
else:
styleToMerge = {'default':{'glyphs':('╞','═','╡','┄','┄','▶')}}
if not _adj:
mbl[_loc] = None
else:
mbl[_loc] = {'pos':(_x,_y),'text':f"┄{'─'*(_w-2)}┄"}
for m in _mb._menus(TTkK.LEFT_ALIGN): m.mergeStyle(styleToMerge)
for m in _mb._menus(TTkK.RIGHT_ALIGN): m.mergeStyle(styleToMerge)
for m in _mb._menus(TTkK.CENTER_ALIGN): m.mergeStyle(styleToMerge)
_moff = 0 if _adj else -1
_mb.setGeometry(_x+1,_y+_moff,_w-2,1)
_p.setGeometry(_x,_y+_adj,_w,_h-_adj)
# x y w h _mb _adj _fix
_setGeometries( TTkAppTemplate.MAIN , pm, bm+sl+bl , bm+sh+bh+st+bt , newszw , newszh , mm, adjm, adjmf)
if pl: _setGeometries(TTkAppTemplate.LEFT , pl, bm , bm+sh+bh , sl , h-sh-bh-sf-bf , ml, adjl, adjlf)
if pr: _setGeometries(TTkAppTemplate.RIGHT , pr, bm+sl+bl+newszw+br , bm+sh+bh , sr , h-sh-bh-sf-bf , mr, adjr, adjrf)
if ph: _setGeometries(TTkAppTemplate.HEADER , ph, bm , bm , w , sh , mh, adjh, adjhf)
if pt: _setGeometries(TTkAppTemplate.TOP , pt, bm+sl+bl , bm+sh+bh , newszw , st , mt, adjt, adjtf)
if pb: _setGeometries(TTkAppTemplate.BOTTOM , pb, bm+sl+bl , bm+sh+bh+st+bt+newszh+bb , newszw , sb , mb, adjb, adjbf)
if pf: _setGeometries(TTkAppTemplate.FOOTER , pf, bm , bm+sh+bh+st+bt+newszh+bb+sb+bf , w , sf , mf, adjf, adjff)
# Define Splitter geometries
w,h = self.size()
spl[TTkAppTemplate.HEADER] = None if not bh else {'pos':(0 , bm+sh ) ,'size':w , 'fixed':fh , 'panel': ph }
spl[TTkAppTemplate.FOOTER] = None if not bf else {'pos':(0 , bm+sh+bh+st+bt+newszh+bb+sb) ,'size':w , 'fixed':ff , 'panel': pf }
ca = sh + (bm if ph else 0 )
cb = bm+sh+bh+st+bt+newszh+bb+sb + (bf if pf else bm)
spl[TTkAppTemplate.LEFT] = None if not bl else {'pos':(bm+sl , ca ) ,'size':cb-ca , 'fixed':fl , 'panel': pl }
spl[TTkAppTemplate.RIGHT] = None if not br else {'pos':(bm+sl+bl+newszw , ca ) ,'size':cb-ca , 'fixed':fr , 'panel': pr }
ca = sl + (bm if pl else 0 )
cb = bm+sl+bl+newszw + (br if pr else bm)
spl[TTkAppTemplate.TOP] = None if not bt else {'pos':(ca , bm+sh+bh+st ) ,'size':cb-ca , 'fixed':ft , 'panel': pt }
spl[TTkAppTemplate.BOTTOM] = None if not bb else {'pos':(ca , bm+sh+bh+st+bt+newszh) ,'size':cb-ca , 'fixed':fb , 'panel': pb }
self.update()
def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False) -> None:
if updateLayout:
self._updateGeometries()
super().update(repaint=repaint,updateLayout=updateLayout,updateParent=updateParent)
#def layout(self):
# return self._panels[TTkAppTemplate.MAIN].item
#def setLayout(self, layout):
# self._panels[TTkAppTemplate.MAIN].item = layout
def paintEvent(self, canvas: TTkCanvas) -> None:
w,h = self.size()
pns = self._panels
spl = self._splitters
mbl = self._menubarLines
if b:=pns[TTkAppTemplate.MAIN].border:
canvas.drawBox(pos=(0,0), size=(w,h))
selectColor = TTkColor.fg('#88FF00')
# hline = ('╞','═','╡')
# vline = ('╥','║','╨')
def drawVLine(sp, color=TTkColor.RST):
_x,_y = sp['pos']
_w,_h = 1,sp['size']
chs = ('│','┬','┴','╿','╽') if sp['fixed'] else ('║','╥','╨','┇','┇')
canvas.fill(pos=(_x,_y), size=(_w,_h), color=color, char=chs[0] )
canvas.drawChar(pos=(_x,_y), color=color, char=chs[1]if b and _y==0 else chs[3])
canvas.drawChar(pos=(_x,_y+_h-1), color=color, char=chs[2]if b and _y+_h==h else chs[4])
def drawHLine(sp, color=TTkColor.RST):
_x,_y = sp['pos']
_w,_h = sp['size'],1
chs = ('─','├','┤','╾','╼') if sp['fixed'] else ('═','╞','╡','╍','╍')
canvas.fill(pos=(_x,_y), size=(_w,_h), color=color, char=chs[0] )
canvas.drawChar(pos=(_x,_y), color=color, char=chs[1]if b and _x==0 else chs[3])
canvas.drawChar(pos=(_x+_w-1,_y), color=color, char=chs[2]if b and _x+_w==w else chs[4])
if _title:=sp['panel'].title:
_l = min(w-2,_title.termWidth())
_tx = (_w-_l)//2
canvas.drawChar(pos=(_x+_tx,_y), color=color, char=chs[2])
canvas.drawChar(pos=(_x+_tx+_l+1,_y),color=color, char=chs[1])
canvas.drawTTkString(pos=(_x+_tx+1,_y),text=_title,width=_l)
# Draw the 4 splittters
if (sp:=spl[TTkAppTemplate.HEADER]) : drawHLine(sp, color=selectColor if self._selected and TTkAppTemplate.HEADER in self._selected else TTkColor.RST)
if (sp:=spl[TTkAppTemplate.FOOTER]) : drawHLine(sp, color=selectColor if self._selected and TTkAppTemplate.FOOTER in self._selected else TTkColor.RST)
if (sp:=spl[TTkAppTemplate.LEFT]) : drawVLine(sp, color=selectColor if self._selected and TTkAppTemplate.LEFT in self._selected else TTkColor.RST)
if (sp:=spl[TTkAppTemplate.RIGHT]) : drawVLine(sp, color=selectColor if self._selected and TTkAppTemplate.RIGHT in self._selected else TTkColor.RST)
if (sp:=spl[TTkAppTemplate.TOP]) : drawHLine(sp, color=selectColor if self._selected and TTkAppTemplate.TOP in self._selected else TTkColor.RST)
if (sp:=spl[TTkAppTemplate.BOTTOM]) : drawHLine(sp, color=selectColor if self._selected and TTkAppTemplate.BOTTOM in self._selected else TTkColor.RST)
# Draw the 12 intersect
def drawIntersect(sph,spv,chs):
if sph and spv:
x = spv['pos'][0]
y = sph['pos'][1]
ch = chs[( 0 if sph['fixed'] else 0x01 ) | ( 0 if spv['fixed'] else 0x02 )]
canvas.drawChar(pos=(x,y), char=ch)
drawIntersect(spl[TTkAppTemplate.HEADER], spl[TTkAppTemplate.LEFT] , ('┬','╤','╥','╦'))
drawIntersect(spl[TTkAppTemplate.HEADER], spl[TTkAppTemplate.RIGHT], ('┬','╤','╥','╦'))
drawIntersect(spl[TTkAppTemplate.FOOTER], spl[TTkAppTemplate.LEFT] , ('┴','╧','╨','╩'))
drawIntersect(spl[TTkAppTemplate.FOOTER], spl[TTkAppTemplate.RIGHT], ('┴','╧','╨','╩'))
drawIntersect(spl[TTkAppTemplate.TOP ], spl[TTkAppTemplate.LEFT] , ('├','╞','╟','╠'))
drawIntersect(spl[TTkAppTemplate.TOP ], spl[TTkAppTemplate.RIGHT], ('┤','╡','╢','╣'))
drawIntersect(spl[TTkAppTemplate.BOTTOM], spl[TTkAppTemplate.LEFT] , ('├','╞','╟','╠'))
drawIntersect(spl[TTkAppTemplate.BOTTOM], spl[TTkAppTemplate.RIGHT], ('┤','╡','╢','╣'))
# Draw extra MenuBar Lines if there is no border to place them
for l in mbl:
if mb:=mbl[l]:
canvas.drawText(pos=mb['pos'],text=mb['text'])
return super().paintEvent(canvas)