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