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