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-statements 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 = 1640 if uiscale is bui.UIScale.SMALL else 1040 92 x_offs = 200 if uiscale is bui.UIScale.SMALL else 0 93 y_offs = -65 if uiscale is bui.UIScale.SMALL else 0 94 self._height = ( 95 650 96 if uiscale is bui.UIScale.SMALL 97 else 680 if uiscale is bui.UIScale.MEDIUM else 800 98 ) 99 self._current_tab: GatherWindow.TabID | None = None 100 extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 101 self._r = 'gatherWindow' 102 103 super().__init__( 104 root_widget=bui.containerwidget( 105 size=(self._width, self._height + extra_top), 106 toolbar_visibility=( 107 'menu_tokens' 108 if uiscale is bui.UIScale.SMALL 109 else 'menu_full' 110 ), 111 scale=( 112 1.15 113 if uiscale is bui.UIScale.SMALL 114 else 0.95 if uiscale is bui.UIScale.MEDIUM else 0.7 115 ), 116 stack_offset=( 117 (0, 0) 118 if uiscale is bui.UIScale.SMALL 119 else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) 120 ), 121 ), 122 transition=transition, 123 origin_widget=origin_widget, 124 ) 125 126 if uiscale is bui.UIScale.SMALL: 127 bui.containerwidget( 128 edit=self._root_widget, on_cancel_call=self.main_window_back 129 ) 130 self._back_button = None 131 else: 132 self._back_button = btn = bui.buttonwidget( 133 parent=self._root_widget, 134 position=(70 + x_offs, self._height - 74 + y_offs), 135 size=(140, 60), 136 scale=1.1, 137 autoselect=True, 138 label=bui.Lstr(resource='backText'), 139 button_type='back', 140 on_activate_call=self.main_window_back, 141 ) 142 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 143 bui.buttonwidget( 144 edit=btn, 145 button_type='backSmall', 146 position=(70 + x_offs, self._height - 78), 147 size=(60, 60), 148 label=bui.charstr(bui.SpecialChar.BACK), 149 ) 150 151 condensed = uiscale is not bui.UIScale.LARGE 152 t_offs_y = ( 153 0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 33 154 ) 155 bui.textwidget( 156 parent=self._root_widget, 157 position=(self._width * 0.5, self._height - 42 + t_offs_y + y_offs), 158 size=(0, 0), 159 color=bui.app.ui_v1.title_color, 160 scale=( 161 1.5 162 if not condensed 163 else 1.0 if uiscale is bui.UIScale.MEDIUM else 1.0 164 ), 165 h_align='center', 166 v_align='center', 167 text=bui.Lstr(resource=f'{self._r}.titleText'), 168 maxwidth=320, 169 ) 170 171 scroll_buffer_h = 130 + 2 * x_offs 172 tab_buffer_h = (320 if condensed else 250) + 2 * x_offs 173 174 # Build up the set of tabs we want. 175 tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ 176 (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) 177 ] 178 if plus.get_v1_account_misc_read_val('enablePublicParties', True): 179 tabdefs.append( 180 ( 181 self.TabID.INTERNET, 182 bui.Lstr(resource=f'{self._r}.publicText'), 183 ) 184 ) 185 tabdefs.append( 186 (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) 187 ) 188 tabdefs.append( 189 (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) 190 ) 191 tabdefs.append( 192 (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) 193 ) 194 195 # On small UI, push our tabs up closer to the top of the screen to 196 # save a bit of space. 197 tabs_top_extra = 42 if condensed else 0 198 self._tab_row = TabRow( 199 self._root_widget, 200 tabdefs, 201 pos=( 202 tab_buffer_h * 0.5, 203 self._height - 130 + tabs_top_extra + y_offs, 204 ), 205 size=(self._width - tab_buffer_h, 50), 206 on_select_call=bui.WeakCall(self._set_tab), 207 ) 208 209 # Now instantiate handlers for these tabs. 210 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 211 self.TabID.ABOUT: AboutGatherTab, 212 self.TabID.MANUAL: ManualGatherTab, 213 self.TabID.PRIVATE: PrivateGatherTab, 214 self.TabID.INTERNET: PublicGatherTab, 215 self.TabID.NEARBY: NearbyGatherTab, 216 } 217 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 218 for tab_id in self._tab_row.tabs: 219 tabtype = tabtypes.get(tab_id) 220 if tabtype is not None: 221 self._tabs[tab_id] = tabtype(self) 222 223 bui.widget( 224 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 225 right_widget=bui.get_special_widget('squad_button'), 226 ) 227 if uiscale is bui.UIScale.SMALL: 228 bui.widget( 229 edit=self._tab_row.tabs[tabdefs[0][0]].button, 230 left_widget=bui.get_special_widget('back_button'), 231 ) 232 233 self._scroll_width = self._width - scroll_buffer_h 234 self._scroll_height = ( 235 self._height 236 - (270.0 if uiscale is bui.UIScale.SMALL else 180.0) 237 + tabs_top_extra 238 ) 239 240 self._scroll_left = (self._width - self._scroll_width) * 0.5 241 self._scroll_bottom = ( 242 self._height 243 - self._scroll_height 244 - 79 245 - 48 246 + tabs_top_extra 247 + y_offs 248 ) 249 buffer_h = 10 250 buffer_v = 4 251 252 # Not actually using a scroll widget anymore; just an image. 253 bui.imagewidget( 254 parent=self._root_widget, 255 position=( 256 self._scroll_left - buffer_h, 257 self._scroll_bottom - buffer_v, 258 ), 259 size=( 260 self._scroll_width + 2 * buffer_h, 261 self._scroll_height + 2 * buffer_v, 262 ), 263 texture=bui.gettexture('scrollWidget'), 264 mesh_transparent=bui.getmesh('softEdgeOutside'), 265 ) 266 self._tab_container: bui.Widget | None = None 267 268 self._restore_state() 269 270 @override 271 def get_main_window_state(self) -> bui.MainWindowState: 272 # Support recreating our window for back/refresh purposes. 273 cls = type(self) 274 return bui.BasicMainWindowState( 275 create_call=lambda transition, origin_widget: cls( 276 transition=transition, origin_widget=origin_widget 277 ) 278 ) 279 280 @override 281 def on_main_window_close(self) -> None: 282 self._save_state() 283 284 def playlist_select( 285 self, 286 origin_widget: bui.Widget, 287 context: PlaylistSelectContext, 288 ) -> None: 289 """Called by the private-hosting tab to select a playlist.""" 290 from bauiv1lib.play import PlayWindow 291 292 # Avoid redundant window spawns. 293 if not self.main_window_has_control(): 294 return 295 296 playwindow = PlayWindow( 297 origin_widget=origin_widget, playlist_select_context=context 298 ) 299 self.main_window_replace(playwindow) 300 301 # Grab the newly-set main-window's back-state; that will lead us 302 # back here once we're done going down our main-window 303 # rabbit-hole for playlist selection. 304 context.back_state = playwindow.main_window_back_state 305 306 def _set_tab(self, tab_id: TabID) -> None: 307 if self._current_tab is tab_id: 308 return 309 prev_tab_id = self._current_tab 310 self._current_tab = tab_id 311 312 # We wanna preserve our current tab between runs. 313 cfg = bui.app.config 314 cfg['Gather Tab'] = tab_id.value 315 cfg.commit() 316 317 # Update tab colors based on which is selected. 318 self._tab_row.update_appearance(tab_id) 319 320 if prev_tab_id is not None: 321 prev_tab = self._tabs.get(prev_tab_id) 322 if prev_tab is not None: 323 prev_tab.on_deactivate() 324 325 # Clear up prev container if it hasn't been done. 326 if self._tab_container: 327 self._tab_container.delete() 328 329 tab = self._tabs.get(tab_id) 330 if tab is not None: 331 self._tab_container = tab.on_activate( 332 self._root_widget, 333 self._tab_row.tabs[tab_id].button, 334 self._scroll_width, 335 self._scroll_height, 336 self._scroll_left, 337 self._scroll_bottom, 338 ) 339 return 340 341 def _save_state(self) -> None: 342 try: 343 for tab in self._tabs.values(): 344 tab.save_state() 345 346 sel = self._root_widget.get_selected_child() 347 selected_tab_ids = [ 348 tab_id 349 for tab_id, tab in self._tab_row.tabs.items() 350 if sel == tab.button 351 ] 352 if sel == self._back_button: 353 sel_name = 'Back' 354 elif selected_tab_ids: 355 assert len(selected_tab_ids) == 1 356 sel_name = f'Tab:{selected_tab_ids[0].value}' 357 elif sel == self._tab_container: 358 sel_name = 'TabContainer' 359 else: 360 raise ValueError(f'unrecognized selection: \'{sel}\'') 361 assert bui.app.classic is not None 362 bui.app.ui_v1.window_states[type(self)] = { 363 'sel_name': sel_name, 364 } 365 except Exception: 366 logging.exception('Error saving state for %s.', self) 367 368 def _restore_state(self) -> None: 369 try: 370 for tab in self._tabs.values(): 371 tab.restore_state() 372 373 sel: bui.Widget | None 374 assert bui.app.classic is not None 375 winstate = bui.app.ui_v1.window_states.get(type(self), {}) 376 sel_name = winstate.get('sel_name', None) 377 assert isinstance(sel_name, (str, type(None))) 378 current_tab = self.TabID.ABOUT 379 gather_tab_val = bui.app.config.get('Gather Tab') 380 try: 381 stored_tab = self.TabID(gather_tab_val) 382 if stored_tab in self._tab_row.tabs: 383 current_tab = stored_tab 384 except ValueError: 385 pass 386 self._set_tab(current_tab) 387 if sel_name == 'Back': 388 sel = self._back_button 389 elif sel_name == 'TabContainer': 390 sel = self._tab_container 391 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 392 try: 393 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 394 except ValueError: 395 sel_tab_id = self.TabID.ABOUT 396 sel = self._tab_row.tabs[sel_tab_id].button 397 else: 398 sel = self._tab_row.tabs[current_tab].button 399 bui.containerwidget(edit=self._root_widget, selected_child=sel) 400 401 except Exception: 402 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-statements 79 # pylint: disable=too-many-locals 80 # pylint: disable=cyclic-import 81 from bauiv1lib.gather.abouttab import AboutGatherTab 82 from bauiv1lib.gather.manualtab import ManualGatherTab 83 from bauiv1lib.gather.privatetab import PrivateGatherTab 84 from bauiv1lib.gather.publictab import PublicGatherTab 85 from bauiv1lib.gather.nearbytab import NearbyGatherTab 86 87 plus = bui.app.plus 88 assert plus is not None 89 90 bui.set_analytics_screen('Gather Window') 91 uiscale = bui.app.ui_v1.uiscale 92 self._width = 1640 if uiscale is bui.UIScale.SMALL else 1040 93 x_offs = 200 if uiscale is bui.UIScale.SMALL else 0 94 y_offs = -65 if uiscale is bui.UIScale.SMALL else 0 95 self._height = ( 96 650 97 if uiscale is bui.UIScale.SMALL 98 else 680 if uiscale is bui.UIScale.MEDIUM else 800 99 ) 100 self._current_tab: GatherWindow.TabID | None = None 101 extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 102 self._r = 'gatherWindow' 103 104 super().__init__( 105 root_widget=bui.containerwidget( 106 size=(self._width, self._height + extra_top), 107 toolbar_visibility=( 108 'menu_tokens' 109 if uiscale is bui.UIScale.SMALL 110 else 'menu_full' 111 ), 112 scale=( 113 1.15 114 if uiscale is bui.UIScale.SMALL 115 else 0.95 if uiscale is bui.UIScale.MEDIUM else 0.7 116 ), 117 stack_offset=( 118 (0, 0) 119 if uiscale is bui.UIScale.SMALL 120 else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) 121 ), 122 ), 123 transition=transition, 124 origin_widget=origin_widget, 125 ) 126 127 if uiscale is bui.UIScale.SMALL: 128 bui.containerwidget( 129 edit=self._root_widget, on_cancel_call=self.main_window_back 130 ) 131 self._back_button = None 132 else: 133 self._back_button = btn = bui.buttonwidget( 134 parent=self._root_widget, 135 position=(70 + x_offs, self._height - 74 + y_offs), 136 size=(140, 60), 137 scale=1.1, 138 autoselect=True, 139 label=bui.Lstr(resource='backText'), 140 button_type='back', 141 on_activate_call=self.main_window_back, 142 ) 143 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 144 bui.buttonwidget( 145 edit=btn, 146 button_type='backSmall', 147 position=(70 + x_offs, self._height - 78), 148 size=(60, 60), 149 label=bui.charstr(bui.SpecialChar.BACK), 150 ) 151 152 condensed = uiscale is not bui.UIScale.LARGE 153 t_offs_y = ( 154 0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 33 155 ) 156 bui.textwidget( 157 parent=self._root_widget, 158 position=(self._width * 0.5, self._height - 42 + t_offs_y + y_offs), 159 size=(0, 0), 160 color=bui.app.ui_v1.title_color, 161 scale=( 162 1.5 163 if not condensed 164 else 1.0 if uiscale is bui.UIScale.MEDIUM else 1.0 165 ), 166 h_align='center', 167 v_align='center', 168 text=bui.Lstr(resource=f'{self._r}.titleText'), 169 maxwidth=320, 170 ) 171 172 scroll_buffer_h = 130 + 2 * x_offs 173 tab_buffer_h = (320 if condensed else 250) + 2 * x_offs 174 175 # Build up the set of tabs we want. 176 tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ 177 (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) 178 ] 179 if plus.get_v1_account_misc_read_val('enablePublicParties', True): 180 tabdefs.append( 181 ( 182 self.TabID.INTERNET, 183 bui.Lstr(resource=f'{self._r}.publicText'), 184 ) 185 ) 186 tabdefs.append( 187 (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) 188 ) 189 tabdefs.append( 190 (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) 191 ) 192 tabdefs.append( 193 (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) 194 ) 195 196 # On small UI, push our tabs up closer to the top of the screen to 197 # save a bit of space. 198 tabs_top_extra = 42 if condensed else 0 199 self._tab_row = TabRow( 200 self._root_widget, 201 tabdefs, 202 pos=( 203 tab_buffer_h * 0.5, 204 self._height - 130 + tabs_top_extra + y_offs, 205 ), 206 size=(self._width - tab_buffer_h, 50), 207 on_select_call=bui.WeakCall(self._set_tab), 208 ) 209 210 # Now instantiate handlers for these tabs. 211 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 212 self.TabID.ABOUT: AboutGatherTab, 213 self.TabID.MANUAL: ManualGatherTab, 214 self.TabID.PRIVATE: PrivateGatherTab, 215 self.TabID.INTERNET: PublicGatherTab, 216 self.TabID.NEARBY: NearbyGatherTab, 217 } 218 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 219 for tab_id in self._tab_row.tabs: 220 tabtype = tabtypes.get(tab_id) 221 if tabtype is not None: 222 self._tabs[tab_id] = tabtype(self) 223 224 bui.widget( 225 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 226 right_widget=bui.get_special_widget('squad_button'), 227 ) 228 if uiscale is bui.UIScale.SMALL: 229 bui.widget( 230 edit=self._tab_row.tabs[tabdefs[0][0]].button, 231 left_widget=bui.get_special_widget('back_button'), 232 ) 233 234 self._scroll_width = self._width - scroll_buffer_h 235 self._scroll_height = ( 236 self._height 237 - (270.0 if uiscale is bui.UIScale.SMALL else 180.0) 238 + tabs_top_extra 239 ) 240 241 self._scroll_left = (self._width - self._scroll_width) * 0.5 242 self._scroll_bottom = ( 243 self._height 244 - self._scroll_height 245 - 79 246 - 48 247 + tabs_top_extra 248 + y_offs 249 ) 250 buffer_h = 10 251 buffer_v = 4 252 253 # Not actually using a scroll widget anymore; just an image. 254 bui.imagewidget( 255 parent=self._root_widget, 256 position=( 257 self._scroll_left - buffer_h, 258 self._scroll_bottom - buffer_v, 259 ), 260 size=( 261 self._scroll_width + 2 * buffer_h, 262 self._scroll_height + 2 * buffer_v, 263 ), 264 texture=bui.gettexture('scrollWidget'), 265 mesh_transparent=bui.getmesh('softEdgeOutside'), 266 ) 267 self._tab_container: bui.Widget | None = None 268 269 self._restore_state() 270 271 @override 272 def get_main_window_state(self) -> bui.MainWindowState: 273 # Support recreating our window for back/refresh purposes. 274 cls = type(self) 275 return bui.BasicMainWindowState( 276 create_call=lambda transition, origin_widget: cls( 277 transition=transition, origin_widget=origin_widget 278 ) 279 ) 280 281 @override 282 def on_main_window_close(self) -> None: 283 self._save_state() 284 285 def playlist_select( 286 self, 287 origin_widget: bui.Widget, 288 context: PlaylistSelectContext, 289 ) -> None: 290 """Called by the private-hosting tab to select a playlist.""" 291 from bauiv1lib.play import PlayWindow 292 293 # Avoid redundant window spawns. 294 if not self.main_window_has_control(): 295 return 296 297 playwindow = PlayWindow( 298 origin_widget=origin_widget, playlist_select_context=context 299 ) 300 self.main_window_replace(playwindow) 301 302 # Grab the newly-set main-window's back-state; that will lead us 303 # back here once we're done going down our main-window 304 # rabbit-hole for playlist selection. 305 context.back_state = playwindow.main_window_back_state 306 307 def _set_tab(self, tab_id: TabID) -> None: 308 if self._current_tab is tab_id: 309 return 310 prev_tab_id = self._current_tab 311 self._current_tab = tab_id 312 313 # We wanna preserve our current tab between runs. 314 cfg = bui.app.config 315 cfg['Gather Tab'] = tab_id.value 316 cfg.commit() 317 318 # Update tab colors based on which is selected. 319 self._tab_row.update_appearance(tab_id) 320 321 if prev_tab_id is not None: 322 prev_tab = self._tabs.get(prev_tab_id) 323 if prev_tab is not None: 324 prev_tab.on_deactivate() 325 326 # Clear up prev container if it hasn't been done. 327 if self._tab_container: 328 self._tab_container.delete() 329 330 tab = self._tabs.get(tab_id) 331 if tab is not None: 332 self._tab_container = tab.on_activate( 333 self._root_widget, 334 self._tab_row.tabs[tab_id].button, 335 self._scroll_width, 336 self._scroll_height, 337 self._scroll_left, 338 self._scroll_bottom, 339 ) 340 return 341 342 def _save_state(self) -> None: 343 try: 344 for tab in self._tabs.values(): 345 tab.save_state() 346 347 sel = self._root_widget.get_selected_child() 348 selected_tab_ids = [ 349 tab_id 350 for tab_id, tab in self._tab_row.tabs.items() 351 if sel == tab.button 352 ] 353 if sel == self._back_button: 354 sel_name = 'Back' 355 elif selected_tab_ids: 356 assert len(selected_tab_ids) == 1 357 sel_name = f'Tab:{selected_tab_ids[0].value}' 358 elif sel == self._tab_container: 359 sel_name = 'TabContainer' 360 else: 361 raise ValueError(f'unrecognized selection: \'{sel}\'') 362 assert bui.app.classic is not None 363 bui.app.ui_v1.window_states[type(self)] = { 364 'sel_name': sel_name, 365 } 366 except Exception: 367 logging.exception('Error saving state for %s.', self) 368 369 def _restore_state(self) -> None: 370 try: 371 for tab in self._tabs.values(): 372 tab.restore_state() 373 374 sel: bui.Widget | None 375 assert bui.app.classic is not None 376 winstate = bui.app.ui_v1.window_states.get(type(self), {}) 377 sel_name = winstate.get('sel_name', None) 378 assert isinstance(sel_name, (str, type(None))) 379 current_tab = self.TabID.ABOUT 380 gather_tab_val = bui.app.config.get('Gather Tab') 381 try: 382 stored_tab = self.TabID(gather_tab_val) 383 if stored_tab in self._tab_row.tabs: 384 current_tab = stored_tab 385 except ValueError: 386 pass 387 self._set_tab(current_tab) 388 if sel_name == 'Back': 389 sel = self._back_button 390 elif sel_name == 'TabContainer': 391 sel = self._tab_container 392 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 393 try: 394 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 395 except ValueError: 396 sel_tab_id = self.TabID.ABOUT 397 sel = self._tab_row.tabs[sel_tab_id].button 398 else: 399 sel = self._tab_row.tabs[current_tab].button 400 bui.containerwidget(edit=self._root_widget, selected_child=sel) 401 402 except Exception: 403 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-statements 79 # pylint: disable=too-many-locals 80 # pylint: disable=cyclic-import 81 from bauiv1lib.gather.abouttab import AboutGatherTab 82 from bauiv1lib.gather.manualtab import ManualGatherTab 83 from bauiv1lib.gather.privatetab import PrivateGatherTab 84 from bauiv1lib.gather.publictab import PublicGatherTab 85 from bauiv1lib.gather.nearbytab import NearbyGatherTab 86 87 plus = bui.app.plus 88 assert plus is not None 89 90 bui.set_analytics_screen('Gather Window') 91 uiscale = bui.app.ui_v1.uiscale 92 self._width = 1640 if uiscale is bui.UIScale.SMALL else 1040 93 x_offs = 200 if uiscale is bui.UIScale.SMALL else 0 94 y_offs = -65 if uiscale is bui.UIScale.SMALL else 0 95 self._height = ( 96 650 97 if uiscale is bui.UIScale.SMALL 98 else 680 if uiscale is bui.UIScale.MEDIUM else 800 99 ) 100 self._current_tab: GatherWindow.TabID | None = None 101 extra_top = 20 if uiscale is bui.UIScale.SMALL else 0 102 self._r = 'gatherWindow' 103 104 super().__init__( 105 root_widget=bui.containerwidget( 106 size=(self._width, self._height + extra_top), 107 toolbar_visibility=( 108 'menu_tokens' 109 if uiscale is bui.UIScale.SMALL 110 else 'menu_full' 111 ), 112 scale=( 113 1.15 114 if uiscale is bui.UIScale.SMALL 115 else 0.95 if uiscale is bui.UIScale.MEDIUM else 0.7 116 ), 117 stack_offset=( 118 (0, 0) 119 if uiscale is bui.UIScale.SMALL 120 else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) 121 ), 122 ), 123 transition=transition, 124 origin_widget=origin_widget, 125 ) 126 127 if uiscale is bui.UIScale.SMALL: 128 bui.containerwidget( 129 edit=self._root_widget, on_cancel_call=self.main_window_back 130 ) 131 self._back_button = None 132 else: 133 self._back_button = btn = bui.buttonwidget( 134 parent=self._root_widget, 135 position=(70 + x_offs, self._height - 74 + y_offs), 136 size=(140, 60), 137 scale=1.1, 138 autoselect=True, 139 label=bui.Lstr(resource='backText'), 140 button_type='back', 141 on_activate_call=self.main_window_back, 142 ) 143 bui.containerwidget(edit=self._root_widget, cancel_button=btn) 144 bui.buttonwidget( 145 edit=btn, 146 button_type='backSmall', 147 position=(70 + x_offs, self._height - 78), 148 size=(60, 60), 149 label=bui.charstr(bui.SpecialChar.BACK), 150 ) 151 152 condensed = uiscale is not bui.UIScale.LARGE 153 t_offs_y = ( 154 0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 33 155 ) 156 bui.textwidget( 157 parent=self._root_widget, 158 position=(self._width * 0.5, self._height - 42 + t_offs_y + y_offs), 159 size=(0, 0), 160 color=bui.app.ui_v1.title_color, 161 scale=( 162 1.5 163 if not condensed 164 else 1.0 if uiscale is bui.UIScale.MEDIUM else 1.0 165 ), 166 h_align='center', 167 v_align='center', 168 text=bui.Lstr(resource=f'{self._r}.titleText'), 169 maxwidth=320, 170 ) 171 172 scroll_buffer_h = 130 + 2 * x_offs 173 tab_buffer_h = (320 if condensed else 250) + 2 * x_offs 174 175 # Build up the set of tabs we want. 176 tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [ 177 (self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText')) 178 ] 179 if plus.get_v1_account_misc_read_val('enablePublicParties', True): 180 tabdefs.append( 181 ( 182 self.TabID.INTERNET, 183 bui.Lstr(resource=f'{self._r}.publicText'), 184 ) 185 ) 186 tabdefs.append( 187 (self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText')) 188 ) 189 tabdefs.append( 190 (self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText')) 191 ) 192 tabdefs.append( 193 (self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText')) 194 ) 195 196 # On small UI, push our tabs up closer to the top of the screen to 197 # save a bit of space. 198 tabs_top_extra = 42 if condensed else 0 199 self._tab_row = TabRow( 200 self._root_widget, 201 tabdefs, 202 pos=( 203 tab_buffer_h * 0.5, 204 self._height - 130 + tabs_top_extra + y_offs, 205 ), 206 size=(self._width - tab_buffer_h, 50), 207 on_select_call=bui.WeakCall(self._set_tab), 208 ) 209 210 # Now instantiate handlers for these tabs. 211 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 212 self.TabID.ABOUT: AboutGatherTab, 213 self.TabID.MANUAL: ManualGatherTab, 214 self.TabID.PRIVATE: PrivateGatherTab, 215 self.TabID.INTERNET: PublicGatherTab, 216 self.TabID.NEARBY: NearbyGatherTab, 217 } 218 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 219 for tab_id in self._tab_row.tabs: 220 tabtype = tabtypes.get(tab_id) 221 if tabtype is not None: 222 self._tabs[tab_id] = tabtype(self) 223 224 bui.widget( 225 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 226 right_widget=bui.get_special_widget('squad_button'), 227 ) 228 if uiscale is bui.UIScale.SMALL: 229 bui.widget( 230 edit=self._tab_row.tabs[tabdefs[0][0]].button, 231 left_widget=bui.get_special_widget('back_button'), 232 ) 233 234 self._scroll_width = self._width - scroll_buffer_h 235 self._scroll_height = ( 236 self._height 237 - (270.0 if uiscale is bui.UIScale.SMALL else 180.0) 238 + tabs_top_extra 239 ) 240 241 self._scroll_left = (self._width - self._scroll_width) * 0.5 242 self._scroll_bottom = ( 243 self._height 244 - self._scroll_height 245 - 79 246 - 48 247 + tabs_top_extra 248 + y_offs 249 ) 250 buffer_h = 10 251 buffer_v = 4 252 253 # Not actually using a scroll widget anymore; just an image. 254 bui.imagewidget( 255 parent=self._root_widget, 256 position=( 257 self._scroll_left - buffer_h, 258 self._scroll_bottom - buffer_v, 259 ), 260 size=( 261 self._scroll_width + 2 * buffer_h, 262 self._scroll_height + 2 * buffer_v, 263 ), 264 texture=bui.gettexture('scrollWidget'), 265 mesh_transparent=bui.getmesh('softEdgeOutside'), 266 ) 267 self._tab_container: bui.Widget | None = None 268 269 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.
271 @override 272 def get_main_window_state(self) -> bui.MainWindowState: 273 # Support recreating our window for back/refresh purposes. 274 cls = type(self) 275 return bui.BasicMainWindowState( 276 create_call=lambda transition, origin_widget: cls( 277 transition=transition, origin_widget=origin_widget 278 ) 279 )
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:
285 def playlist_select( 286 self, 287 origin_widget: bui.Widget, 288 context: PlaylistSelectContext, 289 ) -> None: 290 """Called by the private-hosting tab to select a playlist.""" 291 from bauiv1lib.play import PlayWindow 292 293 # Avoid redundant window spawns. 294 if not self.main_window_has_control(): 295 return 296 297 playwindow = PlayWindow( 298 origin_widget=origin_widget, playlist_select_context=context 299 ) 300 self.main_window_replace(playwindow) 301 302 # Grab the newly-set main-window's back-state; that will lead us 303 # back here once we're done going down our main-window 304 # rabbit-hole for playlist selection. 305 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.