Source code for bauiv1lib.gather

# Released under the MIT License. See LICENSE for details.
#
"""Provides UI for inviting/joining friends."""

from __future__ import annotations

import weakref
import logging
from enum import Enum
from typing import override, TYPE_CHECKING

from bauiv1lib.tabs import TabRow
import bauiv1 as bui

if TYPE_CHECKING:
    from bauiv1lib.play import PlaylistSelectContext


[docs] class GatherTab: """Defines a tab for use in the gather UI.""" def __init__(self, window: GatherWindow) -> None: self._window = weakref.ref(window) @property def window(self) -> GatherWindow: """The GatherWindow that this tab belongs to.""" window = self._window() if window is None: raise bui.NotFoundError("GatherTab's window no longer exists.") return window
[docs] def on_activate( self, parent_widget: bui.Widget, tab_button: bui.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float, ) -> bui.Widget: """Called when the tab becomes the active one. The tab should create and return a container widget covering the specified region. """ # pylint: disable=too-many-positional-arguments raise RuntimeError('Should not get here.')
[docs] def on_deactivate(self) -> None: """Called when the tab will no longer be the active one."""
[docs] def save_state(self) -> None: """Called when the parent window is saving state."""
[docs] def restore_state(self) -> None: """Called when the parent window is restoring state."""
[docs] class GatherWindow(bui.MainWindow): """Window for joining/inviting friends."""
[docs] class TabID(Enum): """Our available tab types.""" ABOUT = 'about' INTERNET = 'internet' PRIVATE = 'private' NEARBY = 'nearby' MANUAL = 'manual'
def __init__( self, transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, ): # pylint: disable=too-many-locals # pylint: disable=cyclic-import from bauiv1lib.gather.abouttab import AboutGatherTab from bauiv1lib.gather.manualtab import ManualGatherTab from bauiv1lib.gather.privatetab import PrivateGatherTab from bauiv1lib.gather.publictab import PublicGatherTab from bauiv1lib.gather.nearbytab import NearbyGatherTab plus = bui.app.plus assert plus is not None bui.set_analytics_screen('Gather Window') uiscale = bui.app.ui_v1.uiscale self._width = ( 1640 if uiscale is bui.UIScale.SMALL else 1100 if uiscale is bui.UIScale.MEDIUM else 1200 ) self._height = ( 1000 if uiscale is bui.UIScale.SMALL else 730 if uiscale is bui.UIScale.MEDIUM else 900 ) self._current_tab: GatherWindow.TabID | None = None self._r = 'gatherWindow' # Do some fancy math to fill all available screen area up to the # size of our backing container. This lets us fit to the exact # screen shape at small ui scale. screensize = bui.get_virtual_screen_size() scale = ( 1.4 if uiscale is bui.UIScale.SMALL else 0.88 if uiscale is bui.UIScale.MEDIUM else 0.66 ) # Calc screen size in our local container space and clamp to a # bit smaller than our container size. target_width = min(self._width - 130, screensize[0] / scale) target_height = min(self._height - 130, screensize[1] / scale) # To get top/left coords, go to the center of our window and # offset by half the width/height of our target area. yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 self._scroll_width = target_width self._scroll_height = target_height - 65 self._scroll_bottom = yoffs - 93 - self._scroll_height self._scroll_left = (self._width - self._scroll_width) * 0.5 super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height), toolbar_visibility=( 'menu_tokens' if uiscale is bui.UIScale.SMALL else 'menu_full' ), scale=scale, ), transition=transition, origin_widget=origin_widget, # We're affected by screen size only at small ui-scale. refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, ) if uiscale is bui.UIScale.SMALL: bui.containerwidget( edit=self._root_widget, on_cancel_call=self.main_window_back ) self._back_button = None else: self._back_button = btn = bui.buttonwidget( parent=self._root_widget, position=(70, yoffs - 43), size=(60, 60), scale=1.1, autoselect=True, label=bui.charstr(bui.SpecialChar.BACK), button_type='backSmall', on_activate_call=self.main_window_back, ) bui.containerwidget(edit=self._root_widget, cancel_button=btn) bui.textwidget( parent=self._root_widget, position=( ( self._width * 0.5 + ( (self._scroll_width * -0.5 + 170.0 - 70.0) if uiscale is bui.UIScale.SMALL else 0.0 ) ), yoffs - (64 if uiscale is bui.UIScale.SMALL else 4), ), size=(0, 0), color=bui.app.ui_v1.title_color, scale=1.3 if uiscale is bui.UIScale.SMALL else 1.0, h_align='left' if uiscale is bui.UIScale.SMALL else 'center', v_align='center', text=(bui.Lstr(resource=f'{self._r}.titleText')), maxwidth=135 if uiscale is bui.UIScale.SMALL else 320, ) # Build up the set of tabs we want. tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) ] if plus.get_v1_account_misc_read_val('enablePublicParties', True): tabdefs.append( ( self.TabID.INTERNET, bui.Lstr(resource=f'{self._r}.publicText'), ) ) tabdefs.append( (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) ) tabdefs.append( (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) ) tabdefs.append( (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) ) tab_inset = 250.0 if uiscale is bui.UIScale.SMALL else 100.0 self._tab_row = TabRow( self._root_widget, tabdefs, size=(self._scroll_width - 2.0 * tab_inset, 50), pos=( self._scroll_left + tab_inset, self._scroll_bottom + self._scroll_height - 4.0, ), on_select_call=bui.WeakCall(self._set_tab), ) # Now instantiate handlers for these tabs. tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { self.TabID.ABOUT: AboutGatherTab, self.TabID.MANUAL: ManualGatherTab, self.TabID.PRIVATE: PrivateGatherTab, self.TabID.INTERNET: PublicGatherTab, self.TabID.NEARBY: NearbyGatherTab, } self._tabs: dict[GatherWindow.TabID, GatherTab] = {} for tab_id in self._tab_row.tabs: tabtype = tabtypes.get(tab_id) if tabtype is not None: self._tabs[tab_id] = tabtype(self) # Eww; tokens meter may or may not be here; should be smarter # about this. bui.widget( edit=self._tab_row.tabs[tabdefs[-1][0]].button, right_widget=bui.get_special_widget('tokens_meter'), ) if uiscale is bui.UIScale.SMALL: bui.widget( edit=self._tab_row.tabs[tabdefs[0][0]].button, left_widget=bui.get_special_widget('back_button'), up_widget=bui.get_special_widget('back_button'), ) # Not actually using a scroll widget anymore; just an image. bui.imagewidget( parent=self._root_widget, size=(self._scroll_width, self._scroll_height), position=( self._width * 0.5 - self._scroll_width * 0.5, self._scroll_bottom, ), texture=bui.gettexture('scrollWidget'), mesh_transparent=bui.getmesh('softEdgeOutside'), opacity=0.4, ) self._tab_container: bui.Widget | None = None self._restore_state()
[docs] @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. cls = type(self) return bui.BasicMainWindowState( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget ) )
[docs] @override def on_main_window_close(self) -> None: self._save_state()
[docs] def playlist_select( self, origin_widget: bui.Widget, context: PlaylistSelectContext, ) -> None: """Called by the private-hosting tab to select a playlist.""" from bauiv1lib.play import PlayWindow # Avoid redundant window spawns. if not self.main_window_has_control(): return playwindow = PlayWindow( origin_widget=origin_widget, playlist_select_context=context ) self.main_window_replace(playwindow) # Grab the newly-set main-window's back-state; that will lead us # back here once we're done going down our main-window # rabbit-hole for playlist selection. context.back_state = playwindow.main_window_back_state
def _set_tab(self, tab_id: TabID) -> None: if self._current_tab is tab_id: return prev_tab_id = self._current_tab self._current_tab = tab_id # We wanna preserve our current tab between runs. cfg = bui.app.config cfg['Gather Tab'] = tab_id.value cfg.commit() # Update tab colors based on which is selected. self._tab_row.update_appearance(tab_id) if prev_tab_id is not None: prev_tab = self._tabs.get(prev_tab_id) if prev_tab is not None: prev_tab.on_deactivate() # Clear up prev container if it hasn't been done. if self._tab_container: self._tab_container.delete() tab = self._tabs.get(tab_id) if tab is not None: self._tab_container = tab.on_activate( self._root_widget, self._tab_row.tabs[tab_id].button, self._scroll_width, self._scroll_height, self._scroll_left, self._scroll_bottom, ) return def _save_state(self) -> None: try: for tab in self._tabs.values(): tab.save_state() sel = self._root_widget.get_selected_child() selected_tab_ids = [ tab_id for tab_id, tab in self._tab_row.tabs.items() if sel == tab.button ] if sel == self._back_button: sel_name = 'Back' elif selected_tab_ids: assert len(selected_tab_ids) == 1 sel_name = f'Tab:{selected_tab_ids[0].value}' elif sel == self._tab_container: sel_name = 'TabContainer' else: raise ValueError(f'unrecognized selection: \'{sel}\'') assert bui.app.classic is not None bui.app.ui_v1.window_states[type(self)] = { 'sel_name': sel_name, } except Exception: logging.exception('Error saving state for %s.', self) def _restore_state(self) -> None: try: for tab in self._tabs.values(): tab.restore_state() sel: bui.Widget | None assert bui.app.classic is not None winstate = bui.app.ui_v1.window_states.get(type(self), {}) sel_name = winstate.get('sel_name', None) assert isinstance(sel_name, (str, type(None))) current_tab = self.TabID.ABOUT gather_tab_val = bui.app.config.get('Gather Tab') try: stored_tab = self.TabID(gather_tab_val) if stored_tab in self._tab_row.tabs: current_tab = stored_tab except ValueError: pass self._set_tab(current_tab) if sel_name == 'Back': sel = self._back_button elif sel_name == 'TabContainer': sel = self._tab_container elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): try: sel_tab_id = self.TabID(sel_name.split(':')[-1]) except ValueError: sel_tab_id = self.TabID.ABOUT sel = self._tab_row.tabs[sel_tab_id].button else: sel = self._tab_row.tabs[current_tab].button bui.containerwidget(edit=self._root_widget, selected_child=sel) except Exception: logging.exception('Error restoring state for %s.', self)
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum