2025-01-13 17:04:48 +05:30
|
|
|
# ba_meta require api 9
|
2023-05-17 02:57:02 +05:30
|
|
|
import babase
|
|
|
|
|
import _babase
|
Wait for SceneAppMode to activate
Reported in ballistica's official discord server:
https://discord.com/channels/1001896771347304639/1218201750440247296/1227731932838760521.
We were calling `_bascenev1.protocol_version()` before SceneAppMode was
activated (by ballistica), which isn't allowed in newer ballistica
versions.
It was the cause of the following traceback:
```sh
$ CMAKE_BUILD_TYPE="Debug" make prefab-gui-debug
BA_WSL_TARGETS_WINDOWS=1 tools/pcommand make_prefab gui-debug
make[1]: Entering directory '/home/rikkolovescats/ballistica'
Lazybuild: skipping "meta" (checked 89 inputs in 0.0026s).
Lazybuild: skipping "assets-cmake" (checked 376 inputs in 0.0018s).
Staging for cmake at build/prefab/full/linux_x86_64_gui/debug...
make[1]: Leaving directory '/home/rikkolovescats/ballistica'
cd build/prefab/full/linux_x86_64_gui/debug && ./ballisticakit
root: BallisticaKit 1.7.34 build 21800.
root: Error loading plugin class 'plugin_manager.EntryPoint'.
Traceback (most recent call last):
File "/home/rikkolovescats/ballistica/build/prefab/full/linux_x86_64_gui/debug/ba_data/python/babase/_plugin.py", line 283, in attempt_load_if_enabled
cls = getclass(self.class_path, Plugin)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/rikkolovescats/ballistica/build/prefab/full/linux_x86_64_gui/debug/ba_data/python/babase/_general.py", line 83, in getclass
module = importlib.import_module(modulename)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.11/importlib/__init__.py", line 126, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
File "<frozen importlib._bootstrap_external>", line 940, in exec_module
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
File "/home/rikkolovescats/.ballisticakit/mods/plugin_manager.py", line 70, in <module>
protocol_version = _bs.protocol_version()
^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Attempting to access SceneAppMode while it is inactive.
```
This commit improves the compatibility layer such that any calls that
require SceneAppMode to be active aren't made before it gets active.
2024-04-11 12:48:14 +05:30
|
|
|
import _bauiv1
|
|
|
|
|
import _bascenev1
|
2025-01-14 16:49:58 +05:30
|
|
|
import bauiv1 as bui
|
2023-06-08 21:24:02 +05:30
|
|
|
from bauiv1lib import popup, confirm
|
2025-01-14 16:49:58 +05:30
|
|
|
from babase._meta import EXPORT_CLASS_NAME_SHORTCUTS
|
|
|
|
|
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
import urllib.request
|
2022-12-17 20:10:00 +05:30
|
|
|
import http.client
|
|
|
|
|
import socket
|
2022-07-03 14:32:05 +05:30
|
|
|
import json
|
2025-01-14 16:49:58 +05:30
|
|
|
import ssl
|
2022-12-17 20:10:00 +05:30
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
import re
|
2022-07-31 21:40:54 +05:30
|
|
|
import os
|
2022-08-14 07:57:03 +05:30
|
|
|
import sys
|
2025-01-14 16:49:58 +05:30
|
|
|
import copy
|
2022-07-03 14:32:05 +05:30
|
|
|
import asyncio
|
2022-08-12 16:34:57 +05:30
|
|
|
import pathlib
|
2022-08-21 04:28:35 +05:30
|
|
|
import hashlib
|
2025-01-14 16:49:58 +05:30
|
|
|
import contextlib
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-08-05 06:27:53 +05:30
|
|
|
from typing import cast, override
|
2023-05-11 00:50:42 +05:30
|
|
|
from datetime import datetime
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2023-05-17 02:57:02 +05:30
|
|
|
# Modules used for overriding AllSettingsWindow
|
|
|
|
|
import logging
|
|
|
|
|
|
2025-08-06 00:53:13 +05:30
|
|
|
PLUGIN_MANAGER_VERSION = "1.2.1"
|
2022-11-30 18:31:00 +05:30
|
|
|
REPOSITORY_URL = "https://github.com/bombsquad-community/plugin-manager"
|
2023-04-30 22:33:01 +05:30
|
|
|
# Current tag can be changed to "staging" or any other branch in
|
|
|
|
|
# plugin manager repo for testing purpose.
|
2022-08-14 07:57:03 +05:30
|
|
|
CURRENT_TAG = "main"
|
2023-10-06 22:16:07 +05:30
|
|
|
|
|
|
|
|
_env = _babase.env()
|
2025-01-21 20:33:50 +05:30
|
|
|
_app_api_version = babase.app.env.api_version
|
2023-10-06 22:16:07 +05:30
|
|
|
|
2022-08-14 07:57:03 +05:30
|
|
|
INDEX_META = "{repository_url}/{content_type}/{tag}/index.json"
|
2024-04-22 21:31:22 +05:30
|
|
|
CHANGELOG_META = "{repository_url}/{content_type}/{tag}/CHANGELOG.md"
|
2022-07-03 14:32:05 +05:30
|
|
|
HEADERS = {
|
2023-06-21 00:33:31 +05:30
|
|
|
"User-Agent": _env["legacy_user_agent_string"],
|
2022-07-03 14:32:05 +05:30
|
|
|
}
|
2022-07-31 21:40:54 +05:30
|
|
|
PLUGIN_DIRECTORY = _env["python_directory_user"]
|
2023-10-03 11:19:36 +03:00
|
|
|
loop = babase._asyncio._asyncio_event_loop
|
2023-10-02 22:28:17 +00:00
|
|
|
|
2025-08-06 17:55:51 +05:30
|
|
|
open_popups = []
|
|
|
|
|
|
|
|
|
|
def _add_popup(popup): open_popups.append(popup)
|
|
|
|
|
|
|
|
|
|
def _remove_popup(popup):
|
|
|
|
|
try: open_popups.remove(popup)
|
|
|
|
|
except ValueError: pass
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
def _uiscale(): return bui.app.ui_v1.uiscale
|
2023-06-20 19:26:47 +00:00
|
|
|
def _regexp_friendly_class_name_shortcut(string): return string.replace(".", "\\.")
|
|
|
|
|
|
2023-05-16 23:39:05 +00:00
|
|
|
|
2022-08-08 03:36:49 +05:30
|
|
|
REGEXP = {
|
|
|
|
|
"plugin_api_version": re.compile(b"(?<=ba_meta require api )(.*)"),
|
2023-05-17 05:08:21 +05:30
|
|
|
"plugin_entry_points": re.compile(
|
|
|
|
|
bytes(
|
|
|
|
|
"(ba_meta export (plugin|{})\n+class )(.*)\\(".format(
|
|
|
|
|
_regexp_friendly_class_name_shortcut(EXPORT_CLASS_NAME_SHORTCUTS["plugin"]),
|
|
|
|
|
),
|
|
|
|
|
"utf-8"
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
"minigames": re.compile(
|
|
|
|
|
bytes(
|
2023-06-08 21:24:02 +05:30
|
|
|
"(ba_meta export ({})\n+class )(.*)\\(".format(
|
|
|
|
|
_regexp_friendly_class_name_shortcut("bascenev1.GameActivity"),
|
2023-05-17 05:08:21 +05:30
|
|
|
),
|
|
|
|
|
"utf-8"
|
|
|
|
|
),
|
|
|
|
|
),
|
2022-08-08 03:36:49 +05:30
|
|
|
}
|
2022-11-30 18:31:00 +05:30
|
|
|
DISCORD_URL = "https://ballistica.net/discord"
|
2022-08-10 02:00:03 +05:30
|
|
|
|
2023-10-06 22:16:07 +05:30
|
|
|
|
2024-04-29 02:19:04 +05:30
|
|
|
_CACHE = {}
|
2022-08-06 01:01:55 +05:30
|
|
|
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2023-06-30 04:38:42 +05:30
|
|
|
class MD5CheckSumFailed(Exception):
|
2022-08-27 19:30:21 +05:30
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2023-06-30 04:38:42 +05:30
|
|
|
class PluginNotInstalled(Exception):
|
2022-08-27 19:30:21 +05:30
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2023-06-30 04:38:42 +05:30
|
|
|
class CategoryDoesNotExist(Exception):
|
2022-08-27 19:30:21 +05:30
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2023-06-30 04:38:42 +05:30
|
|
|
class NoCompatibleVersion(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PluginSourceNetworkError(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CategoryMetadataParseError(Exception):
|
2022-08-28 23:09:09 +05:30
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2022-08-08 03:36:49 +05:30
|
|
|
def send_network_request(request):
|
|
|
|
|
return urllib.request.urlopen(request)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def async_send_network_request(request):
|
|
|
|
|
response = await loop.run_in_executor(None, send_network_request, request)
|
2022-07-03 14:32:05 +05:30
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
2022-08-27 18:39:43 +05:30
|
|
|
def stream_network_response_to_file(request, file, md5sum=None, retries=3):
|
2022-08-08 03:36:49 +05:30
|
|
|
response = urllib.request.urlopen(request)
|
|
|
|
|
chunk_size = 16 * 1024
|
|
|
|
|
content = b""
|
|
|
|
|
with open(file, "wb") as fout:
|
|
|
|
|
while True:
|
|
|
|
|
chunk = response.read(chunk_size)
|
|
|
|
|
if not chunk:
|
|
|
|
|
break
|
|
|
|
|
fout.write(chunk)
|
|
|
|
|
content += chunk
|
2022-08-21 04:50:29 +05:30
|
|
|
if md5sum and hashlib.md5(content).hexdigest() != md5sum:
|
|
|
|
|
if retries <= 0:
|
2023-06-30 04:38:42 +05:30
|
|
|
raise MD5CheckSumFailed("MD5 checksum match failed.")
|
2022-08-27 18:39:43 +05:30
|
|
|
return stream_network_response_to_file(
|
2022-08-21 04:50:29 +05:30
|
|
|
request,
|
|
|
|
|
file,
|
|
|
|
|
md5sum=md5sum,
|
|
|
|
|
retries=retries-1,
|
|
|
|
|
)
|
2022-08-08 03:36:49 +05:30
|
|
|
return content
|
|
|
|
|
|
|
|
|
|
|
2022-08-27 18:39:43 +05:30
|
|
|
async def async_stream_network_response_to_file(request, file, md5sum=None, retries=3):
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-27 18:39:43 +05:30
|
|
|
content = await loop.run_in_executor(
|
|
|
|
|
None,
|
|
|
|
|
stream_network_response_to_file,
|
|
|
|
|
request,
|
|
|
|
|
file,
|
|
|
|
|
md5sum,
|
|
|
|
|
retries,
|
|
|
|
|
)
|
|
|
|
|
return content
|
|
|
|
|
|
|
|
|
|
|
2022-08-14 00:42:40 +05:30
|
|
|
def partial_format(string_template, **kwargs):
|
2022-08-14 07:57:03 +05:30
|
|
|
for key, value in kwargs.items():
|
|
|
|
|
string_template = string_template.replace("{" + key + "}", value)
|
|
|
|
|
return string_template
|
2022-08-14 00:42:40 +05:30
|
|
|
|
|
|
|
|
|
2022-12-17 20:10:00 +05:30
|
|
|
class DNSBlockWorkaround:
|
|
|
|
|
"""
|
|
|
|
|
Some ISPs put a DNS block on domains that are needed for plugin manager to
|
|
|
|
|
work properly. This class stores methods to workaround such blocks by adding
|
|
|
|
|
dns.google as a fallback.
|
|
|
|
|
|
|
|
|
|
Such as Jio, a pretty popular ISP in India has a DNS block on
|
|
|
|
|
raw.githubusercontent.com (sigh..).
|
|
|
|
|
|
2023-10-06 22:16:07 +05:30
|
|
|
References:
|
|
|
|
|
* https://github.com/orgs/community/discussions/42655
|
|
|
|
|
|
2022-12-17 20:10:00 +05:30
|
|
|
Usage:
|
|
|
|
|
-----
|
|
|
|
|
>>> import urllib.request
|
|
|
|
|
>>> import http.client
|
|
|
|
|
>>> import socket
|
|
|
|
|
>>> import ssl
|
|
|
|
|
>>> import json
|
|
|
|
|
>>> DNSBlockWorkaround.apply()
|
|
|
|
|
>>> response = urllib.request.urlopen("https://dnsblockeddomain.com/path/to/resource/")
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
_google_dns_cache = {}
|
|
|
|
|
|
|
|
|
|
def apply():
|
|
|
|
|
opener = urllib.request.build_opener(
|
|
|
|
|
DNSBlockWorkaround._HTTPHandler,
|
|
|
|
|
DNSBlockWorkaround._HTTPSHandler,
|
|
|
|
|
)
|
|
|
|
|
urllib.request.install_opener(opener)
|
|
|
|
|
|
|
|
|
|
def _resolve_using_google_dns(hostname):
|
|
|
|
|
response = urllib.request.urlopen(f"https://dns.google/resolve?name={hostname}")
|
|
|
|
|
response = response.read()
|
|
|
|
|
response = json.loads(response)
|
|
|
|
|
resolved_host = response["Answer"][0]["data"]
|
|
|
|
|
return resolved_host
|
|
|
|
|
|
|
|
|
|
def _resolve_using_system_dns(hostname):
|
|
|
|
|
resolved_host = socket.gethostbyname(hostname)
|
|
|
|
|
return resolved_host
|
|
|
|
|
|
|
|
|
|
def _resolve_with_workaround(hostname):
|
|
|
|
|
resolved_host_from_cache = DNSBlockWorkaround._google_dns_cache.get(hostname)
|
|
|
|
|
if resolved_host_from_cache:
|
|
|
|
|
return resolved_host_from_cache
|
|
|
|
|
|
|
|
|
|
resolved_host_by_system_dns = DNSBlockWorkaround._resolve_using_system_dns(hostname)
|
|
|
|
|
|
|
|
|
|
if DNSBlockWorkaround._is_blocked(hostname, resolved_host_by_system_dns):
|
|
|
|
|
resolved_host = DNSBlockWorkaround._resolve_using_google_dns(hostname)
|
|
|
|
|
DNSBlockWorkaround._google_dns_cache[hostname] = resolved_host
|
|
|
|
|
else:
|
|
|
|
|
resolved_host = resolved_host_by_system_dns
|
|
|
|
|
|
|
|
|
|
return resolved_host
|
|
|
|
|
|
|
|
|
|
def _is_blocked(hostname, address):
|
|
|
|
|
is_blocked = False
|
|
|
|
|
if hostname == "raw.githubusercontent.com":
|
|
|
|
|
# Jio's DNS server may be blocking it.
|
|
|
|
|
is_blocked = address.startswith("49.44.")
|
|
|
|
|
|
|
|
|
|
return is_blocked
|
|
|
|
|
|
|
|
|
|
class _HTTPConnection(http.client.HTTPConnection):
|
|
|
|
|
def connect(self):
|
|
|
|
|
host = DNSBlockWorkaround._resolve_with_workaround(self.host)
|
|
|
|
|
self.sock = socket.create_connection(
|
|
|
|
|
(host, self.port),
|
|
|
|
|
self.timeout,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
class _HTTPSConnection(http.client.HTTPSConnection):
|
|
|
|
|
def connect(self):
|
|
|
|
|
host = DNSBlockWorkaround._resolve_with_workaround(self.host)
|
|
|
|
|
sock = socket.create_connection(
|
|
|
|
|
(host, self.port),
|
|
|
|
|
self.timeout,
|
|
|
|
|
)
|
|
|
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
|
|
|
context.verify_mode = ssl.CERT_REQUIRED
|
|
|
|
|
context.check_hostname = True
|
|
|
|
|
context.load_default_certs()
|
|
|
|
|
sock = context.wrap_socket(sock, server_hostname=self.host)
|
|
|
|
|
self.sock = sock
|
|
|
|
|
|
|
|
|
|
class _HTTPHandler(urllib.request.HTTPHandler):
|
|
|
|
|
def http_open(self, req):
|
|
|
|
|
return self.do_open(DNSBlockWorkaround._HTTPConnection, req)
|
|
|
|
|
|
|
|
|
|
class _HTTPSHandler(urllib.request.HTTPSHandler):
|
|
|
|
|
def https_open(self, req):
|
|
|
|
|
return self.do_open(DNSBlockWorkaround._HTTPSConnection, req)
|
|
|
|
|
|
|
|
|
|
|
2022-08-15 21:01:48 +05:30
|
|
|
class StartupTasks:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.plugin_manager = PluginManager()
|
|
|
|
|
|
|
|
|
|
def setup_config(self):
|
2022-08-24 16:18:12 +05:30
|
|
|
# is_config_updated = False
|
|
|
|
|
existing_plugin_manager_config = copy.deepcopy(
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config.get("Community Plugin Manager"))
|
2022-08-16 02:29:52 +05:30
|
|
|
|
2023-05-17 02:57:02 +05:30
|
|
|
plugin_manager_config = babase.app.config.setdefault("Community Plugin Manager", {})
|
2022-08-16 02:29:52 +05:30
|
|
|
plugin_manager_config.setdefault("Custom Sources", [])
|
|
|
|
|
installed_plugins = plugin_manager_config.setdefault("Installed Plugins", {})
|
|
|
|
|
for plugin_name in tuple(installed_plugins.keys()):
|
2022-08-15 21:01:48 +05:30
|
|
|
plugin = PluginLocal(plugin_name)
|
|
|
|
|
if not plugin.is_installed:
|
2022-08-16 02:29:52 +05:30
|
|
|
del installed_plugins[plugin_name]
|
|
|
|
|
|
|
|
|
|
# This order is the options will show up in Settings window.
|
|
|
|
|
current_settings = {
|
|
|
|
|
"Auto Update Plugin Manager": True,
|
|
|
|
|
"Auto Update Plugins": True,
|
|
|
|
|
"Auto Enable Plugins After Installation": True,
|
2022-10-03 20:20:33 +05:30
|
|
|
"Notify New Plugins": True
|
2022-08-16 02:29:52 +05:30
|
|
|
}
|
|
|
|
|
settings = plugin_manager_config.setdefault("Settings", {})
|
|
|
|
|
|
|
|
|
|
for setting, value in settings.items():
|
|
|
|
|
if setting in current_settings:
|
|
|
|
|
current_settings[setting] = value
|
|
|
|
|
|
|
|
|
|
plugin_manager_config["Settings"] = current_settings
|
|
|
|
|
|
|
|
|
|
if plugin_manager_config != existing_plugin_manager_config:
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config.commit()
|
2022-08-15 21:01:48 +05:30
|
|
|
|
|
|
|
|
async def update_plugin_manager(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
if not babase.app.config["Community Plugin Manager"]["Settings"]["Auto Update Plugin Manager"]:
|
2022-08-15 21:01:48 +05:30
|
|
|
return
|
|
|
|
|
update_details = await self.plugin_manager.get_update_details()
|
|
|
|
|
if update_details:
|
|
|
|
|
to_version, commit_sha = update_details
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"Plugin Manager is being updated to v{to_version}")
|
2022-08-29 18:41:17 +05:30
|
|
|
try:
|
|
|
|
|
await self.plugin_manager.update(to_version, commit_sha)
|
2023-06-30 04:38:42 +05:30
|
|
|
except MD5CheckSumFailed:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('error').play()
|
2022-08-29 18:41:17 +05:30
|
|
|
else:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage("Update successful. Restart game to reload changes.",
|
|
|
|
|
color=(0, 1, 0))
|
|
|
|
|
bui.getsound('shieldUp').play()
|
2022-08-15 21:01:48 +05:30
|
|
|
|
|
|
|
|
async def update_plugins(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
if not babase.app.config["Community Plugin Manager"]["Settings"]["Auto Update Plugins"]:
|
2022-08-21 06:01:15 +05:30
|
|
|
return
|
|
|
|
|
await self.plugin_manager.setup_index()
|
|
|
|
|
all_plugins = await self.plugin_manager.categories["All"].get_plugins()
|
|
|
|
|
plugins_to_update = []
|
|
|
|
|
for plugin in all_plugins:
|
2022-08-27 18:03:36 +05:30
|
|
|
if plugin.is_installed and await plugin.get_local().is_enabled() and plugin.has_update():
|
2022-08-21 06:01:15 +05:30
|
|
|
plugins_to_update.append(plugin.update())
|
|
|
|
|
await asyncio.gather(*plugins_to_update)
|
2022-08-15 21:01:48 +05:30
|
|
|
|
2023-05-14 15:41:24 +05:30
|
|
|
@staticmethod
|
|
|
|
|
def _is_new_supported_plugin(plugin):
|
|
|
|
|
is_an_update = len(plugin.versions) > 1
|
|
|
|
|
if is_an_update:
|
|
|
|
|
return False
|
|
|
|
|
try:
|
|
|
|
|
plugin.latest_compatible_version
|
2023-06-30 04:38:42 +05:30
|
|
|
except NoCompatibleVersion:
|
2023-05-14 15:41:24 +05:30
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
return True
|
2023-05-10 00:43:02 +05:30
|
|
|
|
2022-10-03 20:20:33 +05:30
|
|
|
async def notify_new_plugins(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
if not babase.app.config["Community Plugin Manager"]["Settings"]["Notify New Plugins"]:
|
2022-10-03 20:20:33 +05:30
|
|
|
return
|
2024-04-21 02:25:32 +05:30
|
|
|
show_max_names = 2
|
2022-10-03 20:20:33 +05:30
|
|
|
await self.plugin_manager.setup_index()
|
2023-05-14 15:41:24 +05:30
|
|
|
new_num_of_plugins = len(await self.plugin_manager.categories["All"].get_plugins())
|
2022-10-03 20:20:33 +05:30
|
|
|
try:
|
2023-05-17 02:57:02 +05:30
|
|
|
existing_num_of_plugins = babase.app.config["Community Plugin Manager"]["Existing Number of Plugins"]
|
2023-05-14 15:41:24 +05:30
|
|
|
except KeyError:
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config["Community Plugin Manager"]["Existing Number of Plugins"] = new_num_of_plugins
|
|
|
|
|
babase.app.config.commit()
|
2022-10-03 20:20:33 +05:30
|
|
|
return
|
|
|
|
|
|
2024-05-21 02:37:34 +05:30
|
|
|
def title_it(plug):
|
|
|
|
|
plug = str(plug).replace('_', ' ').title()
|
|
|
|
|
return plug
|
2023-05-14 15:41:24 +05:30
|
|
|
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()
|
|
|
|
|
new_supported_plugins = list(filter(self._is_new_supported_plugin, all_plugins))
|
|
|
|
|
new_supported_plugins.sort(
|
|
|
|
|
key=lambda plugin: plugin.latest_compatible_version.released_on_date,
|
|
|
|
|
reverse=True,
|
|
|
|
|
)
|
|
|
|
|
new_supported_plugins = new_supported_plugins[:new_plugin_count]
|
|
|
|
|
new_supported_plugins_count = len(new_supported_plugins)
|
|
|
|
|
if new_supported_plugins_count > 0:
|
2024-05-21 02:37:34 +05:30
|
|
|
new_supported_plugins = ", ".join(map(title_it, (new_supported_plugins
|
2024-05-20 21:33:08 +00:00
|
|
|
if new_supported_plugins_count <= show_max_names else
|
|
|
|
|
new_supported_plugins[0:show_max_names])
|
2024-04-20 23:53:59 +05:30
|
|
|
))
|
2023-05-14 15:41:24 +05:30
|
|
|
if new_supported_plugins_count == 1:
|
|
|
|
|
notification_text = f"{new_supported_plugins_count} new plugin ({new_supported_plugins}) is available!"
|
|
|
|
|
else:
|
2024-04-21 02:25:32 +05:30
|
|
|
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"
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(notification_text, color=(0, 1, 0))
|
2023-05-14 15:41:24 +05:30
|
|
|
|
|
|
|
|
if existing_num_of_plugins != new_num_of_plugins:
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config["Community Plugin Manager"]["Existing Number of Plugins"] = new_num_of_plugins
|
|
|
|
|
babase.app.config.commit()
|
2022-10-03 20:20:33 +05:30
|
|
|
|
2022-08-15 21:01:48 +05:30
|
|
|
async def execute(self):
|
|
|
|
|
self.setup_config()
|
2022-08-17 00:00:04 +05:30
|
|
|
try:
|
|
|
|
|
await asyncio.gather(
|
|
|
|
|
self.update_plugin_manager(),
|
|
|
|
|
self.update_plugins(),
|
2022-10-03 20:20:33 +05:30
|
|
|
self.notify_new_plugins(),
|
2022-08-17 00:00:04 +05:30
|
|
|
)
|
|
|
|
|
except urllib.error.URLError:
|
|
|
|
|
pass
|
2022-08-15 21:01:48 +05:30
|
|
|
|
|
|
|
|
|
2022-07-03 14:32:05 +05:30
|
|
|
class Category:
|
2023-06-30 04:38:42 +05:30
|
|
|
def __init__(self, meta_url, tag=CURRENT_TAG):
|
2022-07-03 14:32:05 +05:30
|
|
|
self.meta_url = meta_url
|
2023-06-30 04:38:42 +05:30
|
|
|
self.tag = tag
|
2022-08-08 03:36:49 +05:30
|
|
|
self.request_headers = HEADERS
|
2022-08-10 01:02:10 +05:30
|
|
|
self._metadata = _CACHE.get("categories", {}).get(meta_url, {}).get("metadata")
|
|
|
|
|
self._plugins = _CACHE.get("categories", {}).get(meta_url, {}).get("plugins")
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
async def fetch_metadata(self):
|
|
|
|
|
if self._metadata is None:
|
2023-04-30 22:33:01 +05:30
|
|
|
# Let's keep depending on the "main" branch for 3rd party sources
|
|
|
|
|
# even if we're using a different branch of plugin manager's repository.
|
2022-07-03 14:32:05 +05:30
|
|
|
request = urllib.request.Request(
|
2023-06-30 04:38:42 +05:30
|
|
|
self.meta_url.format(content_type="raw", tag=self.tag),
|
2022-08-08 03:36:49 +05:30
|
|
|
headers=self.request_headers,
|
2022-07-03 14:32:05 +05:30
|
|
|
)
|
2022-08-08 03:36:49 +05:30
|
|
|
response = await async_send_network_request(request)
|
2022-08-10 01:02:10 +05:30
|
|
|
self._metadata = json.loads(response.read())
|
|
|
|
|
self.set_category_global_cache("metadata", self._metadata)
|
|
|
|
|
return self
|
|
|
|
|
|
2023-06-30 04:38:42 +05:30
|
|
|
async def validate(self):
|
2022-08-29 18:41:17 +05:30
|
|
|
try:
|
|
|
|
|
await self.fetch_metadata()
|
2023-06-30 04:38:42 +05:30
|
|
|
except urllib.error.HTTPError as e:
|
|
|
|
|
raise PluginSourceNetworkError(str(e))
|
|
|
|
|
except json.decoder.JSONDecodeError as e:
|
|
|
|
|
raise CategoryMetadataParseError(f"Failed to parse JSON: {str(e)}")
|
2022-08-11 06:34:17 +05:30
|
|
|
try:
|
|
|
|
|
await asyncio.gather(
|
|
|
|
|
self.get_name(),
|
|
|
|
|
self.get_description(),
|
|
|
|
|
self.get_plugins_base_url(),
|
|
|
|
|
self.get_plugins(),
|
|
|
|
|
)
|
|
|
|
|
except KeyError:
|
2023-06-30 04:38:42 +05:30
|
|
|
raise CategoryMetadataParseError(f"Failed to parse JSON; missing required fields.")
|
2022-08-11 06:34:17 +05:30
|
|
|
else:
|
|
|
|
|
return True
|
|
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
async def get_name(self):
|
2022-08-11 06:34:17 +05:30
|
|
|
await self.fetch_metadata()
|
2022-08-10 01:02:10 +05:30
|
|
|
return self._metadata["name"]
|
|
|
|
|
|
|
|
|
|
async def get_description(self):
|
2022-08-11 06:34:17 +05:30
|
|
|
await self.fetch_metadata()
|
2022-08-10 01:02:10 +05:30
|
|
|
return self._metadata["description"]
|
|
|
|
|
|
|
|
|
|
async def get_plugins_base_url(self):
|
2022-08-11 06:34:17 +05:30
|
|
|
await self.fetch_metadata()
|
2022-08-10 01:02:10 +05:30
|
|
|
return self._metadata["plugins_base_url"]
|
|
|
|
|
|
|
|
|
|
async def get_plugins(self):
|
|
|
|
|
if self._plugins is None:
|
2022-08-11 06:34:17 +05:30
|
|
|
await self.fetch_metadata()
|
2022-08-10 01:02:10 +05:30
|
|
|
self._plugins = ([
|
2022-08-10 02:00:03 +05:30
|
|
|
Plugin(
|
|
|
|
|
plugin_info,
|
|
|
|
|
f"{await self.get_plugins_base_url()}/{plugin_info[0]}.py",
|
2023-06-30 04:38:42 +05:30
|
|
|
tag=self.tag,
|
2022-08-10 02:00:03 +05:30
|
|
|
)
|
2022-08-10 01:02:10 +05:30
|
|
|
for plugin_info in self._metadata["plugins"].items()
|
|
|
|
|
])
|
|
|
|
|
self.set_category_global_cache("plugins", self._plugins)
|
2022-07-03 14:32:05 +05:30
|
|
|
return self._plugins
|
|
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
def set_category_global_cache(self, key, value):
|
2022-08-06 01:01:55 +05:30
|
|
|
if "categories" not in _CACHE:
|
|
|
|
|
_CACHE["categories"] = {}
|
2022-08-10 01:02:10 +05:30
|
|
|
if self.meta_url not in _CACHE["categories"]:
|
|
|
|
|
_CACHE["categories"][self.meta_url] = {}
|
|
|
|
|
_CACHE["categories"][self.meta_url][key] = value
|
2022-08-06 01:01:55 +05:30
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
def unset_category_global_cache(self):
|
2022-08-06 01:01:55 +05:30
|
|
|
try:
|
2022-08-10 01:02:10 +05:30
|
|
|
del _CACHE["categories"][self.meta_url]
|
2022-08-06 01:01:55 +05:30
|
|
|
except KeyError:
|
|
|
|
|
pass
|
|
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
def cleanup(self):
|
|
|
|
|
self._metadata = None
|
|
|
|
|
self._plugins.clear()
|
|
|
|
|
self.unset_category_global_cache()
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-21 01:33:25 +05:30
|
|
|
async def refresh(self):
|
|
|
|
|
self.cleanup()
|
|
|
|
|
await self.get_plugins()
|
|
|
|
|
|
2022-08-11 06:34:17 +05:30
|
|
|
def save(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config["Community Plugin Manager"]["Custom Sources"].append(self.meta_url)
|
|
|
|
|
babase.app.config.commit()
|
2022-08-11 06:34:17 +05:30
|
|
|
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
class CategoryAll(Category):
|
|
|
|
|
def __init__(self, plugins={}):
|
2022-08-10 01:02:10 +05:30
|
|
|
super().__init__(meta_url=None)
|
|
|
|
|
self._name = "All"
|
|
|
|
|
self._description = "All plugins"
|
2022-07-03 14:32:05 +05:30
|
|
|
self._plugins = plugins
|
|
|
|
|
|
|
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
class PluginLocal:
|
|
|
|
|
def __init__(self, name):
|
|
|
|
|
"""
|
|
|
|
|
Initialize a plugin locally installed on the device.
|
|
|
|
|
"""
|
|
|
|
|
self.name = name
|
|
|
|
|
self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{name}.py")
|
2022-08-12 07:21:51 +05:30
|
|
|
self._entry_point_initials = f"{self.name}."
|
2022-08-08 22:09:22 +05:30
|
|
|
self.cleanup()
|
|
|
|
|
|
|
|
|
|
def cleanup(self):
|
2022-08-08 03:36:49 +05:30
|
|
|
self._content = None
|
|
|
|
|
self._api_version = None
|
|
|
|
|
self._entry_points = []
|
2022-08-10 01:02:10 +05:30
|
|
|
self._has_minigames = None
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_installed(self):
|
|
|
|
|
return os.path.isfile(self.install_path)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def is_installed_via_plugin_manager(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
return self.name in babase.app.config["Community Plugin Manager"]["Installed Plugins"]
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
def initialize(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
if self.name not in babase.app.config["Community Plugin Manager"]["Installed Plugins"]:
|
|
|
|
|
babase.app.config["Community Plugin Manager"]["Installed Plugins"][self.name] = {}
|
2022-08-05 22:43:07 +05:30
|
|
|
return self
|
|
|
|
|
|
2022-08-12 16:34:57 +05:30
|
|
|
async def uninstall(self):
|
|
|
|
|
if await self.has_minigames():
|
|
|
|
|
self.unload_minigames()
|
2022-08-05 22:43:07 +05:30
|
|
|
try:
|
|
|
|
|
os.remove(self.install_path)
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
2023-05-17 02:57:02 +05:30
|
|
|
del babase.app.config["Community Plugin Manager"]["Installed Plugins"][self.name]
|
2022-08-05 22:43:07 +05:30
|
|
|
except KeyError:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
2022-08-10 02:00:03 +05:30
|
|
|
self.save()
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def version(self):
|
|
|
|
|
try:
|
2023-05-17 02:57:02 +05:30
|
|
|
version = (babase.app.config["Community Plugin Manager"]
|
2022-08-07 00:52:33 +05:30
|
|
|
["Installed Plugins"][self.name]["version"])
|
2022-08-05 22:43:07 +05:30
|
|
|
except KeyError:
|
|
|
|
|
version = None
|
|
|
|
|
return version
|
|
|
|
|
|
2022-08-08 03:36:49 +05:30
|
|
|
def _get_content(self):
|
|
|
|
|
with open(self.install_path, "rb") as fin:
|
|
|
|
|
return fin.read()
|
|
|
|
|
|
|
|
|
|
def _set_content(self, content):
|
|
|
|
|
with open(self.install_path, "wb") as fout:
|
|
|
|
|
fout.write(content)
|
|
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
def has_settings(self):
|
2023-06-30 04:38:42 +05:30
|
|
|
for plugin_entry_point, plugin_spec in bui.app.plugins.plugin_specs.items():
|
2022-08-12 07:21:51 +05:30
|
|
|
if plugin_entry_point.startswith(self._entry_point_initials):
|
2023-06-30 04:38:42 +05:30
|
|
|
return plugin_spec.plugin.has_settings_ui()
|
2022-08-12 07:21:51 +05:30
|
|
|
|
2022-12-05 21:39:08 +05:30
|
|
|
def launch_settings(self, source_widget):
|
2023-06-30 04:38:42 +05:30
|
|
|
for plugin_entry_point, plugin_spec in bui.app.plugins.plugin_specs.items():
|
2022-08-12 07:21:51 +05:30
|
|
|
if plugin_entry_point.startswith(self._entry_point_initials):
|
2023-06-30 04:38:42 +05:30
|
|
|
return plugin_spec.plugin.show_settings_ui(source_widget)
|
2022-08-12 07:21:51 +05:30
|
|
|
|
2022-08-08 03:36:49 +05:30
|
|
|
async def get_content(self):
|
|
|
|
|
if self._content is None:
|
|
|
|
|
if not self.is_installed:
|
2023-06-30 04:38:42 +05:30
|
|
|
raise PluginNotInstalled("Plugin is not available locally.")
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-08 03:36:49 +05:30
|
|
|
self._content = await loop.run_in_executor(None, self._get_content)
|
|
|
|
|
return self._content
|
|
|
|
|
|
|
|
|
|
async def get_api_version(self):
|
|
|
|
|
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):
|
|
|
|
|
if not self._entry_points:
|
|
|
|
|
content = await self.get_content()
|
|
|
|
|
groups = REGEXP["plugin_entry_points"].findall(content)
|
2023-05-17 05:08:21 +05:30
|
|
|
# Actual entry points are stored in the last index inside the matching groups.
|
|
|
|
|
entry_points = tuple(f"{self.name}.{group[-1].decode('utf-8')}" for group in groups)
|
2022-08-08 03:36:49 +05:30
|
|
|
self._entry_points = entry_points
|
|
|
|
|
return self._entry_points
|
|
|
|
|
|
2022-08-08 22:09:22 +05:30
|
|
|
async def has_minigames(self):
|
2022-08-10 01:02:10 +05:30
|
|
|
if self._has_minigames is None:
|
2022-08-08 22:09:22 +05:30
|
|
|
content = await self.get_content()
|
2022-08-10 01:02:10 +05:30
|
|
|
self._has_minigames = REGEXP["minigames"].search(content) is not None
|
|
|
|
|
return self._has_minigames
|
2022-08-08 22:09:22 +05:30
|
|
|
|
2022-09-05 00:27:45 +05:30
|
|
|
async def has_plugins(self):
|
|
|
|
|
entry_points = await self.get_entry_points()
|
|
|
|
|
return len(entry_points) > 0
|
|
|
|
|
|
2022-08-12 16:34:57 +05:30
|
|
|
def load_minigames(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
scanner = babase._meta.DirectoryScan(paths="")
|
2022-08-12 16:34:57 +05:30
|
|
|
directory, module = self.install_path.rsplit(os.path.sep, 1)
|
2022-08-13 21:20:20 +05:30
|
|
|
scanner._scan_module(
|
2022-08-12 16:34:57 +05:30
|
|
|
pathlib.Path(directory),
|
|
|
|
|
pathlib.Path(module),
|
|
|
|
|
)
|
2023-06-09 02:12:35 +05:30
|
|
|
scanned_results = set(babase.app.meta.scanresults.exports["bascenev1.GameActivity"])
|
|
|
|
|
for game in scanner.results.exports["bascenev1.GameActivity"]:
|
2022-08-12 16:34:57 +05:30
|
|
|
if game not in scanned_results:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"{game} minigame loaded")
|
2023-06-09 02:12:35 +05:30
|
|
|
babase.app.meta.scanresults.exports["bascenev1.GameActivity"].append(game)
|
2022-08-12 16:34:57 +05:30
|
|
|
|
|
|
|
|
def unload_minigames(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
scanner = babase._meta.DirectoryScan(paths="")
|
2022-08-12 16:34:57 +05:30
|
|
|
directory, module = self.install_path.rsplit(os.path.sep, 1)
|
2022-08-13 21:20:20 +05:30
|
|
|
scanner._scan_module(
|
2022-08-12 16:34:57 +05:30
|
|
|
pathlib.Path(directory),
|
|
|
|
|
pathlib.Path(module),
|
|
|
|
|
)
|
|
|
|
|
new_scanned_results_games = []
|
2023-06-09 02:12:35 +05:30
|
|
|
for game in babase.app.meta.scanresults.exports["bascenev1.GameActivity"]:
|
|
|
|
|
if game in scanner.results.exports["bascenev1.GameActivity"]:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"{game} minigame unloaded")
|
2022-08-21 06:01:15 +05:30
|
|
|
else:
|
2022-08-12 16:34:57 +05:30
|
|
|
new_scanned_results_games.append(game)
|
2023-06-09 02:12:35 +05:30
|
|
|
babase.app.meta.scanresults.exports["bascenev1.GameActivity"] = new_scanned_results_games
|
2022-08-12 16:34:57 +05:30
|
|
|
|
2022-08-08 22:09:22 +05:30
|
|
|
async def is_enabled(self):
|
2022-08-08 03:36:49 +05:30
|
|
|
"""
|
2022-08-08 22:09:22 +05:30
|
|
|
Return True even if a single entry point is enabled or contains minigames.
|
2022-08-08 03:36:49 +05:30
|
|
|
"""
|
2022-09-05 00:27:45 +05:30
|
|
|
if not await self.has_plugins():
|
|
|
|
|
return True
|
2023-05-17 02:57:02 +05:30
|
|
|
for entry_point, plugin_info in babase.app.config["Plugins"].items():
|
2022-08-12 07:21:51 +05:30
|
|
|
if entry_point.startswith(self._entry_point_initials) and plugin_info["enabled"]:
|
2022-08-08 03:36:49 +05:30
|
|
|
return True
|
2022-09-05 00:27:45 +05:30
|
|
|
return False
|
2022-08-08 03:36:49 +05:30
|
|
|
|
|
|
|
|
async def enable(self):
|
|
|
|
|
for entry_point in await self.get_entry_points():
|
2023-05-17 02:57:02 +05:30
|
|
|
if entry_point not in babase.app.config["Plugins"]:
|
|
|
|
|
babase.app.config["Plugins"][entry_point] = {}
|
|
|
|
|
babase.app.config["Plugins"][entry_point]["enabled"] = True
|
2023-06-30 04:38:42 +05:30
|
|
|
plugin_spec = bui.app.plugins.plugin_specs.get(entry_point)
|
|
|
|
|
if plugin_spec not in bui.app.plugins.active_plugins:
|
2022-08-12 08:24:40 +05:30
|
|
|
self.load_plugin(entry_point)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"{entry_point} loaded")
|
2022-08-12 16:34:57 +05:30
|
|
|
if await self.has_minigames():
|
|
|
|
|
self.load_minigames()
|
2022-08-10 02:00:03 +05:30
|
|
|
self.save()
|
2022-08-08 03:36:49 +05:30
|
|
|
|
2022-08-12 08:24:40 +05:30
|
|
|
def load_plugin(self, entry_point):
|
2023-05-17 02:57:02 +05:30
|
|
|
plugin_class = babase._general.getclass(entry_point, babase.Plugin)
|
2023-06-30 04:38:42 +05:30
|
|
|
loaded_plugin_instance = plugin_class()
|
|
|
|
|
loaded_plugin_instance.on_app_running()
|
|
|
|
|
|
|
|
|
|
plugin_spec = babase.PluginSpec(class_path=entry_point, loadable=True)
|
|
|
|
|
plugin_spec.enabled = True
|
|
|
|
|
plugin_spec.plugin = loaded_plugin_instance
|
|
|
|
|
bui.app.plugins.plugin_specs[entry_point] = plugin_spec
|
|
|
|
|
bui.app.plugins.active_plugins.append(plugin_spec.plugin)
|
2022-08-12 08:24:40 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
def disable(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
for entry_point, plugin_info in babase.app.config["Plugins"].items():
|
2022-08-12 07:21:51 +05:30
|
|
|
if entry_point.startswith(self._entry_point_initials):
|
2022-08-21 06:46:41 +05:30
|
|
|
plugin_info["enabled"] = False
|
2022-08-10 02:00:03 +05:30
|
|
|
self.save()
|
2022-08-08 03:36:49 +05:30
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
def set_version(self, version):
|
2023-05-17 02:57:02 +05:30
|
|
|
app = babase.app
|
2022-08-24 16:03:15 +05:30
|
|
|
app.config["Community Plugin Manager"]["Installed Plugins"][self.name]["version"] = version
|
2022-08-08 03:36:49 +05:30
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
async def set_content(self, content):
|
2022-08-21 04:28:35 +05:30
|
|
|
if not self._content:
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-21 04:28:35 +05:30
|
|
|
await loop.run_in_executor(None, self._set_content, content)
|
|
|
|
|
self._content = content
|
2022-08-08 03:36:49 +05:30
|
|
|
return self
|
|
|
|
|
|
2022-08-27 18:39:43 +05:30
|
|
|
async def set_content_from_network_response(self, request, md5sum=None, retries=3):
|
2022-08-21 04:28:35 +05:30
|
|
|
if not self._content:
|
2022-08-21 04:50:29 +05:30
|
|
|
self._content = await async_stream_network_response_to_file(
|
|
|
|
|
request,
|
|
|
|
|
self.install_path,
|
|
|
|
|
md5sum=md5sum,
|
2022-08-27 18:39:43 +05:30
|
|
|
retries=retries,
|
2022-08-21 04:50:29 +05:30
|
|
|
)
|
2022-08-21 04:28:35 +05:30
|
|
|
return self._content
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
def save(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config.commit()
|
2022-08-05 22:43:07 +05:30
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
2022-08-08 03:36:49 +05:30
|
|
|
class PluginVersion:
|
2023-06-30 04:38:42 +05:30
|
|
|
def __init__(self, plugin, version, tag=CURRENT_TAG):
|
2022-08-17 02:07:08 +05:30
|
|
|
self.number, info = version
|
|
|
|
|
self.plugin = plugin
|
|
|
|
|
self.api_version = info["api_version"]
|
2023-05-14 15:41:24 +05:30
|
|
|
self.released_on = info["released_on"]
|
2022-08-17 02:07:08 +05:30
|
|
|
self.commit_sha = info["commit_sha"]
|
|
|
|
|
self.md5sum = info["md5sum"]
|
|
|
|
|
|
|
|
|
|
self.download_url = self.plugin.url.format(content_type="raw", tag=tag)
|
|
|
|
|
self.view_url = self.plugin.url.format(content_type="blob", tag=tag)
|
|
|
|
|
|
|
|
|
|
def __eq__(self, plugin_version):
|
2022-08-24 16:03:15 +05:30
|
|
|
return (self.number, self.plugin.name) == (plugin_version.number,
|
|
|
|
|
plugin_version.plugin.name)
|
2022-08-17 02:07:08 +05:30
|
|
|
|
|
|
|
|
def __repr__(self):
|
2022-08-21 01:33:25 +05:30
|
|
|
return f"<PluginVersion({self.plugin.name} {self.number})>"
|
2022-08-17 02:07:08 +05:30
|
|
|
|
2023-05-14 15:41:24 +05:30
|
|
|
@property
|
|
|
|
|
def released_on_date(self):
|
|
|
|
|
return datetime.strptime(self.released_on, "%d-%m-%Y")
|
|
|
|
|
|
2022-08-21 04:28:35 +05:30
|
|
|
async def _download(self, retries=3):
|
2022-08-17 02:07:08 +05:30
|
|
|
local_plugin = self.plugin.create_local()
|
2022-08-27 18:39:43 +05:30
|
|
|
await local_plugin.set_content_from_network_response(
|
|
|
|
|
self.download_url,
|
|
|
|
|
md5sum=self.md5sum,
|
|
|
|
|
retries=retries,
|
|
|
|
|
)
|
2022-08-17 02:07:08 +05:30
|
|
|
local_plugin.set_version(self.number)
|
|
|
|
|
local_plugin.save()
|
|
|
|
|
return local_plugin
|
|
|
|
|
|
2022-08-27 18:39:43 +05:30
|
|
|
async def install(self, suppress_screenmessage=False):
|
|
|
|
|
try:
|
|
|
|
|
local_plugin = await self._download()
|
2023-06-30 04:38:42 +05:30
|
|
|
except MD5CheckSumFailed:
|
2022-08-27 18:39:43 +05:30
|
|
|
if not suppress_screenmessage:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(
|
2022-08-31 19:26:52 +05:30
|
|
|
f"{self.plugin.name} failed MD5 checksum during installation", color=(1, 0, 0))
|
2022-08-27 18:39:43 +05:30
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
if not suppress_screenmessage:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"{self.plugin.name} installed", color=(0, 1, 0))
|
|
|
|
|
check = babase.app.config["Community Plugin Manager"]["Settings"]
|
2022-08-27 18:39:43 +05:30
|
|
|
if check["Auto Enable Plugins After Installation"]:
|
|
|
|
|
await local_plugin.enable()
|
|
|
|
|
return True
|
2022-08-08 03:36:49 +05:30
|
|
|
|
|
|
|
|
|
2022-07-31 21:40:54 +05:30
|
|
|
class Plugin:
|
2023-06-30 04:38:42 +05:30
|
|
|
def __init__(self, plugin, url, tag=CURRENT_TAG):
|
2022-08-05 22:43:07 +05:30
|
|
|
"""
|
|
|
|
|
Initialize a plugin from network repository.
|
|
|
|
|
"""
|
2022-07-31 21:40:54 +05:30
|
|
|
self.name, self.info = plugin
|
|
|
|
|
self.install_path = os.path.join(PLUGIN_DIRECTORY, f"{self.name}.py")
|
2022-08-17 02:07:08 +05:30
|
|
|
self.url = url
|
2023-06-30 04:38:42 +05:30
|
|
|
self.tag = tag
|
2022-08-08 03:36:49 +05:30
|
|
|
self._local_plugin = None
|
2022-07-31 21:40:54 +05:30
|
|
|
|
2022-08-17 02:07:08 +05:30
|
|
|
self._versions = None
|
|
|
|
|
self._latest_version = None
|
|
|
|
|
self._latest_compatible_version = None
|
|
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
def __repr__(self):
|
|
|
|
|
return f"<Plugin({self.name})>"
|
|
|
|
|
|
2023-05-14 15:41:24 +05:30
|
|
|
def __str__(self):
|
|
|
|
|
return self.name
|
|
|
|
|
|
2022-08-28 23:31:46 +05:30
|
|
|
@property
|
|
|
|
|
def view_url(self):
|
|
|
|
|
if self.latest_compatible_version == self.latest_version:
|
|
|
|
|
tag = CURRENT_TAG
|
|
|
|
|
else:
|
|
|
|
|
tag = self.latest_compatible_version.commit_sha
|
|
|
|
|
return self.url.format(content_type="blob", tag=tag)
|
|
|
|
|
|
2022-07-31 21:40:54 +05:30
|
|
|
@property
|
2022-08-05 22:43:07 +05:30
|
|
|
def is_installed(self):
|
2022-07-31 21:40:54 +05:30
|
|
|
return os.path.isfile(self.install_path)
|
|
|
|
|
|
2022-08-17 02:07:08 +05:30
|
|
|
@property
|
|
|
|
|
def versions(self):
|
|
|
|
|
if self._versions is None:
|
|
|
|
|
self._versions = [
|
|
|
|
|
PluginVersion(
|
|
|
|
|
self,
|
|
|
|
|
version,
|
2023-06-30 04:38:42 +05:30
|
|
|
tag=self.tag,
|
2022-08-17 02:07:08 +05:30
|
|
|
) for version in self.info["versions"].items()
|
|
|
|
|
]
|
|
|
|
|
return self._versions
|
|
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
@property
|
|
|
|
|
def latest_version(self):
|
2022-08-17 02:07:08 +05:30
|
|
|
if self._latest_version is None:
|
|
|
|
|
self._latest_version = PluginVersion(
|
|
|
|
|
self,
|
|
|
|
|
tuple(self.info["versions"].items())[0],
|
2023-06-30 04:38:42 +05:30
|
|
|
tag=self.tag,
|
2022-08-17 02:07:08 +05:30
|
|
|
)
|
|
|
|
|
return self._latest_version
|
2022-08-05 22:43:07 +05:30
|
|
|
|
2022-08-17 02:07:08 +05:30
|
|
|
@property
|
|
|
|
|
def latest_compatible_version(self):
|
|
|
|
|
if self._latest_compatible_version is None:
|
|
|
|
|
for number, info in self.info["versions"].items():
|
2025-01-21 20:33:50 +05:30
|
|
|
if info["api_version"] == _app_api_version:
|
2022-08-17 02:07:08 +05:30
|
|
|
self._latest_compatible_version = PluginVersion(
|
|
|
|
|
self,
|
|
|
|
|
(number, info),
|
2023-06-30 04:38:42 +05:30
|
|
|
tag=self.tag if self.latest_version.number == number else info["commit_sha"]
|
2022-08-17 02:07:08 +05:30
|
|
|
)
|
|
|
|
|
break
|
2022-08-28 23:09:09 +05:30
|
|
|
if self._latest_compatible_version is None:
|
2023-06-30 04:38:42 +05:30
|
|
|
raise NoCompatibleVersion(
|
2025-01-21 20:33:50 +05:30
|
|
|
f"{self.name} has no version compatible with API {_app_api_version}."
|
2022-08-28 23:09:09 +05:30
|
|
|
)
|
2022-08-17 02:07:08 +05:30
|
|
|
return self._latest_compatible_version
|
2022-08-08 03:36:49 +05:30
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
def get_local(self):
|
|
|
|
|
if not self.is_installed:
|
2023-06-30 04:38:42 +05:30
|
|
|
raise PluginNotInstalled(
|
2022-08-31 19:26:52 +05:30
|
|
|
f"{self.name} needs to be installed to get its local plugin.")
|
2022-08-08 03:36:49 +05:30
|
|
|
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()
|
2022-08-05 22:43:07 +05:30
|
|
|
)
|
|
|
|
|
|
2022-08-12 16:34:57 +05:30
|
|
|
async def uninstall(self):
|
|
|
|
|
await self.get_local().uninstall()
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"{self.name} uninstalled", color=(0.9, 1, 0))
|
2022-08-21 06:01:15 +05:30
|
|
|
|
|
|
|
|
def has_update(self):
|
2022-08-28 23:09:09 +05:30
|
|
|
try:
|
|
|
|
|
latest_compatible_version = self.latest_compatible_version
|
2023-06-30 04:38:42 +05:30
|
|
|
except NoCompatibleVersion:
|
2022-08-28 23:09:09 +05:30
|
|
|
return False
|
|
|
|
|
else:
|
|
|
|
|
return self.get_local().version != latest_compatible_version.number
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
async def update(self):
|
2022-08-27 18:39:43 +05:30
|
|
|
if await self.latest_compatible_version.install(suppress_screenmessage=True):
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage(f"{self.name} updated to {self.latest_compatible_version.number}",
|
|
|
|
|
color=(0, 1, 0))
|
|
|
|
|
bui.getsound('shieldUp').play()
|
2022-08-27 18:39:43 +05:30
|
|
|
else:
|
2023-05-17 02:57:02 +05:30
|
|
|
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()
|
2022-07-31 21:40:54 +05:30
|
|
|
|
|
|
|
|
|
2025-08-05 06:27:53 +05:30
|
|
|
class PluginManager:
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.request_headers = HEADERS
|
|
|
|
|
self._index = _CACHE.get("index", {})
|
|
|
|
|
self._changelog = _CACHE.get("changelog", {})
|
|
|
|
|
self.categories = {}
|
|
|
|
|
self.module_path = sys.modules[__name__].__file__
|
|
|
|
|
self._index_setup_in_progress = False
|
|
|
|
|
self._changelog_setup_in_progress = False
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
),
|
|
|
|
|
headers=self.request_headers,
|
|
|
|
|
)
|
|
|
|
|
response = await async_send_network_request(request)
|
|
|
|
|
index = json.loads(response.read())
|
|
|
|
|
self.set_index_global_cache(index)
|
|
|
|
|
self._index = index
|
|
|
|
|
return self._index
|
|
|
|
|
|
|
|
|
|
async def setup_index(self):
|
|
|
|
|
while self._index_setup_in_progress:
|
|
|
|
|
# Avoid making multiple network calls to the same resource in parallel.
|
|
|
|
|
# Rather wait for the previous network call to complete.
|
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
self._index_setup_in_progress = not bool(self._index)
|
|
|
|
|
index = await self.get_index()
|
|
|
|
|
await self.setup_plugin_categories(index)
|
|
|
|
|
self._index_setup_in_progress = False
|
|
|
|
|
|
|
|
|
|
async def get_changelog(self) -> str:
|
|
|
|
|
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)
|
|
|
|
|
response = await async_send_network_request(request)
|
|
|
|
|
self._changelog = response.read().decode()
|
|
|
|
|
requested = True
|
|
|
|
|
return [self._changelog, requested]
|
|
|
|
|
|
|
|
|
|
async def setup_changelog(self, version=None) -> None:
|
|
|
|
|
if version is None:
|
|
|
|
|
version = PLUGIN_MANAGER_VERSION
|
|
|
|
|
while self._changelog_setup_in_progress:
|
|
|
|
|
# Avoid making multiple network calls to the same resource in parallel.
|
|
|
|
|
# Rather wait for the previous network call to complete.
|
|
|
|
|
await asyncio.sleep(0.1)
|
|
|
|
|
self._changelog_setup_in_progress = not bool(self._changelog)
|
|
|
|
|
try:
|
|
|
|
|
full_changelog = await self.get_changelog()
|
|
|
|
|
if full_changelog[1]:
|
|
|
|
|
pattern = rf"### {version} \(\d\d-\d\d-\d{{4}}\)\n(.*?)(?=### \d+\.\d+\.\d+|\Z)"
|
|
|
|
|
released_on = full_changelog[0].split(version)[1].split('\n')[0]
|
|
|
|
|
matches = re.findall(pattern, full_changelog[0], re.DOTALL)
|
|
|
|
|
if matches:
|
|
|
|
|
changelog = {
|
|
|
|
|
'released_on': released_on,
|
|
|
|
|
'info': matches[0].strip()
|
|
|
|
|
}
|
|
|
|
|
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.'}
|
|
|
|
|
self.set_changelog_global_cache(changelog)
|
|
|
|
|
self._changelog_setup_in_progress = False
|
|
|
|
|
|
|
|
|
|
async def setup_plugin_categories(self, plugin_index):
|
|
|
|
|
# A hack to have the "All" category show at the top.
|
|
|
|
|
self.categories["All"] = None
|
|
|
|
|
|
|
|
|
|
requests = []
|
|
|
|
|
for meta_url in plugin_index["categories"]:
|
|
|
|
|
category = Category(meta_url)
|
|
|
|
|
request = category.fetch_metadata()
|
|
|
|
|
requests.append(request)
|
|
|
|
|
for source in babase.app.config["Community Plugin Manager"]["Custom Sources"]:
|
|
|
|
|
source_splits = source.split("@", maxsplit=1)
|
|
|
|
|
if len(source_splits) == 1:
|
|
|
|
|
# Fallack to `main` if `@branchname` isn't specified in an external source URI.
|
|
|
|
|
source_repo, source_tag = source_splits[0], "main"
|
|
|
|
|
else:
|
|
|
|
|
source_repo, source_tag = source_splits
|
|
|
|
|
meta_url = partial_format(
|
|
|
|
|
plugin_index["external_source_url"],
|
|
|
|
|
repository=source_repo,
|
|
|
|
|
)
|
|
|
|
|
category = Category(meta_url, tag=source_tag)
|
|
|
|
|
request = category.fetch_metadata()
|
|
|
|
|
requests.append(request)
|
|
|
|
|
categories = await asyncio.gather(*requests)
|
|
|
|
|
|
|
|
|
|
all_plugins = []
|
|
|
|
|
for category in categories:
|
|
|
|
|
self.categories[await category.get_name()] = category
|
|
|
|
|
all_plugins.extend(await category.get_plugins())
|
|
|
|
|
self.categories["All"] = CategoryAll(plugins=all_plugins)
|
|
|
|
|
|
|
|
|
|
def cleanup(self):
|
|
|
|
|
for category in self.categories.values():
|
|
|
|
|
if category is not None:
|
|
|
|
|
category.cleanup()
|
|
|
|
|
self.categories.clear()
|
|
|
|
|
self._index.clear()
|
|
|
|
|
self._changelog = None
|
|
|
|
|
self.unset_index_global_cache()
|
|
|
|
|
|
|
|
|
|
async def refresh(self):
|
|
|
|
|
self.cleanup()
|
|
|
|
|
await self.setup_index()
|
|
|
|
|
|
|
|
|
|
def set_index_global_cache(self, index):
|
|
|
|
|
_CACHE["index"] = index
|
|
|
|
|
|
|
|
|
|
def set_changelog_global_cache(self, changelog):
|
|
|
|
|
_CACHE["changelog"] = changelog
|
|
|
|
|
|
|
|
|
|
def unset_index_global_cache(self):
|
|
|
|
|
try:
|
|
|
|
|
del _CACHE["index"]
|
|
|
|
|
del _CACHE["changelog"]
|
|
|
|
|
except KeyError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
async def get_update_details(self):
|
|
|
|
|
index = await self.get_index()
|
|
|
|
|
for version, info in index["versions"].items():
|
|
|
|
|
if info["api_version"] != _app_api_version:
|
|
|
|
|
# No point checking a version of the API game doesn't support.
|
|
|
|
|
continue
|
|
|
|
|
if version == PLUGIN_MANAGER_VERSION:
|
|
|
|
|
# We're already on the latest version for the current API.
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
if next(iter(index["versions"])) == version:
|
|
|
|
|
# Version on the top is the latest, so no need to specify
|
|
|
|
|
# the commit SHA explicitly to GitHub to access the latest file.
|
|
|
|
|
commit_sha = None
|
|
|
|
|
else:
|
|
|
|
|
commit_sha = info["commit_sha"]
|
|
|
|
|
return version, commit_sha
|
|
|
|
|
|
|
|
|
|
async def update(self, to_version=None, commit_sha=None):
|
|
|
|
|
index = await self.get_index()
|
|
|
|
|
if to_version is None:
|
|
|
|
|
to_version, commit_sha = await self.get_update_details()
|
|
|
|
|
to_version_info = index["versions"][to_version]
|
|
|
|
|
tag = commit_sha or CURRENT_TAG
|
|
|
|
|
download_url = index["plugin_manager_url"].format(
|
|
|
|
|
content_type="raw",
|
|
|
|
|
tag=tag,
|
|
|
|
|
)
|
|
|
|
|
response = await async_send_network_request(download_url)
|
|
|
|
|
content = response.read()
|
|
|
|
|
if hashlib.md5(content).hexdigest() != to_version_info["md5sum"]:
|
|
|
|
|
raise MD5CheckSumFailed("MD5 checksum failed during plugin manager update.")
|
|
|
|
|
with open(self.module_path, "wb") as fout:
|
|
|
|
|
fout.write(content)
|
|
|
|
|
return to_version_info
|
|
|
|
|
|
|
|
|
|
async def soft_refresh(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
2024-04-23 01:49:42 +05:30
|
|
|
class ChangelogWindow(popup.PopupWindow):
|
|
|
|
|
def __init__(self, origin_widget):
|
|
|
|
|
self.scale_origin = origin_widget.get_screen_space_center()
|
|
|
|
|
bui.getsound('swish').play()
|
2025-08-04 02:51:27 +05:30
|
|
|
s = 1.65 if _uiscale() is babase.UIScale.SMALL else 1.39 if _uiscale() is babase.UIScale.MEDIUM else 1.67
|
2024-04-23 01:49:42 +05:30
|
|
|
width = 400 * s
|
|
|
|
|
height = width * 0.5
|
|
|
|
|
color = (1, 1, 1)
|
|
|
|
|
text_scale = 0.7 * s
|
|
|
|
|
self._transition_out = 'out_scale'
|
|
|
|
|
transition = 'in_scale'
|
|
|
|
|
|
2025-04-08 18:31:48 +02:00
|
|
|
self._root_widget = bui.containerwidget(
|
|
|
|
|
size=(width, height),
|
|
|
|
|
on_outside_click_call=self._back,
|
|
|
|
|
transition=transition,
|
2025-08-05 19:24:53 +00:00
|
|
|
scale=(1.5 if _uiscale() is babase.UIScale.SMALL else 1.5 if _uiscale()
|
|
|
|
|
is babase.UIScale.MEDIUM else 1.0),
|
2025-04-08 18:31:48 +02:00
|
|
|
scale_origin_stack_offset=self.scale_origin
|
|
|
|
|
)
|
2024-04-23 01:49:42 +05:30
|
|
|
|
2025-08-06 17:55:51 +05:30
|
|
|
_add_popup(self)
|
|
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, height * 0.87),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text='ChangeLog',
|
|
|
|
|
scale=text_scale * 1.25,
|
|
|
|
|
color=bui.app.ui_v1.title_color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2024-04-23 01:49:42 +05:30
|
|
|
|
|
|
|
|
back_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.1, height * 0.8),
|
|
|
|
|
size=(60, 60),
|
|
|
|
|
scale=0.8,
|
|
|
|
|
label=babase.charstr(babase.SpecialChar.BACK),
|
|
|
|
|
button_type='backSmall',
|
2025-08-04 02:35:20 +05:30
|
|
|
on_activate_call=self._back
|
|
|
|
|
)
|
2024-04-23 01:49:42 +05:30
|
|
|
|
|
|
|
|
bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
|
|
|
|
|
|
2024-08-08 01:49:31 +05:30
|
|
|
try:
|
|
|
|
|
released_on = _CACHE['changelog']['released_on']
|
|
|
|
|
logs = _CACHE['changelog']['info'].split('\n')
|
|
|
|
|
h_align = 'left'
|
|
|
|
|
extra = 0.1
|
|
|
|
|
except KeyError:
|
|
|
|
|
released_on = ''
|
|
|
|
|
logs = ["Could not load ChangeLog"]
|
|
|
|
|
h_align = 'center'
|
|
|
|
|
extra = 1
|
|
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, height * 0.72),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=PLUGIN_MANAGER_VERSION + released_on,
|
|
|
|
|
scale=text_scale * 0.9,
|
|
|
|
|
color=color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2024-04-23 01:49:42 +05:30
|
|
|
|
|
|
|
|
bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.7, height * 0.72 - 20),
|
|
|
|
|
size=(140, 60),
|
|
|
|
|
scale=0.8,
|
|
|
|
|
label='Full ChangeLog',
|
|
|
|
|
button_type='square',
|
2025-08-04 02:35:20 +05:30
|
|
|
on_activate_call=lambda: bui.open_url(REPOSITORY_URL + '/blob/main/CHANGELOG.md')
|
|
|
|
|
)
|
2024-04-23 01:49:42 +05:30
|
|
|
|
|
|
|
|
loop_height = height * 0.62
|
|
|
|
|
for log in logs:
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.5 * extra, loop_height),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align=h_align,
|
|
|
|
|
v_align='top',
|
|
|
|
|
text=log,
|
|
|
|
|
scale=text_scale,
|
|
|
|
|
color=color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2024-04-23 01:49:42 +05:30
|
|
|
loop_height -= 35
|
|
|
|
|
|
|
|
|
|
def _back(self) -> None:
|
|
|
|
|
bui.getsound('swish').play()
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2024-04-23 01:49:42 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
|
|
|
|
|
|
|
|
|
|
2024-04-21 15:58:14 +05:30
|
|
|
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()
|
|
|
|
|
bui.getsound('swish').play()
|
2025-08-04 02:51:27 +05:30
|
|
|
s = 1.25 if _uiscale() is babase.UIScale.SMALL else 1.39 if _uiscale() is babase.UIScale.MEDIUM else 1.67
|
2024-04-21 15:58:14 +05:30
|
|
|
width = 400 * s
|
|
|
|
|
height = width * 0.8
|
|
|
|
|
color = (1, 1, 1)
|
|
|
|
|
text_scale = 0.7 * s
|
|
|
|
|
self._transition_out = 'out_scale'
|
|
|
|
|
transition = 'in_scale'
|
|
|
|
|
|
2025-04-08 18:31:48 +02:00
|
|
|
self._root_widget = bui.containerwidget(
|
|
|
|
|
size=(width, height),
|
|
|
|
|
on_outside_click_call=self._back,
|
|
|
|
|
transition=transition,
|
2025-08-04 02:51:27 +05:30
|
|
|
scale=(1.5 if _uiscale() is babase.UIScale.SMALL else 1.5
|
|
|
|
|
if _uiscale() is babase.UIScale.MEDIUM else 1.0),
|
2025-04-08 18:31:48 +02:00
|
|
|
scale_origin_stack_offset=self.scale_origin
|
|
|
|
|
)
|
2024-04-21 15:58:14 +05:30
|
|
|
|
2025-08-06 17:55:51 +05:30
|
|
|
_add_popup(self)
|
|
|
|
|
|
2024-04-21 15:58:14 +05:30
|
|
|
pos = height * 0.9
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, pos),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text='Authors',
|
|
|
|
|
scale=text_scale * 1.25,
|
|
|
|
|
color=color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2024-04-21 10:28:35 +00:00
|
|
|
|
2024-04-21 15:58:14 +05:30
|
|
|
back_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.1, height * 0.87),
|
|
|
|
|
size=(60, 60),
|
|
|
|
|
scale=0.8,
|
|
|
|
|
label=babase.charstr(babase.SpecialChar.BACK),
|
|
|
|
|
button_type='backSmall',
|
2025-08-04 02:35:20 +05:30
|
|
|
on_activate_call=self._back
|
|
|
|
|
)
|
2024-04-21 15:58:14 +05:30
|
|
|
|
|
|
|
|
bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
|
|
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
self._scrollwidget = bui.scrollwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
size=(width * 0.8, height * 0.75),
|
|
|
|
|
position=(width * 0.1, height * 0.1)
|
2025-08-06 17:55:51 +05:30
|
|
|
)
|
2025-08-04 02:35:20 +05:30
|
|
|
self._columnwidget = bui.columnwidget(
|
|
|
|
|
parent=self._scrollwidget,
|
|
|
|
|
border=1,
|
|
|
|
|
left_border=-15,
|
|
|
|
|
margin=0
|
|
|
|
|
)
|
2024-04-21 15:58:14 +05:30
|
|
|
|
|
|
|
|
for author in self.authors_info:
|
|
|
|
|
for key, value in author.items():
|
|
|
|
|
text = f"{key.title()}: {value if value != '' else 'Not Provided'}"
|
|
|
|
|
if key == 'name':
|
2024-04-21 16:50:02 +05:30
|
|
|
text = value
|
2025-08-04 02:35:20 +05:30
|
|
|
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),
|
|
|
|
|
scale=(
|
2025-08-05 19:24:53 +00:00
|
|
|
(1.1 if key == 'name' else 0.9) if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
(1.2 if key == 'name' else 1.0)
|
|
|
|
|
),
|
2025-08-04 02:35:20 +05:30
|
|
|
text=text,
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
maxwidth=420
|
|
|
|
|
)
|
|
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._columnwidget,
|
|
|
|
|
size=(width * 0.8, 30),
|
|
|
|
|
always_highlight=True,
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center'
|
|
|
|
|
)
|
2024-04-21 15:58:14 +05:30
|
|
|
|
|
|
|
|
def _back(self) -> None:
|
|
|
|
|
bui.getsound('swish').play()
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2024-04-21 15:58:14 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
|
|
|
|
|
|
|
|
|
|
2022-08-02 00:35:19 +05:30
|
|
|
class PluginWindow(popup.PopupWindow):
|
2025-08-06 00:50:09 +05:30
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
plugin: Plugin,
|
|
|
|
|
origin_widget,
|
|
|
|
|
plugins_list,
|
2025-08-05 19:24:53 +00:00
|
|
|
transition='in_scale',
|
2025-08-06 00:50:09 +05:30
|
|
|
button_callback=lambda: None,
|
|
|
|
|
):
|
|
|
|
|
self.plugin: Plugin = plugin
|
|
|
|
|
self.transition = transition
|
|
|
|
|
self.plugins_list = plugins_list
|
2022-08-06 00:18:35 +05:30
|
|
|
self.button_callback = button_callback
|
2022-08-08 22:09:22 +05:30
|
|
|
self.scale_origin = origin_widget.get_screen_space_center()
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-08 22:09:22 +05:30
|
|
|
loop.create_task(self.draw_ui())
|
2022-12-21 08:39:02 +00:00
|
|
|
|
2023-01-18 18:54:16 +05:30
|
|
|
def get_description(self, minimum_character_offset=40):
|
|
|
|
|
"""
|
2025-08-06 00:50:09 +05:30
|
|
|
Splits the long plugin description into multiple lines.
|
2023-01-18 18:54:16 +05:30
|
|
|
"""
|
|
|
|
|
string = self.plugin.info["description"]
|
|
|
|
|
string_length = len(string)
|
|
|
|
|
|
|
|
|
|
partitioned_string = ""
|
|
|
|
|
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)
|
|
|
|
|
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.
|
|
|
|
|
partitioned_string += "\n"
|
2022-12-22 17:06:27 +05:30
|
|
|
partitioned_string_length = len(partitioned_string)
|
|
|
|
|
|
2023-01-18 18:54:16 +05:30
|
|
|
return partitioned_string
|
|
|
|
|
|
|
|
|
|
async def draw_ui(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('swish').play()
|
2022-07-31 23:44:25 +05:30
|
|
|
b_text_color = (0.75, 0.7, 0.8)
|
2025-08-04 02:51:27 +05:30
|
|
|
s = 1.25 if _uiscale() is babase.UIScale.SMALL else 1.39 if babase.UIScale.MEDIUM else 1.67
|
2025-08-06 00:50:09 +05:30
|
|
|
width = 450 * s
|
2023-03-04 13:01:48 +05:30
|
|
|
height = 120 + 100 * s
|
2022-07-31 21:40:54 +05:30
|
|
|
color = (1, 1, 1)
|
|
|
|
|
text_scale = 0.7 * s
|
2022-08-11 06:34:17 +05:30
|
|
|
|
2025-04-08 18:31:48 +02:00
|
|
|
self._root_widget = bui.containerwidget(
|
|
|
|
|
size=(width, height),
|
|
|
|
|
on_outside_click_call=self._cancel,
|
2025-08-06 00:50:09 +05:30
|
|
|
transition=self.transition,
|
2025-08-04 02:51:27 +05:30
|
|
|
scale=(2.1 if _uiscale() is babase.UIScale.SMALL else 1.5
|
|
|
|
|
if _uiscale() is babase.UIScale.MEDIUM else 1.0),
|
2025-04-08 18:31:48 +02:00
|
|
|
scale_origin_stack_offset=self.scale_origin
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
2025-08-06 17:55:51 +05:30
|
|
|
_add_popup(self)
|
|
|
|
|
|
2025-08-06 00:50:09 +05:30
|
|
|
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
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if self.p_n_plugins is not None:
|
|
|
|
|
if self.p_n_plugins[0] is not None:
|
|
|
|
|
previous_plugin_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
2025-08-05 19:24:53 +00:00
|
|
|
position=(-12.5*s + (4 if _uiscale() is babase.UIScale.SMALL else -5),
|
|
|
|
|
height/2 - 20*s),
|
2025-08-06 00:50:09 +05:30
|
|
|
label='<',
|
|
|
|
|
size=(25, 40),
|
|
|
|
|
color=(1, 0.5, 0.5),
|
|
|
|
|
scale=s,
|
|
|
|
|
on_activate_call=self.show_previous_plugin
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if self.p_n_plugins[1] is not None:
|
|
|
|
|
next_plugin_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
2025-08-05 19:24:53 +00:00
|
|
|
position=(width - 12.5*s - (8 if _uiscale()
|
|
|
|
|
is babase.UIScale.SMALL else 0), height/2 - 20*s),
|
2025-08-06 00:50:09 +05:30
|
|
|
label='>',
|
|
|
|
|
size=(25, 40),
|
|
|
|
|
color=(1, 0.5, 0.5),
|
|
|
|
|
scale=s,
|
|
|
|
|
on_activate_call=self.show_next_plugin
|
|
|
|
|
)
|
|
|
|
|
|
2022-07-31 21:40:54 +05:30
|
|
|
pos = height * 0.8
|
2024-04-20 23:53:59 +05:30
|
|
|
plug_name = self.plugin.name.replace('_', ' ').title()
|
|
|
|
|
plugin_title = f"{plug_name} (v{self.plugin.latest_compatible_version.number})"
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, pos),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=plugin_title,
|
|
|
|
|
scale=text_scale * 1.25,
|
|
|
|
|
color=color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2022-07-31 21:40:54 +05:30
|
|
|
pos -= 25
|
2024-04-21 15:35:56 +05:30
|
|
|
# Author
|
|
|
|
|
text = 'by ' + ', '.join([author["name"] for author in self.plugin.info["authors"]])
|
2025-08-04 02:35:20 +05:30
|
|
|
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),
|
|
|
|
|
label='',
|
|
|
|
|
texture=bui.gettexture("empty"),
|
|
|
|
|
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),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=text,
|
|
|
|
|
scale=text_scale * 0.8,
|
|
|
|
|
color=(0.75, 0.7, 0.8),
|
|
|
|
|
maxwidth=width * 0.9,
|
|
|
|
|
draw_controller=author_text_control_btn,
|
|
|
|
|
)
|
2025-01-14 16:49:58 +05:30
|
|
|
pos -= 60
|
|
|
|
|
# Info
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, pos),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=self.get_description(),
|
|
|
|
|
scale=text_scale * 0.6,
|
|
|
|
|
color=color,
|
|
|
|
|
maxwidth=width * 0.95
|
|
|
|
|
)
|
2022-08-29 18:41:17 +05:30
|
|
|
b1_color = None
|
2022-08-05 22:43:07 +05:30
|
|
|
b2_color = (0.8, 0.15, 0.35)
|
|
|
|
|
b3_color = (0.2, 0.8, 0.3)
|
2022-07-31 23:44:25 +05:30
|
|
|
pos = height * 0.1
|
|
|
|
|
button_size = (80 * s, 40 * s)
|
2022-08-02 00:35:19 +05:30
|
|
|
|
2022-08-08 22:09:22 +05:30
|
|
|
to_draw_button1 = True
|
2022-08-12 07:21:51 +05:30
|
|
|
to_draw_button4 = False
|
2022-08-08 22:09:22 +05:30
|
|
|
if self.plugin.is_installed:
|
|
|
|
|
self.local_plugin = self.plugin.get_local()
|
2022-09-05 00:27:45 +05:30
|
|
|
if not await self.local_plugin.has_plugins():
|
2022-08-08 22:09:22 +05:30
|
|
|
to_draw_button1 = False
|
2022-07-31 23:44:25 +05:30
|
|
|
else:
|
2022-08-08 22:09:22 +05:30
|
|
|
if await self.local_plugin.is_enabled():
|
|
|
|
|
button1_label = "Disable"
|
2022-08-29 18:41:17 +05:30
|
|
|
b1_color = (0.6, 0.53, 0.63)
|
2022-08-08 22:09:22 +05:30
|
|
|
button1_action = self.disable
|
2022-08-12 07:21:51 +05:30
|
|
|
if self.local_plugin.has_settings():
|
|
|
|
|
to_draw_button4 = True
|
2022-08-08 22:09:22 +05:30
|
|
|
else:
|
|
|
|
|
button1_label = "Enable"
|
|
|
|
|
button1_action = self.enable
|
2022-07-31 23:44:25 +05:30
|
|
|
button2_label = "Uninstall"
|
2022-08-05 22:43:07 +05:30
|
|
|
button2_action = self.uninstall
|
2022-08-21 06:01:15 +05:30
|
|
|
has_update = self.plugin.has_update()
|
2022-08-05 22:43:07 +05:30
|
|
|
if has_update:
|
|
|
|
|
button3_label = "Update"
|
|
|
|
|
button3_action = self.update
|
2022-07-31 23:44:25 +05:30
|
|
|
else:
|
|
|
|
|
button1_label = "Install"
|
2022-08-05 22:43:07 +05:30
|
|
|
button1_action = self.install
|
2022-07-31 23:44:25 +05:30
|
|
|
|
2022-08-08 22:09:22 +05:30
|
|
|
if to_draw_button1:
|
2025-08-04 02:35:20 +05:30
|
|
|
selected_btn = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(
|
|
|
|
|
width * (0.1 if self.plugin.is_installed and has_update else
|
2025-08-05 19:24:53 +00:00
|
|
|
0.25 if self.plugin.is_installed else 0.4), pos
|
2025-08-04 02:35:20 +05:30
|
|
|
),
|
|
|
|
|
size=button_size,
|
|
|
|
|
on_activate_call=button1_action,
|
|
|
|
|
color=b1_color,
|
|
|
|
|
textcolor=b_text_color,
|
|
|
|
|
button_type='square',
|
|
|
|
|
text_scale=1,
|
|
|
|
|
label=button1_label
|
|
|
|
|
)
|
2022-08-01 00:32:52 +05:30
|
|
|
|
2022-08-08 22:09:22 +05:30
|
|
|
if self.plugin.is_installed:
|
2025-08-04 02:35:20 +05:30
|
|
|
selected_btn = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
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
|
|
|
|
|
)
|
2022-08-01 00:32:52 +05:30
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
if has_update:
|
2025-08-04 02:35:20 +05:30
|
|
|
selected_btn = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.7, pos),
|
|
|
|
|
size=button_size,
|
|
|
|
|
on_activate_call=button3_action,
|
|
|
|
|
color=b3_color,
|
|
|
|
|
textcolor=b_text_color,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
button_type='square',
|
|
|
|
|
text_scale=1,
|
|
|
|
|
label=button3_label
|
|
|
|
|
)
|
2024-04-22 19:29:31 +05:30
|
|
|
|
2025-04-08 18:31:48 +02:00
|
|
|
bui.containerwidget(
|
|
|
|
|
edit=self._root_widget,
|
|
|
|
|
on_cancel_call=self._cancel,
|
|
|
|
|
selected_child=selected_btn
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
open_pos_x = (390 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
450 if _uiscale() is babase.UIScale.MEDIUM else 440)
|
|
|
|
|
open_pos_y = (100 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
110 if _uiscale() is babase.UIScale.MEDIUM else 120)
|
2025-08-04 02:35:20 +05:30
|
|
|
open_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(open_pos_x, open_pos_y),
|
|
|
|
|
size=(40, 40),
|
|
|
|
|
button_type="square",
|
|
|
|
|
label="",
|
|
|
|
|
color=(0.6, 0.53, 0.63),
|
|
|
|
|
on_activate_call=lambda: bui.open_url(self.plugin.view_url)
|
|
|
|
|
)
|
|
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(open_pos_x, open_pos_y),
|
|
|
|
|
size=(40, 40),
|
|
|
|
|
color=(0.8, 0.95, 1),
|
|
|
|
|
texture=bui.gettexture("file"),
|
|
|
|
|
draw_controller=open_button
|
|
|
|
|
)
|
|
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(open_pos_x-3, open_pos_y+12),
|
|
|
|
|
text="Source",
|
|
|
|
|
size=(10, 10),
|
|
|
|
|
draw_controller=open_button,
|
|
|
|
|
color=(1, 1, 1, 1),
|
|
|
|
|
rotate=25,
|
|
|
|
|
scale=0.45
|
|
|
|
|
)
|
2023-02-07 23:12:23 +05:30
|
|
|
|
2023-02-08 14:16:49 +00:00
|
|
|
# Below snippet handles the tutorial button in the plugin window
|
2023-02-07 21:55:25 +05:30
|
|
|
tutorial_url = self.plugin.info["external_url"]
|
|
|
|
|
if tutorial_url:
|
|
|
|
|
def tutorial_confirm_window():
|
2023-02-08 14:16:49 +00:00
|
|
|
text = "This will take you to \n\""+self.plugin.info["external_url"] + "\""
|
2023-02-07 21:55:25 +05:30
|
|
|
tutorial_confirm_window = confirm.ConfirmWindow(
|
2023-02-08 14:16:49 +00:00
|
|
|
text=text,
|
2023-05-17 02:57:02 +05:30
|
|
|
action=lambda: bui.open_url(self.plugin.info["external_url"]),
|
2023-02-08 14:16:49 +00:00
|
|
|
)
|
2025-08-04 02:51:27 +05:30
|
|
|
open_pos_x = (440 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
500 if _uiscale() is babase.UIScale.MEDIUM else 490)
|
|
|
|
|
open_pos_y = (100 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
110 if _uiscale() is babase.UIScale.MEDIUM else 120)
|
2025-08-04 02:35:20 +05:30
|
|
|
open_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(open_pos_x, open_pos_y),
|
|
|
|
|
size=(40, 40),
|
|
|
|
|
button_type="square",
|
|
|
|
|
label="",
|
|
|
|
|
color=(0.6, 0.53, 0.63),
|
|
|
|
|
on_activate_call=tutorial_confirm_window
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(open_pos_x, open_pos_y),
|
|
|
|
|
size=(40, 40),
|
|
|
|
|
color=(0.8, 0.95, 1),
|
|
|
|
|
texture=bui.gettexture("frameInset"),
|
|
|
|
|
draw_controller=open_button
|
|
|
|
|
)
|
|
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(open_pos_x - 3, open_pos_y + 12),
|
|
|
|
|
text="Tutorial",
|
|
|
|
|
size=(10, 10),
|
|
|
|
|
draw_controller=open_button,
|
|
|
|
|
color=(1, 1, 1, 1),
|
|
|
|
|
rotate=25,
|
|
|
|
|
scale=0.45
|
|
|
|
|
)
|
2023-02-07 23:12:23 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
if to_draw_button4:
|
2025-08-04 02:51:27 +05:30
|
|
|
settings_pos_x = (60 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
60 if _uiscale() is babase.UIScale.MEDIUM else 60)
|
|
|
|
|
settings_pos_y = (100 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
110 if _uiscale() is babase.UIScale.MEDIUM else 120)
|
2025-08-04 02:35:20 +05:30
|
|
|
settings_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(settings_pos_x, settings_pos_y),
|
|
|
|
|
size=(40, 40),
|
|
|
|
|
button_type="square",
|
|
|
|
|
label="",
|
|
|
|
|
color=(0, 0.75, 0.75)
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.buttonwidget(
|
2022-12-05 21:39:08 +05:30
|
|
|
edit=settings_button,
|
2025-08-04 02:35:20 +05:30
|
|
|
on_activate_call=babase.Call(self.settings, settings_button)
|
|
|
|
|
)
|
|
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(settings_pos_x, settings_pos_y),
|
|
|
|
|
size=(40, 40),
|
|
|
|
|
color=(0.8, 0.95, 1),
|
|
|
|
|
texture=bui.gettexture("settingsIcon"),
|
|
|
|
|
draw_controller=settings_button
|
|
|
|
|
)
|
2022-08-12 07:21:51 +05:30
|
|
|
|
2022-08-06 16:03:12 +05:30
|
|
|
def _ok(self) -> None:
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
2023-04-30 22:24:56 +05:30
|
|
|
|
|
|
|
|
def _cancel(self) -> None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('swish').play()
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
2022-08-01 00:32:52 +05:30
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
def button(fn):
|
2022-08-06 00:18:35 +05:30
|
|
|
async def asyncio_handler(fn, self, *args, **kwargs):
|
|
|
|
|
await fn(self, *args, **kwargs)
|
2022-08-08 22:09:22 +05:30
|
|
|
await self.button_callback()
|
2022-08-06 00:18:35 +05:30
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
def wrapper(self, *args, **kwargs):
|
2022-08-06 16:03:12 +05:30
|
|
|
self._ok()
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-06 00:18:35 +05:30
|
|
|
if asyncio.iscoroutinefunction(fn):
|
|
|
|
|
loop.create_task(asyncio_handler(fn, self, *args, **kwargs))
|
|
|
|
|
else:
|
|
|
|
|
fn(self, *args, **kwargs)
|
2022-08-12 16:34:57 +05:30
|
|
|
loop.create_task(self.button_callback())
|
2022-08-08 22:09:22 +05:30
|
|
|
|
2022-08-05 22:43:07 +05:30
|
|
|
return wrapper
|
|
|
|
|
|
2022-12-05 21:39:08 +05:30
|
|
|
def settings(self, source_widget):
|
|
|
|
|
self.local_plugin.launch_settings(source_widget)
|
2022-08-12 07:21:51 +05:30
|
|
|
|
2025-08-06 00:50:09 +05:30
|
|
|
def show_previous_plugin(self):
|
|
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_right')
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2025-08-06 00:50:09 +05:30
|
|
|
PluginWindow(
|
|
|
|
|
self.p_n_plugins[0],
|
|
|
|
|
self._root_widget,
|
|
|
|
|
transition='in_left',
|
|
|
|
|
plugins_list=self.plugins_list,
|
|
|
|
|
button_callback=lambda: None
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def show_next_plugin(self):
|
|
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_left')
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2025-08-06 00:50:09 +05:30
|
|
|
PluginWindow(
|
|
|
|
|
self.p_n_plugins[1],
|
|
|
|
|
self._root_widget,
|
|
|
|
|
transition='in_right',
|
|
|
|
|
plugins_list=self.plugins_list,
|
|
|
|
|
button_callback=lambda: None
|
|
|
|
|
)
|
|
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
@button
|
|
|
|
|
def disable(self) -> None:
|
|
|
|
|
self.local_plugin.disable()
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
@button
|
2022-08-06 00:18:35 +05:30
|
|
|
async def enable(self) -> None:
|
2022-08-08 03:36:49 +05:30
|
|
|
await self.local_plugin.enable()
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('gunCocking').play()
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
@button
|
2022-08-06 00:18:35 +05:30
|
|
|
async def install(self):
|
2022-08-17 02:28:55 +05:30
|
|
|
await self.plugin.latest_compatible_version.install()
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('cashRegister2').play()
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
@button
|
2022-08-12 16:34:57 +05:30
|
|
|
async def uninstall(self):
|
|
|
|
|
await self.plugin.uninstall()
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('shieldDown').play()
|
2022-08-05 22:43:07 +05:30
|
|
|
|
|
|
|
|
@button
|
2022-08-06 00:18:35 +05:30
|
|
|
async def update(self):
|
|
|
|
|
await self.plugin.update()
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('shieldUp').play()
|
2022-08-05 22:43:07 +05:30
|
|
|
|
2022-08-03 21:18:03 +05:30
|
|
|
|
2025-08-06 17:55:51 +05:30
|
|
|
class PluginCustomSourcesWindow(popup.PopupWindow):
|
2022-08-11 06:34:17 +05:30
|
|
|
def __init__(self, origin_widget):
|
2022-08-29 18:41:17 +05:30
|
|
|
self.selected_source = None
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
self.scale_origin = origin_widget.get_screen_space_center()
|
|
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
b_textcolor = (0.75, 0.7, 0.8)
|
2022-08-11 06:34:17 +05:30
|
|
|
self._transition_out = 'out_scale'
|
|
|
|
|
transition = 'in_scale'
|
2025-04-08 18:31:48 +02:00
|
|
|
self._root_widget = bui.containerwidget(
|
|
|
|
|
size=(400, 340),
|
|
|
|
|
on_outside_click_call=self._ok,
|
|
|
|
|
transition=transition,
|
2025-08-04 02:51:27 +05:30
|
|
|
scale=(2.1 if _uiscale() is babase.UIScale.SMALL else 1.5
|
|
|
|
|
if _uiscale() is babase.UIScale.MEDIUM else 1.0),
|
2025-04-08 18:31:48 +02:00
|
|
|
scale_origin_stack_offset=self.scale_origin,
|
|
|
|
|
on_cancel_call=self._ok
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2025-08-06 17:55:51 +05:30
|
|
|
_add_popup(self)
|
|
|
|
|
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.textwidget(
|
2022-08-12 07:21:51 +05:30
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(155, 300),
|
|
|
|
|
size=(100, 25),
|
|
|
|
|
text="Custom Plugin Sources",
|
2023-06-21 00:33:31 +05:30
|
|
|
color=bui.app.ui_v1.title_color,
|
2022-08-12 07:21:51 +05:30
|
|
|
scale=0.8,
|
|
|
|
|
h_align="center",
|
|
|
|
|
v_align="center",
|
|
|
|
|
maxwidth=270,
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
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)
|
2022-08-15 21:40:56 +05:30
|
|
|
scroll_pos_y = 105
|
|
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
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
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
delete_source_button_position_pos_x = 360
|
|
|
|
|
delete_source_button_position_pos_y = 110
|
2025-08-04 02:35:20 +05:30
|
|
|
delete_source_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
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)
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
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
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
warning_pos_x = (43 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
35 if _uiscale() is babase.UIScale.MEDIUM else
|
2022-08-29 18:41:17 +05:30
|
|
|
48)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.textwidget(
|
2022-08-11 06:34:17 +05:30
|
|
|
parent=self._root_widget,
|
2022-08-29 18:41:17 +05:30
|
|
|
position=(warning_pos_x, 74),
|
2022-08-12 07:21:51 +05:30
|
|
|
size=(50, 22),
|
|
|
|
|
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",
|
2022-08-11 06:34:17 +05:30
|
|
|
v_align="center",
|
2022-08-12 07:21:51 +05:30
|
|
|
maxwidth=400,
|
2022-08-11 06:34:17 +05:30
|
|
|
)
|
|
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
self._add_source_widget = bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
size=(335, 50),
|
|
|
|
|
position=(21, 22),
|
|
|
|
|
h_align='left',
|
|
|
|
|
v_align='center',
|
|
|
|
|
editable=True,
|
|
|
|
|
scale=0.75,
|
|
|
|
|
maxwidth=215,
|
|
|
|
|
description="Add Source"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(330, 28),
|
|
|
|
|
size=(37, 37),
|
|
|
|
|
on_activate_call=lambda: loop.create_task(self.add_source()),
|
|
|
|
|
label="",
|
|
|
|
|
texture=bui.gettexture("startButton"),
|
|
|
|
|
button_type="square",
|
|
|
|
|
color=(0, 0.9, 0),
|
|
|
|
|
textcolor=b_textcolor,
|
|
|
|
|
text_scale=1
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
self.draw_sources()
|
|
|
|
|
|
|
|
|
|
def draw_sources(self):
|
|
|
|
|
for plugin in self._columnwidget.get_children():
|
|
|
|
|
plugin.delete()
|
|
|
|
|
|
|
|
|
|
color = (1, 1, 1)
|
2023-05-17 02:57:02 +05:30
|
|
|
for custom_source in babase.app.config["Community Plugin Manager"]["Custom Sources"]:
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._columnwidget,
|
|
|
|
|
selectable=True,
|
|
|
|
|
color=color,
|
|
|
|
|
text=custom_source,
|
|
|
|
|
on_select_call=lambda: self.select_source(custom_source),
|
|
|
|
|
h_align='left',
|
|
|
|
|
v_align='center',
|
|
|
|
|
scale=0.75,
|
|
|
|
|
maxwidth=260
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
def select_source(self, source):
|
|
|
|
|
self.selected_source = source
|
|
|
|
|
|
|
|
|
|
async def add_source(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
source = bui.textwidget(query=self._add_source_widget)
|
2023-06-30 04:38:42 +05:30
|
|
|
# External source URIs can optionally suffix `@branchname`, for example:
|
|
|
|
|
# `bombsquad-community/sample-plugin-source@experimental`
|
|
|
|
|
source_splits = source.split("@", maxsplit=1)
|
|
|
|
|
if len(source_splits) == 1:
|
|
|
|
|
# Fallack to `main` if `@branchname` isn't specified in an external source URI.
|
|
|
|
|
source_repo, source_tag = source_splits[0], "main"
|
|
|
|
|
else:
|
|
|
|
|
source_repo, source_tag = source_splits
|
|
|
|
|
meta_url = partial_format(
|
|
|
|
|
_CACHE["index"]["external_source_url"],
|
|
|
|
|
repository=source_repo,
|
2022-08-14 00:42:40 +05:30
|
|
|
)
|
2023-06-30 04:38:42 +05:30
|
|
|
category = Category(meta_url, tag=source_tag)
|
|
|
|
|
try:
|
|
|
|
|
await category.validate()
|
|
|
|
|
except (PluginSourceNetworkError, CategoryMetadataParseError) as e:
|
|
|
|
|
bui.screenmessage(str(e), color=(1, 0, 0))
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('error').play()
|
2022-08-11 06:34:17 +05:30
|
|
|
return
|
2023-05-17 02:57:02 +05:30
|
|
|
if source in babase.app.config["Community Plugin Manager"]["Custom Sources"]:
|
|
|
|
|
bui.screenmessage("Plugin source already exists")
|
|
|
|
|
bui.getsound('error').play()
|
2022-08-12 07:21:51 +05:30
|
|
|
return
|
2023-05-17 02:57:02 +05:30
|
|
|
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.getsound('cashRegister2').play()
|
2022-08-11 06:34:17 +05:30
|
|
|
self.draw_sources()
|
|
|
|
|
|
|
|
|
|
def delete_selected_source(self):
|
2022-08-29 18:41:17 +05:30
|
|
|
if self.selected_source is None:
|
|
|
|
|
return
|
2023-05-17 02:57:02 +05:30
|
|
|
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.getsound('shieldDown').play()
|
2022-08-29 18:41:17 +05:30
|
|
|
self.draw_sources()
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
def _ok(self) -> None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('swish').play()
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
class PluginCategoryWindow(popup.PopupMenuWindow):
|
2022-08-11 06:34:17 +05:30
|
|
|
def __init__(self, choices, current_choice, origin_widget, asyncio_callback):
|
2024-04-22 16:01:07 +05:30
|
|
|
choices = (*choices, "Installed", "Custom Sources")
|
2022-08-11 06:34:17 +05:30
|
|
|
self._asyncio_callback = asyncio_callback
|
|
|
|
|
self.scale_origin = origin_widget.get_screen_space_center()
|
|
|
|
|
super().__init__(
|
2025-04-05 15:43:51 +02:00
|
|
|
position=self.scale_origin,
|
2025-08-04 02:51:27 +05:30
|
|
|
scale=(2.3 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
1.65 if _uiscale() is babase.UIScale.MEDIUM else 1.23),
|
2022-08-11 06:34:17 +05:30
|
|
|
choices=choices,
|
|
|
|
|
current_choice=current_choice,
|
2025-08-04 02:35:20 +05:30
|
|
|
delegate=self
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
self._update_custom_sources_widget()
|
|
|
|
|
|
|
|
|
|
def _update_custom_sources_widget(self):
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
edit=self._columnwidget.get_children()[-1],
|
|
|
|
|
color=(0.5, 0.5, 0.5),
|
|
|
|
|
on_activate_call=self.show_sources_window
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
def popup_menu_selected_choice(self, window, choice):
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-11 06:34:17 +05:30
|
|
|
loop.create_task(self._asyncio_callback(choice))
|
|
|
|
|
|
|
|
|
|
def popup_menu_closing(self, window):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def show_sources_window(self):
|
|
|
|
|
self._ok()
|
2025-08-06 17:55:51 +05:30
|
|
|
PluginCustomSourcesWindow(origin_widget=self.root_widget)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
def _ok(self) -> None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('swish').play()
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(edit=self.root_widget, transition='out_scale')
|
2022-08-11 06:34:17 +05:30
|
|
|
|
|
|
|
|
|
2025-01-13 17:04:48 +05:30
|
|
|
class PluginManagerWindow(bui.MainWindow):
|
2025-08-05 06:27:53 +05:30
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
transition: str = "in_right",
|
|
|
|
|
origin_widget: bui.Widget = None
|
|
|
|
|
):
|
2022-08-10 01:02:10 +05:30
|
|
|
self.plugin_manager = PluginManager()
|
2022-07-03 14:32:05 +05:30
|
|
|
self.category_selection_button = None
|
2024-04-29 02:19:04 +05:30
|
|
|
self.selected_category = 'All'
|
2022-08-06 00:18:35 +05:30
|
|
|
self.plugins_in_current_view = {}
|
2024-04-22 15:33:31 +05:30
|
|
|
self.selected_alphabet_order = 'a_z'
|
|
|
|
|
self.alphabet_order_selection_button = None
|
2025-08-06 17:55:51 +05:30
|
|
|
global open_popups
|
|
|
|
|
open_popups = []
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
loop.create_task(self.draw_index())
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
self._width = (700 if _uiscale() is babase.UIScale.SMALL
|
|
|
|
|
else 550 if _uiscale() is babase.UIScale.MEDIUM
|
2022-08-29 18:41:17 +05:30
|
|
|
else 570)
|
2025-08-04 02:51:27 +05:30
|
|
|
self._height = (500 if _uiscale() is babase.UIScale.SMALL
|
|
|
|
|
else 422 if _uiscale() is babase.UIScale.MEDIUM
|
2022-08-07 17:12:45 +05:30
|
|
|
else 500)
|
2025-08-04 02:51:27 +05:30
|
|
|
top_extra = 20 if _uiscale() is babase.UIScale.SMALL else 0
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
if origin_widget:
|
|
|
|
|
self._transition_out = "out_scale"
|
|
|
|
|
self._scale_origin = origin_widget.get_screen_space_center()
|
|
|
|
|
transition = "in_scale"
|
|
|
|
|
|
2025-01-13 17:04:48 +05:30
|
|
|
super().__init__(
|
|
|
|
|
root_widget=bui.containerwidget(
|
|
|
|
|
size=(self._width, self._height + top_extra),
|
|
|
|
|
toolbar_visibility="menu_minimal",
|
2025-08-04 02:51:27 +05:30
|
|
|
scale=(1.9 if _uiscale() is babase.UIScale.SMALL
|
|
|
|
|
else 1.5 if _uiscale() is babase.UIScale.MEDIUM
|
2025-01-14 21:49:07 +00:00
|
|
|
else 1.0),
|
2025-08-04 02:51:27 +05:30
|
|
|
stack_offset=(0, -25) if _uiscale() is babase.UIScale.SMALL else (0, 0)
|
2025-01-13 17:04:48 +05:30
|
|
|
),
|
2022-07-03 14:32:05 +05:30
|
|
|
transition=transition,
|
2025-01-13 17:04:48 +05:30
|
|
|
origin_widget=origin_widget,
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
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)
|
2025-01-14 21:49:07 +00:00
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
if _uiscale() is bui.UIScale.SMALL:
|
2025-01-13 17:04:48 +05:30
|
|
|
self._back_button = None
|
|
|
|
|
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,
|
|
|
|
|
position=(back_pos_x, back_pos_y),
|
|
|
|
|
size=(60, 60),
|
|
|
|
|
scale=0.8,
|
|
|
|
|
label=babase.charstr(babase.SpecialChar.BACK),
|
|
|
|
|
button_type='backSmall',
|
2025-08-04 02:35:20 +05:30
|
|
|
on_activate_call=self.main_window_back
|
|
|
|
|
)
|
2022-08-01 00:32:52 +05:30
|
|
|
|
2025-01-13 17:04:48 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
title_pos = self._height - (83 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
50 if _uiscale() is babase.UIScale.MEDIUM else 50)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.textwidget(
|
2022-07-03 14:32:05 +05:30
|
|
|
parent=self._root_widget,
|
2022-08-01 02:20:48 +05:30
|
|
|
position=(-10, title_pos),
|
2022-07-03 14:32:05 +05:30
|
|
|
size=(self._width, 25),
|
|
|
|
|
text="Community Plugin Manager",
|
2023-06-21 00:33:31 +05:30
|
|
|
color=bui.app.ui_v1.title_color,
|
2022-07-03 14:32:05 +05:30
|
|
|
scale=1.05,
|
|
|
|
|
h_align="center",
|
|
|
|
|
v_align="center",
|
|
|
|
|
maxwidth=270,
|
|
|
|
|
)
|
2022-08-10 01:02:10 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
loading_pos_y = self._height - (275 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
235 if _uiscale() is babase.UIScale.MEDIUM else 270)
|
2022-08-10 01:02:10 +05:30
|
|
|
|
2023-05-17 02:57:02 +05:30
|
|
|
self._plugin_manager_status_text = bui.textwidget(
|
2022-07-03 14:32:05 +05:30
|
|
|
parent=self._root_widget,
|
2022-08-08 21:09:07 +05:30
|
|
|
position=(-5, loading_pos_y),
|
2022-07-03 14:32:05 +05:30
|
|
|
size=(self._width, 25),
|
2025-02-07 14:39:46 +03:00
|
|
|
text="",
|
2023-06-21 00:33:31 +05:30
|
|
|
color=bui.app.ui_v1.title_color,
|
2022-07-03 14:32:05 +05:30
|
|
|
scale=0.7,
|
|
|
|
|
h_align="center",
|
|
|
|
|
v_align="center",
|
2022-08-07 16:09:38 +05:30
|
|
|
maxwidth=400,
|
2022-07-03 14:32:05 +05:30
|
|
|
)
|
2025-02-07 14:39:46 +03:00
|
|
|
self._loading_spinner = bui.spinnerwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(self._width * 0.5, loading_pos_y),
|
|
|
|
|
style='bomb',
|
|
|
|
|
size=48,
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-08-05 06:27:53 +05:30
|
|
|
@override
|
|
|
|
|
def get_main_window_state(self) -> bui.MainWindowState:
|
|
|
|
|
# Support recreating our window for back/refresh purposes.
|
2025-08-06 17:55:51 +05:30
|
|
|
global open_popups
|
|
|
|
|
print(open_popups)
|
|
|
|
|
for popup in open_popups:
|
|
|
|
|
try:
|
|
|
|
|
bui.containerwidget(edit=popup._root_widget, transition='out_scale')
|
|
|
|
|
except: pass
|
2025-08-05 06:27:53 +05:30
|
|
|
cls = type(self)
|
|
|
|
|
return bui.BasicMainWindowState(
|
|
|
|
|
create_call=lambda transition, origin_widget: cls(
|
|
|
|
|
transition=transition, origin_widget=origin_widget
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2025-04-08 16:32:09 +00:00
|
|
|
def spin(self, show=False):
|
2025-04-08 18:24:09 +02:00
|
|
|
w = self._loading_spinner
|
|
|
|
|
p = self._root_widget
|
2025-04-08 16:32:09 +00:00
|
|
|
bui.spinnerwidget(w, visible=show) if w.exists(
|
|
|
|
|
) and p.exists() and not p.transitioning_out else None
|
2025-04-08 18:24:09 +02:00
|
|
|
|
2022-08-21 01:57:34 +05:30
|
|
|
@contextlib.contextmanager
|
|
|
|
|
def exception_handler(self):
|
2022-08-07 02:05:48 +05:30
|
|
|
try:
|
2022-08-21 01:57:34 +05:30
|
|
|
yield
|
|
|
|
|
except urllib.error.URLError:
|
2025-04-08 18:24:09 +02:00
|
|
|
self.spin()
|
2025-01-13 17:04:48 +05:30
|
|
|
try:
|
|
|
|
|
bui.textwidget(
|
|
|
|
|
edit=self._plugin_manager_status_text,
|
|
|
|
|
text="Make sure you are connected\n to the Internet and try again."
|
|
|
|
|
)
|
2025-01-21 15:04:16 +00:00
|
|
|
except:
|
|
|
|
|
pass
|
2024-04-29 02:19:04 +05:30
|
|
|
self.plugin_manager._index_setup_in_progress = False
|
2022-08-07 16:09:38 +05:30
|
|
|
except RuntimeError:
|
2023-05-17 02:57:02 +05:30
|
|
|
# User probably went back before a bui.Window could finish loading.
|
2022-08-07 16:09:38 +05:30
|
|
|
pass
|
2023-06-16 00:18:04 +05:30
|
|
|
except Exception as e:
|
2025-04-08 18:24:09 +02:00
|
|
|
self.spin()
|
2025-01-13 17:04:48 +05:30
|
|
|
try:
|
|
|
|
|
bui.textwidget(edit=self._plugin_manager_status_text, text=str(e))
|
2025-01-21 15:04:16 +00:00
|
|
|
except:
|
|
|
|
|
pass
|
2023-06-16 00:18:04 +05:30
|
|
|
raise
|
2022-08-21 01:57:34 +05:30
|
|
|
|
|
|
|
|
async def draw_index(self):
|
|
|
|
|
self.draw_search_bar()
|
|
|
|
|
self.draw_plugins_scroll_bar()
|
|
|
|
|
self.draw_category_selection_button(post_label="All")
|
|
|
|
|
self.draw_refresh_icon()
|
|
|
|
|
self.draw_settings_icon()
|
|
|
|
|
with self.exception_handler():
|
2024-06-19 21:27:58 +05:30
|
|
|
await self.plugin_manager.setup_changelog()
|
2022-08-21 01:57:34 +05:30
|
|
|
await self.plugin_manager.setup_index()
|
2025-04-08 18:24:09 +02:00
|
|
|
self.spin()
|
2025-01-13 17:04:48 +05:30
|
|
|
try:
|
|
|
|
|
bui.textwidget(edit=self._plugin_manager_status_text, text="")
|
2025-01-21 15:04:16 +00:00
|
|
|
except:
|
|
|
|
|
pass
|
2022-08-21 01:57:34 +05:30
|
|
|
await self.select_category("All")
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-13 21:20:20 +05:30
|
|
|
def draw_plugins_scroll_bar(self):
|
2025-08-04 02:51:27 +05:30
|
|
|
scroll_size_x = (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)
|
2025-08-04 21:07:55 +05:30
|
|
|
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=2,
|
|
|
|
|
margin=0
|
|
|
|
|
)
|
2022-08-13 21:20:20 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
def draw_category_selection_button(self, post_label):
|
2025-08-04 02:51:27 +05:30
|
|
|
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)
|
2022-08-07 19:23:57 +05:30
|
|
|
b_size = (140, 30)
|
2022-08-29 18:41:17 +05:30
|
|
|
b_textcolor = (0.8, 0.8, 0.85)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2024-04-22 15:33:31 +05:30
|
|
|
if self.alphabet_order_selection_button is None:
|
2025-08-04 02:35:20 +05:30
|
|
|
self.alphabet_order_selection_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
size=(40, 30),
|
|
|
|
|
position=(category_pos_x - 47, category_pos_y),
|
|
|
|
|
label='Z - A' if self.selected_alphabet_order == 'z_a' else 'A - Z',
|
|
|
|
|
on_activate_call=lambda: loop.create_task(self._on_order_button_press()),
|
|
|
|
|
button_type="square",
|
|
|
|
|
textcolor=b_textcolor,
|
|
|
|
|
text_scale=0.6
|
|
|
|
|
)
|
2024-04-22 15:33:31 +05:30
|
|
|
else:
|
2025-04-08 18:24:09 +02:00
|
|
|
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
|
2024-04-22 15:33:31 +05:30
|
|
|
|
2022-08-10 01:02:10 +05:30
|
|
|
label = f"Category: {post_label}"
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-06 03:44:15 +05:30
|
|
|
if self.category_selection_button is None:
|
2025-08-04 02:35:20 +05:30
|
|
|
self.category_selection_button = b = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(category_pos_x, category_pos_y),
|
|
|
|
|
size=b_size,
|
|
|
|
|
label=label,
|
|
|
|
|
button_type="square",
|
|
|
|
|
textcolor=b_textcolor,
|
|
|
|
|
text_scale=0.6
|
|
|
|
|
)
|
2025-08-05 19:24:53 +00:00
|
|
|
bui.buttonwidget(
|
2025-08-06 17:55:51 +05:30
|
|
|
edit=b,
|
|
|
|
|
on_activate_call=lambda: self.show_categories_window(source=b)
|
|
|
|
|
)
|
2022-08-06 03:44:15 +05:30
|
|
|
else:
|
2025-04-08 18:24:09 +02:00
|
|
|
b = self.category_selection_button
|
|
|
|
|
bui.buttonwidget(
|
|
|
|
|
edit=b,
|
|
|
|
|
label=label
|
|
|
|
|
) if b.exists() else None
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2024-04-22 15:33:31 +05:30
|
|
|
async def _on_order_button_press(self) -> None:
|
|
|
|
|
self.selected_alphabet_order = ('a_z' if self.selected_alphabet_order == 'z_a' else 'z_a')
|
|
|
|
|
bui.buttonwidget(edit=self.alphabet_order_selection_button,
|
2024-04-22 10:03:53 +00:00
|
|
|
label=('Z - A' if self.selected_alphabet_order == 'z_a' else 'A - Z')
|
|
|
|
|
)
|
2024-04-22 15:33:31 +05:30
|
|
|
filter_text = bui.textwidget(parent=self._root_widget, query=self._filter_widget)
|
2024-04-29 20:16:25 +05:30
|
|
|
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
|
|
|
|
|
)
|
2024-04-22 15:33:31 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
def draw_search_bar(self):
|
2025-08-04 02:51:27 +05:30
|
|
|
search_bar_pos_x = (85 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
68 if _uiscale() is babase.UIScale.MEDIUM else 75)
|
2022-08-06 15:55:19 +05:30
|
|
|
search_bar_pos_y = self._height - (
|
2025-08-04 02:51:27 +05:30
|
|
|
145 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
110 if _uiscale() is babase.UIScale.MEDIUM else 116)
|
2022-08-07 19:23:57 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
search_bar_size_x = (320 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
230 if _uiscale() is babase.UIScale.MEDIUM else 260)
|
2022-08-07 23:44:30 +05:30
|
|
|
search_bar_size_y = (
|
2025-08-04 02:51:27 +05:30
|
|
|
35 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
35 if _uiscale() is babase.UIScale.MEDIUM else 45)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2025-08-04 02:51:27 +05:30
|
|
|
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)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
|
|
|
|
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',
|
2023-06-21 00:33:31 +05:30
|
|
|
color=bui.app.ui_v1.title_color,
|
2023-05-17 02:57:02 +05:30
|
|
|
scale=0.5)
|
|
|
|
|
|
|
|
|
|
filter_txt = babase.Lstr(resource='filterText')
|
2025-08-04 02:51:27 +05:30
|
|
|
search_bar_maxwidth = search_bar_size_x - (95 if _uiscale() is babase.UIScale.SMALL else
|
|
|
|
|
77 if _uiscale() is babase.UIScale.MEDIUM else
|
2022-08-29 18:41:17 +05:30
|
|
|
85)
|
2025-08-04 21:07:55 +05:30
|
|
|
self._filter_widget = bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
text="",
|
|
|
|
|
size=(search_bar_size_x, search_bar_size_y),
|
|
|
|
|
position=(search_bar_pos_x, search_bar_pos_y),
|
|
|
|
|
h_align='left',
|
|
|
|
|
v_align='center',
|
|
|
|
|
editable=True,
|
|
|
|
|
scale=0.8,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
maxwidth=search_bar_maxwidth,
|
|
|
|
|
description=filter_txt
|
|
|
|
|
)
|
2023-12-08 23:44:14 +05:30
|
|
|
self._last_filter_text = ""
|
2022-08-21 01:33:25 +05:30
|
|
|
self._last_filter_plugins = []
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-12-03 21:04:51 +05:30
|
|
|
loop.create_task(self.process_search_term())
|
2022-08-21 01:33:25 +05:30
|
|
|
|
2022-12-03 21:04:51 +05:30
|
|
|
async def process_search_term(self):
|
2022-08-21 01:33:25 +05:30
|
|
|
while True:
|
|
|
|
|
await asyncio.sleep(0.2)
|
2023-12-08 23:44:14 +05:30
|
|
|
if not self._filter_widget:
|
2022-08-21 01:33:25 +05:30
|
|
|
# Search filter widget got destroyed. No point checking for filter text anymore.
|
|
|
|
|
return
|
2023-12-08 23:44:14 +05:30
|
|
|
filter_text = bui.textwidget(parent=self._root_widget, query=self._filter_widget)
|
2022-08-21 01:33:25 +05:30
|
|
|
if self.selected_category is None:
|
|
|
|
|
continue
|
|
|
|
|
try:
|
2024-04-22 15:33:31 +05:30
|
|
|
await self.draw_plugin_names(
|
|
|
|
|
self.selected_category, search_term=filter_text.lower(), order=self.selected_alphabet_order)
|
2023-06-30 04:38:42 +05:30
|
|
|
except CategoryDoesNotExist:
|
2022-08-21 01:33:25 +05:30
|
|
|
pass
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
def draw_settings_icon(self):
|
2025-08-04 02:51:27 +05:30
|
|
|
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)
|
2025-08-04 21:07:55 +05:30
|
|
|
controller_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(settings_pos_x, settings_pos_y),
|
|
|
|
|
size=(30, 30),
|
|
|
|
|
button_type="square",
|
|
|
|
|
label=""
|
|
|
|
|
)
|
2025-04-05 15:43:51 +02:00
|
|
|
bui.buttonwidget(
|
|
|
|
|
controller_button,
|
|
|
|
|
on_activate_call=babase.Call(
|
|
|
|
|
PluginManagerSettingsWindow,
|
|
|
|
|
self.plugin_manager,
|
|
|
|
|
controller_button
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-08-04 21:07:55 +05:30
|
|
|
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
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-12 07:21:51 +05:30
|
|
|
def draw_refresh_icon(self):
|
2025-08-04 02:51:27 +05:30
|
|
|
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)
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2025-04-08 18:24:09 +02:00
|
|
|
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: loop.create_task(self.refresh())
|
|
|
|
|
)
|
|
|
|
|
bui.imagewidget(
|
|
|
|
|
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
|
|
|
|
|
)
|
2022-08-06 03:44:15 +05:30
|
|
|
|
2022-12-03 21:04:51 +05:30
|
|
|
def search_term_filterer(self, plugin, search_term):
|
2023-04-30 22:45:54 +05:30
|
|
|
# This helps resolve "plugin name" to "plugin_name".
|
2025-01-14 16:49:58 +05:30
|
|
|
if search_term in plugin.info["description"].lower():
|
|
|
|
|
return True
|
2023-04-30 22:45:54 +05:30
|
|
|
search_term = search_term.replace(" ", "_")
|
2022-12-03 21:04:51 +05:30
|
|
|
if search_term in plugin.name:
|
|
|
|
|
return True
|
|
|
|
|
for author in plugin.info["authors"]:
|
|
|
|
|
if search_term in author["name"].lower():
|
|
|
|
|
return True
|
|
|
|
|
return False
|
|
|
|
|
|
2022-08-21 01:33:25 +05:30
|
|
|
# XXX: Not sure if this is the best way to handle search filters.
|
2024-04-22 15:33:31 +05:30
|
|
|
async def draw_plugin_names(self, category, search_term="", refresh=False, order='a_z'):
|
2022-12-03 21:04:51 +05:30
|
|
|
# Re-draw plugin list UI if either search term or category was switched.
|
|
|
|
|
to_draw_plugin_names = (search_term, category) != (self._last_filter_text,
|
2022-12-03 15:37:23 +00:00
|
|
|
self.selected_category)
|
2024-04-20 03:15:58 +05:30
|
|
|
if not (to_draw_plugin_names or refresh):
|
2022-08-21 01:33:25 +05:30
|
|
|
return
|
|
|
|
|
|
2022-08-27 19:30:21 +05:30
|
|
|
try:
|
2024-04-29 20:31:32 +05:30
|
|
|
if self.plugin_manager.categories != {}:
|
2024-04-29 20:29:30 +05:30
|
|
|
if self.plugin_manager.categories['All'] is not None:
|
|
|
|
|
category_plugins = await self.plugin_manager.categories[category if category != 'Installed' else 'All'].get_plugins()
|
2024-04-30 09:23:50 +00:00
|
|
|
else:
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
return
|
2022-08-27 19:30:21 +05:30
|
|
|
except (KeyError, AttributeError):
|
2024-04-29 02:19:04 +05:30
|
|
|
no_internet_text = "Make sure you are connected\n to the Internet and try again."
|
|
|
|
|
if bui.textwidget(query=self._plugin_manager_status_text) != no_internet_text:
|
|
|
|
|
raise CategoryDoesNotExist(f"{category} does not exist.")
|
|
|
|
|
else:
|
|
|
|
|
return
|
2022-08-21 01:33:25 +05:30
|
|
|
|
2022-12-03 21:04:51 +05:30
|
|
|
if search_term:
|
2024-04-22 15:33:31 +05:30
|
|
|
plugins = list(filter(
|
2023-12-08 23:44:14 +05:30
|
|
|
lambda plugin: self.search_term_filterer(plugin, search_term),
|
|
|
|
|
category_plugins,
|
2024-04-22 15:33:31 +05:30
|
|
|
))
|
2022-08-21 01:33:25 +05:30
|
|
|
else:
|
|
|
|
|
plugins = category_plugins
|
|
|
|
|
|
2024-04-22 10:03:53 +00:00
|
|
|
def return_name(val):
|
|
|
|
|
return val.name
|
2024-04-22 15:33:31 +05:30
|
|
|
plugins.sort(key=return_name, reverse=(True if order == 'z_a' else False))
|
|
|
|
|
|
|
|
|
|
if plugins == self._last_filter_plugins and not refresh:
|
2022-12-03 21:04:51 +05:30
|
|
|
# Plugins names to draw on UI are already drawn.
|
2022-08-21 01:33:25 +05:30
|
|
|
return
|
|
|
|
|
|
2022-12-03 21:04:51 +05:30
|
|
|
self._last_filter_text = search_term
|
2022-08-21 01:33:25 +05:30
|
|
|
self._last_filter_plugins = plugins
|
|
|
|
|
|
2025-04-08 16:32:09 +00:00
|
|
|
if not self._columnwidget.exists():
|
|
|
|
|
return
|
2025-04-08 18:24:09 +02:00
|
|
|
|
2024-04-22 16:01:07 +05:30
|
|
|
if category == 'Installed':
|
2025-04-08 18:24:09 +02:00
|
|
|
plugin_names_to_draw = tuple(
|
2025-08-06 00:50:09 +05:30
|
|
|
plugin for plugin in plugins if plugin.is_installed
|
2025-04-08 18:24:09 +02:00
|
|
|
)
|
2024-04-22 16:01:07 +05:30
|
|
|
else:
|
2025-08-06 00:50:09 +05:30
|
|
|
plugin_names_to_draw = plugins
|
2022-08-21 01:33:25 +05:30
|
|
|
|
2025-04-08 18:24:09 +02:00
|
|
|
[plugin.delete() for plugin in self._columnwidget.get_children()]
|
2024-08-08 03:04:04 +05:30
|
|
|
text_widget = bui.textwidget(parent=self._columnwidget)
|
|
|
|
|
text_widget.delete()
|
2025-08-06 00:50:09 +05:30
|
|
|
# await asyncio.gather(*plugin_names_to_draw)
|
2022-08-06 00:18:35 +05:30
|
|
|
|
2025-08-06 00:50:09 +05:30
|
|
|
plugin_names_ready_to_draw = []
|
|
|
|
|
for plugin in plugin_names_to_draw:
|
2025-08-05 19:24:53 +00:00
|
|
|
try:
|
|
|
|
|
lcv = plugin.latest_compatible_version
|
|
|
|
|
except NoCompatibleVersion:
|
|
|
|
|
continue
|
2025-08-06 00:50:09 +05:30
|
|
|
plugin_names_ready_to_draw += [plugin]
|
|
|
|
|
|
|
|
|
|
for i, plugin in enumerate(plugin_names_ready_to_draw):
|
|
|
|
|
await self.draw_plugin_name(plugin, plugin_names_ready_to_draw)
|
|
|
|
|
|
|
|
|
|
async def draw_plugin_name(self, plugin, plugins_list):
|
2022-08-28 23:09:09 +05:30
|
|
|
|
2022-08-06 00:18:35 +05:30
|
|
|
if plugin.is_installed:
|
2022-08-08 03:36:49 +05:30
|
|
|
local_plugin = plugin.get_local()
|
2022-08-08 22:09:22 +05:30
|
|
|
if await local_plugin.is_enabled():
|
2022-08-06 00:18:35 +05:30
|
|
|
if not local_plugin.is_installed_via_plugin_manager:
|
|
|
|
|
color = (0.8, 0.2, 0.2)
|
2025-08-06 00:50:09 +05:30
|
|
|
elif local_plugin.version == plugin.latest_compatible_version.number:
|
2022-08-29 18:41:17 +05:30
|
|
|
color = (0, 0.95, 0.2)
|
2022-08-05 22:43:07 +05:30
|
|
|
else:
|
2022-08-06 00:18:35 +05:30
|
|
|
color = (1, 0.6, 0)
|
2022-08-05 22:43:07 +05:30
|
|
|
else:
|
2022-08-06 01:01:55 +05:30
|
|
|
color = (1, 1, 1)
|
2022-08-06 00:18:35 +05:30
|
|
|
else:
|
2022-08-06 01:01:55 +05:30
|
|
|
color = (0.5, 0.5, 0.5)
|
2022-08-06 00:18:35 +05:30
|
|
|
|
2022-08-21 01:33:25 +05:30
|
|
|
plugin_name_widget_to_update = self.plugins_in_current_view.get(plugin.name)
|
|
|
|
|
if plugin_name_widget_to_update:
|
2025-04-08 18:24:09 +02:00
|
|
|
bui.textwidget(
|
|
|
|
|
edit=plugin_name_widget_to_update,
|
|
|
|
|
color=color
|
|
|
|
|
)
|
2022-08-06 00:18:35 +05:30
|
|
|
else:
|
2025-04-08 18:24:09 +02:00
|
|
|
text_widget = bui.textwidget(
|
|
|
|
|
parent=self._columnwidget,
|
|
|
|
|
size=(410, 30),
|
|
|
|
|
selectable=True,
|
|
|
|
|
always_highlight=True,
|
|
|
|
|
color=color,
|
|
|
|
|
text=plugin.name.replace('_', ' ').title(),
|
|
|
|
|
click_activate=True,
|
2025-08-06 00:50:09 +05:30
|
|
|
on_activate_call=lambda: self.show_plugin_window(plugin, plugins_list),
|
2025-04-08 18:24:09 +02:00
|
|
|
h_align='left',
|
|
|
|
|
v_align='center',
|
|
|
|
|
maxwidth=420
|
|
|
|
|
)
|
2022-08-06 00:18:35 +05:30
|
|
|
self.plugins_in_current_view[plugin.name] = text_widget
|
2022-08-21 01:33:25 +05:30
|
|
|
# XXX: This seems nicer. Might wanna use this in future.
|
|
|
|
|
# text_widget.add_delete_callback(lambda: self.plugins_in_current_view.pop(plugin.name))
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-08-06 00:50:09 +05:30
|
|
|
def show_plugin_window(self, plugin, plugins_list):
|
|
|
|
|
PluginWindow(
|
|
|
|
|
plugin,
|
|
|
|
|
self._root_widget,
|
|
|
|
|
plugins_list=plugins_list,
|
|
|
|
|
button_callback=lambda: self.draw_plugin_name(plugin, plugins_list)
|
|
|
|
|
)
|
2022-08-11 06:34:17 +05:30
|
|
|
|
2025-04-08 16:32:09 +00:00
|
|
|
def show_categories_window(self, source):
|
2022-08-12 07:21:51 +05:30
|
|
|
PluginCategoryWindow(
|
2022-08-11 06:34:17 +05:30
|
|
|
self.plugin_manager.categories.keys(),
|
|
|
|
|
self.selected_category,
|
2025-04-05 15:43:51 +02:00
|
|
|
source,
|
2022-08-11 06:34:17 +05:30
|
|
|
self.select_category,
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
async def select_category(self, category):
|
2022-08-06 03:44:15 +05:30
|
|
|
self.plugins_in_current_view.clear()
|
2022-08-12 07:21:51 +05:30
|
|
|
self.draw_category_selection_button(post_label=category)
|
2024-04-22 15:33:31 +05:30
|
|
|
await self.draw_plugin_names(
|
|
|
|
|
category, search_term=self._last_filter_text, refresh=True, order=self.selected_alphabet_order)
|
2022-08-21 01:33:25 +05:30
|
|
|
self.selected_category = category
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2022-08-06 03:44:15 +05:30
|
|
|
def cleanup(self):
|
2022-08-10 01:02:10 +05:30
|
|
|
self.plugin_manager.cleanup()
|
2022-08-06 03:44:15 +05:30
|
|
|
for plugin in self._columnwidget.get_children():
|
|
|
|
|
plugin.delete()
|
|
|
|
|
self.plugins_in_current_view.clear()
|
2023-12-08 23:44:14 +05:30
|
|
|
self._last_filter_text = ""
|
2022-08-21 01:33:25 +05:30
|
|
|
self._last_filter_plugins = []
|
2022-08-06 03:44:15 +05:30
|
|
|
|
2022-08-21 01:33:25 +05:30
|
|
|
async def refresh(self):
|
|
|
|
|
self.cleanup()
|
2025-02-07 14:39:46 +03:00
|
|
|
# try:
|
|
|
|
|
# bui.textwidget(edit=self._plugin_manager_status_text, text="Refreshing")
|
|
|
|
|
# except:
|
|
|
|
|
# pass
|
|
|
|
|
|
2025-04-08 18:24:09 +02:00
|
|
|
self.spin(True)
|
2022-08-21 01:57:34 +05:30
|
|
|
|
|
|
|
|
with self.exception_handler():
|
|
|
|
|
await self.plugin_manager.refresh()
|
2024-04-29 02:19:04 +05:30
|
|
|
await self.plugin_manager.setup_changelog()
|
2022-08-21 01:57:34 +05:30
|
|
|
await self.plugin_manager.setup_index()
|
2025-04-08 18:24:09 +02:00
|
|
|
self.spin()
|
2025-01-13 17:04:48 +05:30
|
|
|
try:
|
|
|
|
|
bui.textwidget(edit=self._plugin_manager_status_text, text="")
|
2025-01-21 15:04:16 +00:00
|
|
|
except:
|
|
|
|
|
pass
|
2022-08-21 01:57:34 +05:30
|
|
|
await self.select_category(self.selected_category)
|
2022-08-06 03:44:15 +05:30
|
|
|
|
2022-08-06 00:18:35 +05:30
|
|
|
def soft_refresh(self):
|
|
|
|
|
pass
|
|
|
|
|
|
2022-08-07 17:12:45 +05:30
|
|
|
|
2022-08-07 16:00:14 +05:30
|
|
|
class PluginManagerSettingsWindow(popup.PopupWindow):
|
2022-08-14 07:57:03 +05:30
|
|
|
def __init__(self, plugin_manager, origin_widget):
|
|
|
|
|
self._plugin_manager = plugin_manager
|
2022-08-14 00:42:40 +05:30
|
|
|
self.scale_origin = origin_widget.get_screen_space_center()
|
2023-05-17 02:57:02 +05:30
|
|
|
self.settings = babase.app.config["Community Plugin Manager"]["Settings"].copy()
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-14 00:42:40 +05:30
|
|
|
loop.create_task(self.draw_ui())
|
|
|
|
|
|
|
|
|
|
async def draw_ui(self):
|
2022-08-29 18:41:17 +05:30
|
|
|
b_text_color = (0.8, 0.8, 0.85)
|
2025-08-04 02:51:27 +05:30
|
|
|
s = 1.25 if _uiscale() is babase.UIScale.SMALL else 1.27 if _uiscale() is babase.UIScale.MEDIUM else 1.3
|
2022-08-14 07:57:03 +05:30
|
|
|
width = 380 * s
|
|
|
|
|
height = 150 + 150 * s
|
|
|
|
|
color = (0.9, 0.9, 0.9)
|
2022-12-05 20:55:07 +05:30
|
|
|
|
|
|
|
|
# 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)
|
2022-08-07 16:00:14 +05:30
|
|
|
text_scale = 0.7 * s
|
|
|
|
|
self._transition_out = 'out_scale'
|
|
|
|
|
transition = 'in_scale'
|
2022-12-05 13:16:01 +05:30
|
|
|
button_size = (32 * s, 32 * s)
|
2022-08-21 01:57:34 +05:30
|
|
|
# index = await self._plugin_manager.get_index()
|
2025-04-08 18:31:48 +02:00
|
|
|
self._root_widget = bui.containerwidget(
|
|
|
|
|
size=(width, height),
|
|
|
|
|
on_outside_click_call=self._ok,
|
|
|
|
|
transition=transition,
|
2025-08-04 02:51:27 +05:30
|
|
|
scale=(2.1 if _uiscale() is babase.UIScale.SMALL else 1.5
|
|
|
|
|
if _uiscale() is babase.UIScale.MEDIUM else 1.0),
|
2025-04-08 18:31:48 +02:00
|
|
|
scale_origin_stack_offset=self.scale_origin
|
|
|
|
|
)
|
2025-08-06 17:55:51 +05:30
|
|
|
_add_popup(self)
|
2022-08-07 16:00:14 +05:30
|
|
|
pos = height * 0.9
|
|
|
|
|
setting_title = "Settings"
|
2025-04-08 18:31:48 +02:00
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, pos),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=setting_title,
|
|
|
|
|
scale=text_scale,
|
|
|
|
|
color=bui.app.ui_v1.title_color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2022-08-15 20:11:08 +05:30
|
|
|
|
|
|
|
|
pos -= 20
|
2025-08-04 02:35:20 +05:30
|
|
|
self._changelog_button = b = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=((width * 0.2) - button_size[0] / 2 - 5, pos),
|
|
|
|
|
size=(80, 30),
|
|
|
|
|
textcolor=b_text_color,
|
|
|
|
|
button_type='square',
|
|
|
|
|
label=''
|
|
|
|
|
)
|
2025-04-08 16:32:09 +00:00
|
|
|
bui.buttonwidget(b, on_activate_call=lambda: ChangelogWindow(b))
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=((width * 0.2) - button_size[0] / 2, pos),
|
|
|
|
|
size=(70, 30),
|
|
|
|
|
scale=0.6,
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text='ChangeLog',
|
|
|
|
|
color=b_text_color,
|
|
|
|
|
draw_controller=self._changelog_button,
|
|
|
|
|
)
|
|
|
|
|
self._save_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=((width * 0.82) - button_size[0] / 2, pos),
|
|
|
|
|
size=(73, 35),
|
|
|
|
|
on_activate_call=self.save_settings_button,
|
|
|
|
|
textcolor=b_text_color,
|
|
|
|
|
button_type='square',
|
|
|
|
|
text_scale=1,
|
|
|
|
|
scale=0,
|
|
|
|
|
selectable=False,
|
|
|
|
|
label="Save"
|
|
|
|
|
)
|
2022-08-15 20:11:08 +05:30
|
|
|
pos -= 40
|
|
|
|
|
|
|
|
|
|
for setting, value in self.settings.items():
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.checkboxwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.1, pos),
|
|
|
|
|
size=(170, 30),
|
|
|
|
|
text=setting,
|
|
|
|
|
value=value,
|
|
|
|
|
on_value_change_call=babase.Call(self.toggle_setting, setting),
|
|
|
|
|
maxwidth=500,
|
|
|
|
|
textcolor=(0.9, 0.9, 0.9),
|
|
|
|
|
scale=text_scale * 0.8
|
|
|
|
|
)
|
2022-08-29 18:41:17 +05:30
|
|
|
pos -= 34 * text_scale
|
2022-08-15 20:11:08 +05:30
|
|
|
|
2022-10-03 20:20:33 +05:30
|
|
|
pos = height - 200
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
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
|
|
|
|
|
)
|
2022-08-07 16:00:14 +05:30
|
|
|
|
2022-10-03 20:20:33 +05:30
|
|
|
pos -= 75
|
2024-08-08 01:49:31 +05:30
|
|
|
try:
|
|
|
|
|
plugin_manager_update_available = await self._plugin_manager.get_update_details()
|
|
|
|
|
except urllib.error.URLError:
|
|
|
|
|
plugin_manager_update_available = False
|
|
|
|
|
discord_width = (width * 0.20) if plugin_manager_update_available else (width * 0.31)
|
2025-08-04 02:35:20 +05:30
|
|
|
self.discord_button = bui.buttonwidget(
|
|
|
|
|
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=""
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
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
|
|
|
|
|
)
|
2023-05-17 02:57:02 +05:30
|
|
|
|
2024-08-08 01:49:31 +05:30
|
|
|
github_width = (width * 0.49) if plugin_manager_update_available else (width * 0.65)
|
2025-08-04 02:35:20 +05:30
|
|
|
self.github_button = bui.buttonwidget(
|
|
|
|
|
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=''
|
|
|
|
|
)
|
2022-12-05 13:16:01 +05:30
|
|
|
|
2025-08-04 02:35:20 +05:30
|
|
|
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
|
|
|
|
|
)
|
2022-10-03 20:20:33 +05:30
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, on_cancel_call=self._ok)
|
2022-08-14 07:57:03 +05:30
|
|
|
|
2022-08-21 01:57:34 +05:30
|
|
|
try:
|
|
|
|
|
plugin_manager_update_available = await self._plugin_manager.get_update_details()
|
|
|
|
|
except urllib.error.URLError:
|
|
|
|
|
plugin_manager_update_available = False
|
2022-08-14 07:57:03 +05:30
|
|
|
if plugin_manager_update_available:
|
|
|
|
|
text_color = (0.75, 0.2, 0.2)
|
|
|
|
|
button_size = (95 * s, 32 * s)
|
2022-08-24 15:44:54 +05:30
|
|
|
update_button_label = f'Update to v{plugin_manager_update_available[0]}'
|
2025-08-04 02:35:20 +05:30
|
|
|
self._update_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=((width * 0.77) - button_size[0] / 2, pos),
|
|
|
|
|
size=button_size,
|
2025-08-05 19:24:53 +00:00
|
|
|
on_activate_call=lambda: loop.create_task(
|
|
|
|
|
self.update(*plugin_manager_update_available)),
|
2025-08-04 02:35:20 +05:30
|
|
|
textcolor=b_text_color,
|
|
|
|
|
button_type='square',
|
|
|
|
|
text_scale=1,
|
|
|
|
|
color=(0, 0.7, 0),
|
|
|
|
|
label=update_button_label
|
|
|
|
|
)
|
|
|
|
|
self._restart_to_reload_changes_text = bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.79, pos + 20),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text='',
|
|
|
|
|
scale=text_scale * 0.65,
|
|
|
|
|
color=(0, 0.8, 0),
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2022-08-14 07:57:03 +05:30
|
|
|
else:
|
|
|
|
|
text_color = (0, 0.8, 0)
|
|
|
|
|
pos -= 25
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, pos),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=f'Plugin Manager v{PLUGIN_MANAGER_VERSION}',
|
|
|
|
|
scale=text_scale * 0.8,
|
|
|
|
|
color=text_color,
|
|
|
|
|
maxwidth=width * 0.9
|
|
|
|
|
)
|
2022-10-03 20:20:33 +05:30
|
|
|
pos -= 25
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(width * 0.49, pos),
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
text=f'API Version: {_app_api_version}',
|
|
|
|
|
scale=text_scale * 0.7,
|
|
|
|
|
color=(0.4, 0.8, 1),
|
|
|
|
|
maxwidth=width * 0.95
|
|
|
|
|
)
|
2022-08-14 07:57:03 +05:30
|
|
|
|
|
|
|
|
pos = height * 0.1
|
|
|
|
|
|
2022-08-15 20:11:08 +05:30
|
|
|
def toggle_setting(self, setting, set_value):
|
|
|
|
|
self.settings[setting] = set_value
|
2025-01-14 16:49:58 +05:30
|
|
|
check = self.settings == babase.app.config["Community Plugin Manager"]["Settings"]
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.buttonwidget(
|
|
|
|
|
edit=self._save_button,
|
|
|
|
|
scale=0 if check else 1,
|
|
|
|
|
selectable=(not check)
|
|
|
|
|
)
|
2022-08-15 20:11:08 +05:30
|
|
|
|
|
|
|
|
def save_settings_button(self):
|
2023-05-17 02:57:02 +05:30
|
|
|
babase.app.config["Community Plugin Manager"]["Settings"] = self.settings.copy()
|
|
|
|
|
babase.app.config.commit()
|
2022-08-15 20:11:08 +05:30
|
|
|
self._ok()
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('gunCocking').play()
|
2022-08-15 20:11:08 +05:30
|
|
|
|
2022-08-15 21:01:48 +05:30
|
|
|
async def update(self, to_version=None, commit_sha=None):
|
2022-08-21 04:50:29 +05:30
|
|
|
try:
|
|
|
|
|
await self._plugin_manager.update(to_version, commit_sha)
|
2024-04-23 02:31:25 +05:30
|
|
|
await self._plugin_manager.setup_changelog()
|
2023-06-30 04:38:42 +05:30
|
|
|
except MD5CheckSumFailed:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage("MD5 checksum failed during plugin manager update", color=(1, 0, 0))
|
|
|
|
|
bui.getsound('error').play()
|
2022-08-21 04:50:29 +05:30
|
|
|
else:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.screenmessage("Plugin manager update successful", color=(0, 1, 0))
|
|
|
|
|
bui.getsound('shieldUp').play()
|
2025-08-04 02:35:20 +05:30
|
|
|
bui.textwidget(
|
|
|
|
|
edit=self._restart_to_reload_changes_text,
|
|
|
|
|
text='Update Applied!\nRestart game to reload changes.'
|
|
|
|
|
)
|
2022-08-21 04:50:29 +05:30
|
|
|
self._update_button.delete()
|
2022-08-07 16:00:14 +05:30
|
|
|
|
2022-08-15 20:11:08 +05:30
|
|
|
def _ok(self) -> None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.getsound('swish').play()
|
2025-08-06 17:55:51 +05:30
|
|
|
_remove_popup(self)
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, transition='out_scale')
|
2022-08-07 16:00:14 +05:30
|
|
|
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
class NewAllSettingsWindow(AllSettingsWindow):
|
2023-05-17 02:57:02 +05:30
|
|
|
"""Window for selecting a settings category."""
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
2025-01-13 17:04:48 +05:30
|
|
|
transition: str | None = 'in_right',
|
2025-04-08 22:29:14 +05:30
|
|
|
origin_widget: bui.Widget | None = None,
|
2023-05-17 02:57:02 +05:30
|
|
|
):
|
2022-07-03 14:32:05 +05:30
|
|
|
# pylint: disable=too-many-statements
|
|
|
|
|
# pylint: disable=too-many-locals
|
2023-05-17 02:57:02 +05:30
|
|
|
assert bui.app.classic is not None
|
2025-01-13 17:04:48 +05:30
|
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
|
|
|
width = 1000 if uiscale is bui.UIScale.SMALL else 800
|
|
|
|
|
x_inset = 125 if uiscale is bui.UIScale.SMALL else 105
|
|
|
|
|
height = 490
|
|
|
|
|
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
|
2025-01-14 16:49:58 +05:30
|
|
|
self._plugman_button = None
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
super().__init__(transition, origin_widget)
|
|
|
|
|
|
|
|
|
|
for child in self._root_widget.get_children():
|
|
|
|
|
child.delete()
|
2025-01-14 21:49:07 +00:00
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
bui.containerwidget(
|
|
|
|
|
edit=self._root_widget, size=(width, height + top_extra)
|
2023-05-17 02:57:02 +05:30
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-01-13 17:04:48 +05:30
|
|
|
if uiscale is bui.UIScale.SMALL:
|
2022-07-03 14:32:05 +05:30
|
|
|
self._back_button = None
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(
|
2025-01-13 17:04:48 +05:30
|
|
|
edit=self._root_widget, on_cancel_call=self.main_window_back
|
2023-05-17 02:57:02 +05:30
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
else:
|
2023-05-17 02:57:02 +05:30
|
|
|
self._back_button = btn = bui.buttonwidget(
|
2022-07-03 14:32:05 +05:30
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
2025-01-13 17:04:48 +05:30
|
|
|
position=(x_inset - 20, height - 85),
|
2022-07-03 14:32:05 +05:30
|
|
|
size=(130, 60),
|
|
|
|
|
scale=0.8,
|
|
|
|
|
text_scale=1.2,
|
2023-05-17 02:57:02 +05:30
|
|
|
label=bui.Lstr(resource='backText'),
|
|
|
|
|
button_type='back',
|
2025-01-13 17:04:48 +05:30
|
|
|
on_activate_call=self.main_window_back,
|
2023-05-17 02:57:02 +05:30
|
|
|
)
|
|
|
|
|
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
|
|
|
|
|
|
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
2025-01-13 17:04:48 +05:30
|
|
|
position=(0, height - 80),
|
2023-05-17 02:57:02 +05:30
|
|
|
size=(width, 25),
|
2025-01-13 17:04:48 +05:30
|
|
|
text=bui.Lstr(resource=f'{self._r}.titleText'),
|
2023-06-21 00:33:31 +05:30
|
|
|
color=bui.app.ui_v1.title_color,
|
2023-05-17 02:57:02 +05:30
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
maxwidth=130,
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
if self._back_button is not None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.buttonwidget(
|
|
|
|
|
edit=self._back_button,
|
|
|
|
|
button_type='backSmall',
|
|
|
|
|
size=(60, 60),
|
|
|
|
|
label=bui.charstr(bui.SpecialChar.BACK),
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
v = height - 265
|
2025-01-13 17:04:48 +05:30
|
|
|
basew = 280 if uiscale is bui.UIScale.SMALL else 230
|
|
|
|
|
baseh = 170
|
2023-05-17 02:57:02 +05:30
|
|
|
x_offs = (
|
2025-01-13 17:04:48 +05:30
|
|
|
x_inset + (105 if uiscale is bui.UIScale.SMALL else 72) - basew
|
2023-05-17 02:57:02 +05:30
|
|
|
) # now unused
|
2025-01-13 17:04:48 +05:30
|
|
|
x_dif = (basew - 7) / 2
|
2022-07-03 14:32:05 +05:30
|
|
|
x_offs2 = x_offs + basew - 7
|
|
|
|
|
x_offs3 = x_offs + 2 * (basew - 7)
|
|
|
|
|
x_offs4 = x_offs + 3 * (basew - 7)
|
2025-01-13 17:04:48 +05:30
|
|
|
x_offs5 = x_offs2
|
|
|
|
|
x_offs6 = x_offs3
|
|
|
|
|
x_offs2 -= x_dif
|
|
|
|
|
x_offs3 -= x_dif
|
|
|
|
|
x_offs4 -= x_dif
|
2022-07-03 14:32:05 +05:30
|
|
|
|
2023-05-17 02:57:02 +05:30
|
|
|
def _b_title(
|
2025-04-08 22:29:14 +05:30
|
|
|
x: float, y: float, button: bui.Widget, text: str | bui.Lstr
|
2023-05-17 02:57:02 +05:30
|
|
|
) -> None:
|
|
|
|
|
bui.textwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
text=text,
|
|
|
|
|
position=(x + basew * 0.47, y + baseh * 0.22),
|
|
|
|
|
maxwidth=basew * 0.7,
|
|
|
|
|
size=(0, 0),
|
|
|
|
|
h_align='center',
|
|
|
|
|
v_align='center',
|
|
|
|
|
draw_controller=button,
|
|
|
|
|
color=(0.7, 0.9, 0.7, 1.0),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
ctb = self._controllers_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(x_offs2, v),
|
|
|
|
|
size=(basew, baseh),
|
|
|
|
|
button_type='square',
|
|
|
|
|
label='',
|
|
|
|
|
on_activate_call=self._do_controllers,
|
|
|
|
|
)
|
2025-01-13 17:04:48 +05:30
|
|
|
if self._back_button is None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bbtn = bui.get_special_widget('back_button')
|
|
|
|
|
bui.widget(edit=ctb, left_widget=bbtn)
|
|
|
|
|
_b_title(
|
2025-01-13 17:04:48 +05:30
|
|
|
x_offs2, v, ctb, bui.Lstr(resource=f'{self._r}.controllersText')
|
2023-05-17 02:57:02 +05:30
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
imgw = imgh = 130
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35),
|
|
|
|
|
size=(imgw, imgh),
|
|
|
|
|
texture=bui.gettexture('controllerIcon'),
|
|
|
|
|
draw_controller=ctb,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
gfxb = self._graphics_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(x_offs3, v),
|
|
|
|
|
size=(basew, baseh),
|
|
|
|
|
button_type='square',
|
|
|
|
|
label='',
|
|
|
|
|
on_activate_call=self._do_graphics,
|
|
|
|
|
)
|
2025-01-13 17:04:48 +05:30
|
|
|
pbtn = bui.get_special_widget('squad_button')
|
|
|
|
|
bui.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
|
|
|
|
|
_b_title(x_offs3, v, gfxb, bui.Lstr(resource=f'{self._r}.graphicsText'))
|
2022-07-03 14:32:05 +05:30
|
|
|
imgw = imgh = 110
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42),
|
|
|
|
|
size=(imgw, imgh),
|
|
|
|
|
texture=bui.gettexture('graphicsIcon'),
|
|
|
|
|
draw_controller=gfxb,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
abtn = self._audio_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(x_offs4, v),
|
|
|
|
|
size=(basew, baseh),
|
|
|
|
|
button_type='square',
|
|
|
|
|
label='',
|
|
|
|
|
on_activate_call=self._do_audio,
|
|
|
|
|
)
|
2025-01-13 17:04:48 +05:30
|
|
|
_b_title(x_offs4, v, abtn, bui.Lstr(resource=f'{self._r}.audioText'))
|
2022-07-03 14:32:05 +05:30
|
|
|
imgw = imgh = 120
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
|
|
|
|
|
size=(imgw, imgh),
|
|
|
|
|
color=(1, 1, 0),
|
|
|
|
|
texture=bui.gettexture('audioIcon'),
|
|
|
|
|
draw_controller=abtn,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
v -= baseh - 5
|
|
|
|
|
|
|
|
|
|
avb = self._advanced_button = bui.buttonwidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(x_offs5, v),
|
|
|
|
|
size=(basew, baseh),
|
|
|
|
|
button_type='square',
|
|
|
|
|
label='',
|
|
|
|
|
on_activate_call=self._do_advanced,
|
|
|
|
|
)
|
2025-01-13 17:04:48 +05:30
|
|
|
_b_title(x_offs5, v, avb, bui.Lstr(resource=f'{self._r}.advancedText'))
|
2022-07-03 14:32:05 +05:30
|
|
|
imgw = imgh = 120
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
|
|
|
|
|
size=(imgw, imgh),
|
|
|
|
|
color=(0.8, 0.95, 1),
|
|
|
|
|
texture=bui.gettexture('advancedIcon'),
|
|
|
|
|
draw_controller=avb,
|
|
|
|
|
)
|
|
|
|
|
|
2025-01-14 16:49:58 +05:30
|
|
|
self._plugman_button = pmb = bui.buttonwidget(
|
2025-01-13 17:04:48 +05:30
|
|
|
parent=self._root_widget,
|
|
|
|
|
autoselect=True,
|
|
|
|
|
position=(x_offs6, v),
|
|
|
|
|
size=(basew, baseh),
|
|
|
|
|
button_type='square',
|
|
|
|
|
label='',
|
|
|
|
|
on_activate_call=self._do_plugman,
|
|
|
|
|
)
|
|
|
|
|
_b_title(x_offs6, v, pmb, bui.Lstr(value="Plugin Manager"))
|
|
|
|
|
imgw = imgh = 120
|
|
|
|
|
bui.imagewidget(
|
|
|
|
|
parent=self._root_widget,
|
|
|
|
|
position=(x_offs6 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
|
|
|
|
|
size=(imgw, imgh),
|
|
|
|
|
color=(0.8, 0.95, 1),
|
|
|
|
|
texture=bui.gettexture('storeIcon'),
|
|
|
|
|
draw_controller=pmb,
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
self._restore_state()
|
|
|
|
|
|
2025-01-13 17:04:48 +05:30
|
|
|
def _do_plugman(self) -> None:
|
|
|
|
|
# no-op if we're not in control.
|
|
|
|
|
if not self.main_window_has_control():
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self.main_window_replace(
|
|
|
|
|
PluginManagerWindow(origin_widget=self._plugman_button)
|
|
|
|
|
)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
def _save_state(self) -> None:
|
|
|
|
|
try:
|
|
|
|
|
sel = self._root_widget.get_selected_child()
|
|
|
|
|
if sel == self._controllers_button:
|
2023-05-17 02:57:02 +05:30
|
|
|
sel_name = 'Controllers'
|
2022-07-03 14:32:05 +05:30
|
|
|
elif sel == self._graphics_button:
|
2023-05-17 02:57:02 +05:30
|
|
|
sel_name = 'Graphics'
|
2022-07-03 14:32:05 +05:30
|
|
|
elif sel == self._audio_button:
|
2023-05-17 02:57:02 +05:30
|
|
|
sel_name = 'Audio'
|
2022-07-03 14:32:05 +05:30
|
|
|
elif sel == self._advanced_button:
|
2023-05-17 02:57:02 +05:30
|
|
|
sel_name = 'Advanced'
|
2025-01-13 17:04:48 +05:30
|
|
|
elif sel == self._plugman_button:
|
|
|
|
|
sel_name = 'PlugMan'
|
2022-07-03 14:32:05 +05:30
|
|
|
elif sel == self._back_button:
|
2023-05-17 02:57:02 +05:30
|
|
|
sel_name = 'Back'
|
2022-07-03 14:32:05 +05:30
|
|
|
else:
|
2023-05-17 02:57:02 +05:30
|
|
|
raise ValueError(f'unrecognized selection \'{sel}\'')
|
|
|
|
|
assert bui.app.classic is not None
|
2025-01-13 17:04:48 +05:30
|
|
|
bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
|
2022-07-03 14:32:05 +05:30
|
|
|
except Exception:
|
2023-05-17 02:57:02 +05:30
|
|
|
logging.exception('Error saving state for %s.', self)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
def _restore_state(self) -> None:
|
|
|
|
|
try:
|
2023-05-17 02:57:02 +05:30
|
|
|
assert bui.app.classic is not None
|
2023-06-21 00:33:31 +05:30
|
|
|
sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
|
2023-05-17 02:57:02 +05:30
|
|
|
'sel_name'
|
|
|
|
|
)
|
2025-04-08 22:29:14 +05:30
|
|
|
sel: bui.Widget | None
|
2023-05-17 02:57:02 +05:30
|
|
|
if sel_name == 'Controllers':
|
2022-07-03 14:32:05 +05:30
|
|
|
sel = self._controllers_button
|
2023-05-17 02:57:02 +05:30
|
|
|
elif sel_name == 'Graphics':
|
2022-07-03 14:32:05 +05:30
|
|
|
sel = self._graphics_button
|
2023-05-17 02:57:02 +05:30
|
|
|
elif sel_name == 'Audio':
|
2022-07-03 14:32:05 +05:30
|
|
|
sel = self._audio_button
|
2023-05-17 02:57:02 +05:30
|
|
|
elif sel_name == 'Advanced':
|
2022-07-03 14:32:05 +05:30
|
|
|
sel = self._advanced_button
|
2025-01-13 17:04:48 +05:30
|
|
|
elif sel_name == "PlugMan":
|
|
|
|
|
sel = self._plugman_button
|
2023-05-17 02:57:02 +05:30
|
|
|
elif sel_name == 'Back':
|
2022-07-03 14:32:05 +05:30
|
|
|
sel = self._back_button
|
|
|
|
|
else:
|
|
|
|
|
sel = self._controllers_button
|
|
|
|
|
if sel is not None:
|
2023-05-17 02:57:02 +05:30
|
|
|
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
2022-07-03 14:32:05 +05:30
|
|
|
except Exception:
|
2023-05-17 02:57:02 +05:30
|
|
|
logging.exception('Error restoring state for %s.', self)
|
2022-07-03 14:32:05 +05:30
|
|
|
|
|
|
|
|
|
2023-05-17 02:57:02 +05:30
|
|
|
# ba_meta export babase.Plugin
|
|
|
|
|
class EntryPoint(babase.Plugin):
|
2022-07-21 19:12:31 +05:30
|
|
|
def on_app_running(self) -> None:
|
2022-07-03 14:32:05 +05:30
|
|
|
"""Called when the app is being launched."""
|
2023-06-08 21:24:02 +05:30
|
|
|
from bauiv1lib.settings import allsettings
|
2022-07-03 14:32:05 +05:30
|
|
|
allsettings.AllSettingsWindow = NewAllSettingsWindow
|
2022-12-17 20:10:00 +05:30
|
|
|
DNSBlockWorkaround.apply()
|
2023-05-17 02:57:02 +05:30
|
|
|
asyncio.set_event_loop(babase._asyncio._asyncio_event_loop)
|
2022-08-15 21:01:48 +05:30
|
|
|
startup_tasks = StartupTasks()
|
2023-10-03 08:20:09 +00:00
|
|
|
|
2022-08-15 21:01:48 +05:30
|
|
|
loop.create_task(startup_tasks.execute())
|