Исходный код 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()