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
 10
 11from bauiv1lib.tabs import TabRow
 12import bauiv1 as bui
 13
 14
 15class GatherTab:
 16    """Defines a tab for use in the gather UI."""
 17
 18    def __init__(self, window: GatherWindow) -> None:
 19        self._window = weakref.ref(window)
 20
 21    @property
 22    def window(self) -> GatherWindow:
 23        """The GatherWindow that this tab belongs to."""
 24        window = self._window()
 25        if window is None:
 26            raise bui.NotFoundError("GatherTab's window no longer exists.")
 27        return window
 28
 29    def on_activate(
 30        self,
 31        parent_widget: bui.Widget,
 32        tab_button: bui.Widget,
 33        region_width: float,
 34        region_height: float,
 35        region_left: float,
 36        region_bottom: float,
 37    ) -> bui.Widget:
 38        """Called when the tab becomes the active one.
 39
 40        The tab should create and return a container widget covering the
 41        specified region.
 42        """
 43        raise RuntimeError('Should not get here.')
 44
 45    def on_deactivate(self) -> None:
 46        """Called when the tab will no longer be the active one."""
 47
 48    def save_state(self) -> None:
 49        """Called when the parent window is saving state."""
 50
 51    def restore_state(self) -> None:
 52        """Called when the parent window is restoring state."""
 53
 54
 55class GatherWindow(bui.Window):
 56    """Window for joining/inviting friends."""
 57
 58    class TabID(Enum):
 59        """Our available tab types."""
 60
 61        ABOUT = 'about'
 62        INTERNET = 'internet'
 63        PRIVATE = 'private'
 64        NEARBY = 'nearby'
 65        MANUAL = 'manual'
 66
 67    def __init__(
 68        self,
 69        transition: str | None = 'in_right',
 70        origin_widget: bui.Widget | None = None,
 71    ):
 72        # pylint: disable=too-many-statements
 73        # pylint: disable=too-many-locals
 74        # pylint: disable=cyclic-import
 75        from bauiv1lib.gather.abouttab import AboutGatherTab
 76        from bauiv1lib.gather.manualtab import ManualGatherTab
 77        from bauiv1lib.gather.privatetab import PrivateGatherTab
 78        from bauiv1lib.gather.publictab import PublicGatherTab
 79        from bauiv1lib.gather.nearbytab import NearbyGatherTab
 80
 81        plus = bui.app.plus
 82        assert plus is not None
 83
 84        bui.set_analytics_screen('Gather Window')
 85        scale_origin: tuple[float, float] | None
 86        if origin_widget is not None:
 87            self._transition_out = 'out_scale'
 88            scale_origin = origin_widget.get_screen_space_center()
 89            transition = 'in_scale'
 90        else:
 91            self._transition_out = 'out_right'
 92            scale_origin = None
 93        assert bui.app.classic is not None
 94        bui.app.ui_v1.set_main_menu_location('Gather')
 95        bui.set_party_icon_always_visible(True)
 96        uiscale = bui.app.ui_v1.uiscale
 97        self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
 98        x_offs = 200 if uiscale is bui.UIScale.SMALL else 0
 99        self._height = (
100            582
101            if uiscale is bui.UIScale.SMALL
102            else 680
103            if uiscale is bui.UIScale.MEDIUM
104            else 800
105        )
106        self._current_tab: GatherWindow.TabID | None = None
107        extra_top = 20 if uiscale is bui.UIScale.SMALL else 0
108        self._r = 'gatherWindow'
109
110        super().__init__(
111            root_widget=bui.containerwidget(
112                size=(self._width, self._height + extra_top),
113                transition=transition,
114                toolbar_visibility='menu_minimal',
115                scale_origin_stack_offset=scale_origin,
116                scale=(
117                    1.3
118                    if uiscale is bui.UIScale.SMALL
119                    else 0.97
120                    if uiscale is bui.UIScale.MEDIUM
121                    else 0.8
122                ),
123                stack_offset=(0, -11)
124                if uiscale is bui.UIScale.SMALL
125                else (0, 0)
126                if uiscale is bui.UIScale.MEDIUM
127                else (0, 0),
128            )
129        )
130
131        if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
132            bui.containerwidget(
133                edit=self._root_widget, on_cancel_call=self._back
134            )
135            self._back_button = None
136        else:
137            self._back_button = btn = bui.buttonwidget(
138                parent=self._root_widget,
139                position=(70 + x_offs, self._height - 74),
140                size=(140, 60),
141                scale=1.1,
142                autoselect=True,
143                label=bui.Lstr(resource='backText'),
144                button_type='back',
145                on_activate_call=self._back,
146            )
147            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
148            bui.buttonwidget(
149                edit=btn,
150                button_type='backSmall',
151                position=(70 + x_offs, self._height - 78),
152                size=(60, 60),
153                label=bui.charstr(bui.SpecialChar.BACK),
154            )
155
156        condensed = uiscale is not bui.UIScale.LARGE
157        t_offs_y = (
158            0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 17
159        )
160        bui.textwidget(
161            parent=self._root_widget,
162            position=(self._width * 0.5, self._height - 42 + t_offs_y),
163            size=(0, 0),
164            color=bui.app.ui_v1.title_color,
165            scale=(
166                1.5
167                if not condensed
168                else 1.0
169                if uiscale is bui.UIScale.MEDIUM
170                else 0.6
171            ),
172            h_align='center',
173            v_align='center',
174            text=bui.Lstr(resource=self._r + '.titleText'),
175            maxwidth=550,
176        )
177
178        scroll_buffer_h = 130 + 2 * x_offs
179        tab_buffer_h = (320 if condensed else 250) + 2 * x_offs
180
181        # Build up the set of tabs we want.
182        tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [
183            (self.TabID.ABOUT, bui.Lstr(resource=self._r + '.aboutText'))
184        ]
185        if plus.get_v1_account_misc_read_val('enablePublicParties', True):
186            tabdefs.append(
187                (
188                    self.TabID.INTERNET,
189                    bui.Lstr(resource=self._r + '.publicText'),
190                )
191            )
192        tabdefs.append(
193            (self.TabID.PRIVATE, bui.Lstr(resource=self._r + '.privateText'))
194        )
195        tabdefs.append(
196            (self.TabID.NEARBY, bui.Lstr(resource=self._r + '.nearbyText'))
197        )
198        tabdefs.append(
199            (self.TabID.MANUAL, bui.Lstr(resource=self._r + '.manualText'))
200        )
201
202        # On small UI, push our tabs up closer to the top of the screen to
203        # save a bit of space.
204        tabs_top_extra = 42 if condensed else 0
205        self._tab_row = TabRow(
206            self._root_widget,
207            tabdefs,
208            pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra),
209            size=(self._width - tab_buffer_h, 50),
210            on_select_call=bui.WeakCall(self._set_tab),
211        )
212
213        # Now instantiate handlers for these tabs.
214        tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = {
215            self.TabID.ABOUT: AboutGatherTab,
216            self.TabID.MANUAL: ManualGatherTab,
217            self.TabID.PRIVATE: PrivateGatherTab,
218            self.TabID.INTERNET: PublicGatherTab,
219            self.TabID.NEARBY: NearbyGatherTab,
220        }
221        self._tabs: dict[GatherWindow.TabID, GatherTab] = {}
222        for tab_id in self._tab_row.tabs:
223            tabtype = tabtypes.get(tab_id)
224            if tabtype is not None:
225                self._tabs[tab_id] = tabtype(self)
226
227        if bui.app.ui_v1.use_toolbars:
228            bui.widget(
229                edit=self._tab_row.tabs[tabdefs[-1][0]].button,
230                right_widget=bui.get_special_widget('party_button'),
231            )
232            if uiscale is bui.UIScale.SMALL:
233                bui.widget(
234                    edit=self._tab_row.tabs[tabdefs[0][0]].button,
235                    left_widget=bui.get_special_widget('back_button'),
236                )
237
238        self._scroll_width = self._width - scroll_buffer_h
239        self._scroll_height = self._height - 180.0 + tabs_top_extra
240
241        self._scroll_left = (self._width - self._scroll_width) * 0.5
242        self._scroll_bottom = (
243            self._height - self._scroll_height - 79 - 48 + tabs_top_extra
244        )
245        buffer_h = 10
246        buffer_v = 4
247
248        # Not actually using a scroll widget anymore; just an image.
249        bui.imagewidget(
250            parent=self._root_widget,
251            position=(
252                self._scroll_left - buffer_h,
253                self._scroll_bottom - buffer_v,
254            ),
255            size=(
256                self._scroll_width + 2 * buffer_h,
257                self._scroll_height + 2 * buffer_v,
258            ),
259            texture=bui.gettexture('scrollWidget'),
260            mesh_transparent=bui.getmesh('softEdgeOutside'),
261        )
262        self._tab_container: bui.Widget | None = None
263
264        self._restore_state()
265
266    def __del__(self) -> None:
267        bui.set_party_icon_always_visible(False)
268
269    def playlist_select(self, origin_widget: bui.Widget) -> None:
270        """Called by the private-hosting tab to select a playlist."""
271        from bauiv1lib.play import PlayWindow
272
273        # no-op if our underlying widget is dead or on its way out.
274        if not self._root_widget or self._root_widget.transitioning_out:
275            return
276
277        self._save_state()
278        bui.containerwidget(edit=self._root_widget, transition='out_left')
279        assert bui.app.classic is not None
280        bui.app.ui_v1.selecting_private_party_playlist = True
281        bui.app.ui_v1.set_main_menu_window(
282            PlayWindow(origin_widget=origin_widget).get_root_widget(),
283            from_window=self._root_widget,
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        from efro.util import enum_by_value
350
351        try:
352            for tab in self._tabs.values():
353                tab.restore_state()
354
355            sel: bui.Widget | None
356            assert bui.app.classic is not None
357            winstate = bui.app.ui_v1.window_states.get(type(self), {})
358            sel_name = winstate.get('sel_name', None)
359            assert isinstance(sel_name, (str, type(None)))
360            current_tab = self.TabID.ABOUT
361            gather_tab_val = bui.app.config.get('Gather Tab')
362            try:
363                stored_tab = enum_by_value(self.TabID, gather_tab_val)
364                if stored_tab in self._tab_row.tabs:
365                    current_tab = stored_tab
366            except ValueError:
367                pass
368            self._set_tab(current_tab)
369            if sel_name == 'Back':
370                sel = self._back_button
371            elif sel_name == 'TabContainer':
372                sel = self._tab_container
373            elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
374                try:
375                    sel_tab_id = enum_by_value(
376                        self.TabID, sel_name.split(':')[-1]
377                    )
378                except ValueError:
379                    sel_tab_id = self.TabID.ABOUT
380                sel = self._tab_row.tabs[sel_tab_id].button
381            else:
382                sel = self._tab_row.tabs[current_tab].button
383            bui.containerwidget(edit=self._root_widget, selected_child=sel)
384
385        except Exception:
386            logging.exception('Error restoring state for %s.', self)
387
388    def _back(self) -> None:
389        from bauiv1lib.mainmenu import MainMenuWindow
390
391        # no-op if our underlying widget is dead or on its way out.
392        if not self._root_widget or self._root_widget.transitioning_out:
393            return
394
395        self._save_state()
396        bui.containerwidget(
397            edit=self._root_widget, transition=self._transition_out
398        )
399        assert bui.app.classic is not None
400        bui.app.ui_v1.set_main_menu_window(
401            MainMenuWindow(transition='in_left').get_root_widget(),
402            from_window=self._root_widget,
403        )
class GatherTab:
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."""

Defines a tab for use in the gather UI.

GatherTab(window: GatherWindow)
19    def __init__(self, window: GatherWindow) -> None:
20        self._window = weakref.ref(window)
window: GatherWindow
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

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:
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.')

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

Called when the parent window is saving state.

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

Called when the parent window is restoring state.

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

Window for joining/inviting friends.

GatherWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 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        scale_origin: tuple[float, float] | None
 87        if origin_widget is not None:
 88            self._transition_out = 'out_scale'
 89            scale_origin = origin_widget.get_screen_space_center()
 90            transition = 'in_scale'
 91        else:
 92            self._transition_out = 'out_right'
 93            scale_origin = None
 94        assert bui.app.classic is not None
 95        bui.app.ui_v1.set_main_menu_location('Gather')
 96        bui.set_party_icon_always_visible(True)
 97        uiscale = bui.app.ui_v1.uiscale
 98        self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
 99        x_offs = 200 if uiscale is bui.UIScale.SMALL else 0
100        self._height = (
101            582
102            if uiscale is bui.UIScale.SMALL
103            else 680
104            if uiscale is bui.UIScale.MEDIUM
105            else 800
106        )
107        self._current_tab: GatherWindow.TabID | None = None
108        extra_top = 20 if uiscale is bui.UIScale.SMALL else 0
109        self._r = 'gatherWindow'
110
111        super().__init__(
112            root_widget=bui.containerwidget(
113                size=(self._width, self._height + extra_top),
114                transition=transition,
115                toolbar_visibility='menu_minimal',
116                scale_origin_stack_offset=scale_origin,
117                scale=(
118                    1.3
119                    if uiscale is bui.UIScale.SMALL
120                    else 0.97
121                    if uiscale is bui.UIScale.MEDIUM
122                    else 0.8
123                ),
124                stack_offset=(0, -11)
125                if uiscale is bui.UIScale.SMALL
126                else (0, 0)
127                if uiscale is bui.UIScale.MEDIUM
128                else (0, 0),
129            )
130        )
131
132        if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
133            bui.containerwidget(
134                edit=self._root_widget, on_cancel_call=self._back
135            )
136            self._back_button = None
137        else:
138            self._back_button = btn = bui.buttonwidget(
139                parent=self._root_widget,
140                position=(70 + x_offs, self._height - 74),
141                size=(140, 60),
142                scale=1.1,
143                autoselect=True,
144                label=bui.Lstr(resource='backText'),
145                button_type='back',
146                on_activate_call=self._back,
147            )
148            bui.containerwidget(edit=self._root_widget, cancel_button=btn)
149            bui.buttonwidget(
150                edit=btn,
151                button_type='backSmall',
152                position=(70 + x_offs, self._height - 78),
153                size=(60, 60),
154                label=bui.charstr(bui.SpecialChar.BACK),
155            )
156
157        condensed = uiscale is not bui.UIScale.LARGE
158        t_offs_y = (
159            0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 17
160        )
161        bui.textwidget(
162            parent=self._root_widget,
163            position=(self._width * 0.5, self._height - 42 + t_offs_y),
164            size=(0, 0),
165            color=bui.app.ui_v1.title_color,
166            scale=(
167                1.5
168                if not condensed
169                else 1.0
170                if uiscale is bui.UIScale.MEDIUM
171                else 0.6
172            ),
173            h_align='center',
174            v_align='center',
175            text=bui.Lstr(resource=self._r + '.titleText'),
176            maxwidth=550,
177        )
178
179        scroll_buffer_h = 130 + 2 * x_offs
180        tab_buffer_h = (320 if condensed else 250) + 2 * x_offs
181
182        # Build up the set of tabs we want.
183        tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [
184            (self.TabID.ABOUT, bui.Lstr(resource=self._r + '.aboutText'))
185        ]
186        if plus.get_v1_account_misc_read_val('enablePublicParties', True):
187            tabdefs.append(
188                (
189                    self.TabID.INTERNET,
190                    bui.Lstr(resource=self._r + '.publicText'),
191                )
192            )
193        tabdefs.append(
194            (self.TabID.PRIVATE, bui.Lstr(resource=self._r + '.privateText'))
195        )
196        tabdefs.append(
197            (self.TabID.NEARBY, bui.Lstr(resource=self._r + '.nearbyText'))
198        )
199        tabdefs.append(
200            (self.TabID.MANUAL, bui.Lstr(resource=self._r + '.manualText'))
201        )
202
203        # On small UI, push our tabs up closer to the top of the screen to
204        # save a bit of space.
205        tabs_top_extra = 42 if condensed else 0
206        self._tab_row = TabRow(
207            self._root_widget,
208            tabdefs,
209            pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra),
210            size=(self._width - tab_buffer_h, 50),
211            on_select_call=bui.WeakCall(self._set_tab),
212        )
213
214        # Now instantiate handlers for these tabs.
215        tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = {
216            self.TabID.ABOUT: AboutGatherTab,
217            self.TabID.MANUAL: ManualGatherTab,
218            self.TabID.PRIVATE: PrivateGatherTab,
219            self.TabID.INTERNET: PublicGatherTab,
220            self.TabID.NEARBY: NearbyGatherTab,
221        }
222        self._tabs: dict[GatherWindow.TabID, GatherTab] = {}
223        for tab_id in self._tab_row.tabs:
224            tabtype = tabtypes.get(tab_id)
225            if tabtype is not None:
226                self._tabs[tab_id] = tabtype(self)
227
228        if bui.app.ui_v1.use_toolbars:
229            bui.widget(
230                edit=self._tab_row.tabs[tabdefs[-1][0]].button,
231                right_widget=bui.get_special_widget('party_button'),
232            )
233            if uiscale is bui.UIScale.SMALL:
234                bui.widget(
235                    edit=self._tab_row.tabs[tabdefs[0][0]].button,
236                    left_widget=bui.get_special_widget('back_button'),
237                )
238
239        self._scroll_width = self._width - scroll_buffer_h
240        self._scroll_height = self._height - 180.0 + tabs_top_extra
241
242        self._scroll_left = (self._width - self._scroll_width) * 0.5
243        self._scroll_bottom = (
244            self._height - self._scroll_height - 79 - 48 + tabs_top_extra
245        )
246        buffer_h = 10
247        buffer_v = 4
248
249        # Not actually using a scroll widget anymore; just an image.
250        bui.imagewidget(
251            parent=self._root_widget,
252            position=(
253                self._scroll_left - buffer_h,
254                self._scroll_bottom - buffer_v,
255            ),
256            size=(
257                self._scroll_width + 2 * buffer_h,
258                self._scroll_height + 2 * buffer_v,
259            ),
260            texture=bui.gettexture('scrollWidget'),
261            mesh_transparent=bui.getmesh('softEdgeOutside'),
262        )
263        self._tab_container: bui.Widget | None = None
264
265        self._restore_state()
def playlist_select(self, origin_widget: _bauiv1.Widget) -> None:
270    def playlist_select(self, origin_widget: bui.Widget) -> None:
271        """Called by the private-hosting tab to select a playlist."""
272        from bauiv1lib.play import PlayWindow
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        assert bui.app.classic is not None
281        bui.app.ui_v1.selecting_private_party_playlist = True
282        bui.app.ui_v1.set_main_menu_window(
283            PlayWindow(origin_widget=origin_widget).get_root_widget(),
284            from_window=self._root_widget,
285        )

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

Inherited Members
bauiv1._uitypes.Window
get_root_widget
class GatherWindow.TabID(enum.Enum):
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'

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