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 opacity=0.4, 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)
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 opacity=0.4, 267 ) 268 self._tab_container: bui.Widget | None = None 269 270 self._restore_state() 271 272 @override 273 def get_main_window_state(self) -> bui.MainWindowState: 274 # Support recreating our window for back/refresh purposes. 275 cls = type(self) 276 return bui.BasicMainWindowState( 277 create_call=lambda transition, origin_widget: cls( 278 transition=transition, origin_widget=origin_widget 279 ) 280 ) 281 282 @override 283 def on_main_window_close(self) -> None: 284 self._save_state() 285 286 def playlist_select( 287 self, 288 origin_widget: bui.Widget, 289 context: PlaylistSelectContext, 290 ) -> None: 291 """Called by the private-hosting tab to select a playlist.""" 292 from bauiv1lib.play import PlayWindow 293 294 # Avoid redundant window spawns. 295 if not self.main_window_has_control(): 296 return 297 298 playwindow = PlayWindow( 299 origin_widget=origin_widget, playlist_select_context=context 300 ) 301 self.main_window_replace(playwindow) 302 303 # Grab the newly-set main-window's back-state; that will lead us 304 # back here once we're done going down our main-window 305 # rabbit-hole for playlist selection. 306 context.back_state = playwindow.main_window_back_state 307 308 def _set_tab(self, tab_id: TabID) -> None: 309 if self._current_tab is tab_id: 310 return 311 prev_tab_id = self._current_tab 312 self._current_tab = tab_id 313 314 # We wanna preserve our current tab between runs. 315 cfg = bui.app.config 316 cfg['Gather Tab'] = tab_id.value 317 cfg.commit() 318 319 # Update tab colors based on which is selected. 320 self._tab_row.update_appearance(tab_id) 321 322 if prev_tab_id is not None: 323 prev_tab = self._tabs.get(prev_tab_id) 324 if prev_tab is not None: 325 prev_tab.on_deactivate() 326 327 # Clear up prev container if it hasn't been done. 328 if self._tab_container: 329 self._tab_container.delete() 330 331 tab = self._tabs.get(tab_id) 332 if tab is not None: 333 self._tab_container = tab.on_activate( 334 self._root_widget, 335 self._tab_row.tabs[tab_id].button, 336 self._scroll_width, 337 self._scroll_height, 338 self._scroll_left, 339 self._scroll_bottom, 340 ) 341 return 342 343 def _save_state(self) -> None: 344 try: 345 for tab in self._tabs.values(): 346 tab.save_state() 347 348 sel = self._root_widget.get_selected_child() 349 selected_tab_ids = [ 350 tab_id 351 for tab_id, tab in self._tab_row.tabs.items() 352 if sel == tab.button 353 ] 354 if sel == self._back_button: 355 sel_name = 'Back' 356 elif selected_tab_ids: 357 assert len(selected_tab_ids) == 1 358 sel_name = f'Tab:{selected_tab_ids[0].value}' 359 elif sel == self._tab_container: 360 sel_name = 'TabContainer' 361 else: 362 raise ValueError(f'unrecognized selection: \'{sel}\'') 363 assert bui.app.classic is not None 364 bui.app.ui_v1.window_states[type(self)] = { 365 'sel_name': sel_name, 366 } 367 except Exception: 368 logging.exception('Error saving state for %s.', self) 369 370 def _restore_state(self) -> None: 371 try: 372 for tab in self._tabs.values(): 373 tab.restore_state() 374 375 sel: bui.Widget | None 376 assert bui.app.classic is not None 377 winstate = bui.app.ui_v1.window_states.get(type(self), {}) 378 sel_name = winstate.get('sel_name', None) 379 assert isinstance(sel_name, (str, type(None))) 380 current_tab = self.TabID.ABOUT 381 gather_tab_val = bui.app.config.get('Gather Tab') 382 try: 383 stored_tab = self.TabID(gather_tab_val) 384 if stored_tab in self._tab_row.tabs: 385 current_tab = stored_tab 386 except ValueError: 387 pass 388 self._set_tab(current_tab) 389 if sel_name == 'Back': 390 sel = self._back_button 391 elif sel_name == 'TabContainer': 392 sel = self._tab_container 393 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 394 try: 395 sel_tab_id = self.TabID(sel_name.split(':')[-1]) 396 except ValueError: 397 sel_tab_id = self.TabID.ABOUT 398 sel = self._tab_row.tabs[sel_tab_id].button 399 else: 400 sel = self._tab_row.tabs[current_tab].button 401 bui.containerwidget(edit=self._root_widget, selected_child=sel) 402 403 except Exception: 404 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 opacity=0.4, 267 ) 268 self._tab_container: bui.Widget | None = None 269 270 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.
272 @override 273 def get_main_window_state(self) -> bui.MainWindowState: 274 # Support recreating our window for back/refresh purposes. 275 cls = type(self) 276 return bui.BasicMainWindowState( 277 create_call=lambda transition, origin_widget: cls( 278 transition=transition, origin_widget=origin_widget 279 ) 280 )
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:
286 def playlist_select( 287 self, 288 origin_widget: bui.Widget, 289 context: PlaylistSelectContext, 290 ) -> None: 291 """Called by the private-hosting tab to select a playlist.""" 292 from bauiv1lib.play import PlayWindow 293 294 # Avoid redundant window spawns. 295 if not self.main_window_has_control(): 296 return 297 298 playwindow = PlayWindow( 299 origin_widget=origin_widget, playlist_select_context=context 300 ) 301 self.main_window_replace(playwindow) 302 303 # Grab the newly-set main-window's back-state; that will lead us 304 # back here once we're done going down our main-window 305 # rabbit-hole for playlist selection. 306 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.