# Author: Miguel Martinez Lopez
import pytweening
try:
from Tkinter import Toplevel, PhotoImage, Frame, Button, N, S, E, W, RIGHT, BOTH, X
except ImportError:
from tkinter import Toplevel, PhotoImage, Frame, Button, N, S, E, W, RIGHT, BOTH, X
SUCCESS_BACKGROUND = "#60a917"
WARNING_BACKGROUND = "#fa6800"
ALERT_BACKGROUND = "#ce352c"
INFO_BACKGROUND = "#59cde2"
class Notification(Toplevel):
def __init__(self, notification_manager, builder, index, x, y, h, v, padx, pady, background=None, on_hide=None):
Toplevel.__init__(self)
self._notification_manager = notification_manager
self.index = index
self.on_hide = on_hide
# Removes the native window boarder.
self.overrideredirect(True)
# Disables resizing of the widget.
self.resizable(False, False)
# Places window above all other windows in the window stack.
self.wm_attributes("-topmost", True)
notification_frame = Frame(self)
notification_frame.pack(expand=True, fill=BOTH, padx=padx, pady=pady)
top_row = Frame(notification_frame)
top_row.pack(fill=X)
if not hasattr(notification_manager, "_close_icon"):
notification_manager._close_icon = PhotoImage(data="R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw==")
close_button = Button(top_row, image=notification_manager._close_icon, highlightthickness=0, borderwidth=0, command=self.close)
close_button.pack(side=RIGHT, anchor=E)
self.interior = Frame(notification_frame)
self.interior.pack(expand=True, fill=BOTH)
if builder:
builder(self.interior)
if background is not None:
top_row.config(background=background)
notification_frame.config(background=background)
self.config(background=background)
self.interior.config(background=background)
close_button.config(background=background)
self.place(x,y, h, v)
@property
def x(self):
return self._offset_x
@property
def y(self):
return self._offset_y
@property
def h(self):
return self._h
@property
def v(self):
return self._v
def place(self, x, y, h, v):
''' The windows overall position on the screen '''
self.wm_geometry("{h}{x}{v}{y}".format(x=x,y=y, h=h, v=v))
self._offset_x = x
self._offset_y = y
self._h = h
self._v = v
def start_animation(self, easing_function, ticks, duration, start_time=0):
self._tick = 0
self._total_ticks = float(ticks)
self._easing_function = easing_function
self._duration = duration
self._interval_time = int(duration * 1000 / self._total_ticks)
if start_time != 0:
self.after(int(start_time*1000), self._animate)
else:
self._animate()
def _animate(self):
t = self._tick / self._total_ticks
# This changes the alpha value (How transparent the window should be).
# It ranges from 0.0 (completely transparent) to 1.0 (completely opaque).
self.attributes("-alpha", self._easing_function(1-t))
self._tick += 1
if self._tick <= self._total_ticks:
self.after(self._interval_time, self._animate)
else:
self.after(self._interval_time, self.close)
def close(self):
self._notification_manager.delete(self)
class Notification_Manager(object):
def __init__(self, offset_x=12, offset_y=8, corner=N+E, background=None, spacing=5, ticks=15, easing_function=pytweening.linear, duration=3, start_time=3, padx=5, pady=5):
if corner == N+W:
self._h = "+"
self._v = "+"
elif corner == N+E:
self._h = "-"
self._v = "+"
elif corner == S+W:
self._h = "+"
self._v = "-"
elif corner == S+E:
self._h = "-"
self._v = "-"
else:
raise ValueError("Not a valid corner value: %s"%corner)
self._list_of_notifications = []
self._offset_x = offset_x
self._offset_y = offset_y
self._padx = padx
self._pady = pady
self._corner = corner
self._background = background
self._ticks = ticks
self._duration = duration
self._easing_function = easing_function
self._spacing = spacing
self._start_time = start_time
@property
def corner(self):
return self._corner
@property
def background(self):
return self._background
@property
def duration(self):
return self._duration
@property
def spacing(self):
return self._spacing
@property
def ticks(self):
return self._ticks
def create_notification(self, builder, start_time=None, duration=None, easing_function=None,ticks=None, background=None, padx=None, pady=None, on_hide=None):
if ticks is None:
ticks = self._ticks
if builder is None:
builder = self._builder
notification.on_hide = on_hide
if duration is None:
duration = self._duration
if easing_function is None:
easing_function = self._easing_function
if background is None:
background = self._background
if padx is None:
padx = self._padx
if pady is None:
pady = self._pady
if start_time is None:
start_time = self._start_time
if len(self._list_of_notifications) == 0:
x = self._offset_x
y = self._offset_y
index = 0
else:
last_notification = self._list_of_notifications[-1]
last_notification.update_idletasks()
x = self._offset_x
y = last_notification.y + last_notification.winfo_height() + self._spacing
index = len(self._list_of_notifications)
notification = Notification(self, builder, index, x, y, self._h, self._v, padx, pady, background, on_hide)
self._list_of_notifications.append(notification)
notification.start_animation(easing_function=easing_function, ticks=ticks, duration=duration, start_time=start_time)
def simple_notification(self, text, foreground, background, font=None, width=None, anchor=None, justify=None, wraplength=None, start_time=None, duration=None, easing_function=None,ticks=None, padx=None, pady=None, on_hide=None):
builder = self.create_builder(text, foreground, background, font=font, width=width, anchor=anchor, justify=justify, wraplength=wraplength)
self.create_notification(builder, background= background, start_time=start_time, duration=duration, easing_function=easing_function, ticks=ticks, padx=padx, pady=pady, on_hide=on_hide)
def success(self, text, font=None, width=None, anchor=None, justify=None, wraplength=None, start_time=None, duration=None, easing_function=None,ticks=None, padx=None, pady=None, on_hide=None):
self.simple_notification(text, "white", SUCCESS_BACKGROUND, font=font, width=width, anchor=anchor, justify=justify, wraplength=wraplength, start_time=start_time, duration=duration, easing_function=easing_function, ticks=ticks, padx=padx, pady=pady, on_hide=on_hide)
def warning(self, text, font=None, width=None, anchor=None, justify=None, wraplength=None, start_time=None, duration=None, easing_function=None,ticks=None, padx=None, pady=None, on_hide=None):
self.simple_notification(text, "white", WARNING_BACKGROUND, font=font, width=width, anchor=anchor, justify=justify, wraplength=wraplength, start_time=start_time, duration=duration, easing_function=easing_function, ticks=ticks, padx=padx, pady=pady, on_hide=on_hide)
def alert(self, text, font=None, width=None, anchor=None, justify=None, wraplength=None, start_time=None, duration=None, easing_function=None,ticks=None, padx=None, pady=None, on_hide=None):
self.simple_notification(text, "white", ALERT_BACKGROUND, font=font, width=width, anchor=anchor, justify=justify, wraplength=wraplength, start_time=start_time, duration=duration, easing_function=easing_function, ticks=ticks, padx=padx, pady=pady, on_hide=on_hide)
def info(self, text, font=None, width=None, anchor=None, justify=None, wraplength=None, start_time=None, duration=None, easing_function=None,ticks=None, padx=None, pady=None, on_hide=None):
self.simple_notification(text, "white", INFO_BACKGROUND, font=font, width=width, anchor=anchor, justify=justify, wraplength=wraplength, start_time=start_time, duration=duration, easing_function=easing_function, ticks=ticks, padx=padx, pady=pady, on_hide=on_hide)
def create_builder(self, text, foreground, background, font=None, width=None, anchor=None, justify=None, wraplength=None):
kwargs = dict(text=text, fg=foreground, background=background)
if font:
kwargs["font"] = font
if anchor:
kwargs["anchor"] = anchor
if justify:
kwargs["justify"] = justify
if width:
kwargs["width"] = width
if wraplength:
kwargs["wraplength"] = wraplength
def builder(interior):
Label(interior, **kwargs).pack()
return builder
def delete(self, notification):
index = notification.index
height = notification.winfo_height()
self._list_of_notifications.pop(index)
notification.destroy()
x = self._offset_x
for i in range(index, len(self._list_of_notifications)):
_notification = self._list_of_notifications[i]
y = _notification.y - height - self._spacing
_notification.index = i
_notification.place(x, y, h=self._h, v=self._v)
if notification.on_hide:
notification.on_hide()
if __name__ == "__main__":
try:
from Tkinter import Tk, Label
except ImportError:
from tkinter import Tk, Label
root = Tk()
notification_manager = Notification_Manager(background="white")
def create_notification(start_time, text):
def notify():
def builder(interior):
Label(interior, text=text, background="white").pack()
notification_manager.create_notification(builder=builder)
root.after(start_time, notify)
create_notification(100, "this is a label")
create_notification(2500, "this is another label")
create_notification(5000, "this is the third label")
notification_manager.success("my succes message")
notification_manager.warning("warning!")
root.mainloop()
Diff to Previous Revision
--- revision 1 2017-04-01 19:18:42
+++ revision 2 2017-04-01 19:27:59
@@ -21,13 +21,13 @@
self.index = index
self.on_hide = on_hide
- ''' Removes the native window boarder. '''
+ # Removes the native window boarder.
self.overrideredirect(True)
- ''' Disables resizing of the widget. '''
+ # Disables resizing of the widget.
self.resizable(False, False)
- ''' Places window above all other windows in the window stack. '''
+ # Places window above all other windows in the window stack.
self.wm_attributes("-topmost", True)
notification_frame = Frame(self)