244 lines
8.2 KiB
Python
244 lines
8.2 KiB
Python
|
from tkinter import font
|
||
|
import ttkbootstrap as ttk
|
||
|
from ttkbootstrap.constants import *
|
||
|
from ttkbootstrap import utility
|
||
|
|
||
|
# https://www.fontspace.com/freeserif-font-f13277
|
||
|
|
||
|
DEFAULT_ICON_WIN32 = "\ue154"
|
||
|
DEFAULT_ICON = "\u25f0"
|
||
|
|
||
|
|
||
|
class ToastNotification:
|
||
|
"""A semi-transparent popup window for temporary alerts or messages.
|
||
|
You may choose to display the toast for a specified period of time,
|
||
|
otherwise you must click the toast to close it.
|
||
|
|
||
|
![toast notification](../assets/toast/toast.png)
|
||
|
|
||
|
Examples:
|
||
|
|
||
|
```python
|
||
|
import ttkbootstrap as ttk
|
||
|
from ttkbootstrap.toast import ToastNotification
|
||
|
|
||
|
app = ttk.Window()
|
||
|
|
||
|
toast = ToastNotification(
|
||
|
title="ttkbootstrap toast message",
|
||
|
message="This is a toast message",
|
||
|
duration=3000,
|
||
|
)
|
||
|
toast.show_toast()
|
||
|
|
||
|
app.mainloop()
|
||
|
```
|
||
|
"""
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
title,
|
||
|
message,
|
||
|
duration=None,
|
||
|
bootstyle=LIGHT,
|
||
|
alert=False,
|
||
|
icon=None,
|
||
|
iconfont=None,
|
||
|
position=None,
|
||
|
**kwargs,
|
||
|
):
|
||
|
"""
|
||
|
Parameters:
|
||
|
|
||
|
title (str):
|
||
|
The toast title.
|
||
|
|
||
|
message (str):
|
||
|
The toast message.
|
||
|
|
||
|
duration (int):
|
||
|
The number of milliseconds to show the toast. If None
|
||
|
(default), then you must click the toast to close it.
|
||
|
|
||
|
bootstyle (str):
|
||
|
Style keywords used to updated the label style. One of
|
||
|
the accepted color keywords.
|
||
|
|
||
|
alert (bool):
|
||
|
Indicates whether to ring the display bell when the
|
||
|
toast is shown.
|
||
|
|
||
|
icon (str):
|
||
|
A unicode character to display on the top-left hand
|
||
|
corner of the toast. The default symbol is OS specific.
|
||
|
Pass an empty string to remove the symbol.
|
||
|
|
||
|
iconfont (Union[str, Font]):
|
||
|
The font used to render the icon. By default, this is
|
||
|
OS specific. You may need to change the font to enable
|
||
|
better character or emoji support for the icon you
|
||
|
want to use. Windows (Segoe UI Symbol),
|
||
|
Linux (FreeSerif), MacOS (Apple Symbol)
|
||
|
|
||
|
position (Tuple[int, int, str]):
|
||
|
A tuple that controls the position of the toast. Default
|
||
|
is OS specific. The tuple cooresponds to
|
||
|
(horizontal, vertical, anchor), where the horizontal and
|
||
|
vertical elements represent the position of the toplevel
|
||
|
releative to the anchor, which is "ne" or top-left by
|
||
|
default. Acceptable anchors include: n, e, s, w, nw, ne,
|
||
|
sw, se. For example: (100, 100, 'ne').
|
||
|
|
||
|
**kwargs (Dict):
|
||
|
Other keyword arguments passed to the `Toplevel` window.
|
||
|
"""
|
||
|
self.message = message
|
||
|
self.title = title
|
||
|
self.duration = duration
|
||
|
self.bootstyle = bootstyle
|
||
|
self.icon = icon
|
||
|
self.iconfont = iconfont
|
||
|
self.iconfont = None
|
||
|
self.titlefont = None
|
||
|
self.toplevel = None
|
||
|
self.kwargs = kwargs
|
||
|
self.alert = alert
|
||
|
self.position = position
|
||
|
|
||
|
if "overrideredirect" not in self.kwargs:
|
||
|
self.kwargs["overrideredirect"] = True
|
||
|
if "alpha" not in self.kwargs:
|
||
|
self.kwargs["alpha"] = 0.95
|
||
|
|
||
|
if position is not None:
|
||
|
if len(position) != 3:
|
||
|
self.position = None
|
||
|
|
||
|
def show_toast(self, *_):
|
||
|
"""Create and show the toast window."""
|
||
|
|
||
|
# build toast
|
||
|
self.toplevel = ttk.Toplevel(**self.kwargs)
|
||
|
self._setup(self.toplevel)
|
||
|
|
||
|
self.container = ttk.Frame(self.toplevel, bootstyle=self.bootstyle)
|
||
|
self.container.pack(fill=BOTH, expand=YES)
|
||
|
ttk.Label(
|
||
|
self.container,
|
||
|
text=self.icon,
|
||
|
font=self.iconfont,
|
||
|
bootstyle=f"{self.bootstyle}-inverse",
|
||
|
anchor=NW,
|
||
|
).grid(row=0, column=0, rowspan=2, sticky=NSEW, padx=(5, 0))
|
||
|
ttk.Label(
|
||
|
self.container,
|
||
|
text=self.title,
|
||
|
font=self.titlefont,
|
||
|
bootstyle=f"{self.bootstyle}-inverse",
|
||
|
anchor=NW,
|
||
|
).grid(row=0, column=1, sticky=NSEW, padx=10, pady=(5, 0))
|
||
|
ttk.Label(
|
||
|
self.container,
|
||
|
text=self.message,
|
||
|
wraplength=utility.scale_size(self.toplevel, 300),
|
||
|
bootstyle=f"{self.bootstyle}-inverse",
|
||
|
anchor=NW,
|
||
|
).grid(row=1, column=1, sticky=NSEW, padx=10, pady=(0, 5))
|
||
|
|
||
|
self.toplevel.bind("<ButtonPress>", self.hide_toast)
|
||
|
|
||
|
# alert toast
|
||
|
if self.alert:
|
||
|
self.toplevel.bell()
|
||
|
|
||
|
# specified duration to close
|
||
|
if self.duration:
|
||
|
self.toplevel.after(self.duration, self.hide_toast)
|
||
|
|
||
|
def hide_toast(self, *_):
|
||
|
"""Destroy and close the toast window."""
|
||
|
try:
|
||
|
alpha = float(self.toplevel.attributes("-alpha"))
|
||
|
if alpha <= 0.1:
|
||
|
self.toplevel.destroy()
|
||
|
else:
|
||
|
self.toplevel.attributes("-alpha", alpha - 0.1)
|
||
|
self.toplevel.after(25, self.hide_toast)
|
||
|
except:
|
||
|
if self.toplevel:
|
||
|
self.toplevel.destroy()
|
||
|
|
||
|
def _setup(self, window: ttk.Toplevel):
|
||
|
winsys = window.tk.call("tk", "windowingsystem")
|
||
|
|
||
|
self.toplevel.configure(relief=RAISED)
|
||
|
|
||
|
# minsize
|
||
|
if "minsize" not in self.kwargs:
|
||
|
w, h = utility.scale_size(self.toplevel, [300, 75])
|
||
|
self.toplevel.minsize(w, h)
|
||
|
|
||
|
# heading font
|
||
|
_font = font.nametofont("TkDefaultFont")
|
||
|
self.titlefont = font.Font(
|
||
|
family=_font["family"],
|
||
|
size=_font["size"] + 1,
|
||
|
weight="bold",
|
||
|
)
|
||
|
# symbol font
|
||
|
self.iconfont = font.Font(size=30, weight="bold")
|
||
|
if winsys == "win32":
|
||
|
self.iconfont["family"] = "Segoe UI Symbol"
|
||
|
self.icon = DEFAULT_ICON_WIN32 if self.icon is None else self.icon
|
||
|
if self.position is None:
|
||
|
x, y = utility.scale_size(self.toplevel, [5, 50])
|
||
|
self.position = (x, y, SE)
|
||
|
elif winsys == "x11":
|
||
|
self.iconfont["family"] = "FreeSerif"
|
||
|
self.icon = DEFAULT_ICON if self.icon is None else self.icon
|
||
|
if self.position is None:
|
||
|
x, y = utility.scale_size(self.toplevel, [0, 0])
|
||
|
self.position = (x, y, SE)
|
||
|
else:
|
||
|
self.iconfont["family"] = "Apple Symbols"
|
||
|
self.toplevel.update_idletasks()
|
||
|
self.icon = DEFAULT_ICON if self.icon is None else self.icon
|
||
|
if self.position is None:
|
||
|
x, y = utility.scale_size(self.toplevel, [50, 50])
|
||
|
self.position = (x, y, NE)
|
||
|
|
||
|
self.set_geometry()
|
||
|
|
||
|
def set_geometry(self):
|
||
|
self.toplevel.update_idletasks() # actualize geometry
|
||
|
anchor = self.position[-1]
|
||
|
x_anchor = "-" if "w" not in anchor else "+"
|
||
|
y_anchor = "-" if "n" not in anchor else "+"
|
||
|
screen_w = self.toplevel.winfo_screenwidth() // 2
|
||
|
screen_h = self.toplevel.winfo_screenheight() // 2
|
||
|
top_w = self.toplevel.winfo_width() // 2
|
||
|
top_h = self.toplevel.winfo_height() // 2
|
||
|
|
||
|
if all(["e" not in anchor, "w" not in anchor]):
|
||
|
xpos = screen_w - top_w
|
||
|
else:
|
||
|
xpos = self.position[0]
|
||
|
if all(["n" not in anchor, "s" not in anchor]):
|
||
|
ypos = screen_h - top_h
|
||
|
else:
|
||
|
ypos = self.position[1]
|
||
|
|
||
|
self.toplevel.geometry(f"{x_anchor}{xpos}{y_anchor}{ypos}")
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
|
||
|
app = ttk.Window()
|
||
|
|
||
|
ToastNotification(
|
||
|
"ttkbootstrap toast message",
|
||
|
"This is a toast message; you can place a symbol on the top-left that is supported by the selected font. You can either make it appear for a specified period of time, or click to close.",
|
||
|
).show_toast()
|
||
|
|
||
|
app.mainloop()
|