Source code for frontend.ui_dialogs.collapsible_section

"""Reusable collapsible section widget for Tkinter dialogs."""

from __future__ import annotations

import tkinter as tk
from tkinter import ttk

from frontend.ui_dialogs.scrollable_frame import ScrollableFrame

COLLAPSED = "\u25b6"
EXPANDED = "\u25bc"

_REFRESH_DELAY_MS = 50


[docs] class CollapsibleSection: """A header bar that toggles the visibility of an inner content frame. Usage:: section = CollapsibleSection(parent, scroll, "Title", expanded=True) ttk.Label(section.content, text="Hello").pack() Args: parent: Parent widget to pack into. scroll: The :class:`ScrollableFrame` that hosts the section (used to refresh the scroll region and rebind mousewheel events). title: Section header text. expanded: Whether the section starts open. pad: Vertical padding above the wrapper. """ def __init__( self, parent: ttk.Frame, scroll: ScrollableFrame, title: str, *, expanded: bool = False, pad: int = 6, ) -> None: self._scroll = scroll arrow_var = tk.StringVar(value=EXPANDED if expanded else COLLAPSED) wrapper = ttk.Frame(parent) wrapper.pack(fill=tk.X, pady=(pad // 2, 0)) header = ttk.Frame(wrapper, style="SectionHeader.TFrame") header.configure(cursor="hand2") header.pack(fill=tk.X) arrow_lbl = ttk.Label( header, textvariable=arrow_var, style="SectionHeader.TLabel", ) arrow_lbl.pack(side=tk.LEFT, padx=(10, 6), pady=8) title_lbl = ttk.Label( header, text=title, style="SectionHeader.TLabel", ) title_lbl.pack(side=tk.LEFT, pady=8) self.content = ttk.Frame(wrapper, padding=(16, 4, 4, 8)) if expanded: self.content.pack(fill=tk.X) def toggle(_e: tk.Event | None = None) -> None: # type: ignore[type-arg] if self.content.winfo_manager(): self.content.pack_forget() arrow_var.set(COLLAPSED) else: self.content.pack(fill=tk.X) arrow_var.set(EXPANDED) scroll.bind_new_children() wrapper.after(_REFRESH_DELAY_MS, scroll.refresh_scroll_region) for w in (header, arrow_lbl, title_lbl): w.bind("<Button-1>", toggle)