Browse Source

chore: responsive tab close button (#609)

602-ttktextwrap-has-very-slow-performance-for-large-documents
Pier CeccoPierangioliEugenio 2 weeks ago committed by GitHub
parent
commit
19ab5df32c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 18
      apps/ttkode/ttkode/app/ttkode.py
  2. 6
      demo/showcase/tab.py
  3. 18
      libs/pyTermTk/TermTk/TTkCore/ttk.py
  4. 10
      libs/pyTermTk/TermTk/TTkWidgets/container.py
  5. 11
      libs/pyTermTk/TermTk/TTkWidgets/rootcontainer.py
  6. 80
      libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py
  7. 13
      libs/pyTermTk/TermTk/TTkWidgets/widget.py

18
apps/ttkode/ttkode/app/ttkode.py

@ -75,9 +75,23 @@ class _TextDocument(ttk.TextDocumentHighlight):
def getTabButtonStyle(self) -> dict:
if self._changedStatus:
return {'default':{'closeGlyph':''}}
return {
'default':{
'closeGlyph': {
'default':'',
'hovered':''
}
}
}
else:
return {'default':{'closeGlyph':''}}
return {
'default':{
'closeGlyph': {
'default':'',
'hovered':''
}
}
}
def _handleContentChanged(self) -> None:
'''A signal is emitted when the file status change, marking it as modified or not'''

6
demo/showcase/tab.py

@ -53,6 +53,12 @@ def demoTab(root=None, border=True):
tabWidget1.addMenu("ZZ", ttk.TTkK.RIGHT)
tabWidget1.addMenu("KK", ttk.TTkK.RIGHT)
@ttk.pyTTkSlot(int)
def _reportClose(num:int):
tabWidget1.removeTab(num)
tabWidget1.tabCloseRequested.connect(_reportClose)
return tabWidget1
def main():

18
libs/pyTermTk/TermTk/TTkCore/ttk.py

@ -320,20 +320,15 @@ class TTk(_TTkRootContainer):
# - Drag
# - Release
focusWidget = self._getFocusWidget()
if ( focusWidget is not None and
( mevt.evt == TTkK.Drag or
mevt.evt == TTkK.Release ) and
pendingReleaseWidget = self._getPendingMouseReleaseWidget()
if ( pendingReleaseWidget is not None and
mevt.evt in (TTkK.Drag,TTkK.Release) and
not TTkHelper.isDnD() ) :
x,y = TTkHelper.absPos(focusWidget)
x,y = TTkHelper.absPos(pendingReleaseWidget)
nmevt = mevt.clone(pos=(mevt.x-x, mevt.y-y))
focusWidget.mouseEvent(nmevt)
pendingReleaseWidget.mouseEvent(nmevt)
else:
# Sometimes the release event is not retrieved
if ( focusWidget and
focusWidget._pendingMouseRelease and
not TTkHelper.isDnD() ):
focusWidget.mouseEvent(mevt.clone(evt=TTkK.Release))
focusWidget._pendingMouseRelease = False
# Adding this Crappy logic to handle a corner case in the drop routine
# where the mouse is leaving any widget able to handle the drop event
if not self.mouseEvent(mevt):
@ -347,6 +342,7 @@ class TTk(_TTkRootContainer):
# Clean the Drag and Drop in case of mouse release
if mevt.evt == TTkK.Release:
TTkHelper.dndEnd()
self._setPendingMouseReleaseWidget(None)
def _time_event(self):
# Event.{wait and clear} should be atomic,

10
libs/pyTermTk/TermTk/TTkWidgets/container.py

@ -155,6 +155,16 @@ class TTkContainer(TTkWidget):
self._layout.setParent(self)
self.update(updateLayout=True)
def _getPendingMouseReleaseWidget(self) -> Optional[TTkWidget]:
if (_pw:=self.parentWidget()):
return _pw._getPendingMouseReleaseWidget()
return None
def _setPendingMouseReleaseWidget(self, widget:Optional[TTkWidget]) -> None:
if not (_pw:=self.parentWidget()):
return
_pw._setPendingMouseReleaseWidget(widget)
def _getFocusWidget(self) -> Optional[TTkWidget]:
if (_pw:=self.parentWidget()):
return _pw._getFocusWidget()

11
libs/pyTermTk/TermTk/TTkWidgets/rootcontainer.py

@ -71,17 +71,28 @@ class _TTkRootContainer(TTkContainer):
consumes the event, ensuring focus loops back to the first/last focusable widget.
'''
__slots__ = (
'_pendingMouseReleaseWidget',
'_focusWidget',
'_overlay')
_pendingMouseReleaseWidget:Optional[TTkWidget]
_focusWidget:Optional[TTkWidget]
_overlay:List[_TTkOverlay]
def __init__(self, **kwargs) -> None:
self._pendingMouseReleaseWidget = None
self._focusWidget = None
self._overlay = []
super().__init__(**kwargs)
def _getPendingMouseReleaseWidget(self) -> Optional[TTkWidget]:
return self._pendingMouseReleaseWidget
def _setPendingMouseReleaseWidget(self, widget:Optional[TTkWidget]) -> None:
if self._pendingMouseReleaseWidget is widget:
return
self._pendingMouseReleaseWidget = widget
def _getFocusWidget(self) -> Optional[TTkWidget]:
'''
Returns the currently focused widget.

80
libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py

@ -172,6 +172,10 @@ _tabStyle:Dict[str,Any] = {
'default': {'color': TTkColor.fgbg("#dddd88","#000044"),
'bgColor': TTkColor.fgbg("#000000","#8888aa"),
'borderColor': TTkColor.RST,
'closeColor': {
'default' : TTkColor.CYAN,
'hovered' : TTkColor.YELLOW
},
'borderHighlightColors': {
'main' : TTkColor.fg('#00FFFF'),
'fade' : TTkColor.fg('#88FF88'),
@ -262,10 +266,6 @@ class _TTkTabColorButton(TTkWidget):
self._tabStatus = tabStatus
super().__init__(**kwargs)
def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool:
self.tcbClicked.emit(self)
return True
def keyEvent(self, evt:TTkKeyEvent) -> bool:
if ( evt.type == TTkK.Character and evt.key==" " ) or \
( evt.type == TTkK.SpecialKey and evt.key == TTkK.Key_Enter ):
@ -280,17 +280,31 @@ class TTkTabButton(_TTkTabColorButton):
classStyle = (
_TTkTabColorButton.classStyle |
{ 'default': _TTkTabColorButton.classStyle['default'] |
{'closeGlyph':''} ,
{
'closeGlyph': {
'default':'',
'hovered':''
}
} ,
'hover': _TTkTabColorButton.classStyle['hover'] |
{'closeGlyph':' x '} } )
{
'closeGlyph':{
'default':'',
'hovered':''
}
}
}
)
'''TTkTabButton'''
__slots__ = (
'_data','_sideEnd', '_buttonStatus', '_closable',
'_data','_sideEnd', '_buttonStatus', '_closable', '_closeHovered',
'closeClicked', '_closeButtonPressed', '_text')
_closeHovered:bool
def __init__(self, *,
text:TTkString='',
text:TTkStringType='',
data:object=None,
closable:bool=False,
**kwargs) -> None:
@ -299,6 +313,7 @@ class TTkTabButton(_TTkTabColorButton):
self._buttonStatus = TTkK.Unchecked
self._data = data
self._closable = closable
self._closeHovered = False
self.closeClicked = pyTTkSignal()
super().__init__(**kwargs)
self._closeButtonPressed = False
@ -308,7 +323,7 @@ class TTkTabButton(_TTkTabColorButton):
style = self.currentStyle()
size = self.text().termWidth() + 2
if self._closable:
size += len(style['closeGlyph'])
size += len(style['closeGlyph']['default'])
self.resize(size, self._tabStatus.barType.vSize())
self.setMinimumSize(size, self._tabStatus.barType.vSize())
self.setMaximumSize(size, self._tabStatus.barType.vSize())
@ -344,25 +359,42 @@ class TTkTabButton(_TTkTabColorButton):
x,y = evt.x,evt.y
w,h = self.size()
self._closeButtonPressed = False
if self._closable and evt.key == TTkK.MidButton:
self.closeClicked.emit()
return True
offY = self._tabStatus.barType.offY()
if self._closable and y == offY and w-4<=x<w-1:
self._closeButtonPressed = True
self.update()
return True
self.tcbClicked.emit(self)
return True
return super().mouseReleaseEvent(evt)
def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool:
x,y = evt.x,evt.y
w,h = self.size()
offY = self._tabStatus.barType.offY()
if self._closable and y == offY and w-4<=x<w-1 and self._closeButtonPressed:
self._closeButtonPressed = False
if self._closable and evt.key == TTkK.MidButton:
self.closeClicked.emit()
elif self._closable and y == offY and w-4<=x<w-1 and self._closeButtonPressed:
self.closeClicked.emit()
return False
self._closeButtonPressed = False
return False
return True
def leaveEvent(self, evt):
self._closeHovered = False
self.update()
return super().leaveEvent(evt)
def mouseMoveEvent(self, evt):
x,y = evt.x,evt.y
w,h = self.size()
offY = self._tabStatus.barType.offY()
if self._closable and y == offY and w-4<=x<w-1:
_new_closeHovered = True
else:
_new_closeHovered = False
if self._closeHovered != _new_closeHovered:
self._closeHovered = _new_closeHovered
self.update()
return True
def mouseDragEvent(self, evt:TTkMouseEvent) -> bool:
drag = TTkDrag()
@ -390,7 +422,7 @@ class TTkTabButton(_TTkTabColorButton):
borderColor:TTkColor = style['borderColor']
textColor:TTkColor = style['color']
borderHighlightColors:TTkColor = style['borderHighlightColors']
borderHighlightColors:Dict[str, TTkColor] = style['borderHighlightColors']
w,h = self.size()
offY = self._tabStatus.barType.offY()
@ -543,9 +575,14 @@ class TTkTabButton(_TTkTabColorButton):
canvas.drawText(pos=(1,offY), text=self.text(), color=textColor)
if self._closable:
closeGlyph = style['closeGlyph']
if self._closeHovered:
colorCloseHovered = textColor+style['closeColor']['hovered']
closeGlyph = style['closeGlyph']['hovered']
else:
colorCloseHovered = textColor+style['closeColor']['default']
closeGlyph = style['closeGlyph']['default']
closeOff = len(closeGlyph)
canvas.drawText(pos=(w-closeOff-1,offY), text=closeGlyph, color=textColor)
canvas.drawText(pos=(w-closeOff-1,offY), text=closeGlyph, color=colorCloseHovered)
class _TTkTabMenuButton(TTkMenuBarButton):
def paintEvent(self, canvas: TTkCanvas) -> None:
@ -580,7 +617,8 @@ class _TTkTabScrollerButton(_TTkTabColorButton):
# This is a hack to force the action aftet the keypress
# And not key release as normally happen to the button
def mousePressEvent(self, evt:TTkMouseEvent) -> bool:
return super().mouseReleaseEvent(evt)
self.tcbClicked.emit(self)
return True
def mouseReleaseEvent(self, evt:TTkMouseEvent) -> bool:
return False
def mouseTapEvent(self, evt:TTkMouseEvent) -> bool:

13
libs/pyTermTk/TermTk/TTkWidgets/widget.py

@ -113,7 +113,6 @@ class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
'_focus_policy',
'_canvas', '_widgetItem',
'_visible',
'_pendingMouseRelease',
'_enabled',
'_style', '_currentStyle',
'_toolTip',
@ -136,7 +135,6 @@ class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
_canvas:TTkCanvas
_widgetItem:TTkWidgetItem
_visible:bool
_pendingMouseRelease:bool
_enabled:bool
_style:Dict
_currentStyle:Dict
@ -230,7 +228,6 @@ class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
self._name = name if name else self.__class__.__name__
self._parent = parent
self._pendingMouseRelease = False
self._x, self._y = pos if pos else (x,y)
self._width, self._height = size if size else (width,height)
@ -510,7 +507,6 @@ class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
TTkHelper.toolTipClose()
if evt.evt == TTkK.Release:
self._pendingMouseRelease = False
self._processStyleEvent(TTkWidget._S_NONE)
if self.mouseReleaseEvent(evt):
return True
@ -525,13 +521,12 @@ class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
w.setFocus()
w.raiseWidget()
if evt.tap == 2 and self.mouseDoubleClickEvent(evt):
#self._pendingMouseRelease = True
return True
if evt.tap > 1 and self.mouseTapEvent(evt):
return True
if evt.tap == 1 and self.mousePressEvent(evt):
# TTkLog.debug(f"Click {self._name}")
self._pendingMouseRelease = True
self._setPendingMouseRelease()
return True
if evt.key == TTkK.Wheel:
@ -705,6 +700,12 @@ class TTkWidget(TMouseEvents, TKeyEvents, TDragEvents):
if updateParent and self._parent is not None:
self._parent.update(updateLayout=True)
def _setPendingMouseRelease(self) -> None:
'''set the Pending Mouse Release Widget'''
if not (_p:=self._parent):
return
_p._setPendingMouseReleaseWidget(self)
@pyTTkSlot()
def setFocus(self) -> None:
'''Focus the widget'''

Loading…
Cancel
Save