from __future__ import annotations import tkinter as tk from collections.abc import Callable, Iterable from dataclasses import dataclass from tkinter import ttk MessageType = str @dataclass(frozen=True) class MessageAction: label: str callback: Callable[[], None] @dataclass(frozen=True) class TabMessage: message_type: MessageType text: str action: MessageAction | None = None _TYPE_STYLES = { "error": "Error", "notification": "Notification", "warning": "Warning", "info": "Info", } class MessageBannerList(ttk.Frame): def __init__(self, master: tk.Misc): super().__init__(master) self.columnconfigure(0, weight=1) self._messages: list[TabMessage] = [] def set_messages(self, messages: Iterable[TabMessage]) -> None: self._messages = [message for message in messages if message.text.strip()] self._render() if self._messages: self.grid() else: self.grid_remove() def _render(self) -> None: for child in self.winfo_children(): child.destroy() for row_index, message in enumerate(self._messages): style_name = _message_style_name(message.message_type) banner = ttk.Frame(self, style=f"Message{style_name}Border.TFrame", padding=1) banner.grid(row=row_index, column=0, sticky="ew", pady=(0, 6)) banner.columnconfigure(0, weight=1) body = ttk.Frame(banner, style=f"Message{style_name}.TFrame", padding=(10, 8)) body.grid(row=0, column=0, sticky="ew") body.columnconfigure(0, weight=1) label = ttk.Label( body, text=message.text, style=f"Message{style_name}.TLabel", wraplength=900, justify="left", ) label.grid(row=0, column=0, sticky="ew") body.bind( "", lambda event, target=label: target.configure(wraplength=max(240, event.width - 180)), add="+", ) if message.action is not None: ttk.Button( body, text=message.action.label, command=message.action.callback, ).grid(row=0, column=1, sticky="e", padx=(12, 0)) def _message_style_name(message_type: MessageType) -> str: return _TYPE_STYLES.get(message_type.casefold(), "Info")