bauiv1lib.settings.plugins

Plugin Window UI.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Plugin Window UI."""
  4
  5from __future__ import annotations
  6
  7from enum import Enum
  8import logging
  9from typing import TYPE_CHECKING, assert_never
 10
 11import bauiv1 as bui
 12from bauiv1lib import popup
 13
 14if TYPE_CHECKING:
 15    pass
 16
 17
 18class Category(Enum):
 19    """Categories we can display."""
 20
 21    ALL = 'all'
 22    ENABLED = 'enabled'
 23    DISABLED = 'disabled'
 24
 25    @property
 26    def resource(self) -> str:
 27        """Resource name for us."""
 28        return f'{self.value}Text'
 29
 30
 31class PluginWindow(bui.Window):
 32    """Window for configuring plugins."""
 33
 34    def __init__(
 35        self,
 36        transition: str = 'in_right',
 37        origin_widget: bui.Widget | None = None,
 38    ):
 39        # pylint: disable=too-many-statements
 40        app = bui.app
 41
 42        self._category = Category.ALL
 43
 44        # If they provided an origin-widget, scale up from that.
 45        scale_origin: tuple[float, float] | None
 46        if origin_widget is not None:
 47            self._transition_out = 'out_scale'
 48            scale_origin = origin_widget.get_screen_space_center()
 49            transition = 'in_scale'
 50        else:
 51            self._transition_out = 'out_right'
 52            scale_origin = None
 53
 54        assert bui.app.classic is not None
 55        uiscale = bui.app.ui_v1.uiscale
 56        self._width = 870.0 if uiscale is bui.UIScale.SMALL else 670.0
 57        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 58        self._height = (
 59            390.0
 60            if uiscale is bui.UIScale.SMALL
 61            else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0
 62        )
 63        top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
 64        super().__init__(
 65            root_widget=bui.containerwidget(
 66                size=(self._width, self._height + top_extra),
 67                transition=transition,
 68                toolbar_visibility='menu_minimal',
 69                scale_origin_stack_offset=scale_origin,
 70                scale=(
 71                    2.06
 72                    if uiscale is bui.UIScale.SMALL
 73                    else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
 74                ),
 75                stack_offset=(
 76                    (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
 77                ),
 78            )
 79        )
 80
 81        self._scroll_width = self._width - (100 + 2 * x_inset)
 82        self._scroll_height = self._height - 115.0
 83        self._sub_width = self._scroll_width * 0.95
 84        self._sub_height = 724.0
 85
 86        assert app.classic is not None
 87        if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
 88            bui.containerwidget(
 89                edit=self._root_widget, on_cancel_call=self._do_back
 90            )
 91            self._back_button = None
 92        else:
 93            self._back_button = bui.buttonwidget(
 94                parent=self._root_widget,
 95                position=(53 + x_inset, self._height - 60),
 96                size=(140, 60),
 97                scale=0.8,
 98                autoselect=True,
 99                label=bui.Lstr(resource='backText'),
100                button_type='back',
101                on_activate_call=self._do_back,
102            )
103            bui.containerwidget(
104                edit=self._root_widget, cancel_button=self._back_button
105            )
106
107        self._title_text = bui.textwidget(
108            parent=self._root_widget,
109            position=(self._width * 0.5, self._height - 41),
110            size=(0, 0),
111            text=bui.Lstr(resource='pluginsText'),
112            color=app.ui_v1.title_color,
113            maxwidth=170,
114            h_align='center',
115            v_align='center',
116        )
117
118        if self._back_button is not None:
119            bui.buttonwidget(
120                edit=self._back_button,
121                button_type='backSmall',
122                size=(60, 60),
123                label=bui.charstr(bui.SpecialChar.BACK),
124            )
125
126        settings_button_x = 670 if uiscale is bui.UIScale.SMALL else 570
127
128        self._num_plugins_text = bui.textwidget(
129            parent=self._root_widget,
130            position=(settings_button_x - 130, self._height - 41),
131            size=(0, 0),
132            text='',
133            h_align='center',
134            v_align='center',
135        )
136
137        self._category_button = bui.buttonwidget(
138            parent=self._root_widget,
139            scale=0.7,
140            position=(settings_button_x - 105, self._height - 60),
141            size=(130, 60),
142            label=bui.Lstr(resource='allText'),
143            autoselect=True,
144            on_activate_call=bui.WeakCall(self._show_category_options),
145            color=(0.55, 0.73, 0.25),
146            iconscale=1.2,
147        )
148
149        self._settings_button = bui.buttonwidget(
150            parent=self._root_widget,
151            position=(settings_button_x, self._height - 58),
152            size=(40, 40),
153            label='',
154            on_activate_call=self._open_settings,
155        )
156
157        bui.imagewidget(
158            parent=self._root_widget,
159            position=(settings_button_x + 3, self._height - 57),
160            draw_controller=self._settings_button,
161            size=(35, 35),
162            texture=bui.gettexture('settingsIcon'),
163        )
164
165        bui.widget(
166            edit=self._settings_button,
167            up_widget=self._settings_button,
168            right_widget=self._settings_button,
169        )
170
171        self._scrollwidget = bui.scrollwidget(
172            parent=self._root_widget,
173            position=(50 + x_inset, 50),
174            simple_culling_v=20.0,
175            highlight=False,
176            size=(self._scroll_width, self._scroll_height),
177            selection_loops_to_parent=True,
178            claims_left_right=True,
179        )
180        bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
181
182        self._no_plugins_installed_text = bui.textwidget(
183            parent=self._root_widget,
184            position=(self._width * 0.5, self._height * 0.5),
185            size=(0, 0),
186            text='',
187            color=(0.6, 0.6, 0.6),
188            scale=0.8,
189            h_align='center',
190            v_align='center',
191        )
192
193        if bui.app.meta.scanresults is None:
194            bui.screenmessage(
195                'Still scanning plugins; please try again.', color=(1, 0, 0)
196            )
197            bui.getsound('error').play()
198        plugspecs = bui.app.plugins.plugin_specs
199        plugstates: dict[str, dict] = bui.app.config.get('Plugins', {})
200        assert isinstance(plugstates, dict)
201
202        plug_line_height = 50
203        sub_width = self._scroll_width
204        sub_height = len(plugspecs) * plug_line_height
205        self._subcontainer = bui.containerwidget(
206            parent=self._scrollwidget,
207            size=(sub_width, sub_height),
208            background=False,
209        )
210        self._show_plugins()
211        bui.containerwidget(
212            edit=self._root_widget, selected_child=self._scrollwidget
213        )
214        self._restore_state()
215
216    def _check_value_changed(self, plug: bui.PluginSpec, value: bool) -> None:
217        bui.screenmessage(
218            bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
219            color=(1.0, 0.5, 0.0),
220        )
221        plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
222        assert isinstance(plugstates, dict)
223        plugstate = plugstates.setdefault(plug.class_path, {})
224        plugstate['enabled'] = value
225        bui.app.config.commit()
226
227    def _open_settings(self) -> None:
228        # pylint: disable=cyclic-import
229        from bauiv1lib.settings.pluginsettings import PluginSettingsWindow
230
231        # no-op if our underlying widget is dead or on its way out.
232        if not self._root_widget or self._root_widget.transitioning_out:
233            return
234
235        self._save_state()
236        bui.containerwidget(edit=self._root_widget, transition='out_left')
237        assert bui.app.classic is not None
238        bui.app.ui_v1.set_main_menu_window(
239            PluginSettingsWindow(transition='in_right').get_root_widget(),
240            from_window=self._root_widget,
241        )
242
243    def _show_category_options(self) -> None:
244        uiscale = bui.app.ui_v1.uiscale
245
246        popup.PopupMenuWindow(
247            position=self._category_button.get_screen_space_center(),
248            scale=(
249                2.3
250                if uiscale is bui.UIScale.SMALL
251                else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
252            ),
253            choices=[c.value for c in Category],
254            choices_display=[bui.Lstr(resource=c.resource) for c in Category],
255            current_choice=self._category.value,
256            delegate=self,
257        )
258
259    def popup_menu_selected_choice(
260        self, popup_window: popup.PopupMenuWindow, choice: str
261    ) -> None:
262        """Called when a choice is selected in the popup."""
263        del popup_window  # unused
264        self._category = Category(choice)
265        self._clear_scroll_widget()
266        self._show_plugins()
267
268        bui.buttonwidget(
269            edit=self._category_button,
270            label=bui.Lstr(resource=self._category.resource),
271        )
272
273    def popup_menu_closing(self, popup_window: popup.PopupWindow) -> None:
274        """Called when the popup is closing."""
275
276    def _clear_scroll_widget(self) -> None:
277        existing_widgets = self._subcontainer.get_children()
278        if existing_widgets:
279            for i in existing_widgets:
280                i.delete()
281
282    def _show_plugins(self) -> None:
283        # pylint: disable=too-many-locals
284        # pylint: disable=too-many-branches
285        # pylint: disable=too-many-statements
286        plugspecs = bui.app.plugins.plugin_specs
287        plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
288        assert isinstance(plugstates, dict)
289
290        plug_line_height = 50
291        sub_width = self._scroll_width
292        num_enabled = 0
293        num_disabled = 0
294
295        plugspecs_sorted = sorted(plugspecs.items())
296
297        bui.textwidget(
298            edit=self._no_plugins_installed_text,
299            text='',
300        )
301
302        for _classpath, plugspec in plugspecs_sorted:
303            # counting number of enabled and disabled plugins
304            # plugstate = plugstates.setdefault(plugspec[0], {})
305            if plugspec.enabled:
306                num_enabled += 1
307            else:
308                num_disabled += 1
309
310        if self._category is Category.ALL:
311            sub_height = len(plugspecs) * plug_line_height
312            bui.containerwidget(
313                edit=self._subcontainer, size=(self._scroll_width, sub_height)
314            )
315        elif self._category is Category.ENABLED:
316            sub_height = num_enabled * plug_line_height
317            bui.containerwidget(
318                edit=self._subcontainer, size=(self._scroll_width, sub_height)
319            )
320        elif self._category is Category.DISABLED:
321            sub_height = num_disabled * plug_line_height
322            bui.containerwidget(
323                edit=self._subcontainer, size=(self._scroll_width, sub_height)
324            )
325        else:
326            # Make sure we handle all cases.
327            assert_never(self._category)
328
329        num_shown = 0
330        for classpath, plugspec in plugspecs_sorted:
331            plugin = plugspec.plugin
332            enabled = plugspec.enabled
333
334            if self._category is Category.ALL:
335                show = True
336            elif self._category is Category.ENABLED:
337                show = enabled
338            elif self._category is Category.DISABLED:
339                show = not enabled
340            else:
341                assert_never(self._category)
342                # show = False
343
344            if not show:
345                continue
346
347            item_y = sub_height - (num_shown + 1) * plug_line_height
348            check = bui.checkboxwidget(
349                parent=self._subcontainer,
350                text=bui.Lstr(value=classpath),
351                autoselect=True,
352                value=enabled,
353                maxwidth=self._scroll_width - 200,
354                position=(10, item_y),
355                size=(self._scroll_width - 40, 50),
356                on_value_change_call=bui.Call(
357                    self._check_value_changed, plugspec
358                ),
359                textcolor=(
360                    (0.8, 0.3, 0.3)
361                    if (plugspec.attempted_load and plugspec.plugin is None)
362                    else (
363                        (0.6, 0.6, 0.6)
364                        if plugspec.plugin is None
365                        else (0, 1, 0)
366                    )
367                ),
368            )
369            # noinspection PyUnresolvedReferences
370            if plugin is not None and plugin.has_settings_ui():
371                button = bui.buttonwidget(
372                    parent=self._subcontainer,
373                    label=bui.Lstr(resource='mainMenu.settingsText'),
374                    autoselect=True,
375                    size=(100, 40),
376                    position=(sub_width - 130, item_y + 6),
377                )
378                # noinspection PyUnresolvedReferences
379                bui.buttonwidget(
380                    edit=button,
381                    on_activate_call=bui.Call(plugin.show_settings_ui, button),
382                )
383            else:
384                button = None
385
386            # Allow getting back to back button.
387            if num_shown == 0:
388                bui.widget(
389                    edit=check,
390                    up_widget=self._back_button,
391                    left_widget=self._back_button,
392                    right_widget=self._settings_button,
393                )
394                if button is not None:
395                    bui.widget(edit=button, up_widget=self._back_button)
396
397            # Make sure we scroll all the way to the end when using
398            # keyboard/button nav.
399            bui.widget(edit=check, show_buffer_top=40, show_buffer_bottom=40)
400            num_shown += 1
401
402        bui.textwidget(
403            edit=self._num_plugins_text,
404            text=str(num_shown),
405        )
406
407        if num_shown == 0:
408            bui.textwidget(
409                edit=self._no_plugins_installed_text,
410                text=bui.Lstr(resource='noPluginsInstalledText'),
411            )
412
413    def _save_state(self) -> None:
414        try:
415            sel = self._root_widget.get_selected_child()
416            if sel == self._category_button:
417                sel_name = 'Category'
418            elif sel == self._settings_button:
419                sel_name = 'Settings'
420            elif sel == self._back_button:
421                sel_name = 'Back'
422            elif sel == self._scrollwidget:
423                sel_name = 'Scroll'
424            else:
425                raise ValueError(f'unrecognized selection \'{sel}\'')
426            assert bui.app.classic is not None
427            bui.app.ui_v1.window_states[type(self)] = sel_name
428        except Exception:
429            logging.exception('Error saving state for %s.', self)
430
431    def _restore_state(self) -> None:
432        try:
433            assert bui.app.classic is not None
434            sel_name = bui.app.ui_v1.window_states.get(type(self))
435            sel: bui.Widget | None
436            if sel_name == 'Category':
437                sel = self._category_button
438            elif sel_name == 'Settings':
439                sel = self._settings_button
440            elif sel_name == 'Back':
441                sel = self._back_button
442            else:
443                sel = self._scrollwidget
444            if sel:
445                bui.containerwidget(edit=self._root_widget, selected_child=sel)
446        except Exception:
447            logging.exception('Error restoring state for %s.', self)
448
449    def _do_back(self) -> None:
450        # pylint: disable=cyclic-import
451        from bauiv1lib.settings.advanced import AdvancedSettingsWindow
452
453        # no-op if our underlying widget is dead or on its way out.
454        if not self._root_widget or self._root_widget.transitioning_out:
455            return
456
457        self._save_state()
458        bui.containerwidget(
459            edit=self._root_widget, transition=self._transition_out
460        )
461        assert bui.app.classic is not None
462        bui.app.ui_v1.set_main_menu_window(
463            AdvancedSettingsWindow(transition='in_left').get_root_widget(),
464            from_window=self._root_widget,
465        )
class Category(enum.Enum):
19class Category(Enum):
20    """Categories we can display."""
21
22    ALL = 'all'
23    ENABLED = 'enabled'
24    DISABLED = 'disabled'
25
26    @property
27    def resource(self) -> str:
28        """Resource name for us."""
29        return f'{self.value}Text'

Categories we can display.

ALL = <Category.ALL: 'all'>
ENABLED = <Category.ENABLED: 'enabled'>
DISABLED = <Category.DISABLED: 'disabled'>
resource: str
26    @property
27    def resource(self) -> str:
28        """Resource name for us."""
29        return f'{self.value}Text'

Resource name for us.

Inherited Members
enum.Enum
name
value
class PluginWindow(bauiv1._uitypes.Window):
 32class PluginWindow(bui.Window):
 33    """Window for configuring plugins."""
 34
 35    def __init__(
 36        self,
 37        transition: str = 'in_right',
 38        origin_widget: bui.Widget | None = None,
 39    ):
 40        # pylint: disable=too-many-statements
 41        app = bui.app
 42
 43        self._category = Category.ALL
 44
 45        # If they provided an origin-widget, scale up from that.
 46        scale_origin: tuple[float, float] | None
 47        if origin_widget is not None:
 48            self._transition_out = 'out_scale'
 49            scale_origin = origin_widget.get_screen_space_center()
 50            transition = 'in_scale'
 51        else:
 52            self._transition_out = 'out_right'
 53            scale_origin = None
 54
 55        assert bui.app.classic is not None
 56        uiscale = bui.app.ui_v1.uiscale
 57        self._width = 870.0 if uiscale is bui.UIScale.SMALL else 670.0
 58        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 59        self._height = (
 60            390.0
 61            if uiscale is bui.UIScale.SMALL
 62            else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0
 63        )
 64        top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
 65        super().__init__(
 66            root_widget=bui.containerwidget(
 67                size=(self._width, self._height + top_extra),
 68                transition=transition,
 69                toolbar_visibility='menu_minimal',
 70                scale_origin_stack_offset=scale_origin,
 71                scale=(
 72                    2.06
 73                    if uiscale is bui.UIScale.SMALL
 74                    else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
 75                ),
 76                stack_offset=(
 77                    (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
 78                ),
 79            )
 80        )
 81
 82        self._scroll_width = self._width - (100 + 2 * x_inset)
 83        self._scroll_height = self._height - 115.0
 84        self._sub_width = self._scroll_width * 0.95
 85        self._sub_height = 724.0
 86
 87        assert app.classic is not None
 88        if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
 89            bui.containerwidget(
 90                edit=self._root_widget, on_cancel_call=self._do_back
 91            )
 92            self._back_button = None
 93        else:
 94            self._back_button = bui.buttonwidget(
 95                parent=self._root_widget,
 96                position=(53 + x_inset, self._height - 60),
 97                size=(140, 60),
 98                scale=0.8,
 99                autoselect=True,
100                label=bui.Lstr(resource='backText'),
101                button_type='back',
102                on_activate_call=self._do_back,
103            )
104            bui.containerwidget(
105                edit=self._root_widget, cancel_button=self._back_button
106            )
107
108        self._title_text = bui.textwidget(
109            parent=self._root_widget,
110            position=(self._width * 0.5, self._height - 41),
111            size=(0, 0),
112            text=bui.Lstr(resource='pluginsText'),
113            color=app.ui_v1.title_color,
114            maxwidth=170,
115            h_align='center',
116            v_align='center',
117        )
118
119        if self._back_button is not None:
120            bui.buttonwidget(
121                edit=self._back_button,
122                button_type='backSmall',
123                size=(60, 60),
124                label=bui.charstr(bui.SpecialChar.BACK),
125            )
126
127        settings_button_x = 670 if uiscale is bui.UIScale.SMALL else 570
128
129        self._num_plugins_text = bui.textwidget(
130            parent=self._root_widget,
131            position=(settings_button_x - 130, self._height - 41),
132            size=(0, 0),
133            text='',
134            h_align='center',
135            v_align='center',
136        )
137
138        self._category_button = bui.buttonwidget(
139            parent=self._root_widget,
140            scale=0.7,
141            position=(settings_button_x - 105, self._height - 60),
142            size=(130, 60),
143            label=bui.Lstr(resource='allText'),
144            autoselect=True,
145            on_activate_call=bui.WeakCall(self._show_category_options),
146            color=(0.55, 0.73, 0.25),
147            iconscale=1.2,
148        )
149
150        self._settings_button = bui.buttonwidget(
151            parent=self._root_widget,
152            position=(settings_button_x, self._height - 58),
153            size=(40, 40),
154            label='',
155            on_activate_call=self._open_settings,
156        )
157
158        bui.imagewidget(
159            parent=self._root_widget,
160            position=(settings_button_x + 3, self._height - 57),
161            draw_controller=self._settings_button,
162            size=(35, 35),
163            texture=bui.gettexture('settingsIcon'),
164        )
165
166        bui.widget(
167            edit=self._settings_button,
168            up_widget=self._settings_button,
169            right_widget=self._settings_button,
170        )
171
172        self._scrollwidget = bui.scrollwidget(
173            parent=self._root_widget,
174            position=(50 + x_inset, 50),
175            simple_culling_v=20.0,
176            highlight=False,
177            size=(self._scroll_width, self._scroll_height),
178            selection_loops_to_parent=True,
179            claims_left_right=True,
180        )
181        bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
182
183        self._no_plugins_installed_text = bui.textwidget(
184            parent=self._root_widget,
185            position=(self._width * 0.5, self._height * 0.5),
186            size=(0, 0),
187            text='',
188            color=(0.6, 0.6, 0.6),
189            scale=0.8,
190            h_align='center',
191            v_align='center',
192        )
193
194        if bui.app.meta.scanresults is None:
195            bui.screenmessage(
196                'Still scanning plugins; please try again.', color=(1, 0, 0)
197            )
198            bui.getsound('error').play()
199        plugspecs = bui.app.plugins.plugin_specs
200        plugstates: dict[str, dict] = bui.app.config.get('Plugins', {})
201        assert isinstance(plugstates, dict)
202
203        plug_line_height = 50
204        sub_width = self._scroll_width
205        sub_height = len(plugspecs) * plug_line_height
206        self._subcontainer = bui.containerwidget(
207            parent=self._scrollwidget,
208            size=(sub_width, sub_height),
209            background=False,
210        )
211        self._show_plugins()
212        bui.containerwidget(
213            edit=self._root_widget, selected_child=self._scrollwidget
214        )
215        self._restore_state()
216
217    def _check_value_changed(self, plug: bui.PluginSpec, value: bool) -> None:
218        bui.screenmessage(
219            bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
220            color=(1.0, 0.5, 0.0),
221        )
222        plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
223        assert isinstance(plugstates, dict)
224        plugstate = plugstates.setdefault(plug.class_path, {})
225        plugstate['enabled'] = value
226        bui.app.config.commit()
227
228    def _open_settings(self) -> None:
229        # pylint: disable=cyclic-import
230        from bauiv1lib.settings.pluginsettings import PluginSettingsWindow
231
232        # no-op if our underlying widget is dead or on its way out.
233        if not self._root_widget or self._root_widget.transitioning_out:
234            return
235
236        self._save_state()
237        bui.containerwidget(edit=self._root_widget, transition='out_left')
238        assert bui.app.classic is not None
239        bui.app.ui_v1.set_main_menu_window(
240            PluginSettingsWindow(transition='in_right').get_root_widget(),
241            from_window=self._root_widget,
242        )
243
244    def _show_category_options(self) -> None:
245        uiscale = bui.app.ui_v1.uiscale
246
247        popup.PopupMenuWindow(
248            position=self._category_button.get_screen_space_center(),
249            scale=(
250                2.3
251                if uiscale is bui.UIScale.SMALL
252                else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
253            ),
254            choices=[c.value for c in Category],
255            choices_display=[bui.Lstr(resource=c.resource) for c in Category],
256            current_choice=self._category.value,
257            delegate=self,
258        )
259
260    def popup_menu_selected_choice(
261        self, popup_window: popup.PopupMenuWindow, choice: str
262    ) -> None:
263        """Called when a choice is selected in the popup."""
264        del popup_window  # unused
265        self._category = Category(choice)
266        self._clear_scroll_widget()
267        self._show_plugins()
268
269        bui.buttonwidget(
270            edit=self._category_button,
271            label=bui.Lstr(resource=self._category.resource),
272        )
273
274    def popup_menu_closing(self, popup_window: popup.PopupWindow) -> None:
275        """Called when the popup is closing."""
276
277    def _clear_scroll_widget(self) -> None:
278        existing_widgets = self._subcontainer.get_children()
279        if existing_widgets:
280            for i in existing_widgets:
281                i.delete()
282
283    def _show_plugins(self) -> None:
284        # pylint: disable=too-many-locals
285        # pylint: disable=too-many-branches
286        # pylint: disable=too-many-statements
287        plugspecs = bui.app.plugins.plugin_specs
288        plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
289        assert isinstance(plugstates, dict)
290
291        plug_line_height = 50
292        sub_width = self._scroll_width
293        num_enabled = 0
294        num_disabled = 0
295
296        plugspecs_sorted = sorted(plugspecs.items())
297
298        bui.textwidget(
299            edit=self._no_plugins_installed_text,
300            text='',
301        )
302
303        for _classpath, plugspec in plugspecs_sorted:
304            # counting number of enabled and disabled plugins
305            # plugstate = plugstates.setdefault(plugspec[0], {})
306            if plugspec.enabled:
307                num_enabled += 1
308            else:
309                num_disabled += 1
310
311        if self._category is Category.ALL:
312            sub_height = len(plugspecs) * plug_line_height
313            bui.containerwidget(
314                edit=self._subcontainer, size=(self._scroll_width, sub_height)
315            )
316        elif self._category is Category.ENABLED:
317            sub_height = num_enabled * plug_line_height
318            bui.containerwidget(
319                edit=self._subcontainer, size=(self._scroll_width, sub_height)
320            )
321        elif self._category is Category.DISABLED:
322            sub_height = num_disabled * plug_line_height
323            bui.containerwidget(
324                edit=self._subcontainer, size=(self._scroll_width, sub_height)
325            )
326        else:
327            # Make sure we handle all cases.
328            assert_never(self._category)
329
330        num_shown = 0
331        for classpath, plugspec in plugspecs_sorted:
332            plugin = plugspec.plugin
333            enabled = plugspec.enabled
334
335            if self._category is Category.ALL:
336                show = True
337            elif self._category is Category.ENABLED:
338                show = enabled
339            elif self._category is Category.DISABLED:
340                show = not enabled
341            else:
342                assert_never(self._category)
343                # show = False
344
345            if not show:
346                continue
347
348            item_y = sub_height - (num_shown + 1) * plug_line_height
349            check = bui.checkboxwidget(
350                parent=self._subcontainer,
351                text=bui.Lstr(value=classpath),
352                autoselect=True,
353                value=enabled,
354                maxwidth=self._scroll_width - 200,
355                position=(10, item_y),
356                size=(self._scroll_width - 40, 50),
357                on_value_change_call=bui.Call(
358                    self._check_value_changed, plugspec
359                ),
360                textcolor=(
361                    (0.8, 0.3, 0.3)
362                    if (plugspec.attempted_load and plugspec.plugin is None)
363                    else (
364                        (0.6, 0.6, 0.6)
365                        if plugspec.plugin is None
366                        else (0, 1, 0)
367                    )
368                ),
369            )
370            # noinspection PyUnresolvedReferences
371            if plugin is not None and plugin.has_settings_ui():
372                button = bui.buttonwidget(
373                    parent=self._subcontainer,
374                    label=bui.Lstr(resource='mainMenu.settingsText'),
375                    autoselect=True,
376                    size=(100, 40),
377                    position=(sub_width - 130, item_y + 6),
378                )
379                # noinspection PyUnresolvedReferences
380                bui.buttonwidget(
381                    edit=button,
382                    on_activate_call=bui.Call(plugin.show_settings_ui, button),
383                )
384            else:
385                button = None
386
387            # Allow getting back to back button.
388            if num_shown == 0:
389                bui.widget(
390                    edit=check,
391                    up_widget=self._back_button,
392                    left_widget=self._back_button,
393                    right_widget=self._settings_button,
394                )
395                if button is not None:
396                    bui.widget(edit=button, up_widget=self._back_button)
397
398            # Make sure we scroll all the way to the end when using
399            # keyboard/button nav.
400            bui.widget(edit=check, show_buffer_top=40, show_buffer_bottom=40)
401            num_shown += 1
402
403        bui.textwidget(
404            edit=self._num_plugins_text,
405            text=str(num_shown),
406        )
407
408        if num_shown == 0:
409            bui.textwidget(
410                edit=self._no_plugins_installed_text,
411                text=bui.Lstr(resource='noPluginsInstalledText'),
412            )
413
414    def _save_state(self) -> None:
415        try:
416            sel = self._root_widget.get_selected_child()
417            if sel == self._category_button:
418                sel_name = 'Category'
419            elif sel == self._settings_button:
420                sel_name = 'Settings'
421            elif sel == self._back_button:
422                sel_name = 'Back'
423            elif sel == self._scrollwidget:
424                sel_name = 'Scroll'
425            else:
426                raise ValueError(f'unrecognized selection \'{sel}\'')
427            assert bui.app.classic is not None
428            bui.app.ui_v1.window_states[type(self)] = sel_name
429        except Exception:
430            logging.exception('Error saving state for %s.', self)
431
432    def _restore_state(self) -> None:
433        try:
434            assert bui.app.classic is not None
435            sel_name = bui.app.ui_v1.window_states.get(type(self))
436            sel: bui.Widget | None
437            if sel_name == 'Category':
438                sel = self._category_button
439            elif sel_name == 'Settings':
440                sel = self._settings_button
441            elif sel_name == 'Back':
442                sel = self._back_button
443            else:
444                sel = self._scrollwidget
445            if sel:
446                bui.containerwidget(edit=self._root_widget, selected_child=sel)
447        except Exception:
448            logging.exception('Error restoring state for %s.', self)
449
450    def _do_back(self) -> None:
451        # pylint: disable=cyclic-import
452        from bauiv1lib.settings.advanced import AdvancedSettingsWindow
453
454        # no-op if our underlying widget is dead or on its way out.
455        if not self._root_widget or self._root_widget.transitioning_out:
456            return
457
458        self._save_state()
459        bui.containerwidget(
460            edit=self._root_widget, transition=self._transition_out
461        )
462        assert bui.app.classic is not None
463        bui.app.ui_v1.set_main_menu_window(
464            AdvancedSettingsWindow(transition='in_left').get_root_widget(),
465            from_window=self._root_widget,
466        )

Window for configuring plugins.

PluginWindow( transition: str = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 35    def __init__(
 36        self,
 37        transition: str = 'in_right',
 38        origin_widget: bui.Widget | None = None,
 39    ):
 40        # pylint: disable=too-many-statements
 41        app = bui.app
 42
 43        self._category = Category.ALL
 44
 45        # If they provided an origin-widget, scale up from that.
 46        scale_origin: tuple[float, float] | None
 47        if origin_widget is not None:
 48            self._transition_out = 'out_scale'
 49            scale_origin = origin_widget.get_screen_space_center()
 50            transition = 'in_scale'
 51        else:
 52            self._transition_out = 'out_right'
 53            scale_origin = None
 54
 55        assert bui.app.classic is not None
 56        uiscale = bui.app.ui_v1.uiscale
 57        self._width = 870.0 if uiscale is bui.UIScale.SMALL else 670.0
 58        x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
 59        self._height = (
 60            390.0
 61            if uiscale is bui.UIScale.SMALL
 62            else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0
 63        )
 64        top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
 65        super().__init__(
 66            root_widget=bui.containerwidget(
 67                size=(self._width, self._height + top_extra),
 68                transition=transition,
 69                toolbar_visibility='menu_minimal',
 70                scale_origin_stack_offset=scale_origin,
 71                scale=(
 72                    2.06
 73                    if uiscale is bui.UIScale.SMALL
 74                    else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
 75                ),
 76                stack_offset=(
 77                    (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
 78                ),
 79            )
 80        )
 81
 82        self._scroll_width = self._width - (100 + 2 * x_inset)
 83        self._scroll_height = self._height - 115.0
 84        self._sub_width = self._scroll_width * 0.95
 85        self._sub_height = 724.0
 86
 87        assert app.classic is not None
 88        if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
 89            bui.containerwidget(
 90                edit=self._root_widget, on_cancel_call=self._do_back
 91            )
 92            self._back_button = None
 93        else:
 94            self._back_button = bui.buttonwidget(
 95                parent=self._root_widget,
 96                position=(53 + x_inset, self._height - 60),
 97                size=(140, 60),
 98                scale=0.8,
 99                autoselect=True,
100                label=bui.Lstr(resource='backText'),
101                button_type='back',
102                on_activate_call=self._do_back,
103            )
104            bui.containerwidget(
105                edit=self._root_widget, cancel_button=self._back_button
106            )
107
108        self._title_text = bui.textwidget(
109            parent=self._root_widget,
110            position=(self._width * 0.5, self._height - 41),
111            size=(0, 0),
112            text=bui.Lstr(resource='pluginsText'),
113            color=app.ui_v1.title_color,
114            maxwidth=170,
115            h_align='center',
116            v_align='center',
117        )
118
119        if self._back_button is not None:
120            bui.buttonwidget(
121                edit=self._back_button,
122                button_type='backSmall',
123                size=(60, 60),
124                label=bui.charstr(bui.SpecialChar.BACK),
125            )
126
127        settings_button_x = 670 if uiscale is bui.UIScale.SMALL else 570
128
129        self._num_plugins_text = bui.textwidget(
130            parent=self._root_widget,
131            position=(settings_button_x - 130, self._height - 41),
132            size=(0, 0),
133            text='',
134            h_align='center',
135            v_align='center',
136        )
137
138        self._category_button = bui.buttonwidget(
139            parent=self._root_widget,
140            scale=0.7,
141            position=(settings_button_x - 105, self._height - 60),
142            size=(130, 60),
143            label=bui.Lstr(resource='allText'),
144            autoselect=True,
145            on_activate_call=bui.WeakCall(self._show_category_options),
146            color=(0.55, 0.73, 0.25),
147            iconscale=1.2,
148        )
149
150        self._settings_button = bui.buttonwidget(
151            parent=self._root_widget,
152            position=(settings_button_x, self._height - 58),
153            size=(40, 40),
154            label='',
155            on_activate_call=self._open_settings,
156        )
157
158        bui.imagewidget(
159            parent=self._root_widget,
160            position=(settings_button_x + 3, self._height - 57),
161            draw_controller=self._settings_button,
162            size=(35, 35),
163            texture=bui.gettexture('settingsIcon'),
164        )
165
166        bui.widget(
167            edit=self._settings_button,
168            up_widget=self._settings_button,
169            right_widget=self._settings_button,
170        )
171
172        self._scrollwidget = bui.scrollwidget(
173            parent=self._root_widget,
174            position=(50 + x_inset, 50),
175            simple_culling_v=20.0,
176            highlight=False,
177            size=(self._scroll_width, self._scroll_height),
178            selection_loops_to_parent=True,
179            claims_left_right=True,
180        )
181        bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
182
183        self._no_plugins_installed_text = bui.textwidget(
184            parent=self._root_widget,
185            position=(self._width * 0.5, self._height * 0.5),
186            size=(0, 0),
187            text='',
188            color=(0.6, 0.6, 0.6),
189            scale=0.8,
190            h_align='center',
191            v_align='center',
192        )
193
194        if bui.app.meta.scanresults is None:
195            bui.screenmessage(
196                'Still scanning plugins; please try again.', color=(1, 0, 0)
197            )
198            bui.getsound('error').play()
199        plugspecs = bui.app.plugins.plugin_specs
200        plugstates: dict[str, dict] = bui.app.config.get('Plugins', {})
201        assert isinstance(plugstates, dict)
202
203        plug_line_height = 50
204        sub_width = self._scroll_width
205        sub_height = len(plugspecs) * plug_line_height
206        self._subcontainer = bui.containerwidget(
207            parent=self._scrollwidget,
208            size=(sub_width, sub_height),
209            background=False,
210        )
211        self._show_plugins()
212        bui.containerwidget(
213            edit=self._root_widget, selected_child=self._scrollwidget
214        )
215        self._restore_state()
def popup_menu_selected_choice(self, popup_window: bauiv1lib.popup.PopupMenuWindow, choice: str) -> None:
260    def popup_menu_selected_choice(
261        self, popup_window: popup.PopupMenuWindow, choice: str
262    ) -> None:
263        """Called when a choice is selected in the popup."""
264        del popup_window  # unused
265        self._category = Category(choice)
266        self._clear_scroll_widget()
267        self._show_plugins()
268
269        bui.buttonwidget(
270            edit=self._category_button,
271            label=bui.Lstr(resource=self._category.resource),
272        )

Called when a choice is selected in the popup.

def popup_menu_closing(self, popup_window: bauiv1lib.popup.PopupWindow) -> None:
274    def popup_menu_closing(self, popup_window: popup.PopupWindow) -> None:
275        """Called when the popup is closing."""

Called when the popup is closing.

Inherited Members
bauiv1._uitypes.Window
get_root_widget