diff --git a/plugin_manager.py b/plugin_manager.py index fce6017a..5422febc 100644 --- a/plugin_manager.py +++ b/plugin_manager.py @@ -1,4 +1,7 @@ # ba_meta require api 9 + +from __future__ import annotations + import babase import bauiv1 as bui from bauiv1lib import popup, confirm @@ -44,6 +47,46 @@ } PLUGIN_DIRECTORY = _env["python_directory_user"] + +class UIConfig: + """UI-related configuration and scaling constants.""" + + # Color definitions + CLASS_COLORS = { + 'text_primary': (1, 1, 1), + 'text_secondary': (0.75, 0.7, 0.8), + 'text_muted': (0.5, 0.5, 0.5), + 'button_red': (0.8, 0.15, 0.35), + 'button_green': (0.2, 0.8, 0.3), + 'button_neutral': (0.6, 0.53, 0.63), + 'discord_bg': (0.525, 0.595, 1.458), + 'discord_fg': (10 - 0.32, 10 - 0.39, 10 - 0.96), + 'github_bg': (0.23, 0.23, 0.23), + 'installed_enabled': (0, 0.95, 0.2), + 'installed_update': (1, 0.6, 0), + 'installed_not_managed': (0.8, 0.2, 0.2), + 'not_installed': (0.5, 0.5, 0.5), + 'success_green': (0, 1, 0), + 'error_red': (1, 0, 0), + } + + # UI scaling helpers + @staticmethod + def scale_value(small: float, medium: float, large: float) -> float: + """Return value based on current UI scale.""" + u = _uiscale() + if u is babase.UIScale.SMALL: + return small + elif u is babase.UIScale.MEDIUM: + return medium + return large + + @staticmethod + def get_color(color_name: str) -> tuple: + """Get a color from the configuration.""" + return UIConfig.CLASS_COLORS.get(color_name, UIConfig.CLASS_COLORS['text_primary']) + + # compatibility for older API versions. if _env.get("build_number", 0) < 22714: babase._asyncio._g_asyncio_event_loop = babase._asyncio._asyncio_event_loop @@ -64,17 +107,12 @@ def _remove_popup(popup): pass -def _uiscale(): return bui.app.ui_v1.uiscale -def _regexp_friendly_class_name_shortcut(string): return string.replace(".", "\\.") +def _uiscale(): + return bui.app.ui_v1.uiscale -def _by_scale(a, b, c): - u = _uiscale() - return ( - a if u is babase.UIScale.SMALL else - b if u is babase.UIScale.MEDIUM else - c - ) +def _regexp_friendly_class_name_shortcut(string): + return string.replace(".", "\\.") REGEXP = { @@ -84,7 +122,7 @@ def _by_scale(a, b, c): "(ba_meta export (plugin|{})\n+class )(.*)\\(".format( _regexp_friendly_class_name_shortcut(EXPORT_CLASS_NAME_SHORTCUTS["plugin"]), ), - "utf-8" + "utf-8", ), ), "minigames": re.compile( @@ -92,7 +130,7 @@ def _by_scale(a, b, c): "(ba_meta export ({})\n+class )(.*)\\(".format( _regexp_friendly_class_name_shortcut("bascenev1.GameActivity"), ), - "utf-8" + "utf-8", ), ), } @@ -103,39 +141,83 @@ def _by_scale(a, b, c): class MD5CheckSumFailed(Exception): + """Raised when MD5 checksum verification fails during file operations.""" + pass class PluginNotInstalled(Exception): + """Raised when attempting to access a plugin that is not installed.""" + pass class CategoryDoesNotExist(Exception): + """Raised when accessing a plugin category that doesn't exist.""" + pass class NoCompatibleVersion(Exception): + """Raised when no compatible version exists for the current API version.""" + pass class PluginSourceNetworkError(Exception): + """Raised when there's a network error accessing a plugin source.""" + pass class CategoryMetadataParseError(Exception): + """Raised when plugin category metadata fails to parse.""" + pass -def send_network_request(request): +def send_network_request(request: urllib.request.Request) -> urllib.response.addinfourl: + """Send a network request and return the response. + + Args: + request: urllib Request object + + Returns: + Response object from urllib + """ return urllib.request.urlopen(request) -async def async_send_network_request(request): +async def async_send_network_request(request: urllib.request.Request) -> urllib.response.addinfourl: + """Asynchronously send a network request. + + Args: + request: urllib Request object + + Returns: + Response object from urllib + """ response = await loop.run_in_executor(None, send_network_request, request) return response -def stream_network_response_to_file(request, file, md5sum=None, retries=3): +def stream_network_response_to_file( + request: urllib.request.Request, file: str, md5sum: str | None = None, retries: int = 3 +) -> bytes: + """Download a file from a network request with optional MD5 verification. + + Args: + request: urllib Request object + file: File path to save to + md5sum: Optional MD5 checksum to verify against + retries: Number of retries on checksum failure + + Returns: + Downloaded file content as bytes + + Raises: + MD5CheckSumFailed: If MD5 checksum verification fails + """ response = urllib.request.urlopen(request) chunk_size = 16 * 1024 content = b"" @@ -146,6 +228,7 @@ def stream_network_response_to_file(request, file, md5sum=None, retries=3): break fout.write(chunk) content += chunk + if md5sum and hashlib.md5(content).hexdigest() != md5sum: if retries <= 0: raise MD5CheckSumFailed("MD5 checksum match failed.") @@ -153,13 +236,25 @@ def stream_network_response_to_file(request, file, md5sum=None, retries=3): request, file, md5sum=md5sum, - retries=retries-1, + retries=retries - 1, ) return content -async def async_stream_network_response_to_file(request, file, md5sum=None, retries=3): +async def async_stream_network_response_to_file( + request: urllib.request.Request, file: str, md5sum: str | None = None, retries: int = 3 +) -> bytes: + """Asynchronously download a file with optional MD5 verification. + + Args: + request: urllib Request object + file: File path to save to + md5sum: Optional MD5 checksum to verify against + retries: Number of retries on checksum failure + Returns: + Downloaded file content as bytes + """ content = await loop.run_in_executor( None, stream_network_response_to_file, @@ -171,7 +266,16 @@ async def async_stream_network_response_to_file(request, file, md5sum=None, retr return content -def partial_format(string_template, **kwargs): +def partial_format(string_template: str, **kwargs: str) -> str: + """Partially format a string template, leaving unmatched placeholders. + + Args: + string_template: Template string with placeholders in {key} format + **kwargs: Key-value pairs for replacement + + Returns: + Partially formatted string + """ for key, value in kwargs.items(): string_template = string_template.replace("{" + key + "}", value) return string_template @@ -274,14 +378,93 @@ def https_open(self, req): return self.do_open(DNSBlockWorkaround._HTTPSConnection, req) +class UIHelpers: + """Helper functions for creating common UI patterns.""" + + @staticmethod + def create_icon_button( + parent, position, size, texture, on_activate, icon_color=None, btn_color=None + ): + # Build buttonwidget kwargs + button_kwargs = { + 'parent': parent, + 'position': position, + 'size': size, + 'button_type': 'square', + 'label': '', + 'on_activate_call': on_activate, + } + if btn_color is not None: + button_kwargs['color'] = btn_color + + button = bui.buttonwidget(**button_kwargs) + + # Build imagewidget kwargs + image_kwargs = { + 'parent': parent, + 'position': position, + 'size': size, + 'texture': bui.gettexture(texture), + 'draw_controller': button, + } + if icon_color is not None: + image_kwargs['color'] = icon_color + + bui.imagewidget(**image_kwargs) + return button + + @staticmethod + def create_icon_button_with_text( + parent, + position, + size, + texture, + on_activate, + text, + text_position=None, + text_color=None, + icon_color=None, + btn_color=None, + text_scale=0.45, + text_rotate=25, + ): + """Create an icon button with optional text overlay.""" + button = UIHelpers.create_icon_button( + parent, position, size, texture, on_activate, icon_color, btn_color + ) + + # Add text overlay + if text_position is None: + # Default text position (rotated at bottom-left) + text_position = (position[0] - 3, position[1] + 12) + + if text_color is None: + text_color = (1, 1, 1) + + bui.textwidget( + parent=parent, + position=text_position, + text=text, + size=(10, 10), + draw_controller=button, + color=text_color, + rotate=text_rotate, + scale=text_scale, + ) + return button + + class StartupTasks: + """Handles plugin manager initialization and startup tasks.""" + def __init__(self): self.plugin_manager = PluginManager() def setup_config(self): # is_config_updated = False existing_plugin_manager_config = copy.deepcopy( - babase.app.config.get("Community Plugin Manager")) + babase.app.config.get("Community Plugin Manager") + ) plugin_manager_config = babase.app.config.setdefault("Community Plugin Manager", {}) plugin_manager_config.setdefault("Custom Sources", []) @@ -296,7 +479,7 @@ def setup_config(self): "Auto Update Plugin Manager": True, "Auto Update Plugins": True, "Auto Enable Plugins After Installation": True, - "Notify New Plugins": True + "Notify New Plugins": True, } settings = plugin_manager_config.setdefault("Settings", {}) @@ -310,7 +493,12 @@ def setup_config(self): babase.app.config.commit() async def update_plugin_manager(self): - if not babase.app.config["Community Plugin Manager"]["Settings"]["Auto Update Plugin Manager"]: + # add logic here for updating plugman in workspaces + await self.plugin_manager.get_current_workspaces() + + if not babase.app.config["Community Plugin Manager"]["Settings"][ + "Auto Update Plugin Manager" + ]: return update_details = await self.plugin_manager.get_update_details() if update_details: @@ -321,8 +509,9 @@ async def update_plugin_manager(self): except MD5CheckSumFailed: bui.getsound('error').play() else: - bui.screenmessage("Update successful. Restart game to reload changes.", - color=(0, 1, 0)) + bui.screenmessage( + "Update successful. Restart game to reload changes.", color=(0, 1, 0) + ) bui.getsound('shieldUp').play() async def update_plugins(self): @@ -332,7 +521,11 @@ async def update_plugins(self): all_plugins = await self.plugin_manager.categories["All"].get_plugins() plugins_to_update = [] for plugin in all_plugins: - if plugin.is_installed and await plugin.get_local().is_enabled() and plugin.has_update(): + if ( + plugin.is_installed + and await plugin.get_local().is_enabled() + and plugin.has_update() + ): plugins_to_update.append(plugin.update()) await asyncio.gather(*plugins_to_update) @@ -355,15 +548,20 @@ async def notify_new_plugins(self): await self.plugin_manager.setup_index() new_num_of_plugins = len(await self.plugin_manager.categories["All"].get_plugins()) try: - existing_num_of_plugins = babase.app.config["Community Plugin Manager"]["Existing Number of Plugins"] + existing_num_of_plugins = babase.app.config["Community Plugin Manager"][ + "Existing Number of Plugins" + ] except KeyError: - babase.app.config["Community Plugin Manager"]["Existing Number of Plugins"] = new_num_of_plugins + babase.app.config["Community Plugin Manager"][ + "Existing Number of Plugins" + ] = new_num_of_plugins babase.app.config.commit() return def title_it(plug): plug = str(plug).replace('_', ' ').title() return plug + if existing_num_of_plugins < new_num_of_plugins: new_plugin_count = new_num_of_plugins - existing_num_of_plugins all_plugins = await self.plugin_manager.categories["All"].get_plugins() @@ -375,20 +573,34 @@ def title_it(plug): new_supported_plugins = new_supported_plugins[:new_plugin_count] new_supported_plugins_count = len(new_supported_plugins) if new_supported_plugins_count > 0: - new_supported_plugins = ", ".join(map(title_it, (new_supported_plugins - if new_supported_plugins_count <= show_max_names else - new_supported_plugins[0:show_max_names]) - )) + new_supported_plugins = ", ".join( + map( + title_it, + ( + new_supported_plugins + if new_supported_plugins_count <= show_max_names + else new_supported_plugins[0:show_max_names] + ), + ) + ) if new_supported_plugins_count == 1: notification_text = f"{new_supported_plugins_count} new plugin ({new_supported_plugins}) is available!" else: - notification_text = new_supported_plugins + \ - ('' if new_supported_plugins_count <= show_max_names else ' and +' + - str(new_supported_plugins_count-show_max_names)) + " new plugins are available" + notification_text = ( + new_supported_plugins + + ( + '' + if new_supported_plugins_count <= show_max_names + else ' and +' + str(new_supported_plugins_count - show_max_names) + ) + + " new plugins are available" + ) bui.screenmessage(notification_text, color=(0, 1, 0)) if existing_num_of_plugins != new_num_of_plugins: - babase.app.config["Community Plugin Manager"]["Existing Number of Plugins"] = new_num_of_plugins + babase.app.config["Community Plugin Manager"][ + "Existing Number of Plugins" + ] = new_num_of_plugins babase.app.config.commit() async def execute(self): @@ -403,7 +615,129 @@ async def execute(self): pass +class BallisticaAPI: + """API client for Ballistica workspace management.""" + + # If the token is needed after the initialization, we should call get_api_key again to refresh it. + from efro.error import CommunicationError + from bacommon.restapi.v1.workspaces import ( + WorkspacesResponse, + WorkspaceResponse, + WorkspaceFilesResponse, + ActiveWorkspaceResponse, + ) + from bacommon.restapi.v1.accounts import AccountResponse + from bacommon.restapi.v1 import Endpoint + + def __init__(self, url='https://www.ballistica.net/'): + print(f"Initializing BallisticaAPI") + self.url = url + self.api_key = None + + async def initialize(self) -> BallisticaAPI: + """Initialize the API client by retrieving the API key.""" + self.api_key = await self.get_api_key() + return self + + async def get_api_key(self, max_retries=3, initial_delay=2.0, retry_delay=2.0) -> str: + """Get a transient API key with automatic retry.""" + await asyncio.sleep(initial_delay) + print("Requesting API key...") + for attempt in range(max_retries): + try: + loop_instance = asyncio.get_event_loop() + future = loop_instance.create_future() + + def on_response(api_key): + future.set_result(api_key) + + babase.app.plus.accounts.primary.request_transient_api_key(on_response) + api_key = await asyncio.wait_for(future, timeout=10.0) + + # Validate we got a real key, not an error + if isinstance(api_key, str) and api_key.startswith('bsac-'): + print(f'API key retrieval successful: {api_key[:20]}...') + return api_key + elif isinstance(api_key, self.CommunicationError): + print(f'Communication error while getting API key: {api_key}') + raise api_key + else: + raise ValueError(f'Invalid API key: {api_key}') + + except Exception as e: + if attempt < max_retries - 1: + await asyncio.sleep(retry_delay) + else: + print(f'Failed to get API key: {e}') + raise + + def _make_request(self, api_key: str, Endpoint: str) -> str: + """Make an HTTP GET request to the API and return the response text.""" + headers = {'Authorization': f'Bearer {api_key}'} + try: + response = urllib.request.urlopen( + urllib.request.Request(self.url + Endpoint, headers=headers) + ) + if response.getcode() == 200: + return response.read().decode('utf-8') + else: + raise Exception(f'API request failed with status {response.getcode()}') + except urllib.error.HTTPError as e: + raise Exception(f'HTTP Error: {e.code} - {e.reason}') + + async def get_account(self, api_key: str, account_id: str) -> AccountResponse: + """Get account info. Pass 'me' for the authenticated account.""" + + from bacommon.restapi.v1.accounts import AccountResponse + from efro.dataclassio import dataclass_from_json + + self.Endpoint = self.Endpoint.ACCOUNT.format(account_id=account_id) + response_text = await loop.run_in_executor(None, self._make_request, api_key, self.Endpoint) + return dataclass_from_json(AccountResponse, response_text) + + async def get_workspaces(self, api_key: str) -> WorkspacesResponse: + """List all workspaces for the authenticated account.""" + from efro.dataclassio import dataclass_from_json + + response_text = await loop.run_in_executor( + None, self._make_request, api_key, self.Endpoint.WORKSPACES + ) + return dataclass_from_json(self.WorkspacesResponse, response_text) + + async def get_workspace(self, api_key: str, workspace_id: str) -> WorkspaceResponse: + """Fetch metadata for a single workspace.""" + + from bacommon.restapi.v1.workspaces import WorkspaceResponse + from efro.dataclassio import dataclass_from_json + + self.Endpoint = self.Endpoint.WORKSPACE.format(workspace_id=workspace_id) + response_text = await loop.run_in_executor(None, self._make_request, api_key, self.Endpoint) + return dataclass_from_json(self.WorkspaceResponse, response_text) + + async def get_workspace_files(self, api_key: str, workspace_id: str) -> WorkspaceFilesResponse: + """Get flat listing of all files and directories in the workspace.""" + from efro.dataclassio import dataclass_from_json + + self.Endpoint = self.Endpoint.WORKSPACE_FILES.format(workspace_id=workspace_id) + response_text = await loop.run_in_executor(None, self._make_request, api_key, self.Endpoint) + return dataclass_from_json(self.WorkspaceFilesResponse, response_text) + + async def get_active_workspace(self, api_key: str) -> ActiveWorkspaceResponse: + """Fetch the active workspace for the authenticated account.""" + from efro.dataclassio import dataclass_from_json + + response_text = await loop.run_in_executor( + None, self._make_request, api_key, self.Endpoint.WORKSPACES_ACTIVE + ) + return dataclass_from_json(self.ActiveWorkspaceResponse, response_text) + + class Category: + """Represents a category of plugins from a metadata source. + + Manages fetching, caching, and validating plugin metadata from a remote source. + """ + def __init__(self, meta_url, tag=CURRENT_TAG): self.meta_url = meta_url self.tag = tag @@ -458,14 +792,14 @@ async def get_plugins_base_url(self): async def get_plugins(self): if self._plugins is None: await self.fetch_metadata() - self._plugins = ([ + self._plugins = [ Plugin( plugin_info, f"{await self.get_plugins_base_url()}/{plugin_info[0]}.py", tag=self.tag, ) for plugin_info in self._metadata["plugins"].items() - ]) + ] self.set_category_global_cache("plugins", self._plugins) return self._plugins @@ -505,35 +839,38 @@ def __init__(self, plugins={}): class PluginLocal: - def __init__(self, name): - """ - Initialize a plugin locally installed on the device. - """ + """Represents a locally installed plugin on the device. + + Handles reading plugin metadata, managing installation status, + and controlling plugin enable/disable state. + """ + + def __init__(self, name: str): self.name = name self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{name}.py") self._entry_point_initials = f"{self.name}." self.cleanup() - def cleanup(self): + def cleanup(self) -> None: self._content = None self._api_version = None self._entry_points = [] self._has_minigames = None @property - def is_installed(self): + def is_installed(self) -> bool: return os.path.isfile(self.install_path) @property - def is_installed_via_plugin_manager(self): + def is_installed_via_plugin_manager(self) -> bool: return self.name in babase.app.config["Community Plugin Manager"]["Installed Plugins"] - def initialize(self): + def initialize(self) -> PluginLocal: if self.name not in babase.app.config["Community Plugin Manager"]["Installed Plugins"]: babase.app.config["Community Plugin Manager"]["Installed Plugins"][self.name] = {} return self - async def uninstall(self): + async def uninstall(self) -> None: if await self.has_minigames(): self.unload_minigames() try: @@ -548,10 +885,11 @@ async def uninstall(self): self.save() @property - def version(self): + def version(self) -> str | None: try: - version = (babase.app.config["Community Plugin Manager"] - ["Installed Plugins"][self.name]["version"]) + version = babase.app.config["Community Plugin Manager"]["Installed Plugins"][self.name][ + "version" + ] except KeyError: version = None return version @@ -574,7 +912,7 @@ def launch_settings(self, source_widget): if plugin_entry_point.startswith(self._entry_point_initials): return plugin_spec.plugin.show_settings_ui(source_widget) - async def get_content(self): + async def get_content(self) -> bytes: if self._content is None: if not self.is_installed: raise PluginNotInstalled("Plugin is not available locally.") @@ -582,13 +920,23 @@ async def get_content(self): self._content = await loop.run_in_executor(None, self._get_content) return self._content - async def get_api_version(self): + async def get_api_version(self) -> bytes: + """Extract the required API version from plugin metadata. + + Returns: + API version string from ba_meta require api comment + """ if self._api_version is None: content = await self.get_content() self._api_version = REGEXP["plugin_api_version"].search(content).group() return self._api_version - async def get_entry_points(self): + async def get_entry_points(self) -> tuple[str, ...]: + """Get all entry points exported by this plugin. + + Returns: + Tuple of entry point class names + """ if not self._entry_points: content = await self.get_content() groups = REGEXP["plugin_entry_points"].findall(content) @@ -597,13 +945,23 @@ async def get_entry_points(self): self._entry_points = entry_points return self._entry_points - async def has_minigames(self): + async def has_minigames(self) -> bool: + """Check if plugin exports any minigames. + + Returns: + True if plugin contains GameActivity classes + """ if self._has_minigames is None: content = await self.get_content() self._has_minigames = REGEXP["minigames"].search(content) is not None return self._has_minigames - async def has_plugins(self): + async def has_plugins(self) -> bool: + """Check if plugin has any entry points. + + Returns: + True if plugin exports plugin entry points + """ entry_points = await self.get_entry_points() return len(entry_points) > 0 @@ -704,6 +1062,11 @@ def save(self): class PluginVersion: + """Represents a specific version of a plugin. + + Contains version information, metadata, and handles installation. + """ + def __init__(self, plugin, version, tag=CURRENT_TAG): self.number, info = version self.plugin = plugin @@ -716,8 +1079,10 @@ def __init__(self, plugin, version, tag=CURRENT_TAG): self.view_url = self.plugin.url.format(content_type="blob", tag=tag) def __eq__(self, plugin_version): - return (self.number, self.plugin.name) == (plugin_version.number, - plugin_version.plugin.name) + return (self.number, self.plugin.name) == ( + plugin_version.number, + plugin_version.plugin.name, + ) def __repr__(self): return f"" @@ -743,7 +1108,8 @@ async def install(self, suppress_screenmessage=False): except MD5CheckSumFailed: if not suppress_screenmessage: bui.screenmessage( - f"{self.plugin.name} failed MD5 checksum during installation", color=(1, 0, 0)) + f"{self.plugin.name} failed MD5 checksum during installation", color=(1, 0, 0) + ) return False else: if not suppress_screenmessage: @@ -755,10 +1121,12 @@ async def install(self, suppress_screenmessage=False): class Plugin: + """Represents a plugin from a remote repository. + + Handles plugin information, version management, and installation/updates. + """ + def __init__(self, plugin, url, tag=CURRENT_TAG): - """ - Initialize a plugin from network repository. - """ self.name, self.info = plugin self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{self.name}.py") self.url = url @@ -795,7 +1163,8 @@ def versions(self): self, version, tag=self.tag, - ) for version in self.info["versions"].items() + ) + for version in self.info["versions"].items() ] return self._versions @@ -817,7 +1186,9 @@ def latest_compatible_version(self): self._latest_compatible_version = PluginVersion( self, (number, info), - tag=self.tag if self.latest_version.number == number else info["commit_sha"] + tag=( + self.tag if self.latest_version.number == number else info["commit_sha"] + ), ) break if self._latest_compatible_version is None: @@ -828,17 +1199,13 @@ def latest_compatible_version(self): def get_local(self): if not self.is_installed: - raise PluginNotInstalled( - f"{self.name} needs to be installed to get its local plugin.") + raise PluginNotInstalled(f"{self.name} needs to be installed to get its local plugin.") if self._local_plugin is None: self._local_plugin = PluginLocal(self.name) return self._local_plugin def create_local(self): - return ( - PluginLocal(self.name) - .initialize() - ) + return PluginLocal(self.name).initialize() async def uninstall(self): await self.get_local().uninstall() @@ -854,17 +1221,26 @@ def has_update(self): async def update(self): if await self.latest_compatible_version.install(suppress_screenmessage=True): - bui.screenmessage(f"{self.name} updated to {self.latest_compatible_version.number}", - color=(0, 1, 0)) + bui.screenmessage( + f"{self.name} updated to {self.latest_compatible_version.number}", color=(0, 1, 0) + ) bui.getsound('shieldUp').play() else: - bui.screenmessage(f"{self.name} failed MD5 checksum while updating to " - f"{self.latest_compatible_version.number}", - color=(1, 0, 0)) + bui.screenmessage( + f"{self.name} failed MD5 checksum while updating to " + f"{self.latest_compatible_version.number}", + color=(1, 0, 0), + ) bui.getsound('error').play() class PluginManager: + """Central manager for plugin operations. + + Handles plugin index management, category management, version management, + and self-updates. + """ + def __init__(self): self.request_headers = HEADERS self._index = _CACHE.get("index", {}) @@ -878,9 +1254,7 @@ async def get_index(self): if not self._index: request = urllib.request.Request( INDEX_META.format( - repository_url=REPOSITORY_URL, - content_type="raw", - tag=CURRENT_TAG + repository_url=REPOSITORY_URL, content_type="raw", tag=CURRENT_TAG ), headers=self.request_headers, ) @@ -903,12 +1277,12 @@ async def setup_index(self): async def get_changelog(self) -> list[str, bool]: requested = False if not self._changelog: - request = urllib.request.Request(CHANGELOG_META.format( - repository_url=REPOSITORY_URL, - content_type="raw", - tag=CURRENT_TAG - ), - headers=self.request_headers) + request = urllib.request.Request( + CHANGELOG_META.format( + repository_url=REPOSITORY_URL, content_type="raw", tag=CURRENT_TAG + ), + headers=self.request_headers, + ) response = await async_send_network_request(request) self._changelog = response.read().decode() requested = True @@ -927,28 +1301,34 @@ async def setup_changelog(self, version=None) -> None: # check if the changelog was requested if full_changelog[1]: pattern = rf"### {version} \(\d\d-\d\d-\d{{4}}\)\n(.*?)(?=### \d+\.\d+\.\d+|\Z)" - if (len(full_changelog[0].split(version)) > 1): + if len(full_changelog[0].split(version)) > 1: released_on = full_changelog[0].split(version)[1].split('\n')[0] matches = re.findall(pattern, full_changelog[0], re.DOTALL) else: matches = None if matches: + changelog = {'released_on': released_on, 'info': matches[0].strip()} + else: changelog = { - 'released_on': released_on, - 'info': matches[0].strip() + 'released_on': ' (Not Provided)', + 'info': f"Changelog entry for version {version} not found.", } - else: - changelog = {'released_on': ' (Not Provided)', - 'info': f"Changelog entry for version {version} not found."} else: changelog = full_changelog[0] except urllib.error.URLError: - changelog = {'released_on': ' (Not Provided)', - 'info': 'Could not get ChangeLog due to Internet Issues.'} + changelog = { + 'released_on': ' (Not Provided)', + 'info': 'Could not get ChangeLog due to Internet Issues.', + } self.set_changelog_global_cache(changelog) self._changelog_setup_in_progress = False + async def get_current_workspaces(self): + self.api = BallisticaAPI() + await self.api.initialize() + print((await self.api.get_workspaces(self.api.api_key)).workspaces) + async def setup_plugin_categories(self, plugin_index): # A hack to have the "All" category show at the top. self.categories["All"] = None @@ -1049,10 +1429,10 @@ async def soft_refresh(self): class ChangelogWindow(popup.PopupWindow): def __init__(self, origin_widget): self.scale_origin = origin_widget.get_screen_space_center() - s = 1.65 if _uiscale() is babase.UIScale.SMALL else 1.39 if _uiscale() is babase.UIScale.MEDIUM else 1.67 + s = UIConfig.scale_value(1.65, 1.39, 1.67) width = 400 * s height = width * 0.5 - color = (1, 1, 1) + color = UIConfig.get_color('text_primary') text_scale = 0.7 * s self._transition_out = 'out_scale' transition = 'in_scale' @@ -1061,9 +1441,12 @@ def __init__(self, origin_widget): size=(width, height), on_outside_click_call=self._back, transition=transition, - scale=(1.5 if _uiscale() is babase.UIScale.SMALL else 1.5 if _uiscale() - is babase.UIScale.MEDIUM else 1.0), - scale_origin_stack_offset=self.scale_origin + scale=( + 1.5 + if _uiscale() is babase.UIScale.SMALL + else 1.5 if _uiscale() is babase.UIScale.MEDIUM else 1.0 + ), + scale_origin_stack_offset=self.scale_origin, ) _add_popup(self) @@ -1077,7 +1460,7 @@ def __init__(self, origin_widget): text='ChangeLog', scale=text_scale * 1.25, color=bui.app.ui_v1.title_color, - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) back_button = bui.buttonwidget( @@ -1088,7 +1471,7 @@ def __init__(self, origin_widget): label=babase.charstr(babase.SpecialChar.BACK), button_type='backSmall', on_activate_call=self._back, - enable_sound=False + enable_sound=False, ) bui.containerwidget(edit=self._root_widget, cancel_button=back_button) @@ -1113,7 +1496,7 @@ def __init__(self, origin_widget): text=PLUGIN_MANAGER_VERSION + released_on, scale=text_scale * 0.9, color=color, - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) bui.buttonwidget( @@ -1125,9 +1508,9 @@ def __init__(self, origin_widget): button_type='square', enable_sound=False, on_activate_call=lambda: ( - bui.getsound('deek').play() or - bui.open_url(REPOSITORY_URL + '/blob/main/CHANGELOG.md') - ) + bui.getsound('deek').play() + or bui.open_url(REPOSITORY_URL + '/blob/main/CHANGELOG.md') + ), ) loop_height = height * 0.62 @@ -1141,7 +1524,7 @@ def __init__(self, origin_widget): text=log, scale=text_scale, color=color, - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) loop_height -= 30 @@ -1155,10 +1538,10 @@ class AuthorsWindow(popup.PopupWindow): def __init__(self, authors_info, origin_widget): self.authors_info = authors_info self.scale_origin = origin_widget.get_screen_space_center() - s = 1.25 if _uiscale() is babase.UIScale.SMALL else 1.39 if _uiscale() is babase.UIScale.MEDIUM else 1.67 + s = UIConfig.scale_value(1.25, 1.39, 1.67) width = 400 * s height = width * 0.8 - color = (1, 1, 1) + color = UIConfig.get_color('text_primary') text_scale = 0.7 * s self._transition_out = 'out_scale' transition = 'in_scale' @@ -1167,9 +1550,8 @@ def __init__(self, authors_info, origin_widget): size=(width, height), on_outside_click_call=self._back, transition=transition, - scale=(1.5 if _uiscale() is babase.UIScale.SMALL else 1.5 - if _uiscale() is babase.UIScale.MEDIUM else 1.0), - scale_origin_stack_offset=self.scale_origin + scale=UIConfig.scale_value(1.5, 1.5, 1.0), + scale_origin_stack_offset=self.scale_origin, ) _add_popup(self) @@ -1184,7 +1566,7 @@ def __init__(self, authors_info, origin_widget): text='Authors', scale=text_scale * 1.25, color=color, - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) back_button = bui.buttonwidget( @@ -1195,7 +1577,7 @@ def __init__(self, authors_info, origin_widget): label=babase.charstr(babase.SpecialChar.BACK), button_type='backSmall', on_activate_call=self._back, - enable_sound=False + enable_sound=False, ) bui.containerwidget(edit=self._root_widget, cancel_button=back_button) @@ -1203,13 +1585,10 @@ def __init__(self, authors_info, origin_widget): self._scrollwidget = bui.scrollwidget( parent=self._root_widget, size=(width * 0.8, height * 0.75), - position=(width * 0.1, height * 0.1) + position=(width * 0.1, height * 0.1), ) self._columnwidget = bui.columnwidget( - parent=self._scrollwidget, - border=1, - left_border=-15, - margin=0 + parent=self._scrollwidget, border=1, left_border=-15, margin=0 ) for author in self.authors_info: @@ -1220,22 +1599,27 @@ def __init__(self, authors_info, origin_widget): bui.textwidget( parent=self._columnwidget, size=(width * 0.8, 35 if key == 'name' else 30), - color=color if key == 'name' else (0.75, 0.7, 0.8), + color=color if key == 'name' else UIConfig.get_color('text_secondary'), scale=( - (1.1 if key == 'name' else 0.9) if _uiscale() is babase.UIScale.SMALL else - (1.2 if key == 'name' else 1.0) + UIConfig.scale_value( + (1.1 if key == 'name' else 0.9), + (1.2 if key == 'name' else 1.0), + (1.2 if key == 'name' else 1.0), + ) + if _uiscale() is babase.UIScale.SMALL + else (1.2 if key == 'name' else 1.0) ), text=text, h_align='center', v_align='center', - maxwidth=420 + maxwidth=420, ) bui.textwidget( parent=self._columnwidget, size=(width * 0.8, 30), always_highlight=True, h_align='center', - v_align='center' + v_align='center', ) def _back(self) -> None: @@ -1272,10 +1656,12 @@ def get_description(self, minimum_character_offset=40): partitioned_string_length = len(partitioned_string) while partitioned_string_length != string_length: - next_empty_space = string[partitioned_string_length + - minimum_character_offset:].find(" ") - next_word_end_position = partitioned_string_length + \ - minimum_character_offset + max(0, next_empty_space) + next_empty_space = string[partitioned_string_length + minimum_character_offset:].find( + " " + ) + next_word_end_position = ( + partitioned_string_length + minimum_character_offset + max(0, next_empty_space) + ) partitioned_string += string[partitioned_string_length:next_word_end_position] if next_empty_space != -1: # Insert a line break here, there's still more partitioning to do. @@ -1286,28 +1672,27 @@ def get_description(self, minimum_character_offset=40): async def draw_ui(self): bui.getsound('swish').play() - b_text_color = (0.75, 0.7, 0.8) - s = 1.25 if _uiscale() is babase.UIScale.SMALL else 1.39 if babase.UIScale.MEDIUM else 1.67 + b_text_color = UIConfig.get_color('text_secondary') + s = UIConfig.scale_value(1.25, 1.39, 1.67) width = 450 * s height = 120 + 100 * s - color = (1, 1, 1) + color = UIConfig.get_color('text_primary') text_scale = 0.7 * s self._root_widget = bui.containerwidget( size=(width, height), on_outside_click_call=self._cancel, transition=self.transition, - scale=(2.1 if _uiscale() is babase.UIScale.SMALL else 1.5 - if _uiscale() is babase.UIScale.MEDIUM else 1.0), - scale_origin_stack_offset=self.scale_origin + scale=UIConfig.scale_value(2.1, 1.5, 1.0), + scale_origin_stack_offset=self.scale_origin, ) _add_popup(self) i = self.plugins_list.index(self.plugin) self.p_n_plugins = [ - self.plugins_list[i-1] if (i-1 > -1) else None, - self.plugins_list[i+1] if (i+1 < len(self.plugins_list)) else None + self.plugins_list[i - 1] if (i - 1 > -1) else None, + self.plugins_list[i + 1] if (i + 1 < len(self.plugins_list)) else None, ] # initialize all widgets @@ -1325,30 +1710,34 @@ async def draw_ui(self): if self.p_n_plugins[0] is not None: previous_plugin_button = bui.buttonwidget( parent=self._root_widget, - position=(-12.5*s + (4 if _uiscale() is babase.UIScale.SMALL else -5), - height/2 - 20*s), + position=( + -12.5 * s + (4 if _uiscale() is babase.UIScale.SMALL else -5), + height / 2 - 20 * s, + ), label=bui.charstr(bui.SpecialChar.LEFT_ARROW), size=(25, 40), color=(1, 0.5, 0.5), scale=s, on_activate_call=self.show_previous_plugin, enable_sound=False, - textcolor=(1, 1, 1) + textcolor=(1, 1, 1), ) # next plugin button if self.p_n_plugins[1] is not None: next_plugin_button = bui.buttonwidget( parent=self._root_widget, - position=(width - 12.5*s - (8 if _uiscale() - is babase.UIScale.SMALL else 0), height/2 - 20*s), + position=( + width - 12.5 * s - (8 if _uiscale() is babase.UIScale.SMALL else 0), + height / 2 - 20 * s, + ), label=bui.charstr(bui.SpecialChar.RIGHT_ARROW), size=(25, 40), color=(1, 0.5, 0.5), scale=s, on_activate_call=self.show_next_plugin, enable_sound=False, - textcolor=(1, 1, 1) + textcolor=(1, 1, 1), ) pos = height * 0.85 @@ -1363,7 +1752,7 @@ async def draw_ui(self): text=plugin_title, scale=text_scale * 1.25, color=color, - maxwidth=width * 0.7 - 40 + maxwidth=width * 0.7 - 40, ) pos -= 25 @@ -1371,16 +1760,16 @@ async def draw_ui(self): text = 'by ' + ', '.join([author["name"] for author in self.plugin.info["authors"]]) author_text_control_btn = bui.buttonwidget( parent=self._root_widget, - position=(width * 0.49 - (len(text)*14/2), pos - 10), - size=(len(text)*14, 20), + position=(width * 0.49 - (len(text) * 14 / 2), pos - 10), + size=(len(text) * 14, 20), label='', texture=bui.gettexture("empty"), - on_activate_call=lambda: AuthorsWindow(self.plugin.info["authors"], self._root_widget) + on_activate_call=lambda: AuthorsWindow(self.plugin.info["authors"], self._root_widget), ) bui.textwidget( parent=self._root_widget, - position=(width * 0.49 - (len(text)*14/2), pos - 10), - size=(len(text)*14, 20), + position=(width * 0.49 - (len(text) * 14 / 2), pos - 10), + size=(len(text) * 14, 20), h_align='center', v_align='center', text=text, @@ -1401,12 +1790,12 @@ async def draw_ui(self): text=self.get_description(), scale=text_scale * 0.6, color=color, - maxwidth=width * 0.95 + maxwidth=width * 0.95, ) b1_color = None - b2_color = (0.8, 0.15, 0.35) - b3_color = (0.2, 0.8, 0.3) + b2_color = UIConfig.get_color('button_red') + b3_color = UIConfig.get_color('button_green') pos = height * 0.1 button_size = (80 * s, 40 * s) @@ -1421,7 +1810,7 @@ async def draw_ui(self): else: if await self.local_plugin.is_enabled(): button1_label = "Disable" - b1_color = (0.6, 0.53, 0.63) + b1_color = UIConfig.get_color('button_neutral') button1_action = self.disable if self.local_plugin.has_settings(): to_draw_settings_button = True @@ -1443,8 +1832,13 @@ async def draw_ui(self): button1 = bui.buttonwidget( parent=self._root_widget, position=( - width * (0.1 if self.plugin.is_installed and has_update else - 0.25 if self.plugin.is_installed else 0.4), pos + width + * ( + 0.1 + if self.plugin.is_installed and has_update + else 0.25 if self.plugin.is_installed else 0.4 + ), + pos, ), size=button_size, on_activate_call=button1_action, @@ -1452,22 +1846,21 @@ async def draw_ui(self): textcolor=b_text_color, button_type='square', text_scale=1, - label=button1_label + label=button1_label, ) # button2 if self.plugin.is_installed: button2 = bui.buttonwidget( parent=self._root_widget, - position=( - width * (0.4 if has_update or not to_draw_button1 else 0.55), pos), + position=(width * (0.4 if has_update or not to_draw_button1 else 0.55), pos), size=button_size, on_activate_call=button2_action, color=b2_color, textcolor=b_text_color, button_type='square', text_scale=1, - label=button2_label + label=button2_label, ) # button3 @@ -1482,129 +1875,71 @@ async def draw_ui(self): autoselect=True, button_type='square', text_scale=1, - label=button3_label + label=button3_label, ) # determine selected button - selected_btn = button3 if has_update and self.plugin.is_installed else ( - button2 if self.plugin.is_installed else button1 + selected_btn = ( + button3 + if has_update and self.plugin.is_installed + else (button2 if self.plugin.is_installed else button1) ) bui.containerwidget( - edit=self._root_widget, - on_cancel_call=self._cancel, - selected_child=selected_btn + edit=self._root_widget, on_cancel_call=self._cancel, selected_child=selected_btn ) # button math button_x = width - 90 - button_y = 210 - _by_scale(5, 10, 15) + button_y = 210 - UIConfig.scale_value(5, 10, 15) button_color = (0, 0.75, 0.75) button_image_color = (0.8, 0.95, 1) - button_text_color = (1, 1, 1, 1) + button_text_color = UIConfig.get_color('text_primary') # more button - more_button = bui.buttonwidget( + more_button = UIHelpers.create_icon_button_with_text( parent=self._root_widget, position=(button_x, button_y), size=(40, 40), - button_type='square', - label='', - color=button_color - ) - bui.buttonwidget( - edit=more_button, - on_activate_call=babase.CallPartial( - MoreWindow, - plugin=self.plugin, - origin=more_button - ) - ) - bui.imagewidget( - parent=self._root_widget, - position=(button_x, button_y), - size=(40, 40), - color=button_image_color, - texture=bui.gettexture('file'), - draw_controller=more_button - ) - bui.textwidget( - parent=self._root_widget, - position=(button_x-3, button_y+12), + texture='file', + on_activate=lambda: MoreWindow(plugin=self.plugin, origin=more_button), text='More...', - size=(10, 10), - draw_controller=more_button, - color=button_text_color, - rotate=25, - scale=0.45 + text_color=button_text_color, + btn_color=button_color, + icon_color=button_image_color, ) # settings button if to_draw_settings_button: button_y -= 50 - settings_button = bui.buttonwidget( + settings_button = UIHelpers.create_icon_button( parent=self._root_widget, - autoselect=True, position=(button_x, button_y), size=(40, 40), - button_type="square", - label="", - color=button_color - ) - bui.buttonwidget( - edit=settings_button, - on_activate_call=babase.CallPartial(self.settings, settings_button) - ) - bui.imagewidget( - parent=self._root_widget, - position=(button_x, button_y), - size=(40, 40), - color=button_image_color, - texture=bui.gettexture("settingsIcon"), - draw_controller=settings_button + texture='settingsIcon', + on_activate=babase.CallPartial(self.settings, None), + btn_color=button_color, + icon_color=button_image_color, ) # tutorial tutorial_url = self.plugin.info['external_url'] if tutorial_url: button_y -= 50 - tutorial_button = bui.buttonwidget( - parent=self._root_widget, - autoselect=True, - position=(button_x, button_y), - size=(40, 40), - button_type="square", - label="", - color=button_color - ) - bui.imagewidget( + tutorial_button = UIHelpers.create_icon_button_with_text( parent=self._root_widget, position=(button_x, button_y), size=(40, 40), - color=button_image_color, - texture=bui.gettexture("frameInset"), - draw_controller=tutorial_button - ) - bui.textwidget( - parent=self._root_widget, - position=(button_x - 3, button_y + 12), - text="Tutorial", - size=(10, 10), - draw_controller=tutorial_button, - color=button_text_color, - rotate=25, - scale=0.45 - ) - bui.buttonwidget( - tutorial_button, - on_activate_call=bui.CallPartial( - confirm.ConfirmWindow, + texture='frameInset', + on_activate=lambda: confirm.ConfirmWindow( text=f'This will take you to:\n{tutorial_url}', origin_widget=tutorial_button, - action=bui.CallPartial( - bui.open_url, tutorial_url - ) - ) + action=bui.CallPartial(bui.open_url, tutorial_url), + ), + text='Tutorial', + text_color=button_text_color, + btn_color=button_color, + icon_color=button_image_color, ) # navigation #XXX if button1 and button2 and button3: @@ -1646,7 +1981,7 @@ async def draw_ui(self): left_widget=next_plugin_button, right_widget=leftmost_button, up_widget=author_text_control_btn, - down_widget=leftmost_button + down_widget=leftmost_button, ) # next plugin button @@ -1656,7 +1991,7 @@ async def draw_ui(self): left_widget=bottom_stack, right_widget=bui.get_special_widget('squad_button'), up_widget=author_text_control_btn, - down_widget=bottom_stack + down_widget=bottom_stack, ) # author button @@ -1665,7 +2000,7 @@ async def draw_ui(self): left_widget=previous_plugin_button, right_widget=top_stack, up_widget=center_button or leftmost_button, - down_widget=leftmost_button + down_widget=leftmost_button, ) # button1 @@ -1675,7 +2010,7 @@ async def draw_ui(self): left_widget=previous_plugin_button, right_widget=center_button or bottom_stack, up_widget=author_text_control_btn, - down_widget=author_text_control_btn + down_widget=author_text_control_btn, ) # button2 @@ -1685,7 +2020,7 @@ async def draw_ui(self): left_widget=button1 or previous_plugin_button, right_widget=button3 or bottom_stack, up_widget=author_text_control_btn, - down_widget=author_text_control_btn + down_widget=author_text_control_btn, ) # button3 @@ -1695,7 +2030,7 @@ async def draw_ui(self): left_widget=center_button or leftmost_button, right_widget=bottom_stack, up_widget=author_text_control_btn, - down_widget=author_text_control_btn + down_widget=author_text_control_btn, ) # more button @@ -1704,7 +2039,7 @@ async def draw_ui(self): left_widget=rightmost_button, right_widget=next_plugin_button, up_widget=author_text_control_btn, - down_widget=settings_button + down_widget=settings_button, ) # settings button @@ -1714,7 +2049,7 @@ async def draw_ui(self): left_widget=rightmost_button, right_widget=next_plugin_button, up_widget=more_button, - down_widget=tutorial_button + down_widget=tutorial_button, ) # tutorial button @@ -1724,7 +2059,7 @@ async def draw_ui(self): left_widget=rightmost_button, right_widget=next_plugin_button, up_widget=settings_button or more_button, - down_widget=rightmost_button + down_widget=rightmost_button, ) def _ok(self) -> None: @@ -1763,7 +2098,7 @@ def show_previous_plugin(self): self._root_widget, transition='in_left', plugins_list=self.plugins_list, - button_callback=lambda: None + button_callback=lambda: None, ) def show_next_plugin(self): @@ -1774,7 +2109,7 @@ def show_next_plugin(self): self._root_widget, transition='in_right', plugins_list=self.plugins_list, - button_callback=lambda: None + button_callback=lambda: None, ) @button @@ -1817,19 +2152,15 @@ def __init__(self, plugin: Plugin, origin=None): margin = 20 width = 350 px, py = margin, margin - px2 = width-(margin+step) - mw = px2-margin*2 + px2 = width - (margin + step) + mw = px2 - margin * 2 # root self.transition = origin and 'scale' or 'left' self._root_widget = bui.containerwidget( on_outside_click_call=self._back, transition=f'in_{self.transition}', - scale=_by_scale(1.5, 1.5, 1), - scale_origin_stack_offset=( - origin and - origin.get_screen_space_center() - or None - ) + scale=UIConfig.scale_value(1.5, 1.5, 1), + scale_origin_stack_offset=(origin and origin.get_screen_space_center() or None), ) # last updated bui.textwidget( @@ -1837,17 +2168,13 @@ def __init__(self, plugin: Plugin, origin=None): position=(px, py), h_align='left', v_align='center', - maxwidth=width-margin*2, + maxwidth=width - margin * 2, max_height=step, size=(step, step), - text=( - last_updated and - f'Last updated on: {last_updated}' - or 'Unknown' - ) + text=(last_updated and f'Last updated on: {last_updated}' or 'Unknown'), ) # version - py += margin+step/3 + py += margin + step / 3 bui.textwidget( parent=self._root_widget, position=(px, py), @@ -1856,10 +2183,10 @@ def __init__(self, plugin: Plugin, origin=None): maxwidth=mw, max_height=step, size=(step, step), - text=f"Version: {plugin.latest_compatible_version.number}" + text=f"Version: {plugin.latest_compatible_version.number}", ) # source url - py += step+margin + py += step + margin bui.textwidget( parent=self._root_widget, position=(px, py), @@ -1868,40 +2195,21 @@ def __init__(self, plugin: Plugin, origin=None): maxwidth=mw, max_height=step, size=(step, step), - text='Source URL:' - ) - source_button = bui.buttonwidget( - parent=self._root_widget, - autoselect=True, - position=(px2, py), - size=(step, step), - color=(0.6, 0.53, 0.63), - button_type='square', - label='', - on_activate_call=bui.CallPartial( - bui.open_url, - plugin.view_url - ) + text='Source URL:', ) - bui.imagewidget( + source_button = UIHelpers.create_icon_button_with_text( parent=self._root_widget, position=(px2, py), size=(step, step), - color=(0.8, 0.95, 1), - texture=bui.gettexture('file'), - draw_controller=source_button - ) - bui.textwidget( - parent=self._root_widget, - position=(px2-3, py+12), + texture='file', + on_activate=bui.CallPartial(bui.open_url, plugin.view_url), text='source', - size=(10, 10), - draw_controller=source_button, - rotate=25, - scale=0.45 + text_color=(1, 1, 1), + btn_color=(0.6, 0.53, 0.63), + icon_color=(0.8, 0.95, 1), ) # report bug - py += step+margin + py += step + margin bui.textwidget( parent=self._root_widget, position=(px, py), @@ -1910,27 +2218,19 @@ def __init__(self, plugin: Plugin, origin=None): maxwidth=mw, max_height=step, size=(step, step), - text='Report a bug:' - ) - report_button = bui.buttonwidget( - parent=self._root_widget, - position=(px2, py), - size=(step, step), - button_type='square', - color=(0.6, 0.53, 0.63), - label='', - on_activate_call=self._open_bug_report_url + text='Report a bug:', ) - bui.imagewidget( + report_button = UIHelpers.create_icon_button( parent=self._root_widget, position=(px2, py), size=(step, step), - color=(0.8, 0.95, 1), - texture=bui.gettexture('githubLogo'), - draw_controller=report_button + texture='githubLogo', + on_activate=self._open_bug_report_url, + btn_color=(0.6, 0.53, 0.63), + icon_color=(0.8, 0.95, 1), ) # back - py += step+margin + py += step + margin back_button = bui.buttonwidget( parent=self._root_widget, position=(px, py), @@ -1938,36 +2238,32 @@ def __init__(self, plugin: Plugin, origin=None): label=babase.charstr(babase.SpecialChar.BACK), button_type='backSmall', on_activate_call=self._back, - enable_sound=False + enable_sound=False, ) # title px += margin bui.textwidget( parent=self._root_widget, - position=(px+(step), py), + position=(px + (step), py), h_align='left', v_align='center', - maxwidth=width-margin*2, - max_height=step*2, + maxwidth=width - margin * 2, + max_height=step * 2, size=(step, step), text='More info', scale=1.4, - shadow=0.8 + shadow=0.8, ) # finally - py += step+margin - bui.containerwidget( - edit=self._root_widget, - cancel_button=back_button, - size=(width, py) - ) + py += step + margin + bui.containerwidget(edit=self._root_widget, cancel_button=back_button, size=(width, py)) # navigation bui.buttonwidget( edit=back_button, left_widget=report_button, right_widget=report_button, up_widget=source_button, - down_widget=report_button + down_widget=report_button, ) bui.buttonwidget( @@ -1975,7 +2271,7 @@ def __init__(self, plugin: Plugin, origin=None): left_widget=back_button, right_widget=back_button, up_widget=report_button, - down_widget=back_button + down_widget=back_button, ) bui.buttonwidget( @@ -1983,7 +2279,7 @@ def __init__(self, plugin: Plugin, origin=None): left_widget=back_button, right_widget=back_button, up_widget=back_button, - down_widget=source_button + down_widget=source_button, ) def _open_bug_report_url(self): @@ -2004,25 +2300,33 @@ def _open_bug_report_url(self): params = { "title": "[PLUGIN BUG]: " + self.plugin.name, "plugin-name": self.plugin.name, - "plugin-version": self.plugin.create_local().version if self.plugin.is_installed else 'Not Installed', + "plugin-version": ( + self.plugin.create_local().version if self.plugin.is_installed else 'Not Installed' + ), "plugin-manager-version": 'v' + PLUGIN_MANAGER_VERSION, - "bombsquad-version": 'v' + babase.app.env.engine_version + " (" + str(babase.app.env.engine_build_number) + ")", - "os-version": platform.platform() if platform else babase.app.env.platform.value + babase.app.env.os_version, - "console-log": error_logs + "bombsquad-version": 'v' + + babase.app.env.engine_version + + " (" + + str(babase.app.env.engine_build_number) + + ")", + "os-version": ( + platform.platform() + if platform + else babase.app.env.platform.value + babase.app.env.os_version + ), + "console-log": error_logs, } query_string = urllib.parse.urlencode(params, quote_via=urllib.parse.quote) base_url = GITHUB_PLUGIN_ISSUE_META.format( - repository_url=REPOSITORY_URL, issue_template=PLUGIN_ISSUE_TEMPLATE) + repository_url=REPOSITORY_URL, issue_template=PLUGIN_ISSUE_TEMPLATE + ) final_url = f"{base_url}&{query_string}" bui.open_url(final_url) def _back(self) -> None: _remove_popup(self) bui.getsound('swish').play() - bui.containerwidget( - edit=self._root_widget, - transition=f'out_{self.transition}' - ) + bui.containerwidget(edit=self._root_widget, transition=f'out_{self.transition}') class PluginCustomSourcesWindow(popup.PopupWindow): @@ -2038,10 +2342,13 @@ def __init__(self, origin_widget): size=(400, 340), on_outside_click_call=self._ok, transition=transition, - scale=(2.1 if _uiscale() is babase.UIScale.SMALL else 1.5 - if _uiscale() is babase.UIScale.MEDIUM else 1.0), + scale=( + 2.1 + if _uiscale() is babase.UIScale.SMALL + else 1.5 if _uiscale() is babase.UIScale.MEDIUM else 1.0 + ), scale_origin_stack_offset=self.scale_origin, - on_cancel_call=self._ok + on_cancel_call=self._ok, ) _add_popup(self) @@ -2058,58 +2365,64 @@ def __init__(self, origin_widget): maxwidth=270, ) - scroll_size_x = (290 if _uiscale() is babase.UIScale.SMALL else - 300 if _uiscale() is babase.UIScale.MEDIUM else 290) - scroll_size_y = (170 if _uiscale() is babase.UIScale.SMALL else - 185 if _uiscale() is babase.UIScale.MEDIUM else 180) - scroll_pos_x = (55 if _uiscale() is babase.UIScale.SMALL else - 40 if _uiscale() is babase.UIScale.MEDIUM else 60) + scroll_size_x = ( + 290 + if _uiscale() is babase.UIScale.SMALL + else 300 if _uiscale() is babase.UIScale.MEDIUM else 290 + ) + scroll_size_y = ( + 170 + if _uiscale() is babase.UIScale.SMALL + else 185 if _uiscale() is babase.UIScale.MEDIUM else 180 + ) + scroll_pos_x = ( + 55 + if _uiscale() is babase.UIScale.SMALL + else 40 if _uiscale() is babase.UIScale.MEDIUM else 60 + ) scroll_pos_y = 105 self._scrollwidget = bui.scrollwidget( parent=self._root_widget, size=(scroll_size_x, scroll_size_y), - position=(scroll_pos_x, scroll_pos_y) - ) - self._columnwidget = bui.columnwidget( - parent=self._scrollwidget, - border=1, margin=0 + position=(scroll_pos_x, scroll_pos_y), ) + self._columnwidget = bui.columnwidget(parent=self._scrollwidget, border=1, margin=0) delete_source_button_position_pos_x = 360 delete_source_button_position_pos_y = 110 delete_source_button = bui.buttonwidget( parent=self._root_widget, - position=( - delete_source_button_position_pos_x, delete_source_button_position_pos_y - ), + position=(delete_source_button_position_pos_x, delete_source_button_position_pos_y), size=(25, 25), label="", on_activate_call=self.delete_selected_source, button_type="square", - color=(0.6, 0, 0) + color=(0.6, 0, 0), ) bui.imagewidget( parent=self._root_widget, - position=( - delete_source_button_position_pos_x + 2, delete_source_button_position_pos_y - ), + position=(delete_source_button_position_pos_x + 2, delete_source_button_position_pos_y), size=(25, 25), color=(5, 2, 2), texture=bui.gettexture("crossOut"), - draw_controller=delete_source_button + draw_controller=delete_source_button, ) - warning_pos_x = (43 if _uiscale() is babase.UIScale.SMALL else - 35 if _uiscale() is babase.UIScale.MEDIUM else - 48) + warning_pos_x = ( + 43 + if _uiscale() is babase.UIScale.SMALL + else 35 if _uiscale() is babase.UIScale.MEDIUM else 48 + ) bui.textwidget( parent=self._root_widget, position=(warning_pos_x, 74), size=(50, 22), - text=("Warning: 3rd party plugin sources are not moderated\n" - " by the community and may be dangerous!"), + text=( + "Warning: 3rd party plugin sources are not moderated\n" + " by the community and may be dangerous!" + ), color=(1, 0.23, 0.23), scale=0.5, h_align="left", @@ -2126,7 +2439,7 @@ def __init__(self, origin_widget): editable=True, scale=0.75, maxwidth=215, - description="Add Source" + description="Add Source", ) bui.buttonwidget( @@ -2139,7 +2452,7 @@ def __init__(self, origin_widget): button_type="square", color=(0, 0.9, 0), textcolor=b_textcolor, - text_scale=1 + text_scale=1, ) self.draw_sources() @@ -2159,7 +2472,7 @@ def draw_sources(self): h_align='left', v_align='center', scale=0.75, - maxwidth=260 + maxwidth=260, ) def select_source(self, source): @@ -2192,8 +2505,9 @@ async def add_source(self): return babase.app.config["Community Plugin Manager"]["Custom Sources"].append(source) babase.app.config.commit() - bui.screenmessage("Plugin source added; Refresh plugin list to see changes", - color=(0, 1, 0)) + bui.screenmessage( + "Plugin source added; Refresh plugin list to see changes", color=(0, 1, 0) + ) bui.getsound('cashRegister2').play() self.draw_sources() @@ -2202,8 +2516,9 @@ def delete_selected_source(self): return babase.app.config["Community Plugin Manager"]["Custom Sources"].remove(self.selected_source) babase.app.config.commit() - bui.screenmessage("Plugin source deleted; Refresh plugin list to see changes", - color=(0.9, 1, 0)) + bui.screenmessage( + "Plugin source deleted; Refresh plugin list to see changes", color=(0.9, 1, 0) + ) bui.getsound('shieldDown').play() self.draw_sources() @@ -2220,11 +2535,14 @@ def __init__(self, choices, current_choice, origin_widget, asyncio_callback): self.scale_origin = origin_widget.get_screen_space_center() super().__init__( position=self.scale_origin, - scale=(2.3 if _uiscale() is babase.UIScale.SMALL else - 1.65 if _uiscale() is babase.UIScale.MEDIUM else 1.23), + scale=( + 2.3 + if _uiscale() is babase.UIScale.SMALL + else 1.65 if _uiscale() is babase.UIScale.MEDIUM else 1.23 + ), choices=choices, current_choice=current_choice, - delegate=self + delegate=self, ) self._root_widget = self.root_widget _add_popup(self) @@ -2234,7 +2552,7 @@ def _update_custom_sources_widget(self): bui.textwidget( edit=self._columnwidget.get_children()[-1], color=(0.5, 0.5, 0.5), - on_activate_call=self.show_sources_window + on_activate_call=self.show_sources_window, ) def popup_menu_selected_choice(self, window, choice): @@ -2259,11 +2577,7 @@ class PluginManagerWindow(bui.MainWindow): def main_window_should_preserve_selection(self) -> bool: return False - def __init__( - self, - transition: str = "in_right", - origin_widget: bui.Widget = None - ): + def __init__(self, transition: str = "in_right", origin_widget: bui.Widget = None): self.plugin_manager = PluginManager() self.category_selection_button = None self.selected_category = 'All' @@ -2275,12 +2589,16 @@ def __init__( loop.create_task(self.draw_index()) - self._width = (700 if _uiscale() is babase.UIScale.SMALL - else 550 if _uiscale() is babase.UIScale.MEDIUM - else 570) - self._height = (500 if _uiscale() is babase.UIScale.SMALL - else 422 if _uiscale() is babase.UIScale.MEDIUM - else 500) + self._width = ( + 700 + if _uiscale() is babase.UIScale.SMALL + else 550 if _uiscale() is babase.UIScale.MEDIUM else 570 + ) + self._height = ( + 500 + if _uiscale() is babase.UIScale.SMALL + else 422 if _uiscale() is babase.UIScale.MEDIUM else 500 + ) top_extra = 20 if _uiscale() is babase.UIScale.SMALL else 0 if origin_widget: @@ -2292,25 +2610,31 @@ def __init__( root_widget=bui.containerwidget( size=(self._width, self._height + top_extra), toolbar_visibility="menu_minimal", - scale=(1.9 if _uiscale() is babase.UIScale.SMALL - else 1.5 if _uiscale() is babase.UIScale.MEDIUM - else 1.0), - stack_offset=(0, -25) if _uiscale() is babase.UIScale.SMALL else (0, 0) + scale=( + 1.9 + if _uiscale() is babase.UIScale.SMALL + else 1.5 if _uiscale() is babase.UIScale.MEDIUM else 1.0 + ), + stack_offset=(0, -25) if _uiscale() is babase.UIScale.SMALL else (0, 0), ), transition=transition, origin_widget=origin_widget, ) - back_pos_x = 5 + (37 if _uiscale() is babase.UIScale.SMALL else - 27 if _uiscale() is babase.UIScale.MEDIUM else 68) - back_pos_y = self._height - (95 if _uiscale() is babase.UIScale.SMALL else - 65 if _uiscale() is babase.UIScale.MEDIUM else 50) + back_pos_x = 5 + ( + 37 + if _uiscale() is babase.UIScale.SMALL + else 27 if _uiscale() is babase.UIScale.MEDIUM else 68 + ) + back_pos_y = self._height - ( + 95 + if _uiscale() is babase.UIScale.SMALL + else 65 if _uiscale() is babase.UIScale.MEDIUM else 50 + ) if _uiscale() is bui.UIScale.SMALL: self._back_button = None - bui.containerwidget( - edit=self._root_widget, on_cancel_call=self.main_window_back - ) + bui.containerwidget(edit=self._root_widget, on_cancel_call=self.main_window_back) else: self._back_button = back_button = bui.buttonwidget( parent=self._root_widget, @@ -2319,13 +2643,16 @@ def __init__( scale=0.8, label=babase.charstr(babase.SpecialChar.BACK), button_type='backSmall', - on_activate_call=self.main_window_back + on_activate_call=self.main_window_back, ) bui.containerwidget(edit=self._root_widget, cancel_button=back_button) - title_pos = self._height - (83 if _uiscale() is babase.UIScale.SMALL else - 50 if _uiscale() is babase.UIScale.MEDIUM else 50) + title_pos = self._height - ( + 83 + if _uiscale() is babase.UIScale.SMALL + else 50 if _uiscale() is babase.UIScale.MEDIUM else 50 + ) bui.textwidget( parent=self._root_widget, position=(-10, title_pos), @@ -2338,8 +2665,11 @@ def __init__( maxwidth=270, ) - loading_pos_y = self._height - (275 if _uiscale() is babase.UIScale.SMALL else - 235 if _uiscale() is babase.UIScale.MEDIUM else 270) + loading_pos_y = self._height - ( + 275 + if _uiscale() is babase.UIScale.SMALL + else 235 if _uiscale() is babase.UIScale.MEDIUM else 270 + ) self._plugin_manager_status_text = bui.textwidget( parent=self._root_widget, @@ -2379,8 +2709,11 @@ def get_main_window_state(self) -> bui.MainWindowState: def spin(self, show=False): w = self._loading_spinner p = self._root_widget - bui.spinnerwidget(w, visible=show) if w.exists( - ) and p.exists() and not p.transitioning_out else None + ( + bui.spinnerwidget(w, visible=show) + if w.exists() and p.exists() and not p.transitioning_out + else None + ) @contextlib.contextmanager def exception_handler(self): @@ -2391,7 +2724,7 @@ def exception_handler(self): try: bui.textwidget( edit=self._plugin_manager_status_text, - text="Make sure you are connected\n to the Internet and try again." + text="Make sure you are connected\n to the Internet and try again.", ) except: pass @@ -2424,39 +2757,46 @@ async def draw_index(self): await self.select_category("All") def draw_plugins_scroll_bar(self): - scroll_size_x = self.scrollx = (515 if _uiscale() is babase.UIScale.SMALL else - 430 if _uiscale() is babase.UIScale.MEDIUM else 420) - scroll_size_y = (245 if _uiscale() is babase.UIScale.SMALL else - 265 if _uiscale() is babase.UIScale.MEDIUM else 335) - scroll_pos_x = (70 if _uiscale() is babase.UIScale.SMALL else - 50 if _uiscale() is babase.UIScale.MEDIUM else 70) - scroll_pos_y = (100 if _uiscale() is babase.UIScale.SMALL else - 35 if _uiscale() is babase.UIScale.MEDIUM else 40) + scroll_size_x = self.scrollx = ( + 515 + if _uiscale() is babase.UIScale.SMALL + else 430 if _uiscale() is babase.UIScale.MEDIUM else 420 + ) + scroll_size_y = ( + 245 + if _uiscale() is babase.UIScale.SMALL + else 265 if _uiscale() is babase.UIScale.MEDIUM else 335 + ) + scroll_pos_x = ( + 70 + if _uiscale() is babase.UIScale.SMALL + else 50 if _uiscale() is babase.UIScale.MEDIUM else 70 + ) + scroll_pos_y = ( + 100 + if _uiscale() is babase.UIScale.SMALL + else 35 if _uiscale() is babase.UIScale.MEDIUM else 40 + ) self._scrollwidget = sw = bui.scrollwidget( parent=self._root_widget, size=(scroll_size_x, scroll_size_y), - position=(scroll_pos_x, scroll_pos_y) - ) - self._columnwidget = bui.columnwidget( - parent=self._scrollwidget, - border=2, - margin=0 - ) - bui.widget( - sw, - left_widget=self._back_button, - up_widget=self._filter_widget - ) - bui.widget( - self._filter_widget, - down_widget=sw + position=(scroll_pos_x, scroll_pos_y), ) + self._columnwidget = bui.columnwidget(parent=self._scrollwidget, border=2, margin=0) + bui.widget(sw, left_widget=self._back_button, up_widget=self._filter_widget) + bui.widget(self._filter_widget, down_widget=sw) def draw_category_selection_button(self, post_label): - category_pos_x = (440 if _uiscale() is babase.UIScale.SMALL else - 340 if _uiscale() is babase.UIScale.MEDIUM else 370) - category_pos_y = self._height - (141 if _uiscale() is babase.UIScale.SMALL else - 110 if _uiscale() is babase.UIScale.MEDIUM else 110) + category_pos_x = ( + 440 + if _uiscale() is babase.UIScale.SMALL + else 340 if _uiscale() is babase.UIScale.MEDIUM else 370 + ) + category_pos_y = self._height - ( + 141 + if _uiscale() is babase.UIScale.SMALL + else 110 if _uiscale() is babase.UIScale.MEDIUM else 110 + ) b_size = (140, 30) b_textcolor = (0.8, 0.8, 0.85) @@ -2473,14 +2813,17 @@ def draw_category_selection_button(self, post_label): enable_sound=False, left_widget=self._filter_widget, down_widget=self._scrollwidget, - up_widget=self._back_button + up_widget=self._back_button, ) else: b = self.alphabet_order_selection_button - bui.buttonwidget( - edit=b, - label=('Z - A' if self.selected_alphabet_order == 'z_a' else 'A - Z') - ) if b.exists() else None + ( + bui.buttonwidget( + edit=b, label=('Z - A' if self.selected_alphabet_order == 'z_a' else 'A - Z') + ) + if b.exists() + else None + ) label = f"Category: {post_label}" @@ -2493,64 +2836,85 @@ def draw_category_selection_button(self, post_label): button_type="square", textcolor=b_textcolor, text_scale=0.6, - up_widget=self._back_button + up_widget=self._back_button, ) bui.buttonwidget( edit=b, down_widget=self._scrollwidget, - on_activate_call=lambda: self.show_categories_window(source=b) + on_activate_call=lambda: self.show_categories_window(source=b), ) else: b = self.category_selection_button - bui.buttonwidget( - edit=b, - label=label - ) if b.exists() else None + bui.buttonwidget(edit=b, label=label) if b.exists() else None async def _on_order_button_press(self) -> None: bui.getsound('deek').play() - self.selected_alphabet_order = ('a_z' if self.selected_alphabet_order == 'z_a' else 'z_a') - bui.buttonwidget(edit=self.alphabet_order_selection_button, - label=('Z - A' if self.selected_alphabet_order == 'z_a' else 'A - Z') - ) + self.selected_alphabet_order = 'a_z' if self.selected_alphabet_order == 'z_a' else 'z_a' + bui.buttonwidget( + edit=self.alphabet_order_selection_button, + label=('Z - A' if self.selected_alphabet_order == 'z_a' else 'A - Z'), + ) filter_text = bui.textwidget(parent=self._root_widget, query=self._filter_widget) if self.plugin_manager.categories != {}: if self.plugin_manager.categories['All'] is not None: await self.draw_plugin_names( - self.selected_category, search_term=filter_text, refresh=True, order=self.selected_alphabet_order + self.selected_category, + search_term=filter_text, + refresh=True, + order=self.selected_alphabet_order, ) def draw_search_bar(self): - search_bar_pos_x = (85 if _uiscale() is babase.UIScale.SMALL else - 68 if _uiscale() is babase.UIScale.MEDIUM else 75) + search_bar_pos_x = ( + 85 + if _uiscale() is babase.UIScale.SMALL + else 68 if _uiscale() is babase.UIScale.MEDIUM else 75 + ) search_bar_pos_y = self._height - ( - 145 if _uiscale() is babase.UIScale.SMALL else - 110 if _uiscale() is babase.UIScale.MEDIUM else 116) + 145 + if _uiscale() is babase.UIScale.SMALL + else 110 if _uiscale() is babase.UIScale.MEDIUM else 116 + ) - search_bar_size_x = (320 if _uiscale() is babase.UIScale.SMALL else - 230 if _uiscale() is babase.UIScale.MEDIUM else 260) + search_bar_size_x = ( + 320 + if _uiscale() is babase.UIScale.SMALL + else 230 if _uiscale() is babase.UIScale.MEDIUM else 260 + ) search_bar_size_y = ( - 35 if _uiscale() is babase.UIScale.SMALL else - 35 if _uiscale() is babase.UIScale.MEDIUM else 45) - - filter_txt_pos_x = (60 if _uiscale() is babase.UIScale.SMALL else - 40 if _uiscale() is babase.UIScale.MEDIUM else 50) - filter_txt_pos_y = search_bar_pos_y + (3 if _uiscale() is babase.UIScale.SMALL else - 4 if _uiscale() is babase.UIScale.MEDIUM else 8) - - bui.textwidget(parent=self._root_widget, - text="Filter", - position=(filter_txt_pos_x, filter_txt_pos_y), - selectable=False, - h_align='left', - v_align='center', - color=bui.app.ui_v1.title_color, - scale=0.5) + 35 + if _uiscale() is babase.UIScale.SMALL + else 35 if _uiscale() is babase.UIScale.MEDIUM else 45 + ) + + filter_txt_pos_x = ( + 60 + if _uiscale() is babase.UIScale.SMALL + else 40 if _uiscale() is babase.UIScale.MEDIUM else 50 + ) + filter_txt_pos_y = search_bar_pos_y + ( + 3 + if _uiscale() is babase.UIScale.SMALL + else 4 if _uiscale() is babase.UIScale.MEDIUM else 8 + ) + + bui.textwidget( + parent=self._root_widget, + text="Filter", + position=(filter_txt_pos_x, filter_txt_pos_y), + selectable=False, + h_align='left', + v_align='center', + color=bui.app.ui_v1.title_color, + scale=0.5, + ) filter_txt = babase.Lstr(resource='filterText') - search_bar_maxwidth = search_bar_size_x - (95 if _uiscale() is babase.UIScale.SMALL else - 77 if _uiscale() is babase.UIScale.MEDIUM else - 85) + search_bar_maxwidth = search_bar_size_x - ( + 95 + if _uiscale() is babase.UIScale.SMALL + else 77 if _uiscale() is babase.UIScale.MEDIUM else 85 + ) self._filter_widget = bui.textwidget( parent=self._root_widget, text="", @@ -2562,7 +2926,7 @@ def draw_search_bar(self): scale=0.8, autoselect=True, maxwidth=search_bar_maxwidth, - description=filter_txt + description=filter_txt, ) self._last_filter_text = "" self._last_filter_plugins = [] @@ -2580,73 +2944,69 @@ async def process_search_term(self): continue try: await self.draw_plugin_names( - self.selected_category, search_term=filter_text.lower(), order=self.selected_alphabet_order) + self.selected_category, + search_term=filter_text.lower(), + order=self.selected_alphabet_order, + ) except CategoryDoesNotExist: pass def draw_settings_icon(self): - settings_pos_x = (610 if _uiscale() is babase.UIScale.SMALL else - 500 if _uiscale() is babase.UIScale.MEDIUM else 510) - settings_pos_y = (130 if _uiscale() is babase.UIScale.SMALL else - 60 if _uiscale() is babase.UIScale.MEDIUM else 70) + settings_pos_x = ( + 610 + if _uiscale() is babase.UIScale.SMALL + else 500 if _uiscale() is babase.UIScale.MEDIUM else 510 + ) + settings_pos_y = ( + 130 + if _uiscale() is babase.UIScale.SMALL + else 60 if _uiscale() is babase.UIScale.MEDIUM else 70 + ) controller_button = bui.buttonwidget( parent=self._root_widget, position=(settings_pos_x, settings_pos_y), size=(30, 30), - button_type="square", - label="", + button_type='square', + label='', left_widget=self._scrollwidget, - right_widget=bui.get_special_widget('squad_button') + right_widget=bui.get_special_widget('squad_button'), ) bui.buttonwidget( controller_button, on_activate_call=babase.CallPartial( - PluginManagerSettingsWindow, - self.plugin_manager, - controller_button - ) + PluginManagerSettingsWindow, self.plugin_manager, controller_button + ), ) bui.imagewidget( parent=self._root_widget, position=(settings_pos_x, settings_pos_y), size=(30, 30), color=(0.8, 0.95, 1), - texture=bui.gettexture("settingsIcon"), - draw_controller=controller_button + texture=bui.gettexture('settingsIcon'), + draw_controller=controller_button, ) def draw_refresh_icon(self): - refresh_pos_x = (610 if _uiscale() is babase.UIScale.SMALL else - 500 if _uiscale() is babase.UIScale.MEDIUM else 510) - refresh_pos_y = (180 if _uiscale() is babase.UIScale.SMALL else - 108 if _uiscale() is babase.UIScale.MEDIUM else 120) - - controller_button = bui.buttonwidget( - parent=self._root_widget, - position=(refresh_pos_x, refresh_pos_y), - size=(30, 30), - button_type="square", - label="", - on_activate_call=lambda: ( - bui.getsound('deek').play() or - loop.create_task(self.refresh()) - ), - enable_sound=False, - left_widget=self._scrollwidget, - right_widget=bui.get_special_widget('squad_button') + refresh_pos_x = ( + 610 + if _uiscale() is babase.UIScale.SMALL + else 500 if _uiscale() is babase.UIScale.MEDIUM else 510 ) - bui.widget( - self._scrollwidget, - right_widget=controller_button + refresh_pos_y = ( + 180 + if _uiscale() is babase.UIScale.SMALL + else 108 if _uiscale() is babase.UIScale.MEDIUM else 120 ) - bui.imagewidget( + + controller_button = UIHelpers.create_icon_button( parent=self._root_widget, position=(refresh_pos_x, refresh_pos_y), size=(30, 30), - color=(0.8, 0.95, 1), - texture=bui.gettexture("replayIcon"), - draw_controller=controller_button + texture='replayIcon', + on_activate=lambda: (bui.getsound('deek').play() or loop.create_task(self.refresh())), + icon_color=(0.8, 0.95, 1), ) + bui.widget(self._scrollwidget, right_widget=controller_button) def search_term_filterer(self, plugin, search_term): # This helps resolve "plugin name" to "plugin_name". @@ -2663,15 +3023,19 @@ def search_term_filterer(self, plugin, search_term): # XXX: Not sure if this is the best way to handle search filters. async def draw_plugin_names(self, category, search_term="", refresh=False, order='a_z'): # Re-draw plugin list UI if either search term or category was switched. - to_draw_plugin_names = (search_term, category) != (self._last_filter_text, - self.selected_category) + to_draw_plugin_names = (search_term, category) != ( + self._last_filter_text, + self.selected_category, + ) if not (to_draw_plugin_names or refresh): return try: if self.plugin_manager.categories != {}: if self.plugin_manager.categories['All'] is not None: - category_plugins = await self.plugin_manager.categories[category if category != 'Installed' else 'All'].get_plugins() + category_plugins = await self.plugin_manager.categories[ + category if category != 'Installed' else 'All' + ].get_plugins() else: return else: @@ -2684,15 +3048,18 @@ async def draw_plugin_names(self, category, search_term="", refresh=False, order return if search_term: - plugins = list(filter( - lambda plugin: self.search_term_filterer(plugin, search_term), - category_plugins, - )) + plugins = list( + filter( + lambda plugin: self.search_term_filterer(plugin, search_term), + category_plugins, + ) + ) else: plugins = category_plugins def return_name(val): return val.name + plugins.sort(key=return_name, reverse=(True if order == 'z_a' else False)) if plugins == self._last_filter_plugins and not refresh: @@ -2706,9 +3073,7 @@ def return_name(val): return if category == 'Installed': - plugin_names_to_draw = tuple( - plugin for plugin in plugins if plugin.is_installed - ) + plugin_names_to_draw = tuple(plugin for plugin in plugins if plugin.is_installed) else: plugin_names_to_draw = plugins @@ -2729,7 +3094,7 @@ def return_name(val): for i, plugin in enumerate(plugin_names_ready_to_draw): await self.draw_plugin_name(plugin, plugin_names_ready_to_draw) kids = self._columnwidget.get_children() - first_child, last_child = kids[::len(kids)-1] + first_child, last_child = kids[:: len(kids) - 1] bui.widget(first_child, up_widget=self._filter_widget) bui.widget(last_child, down_widget=self._back_button) @@ -2751,10 +3116,7 @@ async def draw_plugin_name(self, plugin, plugins_list): plugin_name_widget_to_update = self.plugins_in_current_view.get(plugin.name) if plugin_name_widget_to_update: - bui.textwidget( - edit=plugin_name_widget_to_update, - color=color - ) + bui.textwidget(edit=plugin_name_widget_to_update, color=color) else: text_widget = bui.textwidget( parent=self._columnwidget, @@ -2766,16 +3128,13 @@ async def draw_plugin_name(self, plugin, plugins_list): click_activate=True, h_align='left', v_align='center', - maxwidth=self.scrollx-10 + maxwidth=self.scrollx - 10, ) bui.textwidget( text_widget, on_activate_call=bui.CallPartial( - self.show_plugin_window, - plugin, - plugins_list, - text_widget - ) + self.show_plugin_window, plugin, plugins_list, text_widget + ), ) self.plugins_in_current_view[plugin.name] = text_widget # XXX: This seems nicer. Might wanna use this in future. @@ -2786,7 +3145,7 @@ def show_plugin_window(self, plugin, plugins_list, source): plugin, source, plugins_list=plugins_list, - button_callback=lambda: self.draw_plugin_name(plugin, plugins_list) + button_callback=lambda: self.draw_plugin_name(plugin, plugins_list), ) def show_categories_window(self, source): @@ -2801,7 +3160,11 @@ async def select_category(self, category): self.plugins_in_current_view.clear() self.draw_category_selection_button(post_label=category) await self.draw_plugin_names( - category, search_term=self._last_filter_text, refresh=True, order=self.selected_alphabet_order) + category, + search_term=self._last_filter_text, + refresh=True, + order=self.selected_alphabet_order, + ) self.selected_category = category def cleanup(self): @@ -2845,16 +3208,13 @@ def __init__(self, plugin_manager, origin_widget): async def draw_ui(self): b_text_color = (0.8, 0.8, 0.85) - s = 1.25 if _uiscale() is babase.UIScale.SMALL else 1.27 if _uiscale() is babase.UIScale.MEDIUM else 1.3 + s = UIConfig.scale_value(1.25, 1.27, 1.3) width = 380 * s height = 150 + 150 * s color = (0.9, 0.9, 0.9) # Subtracting the default bluish-purple color from the texture, so it's as close # as to white as possible. - discord_fg_color = (10 - 0.32, 10 - 0.39, 10 - 0.96) - discord_bg_color = (0.525, 0.595, 1.458) - github_bg_color = (0.23, 0.23, 0.23) text_scale = 0.7 * s self._transition_out = 'out_scale' transition = 'in_scale' @@ -2864,9 +3224,8 @@ async def draw_ui(self): size=(width, height), on_outside_click_call=self._ok, transition=transition, - scale=(2.1 if _uiscale() is babase.UIScale.SMALL else 1.5 - if _uiscale() is babase.UIScale.MEDIUM else 1.0), - scale_origin_stack_offset=self.scale_origin + scale=UIConfig.scale_value(2.1, 1.5, 1.0), + scale_origin_stack_offset=self.scale_origin, ) _add_popup(self) pos = height * 0.9 @@ -2880,7 +3239,7 @@ async def draw_ui(self): text=setting_title, scale=text_scale, color=bui.app.ui_v1.title_color, - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) pos -= 20 @@ -2890,7 +3249,7 @@ async def draw_ui(self): size=(80, 30), textcolor=b_text_color, button_type='square', - label='' + label='', ) bui.buttonwidget(b, on_activate_call=lambda: ChangelogWindow(b)) bui.textwidget( @@ -2914,7 +3273,7 @@ async def draw_ui(self): text_scale=1, scale=0, selectable=False, - label="Save" + label="Save", ) pos -= 40 @@ -2929,32 +3288,29 @@ async def draw_ui(self): on_value_change_call=babase.CallPartial(self.toggle_setting, setting), maxwidth=500, textcolor=(0.9, 0.9, 0.9), - scale=text_scale * 0.8 + scale=text_scale * 0.8, ) self.checkboxes.append(chk) pos -= 34 * text_scale - bui.widget( - self.checkboxes[0], - up_widget=self._changelog_button - ) + bui.widget(self.checkboxes[0], up_widget=self._changelog_button) bui.widget( self._changelog_button, down_widget=self.checkboxes[0], left_widget=self.checkboxes[0], - right_widget=self.checkboxes[0] + right_widget=self.checkboxes[0], ) pos = height - 200 bui.textwidget( parent=self._root_widget, - position=(width * 0.49, pos-5), + position=(width * 0.49, pos - 5), size=(0, 0), h_align='center', v_align='center', text='Contribute to plugins or to this community plugin manager!', scale=text_scale * 0.65, color=color, - maxwidth=width * 0.95 + maxwidth=width * 0.95, ) pos -= 75 @@ -2963,56 +3319,31 @@ async def draw_ui(self): except urllib.error.URLError: plugin_manager_update_available = False discord_width = (width * 0.20) if plugin_manager_update_available else (width * 0.31) - self.discord_button = bui.buttonwidget( + self.discord_button = UIHelpers.create_icon_button( parent=self._root_widget, position=(discord_width - button_size[0] / 2, pos), size=button_size, - on_activate_call=lambda: bui.open_url(DISCORD_URL), - textcolor=b_text_color, - color=discord_bg_color, - button_type='square', - text_scale=1, - label="", - down_widget=self._changelog_button - ) - bui.widget( - self._changelog_button, - up_widget=self.discord_button - ) - - bui.imagewidget( - parent=self._root_widget, - position=(discord_width+0.5 - button_size[0] / 2, pos), - size=button_size, - texture=bui.gettexture("discordLogo"), - color=discord_fg_color, - draw_controller=self.discord_button + texture='discordLogo', + on_activate=lambda: bui.open_url(DISCORD_URL), + btn_color=UIConfig.get_color('discord_bg'), + icon_color=UIConfig.get_color('discord_fg'), ) + bui.widget(self.discord_button, down_widget=self._changelog_button) + bui.widget(self._changelog_button, up_widget=self.discord_button) github_width = (width * 0.49) if plugin_manager_update_available else (width * 0.65) - self.github_button = bui.buttonwidget( + self.github_button = UIHelpers.create_icon_button( parent=self._root_widget, position=(github_width - button_size[0] / 2, pos), size=button_size, - on_activate_call=lambda: bui.open_url(REPOSITORY_URL), - textcolor=b_text_color, - color=github_bg_color, - button_type='square', - text_scale=1, - label='', - up_widget=self.checkboxes[-1], - down_widget=self._changelog_button + texture='githubLogo', + on_activate=lambda: bui.open_url(REPOSITORY_URL), + btn_color=UIConfig.get_color('github_bg'), + icon_color=UIConfig.get_color('github_fg'), ) - - bui.imagewidget( - parent=self._root_widget, - position=(github_width + 0.5 - button_size[0] / 2, pos), - size=button_size, - texture=bui.gettexture("githubLogo"), - color=(1, 1, 1), - draw_controller=self.github_button + bui.widget( + self.github_button, up_widget=self.checkboxes[-1], down_widget=self._changelog_button ) - bui.containerwidget(edit=self._root_widget, on_cancel_call=self._ok) try: @@ -3028,7 +3359,8 @@ async def draw_ui(self): position=((width * 0.77) - button_size[0] / 2, pos), size=button_size, on_activate_call=lambda: loop.create_task( - self.update(*plugin_manager_update_available)), + self.update(*plugin_manager_update_available) + ), textcolor=b_text_color, button_type='square', text_scale=1, @@ -3036,12 +3368,9 @@ async def draw_ui(self): label=update_button_label, up_widget=self.checkboxes[-1], down_widget=self._changelog_button, - right_widget=self.discord_button - ) - bui.widget( - self.discord_button, - left_widget=self._update_button + right_widget=self.discord_button, ) + bui.widget(self.discord_button, left_widget=self._update_button) self._restart_to_reload_changes_text = bui.textwidget( parent=self._root_widget, position=(width * 0.79, pos + 20), @@ -3051,7 +3380,7 @@ async def draw_ui(self): text='', scale=text_scale * 0.65, color=(0, 0.8, 0), - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) else: text_color = (0, 0.8, 0) @@ -3065,7 +3394,7 @@ async def draw_ui(self): text=f'Plugin Manager v{PLUGIN_MANAGER_VERSION}', scale=text_scale * 0.8, color=text_color, - maxwidth=width * 0.9 + maxwidth=width * 0.9, ) pos -= 25 bui.textwidget( @@ -3077,7 +3406,7 @@ async def draw_ui(self): text=f'API Version: {_app_api_version}', scale=text_scale * 0.7, color=(0.4, 0.8, 1), - maxwidth=width * 0.95 + maxwidth=width * 0.95, ) pos = height * 0.1 @@ -3085,33 +3414,18 @@ async def draw_ui(self): def toggle_setting(self, setting, set_value): self.settings[setting] = set_value check = self.settings == babase.app.config["Community Plugin Manager"]["Settings"] - bui.buttonwidget( - edit=self._save_button, - scale=0 if check else 1, - selectable=(not check) - ) + bui.buttonwidget(edit=self._save_button, scale=0 if check else 1, selectable=(not check)) bui.widget( self._changelog_button, - right_widget=( - check and self.checkboxes[0] - or self._save_button - ), - left_widget=( - check and self.checkboxes[0] - or self._save_button - ) + right_widget=(check and self.checkboxes[0] or self._save_button), + left_widget=(check and self.checkboxes[0] or self._save_button), ) - bui.widget( - self._update_button, - up_widget=( - check and self.checkboxes[0] - or self._save_button - ), - down_widget=( - check and self._changelog_button - or self._save_button + if hasattr(self, '_update_button'): + bui.widget( + self._update_button, + up_widget=(check and self.checkboxes[0] or self._save_button), + down_widget=(check and self._changelog_button or self._save_button), ) - ) def save_settings_button(self): babase.app.config["Community Plugin Manager"]["Settings"] = self.settings.copy() @@ -3131,7 +3445,7 @@ async def update(self, to_version=None, commit_sha=None): bui.getsound('shieldUp').play() bui.textwidget( edit=self._restart_to_reload_changes_text, - text='Update Applied!\nRestart game to reload changes.' + text='Update Applied!\nRestart game to reload changes.', ) self._update_button.delete() @@ -3168,15 +3482,11 @@ def __init__( for child in self._root_widget.get_children(): child.delete() - bui.containerwidget( - edit=self._root_widget, size=(width, height + top_extra) - ) + bui.containerwidget(edit=self._root_widget, size=(width, height + top_extra)) if uiscale is bui.UIScale.SMALL: self._back_button = None - bui.containerwidget( - edit=self._root_widget, on_cancel_call=self.main_window_back - ) + bui.containerwidget(edit=self._root_widget, on_cancel_call=self.main_window_back) else: self._back_button = btn = bui.buttonwidget( parent=self._root_widget, @@ -3213,9 +3523,7 @@ def __init__( v = height - 265 basew = 280 if uiscale is bui.UIScale.SMALL else 230 baseh = 170 - x_offs = ( - x_inset + (105 if uiscale is bui.UIScale.SMALL else 72) - basew - ) # now unused + x_offs = x_inset + (105 if uiscale is bui.UIScale.SMALL else 72) - basew # now unused x_dif = (basew - 7) / 2 x_offs2 = x_offs + basew - 7 x_offs3 = x_offs + 2 * (basew - 7) @@ -3226,9 +3534,7 @@ def __init__( x_offs3 -= x_dif x_offs4 -= x_dif - def _b_title( - x: float, y: float, button: bui.Widget, text: str | bui.Lstr - ) -> None: + def _b_title(x: float, y: float, button: bui.Widget, text: str | bui.Lstr) -> None: bui.textwidget( parent=self._root_widget, text=text, @@ -3253,9 +3559,7 @@ def _b_title( if self._back_button is None: bbtn = bui.get_special_widget('back_button') bui.widget(edit=ctb, left_widget=bbtn) - _b_title( - x_offs2, v, ctb, bui.Lstr(resource=f'{self._r}.controllersText') - ) + _b_title(x_offs2, v, ctb, bui.Lstr(resource=f'{self._r}.controllersText')) imgw = imgh = 130 bui.imagewidget( parent=self._root_widget, @@ -3339,7 +3643,7 @@ def _b_title( button_type='square', label='', up_widget=abtn, - on_activate_call=self._do_plugman + on_activate_call=self._do_plugman, ) _b_title(x_offs6, v, pmb, bui.Lstr(value="Plugin Manager")) imgw = imgh = 120 @@ -3349,7 +3653,7 @@ def _b_title( size=(imgw, imgh), color=(0.8, 0.95, 1), texture=bui.gettexture('storeIcon'), - draw_controller=pmb + draw_controller=pmb, ) self._restore_state() @@ -3358,11 +3662,7 @@ def _do_plugman(self) -> None: if not self.main_window_has_control(): return - self.main_window_replace( - lambda: PluginManagerWindow( - origin_widget=self._plugman_button - ) - ) + self.main_window_replace(lambda: PluginManagerWindow(origin_widget=self._plugman_button)) def _save_state(self) -> None: try: @@ -3389,9 +3689,7 @@ def _save_state(self) -> None: def _restore_state(self) -> None: try: assert bui.app.classic is not None - sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get( - 'sel_name' - ) + sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get('sel_name') sel: bui.Widget | None if sel_name == 'Controllers': sel = self._controllers_button @@ -3418,6 +3716,7 @@ class EntryPoint(babase.Plugin): def on_app_running(self) -> None: """Called when the app is being launched.""" from bauiv1lib.settings import allsettings + allsettings.AllSettingsWindow = NewAllSettingsWindow DNSBlockWorkaround.apply() asyncio.set_event_loop(babase._asyncio._g_asyncio_event_loop)