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

Defines a tab for use in the gather UI.

GatherTab(window: GatherWindow)
20    def __init__(self, window: GatherWindow) -> None:
21        self._window = weakref.ref(window)
window: GatherWindow
23    @property
24    def window(self) -> GatherWindow:
25        """The GatherWindow that this tab belongs to."""
26        window = self._window()
27        if window is None:
28            raise bui.NotFoundError("GatherTab's window no longer exists.")
29        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:
31    def on_activate(
32        self,
33        parent_widget: bui.Widget,
34        tab_button: bui.Widget,
35        region_width: float,
36        region_height: float,
37        region_left: float,
38        region_bottom: float,
39    ) -> bui.Widget:
40        """Called when the tab becomes the active one.
41
42        The tab should create and return a container widget covering the
43        specified region.
44        """
45        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.

def on_deactivate(self) -> None:
47    def on_deactivate(self) -> None:
48        """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:
50    def save_state(self) -> None:
51        """Called when the parent window is saving state."""

Called when the parent window is saving state.

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

Called when the parent window is restoring state.

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

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
254    @override
255    def get_main_window_state(self) -> bui.MainWindowState:
256        # Support recreating our window for back/refresh purposes.
257        cls = type(self)
258        return bui.BasicMainWindowState(
259            create_call=lambda transition, origin_widget: cls(
260                transition=transition, origin_widget=origin_widget
261            )
262        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
264    @override
265    def on_main_window_close(self) -> None:
266        self._save_state()

Called before transitioning out a main window.

A good opportunity to save window state/etc.

def playlist_select(self, origin_widget: _bauiv1.Widget) -> None:
268    def playlist_select(self, origin_widget: bui.Widget) -> None:
269        """Called by the private-hosting tab to select a playlist."""
270        from bauiv1lib.play import PlayWindow
271
272        classic = bui.app.classic
273        assert classic is not None
274
275        # no-op if our underlying widget is dead or on its way out.
276        if not self._root_widget or self._root_widget.transitioning_out:
277            return
278
279        self._save_state()
280        bui.containerwidget(edit=self._root_widget, transition='out_left')
281        classic.selecting_private_party_playlist = True
282        bui.app.ui_v1.set_main_window(
283            PlayWindow(origin_widget=origin_widget), from_window=self
284        )

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

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
bauiv1._uitypes.Window
get_root_widget
class GatherWindow.TabID(enum.Enum):
60    class TabID(Enum):
61        """Our available tab types."""
62
63        ABOUT = 'about'
64        INTERNET = 'internet'
65        PRIVATE = 'private'
66        NEARBY = 'nearby'
67        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