mirror of
https://github.com/MoreTore/openpilot.git
synced 2026-08-05 08:16:06 +08:00
raylib: networking parity with QT (#36197)
* match style * all this was not naught * cool can do this * fix toggle callback - also not for naught * always process callbacks * toggle stuff * cleaner * tethering password * clean up * todos for later * this is fineee * add metered options * wifi metered button * add hidden network buutton and fix instant modal to modal * damped filter * Revert "damped filter" This reverts commit f9f98d5d708fb15cf1ebef4bdace577f0e347658. * fix metered toggle when disconnected * fix tethering enabled * ohh * fix keyboard title * disable edit button temp * move here * proper disable * clean up * more * move for loop into enqueue function * flippy * got more :( * todo * clean up * mypy * rename * todo * rename * again * again * format
This commit is contained in:
@@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.network import WifiManagerUI
|
||||
from openpilot.system.ui.widgets.network import NetworkUI
|
||||
|
||||
# Settings close button
|
||||
SETTINGS_CLOSE_TEXT = "×"
|
||||
@@ -59,7 +59,7 @@ class SettingsLayout(Widget):
|
||||
|
||||
self._panels = {
|
||||
PanelType.DEVICE: PanelInfo("Device", DeviceLayout()),
|
||||
PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager)),
|
||||
PanelType.NETWORK: PanelInfo("Network", NetworkUI(wifi_manager)),
|
||||
PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout()),
|
||||
PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout()),
|
||||
PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout()),
|
||||
|
||||
@@ -189,11 +189,11 @@ class Sidebar(Widget):
|
||||
# Draw colored left edge (clipped rounded rectangle)
|
||||
edge_rect = rl.Rectangle(metric_rect.x + 4, metric_rect.y + 4, 100, 118)
|
||||
rl.begin_scissor_mode(int(metric_rect.x + 4), int(metric_rect.y), 18, int(metric_rect.height))
|
||||
rl.draw_rectangle_rounded(edge_rect, 0.18, 10, metric.color)
|
||||
rl.draw_rectangle_rounded(edge_rect, 0.3, 10, metric.color)
|
||||
rl.end_scissor_mode()
|
||||
|
||||
# Draw border
|
||||
rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.15, 10, 2, Colors.METRIC_BORDER)
|
||||
rl.draw_rectangle_rounded_lines_ex(metric_rect, 0.3, 10, 2, Colors.METRIC_BORDER)
|
||||
|
||||
# Draw label and value
|
||||
labels = [metric.label, metric.value]
|
||||
|
||||
@@ -269,11 +269,11 @@ class GuiApplication:
|
||||
raise Exception
|
||||
|
||||
if result >= 0:
|
||||
# Execute callback with the result and clear the overlay
|
||||
if self._modal_overlay.callback is not None:
|
||||
self._modal_overlay.callback(result)
|
||||
|
||||
# Clear the overlay and execute the callback
|
||||
original_modal = self._modal_overlay
|
||||
self._modal_overlay = ModalOverlay()
|
||||
if original_modal.callback is not None:
|
||||
original_modal.callback(result)
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
@@ -21,9 +21,11 @@ NM_ACCESS_POINT_IFACE = 'org.freedesktop.NetworkManager.AccessPoint'
|
||||
NM_SETTINGS_PATH = '/org/freedesktop/NetworkManager/Settings'
|
||||
NM_SETTINGS_IFACE = 'org.freedesktop.NetworkManager.Settings'
|
||||
NM_CONNECTION_IFACE = 'org.freedesktop.NetworkManager.Settings.Connection'
|
||||
NM_ACTIVE_CONNECTION_IFACE = 'org.freedesktop.NetworkManager.Connection.Active'
|
||||
NM_WIRELESS_IFACE = 'org.freedesktop.NetworkManager.Device.Wireless'
|
||||
NM_PROPERTIES_IFACE = 'org.freedesktop.DBus.Properties'
|
||||
NM_DEVICE_IFACE = "org.freedesktop.NetworkManager.Device"
|
||||
NM_DEVICE_IFACE = 'org.freedesktop.NetworkManager.Device'
|
||||
NM_IP4_CONFIG_IFACE = 'org.freedesktop.NetworkManager.IP4Config'
|
||||
|
||||
NM_DEVICE_TYPE_WIFI = 2
|
||||
NM_DEVICE_TYPE_MODEM = 8
|
||||
|
||||
+233
-34
@@ -15,6 +15,7 @@ from jeepney.low_level import MessageType
|
||||
from jeepney.wrappers import Properties
|
||||
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_802_11_AP_SEC_PAIR_WEP40,
|
||||
NM_802_11_AP_SEC_PAIR_WEP104, NM_802_11_AP_SEC_GROUP_WEP40,
|
||||
NM_802_11_AP_SEC_GROUP_WEP104, NM_802_11_AP_SEC_KEY_MGMT_PSK,
|
||||
@@ -23,8 +24,8 @@ from openpilot.system.ui.lib.networkmanager import (NM, NM_WIRELESS_IFACE, NM_80
|
||||
NM_PATH, NM_IFACE, NM_ACCESS_POINT_IFACE, NM_SETTINGS_PATH,
|
||||
NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE,
|
||||
NM_DEVICE_TYPE_WIFI, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT,
|
||||
NM_DEVICE_STATE_REASON_NEW_ACTIVATION,
|
||||
NMDeviceState)
|
||||
NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE,
|
||||
NM_IP4_CONFIG_IFACE, NMDeviceState)
|
||||
|
||||
TETHERING_IP_ADDRESS = "192.168.43.1"
|
||||
DEFAULT_TETHERING_PASSWORD = "swagswagcomma"
|
||||
@@ -40,6 +41,12 @@ class SecurityType(IntEnum):
|
||||
UNSUPPORTED = 4
|
||||
|
||||
|
||||
class MeteredType(IntEnum):
|
||||
UNKNOWN = 0
|
||||
YES = 1
|
||||
NO = 2
|
||||
|
||||
|
||||
def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityType:
|
||||
wpa_props = wpa_flags | rsn_flags
|
||||
|
||||
@@ -114,7 +121,7 @@ class AccessPoint:
|
||||
|
||||
class WifiManager:
|
||||
def __init__(self):
|
||||
self._networks = [] # a network can be comprised of multiple APs
|
||||
self._networks: list[Network] = [] # a network can be comprised of multiple APs
|
||||
self._active = True # used to not run when not in settings
|
||||
self._exit = False
|
||||
|
||||
@@ -132,15 +139,23 @@ class WifiManager:
|
||||
|
||||
# State
|
||||
self._connecting_to_ssid: str = ""
|
||||
self._ipv4_address: str = ""
|
||||
self._current_network_metered: MeteredType = MeteredType.UNKNOWN
|
||||
self._tethering_password: str = ""
|
||||
self._last_network_update: float = 0.0
|
||||
self._callback_queue: list[Callable] = []
|
||||
|
||||
self._tethering_ssid = "weedle"
|
||||
dongle_id = Params().get("DongleId")
|
||||
if dongle_id:
|
||||
self._tethering_ssid += "-" + dongle_id[:4]
|
||||
|
||||
# Callbacks
|
||||
self._need_auth: Callable[[str], None] | None = None
|
||||
self._activated: Callable[[], None] | None = None
|
||||
self._forgotten: Callable[[], None] | None = None
|
||||
self._networks_updated: Callable[[list[Network]], None] | None = None
|
||||
self._disconnected: Callable[[], None] | None = None
|
||||
self._need_auth: list[Callable[[str], None]] = []
|
||||
self._activated: list[Callable[[], None]] = []
|
||||
self._forgotten: list[Callable[[], None]] = []
|
||||
self._networks_updated: list[Callable[[list[Network]], None]] = []
|
||||
self._disconnected: list[Callable[[], None]] = []
|
||||
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@@ -152,19 +167,37 @@ class WifiManager:
|
||||
|
||||
atexit.register(self.stop)
|
||||
|
||||
def set_callbacks(self, need_auth: Callable[[str], None],
|
||||
activated: Callable[[], None] | None,
|
||||
forgotten: Callable[[], None],
|
||||
networks_updated: Callable[[list[Network]], None],
|
||||
disconnected: Callable[[], None]):
|
||||
self._need_auth = need_auth
|
||||
self._activated = activated
|
||||
self._forgotten = forgotten
|
||||
self._networks_updated = networks_updated
|
||||
self._disconnected = disconnected
|
||||
def set_callbacks(self, need_auth: Callable[[str], None] | None = None,
|
||||
activated: Callable[[], None] | None = None,
|
||||
forgotten: Callable[[], None] | None = None,
|
||||
networks_updated: Callable[[list[Network]], None] | None = None,
|
||||
disconnected: Callable[[], None] | None = None):
|
||||
if need_auth is not None:
|
||||
self._need_auth.append(need_auth)
|
||||
if activated is not None:
|
||||
self._activated.append(activated)
|
||||
if forgotten is not None:
|
||||
self._forgotten.append(forgotten)
|
||||
if networks_updated is not None:
|
||||
self._networks_updated.append(networks_updated)
|
||||
if disconnected is not None:
|
||||
self._disconnected.append(disconnected)
|
||||
|
||||
def _enqueue_callback(self, cb: Callable, *args):
|
||||
self._callback_queue.append(lambda: cb(*args))
|
||||
@property
|
||||
def ipv4_address(self) -> str:
|
||||
return self._ipv4_address
|
||||
|
||||
@property
|
||||
def current_network_metered(self) -> MeteredType:
|
||||
return self._current_network_metered
|
||||
|
||||
@property
|
||||
def tethering_password(self) -> str:
|
||||
return self._tethering_password
|
||||
|
||||
def _enqueue_callbacks(self, cbs: list[Callable], *args):
|
||||
for cb in cbs:
|
||||
self._callback_queue.append(lambda _cb=cb: _cb(*args))
|
||||
|
||||
def process_callbacks(self):
|
||||
# Call from UI thread to run any pending callbacks
|
||||
@@ -180,10 +213,13 @@ class WifiManager:
|
||||
self._last_network_update = 0.0
|
||||
|
||||
def _monitor_state(self):
|
||||
# TODO: make an initialize function to only wait in one place
|
||||
device_path = self._wait_for_wifi_device()
|
||||
if device_path is None:
|
||||
return
|
||||
|
||||
self._tethering_password = self._get_tethering_password()
|
||||
|
||||
rule = MatchRule(
|
||||
type="signal",
|
||||
interface=NM_DEVICE_IFACE,
|
||||
@@ -211,20 +247,18 @@ class WifiManager:
|
||||
# BAD PASSWORD
|
||||
if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid):
|
||||
self.forget_connection(self._connecting_to_ssid, block=True)
|
||||
if self._need_auth is not None:
|
||||
self._enqueue_callback(self._need_auth, self._connecting_to_ssid)
|
||||
self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid)
|
||||
self._connecting_to_ssid = ""
|
||||
|
||||
elif new_state == NMDeviceState.ACTIVATED:
|
||||
if self._activated is not None:
|
||||
if len(self._activated):
|
||||
self._update_networks()
|
||||
self._enqueue_callback(self._activated)
|
||||
self._enqueue_callbacks(self._activated)
|
||||
self._connecting_to_ssid = ""
|
||||
|
||||
elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION:
|
||||
self._connecting_to_ssid = ""
|
||||
if self._disconnected is not None:
|
||||
self._enqueue_callback(self._disconnected)
|
||||
self._enqueue_callbacks(self._forgotten)
|
||||
|
||||
def _network_scanner(self):
|
||||
self._wait_for_wifi_device()
|
||||
@@ -285,14 +319,24 @@ class WifiManager:
|
||||
conns[ssid] = conn_path
|
||||
return conns
|
||||
|
||||
def connect_to_network(self, ssid: str, password: str):
|
||||
def _get_active_connections(self):
|
||||
return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1]
|
||||
|
||||
# TODO: use this
|
||||
def _get_connection_settings(self, conn_path: str) -> dict:
|
||||
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE)
|
||||
reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'GetSettings'))
|
||||
if reply.header.message_type == MessageType.error:
|
||||
cloudlog.warning(f'Failed to get connection settings: {reply}')
|
||||
return {}
|
||||
return dict(reply.body[0])
|
||||
|
||||
def connect_to_network(self, ssid: str, password: str, hidden: bool = False):
|
||||
def worker():
|
||||
# Clear all connections that may already exist to the network we are connecting to
|
||||
self._connecting_to_ssid = ssid
|
||||
self.forget_connection(ssid, block=True)
|
||||
|
||||
is_hidden = False
|
||||
|
||||
connection = {
|
||||
'connection': {
|
||||
'type': ('s', '802-11-wireless'),
|
||||
@@ -302,7 +346,7 @@ class WifiManager:
|
||||
},
|
||||
'802-11-wireless': {
|
||||
'ssid': ('ay', ssid.encode("utf-8")),
|
||||
'hidden': ('b', is_hidden),
|
||||
'hidden': ('b', hidden),
|
||||
'mode': ('s', 'infrastructure'),
|
||||
},
|
||||
'ipv4': {
|
||||
@@ -332,9 +376,9 @@ class WifiManager:
|
||||
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE)
|
||||
self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete'))
|
||||
|
||||
if self._forgotten is not None:
|
||||
if len(self._forgotten):
|
||||
self._update_networks()
|
||||
self._enqueue_callback(self._forgotten)
|
||||
self._enqueue_callbacks(self._forgotten)
|
||||
|
||||
if block:
|
||||
worker()
|
||||
@@ -358,6 +402,137 @@ class WifiManager:
|
||||
else:
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _deactivate_connection(self, ssid: str):
|
||||
for conn_path in self._get_active_connections():
|
||||
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
|
||||
specific_obj_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')).body[0][1]
|
||||
|
||||
if specific_obj_path != "/":
|
||||
ap_addr = DBusAddress(specific_obj_path, bus_name=NM, interface=NM_ACCESS_POINT_IFACE)
|
||||
ap_ssid = bytes(self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')).body[0][1]).decode("utf-8", "replace")
|
||||
|
||||
if ap_ssid == ssid:
|
||||
self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (conn_path,)))
|
||||
return
|
||||
|
||||
def is_tethering_active(self) -> bool:
|
||||
for network in self._networks:
|
||||
if network.is_connected:
|
||||
return bool(network.ssid == self._tethering_ssid)
|
||||
return False
|
||||
|
||||
def set_tethering_password(self, password: str):
|
||||
def worker():
|
||||
conn_path = self._get_connections().get(self._tethering_ssid, None)
|
||||
if conn_path is None:
|
||||
cloudlog.warning('No tethering connection found')
|
||||
return
|
||||
|
||||
settings = self._get_connection_settings(conn_path)
|
||||
if len(settings) == 0:
|
||||
cloudlog.warning(f'Failed to get tethering settings for {conn_path}')
|
||||
return
|
||||
|
||||
settings['802-11-wireless-security']['psk'] = ('s', password)
|
||||
|
||||
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE)
|
||||
reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,)))
|
||||
if reply.header.message_type == MessageType.error:
|
||||
cloudlog.warning(f'Failed to update tethering settings: {reply}')
|
||||
return
|
||||
|
||||
self._tethering_password = password
|
||||
if self.is_tethering_active():
|
||||
self.activate_connection(self._tethering_ssid, block=True)
|
||||
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _get_tethering_password(self) -> str:
|
||||
conn_path = self._get_connections().get(self._tethering_ssid, None)
|
||||
if conn_path is None:
|
||||
cloudlog.warning('No tethering connection found')
|
||||
return ''
|
||||
|
||||
reply = self._router_main.send_and_get_reply(new_method_call(
|
||||
DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE),
|
||||
'GetSecrets', 's', ('802-11-wireless-security',)
|
||||
))
|
||||
|
||||
if reply.header.message_type == MessageType.error:
|
||||
cloudlog.warning(f'Failed to get tethering password: {reply}')
|
||||
return ''
|
||||
|
||||
secrets = reply.body[0]
|
||||
if '802-11-wireless-security' not in secrets:
|
||||
return ''
|
||||
|
||||
return str(secrets['802-11-wireless-security'].get('psk', ('s', ''))[1])
|
||||
|
||||
def set_tethering_active(self, active: bool):
|
||||
def worker():
|
||||
if active:
|
||||
self.activate_connection(self._tethering_ssid, block=True)
|
||||
else:
|
||||
self._deactivate_connection(self._tethering_ssid)
|
||||
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _update_current_network_metered(self) -> None:
|
||||
if self._wifi_device is None:
|
||||
cloudlog.warning("No WiFi device found")
|
||||
return
|
||||
|
||||
self._current_network_metered = MeteredType.UNKNOWN
|
||||
for active_conn in self._get_active_connections():
|
||||
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
|
||||
conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1]
|
||||
|
||||
if conn_type == '802-11-wireless':
|
||||
conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1]
|
||||
if conn_path == "/":
|
||||
continue
|
||||
|
||||
settings = self._get_connection_settings(conn_path)
|
||||
|
||||
if len(settings) == 0:
|
||||
cloudlog.warning(f'Failed to get connection settings for {conn_path}')
|
||||
continue
|
||||
|
||||
metered_prop = settings['connection'].get('metered', ('i', 0))[1]
|
||||
if metered_prop == MeteredType.YES:
|
||||
self._current_network_metered = MeteredType.YES
|
||||
elif metered_prop == MeteredType.NO:
|
||||
self._current_network_metered = MeteredType.NO
|
||||
print('current_network_metered', self._current_network_metered)
|
||||
return
|
||||
|
||||
def set_current_network_metered(self, metered: MeteredType):
|
||||
def worker():
|
||||
for active_conn in self._get_active_connections():
|
||||
conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
|
||||
conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1]
|
||||
|
||||
if conn_type == '802-11-wireless' and not self.is_tethering_active():
|
||||
conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1]
|
||||
if conn_path == "/":
|
||||
continue
|
||||
|
||||
settings = self._get_connection_settings(conn_path)
|
||||
|
||||
if len(settings) == 0:
|
||||
cloudlog.warning(f'Failed to get connection settings for {conn_path}')
|
||||
return
|
||||
|
||||
settings['connection']['metered'] = ('i', int(metered))
|
||||
|
||||
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE)
|
||||
reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,)))
|
||||
if reply.header.message_type == MessageType.error:
|
||||
cloudlog.warning(f'Failed to update tethering settings: {reply}')
|
||||
return
|
||||
|
||||
threading.Thread(target=worker, daemon=True).start()
|
||||
|
||||
def _request_scan(self):
|
||||
if self._wifi_device is None:
|
||||
cloudlog.warning("No WiFi device found")
|
||||
@@ -409,8 +584,32 @@ class WifiManager:
|
||||
networks.sort(key=lambda n: (-n.is_connected, -n.strength, n.ssid.lower()))
|
||||
self._networks = networks
|
||||
|
||||
if self._networks_updated is not None:
|
||||
self._enqueue_callback(self._networks_updated, self._networks)
|
||||
self._update_ipv4_address()
|
||||
self._update_current_network_metered()
|
||||
|
||||
self._enqueue_callbacks(self._networks_updated, self._networks)
|
||||
|
||||
def _update_ipv4_address(self):
|
||||
if self._wifi_device is None:
|
||||
cloudlog.warning("No WiFi device found")
|
||||
return
|
||||
|
||||
self._ipv4_address = ""
|
||||
|
||||
for conn_path in self._get_active_connections():
|
||||
conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE)
|
||||
conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1]
|
||||
if conn_type == '802-11-wireless':
|
||||
ip4config_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Ip4Config')).body[0][1]
|
||||
|
||||
if ip4config_path != "/":
|
||||
ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE)
|
||||
address_data = self._router_main.send_and_get_reply(Properties(ip4config_addr).get('AddressData')).body[0][1]
|
||||
|
||||
for entry in address_data:
|
||||
if 'address' in entry:
|
||||
self._ipv4_address = entry['address'][1]
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
self.stop()
|
||||
|
||||
@@ -14,6 +14,7 @@ class ButtonStyle(IntEnum):
|
||||
PRIMARY = 1 # For main actions
|
||||
DANGER = 2 # For critical actions, like reboot or delete
|
||||
TRANSPARENT = 3 # For buttons with transparent background and border
|
||||
TRANSPARENT_WHITE = 3 # For buttons with transparent background and border
|
||||
ACTION = 4
|
||||
LIST_ACTION = 5 # For list items with action buttons
|
||||
NO_EFFECT = 6
|
||||
@@ -23,8 +24,6 @@ class ButtonStyle(IntEnum):
|
||||
|
||||
ICON_PADDING = 15
|
||||
DEFAULT_BUTTON_FONT_SIZE = 60
|
||||
BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51)
|
||||
BUTTON_DISABLED_BACKGROUND_COLOR = rl.Color(51, 51, 51, 255)
|
||||
ACTION_BUTTON_FONT_SIZE = 48
|
||||
|
||||
BUTTON_TEXT_COLOR = {
|
||||
@@ -32,6 +31,7 @@ BUTTON_TEXT_COLOR = {
|
||||
ButtonStyle.PRIMARY: rl.Color(228, 228, 228, 255),
|
||||
ButtonStyle.DANGER: rl.Color(228, 228, 228, 255),
|
||||
ButtonStyle.TRANSPARENT: rl.BLACK,
|
||||
ButtonStyle.TRANSPARENT_WHITE: rl.WHITE,
|
||||
ButtonStyle.ACTION: rl.BLACK,
|
||||
ButtonStyle.LIST_ACTION: rl.Color(228, 228, 228, 255),
|
||||
ButtonStyle.NO_EFFECT: rl.Color(228, 228, 228, 255),
|
||||
@@ -39,11 +39,16 @@ BUTTON_TEXT_COLOR = {
|
||||
ButtonStyle.FORGET_WIFI: rl.Color(51, 51, 51, 255),
|
||||
}
|
||||
|
||||
BUTTON_DISABLED_TEXT_COLORS = {
|
||||
ButtonStyle.TRANSPARENT_WHITE: rl.WHITE,
|
||||
}
|
||||
|
||||
BUTTON_BACKGROUND_COLORS = {
|
||||
ButtonStyle.NORMAL: rl.Color(51, 51, 51, 255),
|
||||
ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255),
|
||||
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
|
||||
ButtonStyle.TRANSPARENT: rl.BLACK,
|
||||
ButtonStyle.TRANSPARENT_WHITE: rl.BLANK,
|
||||
ButtonStyle.ACTION: rl.Color(189, 189, 189, 255),
|
||||
ButtonStyle.LIST_ACTION: rl.Color(57, 57, 57, 255),
|
||||
ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255),
|
||||
@@ -56,6 +61,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = {
|
||||
ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255),
|
||||
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
|
||||
ButtonStyle.TRANSPARENT: rl.BLACK,
|
||||
ButtonStyle.TRANSPARENT_WHITE: rl.BLANK,
|
||||
ButtonStyle.ACTION: rl.Color(130, 130, 130, 255),
|
||||
ButtonStyle.LIST_ACTION: rl.Color(74, 74, 74, 74),
|
||||
ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255),
|
||||
@@ -63,6 +69,10 @@ BUTTON_PRESSED_BACKGROUND_COLORS = {
|
||||
ButtonStyle.FORGET_WIFI: rl.Color(130, 130, 130, 255),
|
||||
}
|
||||
|
||||
BUTTON_DISABLED_BACKGROUND_COLORS = {
|
||||
ButtonStyle.TRANSPARENT_WHITE: rl.BLANK,
|
||||
}
|
||||
|
||||
_pressed_buttons: set[str] = set() # Track mouse press state globally
|
||||
|
||||
|
||||
@@ -156,7 +166,7 @@ def gui_button(
|
||||
|
||||
# Draw the button text if any
|
||||
if text:
|
||||
color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLOR
|
||||
color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLORS.get(button_style, rl.Color(228, 228, 228, 51))
|
||||
rl.draw_text_ex(font, text, text_pos, font_size, 0, color)
|
||||
|
||||
return result
|
||||
@@ -198,8 +208,8 @@ class Button(Widget):
|
||||
else:
|
||||
self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style]
|
||||
elif self._button_style != ButtonStyle.NO_EFFECT:
|
||||
self._background_color = BUTTON_DISABLED_BACKGROUND_COLOR
|
||||
self._label.set_text_color(BUTTON_DISABLED_TEXT_COLOR)
|
||||
self._background_color = BUTTON_DISABLED_BACKGROUND_COLORS.get(self._button_style, rl.Color(51, 51, 51, 255))
|
||||
self._label.set_text_color(BUTTON_DISABLED_TEXT_COLORS.get(self._button_style, rl.Color(228, 228, 228, 51)))
|
||||
|
||||
def _render(self, _):
|
||||
roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2)
|
||||
|
||||
@@ -104,6 +104,9 @@ class Keyboard(Widget):
|
||||
self._all_keys[CAPS_LOCK_KEY] = Button("", partial(self._key_callback, CAPS_LOCK_KEY), icon=self._key_icons[CAPS_LOCK_KEY],
|
||||
button_style=ButtonStyle.KEYBOARD, multi_touch=True)
|
||||
|
||||
def set_text(self, text: str):
|
||||
self._input_box.text = text
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self._input_box.text
|
||||
@@ -243,7 +246,9 @@ class Keyboard(Widget):
|
||||
if not self._caps_lock and self._layout_name == "uppercase":
|
||||
self._layout_name = "lowercase"
|
||||
|
||||
def reset(self):
|
||||
def reset(self, min_text_size: int | None = None):
|
||||
if min_text_size is not None:
|
||||
self._min_text_size = min_text_size
|
||||
self._render_return_status = -1
|
||||
self.clear()
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@ class ItemAction(Widget, ABC):
|
||||
self.set_rect(rl.Rectangle(0, 0, width, 0))
|
||||
self._enabled_source = enabled
|
||||
|
||||
def set_enabled(self, enabled: bool | Callable[[], bool]):
|
||||
self._enabled_source = enabled
|
||||
|
||||
@property
|
||||
def enabled(self):
|
||||
return _resolve_value(self._enabled_source, False)
|
||||
@@ -58,8 +61,9 @@ class ToggleAction(ItemAction):
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> bool:
|
||||
self.toggle.set_enabled(self.enabled)
|
||||
self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self._rect.width, TOGGLE_HEIGHT))
|
||||
return False
|
||||
clicked = self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self._rect.width, TOGGLE_HEIGHT))
|
||||
self.state = self.toggle.get_state()
|
||||
return bool(clicked)
|
||||
|
||||
def set_state(self, state: bool):
|
||||
self.state = state
|
||||
@@ -86,7 +90,7 @@ class ButtonAction(ItemAction):
|
||||
border_radius=BUTTON_BORDER_RADIUS,
|
||||
click_callback=pressed,
|
||||
)
|
||||
self._button.set_enabled(_resolve_value(enabled))
|
||||
self.set_enabled(enabled)
|
||||
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(touch_callback)
|
||||
@@ -98,6 +102,7 @@ class ButtonAction(ItemAction):
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> bool:
|
||||
self._button.set_text(self.text)
|
||||
self._button.set_enabled(_resolve_value(self.enabled))
|
||||
button_rect = rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
self._button.render(button_rect)
|
||||
|
||||
@@ -121,6 +126,10 @@ class TextAction(ItemAction):
|
||||
def text(self):
|
||||
return _resolve_value(self._text_source, "Error")
|
||||
|
||||
def _update_state(self):
|
||||
text_width = measure_text_cached(self._font, self.text, ITEM_TEXT_FONT_SIZE).x
|
||||
self._rect.width = int(text_width + TEXT_PADDING)
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> bool:
|
||||
current_text = self.text
|
||||
text_size = measure_text_cached(self._font, current_text, ITEM_TEXT_FONT_SIZE)
|
||||
@@ -183,7 +192,7 @@ class MultipleButtonAction(ItemAction):
|
||||
|
||||
# Check button state
|
||||
mouse_pos = rl.get_mouse_position()
|
||||
is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect)
|
||||
is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect) and self.enabled
|
||||
is_pressed = is_hovered and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed
|
||||
is_selected = i == self.selected_button
|
||||
|
||||
@@ -195,6 +204,9 @@ class MultipleButtonAction(ItemAction):
|
||||
else:
|
||||
bg_color = rl.Color(57, 57, 57, 255) # Gray
|
||||
|
||||
if not self.enabled:
|
||||
bg_color = rl.Color(bg_color.r, bg_color.g, bg_color.b, 150) # Dim
|
||||
|
||||
# Draw button
|
||||
rl.draw_rectangle_rounded(button_rect, 1.0, 20, bg_color)
|
||||
|
||||
@@ -202,7 +214,8 @@ class MultipleButtonAction(ItemAction):
|
||||
text_size = measure_text_cached(self._font, text, 40)
|
||||
text_x = button_x + (self.button_width - text_size.x) / 2
|
||||
text_y = button_y + (BUTTON_HEIGHT - text_size.y) / 2
|
||||
rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, rl.Color(228, 228, 228, 255))
|
||||
text_color = rl.Color(228, 228, 228, 255) if self.enabled else rl.Color(150, 150, 150, 255)
|
||||
rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, text_color)
|
||||
|
||||
# Handle click
|
||||
if is_hovered and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed:
|
||||
|
||||
+192
-17
@@ -3,14 +3,17 @@ from functools import partial
|
||||
from typing import cast
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.system.ui.lib.application import gui_app, DEFAULT_FPS
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import ButtonStyle, Button
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
from openpilot.system.ui.widgets.label import TextAlignment, gui_label
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
from openpilot.system.ui.widgets.list_view import text_item, button_item, ListItem, ToggleAction, MultipleButtonAction, ButtonAction
|
||||
|
||||
NM_DEVICE_STATE_NEED_AUTH = 60
|
||||
MIN_PASSWORD_LENGTH = 8
|
||||
@@ -26,6 +29,11 @@ STRENGTH_ICONS = [
|
||||
]
|
||||
|
||||
|
||||
class PanelType(IntEnum):
|
||||
WIFI = 0
|
||||
ADVANCED = 1
|
||||
|
||||
|
||||
class UIState(IntEnum):
|
||||
IDLE = 0
|
||||
CONNECTING = 1
|
||||
@@ -34,10 +42,179 @@ class UIState(IntEnum):
|
||||
FORGETTING = 4
|
||||
|
||||
|
||||
class NavButton(Widget):
|
||||
def __init__(self, text: str):
|
||||
super().__init__()
|
||||
self.text = text
|
||||
self.set_rect(rl.Rectangle(0, 0, 400, 100))
|
||||
self._x_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False)
|
||||
self._y_pos_filter = FirstOrderFilter(0.0, 0.05, 1 / DEFAULT_FPS, initialized=False)
|
||||
|
||||
def set_position(self, x: float, y: float) -> None:
|
||||
x = self._x_pos_filter.update(x)
|
||||
y = self._y_pos_filter.update(y)
|
||||
changed = (self._rect.x != x or self._rect.y != y)
|
||||
self._rect.x, self._rect.y = x, y
|
||||
if changed:
|
||||
self._update_layout_rects()
|
||||
|
||||
def _render(self, _):
|
||||
color = rl.Color(74, 74, 74, 255) if self.is_pressed else rl.Color(57, 57, 57, 255)
|
||||
rl.draw_rectangle_rounded(self._rect, 0.6, 10, color)
|
||||
gui_label(self.rect, self.text, font_size=60, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
|
||||
|
||||
class NetworkUI(Widget):
|
||||
def __init__(self, wifi_manager: WifiManager):
|
||||
super().__init__()
|
||||
self._wifi_manager = wifi_manager
|
||||
self._current_panel: PanelType = PanelType.WIFI
|
||||
self._wifi_panel = WifiManagerUI(wifi_manager)
|
||||
self._advanced_panel = AdvancedNetworkSettings(wifi_manager)
|
||||
self._nav_button = NavButton("Advanced")
|
||||
self._nav_button.set_click_callback(self._cycle_panel)
|
||||
|
||||
def _update_state(self):
|
||||
self._wifi_manager.process_callbacks()
|
||||
|
||||
def show_event(self):
|
||||
self._set_current_panel(PanelType.WIFI)
|
||||
self._wifi_panel.show_event()
|
||||
|
||||
def hide_event(self):
|
||||
self._wifi_panel.hide_event()
|
||||
|
||||
def _cycle_panel(self):
|
||||
if self._current_panel == PanelType.WIFI:
|
||||
self._set_current_panel(PanelType.ADVANCED)
|
||||
else:
|
||||
self._set_current_panel(PanelType.WIFI)
|
||||
|
||||
def _render(self, _):
|
||||
# subtract button
|
||||
content_rect = rl.Rectangle(self._rect.x, self._rect.y + self._nav_button.rect.height + 20,
|
||||
self._rect.width, self._rect.height - self._nav_button.rect.height - 20)
|
||||
if self._current_panel == PanelType.WIFI:
|
||||
self._nav_button.text = "Advanced"
|
||||
self._nav_button.set_position(self._rect.x + self._rect.width - self._nav_button.rect.width, self._rect.y + 10)
|
||||
self._wifi_panel.render(content_rect)
|
||||
else:
|
||||
self._nav_button.text = "Back"
|
||||
self._nav_button.set_position(self._rect.x, self._rect.y + 10)
|
||||
self._advanced_panel.render(content_rect)
|
||||
|
||||
self._nav_button.render()
|
||||
|
||||
def _set_current_panel(self, panel: PanelType):
|
||||
self._current_panel = panel
|
||||
|
||||
|
||||
class AdvancedNetworkSettings(Widget):
|
||||
def __init__(self, wifi_manager: WifiManager):
|
||||
super().__init__()
|
||||
self._wifi_manager = wifi_manager
|
||||
self._wifi_manager.set_callbacks(networks_updated=self._on_network_updated)
|
||||
|
||||
self._keyboard = Keyboard(max_text_size=MAX_PASSWORD_LENGTH, min_text_size=MIN_PASSWORD_LENGTH, show_password_toggle=True)
|
||||
|
||||
# Tethering
|
||||
self._tethering_action = ToggleAction(initial_state=False)
|
||||
self._tethering_btn = ListItem(title="Enable Tethering", action_item=self._tethering_action, callback=self._toggle_tethering)
|
||||
|
||||
# Tethering Password
|
||||
self._tethering_password_action = ButtonAction(text="EDIT")
|
||||
self._tethering_password_btn = ListItem(title="Tethering Password", action_item=self._tethering_password_action, callback=self._edit_tethering_password)
|
||||
|
||||
# TODO: Roaming toggle, edit APN settings, and cellular metered toggle
|
||||
|
||||
# Metered
|
||||
self._wifi_metered_action = MultipleButtonAction(["default", "metered", "unmetered"], 255, 0, callback=self._toggle_wifi_metered)
|
||||
self._wifi_metered_btn = ListItem(title="Wi-Fi Network Metered", description="Prevent large data uploads when on a metered Wi-Fi connection",
|
||||
action_item=self._wifi_metered_action)
|
||||
|
||||
items: list[Widget] = [
|
||||
self._tethering_btn,
|
||||
self._tethering_password_btn,
|
||||
text_item("IP Address", lambda: self._wifi_manager.ipv4_address),
|
||||
self._wifi_metered_btn,
|
||||
button_item("Hidden Network", "CONNECT", callback=self._connect_to_hidden_network),
|
||||
]
|
||||
|
||||
self._scroller = Scroller(items, line_separator=True, spacing=0)
|
||||
|
||||
def _on_network_updated(self, networks: list[Network]):
|
||||
self._tethering_action.set_enabled(True)
|
||||
self._tethering_action.set_state(self._wifi_manager.is_tethering_active())
|
||||
self._tethering_password_action.set_enabled(True)
|
||||
|
||||
if self._wifi_manager.is_tethering_active() or self._wifi_manager.ipv4_address == "":
|
||||
self._wifi_metered_action.set_enabled(False)
|
||||
self._wifi_metered_action.selected_button = 0
|
||||
elif self._wifi_manager.ipv4_address != "":
|
||||
metered = self._wifi_manager.current_network_metered
|
||||
self._wifi_metered_action.set_enabled(True)
|
||||
self._wifi_metered_action.selected_button = int(metered) if metered in (MeteredType.UNKNOWN, MeteredType.YES, MeteredType.NO) else 0
|
||||
|
||||
def _toggle_tethering(self):
|
||||
checked = self._tethering_action.state
|
||||
self._tethering_action.set_enabled(False)
|
||||
if checked:
|
||||
self._wifi_metered_action.set_enabled(False)
|
||||
self._wifi_manager.set_tethering_active(checked)
|
||||
|
||||
def _toggle_wifi_metered(self, metered):
|
||||
metered_type = {0: MeteredType.UNKNOWN, 1: MeteredType.YES, 2: MeteredType.NO}.get(metered, MeteredType.UNKNOWN)
|
||||
self._wifi_metered_action.set_enabled(False)
|
||||
self._wifi_manager.set_current_network_metered(metered_type)
|
||||
|
||||
def _connect_to_hidden_network(self):
|
||||
def connect_hidden(result):
|
||||
if result != 1:
|
||||
return
|
||||
|
||||
ssid = self._keyboard.text
|
||||
if not ssid:
|
||||
return
|
||||
|
||||
def enter_password(result):
|
||||
password = self._keyboard.text
|
||||
if password == "":
|
||||
# connect without password
|
||||
self._wifi_manager.connect_to_network(ssid, "", hidden=True)
|
||||
return
|
||||
|
||||
self._wifi_manager.connect_to_network(ssid, password, hidden=True)
|
||||
|
||||
self._keyboard.reset(min_text_size=0)
|
||||
self._keyboard.set_title("Enter password", f"for \"{ssid}\"")
|
||||
gui_app.set_modal_overlay(self._keyboard, enter_password)
|
||||
|
||||
self._keyboard.reset(min_text_size=1)
|
||||
self._keyboard.set_title("Enter SSID", "")
|
||||
gui_app.set_modal_overlay(self._keyboard, connect_hidden)
|
||||
|
||||
def _edit_tethering_password(self):
|
||||
def update_password(result):
|
||||
if result != 1:
|
||||
return
|
||||
|
||||
password = self._keyboard.text
|
||||
self._wifi_manager.set_tethering_password(password)
|
||||
self._tethering_password_action.set_enabled(False)
|
||||
|
||||
self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH)
|
||||
self._keyboard.set_title("Enter new tethering password", "")
|
||||
self._keyboard.set_text(self._wifi_manager.tethering_password)
|
||||
gui_app.set_modal_overlay(self._keyboard, update_password)
|
||||
|
||||
def _render(self, _):
|
||||
self._scroller.render(self._rect)
|
||||
|
||||
|
||||
class WifiManagerUI(Widget):
|
||||
def __init__(self, wifi_manager: WifiManager):
|
||||
super().__init__()
|
||||
self.wifi_manager = wifi_manager
|
||||
self._wifi_manager = wifi_manager
|
||||
self.state: UIState = UIState.IDLE
|
||||
self._state_network: Network | None = None # for CONNECTING / NEEDS_AUTH / SHOW_FORGET_CONFIRM / FORGETTING
|
||||
self._password_retry: bool = False # for NEEDS_AUTH
|
||||
@@ -51,33 +228,31 @@ class WifiManagerUI(Widget):
|
||||
self._forget_networks_buttons: dict[str, Button] = {}
|
||||
self._confirm_dialog = ConfirmDialog("", "Forget", "Cancel")
|
||||
|
||||
self.wifi_manager.set_callbacks(need_auth=self._on_need_auth,
|
||||
activated=self._on_activated,
|
||||
forgotten=self._on_forgotten,
|
||||
networks_updated=self._on_network_updated,
|
||||
disconnected=self._on_disconnected)
|
||||
self._wifi_manager.set_callbacks(need_auth=self._on_need_auth,
|
||||
activated=self._on_activated,
|
||||
forgotten=self._on_forgotten,
|
||||
networks_updated=self._on_network_updated,
|
||||
disconnected=self._on_disconnected)
|
||||
|
||||
def show_event(self):
|
||||
# start/stop scanning when widget is visible
|
||||
self.wifi_manager.set_active(True)
|
||||
self._wifi_manager.set_active(True)
|
||||
|
||||
def hide_event(self):
|
||||
self.wifi_manager.set_active(False)
|
||||
self._wifi_manager.set_active(False)
|
||||
|
||||
def _load_icons(self):
|
||||
for icon in STRENGTH_ICONS + ["icons/checkmark.png", "icons/circled_slash.png", "icons/lock_closed.png"]:
|
||||
gui_app.texture(icon, ICON_SIZE, ICON_SIZE)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.wifi_manager.process_callbacks()
|
||||
|
||||
if not self._networks:
|
||||
gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)
|
||||
return
|
||||
|
||||
if self.state == UIState.NEEDS_AUTH and self._state_network:
|
||||
self.keyboard.set_title("Wrong password" if self._password_retry else "Enter password", f"for {self._state_network.ssid}")
|
||||
self.keyboard.reset()
|
||||
self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH)
|
||||
gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result))
|
||||
elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network:
|
||||
self._confirm_dialog.set_text(f'Forget Wi-Fi Network "{self._state_network.ssid}"?')
|
||||
@@ -200,20 +375,20 @@ class WifiManagerUI(Widget):
|
||||
self.state = UIState.CONNECTING
|
||||
self._state_network = network
|
||||
if network.is_saved and not password:
|
||||
self.wifi_manager.activate_connection(network.ssid)
|
||||
self._wifi_manager.activate_connection(network.ssid)
|
||||
else:
|
||||
self.wifi_manager.connect_to_network(network.ssid, password)
|
||||
self._wifi_manager.connect_to_network(network.ssid, password)
|
||||
|
||||
def forget_network(self, network: Network):
|
||||
self.state = UIState.FORGETTING
|
||||
self._state_network = network
|
||||
self.wifi_manager.forget_connection(network.ssid)
|
||||
self._wifi_manager.forget_connection(network.ssid)
|
||||
|
||||
def _on_network_updated(self, networks: list[Network]):
|
||||
self._networks = networks
|
||||
for n in self._networks:
|
||||
self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, text_alignment=TextAlignment.LEFT,
|
||||
button_style=ButtonStyle.NO_EFFECT)
|
||||
button_style=ButtonStyle.TRANSPARENT_WHITE)
|
||||
self._forget_networks_buttons[n.ssid] = Button("Forget", partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI,
|
||||
font_size=45)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ class Toggle(Widget):
|
||||
self._enabled = True
|
||||
self._progress = 1.0 if initial_state else 0.0
|
||||
self._target = self._progress
|
||||
self._clicked = False
|
||||
|
||||
def set_rect(self, rect: rl.Rectangle):
|
||||
self._rect = rl.Rectangle(rect.x, rect.y, WIDTH, HEIGHT)
|
||||
@@ -28,6 +29,7 @@ class Toggle(Widget):
|
||||
if not self._enabled:
|
||||
return
|
||||
|
||||
self._clicked = True
|
||||
self._state = not self._state
|
||||
self._target = 1.0 if self._state else 0.0
|
||||
|
||||
@@ -66,5 +68,10 @@ class Toggle(Widget):
|
||||
knob_y = self._rect.y + HEIGHT / 2
|
||||
rl.draw_circle(int(knob_x), int(knob_y), HEIGHT / 2, knob_color)
|
||||
|
||||
# TODO: use click callback
|
||||
clicked = self._clicked
|
||||
self._clicked = False
|
||||
return clicked
|
||||
|
||||
def _blend_color(self, c1, c2, t):
|
||||
return rl.Color(int(c1.r + (c2.r - c1.r) * t), int(c1.g + (c2.g - c1.g) * t), int(c1.b + (c2.b - c1.b) * t), 255)
|
||||
|
||||
Reference in New Issue
Block a user