diff --git a/Launcher.py b/Launcher.py index 50349024..2520fd6b 100644 --- a/Launcher.py +++ b/Launcher.py @@ -121,46 +121,28 @@ def handle_uri(path: str, launch_args: tuple[str, ...]) -> None: launch_args = (path, *launch_args) client_component = [] text_client_component = None - if "game" in queries: - game = queries["game"][0] - else: # TODO around 0.6.0 - this is for pre this change webhost uri's - game = "Archipelago" + game = queries["game"][0] for component in components: if component.supports_uri and component.game_name == game: client_component.append(component) elif component.display_name == "Text Client": text_client_component = component - from kvui import MDButton, MDButtonText - from kivymd.uix.dialog import MDDialog, MDDialogHeadlineText, MDDialogContentContainer, MDDialogSupportingText - from kivymd.uix.divider import MDDivider if not client_component: run_component(text_client_component, *launch_args) return else: - popup_text = MDDialogSupportingText(text="Select client to open and connect with.") - component_buttons = [MDDivider()] - for component in [text_client_component, *client_component]: - component_buttons.append(MDButton( - MDButtonText(text=component.display_name), - on_release=lambda *args, comp=component: run_component(comp, *launch_args), - style="text" - )) - component_buttons.append(MDDivider()) - - MDDialog( - # Headline - MDDialogHeadlineText(text="Connect to Multiworld"), - # Text - popup_text, - # Content - MDDialogContentContainer( - *component_buttons, - orientation="vertical" - ), - - ).open() + from kvui import ButtonsPrompt + component_options = { + text_client_component.display_name: text_client_component, + **{component.display_name: component for component in client_component} + } + popup = ButtonsPrompt("Connect to Multiworld", + "Select client to open and connect with.", + lambda component_name: run_component(component_options[component_name], *launch_args), + *component_options.keys()) + popup.open() def identify(path: None | str) -> tuple[None | str, None | Component]: diff --git a/data/client.kv b/data/client.kv index 562986cd..53000dfe 100644 --- a/data/client.kv +++ b/data/client.kv @@ -222,3 +222,8 @@ spacing: 10 size_hint_y: None height: self.minimum_height +: + valign: "middle" + halign: "center" + text_size: self.width, None + height: self.texture_size[1] diff --git a/kvui.py b/kvui.py index d0d965c3..172b7e55 100644 --- a/kvui.py +++ b/kvui.py @@ -6,7 +6,6 @@ import re import io import pkgutil from collections import deque - assert "kivy" not in sys.modules, "kvui should be imported before kivy for frozen compatibility" if sys.platform == "win32": @@ -57,6 +56,7 @@ from kivy.animation import Animation from kivy.uix.popup import Popup from kivy.uix.image import AsyncImage from kivymd.app import MDApp +from kivymd.uix.dialog import MDDialog, MDDialogHeadlineText, MDDialogSupportingText, MDDialogButtonContainer from kivymd.uix.gridlayout import MDGridLayout from kivymd.uix.floatlayout import MDFloatLayout from kivymd.uix.boxlayout import MDBoxLayout @@ -710,20 +710,62 @@ class CommandPromptTextInput(ResizableTextField): self.text = self._command_history[self._command_history_index] +class MessageBoxLabel(MDLabel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._label.refresh() + + class MessageBox(Popup): - class MessageBoxLabel(MDLabel): - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._label.refresh() def __init__(self, title, text, error=False, **kwargs): - label = MessageBox.MessageBoxLabel(text=text) + label = MessageBoxLabel(text=text) separator_color = [217 / 255, 129 / 255, 122 / 255, 1.] if error else [47 / 255., 167 / 255., 212 / 255, 1.] super().__init__(title=title, content=label, size_hint=(0.5, None), width=max(100, int(label.width) + 40), separator_color=separator_color, **kwargs) self.height += max(0, label.height - 18) +class ButtonsPrompt(MDDialog): + def __init__(self, title: str, text: str, response: typing.Callable[[str], None], + *prompts: str, **kwargs) -> None: + """ + Customizable popup box that lets you create any number of buttons. The text of the pressed button is returned to + the callback. + + :param title: The title of the popup. + :param text: The message prompt in the popup. + :param response: A callable that will get called when the user presses a button. The prompt will not close + itself so should be done here if you want to close it when certain buttons are pressed. + :param prompts: Any number of strings to be used for the buttons. + """ + layout = MDBoxLayout(orientation="vertical") + label = MessageBoxLabel(text=text) + layout.add_widget(label) + + def on_release(button: MDButton, *args) -> None: + response(button.text) + + buttons = [MDDivider()] + for prompt in prompts: + button = MDButton( + MDButtonText(text=prompt, pos_hint={"center_x": 0.5, "center_y": 0.5}), + on_release=on_release, + style="text", + theme_width="Custom", + size_hint_x=1, + ) + button.text = prompt + buttons.extend([button, MDDivider()]) + + super().__init__( + MDDialogHeadlineText(text=title), + MDDialogSupportingText(text=text), + MDDialogButtonContainer(*buttons, orientation="vertical"), + **kwargs, + ) + + class ClientTabs(MDTabsSecondary): carousel: MDTabsCarousel lock_swiping = True diff --git a/worlds/messenger/client_setup.py b/worlds/messenger/client_setup.py index 6b98a1b4..3ef1df75 100644 --- a/worlds/messenger/client_setup.py +++ b/worlds/messenger/client_setup.py @@ -2,35 +2,28 @@ import argparse import io import logging import os.path +import requests import subprocess import urllib.request from shutil import which -from typing import Any +from typing import Any, Callable, TYPE_CHECKING from zipfile import ZipFile -from Utils import open_file +from Utils import is_windows, messagebox, open_file, tuplize_version -import requests - -from Utils import is_windows, messagebox, tuplize_version +if TYPE_CHECKING: + from kvui import ButtonsPrompt MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest" -def ask_yes_no_cancel(title: str, text: str) -> bool | None: - """ - Wrapper for tkinter.messagebox.askyesnocancel, that creates a popup dialog box with yes, no, and cancel buttons. +def create_yes_no_popup(title: str, text: str, callback: Callable[[str], None]) -> "ButtonsPrompt": + from kvui import ButtonsPrompt + buttons = ["Yes", "No", "Cancel"] - :param title: Title to be displayed at the top of the message box. - :param text: Text to be displayed inside the message box. - :return: Returns True if yes, False if no, None if cancel. - """ - from tkinter import Tk, messagebox - root = Tk() - root.withdraw() - ret = messagebox.askyesnocancel(title, text) - root.update() - return ret + prompt = ButtonsPrompt(title, text, callback, *buttons) + prompt.open() + return prompt def launch_game(*args) -> None: @@ -151,6 +144,76 @@ def launch_game(*args) -> None: # one of the alpha builds return "alpha" in latest_version or tuplize_version(latest_version) > tuplize_version(installed_version) + def after_courier_install_popup(answer: str) -> None: + """Gets called if the user doesn't have courier installed. Handle the button they pressed.""" + nonlocal prompt + + prompt.dismiss() + if answer in ("No", "Cancel"): + return + logging.info("Installing Courier") + install_courier() + prompt = create_yes_no_popup("Install Mod", + "No randomizer mod detected. Would you like to install now?", + after_mod_install_popup) + + def after_mod_install_popup(answer: str) -> None: + """Gets called if the user has courier but mod isn't installed, or there's an available update.""" + nonlocal prompt + + prompt.dismiss() + if answer in ("No", "Cancel"): + return + logging.info("Installing Mod") + install_mod() + prompt = create_yes_no_popup("Launch Game", + "Courier and Game mod installed successfully. Launch game now?", + launch) + + def after_mod_update_popup(answer: str) -> None: + """Gets called if there's an available update.""" + nonlocal prompt + + prompt.dismiss() + if answer == "Cancel": + return + if answer == "Yes": + logging.info("Updating Mod") + install_mod() + prompt = create_yes_no_popup("Launch Game", + "Courier and Game mod installed successfully. Launch game now?", + launch) + else: + prompt = create_yes_no_popup("Launch Game", + "Game Mod not updated. Launch game now?", + launch) + + def launch(answer: str | None = None) -> None: + """Launch the game.""" + nonlocal args + + if prompt: + prompt.dismiss() + if answer and answer in ("No", "Cancel"): + return + + parser = argparse.ArgumentParser(description="Messenger Client Launcher") + parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.") + args = parser.parse_args(args) + + if not is_windows: + if args.url: + open_file(f"steam://rungameid/764790//{args.url}/") + else: + open_file("steam://rungameid/764790") + else: + os.chdir(game_folder) + if args.url: + subprocess.Popen([MessengerWorld.settings.game_path, str(args.url)]) + else: + subprocess.Popen(MessengerWorld.settings.game_path) + os.chdir(working_directory) + from . import MessengerWorld try: game_folder = os.path.dirname(MessengerWorld.settings.game_path) @@ -172,49 +235,24 @@ def launch_game(*args) -> None: except ImportError: pass if not courier_installed(): - should_install = ask_yes_no_cancel("Install Courier", - "No Courier installation detected. Would you like to install now?") - if not should_install: - return - logging.info("Installing Courier") - install_courier() + prompt = create_yes_no_popup("Install Courier", + "No Courier installation detected. Would you like to install now?", + after_courier_install_popup) + return if not mod_installed(): - should_install = ask_yes_no_cancel("Install Mod", - "No randomizer mod detected. Would you like to install now?") - if not should_install: - return - logging.info("Installing Mod") - install_mod() + prompt = create_yes_no_popup("Install Mod", + "No randomizer mod detected. Would you like to install now?", + after_mod_install_popup) + return else: latest = request_data(MOD_URL)["tag_name"] if available_mod_update(latest): - should_update = ask_yes_no_cancel("Update Mod", - f"New mod version detected. Would you like to update to {latest} now?") - if should_update: - logging.info("Updating mod") - install_mod() - elif should_update is None: - return - - if not args: - should_launch = ask_yes_no_cancel("Launch Game", - "Mod installed and up to date. Would you like to launch the game now?") - if not should_launch: + prompt = create_yes_no_popup("Update Mod", + f"New mod version detected. Would you like to update to {latest} now?", + after_mod_update_popup) return - parser = argparse.ArgumentParser(description="Messenger Client Launcher") - parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.") - args = parser.parse_args(args) - - if not is_windows: - if args.url: - open_file(f"steam://rungameid/764790//{args.url}/") - else: - open_file("steam://rungameid/764790") - else: - os.chdir(game_folder) - if args.url: - subprocess.Popen([MessengerWorld.settings.game_path, str(args.url)]) - else: - subprocess.Popen(MessengerWorld.settings.game_path) - os.chdir(working_directory) + if not args: + prompt = create_yes_no_popup("Launch Game", + "Mod installed and up to date. Would you like to launch the game now?", + launch)