bastd.ui.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 8from enum import Enum 9from typing import TYPE_CHECKING 10 11import ba 12import ba.internal 13from bastd.ui.tabs import TabRow 14 15if TYPE_CHECKING: 16 pass 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 ba.NotFoundError("GatherTab's window no longer exists.") 31 return window 32 33 def on_activate( 34 self, 35 parent_widget: ba.Widget, 36 tab_button: ba.Widget, 37 region_width: float, 38 region_height: float, 39 region_left: float, 40 region_bottom: float, 41 ) -> ba.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 raise RuntimeError('Should not get here.') 48 49 def on_deactivate(self) -> None: 50 """Called when the tab will no longer be the active one.""" 51 52 def save_state(self) -> None: 53 """Called when the parent window is saving state.""" 54 55 def restore_state(self) -> None: 56 """Called when the parent window is restoring state.""" 57 58 59class GatherWindow(ba.Window): 60 """Window for joining/inviting friends.""" 61 62 class TabID(Enum): 63 """Our available tab types.""" 64 65 ABOUT = 'about' 66 INTERNET = 'internet' 67 PRIVATE = 'private' 68 NEARBY = 'nearby' 69 MANUAL = 'manual' 70 71 def __init__( 72 self, 73 transition: str | None = 'in_right', 74 origin_widget: ba.Widget | None = None, 75 ): 76 # pylint: disable=too-many-statements 77 # pylint: disable=too-many-locals 78 # pylint: disable=cyclic-import 79 from bastd.ui.gather.abouttab import AboutGatherTab 80 from bastd.ui.gather.manualtab import ManualGatherTab 81 from bastd.ui.gather.privatetab import PrivateGatherTab 82 from bastd.ui.gather.publictab import PublicGatherTab 83 from bastd.ui.gather.nearbytab import NearbyGatherTab 84 85 ba.set_analytics_screen('Gather Window') 86 scale_origin: tuple[float, float] | None 87 if origin_widget is not None: 88 self._transition_out = 'out_scale' 89 scale_origin = origin_widget.get_screen_space_center() 90 transition = 'in_scale' 91 else: 92 self._transition_out = 'out_right' 93 scale_origin = None 94 ba.app.ui.set_main_menu_location('Gather') 95 ba.internal.set_party_icon_always_visible(True) 96 uiscale = ba.app.ui.uiscale 97 self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 98 x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 99 self._height = ( 100 582 101 if uiscale is ba.UIScale.SMALL 102 else 680 103 if uiscale is ba.UIScale.MEDIUM 104 else 800 105 ) 106 self._current_tab: GatherWindow.TabID | None = None 107 extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 108 self._r = 'gatherWindow' 109 110 super().__init__( 111 root_widget=ba.containerwidget( 112 size=(self._width, self._height + extra_top), 113 transition=transition, 114 toolbar_visibility='menu_minimal', 115 scale_origin_stack_offset=scale_origin, 116 scale=( 117 1.3 118 if uiscale is ba.UIScale.SMALL 119 else 0.97 120 if uiscale is ba.UIScale.MEDIUM 121 else 0.8 122 ), 123 stack_offset=(0, -11) 124 if uiscale is ba.UIScale.SMALL 125 else (0, 0) 126 if uiscale is ba.UIScale.MEDIUM 127 else (0, 0), 128 ) 129 ) 130 131 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: 132 ba.containerwidget( 133 edit=self._root_widget, on_cancel_call=self._back 134 ) 135 self._back_button = None 136 else: 137 self._back_button = btn = ba.buttonwidget( 138 parent=self._root_widget, 139 position=(70 + x_offs, self._height - 74), 140 size=(140, 60), 141 scale=1.1, 142 autoselect=True, 143 label=ba.Lstr(resource='backText'), 144 button_type='back', 145 on_activate_call=self._back, 146 ) 147 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 148 ba.buttonwidget( 149 edit=btn, 150 button_type='backSmall', 151 position=(70 + x_offs, self._height - 78), 152 size=(60, 60), 153 label=ba.charstr(ba.SpecialChar.BACK), 154 ) 155 156 condensed = uiscale is not ba.UIScale.LARGE 157 t_offs_y = ( 158 0 if not condensed else 25 if uiscale is ba.UIScale.MEDIUM else 17 159 ) 160 ba.textwidget( 161 parent=self._root_widget, 162 position=(self._width * 0.5, self._height - 42 + t_offs_y), 163 size=(0, 0), 164 color=ba.app.ui.title_color, 165 scale=( 166 1.5 167 if not condensed 168 else 1.0 169 if uiscale is ba.UIScale.MEDIUM 170 else 0.6 171 ), 172 h_align='center', 173 v_align='center', 174 text=ba.Lstr(resource=self._r + '.titleText'), 175 maxwidth=550, 176 ) 177 178 scroll_buffer_h = 130 + 2 * x_offs 179 tab_buffer_h = (320 if condensed else 250) + 2 * x_offs 180 181 # Build up the set of tabs we want. 182 tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [ 183 (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText')) 184 ] 185 if ba.internal.get_v1_account_misc_read_val( 186 'enablePublicParties', True 187 ): 188 tabdefs.append( 189 (self.TabID.INTERNET, ba.Lstr(resource=self._r + '.publicText')) 190 ) 191 tabdefs.append( 192 (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')) 193 ) 194 tabdefs.append( 195 (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')) 196 ) 197 tabdefs.append( 198 (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')) 199 ) 200 201 # On small UI, push our tabs up closer to the top of the screen to 202 # save a bit of space. 203 tabs_top_extra = 42 if condensed else 0 204 self._tab_row = TabRow( 205 self._root_widget, 206 tabdefs, 207 pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra), 208 size=(self._width - tab_buffer_h, 50), 209 on_select_call=ba.WeakCall(self._set_tab), 210 ) 211 212 # Now instantiate handlers for these tabs. 213 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 214 self.TabID.ABOUT: AboutGatherTab, 215 self.TabID.MANUAL: ManualGatherTab, 216 self.TabID.PRIVATE: PrivateGatherTab, 217 self.TabID.INTERNET: PublicGatherTab, 218 self.TabID.NEARBY: NearbyGatherTab, 219 } 220 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 221 for tab_id in self._tab_row.tabs: 222 tabtype = tabtypes.get(tab_id) 223 if tabtype is not None: 224 self._tabs[tab_id] = tabtype(self) 225 226 if ba.app.ui.use_toolbars: 227 ba.widget( 228 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 229 right_widget=ba.internal.get_special_widget('party_button'), 230 ) 231 if uiscale is ba.UIScale.SMALL: 232 ba.widget( 233 edit=self._tab_row.tabs[tabdefs[0][0]].button, 234 left_widget=ba.internal.get_special_widget('back_button'), 235 ) 236 237 self._scroll_width = self._width - scroll_buffer_h 238 self._scroll_height = self._height - 180.0 + tabs_top_extra 239 240 self._scroll_left = (self._width - self._scroll_width) * 0.5 241 self._scroll_bottom = ( 242 self._height - self._scroll_height - 79 - 48 + tabs_top_extra 243 ) 244 buffer_h = 10 245 buffer_v = 4 246 247 # Not actually using a scroll widget anymore; just an image. 248 ba.imagewidget( 249 parent=self._root_widget, 250 position=( 251 self._scroll_left - buffer_h, 252 self._scroll_bottom - buffer_v, 253 ), 254 size=( 255 self._scroll_width + 2 * buffer_h, 256 self._scroll_height + 2 * buffer_v, 257 ), 258 texture=ba.gettexture('scrollWidget'), 259 model_transparent=ba.getmodel('softEdgeOutside'), 260 ) 261 self._tab_container: ba.Widget | None = None 262 263 self._restore_state() 264 265 def __del__(self) -> None: 266 ba.internal.set_party_icon_always_visible(False) 267 268 def playlist_select(self, origin_widget: ba.Widget) -> None: 269 """Called by the private-hosting tab to select a playlist.""" 270 from bastd.ui.play import PlayWindow 271 272 self._save_state() 273 ba.containerwidget(edit=self._root_widget, transition='out_left') 274 ba.app.ui.selecting_private_party_playlist = True 275 ba.app.ui.set_main_menu_window( 276 PlayWindow(origin_widget=origin_widget).get_root_widget() 277 ) 278 279 def _set_tab(self, tab_id: TabID) -> None: 280 if self._current_tab is tab_id: 281 return 282 prev_tab_id = self._current_tab 283 self._current_tab = tab_id 284 285 # We wanna preserve our current tab between runs. 286 cfg = ba.app.config 287 cfg['Gather Tab'] = tab_id.value 288 cfg.commit() 289 290 # Update tab colors based on which is selected. 291 self._tab_row.update_appearance(tab_id) 292 293 if prev_tab_id is not None: 294 prev_tab = self._tabs.get(prev_tab_id) 295 if prev_tab is not None: 296 prev_tab.on_deactivate() 297 298 # Clear up prev container if it hasn't been done. 299 if self._tab_container: 300 self._tab_container.delete() 301 302 tab = self._tabs.get(tab_id) 303 if tab is not None: 304 self._tab_container = tab.on_activate( 305 self._root_widget, 306 self._tab_row.tabs[tab_id].button, 307 self._scroll_width, 308 self._scroll_height, 309 self._scroll_left, 310 self._scroll_bottom, 311 ) 312 return 313 314 def _save_state(self) -> None: 315 try: 316 for tab in self._tabs.values(): 317 tab.save_state() 318 319 sel = self._root_widget.get_selected_child() 320 selected_tab_ids = [ 321 tab_id 322 for tab_id, tab in self._tab_row.tabs.items() 323 if sel == tab.button 324 ] 325 if sel == self._back_button: 326 sel_name = 'Back' 327 elif selected_tab_ids: 328 assert len(selected_tab_ids) == 1 329 sel_name = f'Tab:{selected_tab_ids[0].value}' 330 elif sel == self._tab_container: 331 sel_name = 'TabContainer' 332 else: 333 raise ValueError(f'unrecognized selection: \'{sel}\'') 334 ba.app.ui.window_states[type(self)] = { 335 'sel_name': sel_name, 336 } 337 except Exception: 338 ba.print_exception(f'Error saving state for {self}.') 339 340 def _restore_state(self) -> None: 341 from efro.util import enum_by_value 342 343 try: 344 for tab in self._tabs.values(): 345 tab.restore_state() 346 347 sel: ba.Widget | None 348 winstate = ba.app.ui.window_states.get(type(self), {}) 349 sel_name = winstate.get('sel_name', None) 350 assert isinstance(sel_name, (str, type(None))) 351 current_tab = self.TabID.ABOUT 352 gather_tab_val = ba.app.config.get('Gather Tab') 353 try: 354 stored_tab = enum_by_value(self.TabID, gather_tab_val) 355 if stored_tab in self._tab_row.tabs: 356 current_tab = stored_tab 357 except ValueError: 358 pass 359 self._set_tab(current_tab) 360 if sel_name == 'Back': 361 sel = self._back_button 362 elif sel_name == 'TabContainer': 363 sel = self._tab_container 364 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 365 try: 366 sel_tab_id = enum_by_value( 367 self.TabID, sel_name.split(':')[-1] 368 ) 369 except ValueError: 370 sel_tab_id = self.TabID.ABOUT 371 sel = self._tab_row.tabs[sel_tab_id].button 372 else: 373 sel = self._tab_row.tabs[current_tab].button 374 ba.containerwidget(edit=self._root_widget, selected_child=sel) 375 except Exception: 376 ba.print_exception('Error restoring gather-win state.') 377 378 def _back(self) -> None: 379 from bastd.ui.mainmenu import MainMenuWindow 380 381 self._save_state() 382 ba.containerwidget( 383 edit=self._root_widget, transition=self._transition_out 384 ) 385 ba.app.ui.set_main_menu_window( 386 MainMenuWindow(transition='in_left').get_root_widget() 387 )
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 ba.NotFoundError("GatherTab's window no longer exists.") 32 return window 33 34 def on_activate( 35 self, 36 parent_widget: ba.Widget, 37 tab_button: ba.Widget, 38 region_width: float, 39 region_height: float, 40 region_left: float, 41 region_bottom: float, 42 ) -> ba.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 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."""
Defines a tab for use in the gather UI.
GatherTab(window: bastd.ui.gather.GatherWindow)
def
on_activate( self, parent_widget: _ba.Widget, tab_button: _ba.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float) -> _ba.Widget:
34 def on_activate( 35 self, 36 parent_widget: ba.Widget, 37 tab_button: ba.Widget, 38 region_width: float, 39 region_height: float, 40 region_left: float, 41 region_bottom: float, 42 ) -> ba.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 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(ba.ui.Window):
60class GatherWindow(ba.Window): 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: ba.Widget | None = None, 76 ): 77 # pylint: disable=too-many-statements 78 # pylint: disable=too-many-locals 79 # pylint: disable=cyclic-import 80 from bastd.ui.gather.abouttab import AboutGatherTab 81 from bastd.ui.gather.manualtab import ManualGatherTab 82 from bastd.ui.gather.privatetab import PrivateGatherTab 83 from bastd.ui.gather.publictab import PublicGatherTab 84 from bastd.ui.gather.nearbytab import NearbyGatherTab 85 86 ba.set_analytics_screen('Gather Window') 87 scale_origin: tuple[float, float] | None 88 if origin_widget is not None: 89 self._transition_out = 'out_scale' 90 scale_origin = origin_widget.get_screen_space_center() 91 transition = 'in_scale' 92 else: 93 self._transition_out = 'out_right' 94 scale_origin = None 95 ba.app.ui.set_main_menu_location('Gather') 96 ba.internal.set_party_icon_always_visible(True) 97 uiscale = ba.app.ui.uiscale 98 self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 99 x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 100 self._height = ( 101 582 102 if uiscale is ba.UIScale.SMALL 103 else 680 104 if uiscale is ba.UIScale.MEDIUM 105 else 800 106 ) 107 self._current_tab: GatherWindow.TabID | None = None 108 extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 109 self._r = 'gatherWindow' 110 111 super().__init__( 112 root_widget=ba.containerwidget( 113 size=(self._width, self._height + extra_top), 114 transition=transition, 115 toolbar_visibility='menu_minimal', 116 scale_origin_stack_offset=scale_origin, 117 scale=( 118 1.3 119 if uiscale is ba.UIScale.SMALL 120 else 0.97 121 if uiscale is ba.UIScale.MEDIUM 122 else 0.8 123 ), 124 stack_offset=(0, -11) 125 if uiscale is ba.UIScale.SMALL 126 else (0, 0) 127 if uiscale is ba.UIScale.MEDIUM 128 else (0, 0), 129 ) 130 ) 131 132 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: 133 ba.containerwidget( 134 edit=self._root_widget, on_cancel_call=self._back 135 ) 136 self._back_button = None 137 else: 138 self._back_button = btn = ba.buttonwidget( 139 parent=self._root_widget, 140 position=(70 + x_offs, self._height - 74), 141 size=(140, 60), 142 scale=1.1, 143 autoselect=True, 144 label=ba.Lstr(resource='backText'), 145 button_type='back', 146 on_activate_call=self._back, 147 ) 148 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 149 ba.buttonwidget( 150 edit=btn, 151 button_type='backSmall', 152 position=(70 + x_offs, self._height - 78), 153 size=(60, 60), 154 label=ba.charstr(ba.SpecialChar.BACK), 155 ) 156 157 condensed = uiscale is not ba.UIScale.LARGE 158 t_offs_y = ( 159 0 if not condensed else 25 if uiscale is ba.UIScale.MEDIUM else 17 160 ) 161 ba.textwidget( 162 parent=self._root_widget, 163 position=(self._width * 0.5, self._height - 42 + t_offs_y), 164 size=(0, 0), 165 color=ba.app.ui.title_color, 166 scale=( 167 1.5 168 if not condensed 169 else 1.0 170 if uiscale is ba.UIScale.MEDIUM 171 else 0.6 172 ), 173 h_align='center', 174 v_align='center', 175 text=ba.Lstr(resource=self._r + '.titleText'), 176 maxwidth=550, 177 ) 178 179 scroll_buffer_h = 130 + 2 * x_offs 180 tab_buffer_h = (320 if condensed else 250) + 2 * x_offs 181 182 # Build up the set of tabs we want. 183 tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [ 184 (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText')) 185 ] 186 if ba.internal.get_v1_account_misc_read_val( 187 'enablePublicParties', True 188 ): 189 tabdefs.append( 190 (self.TabID.INTERNET, ba.Lstr(resource=self._r + '.publicText')) 191 ) 192 tabdefs.append( 193 (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')) 194 ) 195 tabdefs.append( 196 (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')) 197 ) 198 tabdefs.append( 199 (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')) 200 ) 201 202 # On small UI, push our tabs up closer to the top of the screen to 203 # save a bit of space. 204 tabs_top_extra = 42 if condensed else 0 205 self._tab_row = TabRow( 206 self._root_widget, 207 tabdefs, 208 pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra), 209 size=(self._width - tab_buffer_h, 50), 210 on_select_call=ba.WeakCall(self._set_tab), 211 ) 212 213 # Now instantiate handlers for these tabs. 214 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 215 self.TabID.ABOUT: AboutGatherTab, 216 self.TabID.MANUAL: ManualGatherTab, 217 self.TabID.PRIVATE: PrivateGatherTab, 218 self.TabID.INTERNET: PublicGatherTab, 219 self.TabID.NEARBY: NearbyGatherTab, 220 } 221 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 222 for tab_id in self._tab_row.tabs: 223 tabtype = tabtypes.get(tab_id) 224 if tabtype is not None: 225 self._tabs[tab_id] = tabtype(self) 226 227 if ba.app.ui.use_toolbars: 228 ba.widget( 229 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 230 right_widget=ba.internal.get_special_widget('party_button'), 231 ) 232 if uiscale is ba.UIScale.SMALL: 233 ba.widget( 234 edit=self._tab_row.tabs[tabdefs[0][0]].button, 235 left_widget=ba.internal.get_special_widget('back_button'), 236 ) 237 238 self._scroll_width = self._width - scroll_buffer_h 239 self._scroll_height = self._height - 180.0 + tabs_top_extra 240 241 self._scroll_left = (self._width - self._scroll_width) * 0.5 242 self._scroll_bottom = ( 243 self._height - self._scroll_height - 79 - 48 + tabs_top_extra 244 ) 245 buffer_h = 10 246 buffer_v = 4 247 248 # Not actually using a scroll widget anymore; just an image. 249 ba.imagewidget( 250 parent=self._root_widget, 251 position=( 252 self._scroll_left - buffer_h, 253 self._scroll_bottom - buffer_v, 254 ), 255 size=( 256 self._scroll_width + 2 * buffer_h, 257 self._scroll_height + 2 * buffer_v, 258 ), 259 texture=ba.gettexture('scrollWidget'), 260 model_transparent=ba.getmodel('softEdgeOutside'), 261 ) 262 self._tab_container: ba.Widget | None = None 263 264 self._restore_state() 265 266 def __del__(self) -> None: 267 ba.internal.set_party_icon_always_visible(False) 268 269 def playlist_select(self, origin_widget: ba.Widget) -> None: 270 """Called by the private-hosting tab to select a playlist.""" 271 from bastd.ui.play import PlayWindow 272 273 self._save_state() 274 ba.containerwidget(edit=self._root_widget, transition='out_left') 275 ba.app.ui.selecting_private_party_playlist = True 276 ba.app.ui.set_main_menu_window( 277 PlayWindow(origin_widget=origin_widget).get_root_widget() 278 ) 279 280 def _set_tab(self, tab_id: TabID) -> None: 281 if self._current_tab is tab_id: 282 return 283 prev_tab_id = self._current_tab 284 self._current_tab = tab_id 285 286 # We wanna preserve our current tab between runs. 287 cfg = ba.app.config 288 cfg['Gather Tab'] = tab_id.value 289 cfg.commit() 290 291 # Update tab colors based on which is selected. 292 self._tab_row.update_appearance(tab_id) 293 294 if prev_tab_id is not None: 295 prev_tab = self._tabs.get(prev_tab_id) 296 if prev_tab is not None: 297 prev_tab.on_deactivate() 298 299 # Clear up prev container if it hasn't been done. 300 if self._tab_container: 301 self._tab_container.delete() 302 303 tab = self._tabs.get(tab_id) 304 if tab is not None: 305 self._tab_container = tab.on_activate( 306 self._root_widget, 307 self._tab_row.tabs[tab_id].button, 308 self._scroll_width, 309 self._scroll_height, 310 self._scroll_left, 311 self._scroll_bottom, 312 ) 313 return 314 315 def _save_state(self) -> None: 316 try: 317 for tab in self._tabs.values(): 318 tab.save_state() 319 320 sel = self._root_widget.get_selected_child() 321 selected_tab_ids = [ 322 tab_id 323 for tab_id, tab in self._tab_row.tabs.items() 324 if sel == tab.button 325 ] 326 if sel == self._back_button: 327 sel_name = 'Back' 328 elif selected_tab_ids: 329 assert len(selected_tab_ids) == 1 330 sel_name = f'Tab:{selected_tab_ids[0].value}' 331 elif sel == self._tab_container: 332 sel_name = 'TabContainer' 333 else: 334 raise ValueError(f'unrecognized selection: \'{sel}\'') 335 ba.app.ui.window_states[type(self)] = { 336 'sel_name': sel_name, 337 } 338 except Exception: 339 ba.print_exception(f'Error saving state for {self}.') 340 341 def _restore_state(self) -> None: 342 from efro.util import enum_by_value 343 344 try: 345 for tab in self._tabs.values(): 346 tab.restore_state() 347 348 sel: ba.Widget | None 349 winstate = ba.app.ui.window_states.get(type(self), {}) 350 sel_name = winstate.get('sel_name', None) 351 assert isinstance(sel_name, (str, type(None))) 352 current_tab = self.TabID.ABOUT 353 gather_tab_val = ba.app.config.get('Gather Tab') 354 try: 355 stored_tab = enum_by_value(self.TabID, gather_tab_val) 356 if stored_tab in self._tab_row.tabs: 357 current_tab = stored_tab 358 except ValueError: 359 pass 360 self._set_tab(current_tab) 361 if sel_name == 'Back': 362 sel = self._back_button 363 elif sel_name == 'TabContainer': 364 sel = self._tab_container 365 elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): 366 try: 367 sel_tab_id = enum_by_value( 368 self.TabID, sel_name.split(':')[-1] 369 ) 370 except ValueError: 371 sel_tab_id = self.TabID.ABOUT 372 sel = self._tab_row.tabs[sel_tab_id].button 373 else: 374 sel = self._tab_row.tabs[current_tab].button 375 ba.containerwidget(edit=self._root_widget, selected_child=sel) 376 except Exception: 377 ba.print_exception('Error restoring gather-win state.') 378 379 def _back(self) -> None: 380 from bastd.ui.mainmenu import MainMenuWindow 381 382 self._save_state() 383 ba.containerwidget( 384 edit=self._root_widget, transition=self._transition_out 385 ) 386 ba.app.ui.set_main_menu_window( 387 MainMenuWindow(transition='in_left').get_root_widget() 388 )
Window for joining/inviting friends.
GatherWindow( transition: str | None = 'in_right', origin_widget: _ba.Widget | None = None)
72 def __init__( 73 self, 74 transition: str | None = 'in_right', 75 origin_widget: ba.Widget | None = None, 76 ): 77 # pylint: disable=too-many-statements 78 # pylint: disable=too-many-locals 79 # pylint: disable=cyclic-import 80 from bastd.ui.gather.abouttab import AboutGatherTab 81 from bastd.ui.gather.manualtab import ManualGatherTab 82 from bastd.ui.gather.privatetab import PrivateGatherTab 83 from bastd.ui.gather.publictab import PublicGatherTab 84 from bastd.ui.gather.nearbytab import NearbyGatherTab 85 86 ba.set_analytics_screen('Gather Window') 87 scale_origin: tuple[float, float] | None 88 if origin_widget is not None: 89 self._transition_out = 'out_scale' 90 scale_origin = origin_widget.get_screen_space_center() 91 transition = 'in_scale' 92 else: 93 self._transition_out = 'out_right' 94 scale_origin = None 95 ba.app.ui.set_main_menu_location('Gather') 96 ba.internal.set_party_icon_always_visible(True) 97 uiscale = ba.app.ui.uiscale 98 self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040 99 x_offs = 100 if uiscale is ba.UIScale.SMALL else 0 100 self._height = ( 101 582 102 if uiscale is ba.UIScale.SMALL 103 else 680 104 if uiscale is ba.UIScale.MEDIUM 105 else 800 106 ) 107 self._current_tab: GatherWindow.TabID | None = None 108 extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 109 self._r = 'gatherWindow' 110 111 super().__init__( 112 root_widget=ba.containerwidget( 113 size=(self._width, self._height + extra_top), 114 transition=transition, 115 toolbar_visibility='menu_minimal', 116 scale_origin_stack_offset=scale_origin, 117 scale=( 118 1.3 119 if uiscale is ba.UIScale.SMALL 120 else 0.97 121 if uiscale is ba.UIScale.MEDIUM 122 else 0.8 123 ), 124 stack_offset=(0, -11) 125 if uiscale is ba.UIScale.SMALL 126 else (0, 0) 127 if uiscale is ba.UIScale.MEDIUM 128 else (0, 0), 129 ) 130 ) 131 132 if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars: 133 ba.containerwidget( 134 edit=self._root_widget, on_cancel_call=self._back 135 ) 136 self._back_button = None 137 else: 138 self._back_button = btn = ba.buttonwidget( 139 parent=self._root_widget, 140 position=(70 + x_offs, self._height - 74), 141 size=(140, 60), 142 scale=1.1, 143 autoselect=True, 144 label=ba.Lstr(resource='backText'), 145 button_type='back', 146 on_activate_call=self._back, 147 ) 148 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 149 ba.buttonwidget( 150 edit=btn, 151 button_type='backSmall', 152 position=(70 + x_offs, self._height - 78), 153 size=(60, 60), 154 label=ba.charstr(ba.SpecialChar.BACK), 155 ) 156 157 condensed = uiscale is not ba.UIScale.LARGE 158 t_offs_y = ( 159 0 if not condensed else 25 if uiscale is ba.UIScale.MEDIUM else 17 160 ) 161 ba.textwidget( 162 parent=self._root_widget, 163 position=(self._width * 0.5, self._height - 42 + t_offs_y), 164 size=(0, 0), 165 color=ba.app.ui.title_color, 166 scale=( 167 1.5 168 if not condensed 169 else 1.0 170 if uiscale is ba.UIScale.MEDIUM 171 else 0.6 172 ), 173 h_align='center', 174 v_align='center', 175 text=ba.Lstr(resource=self._r + '.titleText'), 176 maxwidth=550, 177 ) 178 179 scroll_buffer_h = 130 + 2 * x_offs 180 tab_buffer_h = (320 if condensed else 250) + 2 * x_offs 181 182 # Build up the set of tabs we want. 183 tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [ 184 (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText')) 185 ] 186 if ba.internal.get_v1_account_misc_read_val( 187 'enablePublicParties', True 188 ): 189 tabdefs.append( 190 (self.TabID.INTERNET, ba.Lstr(resource=self._r + '.publicText')) 191 ) 192 tabdefs.append( 193 (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')) 194 ) 195 tabdefs.append( 196 (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')) 197 ) 198 tabdefs.append( 199 (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')) 200 ) 201 202 # On small UI, push our tabs up closer to the top of the screen to 203 # save a bit of space. 204 tabs_top_extra = 42 if condensed else 0 205 self._tab_row = TabRow( 206 self._root_widget, 207 tabdefs, 208 pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra), 209 size=(self._width - tab_buffer_h, 50), 210 on_select_call=ba.WeakCall(self._set_tab), 211 ) 212 213 # Now instantiate handlers for these tabs. 214 tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = { 215 self.TabID.ABOUT: AboutGatherTab, 216 self.TabID.MANUAL: ManualGatherTab, 217 self.TabID.PRIVATE: PrivateGatherTab, 218 self.TabID.INTERNET: PublicGatherTab, 219 self.TabID.NEARBY: NearbyGatherTab, 220 } 221 self._tabs: dict[GatherWindow.TabID, GatherTab] = {} 222 for tab_id in self._tab_row.tabs: 223 tabtype = tabtypes.get(tab_id) 224 if tabtype is not None: 225 self._tabs[tab_id] = tabtype(self) 226 227 if ba.app.ui.use_toolbars: 228 ba.widget( 229 edit=self._tab_row.tabs[tabdefs[-1][0]].button, 230 right_widget=ba.internal.get_special_widget('party_button'), 231 ) 232 if uiscale is ba.UIScale.SMALL: 233 ba.widget( 234 edit=self._tab_row.tabs[tabdefs[0][0]].button, 235 left_widget=ba.internal.get_special_widget('back_button'), 236 ) 237 238 self._scroll_width = self._width - scroll_buffer_h 239 self._scroll_height = self._height - 180.0 + tabs_top_extra 240 241 self._scroll_left = (self._width - self._scroll_width) * 0.5 242 self._scroll_bottom = ( 243 self._height - self._scroll_height - 79 - 48 + tabs_top_extra 244 ) 245 buffer_h = 10 246 buffer_v = 4 247 248 # Not actually using a scroll widget anymore; just an image. 249 ba.imagewidget( 250 parent=self._root_widget, 251 position=( 252 self._scroll_left - buffer_h, 253 self._scroll_bottom - buffer_v, 254 ), 255 size=( 256 self._scroll_width + 2 * buffer_h, 257 self._scroll_height + 2 * buffer_v, 258 ), 259 texture=ba.gettexture('scrollWidget'), 260 model_transparent=ba.getmodel('softEdgeOutside'), 261 ) 262 self._tab_container: ba.Widget | None = None 263 264 self._restore_state()
def
playlist_select(self, origin_widget: _ba.Widget) -> None:
269 def playlist_select(self, origin_widget: ba.Widget) -> None: 270 """Called by the private-hosting tab to select a playlist.""" 271 from bastd.ui.play import PlayWindow 272 273 self._save_state() 274 ba.containerwidget(edit=self._root_widget, transition='out_left') 275 ba.app.ui.selecting_private_party_playlist = True 276 ba.app.ui.set_main_menu_window( 277 PlayWindow(origin_widget=origin_widget).get_root_widget() 278 )
Called by the private-hosting tab to select a playlist.
Inherited Members
- ba.ui.Window
- get_root_widget
class
GatherWindow.TabID(enum.Enum):
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'
Our available tab types.
Inherited Members
- enum.Enum
- name
- value