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 = 1240 if uiscale is bui.UIScale.SMALL else 1040
 98        x_offs = 100 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        self._save_state()
274        bui.containerwidget(edit=self._root_widget, transition='out_left')
275        assert bui.app.classic is not None
276        bui.app.ui_v1.selecting_private_party_playlist = True
277        bui.app.ui_v1.set_main_menu_window(
278            PlayWindow(origin_widget=origin_widget).get_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        self._save_state()
387        bui.containerwidget(
388            edit=self._root_widget, transition=self._transition_out
389        )
390        assert bui.app.classic is not None
391        bui.app.ui_v1.set_main_menu_window(
392            MainMenuWindow(transition='in_left').get_root_widget()
393        )
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

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 = 1240 if uiscale is bui.UIScale.SMALL else 1040
 99        x_offs = 100 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        self._save_state()
275        bui.containerwidget(edit=self._root_widget, transition='out_left')
276        assert bui.app.classic is not None
277        bui.app.ui_v1.selecting_private_party_playlist = True
278        bui.app.ui_v1.set_main_menu_window(
279            PlayWindow(origin_widget=origin_widget).get_root_widget()
280        )
281
282    def _set_tab(self, tab_id: TabID) -> None:
283        if self._current_tab is tab_id:
284            return
285        prev_tab_id = self._current_tab
286        self._current_tab = tab_id
287
288        # We wanna preserve our current tab between runs.
289        cfg = bui.app.config
290        cfg['Gather Tab'] = tab_id.value
291        cfg.commit()
292
293        # Update tab colors based on which is selected.
294        self._tab_row.update_appearance(tab_id)
295
296        if prev_tab_id is not None:
297            prev_tab = self._tabs.get(prev_tab_id)
298            if prev_tab is not None:
299                prev_tab.on_deactivate()
300
301        # Clear up prev container if it hasn't been done.
302        if self._tab_container:
303            self._tab_container.delete()
304
305        tab = self._tabs.get(tab_id)
306        if tab is not None:
307            self._tab_container = tab.on_activate(
308                self._root_widget,
309                self._tab_row.tabs[tab_id].button,
310                self._scroll_width,
311                self._scroll_height,
312                self._scroll_left,
313                self._scroll_bottom,
314            )
315            return
316
317    def _save_state(self) -> None:
318        try:
319            for tab in self._tabs.values():
320                tab.save_state()
321
322            sel = self._root_widget.get_selected_child()
323            selected_tab_ids = [
324                tab_id
325                for tab_id, tab in self._tab_row.tabs.items()
326                if sel == tab.button
327            ]
328            if sel == self._back_button:
329                sel_name = 'Back'
330            elif selected_tab_ids:
331                assert len(selected_tab_ids) == 1
332                sel_name = f'Tab:{selected_tab_ids[0].value}'
333            elif sel == self._tab_container:
334                sel_name = 'TabContainer'
335            else:
336                raise ValueError(f'unrecognized selection: \'{sel}\'')
337            assert bui.app.classic is not None
338            bui.app.ui_v1.window_states[type(self)] = {
339                'sel_name': sel_name,
340            }
341        except Exception:
342            logging.exception('Error saving state for %s.', self)
343
344    def _restore_state(self) -> None:
345        from efro.util import enum_by_value
346
347        try:
348            for tab in self._tabs.values():
349                tab.restore_state()
350
351            sel: bui.Widget | None
352            assert bui.app.classic is not None
353            winstate = bui.app.ui_v1.window_states.get(type(self), {})
354            sel_name = winstate.get('sel_name', None)
355            assert isinstance(sel_name, (str, type(None)))
356            current_tab = self.TabID.ABOUT
357            gather_tab_val = bui.app.config.get('Gather Tab')
358            try:
359                stored_tab = enum_by_value(self.TabID, gather_tab_val)
360                if stored_tab in self._tab_row.tabs:
361                    current_tab = stored_tab
362            except ValueError:
363                pass
364            self._set_tab(current_tab)
365            if sel_name == 'Back':
366                sel = self._back_button
367            elif sel_name == 'TabContainer':
368                sel = self._tab_container
369            elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
370                try:
371                    sel_tab_id = enum_by_value(
372                        self.TabID, sel_name.split(':')[-1]
373                    )
374                except ValueError:
375                    sel_tab_id = self.TabID.ABOUT
376                sel = self._tab_row.tabs[sel_tab_id].button
377            else:
378                sel = self._tab_row.tabs[current_tab].button
379            bui.containerwidget(edit=self._root_widget, selected_child=sel)
380
381        except Exception:
382            logging.exception('Error restoring state for %s.', self)
383
384    def _back(self) -> None:
385        from bauiv1lib.mainmenu import MainMenuWindow
386
387        self._save_state()
388        bui.containerwidget(
389            edit=self._root_widget, transition=self._transition_out
390        )
391        assert bui.app.classic is not None
392        bui.app.ui_v1.set_main_menu_window(
393            MainMenuWindow(transition='in_left').get_root_widget()
394        )

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 = 1240 if uiscale is bui.UIScale.SMALL else 1040
 99        x_offs = 100 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        self._save_state()
275        bui.containerwidget(edit=self._root_widget, transition='out_left')
276        assert bui.app.classic is not None
277        bui.app.ui_v1.selecting_private_party_playlist = True
278        bui.app.ui_v1.set_main_menu_window(
279            PlayWindow(origin_widget=origin_widget).get_root_widget()
280        )

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