bauiv1lib.gather
Provides UI for inviting/joining friends.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for inviting/joining friends.""" 4 5from __future__ import annotations 6 7import weakref 8import logging 9from enum import Enum 10from typing import override, TYPE_CHECKING 11 12from bauiv1lib.tabs import TabRow 13import bauiv1 as bui 14 15if TYPE_CHECKING: 16 from bauiv1lib.play import PlaylistSelectContext 17 18 19class GatherTab: 20 """Defines a tab for use in the gather UI.""" 21 22 def __init__(self, window: GatherWindow) -> None: 23 self._window = weakref.ref(window) 24 25 @property 26 def window(self) -> GatherWindow: 27 """The GatherWindow that this tab belongs to.""" 28 window = self._window() 29 if window is None: 30 raise bui.NotFoundError("GatherTab's window no longer exists.") 31 return window 32 33 def on_activate( 34 self, 35 parent_widget: bui.Widget, 36 tab_button: bui.Widget, 37 region_width: float, 38 region_height: float, 39 region_left: float, 40 region_bottom: float, 41 ) -> bui.Widget: 42 """Called when the tab becomes the active one. 43 44 The tab should create and return a container widget covering the 45 specified region. 46 """ 47 # pylint: disable=too-many-positional-arguments 48 raise RuntimeError('Should not get here.') 49 50 def on_deactivate(self) -> None: 51 """Called when the tab will no longer be the active one.""" 52 53 def save_state(self) -> None: 54 """Called when the parent window is saving state.""" 55 56 def restore_state(self) -> None: 57 """Called when the parent window is restoring state.""" 58 59 60class GatherWindow(bui.MainWindow): 61 """Window for joining/inviting friends.""" 62 63 class TabID(Enum): 64 """Our available tab types.""" 65 66 ABOUT = 'about' 67 INTERNET = 'internet' 68 PRIVATE = 'private' 69 NEARBY = 'nearby' 70 MANUAL = 'manual' 71 72 def __init__( 73 self, 74 transition: str | None = 'in_right', 75 origin_widget: bui.Widget | None = None, 76 ): 77 # pylint: disable=too-many-locals 78 # pylint: disable=cyclic-import 79 from bauiv1lib.gather.abouttab import AboutGatherTab 80 from bauiv1lib.gather.manualtab import ManualGatherTab 81 from bauiv1lib.gather.privatetab import PrivateGatherTab 82 from bauiv1lib.gather.publictab import PublicGatherTab 83 from bauiv1lib.gather.nearbytab import NearbyGatherTab 84 85 plus = bui.app.plus 86 assert plus is not None 87 88 bui.set_analytics_screen('Gather Window') 89 uiscale = bui.app.ui_v1.uiscale 90 self._width = ( 91 1640 92 if uiscale is bui.UIScale.SMALL 93 else 1100 if uiscale is bui.UIScale.MEDIUM else 1200 94 ) 95 self._height = ( 96 1000 97 if uiscale is bui.UIScale.SMALL 98 else 730 if uiscale is bui.UIScale.MEDIUM else 900 99 ) 100 self._current_tab: GatherWindow.TabID | None = None 101 self._r = 'gatherWindow' 102 103 # Do some fancy math to fill all available screen area up to the 104 # size of our backing container. This lets us fit to the exact 105 # screen shape at small ui scale. 106 screensize = bui.get_virtual_screen_size() 107 scale = ( 108 1.4 109 if uiscale is bui.UIScale.SMALL 110 else 0.88 if uiscale is bui.UIScale.MEDIUM else 0.66 111 ) 112 # Calc screen size in our local container space and clamp to a 113 # bit smaller than our container size. 114 target_width = min(self._width - 130, screensize[0] / scale) 115 target_height = min(self._height - 130, screensize[1] / scale) 116 117 # To get top/left coords, go to the center of our window and 118 # offset by half the width/height of our target area. 119 yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 120 121 self._scroll_width = target_width 122 self._scroll_height = target_height - 65 123 self._scroll_bottom = yoffs - 93 - self._scroll_height 124 self._scroll_left = (self._width - self._scroll_width) * 0.5 125 126 super().__init__( 127 root_widget=bui.containerwidget( 128 size=(self._width, self._height), 129 toolbar_visibility=( 130 'menu_tokens' 131 if uiscale is bui.UIScale.SMALL 132 else 'menu_full' 133 ), 134 scale=scale, 135 ), 136 transition=transition, 137 origin_widget=origin_widget, 138 # We're affected by screen size only at small ui-scale. 139 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 140 ) 141 142 if uiscale is bui.UIScale.SMALL: 143 bui.containerwidget( 144 edit=self._root_widget, on_cancel_call=self.main_window_back 145 ) 146 self._back_button = None 147 else: 148 self._back_button = btn = bui.buttonwidget( 149 parent=self._root_widget, 150 position=(70, yoffs - 43), 151 size=(60, 60), 152 scale=1.1, 153 autoselect=True, 154 label=bui.charstr(bui.SpecialChar.BACK), 155 button_type='backSmall', 156 on_activate_call=self.main_window_back, 157 ) 158 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 159 160 bui.textwidget( 161 parent=self._root_widget, 162 position=( 163 ( 164 self._width * 0.5 165 + ( 166 (self._scroll_width * -0.5 + 170.0 - 70.0) 167 if uiscale is bui.UIScale.SMALL 168 else 0.0 169 ) 170 ), 171 yoffs - (64 if uiscale is bui.UIScale.SMALL else 4), 172 ), 173 size=(0, 0), 174 color=bui.app.ui_v1.title_color, 175 scale=1.3 if uiscale is bui.UIScale.SMALL else 1.0, 176 h_align='left' if uiscale is bui.UIScale.SMALL else 'center', 177 v_align='center', 178 text=(bui.Lstr(resource=f'{self._r}.titleText')), 179 maxwidth=135 if uiscale is bui.UIScale.SMALL else 320, 180 ) 181 182 # Build up the set of tabs we want. 183 tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ 184 (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) 185 ] 186 if plus.get_v1_account_misc_read_val('enablePublicParties', True): 187 tabdefs.append( 188 ( 189 self.TabID.INTERNET, 190 bui.Lstr(resource=f'{self._r}.publicText'), 191 ) 192 ) 193 tabdefs.append( 194 (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) 195 ) 196 tabdefs.append( 197 (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) 198 ) 199 tabdefs.append( 200 (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) 201 ) 202 203 tab_inset = 250.0 if uiscale is bui.UIScale.SMALL else 100.0 204 205 self._tab_row = TabRow( 206 self._root_widget, 207 tabdefs, 208 size=(self._scroll_width - 2.0 * tab_inset, 50), 209 pos=( 210 self._scroll_left + tab_inset, 211 self._scroll_bottom + self._scroll_height - 4.0, 212 ), 213 on_select_call=bui.WeakCall(self._set_tab), 214 ) 215 216 # Now instantiate handlers for these tabs. 217 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 218 self.TabID.ABOUT: AboutGatherTab, 219 self.TabID.MANUAL: ManualGatherTab, 220 self.TabID.PRIVATE: PrivateGatherTab, 221 self.TabID.INTERNET: PublicGatherTab, 222 self.TabID.NEARBY: NearbyGatherTab, 223 } 224 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 225 for tab_id in self._tab_row.tabs: 226 tabtype = tabtypes.get(tab_id) 227 if tabtype is not None: 228 self._tabs[tab_id] = tabtype(self) 229 230 # Eww; tokens meter may or may not be here; should be smarter 231 # about this. 232 bui.widget( 233 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 234 right_widget=bui.get_special_widget('tokens_meter'), 235 ) 236 if uiscale is bui.UIScale.SMALL: 237 bui.widget( 238 edit=self._tab_row.tabs[tabdefs[0][0]].button, 239 left_widget=bui.get_special_widget('back_button'), 240 up_widget=bui.get_special_widget('back_button'), 241 ) 242 243 # Not actually using a scroll widget anymore; just an image. 244 bui.imagewidget( 245 parent=self._root_widget, 246 size=(self._scroll_width, self._scroll_height), 247 position=( 248 self._width * 0.5 - self._scroll_width * 0.5, 249 self._scroll_bottom, 250 ), 251 texture=bui.gettexture('scrollWidget'), 252 mesh_transparent=bui.getmesh('softEdgeOutside'), 253 opacity=0.4, 254 ) 255 self._tab_container: bui.Widget | None = None 256 257 self._restore_state() 258 259 @override 260 def get_main_window_state(self) -> bui.MainWindowState: 261 # Support recreating our window for back/refresh purposes. 262 cls = type(self) 263 return bui.BasicMainWindowState( 264 create_call=lambda transition, origin_widget: cls( 265 transition=transition, origin_widget=origin_widget 266 ) 267 ) 268 269 @override 270 def on_main_window_close(self) -> None: 271 self._save_state() 272 273 def playlist_select( 274 self, 275 origin_widget: bui.Widget, 276 context: PlaylistSelectContext, 277 ) -> None: 278 """Called by the private-hosting tab to select a playlist.""" 279 from bauiv1lib.play import PlayWindow 280 281 # Avoid redundant window spawns. 282 if not self.main_window_has_control(): 283 return 284 285 playwindow = PlayWindow( 286 origin_widget=origin_widget, playlist_select_context=context 287 ) 288 self.main_window_replace(playwindow) 289 290 # Grab the newly-set main-window's back-state; that will lead us 291 # back here once we're done going down our main-window 292 # rabbit-hole for playlist selection. 293 context.back_state = playwindow.main_window_back_state 294 295 def _set_tab(self, tab_id: TabID) -> None: 296 if self._current_tab is tab_id: 297 return 298 prev_tab_id = self._current_tab 299 self._current_tab = tab_id 300 301 # We wanna preserve our current tab between runs. 302 cfg = bui.app.config 303 cfg['Gather Tab'] = tab_id.value 304 cfg.commit() 305 306 # Update tab colors based on which is selected. 307 self._tab_row.update_appearance(tab_id) 308 309 if prev_tab_id is not None: 310 prev_tab = self._tabs.get(prev_tab_id) 311 if prev_tab is not None: 312 prev_tab.on_deactivate() 313 314 # Clear up prev container if it hasn't been done. 315 if self._tab_container: 316 self._tab_container.delete() 317 318 tab = self._tabs.get(tab_id) 319 if tab is not None: 320 self._tab_container = tab.on_activate( 321 self._root_widget, 322 self._tab_row.tabs[tab_id].button, 323 self._scroll_width, 324 self._scroll_height, 325 self._scroll_left, 326 self._scroll_bottom, 327 ) 328 return 329 330 def _save_state(self) -> None: 331 try: 332 for tab in self._tabs.values(): 333 tab.save_state() 334 335 sel = self._root_widget.get_selected_child() 336 selected_tab_ids = [ 337 tab_id 338 for tab_id, tab in self._tab_row.tabs.items() 339 if sel == tab.button 340 ] 341 if sel == self._back_button: 342 sel_name = 'Back' 343 elif selected_tab_ids: 344 assert len(selected_tab_ids) == 1 345 sel_name = f'Tab:{selected_tab_ids[0].value}' 346 elif sel == self._tab_container: 347 sel_name = 'TabContainer' 348 else: 349 raise ValueError(f'unrecognized selection: \'{sel}\'') 350 assert bui.app.classic is not None 351 bui.app.ui_v1.window_states[type(self)] = { 352 'sel_name': sel_name, 353 } 354 except Exception: 355 logging.exception('Error saving state for %s.', self) 356 357 def _restore_state(self) -> None: 358 try: 359 for tab in self._tabs.values(): 360 tab.restore_state() 361 362 sel: bui.Widget | None 363 assert bui.app.classic is not None 364 winstate = bui.app.ui_v1.window_states.get(type(self), {}) 365 sel_name = winstate.get('sel_name', None) 366 assert isinstance(sel_name, (str, type(None))) 367 current_tab = self.TabID.ABOUT 368 gather_tab_val = bui.app.config.get('Gather Tab') 369 try: 370 stored_tab = self.TabID(gather_tab_val) 371 if stored_tab in self._tab_row.tabs: 372 current_tab = stored_tab 373 except ValueError: 374 pass 375 self._set_tab(current_tab) 376 if sel_name == 'Back': 377 sel = self._back_button 378 elif sel_name == 'TabContainer': 379 sel = self._tab_container 380 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 381 try: 382 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 383 except ValueError: 384 sel_tab_id = self.TabID.ABOUT 385 sel = self._tab_row.tabs[sel_tab_id].button 386 else: 387 sel = self._tab_row.tabs[current_tab].button 388 bui.containerwidget(edit=self._root_widget, selected_child=sel) 389 390 except Exception: 391 logging.exception('Error restoring state for %s.', self)
class
GatherTab:
20class GatherTab: 21 """Defines a tab for use in the gather UI.""" 22 23 def __init__(self, window: GatherWindow) -> None: 24 self._window = weakref.ref(window) 25 26 @property 27 def window(self) -> GatherWindow: 28 """The GatherWindow that this tab belongs to.""" 29 window = self._window() 30 if window is None: 31 raise bui.NotFoundError("GatherTab's window no longer exists.") 32 return window 33 34 def on_activate( 35 self, 36 parent_widget: bui.Widget, 37 tab_button: bui.Widget, 38 region_width: float, 39 region_height: float, 40 region_left: float, 41 region_bottom: float, 42 ) -> bui.Widget: 43 """Called when the tab becomes the active one. 44 45 The tab should create and return a container widget covering the 46 specified region. 47 """ 48 # pylint: disable=too-many-positional-arguments 49 raise RuntimeError('Should not get here.') 50 51 def on_deactivate(self) -> None: 52 """Called when the tab will no longer be the active one.""" 53 54 def save_state(self) -> None: 55 """Called when the parent window is saving state.""" 56 57 def restore_state(self) -> None: 58 """Called when the parent window is restoring state."""
Defines a tab for use in the gather UI.
GatherTab(window: GatherWindow)
window: GatherWindow
26 @property 27 def window(self) -> GatherWindow: 28 """The GatherWindow that this tab belongs to.""" 29 window = self._window() 30 if window is None: 31 raise bui.NotFoundError("GatherTab's window no longer exists.") 32 return window
The GatherWindow that this tab belongs to.
def
on_activate( self, parent_widget: _bauiv1.Widget, tab_button: _bauiv1.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float) -> _bauiv1.Widget:
34 def on_activate( 35 self, 36 parent_widget: bui.Widget, 37 tab_button: bui.Widget, 38 region_width: float, 39 region_height: float, 40 region_left: float, 41 region_bottom: float, 42 ) -> bui.Widget: 43 """Called when the tab becomes the active one. 44 45 The tab should create and return a container widget covering the 46 specified region. 47 """ 48 # pylint: disable=too-many-positional-arguments 49 raise RuntimeError('Should not get here.')
Called when the tab becomes the active one.
The tab should create and return a container widget covering the specified region.
class
GatherWindow(bauiv1._uitypes.MainWindow):
61class GatherWindow(bui.MainWindow): 62 """Window for joining/inviting friends.""" 63 64 class TabID(Enum): 65 """Our available tab types.""" 66 67 ABOUT = 'about' 68 INTERNET = 'internet' 69 PRIVATE = 'private' 70 NEARBY = 'nearby' 71 MANUAL = 'manual' 72 73 def __init__( 74 self, 75 transition: str | None = 'in_right', 76 origin_widget: bui.Widget | None = None, 77 ): 78 # pylint: disable=too-many-locals 79 # pylint: disable=cyclic-import 80 from bauiv1lib.gather.abouttab import AboutGatherTab 81 from bauiv1lib.gather.manualtab import ManualGatherTab 82 from bauiv1lib.gather.privatetab import PrivateGatherTab 83 from bauiv1lib.gather.publictab import PublicGatherTab 84 from bauiv1lib.gather.nearbytab import NearbyGatherTab 85 86 plus = bui.app.plus 87 assert plus is not None 88 89 bui.set_analytics_screen('Gather Window') 90 uiscale = bui.app.ui_v1.uiscale 91 self._width = ( 92 1640 93 if uiscale is bui.UIScale.SMALL 94 else 1100 if uiscale is bui.UIScale.MEDIUM else 1200 95 ) 96 self._height = ( 97 1000 98 if uiscale is bui.UIScale.SMALL 99 else 730 if uiscale is bui.UIScale.MEDIUM else 900 100 ) 101 self._current_tab: GatherWindow.TabID | None = None 102 self._r = 'gatherWindow' 103 104 # Do some fancy math to fill all available screen area up to the 105 # size of our backing container. This lets us fit to the exact 106 # screen shape at small ui scale. 107 screensize = bui.get_virtual_screen_size() 108 scale = ( 109 1.4 110 if uiscale is bui.UIScale.SMALL 111 else 0.88 if uiscale is bui.UIScale.MEDIUM else 0.66 112 ) 113 # Calc screen size in our local container space and clamp to a 114 # bit smaller than our container size. 115 target_width = min(self._width - 130, screensize[0] / scale) 116 target_height = min(self._height - 130, screensize[1] / scale) 117 118 # To get top/left coords, go to the center of our window and 119 # offset by half the width/height of our target area. 120 yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 121 122 self._scroll_width = target_width 123 self._scroll_height = target_height - 65 124 self._scroll_bottom = yoffs - 93 - self._scroll_height 125 self._scroll_left = (self._width - self._scroll_width) * 0.5 126 127 super().__init__( 128 root_widget=bui.containerwidget( 129 size=(self._width, self._height), 130 toolbar_visibility=( 131 'menu_tokens' 132 if uiscale is bui.UIScale.SMALL 133 else 'menu_full' 134 ), 135 scale=scale, 136 ), 137 transition=transition, 138 origin_widget=origin_widget, 139 # We're affected by screen size only at small ui-scale. 140 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 141 ) 142 143 if uiscale is bui.UIScale.SMALL: 144 bui.containerwidget( 145 edit=self._root_widget, on_cancel_call=self.main_window_back 146 ) 147 self._back_button = None 148 else: 149 self._back_button = btn = bui.buttonwidget( 150 parent=self._root_widget, 151 position=(70, yoffs - 43), 152 size=(60, 60), 153 scale=1.1, 154 autoselect=True, 155 label=bui.charstr(bui.SpecialChar.BACK), 156 button_type='backSmall', 157 on_activate_call=self.main_window_back, 158 ) 159 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 160 161 bui.textwidget( 162 parent=self._root_widget, 163 position=( 164 ( 165 self._width * 0.5 166 + ( 167 (self._scroll_width * -0.5 + 170.0 - 70.0) 168 if uiscale is bui.UIScale.SMALL 169 else 0.0 170 ) 171 ), 172 yoffs - (64 if uiscale is bui.UIScale.SMALL else 4), 173 ), 174 size=(0, 0), 175 color=bui.app.ui_v1.title_color, 176 scale=1.3 if uiscale is bui.UIScale.SMALL else 1.0, 177 h_align='left' if uiscale is bui.UIScale.SMALL else 'center', 178 v_align='center', 179 text=(bui.Lstr(resource=f'{self._r}.titleText')), 180 maxwidth=135 if uiscale is bui.UIScale.SMALL else 320, 181 ) 182 183 # Build up the set of tabs we want. 184 tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ 185 (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) 186 ] 187 if plus.get_v1_account_misc_read_val('enablePublicParties', True): 188 tabdefs.append( 189 ( 190 self.TabID.INTERNET, 191 bui.Lstr(resource=f'{self._r}.publicText'), 192 ) 193 ) 194 tabdefs.append( 195 (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) 196 ) 197 tabdefs.append( 198 (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) 199 ) 200 tabdefs.append( 201 (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) 202 ) 203 204 tab_inset = 250.0 if uiscale is bui.UIScale.SMALL else 100.0 205 206 self._tab_row = TabRow( 207 self._root_widget, 208 tabdefs, 209 size=(self._scroll_width - 2.0 * tab_inset, 50), 210 pos=( 211 self._scroll_left + tab_inset, 212 self._scroll_bottom + self._scroll_height - 4.0, 213 ), 214 on_select_call=bui.WeakCall(self._set_tab), 215 ) 216 217 # Now instantiate handlers for these tabs. 218 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 219 self.TabID.ABOUT: AboutGatherTab, 220 self.TabID.MANUAL: ManualGatherTab, 221 self.TabID.PRIVATE: PrivateGatherTab, 222 self.TabID.INTERNET: PublicGatherTab, 223 self.TabID.NEARBY: NearbyGatherTab, 224 } 225 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 226 for tab_id in self._tab_row.tabs: 227 tabtype = tabtypes.get(tab_id) 228 if tabtype is not None: 229 self._tabs[tab_id] = tabtype(self) 230 231 # Eww; tokens meter may or may not be here; should be smarter 232 # about this. 233 bui.widget( 234 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 235 right_widget=bui.get_special_widget('tokens_meter'), 236 ) 237 if uiscale is bui.UIScale.SMALL: 238 bui.widget( 239 edit=self._tab_row.tabs[tabdefs[0][0]].button, 240 left_widget=bui.get_special_widget('back_button'), 241 up_widget=bui.get_special_widget('back_button'), 242 ) 243 244 # Not actually using a scroll widget anymore; just an image. 245 bui.imagewidget( 246 parent=self._root_widget, 247 size=(self._scroll_width, self._scroll_height), 248 position=( 249 self._width * 0.5 - self._scroll_width * 0.5, 250 self._scroll_bottom, 251 ), 252 texture=bui.gettexture('scrollWidget'), 253 mesh_transparent=bui.getmesh('softEdgeOutside'), 254 opacity=0.4, 255 ) 256 self._tab_container: bui.Widget | None = None 257 258 self._restore_state() 259 260 @override 261 def get_main_window_state(self) -> bui.MainWindowState: 262 # Support recreating our window for back/refresh purposes. 263 cls = type(self) 264 return bui.BasicMainWindowState( 265 create_call=lambda transition, origin_widget: cls( 266 transition=transition, origin_widget=origin_widget 267 ) 268 ) 269 270 @override 271 def on_main_window_close(self) -> None: 272 self._save_state() 273 274 def playlist_select( 275 self, 276 origin_widget: bui.Widget, 277 context: PlaylistSelectContext, 278 ) -> None: 279 """Called by the private-hosting tab to select a playlist.""" 280 from bauiv1lib.play import PlayWindow 281 282 # Avoid redundant window spawns. 283 if not self.main_window_has_control(): 284 return 285 286 playwindow = PlayWindow( 287 origin_widget=origin_widget, playlist_select_context=context 288 ) 289 self.main_window_replace(playwindow) 290 291 # Grab the newly-set main-window's back-state; that will lead us 292 # back here once we're done going down our main-window 293 # rabbit-hole for playlist selection. 294 context.back_state = playwindow.main_window_back_state 295 296 def _set_tab(self, tab_id: TabID) -> None: 297 if self._current_tab is tab_id: 298 return 299 prev_tab_id = self._current_tab 300 self._current_tab = tab_id 301 302 # We wanna preserve our current tab between runs. 303 cfg = bui.app.config 304 cfg['Gather Tab'] = tab_id.value 305 cfg.commit() 306 307 # Update tab colors based on which is selected. 308 self._tab_row.update_appearance(tab_id) 309 310 if prev_tab_id is not None: 311 prev_tab = self._tabs.get(prev_tab_id) 312 if prev_tab is not None: 313 prev_tab.on_deactivate() 314 315 # Clear up prev container if it hasn't been done. 316 if self._tab_container: 317 self._tab_container.delete() 318 319 tab = self._tabs.get(tab_id) 320 if tab is not None: 321 self._tab_container = tab.on_activate( 322 self._root_widget, 323 self._tab_row.tabs[tab_id].button, 324 self._scroll_width, 325 self._scroll_height, 326 self._scroll_left, 327 self._scroll_bottom, 328 ) 329 return 330 331 def _save_state(self) -> None: 332 try: 333 for tab in self._tabs.values(): 334 tab.save_state() 335 336 sel = self._root_widget.get_selected_child() 337 selected_tab_ids = [ 338 tab_id 339 for tab_id, tab in self._tab_row.tabs.items() 340 if sel == tab.button 341 ] 342 if sel == self._back_button: 343 sel_name = 'Back' 344 elif selected_tab_ids: 345 assert len(selected_tab_ids) == 1 346 sel_name = f'Tab:{selected_tab_ids[0].value}' 347 elif sel == self._tab_container: 348 sel_name = 'TabContainer' 349 else: 350 raise ValueError(f'unrecognized selection: \'{sel}\'') 351 assert bui.app.classic is not None 352 bui.app.ui_v1.window_states[type(self)] = { 353 'sel_name': sel_name, 354 } 355 except Exception: 356 logging.exception('Error saving state for %s.', self) 357 358 def _restore_state(self) -> None: 359 try: 360 for tab in self._tabs.values(): 361 tab.restore_state() 362 363 sel: bui.Widget | None 364 assert bui.app.classic is not None 365 winstate = bui.app.ui_v1.window_states.get(type(self), {}) 366 sel_name = winstate.get('sel_name', None) 367 assert isinstance(sel_name, (str, type(None))) 368 current_tab = self.TabID.ABOUT 369 gather_tab_val = bui.app.config.get('Gather Tab') 370 try: 371 stored_tab = self.TabID(gather_tab_val) 372 if stored_tab in self._tab_row.tabs: 373 current_tab = stored_tab 374 except ValueError: 375 pass 376 self._set_tab(current_tab) 377 if sel_name == 'Back': 378 sel = self._back_button 379 elif sel_name == 'TabContainer': 380 sel = self._tab_container 381 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 382 try: 383 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 384 except ValueError: 385 sel_tab_id = self.TabID.ABOUT 386 sel = self._tab_row.tabs[sel_tab_id].button 387 else: 388 sel = self._tab_row.tabs[current_tab].button 389 bui.containerwidget(edit=self._root_widget, selected_child=sel) 390 391 except Exception: 392 logging.exception('Error restoring state for %s.', self)
Window for joining/inviting friends.
GatherWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
73 def __init__( 74 self, 75 transition: str | None = 'in_right', 76 origin_widget: bui.Widget | None = None, 77 ): 78 # pylint: disable=too-many-locals 79 # pylint: disable=cyclic-import 80 from bauiv1lib.gather.abouttab import AboutGatherTab 81 from bauiv1lib.gather.manualtab import ManualGatherTab 82 from bauiv1lib.gather.privatetab import PrivateGatherTab 83 from bauiv1lib.gather.publictab import PublicGatherTab 84 from bauiv1lib.gather.nearbytab import NearbyGatherTab 85 86 plus = bui.app.plus 87 assert plus is not None 88 89 bui.set_analytics_screen('Gather Window') 90 uiscale = bui.app.ui_v1.uiscale 91 self._width = ( 92 1640 93 if uiscale is bui.UIScale.SMALL 94 else 1100 if uiscale is bui.UIScale.MEDIUM else 1200 95 ) 96 self._height = ( 97 1000 98 if uiscale is bui.UIScale.SMALL 99 else 730 if uiscale is bui.UIScale.MEDIUM else 900 100 ) 101 self._current_tab: GatherWindow.TabID | None = None 102 self._r = 'gatherWindow' 103 104 # Do some fancy math to fill all available screen area up to the 105 # size of our backing container. This lets us fit to the exact 106 # screen shape at small ui scale. 107 screensize = bui.get_virtual_screen_size() 108 scale = ( 109 1.4 110 if uiscale is bui.UIScale.SMALL 111 else 0.88 if uiscale is bui.UIScale.MEDIUM else 0.66 112 ) 113 # Calc screen size in our local container space and clamp to a 114 # bit smaller than our container size. 115 target_width = min(self._width - 130, screensize[0] / scale) 116 target_height = min(self._height - 130, screensize[1] / scale) 117 118 # To get top/left coords, go to the center of our window and 119 # offset by half the width/height of our target area. 120 yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 121 122 self._scroll_width = target_width 123 self._scroll_height = target_height - 65 124 self._scroll_bottom = yoffs - 93 - self._scroll_height 125 self._scroll_left = (self._width - self._scroll_width) * 0.5 126 127 super().__init__( 128 root_widget=bui.containerwidget( 129 size=(self._width, self._height), 130 toolbar_visibility=( 131 'menu_tokens' 132 if uiscale is bui.UIScale.SMALL 133 else 'menu_full' 134 ), 135 scale=scale, 136 ), 137 transition=transition, 138 origin_widget=origin_widget, 139 # We're affected by screen size only at small ui-scale. 140 refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, 141 ) 142 143 if uiscale is bui.UIScale.SMALL: 144 bui.containerwidget( 145 edit=self._root_widget, on_cancel_call=self.main_window_back 146 ) 147 self._back_button = None 148 else: 149 self._back_button = btn = bui.buttonwidget( 150 parent=self._root_widget, 151 position=(70, yoffs - 43), 152 size=(60, 60), 153 scale=1.1, 154 autoselect=True, 155 label=bui.charstr(bui.SpecialChar.BACK), 156 button_type='backSmall', 157 on_activate_call=self.main_window_back, 158 ) 159 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 160 161 bui.textwidget( 162 parent=self._root_widget, 163 position=( 164 ( 165 self._width * 0.5 166 + ( 167 (self._scroll_width * -0.5 + 170.0 - 70.0) 168 if uiscale is bui.UIScale.SMALL 169 else 0.0 170 ) 171 ), 172 yoffs - (64 if uiscale is bui.UIScale.SMALL else 4), 173 ), 174 size=(0, 0), 175 color=bui.app.ui_v1.title_color, 176 scale=1.3 if uiscale is bui.UIScale.SMALL else 1.0, 177 h_align='left' if uiscale is bui.UIScale.SMALL else 'center', 178 v_align='center', 179 text=(bui.Lstr(resource=f'{self._r}.titleText')), 180 maxwidth=135 if uiscale is bui.UIScale.SMALL else 320, 181 ) 182 183 # Build up the set of tabs we want. 184 tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ 185 (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) 186 ] 187 if plus.get_v1_account_misc_read_val('enablePublicParties', True): 188 tabdefs.append( 189 ( 190 self.TabID.INTERNET, 191 bui.Lstr(resource=f'{self._r}.publicText'), 192 ) 193 ) 194 tabdefs.append( 195 (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) 196 ) 197 tabdefs.append( 198 (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) 199 ) 200 tabdefs.append( 201 (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) 202 ) 203 204 tab_inset = 250.0 if uiscale is bui.UIScale.SMALL else 100.0 205 206 self._tab_row = TabRow( 207 self._root_widget, 208 tabdefs, 209 size=(self._scroll_width - 2.0 * tab_inset, 50), 210 pos=( 211 self._scroll_left + tab_inset, 212 self._scroll_bottom + self._scroll_height - 4.0, 213 ), 214 on_select_call=bui.WeakCall(self._set_tab), 215 ) 216 217 # Now instantiate handlers for these tabs. 218 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 219 self.TabID.ABOUT: AboutGatherTab, 220 self.TabID.MANUAL: ManualGatherTab, 221 self.TabID.PRIVATE: PrivateGatherTab, 222 self.TabID.INTERNET: PublicGatherTab, 223 self.TabID.NEARBY: NearbyGatherTab, 224 } 225 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 226 for tab_id in self._tab_row.tabs: 227 tabtype = tabtypes.get(tab_id) 228 if tabtype is not None: 229 self._tabs[tab_id] = tabtype(self) 230 231 # Eww; tokens meter may or may not be here; should be smarter 232 # about this. 233 bui.widget( 234 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 235 right_widget=bui.get_special_widget('tokens_meter'), 236 ) 237 if uiscale is bui.UIScale.SMALL: 238 bui.widget( 239 edit=self._tab_row.tabs[tabdefs[0][0]].button, 240 left_widget=bui.get_special_widget('back_button'), 241 up_widget=bui.get_special_widget('back_button'), 242 ) 243 244 # Not actually using a scroll widget anymore; just an image. 245 bui.imagewidget( 246 parent=self._root_widget, 247 size=(self._scroll_width, self._scroll_height), 248 position=( 249 self._width * 0.5 - self._scroll_width * 0.5, 250 self._scroll_bottom, 251 ), 252 texture=bui.gettexture('scrollWidget'), 253 mesh_transparent=bui.getmesh('softEdgeOutside'), 254 opacity=0.4, 255 ) 256 self._tab_container: bui.Widget | None = None 257 258 self._restore_state()
Create a MainWindow given a root widget and transition info.
Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.
260 @override 261 def get_main_window_state(self) -> bui.MainWindowState: 262 # Support recreating our window for back/refresh purposes. 263 cls = type(self) 264 return bui.BasicMainWindowState( 265 create_call=lambda transition, origin_widget: cls( 266 transition=transition, origin_widget=origin_widget 267 ) 268 )
Return a WindowState to recreate this window, if supported.
@override
def
on_main_window_close(self) -> None:
Called before transitioning out a main window.
A good opportunity to save window state/etc.
def
playlist_select( self, origin_widget: _bauiv1.Widget, context: bauiv1lib.play.PlaylistSelectContext) -> None:
274 def playlist_select( 275 self, 276 origin_widget: bui.Widget, 277 context: PlaylistSelectContext, 278 ) -> None: 279 """Called by the private-hosting tab to select a playlist.""" 280 from bauiv1lib.play import PlayWindow 281 282 # Avoid redundant window spawns. 283 if not self.main_window_has_control(): 284 return 285 286 playwindow = PlayWindow( 287 origin_widget=origin_widget, playlist_select_context=context 288 ) 289 self.main_window_replace(playwindow) 290 291 # Grab the newly-set main-window's back-state; that will lead us 292 # back here once we're done going down our main-window 293 # rabbit-hole for playlist selection. 294 context.back_state = playwindow.main_window_back_state
Called by the private-hosting tab to select a playlist.
class
GatherWindow.TabID(enum.Enum):
64 class TabID(Enum): 65 """Our available tab types.""" 66 67 ABOUT = 'about' 68 INTERNET = 'internet' 69 PRIVATE = 'private' 70 NEARBY = 'nearby' 71 MANUAL = 'manual'
Our available tab types.