Исходный код EJIO.ctk_extender.windows.popup.image

## /ctk_extender/windows/popup/image.py

import customtkinter as ctk
from pathlib import Path
from typing import Any
from .base import BasePopup
from ...components import ImageWidget
from ...widget import PackStrategy
from EJIO.utils import _

__all__ = [
    'ImagePopup',
]


[документация] class ImagePopup(BasePopup): """ Неблокирующее всплывающее окно для интерактивного просмотра изображений (строки, Path, bytes) с кнопками масштабирования и закрытия. """ __slots__ = ('_source', '_img_widget') def __init__( self, title: str, source: str | Path | bytes, master: Any = None, timeout: float | None = None, ) -> None: self._source: str | Path | bytes = source self._img_widget: ImageWidget | None = None # Передаем modal=False, чтобы окно не блокировало интерфейс основного приложения super().__init__(title, width=450, height=520, modal=False, master=master, timeout=timeout)
[документация] def build_content(self) -> None: # 1. Монтируем готовое интерактивное изображение ImageWidget по центру # Оно уже из коробки поддерживает пропорции, защиту от обрезания и зум колесиком мыши. self._img_widget = ImageWidget( self.container, source=self._source, container_size=(400, 400), geometry=PackStrategy(pady=15, padx=25) ) # 2. Создаем горизонтальный фрейм-строку для кнопок управления под изображением control_frame = ctk.CTkFrame(self.container, fg_color="transparent") control_frame.pack(side="top", fill="x", padx=25) # Метод плавного ручного зума через клик на кнопки интерфейса def manual_zoom(zoom_in: bool) -> None: if self._img_widget is not None and self._img_widget._pil_image is not None: if zoom_in: self._img_widget._zoom_level = min(5.0, self._img_widget._zoom_level + 0.1) else: self._img_widget._zoom_level = max(0.2, self._img_widget._zoom_level - 0.1) # Принудительно просим холст перерисовать Pillow структуру с новым зумом self._img_widget._update_image_on_canvas() # Левая распорка: занимает все свободное место слева, сдвигая кнопки к центру left_spacer = ctk.CTkFrame(control_frame, fg_color="transparent", width=0, height=0) left_spacer.pack(side="left", expand=True, fill="both") # Кнопка Увеличить (+) ctk.CTkButton( control_frame, text="+", width=50, font=("Arial", 16, "bold"), command=lambda: manual_zoom(True) ).pack(side="left", padx=5) # Кнопка Уменьшить (-) ctk.CTkButton( control_frame, text="-", width=50, font=("Arial", 16, "bold"), command=lambda: manual_zoom(False) ).pack(side="left", padx=5) # Правая распорка: уравновешивает левую и держит кнопки строго по центру right_spacer = ctk.CTkFrame(control_frame, fg_color="transparent", width=0, height=0) right_spacer.pack(side="left", expand=True, fill="both") # 3. Кнопка Закрыть в самом низу окна self.add_widget( ctk.CTkButton, text=_("ctk_extender.builtins.popup.close"), command=self.destroy, fg_color="#6B7280", hover_color="#4B5563", ).pack(side="bottom", pady=15)
[документация] def destroy(self) -> None: """Очищает тяжелые Pillow графические буферы виджета при закрытии.""" if self._img_widget is not None: self._img_widget.destroy() self._img_widget = None super().destroy()