# 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()