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

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

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
 63            if uiscale is bui.UIScale.MEDIUM
 64            else 520.0
 65        )
 66        top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
 67        super().__init__(
 68            root_widget=bui.containerwidget(
 69                size=(self._width, self._height + top_extra),
 70                transition=transition,
 71                toolbar_visibility='menu_minimal',
 72                scale_origin_stack_offset=scale_origin,
 73                scale=(
 74                    2.06
 75                    if uiscale is bui.UIScale.SMALL
 76                    else 1.4
 77                    if uiscale is bui.UIScale.MEDIUM
 78                    else 1.0
 79                ),
 80                stack_offset=(0, -25)
 81                if uiscale is bui.UIScale.SMALL
 82                else (0, 0),
 83            )
 84        )
 85
 86        self._scroll_width = self._width - (100 + 2 * x_inset)
 87        self._scroll_height = self._height - 115.0
 88        self._sub_width = self._scroll_width * 0.95
 89        self._sub_height = 724.0
 90
 91        assert app.classic is not None
 92        if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
 93            bui.containerwidget(
 94                edit=self._root_widget, on_cancel_call=self._do_back
 95            )
 96            self._back_button = None
 97        else:
 98            self._back_button = bui.buttonwidget(
 99                parent=self._root_widget,
100                position=(53 + x_inset, self._height - 60),
101                size=(140, 60),
102                scale=0.8,
103                autoselect=True,
104                label=bui.Lstr(resource='backText'),
105                button_type='back',
106                on_activate_call=self._do_back,
107            )
108            bui.containerwidget(
109                edit=self._root_widget, cancel_button=self._back_button
110            )
111
112        self._title_text = bui.textwidget(
113            parent=self._root_widget,
114            position=(self._width * 0.5, self._height - 38),
115            size=(0, 0),
116            text=bui.Lstr(resource='pluginsText'),
117            color=app.ui_v1.title_color,
118            maxwidth=200,
119            h_align='center',
120            v_align='center',
121        )
122
123        if self._back_button is not None:
124            bui.buttonwidget(
125                edit=self._back_button,
126                button_type='backSmall',
127                size=(60, 60),
128                label=bui.charstr(bui.SpecialChar.BACK),
129            )
130
131        settings_button_x = 670 if uiscale is bui.UIScale.SMALL else 570
132
133        self._category_button = bui.buttonwidget(
134            parent=self._root_widget,
135            scale=0.7,
136            position=(settings_button_x - 105, self._height - 60),
137            size=(130, 60),
138            label=bui.Lstr(resource='allText'),
139            autoselect=True,
140            on_activate_call=bui.WeakCall(self._show_category_options),
141            color=(0.55, 0.73, 0.25),
142            iconscale=1.2,
143        )
144
145        self._settings_button = bui.buttonwidget(
146            parent=self._root_widget,
147            position=(settings_button_x, self._height - 58),
148            size=(40, 40),
149            label='',
150            on_activate_call=self._open_settings,
151        )
152
153        bui.imagewidget(
154            parent=self._root_widget,
155            position=(settings_button_x + 3, self._height - 57),
156            draw_controller=self._settings_button,
157            size=(35, 35),
158            texture=bui.gettexture('settingsIcon'),
159        )
160
161        bui.widget(
162            edit=self._settings_button,
163            up_widget=self._settings_button,
164            right_widget=self._settings_button,
165        )
166
167        self._scrollwidget = bui.scrollwidget(
168            parent=self._root_widget,
169            position=(50 + x_inset, 50),
170            simple_culling_v=20.0,
171            highlight=False,
172            size=(self._scroll_width, self._scroll_height),
173            selection_loops_to_parent=True,
174            claims_left_right=True,
175        )
176        bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
177
178        if bui.app.meta.scanresults is None:
179            bui.screenmessage(
180                'Still scanning plugins; please try again.', color=(1, 0, 0)
181            )
182            bui.getsound('error').play()
183        plugspecs = bui.app.plugins.plugin_specs
184        plugstates: dict[str, dict] = bui.app.config.get('Plugins', {})
185        assert isinstance(plugstates, dict)
186
187        plug_line_height = 50
188        sub_width = self._scroll_width
189        sub_height = len(plugspecs) * plug_line_height
190        self._subcontainer = bui.containerwidget(
191            parent=self._scrollwidget,
192            size=(sub_width, sub_height),
193            background=False,
194        )
195        self._show_plugins()
196        bui.containerwidget(
197            edit=self._root_widget, selected_child=self._scrollwidget
198        )
199        self._restore_state()
def popup_menu_selected_choice(self, popup_window: bauiv1lib.popup.PopupMenuWindow, choice: str) -> None:
241    def popup_menu_selected_choice(
242        self, popup_window: popup.PopupMenuWindow, choice: str
243    ) -> None:
244        """Called when a choice is selected in the popup."""
245        del popup_window  # unused
246        self._category = Category(choice)
247        self._clear_scroll_widget()
248        self._show_plugins()
249
250        bui.buttonwidget(
251            edit=self._category_button,
252            label=bui.Lstr(resource=self._category.resource),
253        )

Called when a choice is selected in the popup.

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

Called when the popup is closing.

Inherited Members
bauiv1._uitypes.Window
get_root_widget