diff --git a/config.py b/config.py index fdfde88f..0a7f2328 100644 --- a/config.py +++ b/config.py @@ -10,7 +10,7 @@ from tkinter import font from languages import selectable_languages from models.translation.translation_languages import translatorEngine from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice -from utils import generatePercentageStringsList +from utils import generatePercentageStringsList, isUniqueStrings json_serializable_vars = {} def json_serializable(var_name): @@ -457,6 +457,8 @@ class Config: @MESSAGE_FORMAT.setter def MESSAGE_FORMAT(self, value): if type(value) is str: + if isUniqueStrings(["[message]", "[translation]"], value) is False: + value = "[message]([translation])" self._MESSAGE_FORMAT = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) diff --git a/controller.py b/controller.py index f3f2d7c1..421330a8 100644 --- a/controller.py +++ b/controller.py @@ -3,7 +3,7 @@ from threading import Thread from config import config from model import model from view import view -from utils import get_key_by_value +from utils import get_key_by_value, isUniqueStrings from languages import selectable_languages # Common @@ -13,6 +13,12 @@ def callbackUpdateSoftware(): def callbackRestartSoftware(): model.reStartSoftware() +def callbackFilepathLogs(): + print("callbackFilepathLogs") + +def callbackFilepathConfigFile(): + print("callbackFilepathConfigFile") + # func transcription send message def sendMicMessage(message): if len(message) > 0: @@ -230,6 +236,15 @@ def setTargetLanguageAndCountry(select): config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_selectedTargetLanguages(select) +def swapYourLanguageAndTargetLanguage(): + your_language = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + target_language = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + setYourLanguageAndCountry(target_language) + setTargetLanguageAndCountry(your_language) + # Update Selected Languages for UI + view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) + + def callbackSelectedLanguagePresetTab(selected_tab_no): config.SELECTED_TAB_NO = selected_tab_no view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) @@ -627,7 +642,14 @@ def callbackSetEnableAutoExportMessageLogs(value): def callbackSetMessageFormat(value): print("callbackSetMessageFormat", value) if len(value) > 0: - config.MESSAGE_FORMAT = value + if isUniqueStrings(["[message]", "[translation]"], value) is True: + config.MESSAGE_FORMAT = value + view.clearErrorMessage() + view.setMessageFormatEntryWidgets(config.MESSAGE_FORMAT) + else: + view.showErrorMessage_MessageFormat() + view.setMessageFormatEntryWidgets(config.MESSAGE_FORMAT) + def callbackSetEnableSendMessageToVrc(value): print("callbackSetEnableSendMessageToVrc", value) @@ -684,6 +706,8 @@ def createMainWindow(): common_registers={ "callback_update_software": callbackUpdateSoftware, "callback_restart_software": callbackRestartSoftware, + "callback_filepath_logs": callbackFilepathLogs, + "callback_filepath_config_file": callbackFilepathConfigFile, }, window_action_registers={ @@ -703,6 +727,7 @@ def createMainWindow(): "callback_your_language": setYourLanguageAndCountry, "callback_target_language": setTargetLanguageAndCountry, "values": model.getListLanguageAndCountry(), + "callback_swap_languages": swapYourLanguageAndTargetLanguage, "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, "message_box_bind_Return": messageBoxPressKeyEnter, diff --git a/img/folder_open_icon.png b/img/folder_open_icon.png new file mode 100644 index 00000000..3de75dfe Binary files /dev/null and b/img/folder_open_icon.png differ diff --git a/img/refresh_icon.png b/img/refresh_icon.png index c5acad15..408bc2a6 100644 Binary files a/img/refresh_icon.png and b/img/refresh_icon.png differ diff --git a/img/refresh_update_icon.png b/img/refresh_update_icon.png new file mode 100644 index 00000000..c5acad15 Binary files /dev/null and b/img/refresh_update_icon.png differ diff --git a/img/swap_icon.png b/img/swap_icon.png new file mode 100644 index 00000000..8fa1cf53 Binary files /dev/null and b/img/swap_icon.png differ diff --git a/locales/en.yml b/locales/en.yml index 7eee5744..887d3a92 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -7,6 +7,7 @@ main_window: language_settings: Language Settings your_language: Your Language both_direction_desc: Translate Each Other + swap_button_label: Swap Languages target_language: Target Language textbox_tab_all: All @@ -181,6 +182,8 @@ config_window: message_format: label: Message Format desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\nIt will be used in Notification XSOverlay too." + example_text: This is an example sentence. Fonts, line breaks, etc. may differ from the actual display. + error_message: "The characters '[message]' and '[translation]' cannot be used." send_message_to_vrc: label: Send Message To VRChat @@ -190,4 +193,7 @@ config_window: label: OSC IP Address osc_port: - label: OSC Port \ No newline at end of file + label: OSC Port + + open_config_filepath: + label: Open Config File \ No newline at end of file diff --git a/locales/ja.yml b/locales/ja.yml index b662a3eb..313c991f 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -7,6 +7,7 @@ main_window: language_settings: 言語設定 your_language: あなたの言語 both_direction_desc: 双方向に翻訳 + swap_button_label: 言語を入れ替え target_language: 相手の言語 textbox_tab_all: 全て @@ -179,7 +180,9 @@ config_window: message_format: label: 送信するメッセージのフォーマット - desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、\n[translation]が翻訳されたメッセージに置換されます。\n※XSOverlayでの通知受け取り機能でも使われます。" + desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、[translation]が翻訳されたメッセージに置換されます。\n※XSOverlayでの通知受け取り機能でも使われます。" + example_text: これは例文です。フォントや改行箇所など、実際の表示とは異なる場合があります。 + error_message: "[message]と[translation]という文字は使えません。" send_message_to_vrc: label: VRChatにメッセージを送信する @@ -189,4 +192,7 @@ config_window: label: OSC IP Address osc_port: - label: OSC Port \ No newline at end of file + label: OSC Port + + open_config_filepath: + label: 設定ファイルを開く \ No newline at end of file diff --git a/locales/ko.yml b/locales/ko.yml index ad08942f..fa8cc2b2 100644 --- a/locales/ko.yml +++ b/locales/ko.yml @@ -175,6 +175,7 @@ config_window: message_format: label: 전송 형식 # desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\nIt will be used in Notification XSOverlay too." + example_text: 예문입니다. 글꼴, 줄 바꿈 등이 실제 표시와 다를 수 있습니다. # send_message_to_vrc: # label: Send Message To VRChat diff --git a/utils.py b/utils.py index 242faf15..9cb0dc94 100644 --- a/utils.py +++ b/utils.py @@ -1,3 +1,4 @@ +from typing import Union from os import path as os_path from PIL.Image import open as Image_open @@ -29,4 +30,19 @@ def generatePercentageStringsList(start=40, end=200, step=10): return strings def intToPercentageStringsFormatter(value:int): - return f"{value}%" \ No newline at end of file + return f"{value}%" + +def isUniqueStrings(unique_strings:Union[str, list], input_string:str, require=False): + import re + if isinstance(unique_strings, str): + unique_strings = [unique_strings] + patterns = [re.escape(s) for s in unique_strings] + + counts = [len(re.findall(pattern, input_string)) for pattern in patterns] + + if require is True: + # If require is True, unique_strings must appear once + return all(count == 1 for count in counts) and counts.count(1) == 2 + else: + # If require is False, check if unique strings are used exactly once + return all(count == 1 for count in counts) \ No newline at end of file diff --git a/view.py b/view.py index ac1d0739..f7250b9f 100644 --- a/view.py +++ b/view.py @@ -82,6 +82,8 @@ class View(): # Common CALLBACK_RESTART_SOFTWARE=None, CALLBACK_UPDATE_SOFTWARE=None, + CALLBACK_OPEN_FILEPATH_LOGS=None, + CALLBACK_OPEN_FILEPATH_CONFIG_FILE=None, CALLBACK_QUIT_VRCT=vrct_gui._quitVRCT, @@ -140,6 +142,8 @@ class View(): CALLBACK_SELECTED_YOUR_LANGUAGE=None, VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value=i18n.t("main_window.both_direction_desc")), + VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON=StringVar(value=i18n.t("main_window.swap_button_label")), + CALLBACK_SWAP_LANGUAGES=None, VAR_LABEL_TARGET_LANGUAGE=StringVar(value=i18n.t("main_window.target_language")), VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), @@ -347,7 +351,15 @@ class View(): VAR_LABEL_MESSAGE_FORMAT=StringVar(value=i18n.t("config_window.message_format.label")), VAR_DESC_MESSAGE_FORMAT=StringVar(value=i18n.t("config_window.message_format.desc")), CALLBACK_SET_MESSAGE_FORMAT=None, + CALLBACK_SWAP_MESSAGE_FORMAT_REQUIRED_TEXT=self._swapMessageFormatRequiredText, VAR_MESSAGE_FORMAT=StringVar(value=config.MESSAGE_FORMAT), + VAR_LABEL_EXAMPLE_TEXT_MESSAGE_FORMAT=StringVar(value=""), + VAR_ENTRY_0_MESSAGE_FORMAT=StringVar(value=""), + VAR_ENTRY_1_MESSAGE_FORMAT=StringVar(value=""), + VAR_ENTRY_2_MESSAGE_FORMAT=StringVar(value=""), + VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT=StringVar(value="[message]"), + VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT=StringVar(value="[translation]"), + CALLBACK_FOCUS_OUT_MESSAGE_FORMAT=self.callbackBindFocusOut_MessageFormat, VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.label")), @@ -374,6 +386,9 @@ class View(): VAR_DESC_OSC_PORT=None, CALLBACK_SET_OSC_PORT=None, VAR_OSC_PORT=StringVar(value=config.OSC_PORT), + + VAR_LABEL_OPEN_CONFIG_FILEPATH=StringVar(value=i18n.t("config_window.open_config_filepath.label")), + VAR_DESC_OPEN_CONFIG_FILEPATH=None, ) @@ -390,6 +405,8 @@ class View(): if common_registers is not None: self.view_variable.CALLBACK_UPDATE_SOFTWARE=common_registers.get("callback_update_software", None) self.view_variable.CALLBACK_RESTART_SOFTWARE=common_registers.get("callback_restart_software", None) + self.view_variable.CALLBACK_OPEN_FILEPATH_LOGS=common_registers.get("callback_filepath_logs", None) + self.view_variable.CALLBACK_OPEN_FILEPATH_CONFIG_FILE=common_registers.get("callback_filepath_config_file", None) if window_action_registers is not None: @@ -410,6 +427,7 @@ class View(): self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = main_window_registers.get("callback_your_language", None) self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = main_window_registers.get("callback_target_language", None) main_window_registers.get("values", None) and self.updateList_selectableLanguages(main_window_registers["values"]) + self.view_variable.CALLBACK_SWAP_LANGUAGES = main_window_registers.get("callback_swap_languages", None) self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = main_window_registers.get("callback_selected_language_preset_tab", None) @@ -531,10 +549,56 @@ class View(): self.openSpeakerEnergyThresholdWidget() + self.setMessageFormatEntryWidgets(config.MESSAGE_FORMAT) + # Insert sample conversation for testing. # self._insertSampleConversationToTextbox() + def setMessageFormatEntryWidgets(self, message_format:str): + result = self.extractMessageFormat(message_format) + + if result.is_message_first is True: + self.view_variable.VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT.set("[message]") + self.view_variable.VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT.set("[translation]") + else: + self.view_variable.VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT.set("[translation]") + self.view_variable.VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT.set("[message]") + + self.view_variable.VAR_ENTRY_0_MESSAGE_FORMAT.set(result.before) + self.view_variable.VAR_ENTRY_1_MESSAGE_FORMAT.set(result.between) + self.view_variable.VAR_ENTRY_2_MESSAGE_FORMAT.set(result.after) + self.updateMessageFormat_ExampleTextWidget() + + def _swapMessageFormatRequiredText(self): + text_0 = self.view_variable.VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT.get() + text_1 = self.view_variable.VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT.get() + self.view_variable.VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT.set(text_1) + self.view_variable.VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT.set(text_0) + self.updateMessageFormat_ExampleTextWidget() + + new_message_format = self.getLatestMessageFormatFromWidget() + callFunctionIfCallable(self.view_variable.CALLBACK_SET_MESSAGE_FORMAT, new_message_format) + + + def getLatestMessageFormatFromWidget(self): + text_0 = self.view_variable.VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT.get() + text_1 = self.view_variable.VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT.get() + entry_0 = self.view_variable.VAR_ENTRY_0_MESSAGE_FORMAT.get() + entry_1 = self.view_variable.VAR_ENTRY_1_MESSAGE_FORMAT.get() + entry_2 = self.view_variable.VAR_ENTRY_2_MESSAGE_FORMAT.get() + return entry_0+text_0+entry_1+text_1+entry_2 + + def updateMessageFormat_ExampleTextWidget(self): + message = i18n.t("config_window.message_format.example_text", locale=config.UI_LANGUAGE) + translation_locale = "ja" if config.UI_LANGUAGE == "en" else "en" + translation = i18n.t("config_window.message_format.example_text", locale=translation_locale) + + example_message = config.MESSAGE_FORMAT.replace("[message]", message) + example_message = example_message.replace("[translation]", translation) + + self.view_variable.VAR_LABEL_EXAMPLE_TEXT_MESSAGE_FORMAT.set(example_message) + # GUI process def createGUI(self): @@ -1068,6 +1132,9 @@ class View(): case "SpeakerMaxPhrases": self.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) + case "MessageFormat": + self.setMessageFormatEntryWidgets(config.MESSAGE_FORMAT) + case _: raise ValueError(f"No matching case for target_name: {target_name}") @@ -1217,7 +1284,9 @@ class View(): self.clearErrorMessage() - + def callbackBindFocusOut_MessageFormat(self, _e=None): + self.setLatestConfigVariable("MessageFormat") + self.clearErrorMessage() @@ -1299,6 +1368,13 @@ class View(): self._makeInvalidValueErrorMessage(i18n.t("config_window.speaker_dynamic_energy_threshold.no_device_error_message")) ) + + def showErrorMessage_MessageFormat(self): + self._showErrorMessage( + vrct_gui.config_window.sb__entry_message_format_2, + self._makeInvalidValueErrorMessage(i18n.t("config_window.message_format.error_message")) + ) + @staticmethod def _makeInvalidValueErrorMessage(error_message): return i18n.t("config_window.common_error_message.invalid_value") + "\n" + error_message @@ -1308,6 +1384,59 @@ class View(): vrct_gui._showErrorMessage(target_widget=target_widget) + @staticmethod + def extractMessageFormat(text): + import re + message_index = text.find("[message]") + translation_index = text.find("[translation]") + + result_data = SimpleNamespace( + is_message_first = True, + before = "", + between = "", + after = "", + ) + + if message_index < translation_index: + text_before_message = text[:message_index] + result_data.before = text_before_message + + match = re.search(r"\[message\](.*?)\[translation\]", text) + if match: + extracted_text = match.group(1) + result_data.between = extracted_text + + else: + raise ValueError("Invalid Message Format") + + text_after_translation = text[translation_index + len("[translation]"):] + result_data.after = text_after_translation + + + + + elif translation_index < message_index: + result_data.is_message_first = False + text_before_translation = text[:translation_index] + result_data.before = text_before_translation + + match = re.search(r"\[translation\](.*?)\[message\]", text) + if match: + extracted_text = match.group(1) + result_data.between = extracted_text + else: + raise ValueError("Invalid Message Format") + + text_after_message = text[message_index + len("[message]"):] + result_data.after = text_after_message + + else: + raise ValueError("Invalid Message Format") + + return result_data + + + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 8a7f3622..cb04cf8d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -2,7 +2,7 @@ from functools import partial from types import SimpleNamespace from typing import Union -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, CTkImage from CTkToolTip import * from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth, createOptionMenuBox, getLatestHeight, bindButtonFunctionAndColor @@ -22,7 +22,7 @@ class _SettingBoxGenerator(): self.dropdown_menu_window = vrct_gui.vrct_gui.dropdown_menu_window - def _createSettingBoxFrame(self, sb__attr_name, for_var_label_text=None, for_var_desc_text=None): + def _createSettingBoxFrame(self, sb__attr_name, for_var_label_text=None, for_var_desc_text=None, expand_label_frame:bool=False): self.config_window.sb__widgets[sb__attr_name] = SimpleNamespace() setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) @@ -34,8 +34,7 @@ class _SettingBoxGenerator(): setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") - setting_box_frame_wrapper.grid_columnconfigure(0, weight=0, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2)) - setting_box_frame_wrapper.grid_columnconfigure(2, weight=1, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2)) + setting_box_frame_wrapper_fix_border = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) setting_box_frame_wrapper_fix_border.grid(row=1, column=0, sticky="ew") @@ -43,10 +42,22 @@ class _SettingBoxGenerator(): setting_box_frame_wrapper_fix_border2 = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) setting_box_frame_wrapper_fix_border2.grid(row=0, column=1, sticky="ns") - if for_var_label_text is not None: - self._setSettingBoxLabels(sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) - # setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color="red") + + if for_var_label_text is not None: + self._setSettingBoxLabels(sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text, expand_label_frame) + if expand_label_frame is True: + setting_box_frame_wrapper.grid_columnconfigure(0, weight=1, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH)) + setting_box_frame_wrapper.grid(columnspan=3) + return setting_box_frame + + + setting_box_frame_wrapper.grid_columnconfigure(0, weight=0, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2)) + setting_box_frame_wrapper.grid_columnconfigure(2, weight=1, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2)) + + + + setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) if for_var_label_text is not None: setting_box_item_frame.grid(row=0, column=2, padx=0, sticky="nsew") @@ -57,7 +68,7 @@ class _SettingBoxGenerator(): return (setting_box_frame, setting_box_item_frame) - def _setSettingBoxLabels(self, sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): + def _setSettingBoxLabels(self, sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None, expand_label_frame:bool=False): setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") @@ -86,6 +97,9 @@ class _SettingBoxGenerator(): font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__DESC_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_DESC_TEXT_COLOR ) + if expand_label_frame is True: + setting_box_desc.configure(wraplength=self.settings.uism.MAIN_AREA_MIN_WIDTH) + setting_box_desc.grid(row=2, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") self.config_window.additional_widgets.append(setting_box_desc) self.config_window.sb__widgets[sb__attr_name].desc_widget=setting_box_desc @@ -93,6 +107,16 @@ class _SettingBoxGenerator(): self.config_window.sb__widgets[sb__attr_name].desc_widget=None + def createSettingBox_Labels( + self, + for_var_label_text, for_var_desc_text, + labels_attr_name, + ): + + setting_box_frame= self._createSettingBoxFrame(labels_attr_name, for_var_label_text, for_var_desc_text, expand_label_frame=True) + + return setting_box_frame + def createSettingBoxDropdownMenu( @@ -230,6 +254,67 @@ class _SettingBoxGenerator(): + def createSettingBoxAutoExportMessageLogs( + self, + for_var_label_text, for_var_desc_text, + checkbox_attr_name, + checkbox_command, + button_command, + variable, + ): + + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(checkbox_attr_name, for_var_label_text, for_var_desc_text) + + + + all_wrapper = CTkFrame(setting_box_item_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + all_wrapper.grid(row=1, column=0, sticky="ew") + + all_wrapper.grid_columnconfigure(1, weight=1) + + + + + button_widget = createButtonWithImage( + parent_widget=all_wrapper, + button_fg_color=self.settings.ctm.SB__BUTTON_COLOR, + button_enter_color=self.settings.ctm.SB__BUTTON_HOVERED_COLOR, + button_clicked_color=self.settings.ctm.SB__BUTTON_CLICKED_COLOR, + button_image_file=self.settings.image_file.FOLDER_OPEN_ICON, + button_image_size=self.settings.uism.SB__BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__BUTTON_IPADXY, + button_command=button_command, + ) + button_widget.grid(row=0, column=0, padx=0, sticky="w") + + + + checkbox_widget = CTkCheckBox( + all_wrapper, + text=None, + width=0, + checkbox_width=self.settings.uism.SB__CHECKBOX_SIZE, + checkbox_height=self.settings.uism.SB__CHECKBOX_SIZE, + onvalue=True, + offvalue=False, + variable=variable, + command=checkbox_command, + corner_radius=self.settings.uism.SB__CHECKBOX_CORNER_RADIUS, + border_width=self.settings.uism.SB__CHECKBOX_BORDER_WIDTH, + border_color=self.settings.ctm.SB__CHECKBOX_BORDER_COLOR, + hover_color=self.settings.ctm.SB__CHECKBOX_HOVER_COLOR, + checkmark_color=self.settings.ctm.SB__CHECKBOX_CHECKMARK_COLOR, + fg_color=self.settings.ctm.SB__CHECKBOX_CHECKED_COLOR, + ) + setattr(self.config_window, checkbox_attr_name, checkbox_widget) + + checkbox_widget.grid(row=0, column=2, sticky="e") + + return setting_box_frame + + + + def createSettingBoxSlider( @@ -452,6 +537,237 @@ class _SettingBoxGenerator(): return setting_box_frame + + + + def createSettingBoxMessageFormatEntries(self, + base_entry_attr_name, + entry_textvariable_0, + entry_textvariable_1, + entry_textvariable_2, + textvariable_0, + textvariable_1, + entry_bind__Any_KeyRelease, + entry_bind__FocusOut=None, + ): + + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(base_entry_attr_name) + + + all_wrapper = CTkFrame(setting_box_item_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + all_wrapper.grid(row=1, column=0, sticky="ew") + + all_wrapper.grid_columnconfigure(0, weight=1) + + + example_box_wrapper = CTkFrame(all_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + example_box_wrapper.grid(row=0, column=0, pady=self.settings.uism.SB__MESSAGE_FORMAT__ENTRIES_BOTTOM_PADY, sticky="ew") + + entries_wrapper = CTkFrame(all_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + entries_wrapper.grid(row=1, column=0, pady=self.settings.uism.SB__MESSAGE_FORMAT__ENTRIES_BOTTOM_PADY, sticky="ew") + + swap_button_wrapper = CTkFrame(all_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + swap_button_wrapper.grid(row=2, column=0, sticky="e") + + + + + + example_box_wrapper.grid_columnconfigure((0,2), weight=1) + example_frame_widget = CTkFrame(example_box_wrapper, corner_radius=self.settings.uism.SB__MESSAGE_FORMAT__EXAMPLE_CORNER_RADIUS, fg_color=self.settings.ctm.SB__MESSAGE_FORMAT__EXAMPLE_BG_COLOR, width=0, height=0) + example_frame_widget.grid(row=0, column=1) + + example_frame_widget.grid_rowconfigure((0,2), weight=1) + example_frame_widget.grid_columnconfigure((0,2), weight=1) + example_label_widget = CTkLabel( + example_frame_widget, + textvariable=self.view_variable.VAR_LABEL_EXAMPLE_TEXT_MESSAGE_FORMAT, + anchor="center", + justify="center", + wraplength=self.settings.uism.SB__MESSAGE_FORMAT__EXAMPLE_WRAP_LENGTH, + height=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__MESSAGE_FORMAT__REQUIRED_TEXT_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_TEXT_COLOR, + ) + example_label_widget.grid(row=1, column=1, padx=self.settings.uism.SB__MESSAGE_FORMAT__EXAMPLE_IPADXY, pady=self.settings.uism.SB__MESSAGE_FORMAT__EXAMPLE_IPADXY, sticky="ew") + + self.config_window.additional_widgets.append(example_box_wrapper) + + + + + entry_textvariables = [entry_textvariable_0, entry_textvariable_1, entry_textvariable_2] + for i in range(3): + entry_widget = CTkEntry( + entries_wrapper, + text_color=self.settings.ctm.SB__ENTRY_TEXT_COLOR, + fg_color=self.settings.ctm.SB__ENTRY_BG_COLOR, + border_color=self.settings.ctm.SB__ENTRY_BORDER_COLOR, + height=self.settings.uism.SB__MESSAGE_FORMAT__ENTRY_HEIGHT, + textvariable=entry_textvariables[i], + justify="center", + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__ENTRY_FONT_SIZE, weight="normal"), + ) + setattr(self.config_window, base_entry_attr_name + "_" + str(i), entry_widget) + + + + if entry_bind__FocusOut is not None: + entry_widget.bind("", entry_bind__FocusOut, "+") + + + label_frame_widget_0 = CTkFrame(entries_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + + label_frame_widget_0.grid_rowconfigure((0,2), weight=1) + label_frame_widget_0.grid_columnconfigure(0, weight=1) + label_widget_0 = CTkLabel( + label_frame_widget_0, + textvariable=textvariable_0, + anchor="center", + height=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__MESSAGE_FORMAT__REQUIRED_TEXT_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_TEXT_COLOR + ) + label_widget_0.grid(row=1, column=0, padx=0, pady=0, sticky="ew") + + + label_frame_widget_1 = CTkFrame(entries_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + + label_frame_widget_1.grid_rowconfigure((0,2), weight=1) + label_frame_widget_1.grid_columnconfigure(0, weight=1) + label_widget_1 = CTkLabel( + label_frame_widget_1, + textvariable=textvariable_1, + anchor="center", + height=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__MESSAGE_FORMAT__REQUIRED_TEXT_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_TEXT_COLOR + ) + label_widget_1.grid(row=1, column=0, padx=0, pady=0, sticky="ew") + + + entries_wrapper.grid_columnconfigure((0,2,4), weight=1) + entries_wrapper.grid_columnconfigure((1,3), weight=0, uniform="message_format_fixed_labels") + + entry_widget_0 = getattr(self.config_window, base_entry_attr_name+"_0") + entry_widget_1 = getattr(self.config_window, base_entry_attr_name+"_1") + entry_widget_2 = getattr(self.config_window, base_entry_attr_name+"_2") + entry_widget_0.grid(row=0, column=0, sticky="ew") + entry_widget_1.grid(row=0, column=2, sticky="ew") + entry_widget_2.grid(row=0, column=4, sticky="ew") + label_frame_widget_0.grid(row=0, column=1, padx=self.settings.uism.SB__MESSAGE_FORMAT__REQUIRED_TEXT_PADX, sticky="ew") + label_frame_widget_1.grid(row=0, column=3, padx=self.settings.uism.SB__MESSAGE_FORMAT__REQUIRED_TEXT_PADX, sticky="ew") + + def adjusted_command__for_entry_bind__Any_KeyRelease(_e): + message_format = entry_widget_0.get() + textvariable_0.get() + entry_widget_1.get() + textvariable_1.get() + entry_widget_2.get() + entry_bind__Any_KeyRelease(message_format) + + + entry_widget_0.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) + entry_widget_1.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) + entry_widget_2.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) + + + + + + + swap_button = CTkFrame(swap_button_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.SB__MESSAGE_FORMAT__SWAP_BUTTON_COLOR, cursor="hand2") + swap_button.grid(row=0, column=2, sticky="ew") + + + swap_button.grid_columnconfigure(0, weight=1) + swap_button_label_wrapper = CTkFrame(swap_button, corner_radius=0, fg_color=self.settings.ctm.SB__MESSAGE_FORMAT__SWAP_BUTTON_COLOR) + swap_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__MESSAGE_FORMAT__SWAP_BUTTON_IPADX, pady=self.settings.uism.SB__MESSAGE_FORMAT__SWAP_BUTTON_IPADY, sticky="ew") + + + swap_button_label_wrapper.grid_columnconfigure((0,4), weight=1) + swap_button_label_wrapper.grid_rowconfigure((0,2), weight=1) + + swap_button_label_0 = CTkLabel( + swap_button_label_wrapper, + textvariable=textvariable_0, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__MESSAGE_FORMAT__SWAP_BUTTON_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.LABELS_TEXT_COLOR, + ) + swap_button_label_0.grid(row=1, column=1) + + swap_button_both_direction_arrow_img = CTkLabel( + swap_button_label_wrapper, + text=None, + height=0, + corner_radius=0, + image=CTkImage((self.settings.image_file.SWAP_ICON), size=self.settings.uism.SB__MESSAGE_FORMAT__SWAP_BUTTON_ARROWS_IMG_SIZE), + anchor="w", + text_color=self.settings.ctm.LABELS_TEXT_COLOR, + ) + swap_button_both_direction_arrow_img.grid(row=1, column=2, padx=self.settings.uism.SB__MESSAGE_FORMAT__SWAP_TEXT_PADX) + + swap_button_label_1 = CTkLabel( + swap_button_label_wrapper, + textvariable=textvariable_1, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__MESSAGE_FORMAT__SWAP_BUTTON_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.LABELS_TEXT_COLOR, + ) + swap_button_label_1.grid(row=1, column=3) + + + def adjustedCommand(): + callFunctionIfCallable(self.view_variable.CALLBACK_SWAP_MESSAGE_FORMAT_REQUIRED_TEXT) + + bindButtonFunctionAndColor( + target_widgets=[ + swap_button, + swap_button_label_wrapper, + swap_button_label_0, + swap_button_both_direction_arrow_img, + swap_button_label_1, + ], + enter_color=self.settings.ctm.SB__MESSAGE_FORMAT__SWAP_BUTTON_HOVERED_COLOR, + leave_color=self.settings.ctm.SB__MESSAGE_FORMAT__SWAP_BUTTON_COLOR, + clicked_color=self.settings.ctm.SB__MESSAGE_FORMAT__SWAP_BUTTON_CLICKED_COLOR, + buttonReleasedFunction=lambda _e: adjustedCommand(), + ) + + + return setting_box_frame + + + + def createSettingBoxButtonWithImage( + self, + for_var_label_text, for_var_desc_text, + button_attr_name, + button_image, + button_command, + ): + + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(button_attr_name, for_var_label_text, for_var_desc_text) + + + button_with_image_widget = createButtonWithImage( + parent_widget=setting_box_item_frame, + button_fg_color=self.settings.ctm.SB__BUTTON_COLOR, + button_enter_color=self.settings.ctm.SB__BUTTON_HOVERED_COLOR, + button_clicked_color=self.settings.ctm.SB__BUTTON_CLICKED_COLOR, + button_image_file=button_image, + button_image_size=self.settings.uism.SB__OPEN_CONFIG_FILE_BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__OPEN_CONFIG_FILE_BUTTON_IPADXY, + button_command=button_command, + ) + button_with_image_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") + + return setting_box_frame + + + + def createSettingBoxArrowSwitch( self, for_var_label_text, for_var_desc_text, @@ -482,12 +798,12 @@ class _SettingBoxGenerator(): for_opening_button_wrapper = createButtonWithImage( parent_widget=setting_box_item_frame, - button_fg_color=self.settings.ctm.SB__ARROW_SWITCH_BUTTON_COLOR, - button_enter_color=self.settings.ctm.SB__ARROW_SWITCH_BUTTON_HOVERED_COLOR, - button_clicked_color=self.settings.ctm.SB__ARROW_SWITCH_BUTTON_CLICKED_COLOR, + button_fg_color=self.settings.ctm.SB__BUTTON_COLOR, + button_enter_color=self.settings.ctm.SB__BUTTON_HOVERED_COLOR, + button_clicked_color=self.settings.ctm.SB__BUTTON_CLICKED_COLOR, button_image_file=self.settings.image_file.ARROW_LEFT.rotate(270), - button_image_size=self.settings.uism.SB__ARROW_SWITCH_BUTTON_ICON_SIZE, - button_ipadxy=self.settings.uism.SB__ARROW_SWITCH_BUTTON_IPADXY, + button_image_size=self.settings.uism.SB__BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__BUTTON_IPADXY, button_command=open_command, ) for_opening_button_wrapper.grid(row=1, column=ARROW_BUTTON_COLUMN, padx=self.settings.uism.SB__ARROW_SWITCH_LEFT_PADX, sticky="e") @@ -496,12 +812,12 @@ class _SettingBoxGenerator(): for_closing_button_wrapper = createButtonWithImage( parent_widget=setting_box_item_frame, - button_fg_color=self.settings.ctm.SB__ARROW_SWITCH_BUTTON_COLOR, - button_enter_color=self.settings.ctm.SB__ARROW_SWITCH_BUTTON_HOVERED_COLOR, - button_clicked_color=self.settings.ctm.SB__ARROW_SWITCH_BUTTON_CLICKED_COLOR, + button_fg_color=self.settings.ctm.SB__BUTTON_COLOR, + button_enter_color=self.settings.ctm.SB__BUTTON_HOVERED_COLOR, + button_clicked_color=self.settings.ctm.SB__BUTTON_CLICKED_COLOR, button_image_file=self.settings.image_file.ARROW_LEFT.rotate(90), - button_image_size=self.settings.uism.SB__ARROW_SWITCH_BUTTON_ICON_SIZE, - button_ipadxy=self.settings.uism.SB__ARROW_SWITCH_BUTTON_IPADXY, + button_image_size=self.settings.uism.SB__BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__BUTTON_IPADXY, button_command=close_command, ) for_closing_button_wrapper.grid(row=1, column=ARROW_BUTTON_COLUMN, padx=self.settings.uism.SB__ARROW_SWITCH_LEFT_PADX, sticky="e") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 4c6ef337..bb305ecf 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -5,6 +5,7 @@ from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxEntry = sbg.createSettingBoxEntry + createSettingBoxButtonWithImage = sbg.createSettingBoxButtonWithImage def entry_ip_address_callback(value): @@ -13,6 +14,9 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin def entry_port_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_OSC_PORT, value) + def open_config_filepath_callback(): + callFunctionIfCallable(view_variable.CALLBACK_OPEN_FILEPATH_CONFIG_FILE) + row=0 config_window.sb__ip_address = createSettingBoxEntry( for_var_label_text=view_variable.VAR_LABEL_OSC_IP_ADDRESS, @@ -34,5 +38,15 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), entry_textvariable=view_variable.VAR_OSC_PORT, ) - config_window.sb__port.grid(row=row, pady=0) + config_window.sb__port.grid(row=row) row+=1 + + config_window.sb__open_config_filepath = createSettingBoxButtonWithImage( + for_var_label_text=view_variable.VAR_LABEL_OPEN_CONFIG_FILEPATH, + for_var_desc_text=view_variable.VAR_DESC_OPEN_CONFIG_FILEPATH, + button_attr_name="sb__button_open_config_filepath", + button_command=lambda _e: open_config_filepath_callback(), + button_image=settings.image_file.FOLDER_OPEN_ICON, + ) + config_window.sb__open_config_filepath.grid(row=row, pady=0) + row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index e4f46fb6..161e4bfc 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -5,7 +5,9 @@ from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - createSettingBoxEntry = sbg.createSettingBoxEntry + createSettingBoxAutoExportMessageLogs = sbg.createSettingBoxAutoExportMessageLogs + createSettingBox_Labels = sbg.createSettingBox_Labels + createSettingBoxMessageFormatEntries = sbg.createSettingBoxMessageFormatEntries # 元 checkbox_auto_clear_chatbox_callback @@ -18,6 +20,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_auto_export_message_logs_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) + def button_auto_export_message_logs_callback(): + callFunctionIfCallable(view_variable.CALLBACK_OPEN_FILEPATH_LOGS) + def checkbox_enable_send_message_to_vrc_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_box_widget.get()) @@ -52,24 +57,37 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row+=1 - config_window.sb__auto_export_message_logs = createSettingBoxCheckbox( + config_window.sb__auto_export_message_logs = createSettingBoxAutoExportMessageLogs( for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_attr_name="sb__checkbox_auto_export_message_logs", - command=lambda: checkbox_auto_export_message_logs_callback(config_window.sb__checkbox_auto_export_message_logs), + checkbox_command=lambda: checkbox_auto_export_message_logs_callback(config_window.sb__checkbox_auto_export_message_logs), + button_command=lambda _e: button_auto_export_message_logs_callback(), variable=view_variable.VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, ) config_window.sb__auto_export_message_logs.grid(row=row) row+=1 - config_window.sb__message_format = createSettingBoxEntry( + config_window.sb__message_format_labels = createSettingBox_Labels( for_var_label_text=view_variable.VAR_LABEL_MESSAGE_FORMAT, for_var_desc_text=view_variable.VAR_DESC_MESSAGE_FORMAT, - entry_attr_name="sb__entry_message_format", - entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_250, + labels_attr_name="sb__labels_message_format", + ) + config_window.sb__message_format_labels.grid(row=row, pady=0) + row+=1 + + config_window.sb__message_format = createSettingBoxMessageFormatEntries( + base_entry_attr_name="sb__entry_message_format", + # entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_150, + entry_textvariable_0=view_variable.VAR_ENTRY_0_MESSAGE_FORMAT, + entry_textvariable_1=view_variable.VAR_ENTRY_1_MESSAGE_FORMAT, + entry_textvariable_2=view_variable.VAR_ENTRY_2_MESSAGE_FORMAT, + textvariable_0=view_variable.VAR_TEXT_REQUIRED_0_MESSAGE_FORMAT, + textvariable_1=view_variable.VAR_TEXT_REQUIRED_1_MESSAGE_FORMAT, entry_bind__Any_KeyRelease=lambda value: entry_message_format_callback(value), - entry_textvariable=view_variable.VAR_MESSAGE_FORMAT, + # entry_textvariable=view_variable.VAR_MESSAGE_FORMAT, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MESSAGE_FORMAT, ) config_window.sb__message_format.grid(row=row) row+=1 diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 088a3075..65661216 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -68,7 +68,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): text=None, corner_radius=0, height=0, - image=CTkImage(settings.image_file.REFRESH_ICON.rotate(25), size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) + image=CTkImage(settings.image_file.REFRESH_UPDATE_ICON.rotate(25), size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) ) vrct_gui.update_available_icon.grid(row=1, column=0, padx=(settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX, settings.uism.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON), pady=0) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 346afae5..7561e1ae 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox, bindButtonFunctionAndColor, bindEnterAndLeaveFunction from utils import callFunctionIfCallable @@ -226,36 +226,85 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): # Both direction arrow icon main_window.sls__arrow_direction_box = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) - main_window.sls__arrow_direction_box.grid(row=3, column=0, pady=settings.uism.SLS__BOX_ARROWS_PADY,sticky="ew") + main_window.sls__arrow_direction_box.grid(row=3, column=0, pady=settings.uism.SLS__BOX_ARROWS_PADY, sticky="ew") - main_window.sls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) + main_window.sls__arrow_direction_box.grid_columnconfigure((0,2), weight=0, minsize=settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_PADX) + main_window.sls__arrow_direction_box.grid_columnconfigure(1, weight=1) + + main_window.sls__arrow_direction_swap_box = CTkFrame(main_window.sls__arrow_direction_box, corner_radius=settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0, cursor="hand2") + main_window.sls__arrow_direction_swap_box.grid(row=0, column=1, ipady=settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_IPADY, sticky="ew") + + main_window.sls__arrow_direction_swap_box.grid_rowconfigure((0,2), weight=1) + main_window.sls__arrow_direction_swap_box.grid_columnconfigure(1, weight=1) main_window.sls__both_direction_up = CTkLabel( - main_window.sls__arrow_direction_box, + main_window.sls__arrow_direction_swap_box, text=None, height=0, image=CTkImage((settings.image_file.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) - main_window.sls__both_direction_up.grid(row=0, column=1, pady=0) + main_window.sls__both_direction_up.grid(row=1, column=0, padx=(settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_IPADX, 0), pady=0) main_window.sls__both_direction_desc = CTkLabel( - main_window.sls__arrow_direction_box, + main_window.sls__arrow_direction_swap_box, textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_DESC, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, ) - main_window.sls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX) + main_window.sls__both_direction_desc.grid(row=1, column=1, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX) - main_window.sls__both_direction_label_down = CTkLabel( - main_window.sls__arrow_direction_box, + main_window.sls__both_direction_down = CTkLabel( + main_window.sls__arrow_direction_swap_box, text=None, height=0, image=CTkImage((settings.image_file.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) - main_window.sls__both_direction_label_down.grid(row=0, column=3) + main_window.sls__both_direction_down.grid(row=1, column=2, padx=(0, settings.uism.SLS__BOX_ARROWS_SWAP_BUTTON_IPADX)) + + + + def adjustedCommand_ButtonReleased(): + callFunctionIfCallable(view_variable.CALLBACK_SWAP_LANGUAGES) + + bindButtonFunctionAndColor( + target_widgets=[ + main_window.sls__arrow_direction_swap_box, + main_window.sls__both_direction_up, + main_window.sls__both_direction_desc, + main_window.sls__both_direction_down + ], + enter_color=settings.ctm.SLS__BOX_ARROWS_SWAP_BUTTON_HOVERED_COLOR, + leave_color=settings.ctm.SLS__BG_COLOR, + clicked_color=settings.ctm.SLS__BOX_ARROWS_SWAP_BUTTON_CLICKED_COLOR, + buttonReleasedFunction=lambda _e: adjustedCommand_ButtonReleased(), + ) + + + def adjustedCommand_Entered(): + main_window.sls__both_direction_desc.configure( + textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_SWAP_BUTTON, + text_color=settings.ctm.SLS__BOX_ARROWS_SWAP_BUTTON_TEXT_COLOR, + ) + + def adjustedCommand_Leaved(): + main_window.sls__both_direction_desc.configure( + textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_DESC, + text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, + ) + + bindEnterAndLeaveFunction( + target_widgets=[ + main_window.sls__arrow_direction_swap_box, + main_window.sls__both_direction_up, + main_window.sls__both_direction_desc, + main_window.sls__both_direction_down + ], + enterFunction=lambda _e: adjustedCommand_Entered(), + leaveFunction=lambda _e: adjustedCommand_Leaved(), + ) diff --git a/vrct_gui/ui_managers/Themes/_darkTheme.py b/vrct_gui/ui_managers/Themes/_darkTheme.py index 2522608a..e1b42ed2 100644 --- a/vrct_gui/ui_managers/Themes/_darkTheme.py +++ b/vrct_gui/ui_managers/Themes/_darkTheme.py @@ -78,6 +78,9 @@ def _darkTheme(base_color): SLS__BOX_BG_COLOR = base_color.DARK_825_COLOR, SLS__BOX_SECTION_TITLE_TEXT_COLOR = base_color.DARK_400_COLOR, SLS__BOX_ARROWS_TEXT_COLOR = base_color.DARK_500_COLOR, + SLS__BOX_ARROWS_SWAP_BUTTON_TEXT_COLOR = base_color.DARK_BASIC_TEXT_COLOR, + SLS__BOX_ARROWS_SWAP_BUTTON_HOVERED_COLOR = base_color.DARK_750_COLOR, + SLS__BOX_ARROWS_SWAP_BUTTON_CLICKED_COLOR = base_color.DARK_850_COLOR, SLS__OPTIONMENU_BG_COLOR = base_color.DARK_888_COLOR, SLS__OPTIONMENU_HOVERED_BG_COLOR = base_color.DARK_875_COLOR, @@ -157,6 +160,10 @@ def _darkTheme(base_color): LABELS_TEXT_DISABLED_COLOR = base_color.DARK_600_COLOR, + SB__BUTTON_COLOR = base_color.DARK_888_COLOR, + SB__BUTTON_HOVERED_COLOR = base_color.DARK_800_COLOR, + SB__BUTTON_CLICKED_COLOR = base_color.DARK_900_COLOR, + # Top bar TOP_BAR_BG_COLOR = base_color.DARK_850_COLOR, @@ -228,11 +235,6 @@ def _darkTheme(base_color): SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = base_color.PRIMARY_500_COLOR, SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = base_color.PRIMARY_800_COLOR, - - SB__ARROW_SWITCH_BUTTON_COLOR = base_color.DARK_888_COLOR, - SB__ARROW_SWITCH_BUTTON_HOVERED_COLOR = base_color.DARK_800_COLOR, - SB__ARROW_SWITCH_BUTTON_CLICKED_COLOR = base_color.DARK_900_COLOR, - SB__ADD_AND_DELETE_ABLE_LIST__ADD_BUTTON_COLOR = base_color.PRIMARY_600_COLOR, SB__ADD_AND_DELETE_ABLE_LIST__ADD_BUTTON_HOVERED_COLOR = base_color.PRIMARY_500_COLOR, SB__ADD_AND_DELETE_ABLE_LIST__ADD_BUTTON_CLICKED_COLOR = base_color.PRIMARY_700_COLOR, @@ -246,6 +248,13 @@ def _darkTheme(base_color): SB__ADD_AND_DELETE_ABLE_LIST__VALUES_DELETED_BUTTON_CLICKED_BG_COLOR = base_color.DARK_900_COLOR, + SB__MESSAGE_FORMAT__EXAMPLE_BG_COLOR = "#3a4554", # from VRChat' chat display color + SB__MESSAGE_FORMAT__SWAP_BUTTON_COLOR = base_color.DARK_875_COLOR, + SB__MESSAGE_FORMAT__SWAP_BUTTON_HOVERED_COLOR = base_color.DARK_800_COLOR, + SB__MESSAGE_FORMAT__SWAP_BUTTON_CLICKED_COLOR = base_color.DARK_888_COLOR, + + + # Side menu SIDE_MENU_BG_COLOR = base_color.DARK_950_COLOR, @@ -289,11 +298,14 @@ def _darkTheme(base_color): ARROW_LEFT = getImageFileFromUiUtils("arrow_left_white.png"), ARROW_LEFT_DISABLED = getImageFileFromUiUtils("arrow_left_disabled.png"), + REFRESH_UPDATE_ICON = getImageFileFromUiUtils("refresh_update_icon.png"), REFRESH_ICON = getImageFileFromUiUtils("refresh_icon.png"), HELP_ICON = getImageFileFromUiUtils("help_icon_white.png"), CANCEL_ICON = getImageFileFromUiUtils("cancel_icon.png"), REDO_ICON = getImageFileFromUiUtils("redo_white.png"), + SWAP_ICON = getImageFileFromUiUtils("swap_icon.png"), + FOLDER_OPEN_ICON = getImageFileFromUiUtils("folder_open_icon.png"), ), ) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 9a1ad1c2..b3e7f937 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -94,7 +94,11 @@ class UiScalingManager(): self.main.SLS__BOX_OPTION_MENU_IPADY = self._calculateUiSize(2) self.main.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE = (self._calculateUiSize(20), self._calculateUiSize(20)) # self.main.SLS__BOX_OPTION_MENU_WIDTH = self._calculateUiSize(200) - self.main.SLS__BOX_ARROWS_PADY = self._calculateUiSize(10) + self.main.SLS__BOX_ARROWS_PADY = self._calculateUiSize(4) + self.main.SLS__BOX_ARROWS_SWAP_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) + self.main.SLS__BOX_ARROWS_SWAP_BUTTON_PADX = self._calculateUiSize(20) + self.main.SLS__BOX_ARROWS_SWAP_BUTTON_IPADX = self._calculateUiSize(8) + self.main.SLS__BOX_ARROWS_SWAP_BUTTON_IPADY = self._calculateUiSize(6) self.main.SLS__BOX_ARROWS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(16)) self.main.SLS__BOX_ARROWS_DESC_FONT_SIZE = self._calculateUiSize(12) self.main.SLS__BOX_ARROWS_DESC_PADX = self._calculateUiSize(6) @@ -278,10 +282,6 @@ class UiScalingManager(): self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY = self._calculateUiSize(10) self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE = self._calculateUiSize(20) - - - self.config_window.SB__ARROW_SWITCH_BUTTON_IPADXY = self._calculateUiSize(16) - self.config_window.SB__ARROW_SWITCH_BUTTON_ICON_SIZE = self._calculateUiSize(24) self.config_window.SB__ARROW_SWITCH_DESC_FONT_SIZE = self._calculateUiSize(16) self.config_window.SB__ARROW_SWITCH_LEFT_PADX = (self._calculateUiSize(20), 0) @@ -302,6 +302,31 @@ class UiScalingManager(): self.config_window.ADD_AND_DELETE_ABLE_LIST__ADD_BUTTON_LEFT_PADX = (self._calculateUiSize(20), 0) self.config_window.ADD_AND_DELETE_ABLE_LIST__ADD_BUTTON_FONT_SIZE = self._calculateUiSize(14) + + self.config_window.SB__MESSAGE_FORMAT__EXAMPLE_CORNER_RADIUS = self._calculateUiSize(16) + self.config_window.SB__MESSAGE_FORMAT__EXAMPLE_IPADXY = self._calculateUiSize(10) + self.config_window.SB__MESSAGE_FORMAT__EXAMPLE_WRAP_LENGTH = self._calculateUiSize(300) + + self.config_window.SB__MESSAGE_FORMAT__ENTRY_HEIGHT = self.config_window.SB__ENTRY_HEIGHT + self.config_window.SB__MESSAGE_FORMAT__REQUIRED_TEXT_PADX = self._calculateUiSize(10) + self.config_window.SB__MESSAGE_FORMAT__REQUIRED_TEXT_FONT_SIZE = self._calculateUiSize(16) + + self.config_window.SB__MESSAGE_FORMAT__SWAP_BUTTON_ARROWS_IMG_SIZE = self.dupTuple(self._calculateUiSize(20)) + self.config_window.SB__MESSAGE_FORMAT__SWAP_BUTTON_IPADX = self._calculateUiSize(16) + self.config_window.SB__MESSAGE_FORMAT__SWAP_BUTTON_IPADY = self._calculateUiSize(6) + self.config_window.SB__MESSAGE_FORMAT__SWAP_BUTTON_FONT_SIZE = self._calculateUiSize(14) + self.config_window.SB__MESSAGE_FORMAT__SWAP_TEXT_PADX = self._calculateUiSize(10) + + self.config_window.SB__MESSAGE_FORMAT__ENTRIES_BOTTOM_PADY = (0, self._calculateUiSize(14)) + + + self.config_window.SB__BUTTON_IPADXY = self._calculateUiSize(16) + self.config_window.SB__BUTTON_ICON_SIZE = self._calculateUiSize(24) + + self.config_window.SB__OPEN_CONFIG_FILE_BUTTON_IPADXY = self._calculateUiSize(10) + self.config_window.SB__OPEN_CONFIG_FILE_BUTTON_ICON_SIZE = self._calculateUiSize(20) + + def _calculateUiSize(self, default_size, is_allowed_odd:bool=False, is_zero_allowed:bool=False): size = calculateUiSize(default_size, self.SCALING_FLOAT, is_allowed_odd, is_zero_allowed) return size