diff --git a/.gitignore b/.gitignore index 52825c27..4abb9718 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +tmp/ build/ dist/ /config.json diff --git a/controller.py b/controller.py index ef639284..797f9599 100644 --- a/controller.py +++ b/controller.py @@ -8,9 +8,9 @@ from utils import getKeyByValue, isUniqueStrings, strPctToInt import argparse # Common -def callbackUpdateSoftware(): +def callbackUpdateSoftware(func=None): setMainWindowGeometry() - model.updateSoftware() + model.updateSoftware(restart=True, func=func) def callbackRestartSoftware(): setMainWindowGeometry() diff --git a/img/chato_delivering.png b/img/chato_delivering.png new file mode 100644 index 00000000..d38f1447 Binary files /dev/null and b/img/chato_delivering.png differ diff --git a/img/chato_unpackaging.png b/img/chato_unpackaging.png new file mode 100644 index 00000000..52eb66dc Binary files /dev/null and b/img/chato_unpackaging.png differ diff --git a/img/downloading_unpackaging_d.png b/img/downloading_unpackaging_d.png new file mode 100644 index 00000000..5c3c7031 Binary files /dev/null and b/img/downloading_unpackaging_d.png differ diff --git a/img/downloading_unpackaging_u.png b/img/downloading_unpackaging_u.png new file mode 100644 index 00000000..684433d5 Binary files /dev/null and b/img/downloading_unpackaging_u.png differ diff --git a/img/unpackage_icon.png b/img/unpackage_icon.png new file mode 100644 index 00000000..8fd8e7d2 Binary files /dev/null and b/img/unpackage_icon.png differ diff --git a/img/vrct_update_process.png b/img/vrct_update_process.png new file mode 100644 index 00000000..27a77150 Binary files /dev/null and b/img/vrct_update_process.png differ diff --git a/model.py b/model.py index fb493cde..affe6624 100644 --- a/model.py +++ b/model.py @@ -278,35 +278,51 @@ class Model: @staticmethod def updateSoftware(restart:bool=True, func=None): - filename = 'VRCT.zip' - program_name = 'VRCT.exe' - folder_name = '_internal' - tmp_directory_name = 'tmp' - batch_name = 'update.bat' - current_directory = config.PATH_LOCAL + def updateSoftwareTask(): + filename = 'VRCT.zip' + program_name = 'VRCT.exe' + folder_name = '_internal' + tmp_directory_name = 'tmp' + batch_name = 'update.bat' + current_directory = config.PATH_LOCAL - try: - res = requests_get(config.GITHUB_URL) - assets = res.json()['assets'] - url = [i["browser_download_url"] for i in assets if i["name"] == filename][0] - with tempfile.TemporaryDirectory() as tmp_path: - res = requests_get(url, stream=True) - file_size = int(res.headers.get('content-length', 0)) - total_chunk = 0 - with open(os_path.join(tmp_path, filename), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024*5): - file.write(chunk) - if isinstance(func, Callable): + try: + res = requests_get(config.GITHUB_URL) + assets = res.json()['assets'] + url = [i["browser_download_url"] for i in assets if i["name"] == filename][0] + with tempfile.TemporaryDirectory() as tmp_path: + res = requests_get(url, stream=True) + file_size = int(res.headers.get('content-length', 0)) + total_chunk = 0 + with open(os_path.join(tmp_path, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024*5): + file.write(chunk) total_chunk += len(chunk) - func(total_chunk/file_size) + if isinstance(func, Callable): + func(progress=total_chunk/file_size, progress_type="downloading") + print(f"downloaded {total_chunk}/{file_size}") - with ZipFile(os_path.join(tmp_path, filename)) as zf: - zf.extractall(os_path.join(current_directory, tmp_directory_name)) - copyfile(os_path.join(current_directory, folder_name, "batch", batch_name), os_path.join(current_directory, batch_name)) - command = [os_path.join(current_directory, batch_name), program_name, folder_name, tmp_directory_name, str(restart)] - Popen(command, cwd=current_directory) - except Exception: - webbrowser.open(config.BOOTH_URL, new=2, autoraise=True) + with ZipFile(os_path.join(tmp_path, filename)) as zf: + total_files = len(zf.infolist()) + extracted_files = 0 + for file_info in zf.infolist(): + extracted_files += 1 + zf.extract(file_info, os_path.join(current_directory, tmp_directory_name)) + if isinstance(func, Callable): + func(progress=extracted_files/total_files, progress_type="extracting") + print(f"extracted {extracted_files}/{total_files}") + + copyfile(os_path.join(current_directory, folder_name, "batch", batch_name), os_path.join(current_directory, batch_name)) + command = [os_path.join(current_directory, batch_name), program_name, folder_name, tmp_directory_name, str(restart)] + Popen(command, cwd=current_directory) + except Exception: + import traceback + with open('error.log', 'a') as f: + traceback.print_exc(file=f) + webbrowser.open(config.BOOTH_URL, new=2, autoraise=True) + th_update_software = Thread(target=updateSoftwareTask) + th_update_software.daemon = True + th_update_software.start() @staticmethod def reStartSoftware(): diff --git a/view.py b/view.py index f66a30e9..46f2f46d 100644 --- a/view.py +++ b/view.py @@ -796,6 +796,8 @@ class View(): # Insert sample conversation for testing. # self._insertSampleConversationToTextbox() + # vrct_gui.updating_window.showUpdatingWindow() + # Send Message Format def setSendMessageFormat_EntryWidgets(self, message_format:str): result = self.extractMessageFormat(message_format) @@ -1587,7 +1589,14 @@ class View(): vrct_gui.confirmation_modal.hide_buttons() vrct_gui.update() vrct_gui.confirmation_modal.update() - callFunctionIfCallable(self.view_variable.CALLBACK_UPDATE_SOFTWARE) + + self._hideConfirmationModal() + vrct_gui.withdraw() + vrct_gui.updating_window.showUpdatingWindow() + + def func(**kwargs): + vrct_gui.updating_window.updateDownloadProgress(**kwargs) + callFunctionIfCallable(self.view_variable.CALLBACK_UPDATE_SOFTWARE, func) diff --git a/vrct_gui/_CreateConfirmationModal.py b/vrct_gui/_CreateConfirmationModal.py index 7391a594..898e9c84 100644 --- a/vrct_gui/_CreateConfirmationModal.py +++ b/vrct_gui/_CreateConfirmationModal.py @@ -1,6 +1,6 @@ -from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkProgressBar -from .ui_utils import fadeInAnimation, setGeometryToCenterOfTheWidget, bindButtonFunctionAndColor +from .ui_utils import fadeInAnimation, setGeometryToCenterOfTheWidget, bindButtonFunctionAndColor, generateGradientColor from utils import callFunctionIfCallable @@ -13,6 +13,7 @@ class _CreateConfirmationModal(CTkToplevel): self.settings = settings self._view_variable = view_variable + self.is_showed_progressbar = False self.title("") self.overrideredirect(True) @@ -69,6 +70,18 @@ class _CreateConfirmationModal(CTkToplevel): self.modal_buttons_wrapper.grid(row=1, column=0, sticky="ew") + # Progress bar + self.progressbar_widget = CTkProgressBar( + self.modal_contents_wrapper, + height=8, + corner_radius=0, + fg_color="black", + # fg_color="#4b4c4f", + progress_color="gray", + ) + self.progressbar_widget.set(0) + + if modal_type == "information": self.modal_buttons_wrapper.grid_columnconfigure((0,2), weight=1) @@ -237,5 +250,21 @@ class _CreateConfirmationModal(CTkToplevel): return callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL) + + def updateDownloadProgress(self, progress:float): + if self.is_showed_progressbar is False: + self.progressbar_widget.place(relwidth=0.9, relx=0.5, rely=0.84, anchor="s") + self.is_showed_progressbar = True + self.update() + + progress_color = generateGradientColor( + value=progress, + color_start=[242, 242, 242], # RGB values for #f2f2f2 + color_end=[72, 164, 149], # RGB values for #48a495 + ) + self.progressbar_widget.configure(progress_color=progress_color) + self.progressbar_widget.set(progress) + self.update_idletasks() + def _grab_set(self): self.grab_set() diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py index 3a1084c0..a0b5ad31 100644 --- a/vrct_gui/splash_window/SplashWindow.py +++ b/vrct_gui/splash_window/SplashWindow.py @@ -2,7 +2,7 @@ import math import time from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar, CTkFrame -from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation +from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation, generateGradientColor class SplashWindow(CTkToplevel): def __init__(self): @@ -200,25 +200,6 @@ class SplashWindow(CTkToplevel): rotated_image = image.rotate(angle, expand=True) return rotated_image - # This making gradient color process was made by ChatGPT. - def generateGradientColor(self, value): - # 0の時の色と1の時の色を指定 - color_start = [242, 242, 242] # RGB values for #f2f2f2 - color_end = [72, 164, 149] # RGB values for #48a495 - - # 補完色を計算 - interpolated_color = [ - int(start + (end - start) * value) for start, end in zip(color_start, color_end) - ] - - # RGB値を0から255の範囲にクリップ - interpolated_color = [max(0, min(255, val)) for val in interpolated_color] - - # RGBを16進数に変換 - hex_color = "#{:02x}{:02x}{:02x}".format(*interpolated_color) - - return hex_color - def updateDownloadProgress(self, progress:float): if self.is_showed_weight_download_progressbar is False: @@ -232,7 +213,11 @@ class SplashWindow(CTkToplevel): self.is_showed_weight_download_progressbar = True self.update() - progress_color = self.generateGradientColor(progress) + progress_color = generateGradientColor( + value=progress, + color_start=[242, 242, 242], # RGB values for #f2f2f2 + color_end=[72, 164, 149], # RGB values for #48a495 + ) self.weight_download_progressbar_widget.configure(progress_color=progress_color) self.weight_download_progressbar_widget.set(progress) self.update_idletasks() diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index fc4b35e7..7af40855 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -71,6 +71,22 @@ def calculateUiSize(default_size, scaling_float, is_allowed_odd:bool=False, is_z return size +# This making gradient color process was made by ChatGPT. +def generateGradientColor(value, color_start, color_end): + # 補完色を計算 + interpolated_color = [ + int(start + (end - start) * value) for start, end in zip(color_start, color_end) + ] + + # RGB値を0から255の範囲にクリップ + interpolated_color = [max(0, min(255, val)) for val in interpolated_color] + + # RGBを16進数に変換 + hex_color = "#{:02x}{:02x}{:02x}".format(*interpolated_color) + + return hex_color + + def bindEnterAndLeaveColor(target_widgets, enter_color, leave_color): for target_widget in target_widgets: target_widget.bind("", lambda e, widgets=target_widgets: [w.configure(fg_color=enter_color) for w in widgets], "+") diff --git a/vrct_gui/updating_window/UpdatingWindow.py b/vrct_gui/updating_window/UpdatingWindow.py new file mode 100644 index 00000000..d84fd7dd --- /dev/null +++ b/vrct_gui/updating_window/UpdatingWindow.py @@ -0,0 +1,177 @@ +import math +import time + +from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar, CTkFrame +from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation, generateGradientColor, getImagePath + +class UpdatingWindow(CTkToplevel): + def __init__(self, vrct_gui): + super().__init__() + self.withdraw() + self.overrideredirect(True) + self.configure(fg_color="#292a2d") + self.title("Updating...") + self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) + self.protocol("WM_DELETE_WINDOW", vrct_gui._quitVRCT) + # self.wm_attributes("-toolwindow", True) + self.is_showed_downloading_process = False + self.is_showed_unpackaging_process = False + BG_WIDTH= 300 + BG_HEIGHT= 350 + self.BG_HEX_COLOR = "#292a2d" + + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + self.updating_background = CTkFrame(self, corner_radius=0, fg_color=self.BG_HEX_COLOR, width=BG_WIDTH, height=BG_HEIGHT) + self.updating_background.grid() + + + self.PROGRESSBAR_HEIGHT = 2 + self.PROGRESSBAR_WIDTH = 240 + self.PROGRESSBAR_Y = 240 + self.PROGRESSBAR_X = 30 + + + + + self.downloading_unpackaging_d = getImageFileFromUiUtils("downloading_unpackaging_d.png") + self.downloading_unpackaging_d_label = CTkLabel( + self.updating_background, + text=None, + height=0, + fg_color=self.BG_HEX_COLOR, + image=CTkImage(self.downloading_unpackaging_d, size=(self.downloading_unpackaging_d.width, self.downloading_unpackaging_d.height)) + ) + + + self.downloading_unpackaging_u = getImageFileFromUiUtils("downloading_unpackaging_u.png") + self.downloading_unpackaging_u_label = CTkLabel( + self.updating_background, + text=None, + height=0, + fg_color=self.BG_HEX_COLOR, + image=CTkImage(self.downloading_unpackaging_u, size=(self.downloading_unpackaging_u.width, self.downloading_unpackaging_u.height)) + ) + + + + + + + + + self.unpackage_img = getImageFileFromUiUtils("unpackage_icon.png") + + self.unpackage_img_label = CTkLabel( + self.updating_background, + text=None, + height=0, + fg_color=self.BG_HEX_COLOR, + image=CTkImage(self.unpackage_img, size=(self.unpackage_img.width, self.unpackage_img.height)) + ) + + + + + + + + self.progressbar = CTkProgressBar( + self.updating_background, + height=self.PROGRESSBAR_HEIGHT, + width=self.PROGRESSBAR_WIDTH, + corner_radius=0, + fg_color=self.BG_HEX_COLOR, + progress_color=self.BG_HEX_COLOR, + ) + self.progressbar.set(0) + self.progressbar.place(x=self.PROGRESSBAR_X, y=self.PROGRESSBAR_Y, anchor="nw") + + + self.chato_delivering_img = getImageFileFromUiUtils("chato_delivering.png") + + self.chato_delivering_img_label = CTkLabel( + self.updating_background, + text=None, + height=0, + fg_color=self.BG_HEX_COLOR, + image=CTkImage(self.chato_delivering_img, size=(self.chato_delivering_img.width, self.chato_delivering_img.height)) + ) + self.chato_delivering_img_label.place(x=-30, y=self.PROGRESSBAR_Y - 1, anchor="s") + + + + self.chato_unpackaging_img = getImageFileFromUiUtils("chato_unpackaging.png") + + self.chato_unpackaging_img_label = CTkLabel( + self.updating_background, + text=None, + height=0, + fg_color=self.BG_HEX_COLOR, + image=CTkImage(self.chato_unpackaging_img, size=(self.chato_unpackaging_img.width, self.chato_unpackaging_img.height)) + ) + self.chato_unpackaging_img_label.place(x=-30, y=self.PROGRESSBAR_Y + self.PROGRESSBAR_HEIGHT + 1, anchor="n") + + + + + + + + self.vrct_update_process_img = getImageFileFromUiUtils("vrct_update_process.png") + + self.vrct_update_process_img_label = CTkLabel( + self.updating_background, + text=None, + height=0, + fg_color=self.BG_HEX_COLOR, + image=CTkImage(self.vrct_update_process_img, size=(self.vrct_update_process_img.width, self.vrct_update_process_img.height)) + ) + self.vrct_update_process_img_label.place(x=87, y=300, anchor="nw") + + + + + + + def updateDownloadProgress(self, progress:float, progress_type:str): + if progress_type == "downloading": + if self.is_showed_downloading_process is False: + self.downloading_unpackaging_d_label.place(x=50, y=56, anchor="nw") + self.is_showed_downloading_process = True + + fg_color = generateGradientColor( + value=progress, + color_start=[242, 242, 242], # RGB values for #f2f2f2 + color_end=[72, 164, 149], # RGB values for #48a495 + ) + self.progressbar.configure(fg_color=fg_color) + + chato_x = self.PROGRESSBAR_X + (progress * self.PROGRESSBAR_WIDTH) + self.chato_delivering_img_label.place(x=chato_x) + self.progressbar.set(progress) + self.update_idletasks() + + elif progress_type == "extracting": + if self.is_showed_unpackaging_process is False: + self.chato_delivering_img_label.place_forget() + self.downloading_unpackaging_u_label.place(x=50, y=56, anchor="nw") + self.unpackage_img_label.place(x=130, y=174, anchor="nw") + self.progressbar.configure(fg_color=self.BG_HEX_COLOR, progress_color="#4B4C4F") + self.is_showed_unpackaging_process = True + + chato_x = (self.PROGRESSBAR_X - 3) + (self.PROGRESSBAR_WIDTH - (progress * self.PROGRESSBAR_WIDTH)) + self.chato_unpackaging_img_label.place(x=chato_x) + self.progressbar.set(1 - progress) + self.update_idletasks() + + + def showUpdatingWindow(self): + self.attributes("-alpha", 0) + self.deiconify() + setGeometryToCenterOfScreen(root_widget=self) + fadeInAnimation(self, steps=5, interval=0.02) + + + def destroyUpdatingWindow(self): + self.destroy() \ No newline at end of file diff --git a/vrct_gui/updating_window/__init__.py b/vrct_gui/updating_window/__init__.py new file mode 100644 index 00000000..feed896e --- /dev/null +++ b/vrct_gui/updating_window/__init__.py @@ -0,0 +1 @@ +from .UpdatingWindow import UpdatingWindow \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 0a795436..4b94768e 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -2,6 +2,8 @@ from customtkinter import CTk, CTkImage from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow +from .updating_window import UpdatingWindow + from ._CreateWindowCover import _CreateWindowCover from ._CreateNotificationWindow import _CreateNotificationWindow from ._CreateDropdownMenuWindow import _CreateDropdownMenuWindow @@ -173,6 +175,8 @@ class VRCT_GUI(CTk): init_scaling=(self._view_variable.VAR_TEXTBOX_UI_SCALING.get()/100) ) + self.updating_window = UpdatingWindow(vrct_gui=self) +