bauiv1lib.gather

Provides UI for inviting/joining friends.

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

The GatherWindow that this tab belongs to.

def on_activate( self, parent_widget: _bauiv1.Widget, tab_button: _bauiv1.Widget, region_width: float, region_height: float, region_left: float, region_bottom: float) -> _bauiv1.Widget:
34    def on_activate(
35        self,
36        parent_widget: bui.Widget,
37        tab_button: bui.Widget,
38        region_width: float,
39        region_height: float,
40        region_left: float,
41        region_bottom: float,
42    ) -> bui.Widget:
43        """Called when the tab becomes the active one.
44
45        The tab should create and return a container widget covering the
46        specified region.
47        """
48        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:
50    def on_deactivate(self) -> None:
51        """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:
53    def save_state(self) -> None:
54        """Called when the parent window is saving state."""

Called when the parent window is saving state.

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

Called when the parent window is restoring state.

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

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
267    @override
268    def on_main_window_close(self) -> None:
269        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, context: bauiv1lib.play.PlaylistSelectContext) -> None:
271    def playlist_select(
272        self,
273        origin_widget: bui.Widget,
274        context: PlaylistSelectContext,
275    ) -> None:
276        """Called by the private-hosting tab to select a playlist."""
277        from bauiv1lib.play import PlayWindow
278
279        # Avoid redundant window spawns.
280        if not self.main_window_has_control():
281            return
282
283        playwindow = PlayWindow(
284            origin_widget=origin_widget, playlist_select_context=context
285        )
286        self.main_window_replace(playwindow)
287
288        # Grab the newly-set main-window's back-state; that will lead us
289        # back here once we're done going down our main-window
290        # rabbit-hole for playlist selection.
291        context.back_state = playwindow.main_window_back_state

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

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_is_top_level
main_window_close
main_window_has_control
main_window_back
main_window_replace
bauiv1._uitypes.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.

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