158 lines
6.2 KiB
Python
158 lines
6.2 KiB
Python
import ctypes
|
||
from threading import Thread
|
||
from typing import Optional, Union, Tuple
|
||
|
||
from customtkinter import CTkToplevel, CTkProgressBar, CTkLabel, ThemeManager, CTkFont
|
||
from loguru import logger as _logger
|
||
|
||
logger = _logger.bind(module="CTkLoadingBox", prefix="misc")
|
||
|
||
|
||
class CTkLoadingBox(CTkToplevel):
|
||
|
||
def __init__(self,
|
||
title: str = "CTkDialog",
|
||
text: str = "CTkDialog",
|
||
font: Optional[Union[tuple, CTkFont]] = None,
|
||
|
||
hide_topbar: bool = False,
|
||
|
||
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
text_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
button_text_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
entry_border_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
entry_text_color: Optional[Union[str, Tuple[str, str]]] = None,
|
||
|
||
parent=None):
|
||
|
||
super().__init__(fg_color=fg_color)
|
||
|
||
self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
|
||
self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color)
|
||
self._button_fg_color = ThemeManager.theme["CTkButton"]["fg_color"] if button_fg_color is None else self._check_color_type(button_fg_color)
|
||
self._button_hover_color = ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
|
||
self._button_text_color = ThemeManager.theme["CTkButton"]["text_color"] if button_text_color is None else self._check_color_type(button_text_color)
|
||
self._entry_fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if entry_fg_color is None else self._check_color_type(entry_fg_color)
|
||
self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color)
|
||
self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color)
|
||
|
||
if hide_topbar:
|
||
self.overrideredirect(True)
|
||
# Округление окна
|
||
hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
|
||
self.bind("<ButtonPress-1>", self.__start_move)
|
||
self.bind("<B1-Motion>", self.__on_move)
|
||
|
||
self._running: bool = False
|
||
self._progress = 0
|
||
self._text = [text, ""]
|
||
self._font = font
|
||
|
||
# self.geometry("300x100")
|
||
self.transient(parent)
|
||
self.title(title)
|
||
self.lift() # lift window on top
|
||
self.attributes("-topmost", True) # stay on top
|
||
self.protocol("WM_DELETE_WINDOW", self._on_closing)
|
||
self._create_widgets()
|
||
self.resizable(False, False)
|
||
|
||
# self.grab_set() # make other windows not clickable
|
||
self.withdraw()
|
||
self._req_events()
|
||
|
||
def __start_move(self, event):
|
||
self.x = event.x
|
||
self.y = event.y
|
||
|
||
def __on_move(self, event):
|
||
self.geometry(f"+{event.x_root - self.x}+{event.y_root - self.y}")
|
||
|
||
def _req_events(self):
|
||
event.register("loading.open", self.open)
|
||
event.register("loading.close", self.close)
|
||
event.register("loading.set_text", self.set_text)
|
||
event.register("loading.set_subtext", self.set_subtext)
|
||
event.register("loading.set_progress", self.set_progress)
|
||
|
||
def open(self, *_, **__):
|
||
self.grab_set()
|
||
self.deiconify()
|
||
|
||
def close(self, *_, **__):
|
||
self.grab_release()
|
||
self.withdraw()
|
||
self.set_text("closed")
|
||
self.set_subtext("closed")
|
||
self.set_progress(1)
|
||
|
||
def _create_widgets(self):
|
||
# self.message_label = CTkLabel(self, text=self._text)
|
||
self.message_label = CTkLabel(
|
||
self, width=300, wraplength=300, fg_color="transparent", text=self._text[0]
|
||
)
|
||
self.message_label.pack(pady=5)
|
||
|
||
# Прогресс-бар
|
||
self.progress_bar = CTkProgressBar(self, mode="determinate", width=250)
|
||
self.progress_bar.pack(pady=10)
|
||
self.progress_bar.set(0) # Устанавливаем начальное значение прогресса
|
||
|
||
# Текст с процентами
|
||
self.percent_label = CTkLabel(
|
||
self, width=300, wraplength=300, fg_color="transparent", text="0%"
|
||
)
|
||
self.percent_label.pack(pady=5)
|
||
|
||
def _on_closing(self):
|
||
self.grab_release()
|
||
# self.destroy()
|
||
|
||
def __run(self, f, *args, **kwargs):
|
||
self._running = True
|
||
try:
|
||
f(*args, **kwargs)
|
||
except Exception as e:
|
||
logger.exception(e)
|
||
finally:
|
||
self._running = False
|
||
self.grab_release()
|
||
self.destroy()
|
||
self.quit()
|
||
|
||
def process(self, func, *args, **kwargs):
|
||
Thread(target=self.__run, args=(func, self, *args), kwargs=kwargs, daemon=True).start()
|
||
self.mainloop()
|
||
|
||
def _set_subtext(self, subtext):
|
||
self._text[1] = subtext
|
||
self.message_label.configure(text=f"{self._text[0]}: {self._text[1]}")
|
||
|
||
def set_subtext(self, subtext, *_, **__):
|
||
self.after(0, self._set_subtext, subtext)
|
||
|
||
def _set_text(self, text):
|
||
self._text[0] = text
|
||
self.message_label.configure(text=f"{self._text[0]}")
|
||
|
||
def set_text(self, text, *_, **__):
|
||
self.after(0, self._set_text, text)
|
||
|
||
def _set_progress(self, value):
|
||
if 0.0 <= value <= 1.0:
|
||
self._progress = value
|
||
self.progress_bar.set(value) # Обновляем прогресс-бар
|
||
self.percent_label.configure(text=f"{int(value * 100)}%") # Обновляем текст процентов
|
||
|
||
def set_progress(self, value, *_, **__):
|
||
self.after(0, self._set_progress, value)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
c = CTkLoadingBox("Title", "Message")
|
||
c.set_progress(0.475)
|
||
c.mainloop()
|