diff --git a/.gitignore b/.gitignore index 4abb9718..e7b75066 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ weights/ .vscode error.log *.exe +*.ipynb diff --git a/config.py b/config.py index edef07e4..e069ef7e 100644 --- a/config.py +++ b/config.py @@ -737,16 +737,47 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - @json_serializable('ENABLE_NOTICE_OVERLAY') - def ENABLE_NOTICE_OVERLAY(self): - return self._ENABLE_NOTICE_OVERLAY + @json_serializable('OVERLAY_SETTINGS') + def OVERLAY_SETTINGS(self): + return self._OVERLAY_SETTINGS - @ENABLE_NOTICE_OVERLAY.setter - def ENABLE_NOTICE_OVERLAY(self, value): + @OVERLAY_SETTINGS.setter + def OVERLAY_SETTINGS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SETTINGS.keys()): + for key, value in value.items(): + if isinstance(value, float): + self._OVERLAY_SETTINGS[key] = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SETTINGS) + + @property + @json_serializable('ENABLE_OVERLAY_SMALL_LOG') + def ENABLE_OVERLAY_SMALL_LOG(self): + return self._ENABLE_OVERLAY_SMALL_LOG + + @ENABLE_OVERLAY_SMALL_LOG.setter + def ENABLE_OVERLAY_SMALL_LOG(self, value): if isinstance(value, bool): - self._ENABLE_NOTICE_OVERLAY = value + self._ENABLE_OVERLAY_SMALL_LOG = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('OVERLAY_SMALL_LOG_SETTINGS') + def OVERLAY_SMALL_LOG_SETTINGS(self): + return self._OVERLAY_SMALL_LOG_SETTINGS + + @OVERLAY_SMALL_LOG_SETTINGS.setter + def OVERLAY_SMALL_LOG_SETTINGS(self, value): + if isinstance(value, dict) and set(value.keys()) == set(self.OVERLAY_SMALL_LOG_SETTINGS.keys()): + for key, value in value.items(): + match (key): + case "x_pos" | "y_pos" | "depth": + if isinstance(value, float): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = value + case "display_duration" | "fadeout_duration": + if isinstance(value, int): + self._OVERLAY_SMALL_LOG_SETTINGS[key] = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.OVERLAY_SMALL_LOG_SETTINGS) + @property @json_serializable('OVERLAY_UI_TYPE') def OVERLAY_UI_TYPE(self): @@ -1023,7 +1054,18 @@ class Config: self._ENABLE_SEND_ONLY_TRANSLATED_MESSAGES = False self._SEND_MESSAGE_BUTTON_TYPE = "show" self._ENABLE_NOTICE_XSOVERLAY = False - self._ENABLE_NOTICE_OVERLAY = False + self._OVERLAY_SETTINGS = { + "opacity": 1.0, + "ui_scaling": 1.0, + } + self._ENABLE_OVERLAY_SMALL_LOG = False + self._OVERLAY_SMALL_LOG_SETTINGS = { + "x_pos": 0.0, + "y_pos": -0.41, + "depth": 1.0, + "display_duration": 5, + "fadeout_duration": 2, + } self._OVERLAY_UI_TYPE = "default" self._ENABLE_SEND_MESSAGE_TO_VRC = True self._ENABLE_SEND_RECEIVED_MESSAGE_TO_VRC = False # Speaker2Chatbox diff --git a/controller.py b/controller.py index 797f9599..67d492ff 100644 --- a/controller.py +++ b/controller.py @@ -101,11 +101,11 @@ def sendMicMessage(message): translation = f" ({translation})" model.logger.info(f"[SENT] {message}{translation}") - # if config.ENABLE_NOTICE_OVERLAY is True: + # if config.ENABLE_OVERLAY_SMALL_LOG is True: # overlay_image = model.createOverlayImageShort(message, translation) - # model.setOverlayImage(overlay_image) + # model.updateOverlay(overlay_image) # overlay_image = model.createOverlayImageLong("send", message, translation) - # model.setOverlayImage(overlay_image) + # model.updateOverlay(overlay_image) def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage, view.printToTextbox_TranscriptionSendNoDeviceError) @@ -164,11 +164,11 @@ def receiveSpeakerMessage(message): if model.th_overlay is None: model.startOverlay() - if config.ENABLE_NOTICE_OVERLAY is True: + if config.ENABLE_OVERLAY_SMALL_LOG is True: overlay_image = model.createOverlayImageShort(message, translation) - model.setOverlayImage(overlay_image) + model.updateOverlay(overlay_image) # overlay_image = model.createOverlayImageLong("receive", message, translation) - # model.setOverlayImage(overlay_image) + # model.updateOverlay(overlay_image) # ------------Speaker2Chatbox------------ if config.ENABLE_SPEAKER2CHATBOX is True: @@ -245,11 +245,11 @@ def sendChatMessage(message): osc_message = messageFormatter("SEND", translation, message) model.oscSendMessage(osc_message) - # if config.ENABLE_NOTICE_OVERLAY is True: + # if config.ENABLE_OVERLAY_SMALL_LOG is True: # overlay_image = model.createOverlayImageShort(message, translation) - # model.setOverlayImage(overlay_image) + # model.updateOverlay(overlay_image) # overlay_image = model.createOverlayImageLong("send", message, translation) - # model.setOverlayImage(overlay_image) + # model.updateOverlay(overlay_image) # update textbox message log (Sent) view.printToTextbox_SentMessage(message, translation) @@ -858,6 +858,39 @@ def callbackSetWhisperWeightType(value): config.SELECTED_TRANSCRIPTION_ENGINE = "Google" view.showRestartButtonIfRequired() +# VR Tab +def callbackSetOverlaySettings(value, set_type:str): + print("callbackSetOverlaySettings", value, set_type) + pre_settings = config.OVERLAY_SETTINGS + pre_settings[set_type] = value + config.OVERLAY_SETTINGS = pre_settings + match (set_type): + case "opacity": + model.updateOverlayImageOpacity() + case "ui_scaling": + model.updateOverlayImageUiScaling() + +def callbackSetEnableOverlaySmallLog(value): + print("callbackSetEnableOverlaySmallLog", value) + config.ENABLE_OVERLAY_SMALL_LOG = value + +def callbackSetOverlaySmallLogSettings(value, set_type:str): + print("callbackSetOverlaySmallLogSettings", value, set_type) + pre_settings = config.OVERLAY_SMALL_LOG_SETTINGS + pre_settings[set_type] = value + config.OVERLAY_SMALL_LOG_SETTINGS = pre_settings + match (set_type): + case "x_pos": + model.updateOverlayPosition() + case "y_pos": + model.updateOverlayPosition() + case "depth": + model.updateOverlayPosition() + case "display_duration": + model.updateOverlayTimes() + case "fadeout_duration": + model.updateOverlayTimes() + # Others Tab def callbackSetEnableAutoClearMessageBox(value): print("callbackSetEnableAutoClearMessageBox", value) @@ -1098,6 +1131,11 @@ def createMainWindow(splash): "callback_set_use_whisper_feature": callbackSetUserWhisperFeature, "callback_set_whisper_weight_type": callbackSetWhisperWeightType, + # VR Tab + "callback_set_overlay_settings": callbackSetOverlaySettings, + "callback_set_enable_overlay_small_log": callbackSetEnableOverlaySmallLog, + "callback_set_overlay_small_log_settings": callbackSetOverlaySmallLogSettings, + # Others Tab "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, "callback_set_send_only_translated_messages": callbackSetEnableSendOnlyTranslatedMessages, diff --git a/locales/en.yml b/locales/en.yml index b7d91c2d..9718045e 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -81,6 +81,7 @@ config_window: transcription_mic: Mic transcription_speaker: Speaker transcription_internal_model: Transcription Model + vr: VR others: Others others_send_message_formats: Message Formats (Send) others_received_message_formats: Message Formats (XSOverlay & Speaker2Chatbox) @@ -205,6 +206,11 @@ config_window: model_template: "%{model_name} model (%{capacity})" recommended_model_template: "%{model_name} model (%{capacity}) (Recommended)" + + enable_overlay_small_log: + label: OpenVR + # desc: + auto_clear_the_message_box: label: Auto Clear The Message Box diff --git a/model.py b/model.py index affe6624..e41f4a04 100644 --- a/model.py +++ b/model.py @@ -72,8 +72,17 @@ class Model: self.previous_receive_message = "" self.translator = Translator() self.keyword_processor = KeywordProcessor() - self.overlay = Overlay() + self.overlay = Overlay( + config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"], + config.OVERLAY_SMALL_LOG_SETTINGS["depth"], + config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"], + config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"], + config.OVERLAY_SETTINGS["opacity"], + config.OVERLAY_SETTINGS["ui_scaling"], + ) self.overlay_image = OverlayImage() + self.pre_overlay_message = None self.th_overlay = None def checkCTranslatorCTranslate2ModelWeight(self): @@ -579,21 +588,28 @@ class Model: your_language = config.TARGET_LANGUAGE target_language = config.SOURCE_LANGUAGE ui_type = config.OVERLAY_UI_TYPE - return self.overlay_image.create_overlay_image_short(message, your_language, translation, target_language, ui_type) + self.pre_overlay_message = { + "message" : message, + "your_language" : your_language, + "translation" : translation, + "target_language" : target_language, + "ui_type" : ui_type, + } + return self.overlay_image.createOverlayImageShort(message, your_language, translation, target_language, ui_type) - def createOverlayImageLong(self, message_type, message, translation): - your_language = config.TARGET_LANGUAGE if message_type == "receive" else config.SOURCE_LANGUAGE - target_language = config.SOURCE_LANGUAGE if message_type == "receive" else config.TARGET_LANGUAGE - return self.overlay_image.create_overlay_image_long(message_type, message, your_language, translation, target_language) + # def createOverlayImageLong(self, message_type, message, translation): + # your_language = config.TARGET_LANGUAGE if message_type == "receive" else config.SOURCE_LANGUAGE + # target_language = config.SOURCE_LANGUAGE if message_type == "receive" else config.TARGET_LANGUAGE + # return self.overlay_image.create_overlay_image_long(message_type, message, your_language, translation, target_language) - def setOverlayImage(self, img): - if self.overlay.initFlag is True: - self.overlay.uiMan.uiUpdate(img) + def updateOverlay(self, img): + if self.overlay.initialized is True: + self.overlay.uiManager.uiUpdate(img) def startOverlay(self): - if self.overlay.initFlag is False: + if self.overlay.initialized is False: self.overlay.init() - if self.overlay.initFlag is True: + if self.overlay.initialized is True: self.th_overlay = threadFnc(self.overlay.startOverlay) self.th_overlay.daemon = True self.th_overlay.start() @@ -603,4 +619,30 @@ class Model: self.th_overlay.stop() self.th_overlay = None + def updateOverlayPosition(self): + if self.overlay.initialized is True: + pos = (config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"], config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]) + self.overlay.uiManager.setPosition(pos) + depth = config.OVERLAY_SMALL_LOG_SETTINGS["depth"] + self.overlay.uiManager.setDepth(depth) + self.overlay.uiManager.posUpdate() + + def updateOverlayTimes(self): + if self.overlay.initialized is True: + display_duration = config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"] + self.overlay.uiManager.setFadeTime(display_duration) + fadeout_duration = config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"] + self.overlay.uiManager.setFadeInterval(fadeout_duration) + self.overlay.uiManager.update() + + def updateOverlayImageOpacity(self): + if self.overlay.initialized is True: + opacity = config.OVERLAY_SETTINGS["opacity"] + self.overlay.uiManager.setTransparency(opacity) + + def updateOverlayImageUiScaling(self): + if self.overlay.initialized is True: + ui_scaling = config.OVERLAY_SETTINGS["ui_scaling"] + self.overlay.uiManager.setUiScaling(ui_scaling) + model = Model() \ No newline at end of file diff --git a/models/overlay/overlay.py b/models/overlay/overlay.py index 1f9f1fdd..6091bc5f 100644 --- a/models/overlay/overlay.py +++ b/models/overlay/overlay.py @@ -5,7 +5,7 @@ import asyncio import openvr from PIL import Image -def check_steamvr_running(): +def checkSteamvrRunning(): for proc in psutil.process_iter(): if "vrserver" in proc.name().lower() or "vrcompositor" in proc.name().lower(): return True @@ -23,24 +23,23 @@ def mat34Id(): class UIElement: def __init__(self, overlayRoot, key: str, name: str, settings: dict = None) -> None: """ - pos is a 2-tuple representing (x, y) normalised position of the overlay on the screen + pos is a 2-tuple representing (x, y) normalized position of the overlay on the screen """ self.overlay = overlayRoot self.overlayKey = key self.overlayName = name self.settings = settings - pos = (self.settings['Normalised icon X position'], self.settings['Normalised icon Y position']) self.handle = self.overlay.createOverlay(self.overlayKey, self.overlayName) self.setImage(Image.new("RGBA", (1, 1), (0, 0, 0, 0))) # blank image for default - self.setColour(self.settings['Colour']) + self.setColor(self.settings['Color']) self.setTransparency(self.settings['Transparency']) self.overlay.setOverlayWidthInMeters( self.handle, - self.settings['Normalised icon width'] * self.settings['Icon plane depth'] + self.settings['Ui_scaling'] ) - self.setPosition(pos) + self.updatePosition() self.overlay.showOverlay(self.handle) def setImage(self, img): @@ -50,7 +49,7 @@ class UIElement: img = (ctypes.c_char * len(img)).from_buffer_copy(img) self.overlay.setOverlayRaw(self.handle, img, width, height, 4) - def setColour(self, col): + def setColor(self, col): """ col is a 3-tuple representing (r, g, b) """ @@ -59,16 +58,13 @@ class UIElement: def setTransparency(self, a): self.overlay.setOverlayAlpha(self.handle, a) - def setPosition(self, pos): - """ - pos is a 2-tuple representing normalised (x, y) - """ + def updatePosition(self): self.transform = mat34Id() # no rotation required for HMD attachment # assign position - self.transform[0][3] = pos[0] * self.settings['Icon plane depth'] - self.transform[1][3] = pos[1] * self.settings['Icon plane depth'] - self.transform[2][3] = - self.settings['Icon plane depth'] + self.transform[0][3] = self.settings["Normalized_icon_X_position"] * self.settings['Icon_plane_depth'] + self.transform[1][3] = self.settings["Normalized_icon_Y_position"] * self.settings['Icon_plane_depth'] + self.transform[2][3] = - self.settings['Icon_plane_depth'] self.overlay.setOverlayTransformTrackedDeviceRelative( self.handle, @@ -76,78 +72,114 @@ class UIElement: self.transform ) + def setPosition(self, pos): + """ + pos is a 2-tuple representing normalized (x, y) + """ + self.settings["Normalized_icon_X_position"] = pos[0] + self.settings["Normalized_icon_Y_position"] = pos[1] + + def setDepth(self, depth): + self.settings["Icon_plane_depth"] = depth + + def setUiScaling(self, ui_scaling): + self.overlay.setOverlayWidthInMeters( + self.handle, + ui_scaling, + ) + class UIManager: - def __init__(self, settings): + def __init__(self, overlay_key, overlay_name, settings): self.overlay = openvr.IVROverlay() self.settings = settings self.overlayUI = UIElement( self.overlay, - "VRCT", - "Receive UI Element", + overlay_key, + overlay_name, self.settings, ) + self.fadeRatio = 1 self.lastUpdate = time.monotonic() def update(self): currTime = time.monotonic() - if self.settings['Fade interval'] != 0: - self.evaluateTransparencyFade(self.overlayUI, self.lastUpdate, currTime) + if self.settings['Fade_interval'] != 0: + self.evaluateTransparencyFade(self.lastUpdate, currTime) def uiUpdate(self, img): self.overlayUI.setImage(img) self.overlayUI.setTransparency(self.settings['Transparency']) self.lastUpdate = time.monotonic() - def evaluateTransparencyFade(self, ui, lastUpdate, currentTime): - if (currentTime - lastUpdate) > self.settings['Fade time']: - timeThroughInterval = currentTime - lastUpdate - self.settings['Fade time'] - fadeRatio = 1 - timeThroughInterval / self.settings['Fade interval'] - if fadeRatio < 0: - fadeRatio = 0 + def evaluateTransparencyFade(self, lastUpdate, currentTime): + if (currentTime - lastUpdate) > self.settings['Fade_time']: + timeThroughInterval = currentTime - lastUpdate - self.settings['Fade_time'] + self.fadeRatio = 1 - timeThroughInterval / self.settings['Fade_interval'] + if self.fadeRatio < 0: + self.fadeRatio = 0 - ui.setTransparency(fadeRatio * self.settings['Transparency']) + self.overlayUI.setTransparency(self.fadeRatio * self.settings['Transparency']) - def posUpdate(self, pos): + def posUpdate(self): + self.overlayUI.updatePosition() + + def setPosition(self, pos): self.overlayUI.setPosition(pos) + def setDepth(self, depth): + self.overlayUI.setDepth(depth) + + def setFadeTime(self, fade_time): + self.settings["Fade_time"] = fade_time + + def setFadeInterval(self, fade_interval): + self.settings["Fade_interval"] = fade_interval + + def setUiScaling(self, ui_scaling): + self.overlayUI.setUiScaling(ui_scaling) + + def setTransparency(self, transparency): + self.settings["Transparency"] = transparency + self.overlayUI.setTransparency(self.fadeRatio * self.settings['Transparency']) + class Overlay: - def __init__(self): - self.initFlag = False + def __init__(self, x, y , depth, fade_time, fade_interval, transparency, ui_scaling): + self.initialized = False settings = { - "Colour": [1, 1, 1], - "Transparency": 1, - "Normalised icon X position": 0.0, - "Normalised icon Y position": -0.41, - "Icon plane depth": 1, - "Normalised icon width": 1, - "Fade time": 5, - "Fade interval": 2, + "Color": [1, 1, 1], + "Transparency": transparency, + "Normalized_icon_X_position": x, + "Normalized_icon_Y_position": y, + "Icon_plane_depth": depth, + "Fade_time": fade_time, + "Fade_interval": fade_interval, + "Ui_scaling": ui_scaling, } self.settings = settings def init(self): try: - if check_steamvr_running() is True: + if checkSteamvrRunning() is True: openvr.init(openvr.VRApplication_Overlay) - self.initFlag = True + self.initialized = True except Exception as e: print("Could not initialise OpenVR") async def mainLoop(self): while True: startTime = time.monotonic() - self.uiMan.update() + self.uiManager.update() sleepTime = (1 / 60) - (time.monotonic() - startTime) if sleepTime > 0: await asyncio.sleep(sleepTime) - async def init_main(self): - self.uiMan = UIManager(self.settings) + async def initMain(self): + self.uiManager = UIManager("Overlay_Speaker2log", "SOverlay_Speaker2log_UI", self.settings) await self.mainLoop() def startOverlay(self): - asyncio.run(self.init_main()) + asyncio.run(self.initMain()) if __name__ == '__main__': from overlay_image import OverlayImage @@ -173,24 +205,25 @@ if __name__ == '__main__': overlay = Overlay() overlay_image = OverlayImage() - if overlay.initFlag is False: + if overlay.initialized is False: overlay.init() - if overlay.initFlag is True: + if overlay.initialized is True: t = threadFnc(overlay.startOverlay) t.start() time.sleep(1) - img = overlay_image.create_overlay_image_short("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") - if overlay.initFlag is True: - overlay.uiMan.uiUpdate(img) + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "Hello,World!Goodbye", "Japanese", ui_type="sakura") + # img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", ui_type="sakura") + if overlay.initialized is True: + overlay.uiManager.uiUpdate(img) time.sleep(10) - img = overlay_image.create_overlay_image_short("こんにちは、世界!さようなら", "Japanese", "안녕하세요, 세계!안녕", "Korean") - if overlay.initFlag is True: - overlay.uiMan.uiUpdate(img) + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "안녕하세요, 세계!안녕", "Korean") + if overlay.initialized is True: + overlay.uiManager.uiUpdate(img) time.sleep(10) - img = overlay_image.create_overlay_image_short("こんにちは、世界!さようなら", "Japanese", "你好世界!再见", "Chinese Simplified") - if overlay.initFlag is True: - overlay.uiMan.uiUpdate(img) + img = overlay_image.createOverlayImageShort("こんにちは、世界!さようなら", "Japanese", "你好世界!再见", "Chinese Simplified") + if overlay.initialized is True: + overlay.uiManager.uiUpdate(img) time.sleep(10) \ No newline at end of file diff --git a/models/overlay/overlay_image.py b/models/overlay/overlay_image.py index 10eff3ad..269be290 100644 --- a/models/overlay/overlay_image.py +++ b/models/overlay/overlay_image.py @@ -4,10 +4,6 @@ from typing import Tuple from PIL import Image, ImageDraw, ImageFont class OverlayImage: - WIDTH = 1920//2 - HEIGHT = 46//2 - FONT_SIZE_SHORT = HEIGHT - # TEXT_COLOR_LARGE = (223, 223, 223) # TEXT_COLOR_SMALL = (190, 190, 190) # TEXT_COLOR_SEND = (70, 161, 146) @@ -23,17 +19,17 @@ class OverlayImage: } def __init__(self): - self.log_data = [] + pass @staticmethod - def concatenate_images_vertically(img1: Image, img2: Image) -> Image: + def concatenateImagesVertically(img1: Image, img2: Image) -> Image: dst = Image.new('RGBA', (img1.width, img1.height + img2.height)) dst.paste(img1, (0, 0)) dst.paste(img2, (0, img1.height)) return dst @staticmethod - def add_image_margin(image: Image, top: int, right: int, bottom: int, left: int, color: Tuple[int, int, int, int]) -> Image: + def addImageMargin(image: Image, top: int, right: int, bottom: int, left: int, color: Tuple[int, int, int, int]) -> Image: width, height = image.size new_width = width + right + left new_height = height + top + bottom @@ -101,10 +97,10 @@ class OverlayImage: # if len(translation) > 0 and target_language is not None: # img = self.create_textimage(message_type, "small", message, your_language) # translation_img = self.create_textimage(message_type, "large",translation, target_language) - # img = self.concatenate_images_vertically(img, translation_img) + # img = self.concatenateImagesVertically(img, translation_img) # else: # img = self.create_textimage(message_type, "large", message, your_language) - # return self.concatenate_images_vertically(message_type_img, img) + # return self.concatenateImagesVertically(message_type_img, img) # def create_overlay_image_long(self, message_type, message, your_language, translation="", target_language=None): # if len(self.log_data) > 10: @@ -132,8 +128,8 @@ class OverlayImage: # img = imgs[0] # for i in imgs[1:]: - # img = self.concatenate_images_vertically(img, i) - # img = self.add_image_margin(img, 0, 20, 0, 20, (0, 0, 0, 0)) + # img = self.concatenateImagesVertically(img, i) + # img = self.addImageMargin(img, 0, 20, 0, 20, (0, 0, 0, 0)) # width, height = img.size # background = Image.new("RGBA", (width, height), (0, 0, 0, 0)) @@ -142,22 +138,22 @@ class OverlayImage: # img = Image.alpha_composite(background, img) # return img - def get_ui_size(self): + def getUiSize(self): return { "width": 960, "height": 23, "font_size": 23, } - def get_ui_colors(self, ui_type): + def getUiColors(self, ui_type): match ui_type: case "default": - background_color = (41, 42, 45, 127) - background_outline_color = (41, 42, 45, 127) + background_color = (41, 42, 45) + background_outline_color = (41, 42, 45) text_color = (223, 223, 223) case "sakura": - background_color = (225, 40, 30, 20) - background_outline_color = (255, 255, 255, 50) + background_color = (225, 40, 30) + background_outline_color = (255, 255, 255) text_color = (223, 223, 223) return { "background_color": background_color, @@ -165,25 +161,32 @@ class OverlayImage: "text_color": text_color } - def create_decoration_image(self, ui_type, image_size): + def createDecorationImage(self, ui_type, image_size): decoration_image = Image.new("RGBA", image_size, (0, 0, 0, 0)) match ui_type: case "default": pass case "sakura": + margin = 7 + alpha_ratio = 0.4 overlay_tl = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "overlay_tl_sakura.png")) overlay_br = Image.open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", "overlay_br_sakura.png")) + if overlay_tl.size[1] > image_size[1]: + overlay_tl = overlay_tl.resize((image_size[1]-margin, image_size[1]-margin)) + if overlay_br.size[1] > image_size[1]: + overlay_br = overlay_br.resize((image_size[1]-margin, image_size[1]-margin)) + alpha = overlay_tl.getchannel("A") - alpha = alpha.point(lambda x: x * 0.1) + alpha = alpha.point(lambda x: x * alpha_ratio) overlay_tl.putalpha(alpha) alpha = overlay_br.getchannel("A") - alpha = alpha.point(lambda x: x * 0.1) + alpha = alpha.point(lambda x: x * alpha_ratio) overlay_br.putalpha(alpha) - decoration_image.paste(overlay_tl, (7, 7)) - decoration_image.paste(overlay_br, (image_size[0]-overlay_br.size[0]-7, image_size[1]-overlay_br.size[1]-7)) + decoration_image.paste(overlay_tl, (margin, margin)) + decoration_image.paste(overlay_br, (image_size[0]-overlay_br.size[0]-margin, image_size[1]-overlay_br.size[1]-margin)) return decoration_image - def create_textbox_short(self, text, language, text_color, base_width, base_height, font_size): + def createTextboxShort(self, text, language, text_color, base_width, base_height, font_size): font_family = self.LANGUAGES.get(language, "NotoSansJP-Regular") img = Image.new("RGBA", (base_width, base_height), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) @@ -202,27 +205,27 @@ class OverlayImage: draw.text((text_x, text_y), text, text_color, anchor="mm", stroke_width=0, font=font, align="center") return img - def create_overlay_image_short(self, message, your_language, translation="", target_language=None, ui_type="default"): - ui_size = self.get_ui_size() + def createOverlayImageShort(self, message, your_language, translation="", target_language=None, ui_type="default"): + ui_size = self.getUiSize() height = ui_size["height"] width = ui_size["width"] font_size = ui_size["font_size"] - ui_colors = self.get_ui_colors(ui_type) + ui_colors = self.getUiColors(ui_type) text_color = ui_colors["text_color"] background_color = ui_colors["background_color"] background_outline_color = ui_colors["background_outline_color"] - img = self.create_textbox_short(message, your_language, text_color, width, height, font_size) + img = self.createTextboxShort(message, your_language, text_color, width, height, font_size) if len(translation) > 0 and target_language is not None: - translation_img = self.create_textbox_short(translation, target_language, text_color, width, height, font_size) - img = self.concatenate_images_vertically(img, translation_img) + translation_img = self.createTextboxShort(translation, target_language, text_color, width, height, font_size) + img = self.concatenateImagesVertically(img, translation_img) background = Image.new("RGBA", img.size, (0, 0, 0, 0)) draw = ImageDraw.Draw(background) draw.rounded_rectangle([(0, 0), img.size], radius=30, fill=background_color, outline=background_outline_color, width=5) - decoration_image = self.create_decoration_image(ui_type, img.size) + decoration_image = self.createDecorationImage(ui_type, img.size) background = Image.alpha_composite(background, decoration_image) img = Image.alpha_composite(background, img) return img \ No newline at end of file diff --git a/utils.py b/utils.py index c2af3568..55d3c479 100644 --- a/utils.py +++ b/utils.py @@ -34,6 +34,9 @@ def generatePercentageStringsList(start:int, end:int, step:int): def intToPctStr(value:int): return f"{value}%" +def floatToPctStr(value:float): + return f"{int(value*100)}%" + def strPctToInt(value:str): return int(value.replace("%", "")) diff --git a/view.py b/view.py index 46f2f46d..4623d49b 100644 --- a/view.py +++ b/view.py @@ -5,10 +5,10 @@ from tkinter import font as tk_font import webbrowser import i18n -from customtkinter import StringVar, IntVar, BooleanVar, get_appearance_mode +from customtkinter import StringVar, IntVar, DoubleVar, BooleanVar, get_appearance_mode from vrct_gui.ui_managers import ColorThemeManager, UiScalingManager, AboutVrctManager from vrct_gui import vrct_gui -from utils import callFunctionIfCallable, intToPctStr +from utils import callFunctionIfCallable, intToPctStr, floatToPctStr from config import config @@ -138,6 +138,65 @@ class View(): + + # VR Settings + VAR_VR_SETTINGS=StringVar(value="VR Settings"), + CALLBACK_SET_CALLBACK_OPEN_VR_SETTINGS_WINDOW=self._openVrSettingsWindow, + + + VAR_LABEL_OVERLAY_OPACITY=StringVar(value="Opacity"), + SLIDER_RANGE_OVERLAY_OPACITY=(0.1, 1.0), + NUMBER_OF_STEPS_OVERLAY_OPACITY=18, + VAR_OVERLAY_OPACITY=DoubleVar(value=config.OVERLAY_SETTINGS["opacity"]), + VAR_CURRENT_VALUE_OVERLAY_OPACITY=StringVar(value=floatToPctStr(config.OVERLAY_SETTINGS["opacity"])), + + VAR_LABEL_OVERLAY_UI_SCALING=StringVar(value="Ui Scaling"), + SLIDER_RANGE_OVERLAY_UI_SCALING=(0.4, 2.0), + NUMBER_OF_STEPS_OVERLAY_UI_SCALING=16, + VAR_OVERLAY_UI_SCALING=DoubleVar(value=config.OVERLAY_SETTINGS["ui_scaling"]), + VAR_CURRENT_VALUE_OVERLAY_UI_SCALING=StringVar(value=floatToPctStr(config.OVERLAY_SETTINGS["ui_scaling"])), + + + + CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=None, + + VAR_LABEL_OVERLAY_SMALL_LOG_X_POS=StringVar(value="X_position"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS=(-0.5, 0.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS=100, + VAR_OVERLAY_SMALL_LOG_X_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["x_pos"]), + + VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS=StringVar(value="Y_position"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS=(-0.5, 0.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS=100, + VAR_OVERLAY_SMALL_LOG_Y_POS=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["y_pos"]), + + VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH=StringVar(value="Depth"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH=(0.5, 1.5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH=100, + VAR_OVERLAY_SMALL_LOG_DEPTH=DoubleVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH=StringVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["depth"]), + + VAR_LABEL_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value="Display Duration"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=(1, 60), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DISPLAY_DURATION=59, + VAR_OVERLAY_SMALL_LOG_DISPLAY_DURATION=IntVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["display_duration"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION=StringVar(value=f"{config.OVERLAY_SMALL_LOG_SETTINGS['display_duration']} second(s)"), + + VAR_LABEL_OVERLAY_SMALL_LOG_FADEOUT_DURATION=StringVar(value="Fadeout Duration"), + SLIDER_RANGE_OVERLAY_SMALL_LOG_FADEOUT_DURATION=(0, 5), + NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_FADEOUT_DURATION=5, + VAR_OVERLAY_SMALL_LOG_FADEOUT_DURATION=IntVar(value=config.OVERLAY_SMALL_LOG_SETTINGS["fadeout_duration"]), + VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_FADEOUT_DURATION=StringVar(value=f"{config.OVERLAY_SMALL_LOG_SETTINGS['fadeout_duration']} second(s)"), + + + + + + + + # Main Window # Sidebar # Sidebar Compact Mode @@ -220,6 +279,7 @@ class View(): VAR_SECOND_TITLE_TRANSCRIPTION_MIC=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_mic")), VAR_SECOND_TITLE_TRANSCRIPTION_SPEAKER=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_speaker")), VAR_SECOND_TITLE_TRANSCRIPTION_INTERNAL_MODEL=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_internal_model")), + VAR_SIDE_MENU_LABEL_VR=StringVar(value=i18n.t("config_window.side_menu_labels.vr")), VAR_SIDE_MENU_LABEL_OTHERS=StringVar(value=i18n.t("config_window.side_menu_labels.others")), VAR_SIDE_MENU_LABEL_ADVANCED_SETTINGS=StringVar(value=i18n.t("config_window.side_menu_labels.advanced_settings")), @@ -416,6 +476,14 @@ class View(): VAR_WHISPER_WEIGHT_TYPE=StringVar(value=self.getSelectableWhisperWeightTypeDict()[config.WHISPER_WEIGHT_TYPE]), + # VR Tab + VAR_LABEL_ENABLE_OVERLAY_SMALL_LOG=StringVar(value=i18n.t("config_window.enable_overlay_small_log.label")), + VAR_DESC_ENABLE_OVERLAY_SMALL_LOG=None, + # VAR_DESC_ENABLE_OVERLAY_SMALL_LOG=StringVar(value=i18n.t("config_window.enable_overlay_small_log.desc")), + CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG=None, + VAR_ENABLE_OVERLAY_SMALL_LOG=BooleanVar(value=config.ENABLE_OVERLAY_SMALL_LOG), + + # Others Tab VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value=i18n.t("config_window.auto_clear_the_message_box.label")), VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, @@ -680,6 +748,14 @@ class View(): self.view_variable.CALLBACK_SET_USE_WHISPER_FEATURE=config_window_registers.get("callback_set_use_whisper_feature", None) self.view_variable.CALLBACK_SET_WHISPER_WEIGHT_TYPE=config_window_registers.get("callback_set_whisper_weight_type", None) + # VR Tab + # VR Tab (Quick Settings) + self.view_variable.CALLBACK_SET_OVERLAY_SETTINGS=config_window_registers.get("callback_set_overlay_settings", None) + + self.view_variable.CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG=config_window_registers.get("callback_set_enable_overlay_small_log", None) + # VR Tab (Quick Settings) + self.view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS=config_window_registers.get("callback_set_overlay_small_log_settings", None) + # Others Tab self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX=config_window_registers.get("callback_set_enable_auto_clear_chatbox", None) @@ -1611,6 +1687,9 @@ class View(): self._closeMicWordFilterList() vrct_gui._closeConfigWindow() + def _openVrSettingsWindow(self): + vrct_gui.quick_settings_window.show() + # Window Control (Main Window Cover) def _openTheCoverOfMainWindow(self): vrct_gui.main_window_cover.show() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index b0649e4c..c6f55358 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -11,6 +11,7 @@ from .setting_box_containers.setting_box_transcription import createSettingBox_M from .setting_box_containers.setting_box_others import createSettingBox_Others, createSettingBox_Others_SendMessageFormats, createSettingBox_Others_ReceivedMessageFormats, createSettingBox_Others_Additional from .setting_box_containers.setting_box_advanced_settings import createSettingBox_AdvancedSettings from .setting_box_containers.setting_box_translation import createSettingBox_Translation +from .setting_box_containers.setting_box_vr import createSettingBox_Vr from .setting_box_containers.setting_box_about_vrct import createSettingBox_AboutVrct @@ -103,6 +104,21 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl ] }, }, + { + "side_menu_tab_attr_name": "side_menu_tab_vr", + "label_attr_name": "label_vr", + "selected_mark_attr_name": "selected_mark_vr", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_VR, + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_vr", + "setting_boxes": [ + { + "var_section_title": None, + "setting_box": createSettingBox_Vr + } + ] + }, + }, { "side_menu_tab_attr_name": "side_menu_tab_others", "label_attr_name": "label_others", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/__init__.py new file mode 100644 index 00000000..78090aef --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_Vr import createSettingBox_Vr \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py new file mode 100644 index 00000000..9ecf6f93 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_vr/createSettingBox_Vr.py @@ -0,0 +1,21 @@ +from utils import callFunctionIfCallable + +from .._SettingBoxGenerator import _SettingBoxGenerator + +def createSettingBox_Vr(setting_box_wrapper, config_window, settings, view_variable): + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) + createSettingBoxSwitch = sbg.createSettingBoxSwitch + + def switchEnableOverlayUiCallback(switch_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OVERLAY_SMALL_LOG, switch_widget.get()) + + row=0 + config_window.sb__enable_overlay_small_log = createSettingBoxSwitch( + for_var_label_text=view_variable.VAR_LABEL_ENABLE_OVERLAY_SMALL_LOG, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_OVERLAY_SMALL_LOG, + switch_attr_name="sb__switch_enable_overlay_small_log", + command=lambda: switchEnableOverlayUiCallback(config_window.sb__switch_enable_overlay_small_log), + variable=view_variable.VAR_ENABLE_OVERLAY_SMALL_LOG + ) + config_window.sb__enable_overlay_small_log.grid(row=row) + row+=1 \ No newline at end of file diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index c09f4bca..ff9f5ef3 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -47,6 +47,60 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): # Main Top Bar Container - Right Side # start from 3 main_topbar_column=3 + + # VR Settings Button + vrct_gui.vr_settings_container = CTkFrame( + vrct_gui.main_topbar_container, + corner_radius=settings.uism.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS, + fg_color=settings.ctm.MAIN_BG_COLOR, + cursor="hand2", + ) + vrct_gui.vr_settings_container.grid(row=0, column=main_topbar_column, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nsw") + # vrct_gui.vr_settings_container.grid_remove() + + + vrct_gui.vr_settings_container.grid_rowconfigure((0,2), weight=1) + + vrct_gui.vr_settings_icon = CTkLabel( + vrct_gui.vr_settings_container, + text=None, + corner_radius=0, + height=0, + image=CTkImage(settings.image_file.CONFIGURATION_ICON, size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) + ) + vrct_gui.vr_settings_icon.grid(row=1, column=0, padx=(settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX, settings.uism.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON), pady=0) + + + vrct_gui.vr_settings_label = CTkLabel( + vrct_gui.vr_settings_container, + textvariable=view_variable.VAR_VR_SETTINGS, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), + anchor="e", + text_color="#fff", + # text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, + ) + # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... + vrct_gui.vr_settings_label.grid(row=1, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) + + + bindButtonFunctionAndColor( + target_widgets=[ + vrct_gui.vr_settings_container, + vrct_gui.vr_settings_label, + vrct_gui.vr_settings_icon, + ], + enter_color=settings.ctm.TOP_BAR_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.TOP_BAR_BUTTON_BG_COLOR, + clicked_color=settings.ctm.TOP_BAR_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_SET_CALLBACK_OPEN_VR_SETTINGS_WINDOW), + ) + + + + + main_topbar_column+=1 # Update Available Button vrct_gui.update_available_container = CTkFrame( vrct_gui.main_topbar_container, diff --git a/vrct_gui/quick_settings_window/QuickSettingsWindow.py b/vrct_gui/quick_settings_window/QuickSettingsWindow.py new file mode 100644 index 00000000..e4f95446 --- /dev/null +++ b/vrct_gui/quick_settings_window/QuickSettingsWindow.py @@ -0,0 +1,179 @@ +from utils import callFunctionIfCallable, floatToPctStr + +from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar, CTkFrame, CTkSlider +from ..ui_utils import getImagePath, setGeometryToCenterOfScreen, fadeInAnimation + +from ._CreateQuickSettingBox import _CreateQuickSettingBox + +class QuickSettingsWindow(CTkToplevel): + def __init__(self, vrct_gui, settings, view_variable): + super().__init__() + self.withdraw() + # self.overrideredirect(True) + self.configure(fg_color="#292a2d") + self.title("Quick Settings") + # self.wm_attributes("-toolwindow", True) + self.protocol("WM_DELETE_WINDOW", self.withdraw) + self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) + + + self.settings = settings + + BG_HEIGHT= 220 + BG_WIDTH= 450 + BG_HEX_COLOR = "#292a2d" + + self.grid_columnconfigure(0, weight=1, minsize=400) + self.grid_rowconfigure(0, weight=1) + self.qsw_background = CTkFrame(self, corner_radius=0, fg_color=BG_HEX_COLOR) + self.qsw_background.grid(sticky="nsew") + self.qsw_background.grid_columnconfigure(0, weight=1) + + + cqsb = _CreateQuickSettingBox(self.qsw_background, vrct_gui, settings, view_variable) + createSettingBoxSlider = cqsb.createSettingBoxSlider + + + + + + # Overlay General Settings + row=0 + def sliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SETTINGS, value, "opacity") + view_variable.VAR_CURRENT_VALUE_OVERLAY_OPACITY.set(floatToPctStr(value)) + + self.qsb__overlay_opacity = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_OPACITY, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_OPACITY, + slider_attr_name="qsb__overlay_opacity_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_OPACITY, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_OPACITY, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_OPACITY, + ) + self.qsb__overlay_opacity.grid(row=row) + + + row+=1 + def sliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SETTINGS, value, "ui_scaling") + view_variable.VAR_CURRENT_VALUE_OVERLAY_UI_SCALING.set(floatToPctStr(value)) + + self.qsb__overlay_ui_scaling = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_UI_SCALING, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_UI_SCALING, + slider_attr_name="qsb__overlay_ui_scaling_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_UI_SCALING, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_UI_SCALING, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_UI_SCALING, + ) + self.qsb__overlay_ui_scaling.grid(row=row) + + + + + row+=1 + # Overlay Small Log Settings + def sliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "x_pos") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS.set(str(value)) + + self.qsb__overlay_small_log_settings_x_pos = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_X_POS, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_X_POS, + slider_attr_name="qsb__overlay_small_log_settings_x_pos_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_X_POS, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_X_POS, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_X_POS, + ) + self.qsb__overlay_small_log_settings_x_pos.grid(row=row) + + + row+=1 + def sliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "y_pos") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS.set(str(value)) + + self.qsb__overlay_small_log_settings_y_pos = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_Y_POS, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_Y_POS, + slider_attr_name="qsb__overlay_small_log_settings_y_pos_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_Y_POS, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_Y_POS, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_Y_POS, + ) + self.qsb__overlay_small_log_settings_y_pos.grid(row=row) + + + row+=1 + def sliderCallback(e): + value = round(e,2) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "depth") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH.set(str(value)) + + self.qsb__overlay_small_log_settings_depth = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_DEPTH, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DEPTH, + slider_attr_name="qsb__overlay_small_log_settings_depth_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_DEPTH, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DEPTH, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_DEPTH, + ) + self.qsb__overlay_small_log_settings_depth.grid(row=row) + + + row+=1 + def sliderCallback(e): + value = int(e) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "display_duration") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION.set(f"{value} second(s)") + + self.qsb__overlay_small_log_settings_display_duration = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_DISPLAY_DURATION, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_DISPLAY_DURATION, + slider_attr_name="qsb__overlay_small_log_settings_display_duration_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_DISPLAY_DURATION, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_DISPLAY_DURATION, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_DISPLAY_DURATION, + ) + self.qsb__overlay_small_log_settings_display_duration.grid(row=row) + + + + row+=1 + def sliderCallback(e): + value = int(e) + callFunctionIfCallable(view_variable.CALLBACK_SET_OVERLAY_SMALL_LOG_SETTINGS, value, "fadeout_duration") + view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_FADEOUT_DURATION.set(f"{value} second(s)") + + self.qsb__overlay_small_log_settings_fadeout_duration = createSettingBoxSlider( + for_var_label_text=view_variable.VAR_LABEL_OVERLAY_SMALL_LOG_FADEOUT_DURATION, + for_var_current_value=view_variable.VAR_CURRENT_VALUE_OVERLAY_SMALL_LOG_FADEOUT_DURATION, + slider_attr_name="qsb__overlay_small_log_settings_fadeout_duration_slider", + slider_range=view_variable.SLIDER_RANGE_OVERLAY_SMALL_LOG_FADEOUT_DURATION, + slider_number_of_steps=view_variable.NUMBER_OF_STEPS_OVERLAY_SMALL_LOG_FADEOUT_DURATION, + command=sliderCallback, + variable=view_variable.VAR_OVERLAY_SMALL_LOG_FADEOUT_DURATION, + ) + self.qsb__overlay_small_log_settings_fadeout_duration.grid(row=row) + + + + + + + def show(self): + self.attributes("-alpha", 0) + self.deiconify() + setGeometryToCenterOfScreen(root_widget=self) + fadeInAnimation(self, steps=5, interval=0.02) \ No newline at end of file diff --git a/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py b/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py new file mode 100644 index 00000000..01bb931a --- /dev/null +++ b/vrct_gui/quick_settings_window/_CreateQuickSettingBox.py @@ -0,0 +1,143 @@ +from typing import Union + +from utils import callFunctionIfCallable + +from customtkinter import CTkImage, CTkLabel, CTkToplevel, CTkProgressBar, CTkFrame, CTkSlider, CTkFont +from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation + +class _CreateQuickSettingBox(): + def __init__(self, parent_frame, vrct_gui, settings, view_variable): + self.view_variable = view_variable + self.vrct_gui = vrct_gui + self.settings = settings + self.parent_frame = parent_frame + + + + + + + + + + def _createSettingBoxFrame(self, for_var_label_text=None, for_var_current_value=None): + setting_box_frame = CTkFrame(self.parent_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + + setting_box_frame.grid(row=0, column=0, sticky="ew") + setting_box_frame.grid_columnconfigure(0, weight=1) + + + 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="nsew") + setting_box_frame_wrapper.grid_columnconfigure(0, weight=1) + + + # Labels + 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") + + setting_box_labels_frame.grid_rowconfigure((0,2), weight=1) + setting_box_labels_frame.grid_columnconfigure(1, weight=1) + setting_box_label = CTkLabel( + setting_box_labels_frame, + textvariable=for_var_label_text, + anchor="w", + height=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__LABEL_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_TEXT_COLOR + ) + setting_box_label.grid(row=1, column=0, padx=0, pady=0, sticky="nse") + + + + setting_box_label = CTkLabel( + setting_box_labels_frame, + textvariable=for_var_current_value, + anchor="w", + height=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__LABEL_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_TEXT_COLOR + ) + setting_box_label.grid(row=1, column=2, padx=0, pady=0, sticky="nsw") + + + + + + + + + # Items + setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) + setting_box_item_frame.grid(row=1, column=0, padx=0, sticky="nsew") + + setting_box_item_frame.grid_rowconfigure((0,2), weight=1) + setting_box_item_frame.grid_columnconfigure(1, weight=1) + + return (setting_box_frame, setting_box_item_frame) + + + + + + + + + + + + + + + + + def createSettingBoxSlider( + self, + for_var_label_text, + for_var_current_value, + slider_attr_name, + slider_range, + command, + variable, + slider_number_of_steps: Union[int, + None] = None, + slider_bind__ButtonPress=None, + slider_bind__ButtonRelease=None, + sliderTooltipFormatter=None, + ): + + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_current_value) + + + + slider_widget = CTkSlider( + setting_box_item_frame, + width=self.settings.uism.SB__SLIDER_WIDTH, + height=self.settings.uism.SB__SLIDER_HEIGHT, + from_=slider_range[0], + to=slider_range[1], + number_of_steps=slider_number_of_steps, + fg_color=self.settings.ctm.SB__SLIDER_BG_COLOR, + progress_color=self.settings.ctm.SB__SLIDER_PROGRESS_BG_COLOR, + button_color=self.settings.ctm.SB__SLIDER_BUTTON_COLOR, + button_hover_color=self.settings.ctm.SB__SLIDER_BUTTON_HOVERED_COLOR, + command=command, + variable=variable, + ) + setattr(self.vrct_gui, slider_attr_name, slider_widget) + + + slider_widget.grid(row=1, column=1, sticky="ew") + + if slider_bind__ButtonPress is not None: + def adjusted_slider_bind__ButtonPress(e): + command(e) + slider_bind__ButtonPress() + slider_widget.configure(command=adjusted_slider_bind__ButtonPress) + + if slider_bind__ButtonRelease is not None: + def adjusted_slider_bind__ButtonRelease(_e): + slider_bind__ButtonRelease() + slider_widget.bind("", adjusted_slider_bind__ButtonRelease, "+") + + return setting_box_frame \ No newline at end of file diff --git a/vrct_gui/quick_settings_window/__init__.py b/vrct_gui/quick_settings_window/__init__.py new file mode 100644 index 00000000..4ee26014 --- /dev/null +++ b/vrct_gui/quick_settings_window/__init__.py @@ -0,0 +1 @@ +from .QuickSettingsWindow import * \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 4b94768e..9ff38bef 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -14,6 +14,7 @@ from ._PrintToTextbox import _PrintToTextbox from .main_window import createMainWindowWidgets from .config_window import ConfigWindow +from .quick_settings_window import QuickSettingsWindow from .ui_utils import setDefaultActiveTab, setGeometryToCenterOfScreen, fadeInAnimation from utils import callFunctionIfCallable @@ -130,6 +131,13 @@ class VRCT_GUI(CTk): view_variable=self._view_variable ) + self.quick_settings_window = QuickSettingsWindow( + vrct_gui=self, + settings=self.settings.config_window, + view_variable=self._view_variable + ) + # self.quick_settings_window.show() + self.selectable_languages_window = _CreateSelectableLanguagesWindow( vrct_gui=self, settings=self.settings.selectable_language_window,