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

Defines a tab for use in the gather UI.

GatherTab(window: bastd.ui.gather.GatherWindow)
23    def __init__(self, window: GatherWindow) -> None:
24        self._window = weakref.ref(window)

The GatherWindow that this tab belongs to.

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        """

Called when the tab becomes the active one.

The tab should create and return a container widget covering the specified region.

def on_deactivate(self) -> None:
49    def on_deactivate(self) -> None:
50        """Called when the tab will no longer be the active one."""

Called when the tab will no longer be the active one.

def save_state(self) -> None:
52    def save_state(self) -> None:
53        """Called when the parent window is saving state."""

Called when the parent window is saving state.

def restore_state(self) -> None:
55    def restore_state(self) -> None:
56        """Called when the parent window is restoring state."""

Called when the parent window is restoring state.

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

Window for joining/inviting friends.

GatherWindow( transition: str | None = 'in_right', origin_widget: _ba.Widget | None = None)
 70    def __init__(self,
 71                 transition: str | None = 'in_right',
 72                 origin_widget: ba.Widget | None = None):
 73        # pylint: disable=too-many-statements
 74        # pylint: disable=too-many-locals
 75        # pylint: disable=cyclic-import
 76        from bastd.ui.gather.abouttab import AboutGatherTab
 77        from bastd.ui.gather.manualtab import ManualGatherTab
 78        from bastd.ui.gather.privatetab import PrivateGatherTab
 79        from bastd.ui.gather.publictab import PublicGatherTab
 80        from bastd.ui.gather.nearbytab import NearbyGatherTab
 81
 82        ba.set_analytics_screen('Gather Window')
 83        scale_origin: tuple[float, float] | None
 84        if origin_widget is not None:
 85            self._transition_out = 'out_scale'
 86            scale_origin = origin_widget.get_screen_space_center()
 87            transition = 'in_scale'
 88        else:
 89            self._transition_out = 'out_right'
 90            scale_origin = None
 91        ba.app.ui.set_main_menu_location('Gather')
 92        _ba.set_party_icon_always_visible(True)
 93        uiscale = ba.app.ui.uiscale
 94        self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040
 95        x_offs = 100 if uiscale is ba.UIScale.SMALL else 0
 96        self._height = (582 if uiscale is ba.UIScale.SMALL else
 97                        680 if uiscale is ba.UIScale.MEDIUM else 800)
 98        self._current_tab: GatherWindow.TabID | None = None
 99        extra_top = 20 if uiscale is ba.UIScale.SMALL else 0
100        self._r = 'gatherWindow'
101
102        super().__init__(root_widget=ba.containerwidget(
103            size=(self._width, self._height + extra_top),
104            transition=transition,
105            toolbar_visibility='menu_minimal',
106            scale_origin_stack_offset=scale_origin,
107            scale=(1.3 if uiscale is ba.UIScale.SMALL else
108                   0.97 if uiscale is ba.UIScale.MEDIUM else 0.8),
109            stack_offset=(0, -11) if uiscale is ba.UIScale.SMALL else (
110                0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)))
111
112        if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
113            ba.containerwidget(edit=self._root_widget,
114                               on_cancel_call=self._back)
115            self._back_button = None
116        else:
117            self._back_button = btn = ba.buttonwidget(
118                parent=self._root_widget,
119                position=(70 + x_offs, self._height - 74),
120                size=(140, 60),
121                scale=1.1,
122                autoselect=True,
123                label=ba.Lstr(resource='backText'),
124                button_type='back',
125                on_activate_call=self._back)
126            ba.containerwidget(edit=self._root_widget, cancel_button=btn)
127            ba.buttonwidget(edit=btn,
128                            button_type='backSmall',
129                            position=(70 + x_offs, self._height - 78),
130                            size=(60, 60),
131                            label=ba.charstr(ba.SpecialChar.BACK))
132
133        condensed = uiscale is not ba.UIScale.LARGE
134        t_offs_y = (0 if not condensed else
135                    25 if uiscale is ba.UIScale.MEDIUM else 17)
136        ba.textwidget(parent=self._root_widget,
137                      position=(self._width * 0.5,
138                                self._height - 42 + t_offs_y),
139                      size=(0, 0),
140                      color=ba.app.ui.title_color,
141                      scale=(1.5 if not condensed else
142                             1.0 if uiscale is ba.UIScale.MEDIUM else 0.6),
143                      h_align='center',
144                      v_align='center',
145                      text=ba.Lstr(resource=self._r + '.titleText'),
146                      maxwidth=550)
147
148        scroll_buffer_h = 130 + 2 * x_offs
149        tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs)
150
151        # Build up the set of tabs we want.
152        tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [
153            (self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText'))
154        ]
155        if _ba.get_v1_account_misc_read_val('enablePublicParties', True):
156            tabdefs.append((self.TabID.INTERNET,
157                            ba.Lstr(resource=self._r + '.publicText')))
158        tabdefs.append(
159            (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')))
160        tabdefs.append(
161            (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')))
162        tabdefs.append(
163            (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')))
164
165        # On small UI, push our tabs up closer to the top of the screen to
166        # save a bit of space.
167        tabs_top_extra = 42 if condensed else 0
168        self._tab_row = TabRow(self._root_widget,
169                               tabdefs,
170                               pos=(tab_buffer_h * 0.5,
171                                    self._height - 130 + tabs_top_extra),
172                               size=(self._width - tab_buffer_h, 50),
173                               on_select_call=ba.WeakCall(self._set_tab))
174
175        # Now instantiate handlers for these tabs.
176        tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = {
177            self.TabID.ABOUT: AboutGatherTab,
178            self.TabID.MANUAL: ManualGatherTab,
179            self.TabID.PRIVATE: PrivateGatherTab,
180            self.TabID.INTERNET: PublicGatherTab,
181            self.TabID.NEARBY: NearbyGatherTab
182        }
183        self._tabs: dict[GatherWindow.TabID, GatherTab] = {}
184        for tab_id in self._tab_row.tabs:
185            tabtype = tabtypes.get(tab_id)
186            if tabtype is not None:
187                self._tabs[tab_id] = tabtype(self)
188
189        if ba.app.ui.use_toolbars:
190            ba.widget(edit=self._tab_row.tabs[tabdefs[-1][0]].button,
191                      right_widget=_ba.get_special_widget('party_button'))
192            if uiscale is ba.UIScale.SMALL:
193                ba.widget(edit=self._tab_row.tabs[tabdefs[0][0]].button,
194                          left_widget=_ba.get_special_widget('back_button'))
195
196        self._scroll_width = self._width - scroll_buffer_h
197        self._scroll_height = self._height - 180.0 + tabs_top_extra
198
199        self._scroll_left = (self._width - self._scroll_width) * 0.5
200        self._scroll_bottom = (self._height - self._scroll_height - 79 - 48 +
201                               tabs_top_extra)
202        buffer_h = 10
203        buffer_v = 4
204
205        # Not actually using a scroll widget anymore; just an image.
206        ba.imagewidget(parent=self._root_widget,
207                       position=(self._scroll_left - buffer_h,
208                                 self._scroll_bottom - buffer_v),
209                       size=(self._scroll_width + 2 * buffer_h,
210                             self._scroll_height + 2 * buffer_v),
211                       texture=ba.gettexture('scrollWidget'),
212                       model_transparent=ba.getmodel('softEdgeOutside'))
213        self._tab_container: ba.Widget | None = None
214
215        self._restore_state()
def playlist_select(self, origin_widget: _ba.Widget) -> None:
220    def playlist_select(self, origin_widget: ba.Widget) -> None:
221        """Called by the private-hosting tab to select a playlist."""
222        from bastd.ui.play import PlayWindow
223        self._save_state()
224        ba.containerwidget(edit=self._root_widget, transition='out_left')
225        ba.app.ui.selecting_private_party_playlist = True
226        ba.app.ui.set_main_menu_window(
227            PlayWindow(origin_widget=origin_widget).get_root_widget())

Called by the private-hosting tab to select a playlist.

Inherited Members
ba.ui.Window
get_root_widget
class GatherWindow.TabID(enum.Enum):
62    class TabID(Enum):
63        """Our available tab types."""
64        ABOUT = 'about'
65        INTERNET = 'internet'
66        PRIVATE = 'private'
67        NEARBY = 'nearby'
68        MANUAL = 'manual'

Our available tab types.

ABOUT = <TabID.ABOUT: 'about'>
INTERNET = <TabID.INTERNET: 'internet'>
PRIVATE = <TabID.PRIVATE: 'private'>
NEARBY = <TabID.NEARBY: 'nearby'>
MANUAL = <TabID.MANUAL: 'manual'>
Inherited Members
enum.Enum
name
value