Skip to content
48 changes: 47 additions & 1 deletion tktooltip/tooltip.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from __future__ import annotations

import sys
import threading
import time
import tkinter as tk
from contextlib import suppress
Expand Down Expand Up @@ -45,6 +47,7 @@ def __init__(
msg: str | list[str] | Callable[[], str | list[str]],
delay: float = 0.0,
follow: bool = True,
animations: bool = True,
refresh: float = 1.0,
x_offset: int = +10,
y_offset: int = +10,
Expand All @@ -66,6 +69,9 @@ def __init__(
Delay in seconds before the ToolTip appears, by default 0.0
follow : `bool`, optional
ToolTip follows motion, otherwise hides, by default True
animations : `bool`, optional
ToolTip fades in when showing and fades out when hiding, by default True.
This requires a compositing window manager on Linux to have any effect.
refresh : `float`, optional
Refresh rate in seconds for strings and functions when mouse is
stationary and inside the widget, by default 1.0
Expand All @@ -85,13 +91,17 @@ def __init__(
self.withdraw() # Hide initially in case there is a delay
# Disable ToolTip's title bar
self.overrideredirect(True)
# Hide Tooltip's window from the taskbar on Windows
if sys.platform == "win32":
self.wm_attributes("-toolwindow", True)

# StringVar instance for msg string|function
self.msg_var = tk.StringVar()
self.msg = msg
self._update_message()
self.delay = delay
self.follow = follow
self.animations = animations
self.refresh = refresh
self.x_offset = x_offset
self.y_offset = y_offset
Expand Down Expand Up @@ -144,7 +154,21 @@ def on_leave(self, event: tk.Event | None = None) -> None:
Hides the ToolTip.
"""
self.status = ToolTipStatus.OUTSIDE
self.withdraw()

def animation():
self.wm_attributes("-alpha", 1)

for i in range(11):
self.wm_attributes("-alpha", 0.1 * (10 - i))
self.update()
time.sleep(0.01)

self.withdraw()

if self.animations:
threading.Thread(target=animation, daemon=True).start()
else:
self.withdraw()

def _update_tooltip_coords(self, event: tk.Event) -> None:
"""
Expand Down Expand Up @@ -179,12 +203,34 @@ def _show(self) -> None:
self.status == ToolTipStatus.INSIDE
and time.perf_counter() - self.last_moved >= self.delay
):
self.is_shown = False
self.status = ToolTipStatus.VISIBLE

if self.status == ToolTipStatus.VISIBLE:
self._update_message()
self.deiconify()

if not self.is_shown:
x_pos = self.x_offset + self.winfo_pointerx()
y_pos = self.y_offset + self.winfo_pointery()

self.geometry(f"+{x_pos}+{y_pos}")

def animation():
if not self.is_shown:
self.is_shown = True
self.wm_attributes("-alpha", 0)

for i in range(11):
self.wm_attributes("-alpha", 0.1 * i)
self.update()
time.sleep(0.01)

if self.animations:
threading.Thread(target=animation, daemon=True).start()
else:
self.is_shown = True

# Recursively call _show to update ToolTip with the newest value of msg
# This is a race condition which only exits when upon a binding change
# that in turn changes the `status` to outside
Expand Down