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

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

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