diff --git a/scripts/addons_core/bl_pkg/__init__.py b/scripts/addons_core/bl_pkg/__init__.py index 246b23a200b..2377bad065c 100644 --- a/scripts/addons_core/bl_pkg/__init__.py +++ b/scripts/addons_core/bl_pkg/__init__.py @@ -585,17 +585,24 @@ def register(): description="Show extensions by type", default='ADDON', ) - WindowManager.extension_enabled_only = BoolProperty( - name="Show Enabled Extensions", - description="Only show enabled extensions", - ) WindowManager.extension_updates_only = BoolProperty( name="Show Updates Available", description="Only show extensions with updates available", ) - WindowManager.extension_installed_only = BoolProperty( + WindowManager.extension_show_panel_enabled = BoolProperty( + name="Show Enabled Extensions", + description="Only show enabled extensions", + default=True, + ) + WindowManager.extension_show_panel_installed = BoolProperty( name="Show Installed Extensions", description="Only show installed extensions", + default=True, + ) + WindowManager.extension_show_panel_available = BoolProperty( + name="Show Installed Extensions", + description="Only show installed extensions", + default=True, ) from bl_ui.space_userpref import USERPREF_MT_interface_theme_presets @@ -630,8 +637,9 @@ def unregister(): del WindowManager.extension_tags del WindowManager.extension_search del WindowManager.extension_type - del WindowManager.extension_enabled_only - del WindowManager.extension_installed_only + del WindowManager.extension_show_panel_enabled + del WindowManager.extension_show_panel_installed + del WindowManager.extension_show_panel_available for cls in classes: bpy.utils.unregister_class(cls) diff --git a/scripts/addons_core/bl_pkg/bl_extension_ops.py b/scripts/addons_core/bl_pkg/bl_extension_ops.py index 16165bd7c47..39f9453649f 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ops.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ops.py @@ -3098,7 +3098,8 @@ class EXTENSIONS_OT_userpref_show_for_update(Operator): prefs.view.show_addons_enabled_only = False # Show only extensions that will be updated. - wm.extension_installed_only = False + wm.extension_show_panel_enabled = True + wm.extension_show_panel_installed = True wm.extension_updates_only = True bpy.ops.screen.userpref_show('INVOKE_DEFAULT') diff --git a/scripts/addons_core/bl_pkg/bl_extension_ui.py b/scripts/addons_core/bl_pkg/bl_extension_ui.py index 01140b6dd5d..df0458e40be 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ui.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ui.py @@ -733,6 +733,28 @@ class notify_info: notify_info._update_state = False +# Simple data-storage while drawing. +# NOTE: could be a named-tuple except the `layout_panel` needs to be mutable. +class ExtensionPanelPropData: + __slots__ = ( + "layout_base", + "label", + "prop_id", + + # `False`: uninitialized. + # `None`: initialized & hidden. + # `UILayout`: initialized & visible. + "layout_panel", + ) + + def __init__(self, layout_base, label, prop_id): + self.layout_base = layout_base + self.label = label + self.prop_id = prop_id + # Initialized as needed. + self.layout_panel = False + + def extensions_panel_draw_online_extensions_request_impl( self, _context, @@ -810,9 +832,7 @@ def extensions_panel_draw_impl( search_casefold, # `str` filter_by_type, # `str` extension_tags_exclude, # `Set[str]` - enabled_only, # `bool` updates_only, # `bool` - installed_only, # `bool` operation_in_progress, # `bool` show_development, # `bool` ): @@ -843,13 +863,17 @@ def extensions_panel_draw_impl( prefs = context.preferences - if updates_only: - installed_only = True - # Define a top-most column to place warnings (if-any). # Needed so the warnings aren't mixed in with other content. layout_topmost = layout.column() + SECTION_ENABLED, SECTION_INSTALLED, SECTION_AVAILABLE = 0, 1, 2 + layout_sections = ( + ExtensionPanelPropData(layout.column(), iface_("Enabled"), "extension_show_panel_enabled"), + ExtensionPanelPropData(layout.column(), iface_("Installed"), "extension_show_panel_installed"), + ExtensionPanelPropData(layout.column(), iface_("Available"), "extension_show_panel_available"), + ) + repos_all = extension_repos_read() if bpy.app.online_access: @@ -1004,9 +1028,6 @@ def extensions_panel_draw_impl( is_installed = item_local is not None - if installed_only and (is_installed == 0): - continue - if extension_tags_exclude: if tags_exclude_match(item.tags, extension_tags_exclude): continue @@ -1038,9 +1059,6 @@ def extensions_panel_draw_impl( is_enabled = is_installed addon_module_name = None - if enabled_only and (not is_enabled): - continue - item_version = item.version if item_local is None: item_local_version = None @@ -1053,18 +1071,36 @@ def extensions_panel_draw_impl( if not is_outdated: continue - key = (pkg_id, repo_index) - if show_development: - mark = key in blender_extension_mark - show = key in blender_extension_show - del key + if is_enabled: + section_type = SECTION_ENABLED + elif is_installed: + section_type = SECTION_INSTALLED + else: + section_type = SECTION_AVAILABLE - box = layout.box() + if (layout_panel := layout_sections[section_type].layout_panel) is False: + section = layout_sections[section_type] + layout_header, layout_panel = section.layout_base.panel_prop( + context.window_manager, + section.prop_id, + ) + layout_header.label(text=section.label, translate=False) + section.layout_panel = layout_panel + del section, layout_header + if layout_panel is None: + continue + + box = layout_panel.box() + del layout_panel, section_type # Left align so the operator text isn't centered. colsub = box.column() row = colsub.row(align=True) - # row.label + + if show_development: + mark = (pkg_id, repo_index) in blender_extension_mark + show = (pkg_id, repo_index) in blender_extension_show + if show: props = row.operator("extensions.package_show_clear", text="", icon='DOWNARROW_HLT', emboss=False) else: @@ -1087,13 +1123,8 @@ def extensions_panel_draw_impl( # Without checking `is_enabled` here, there is no way for the user to know if an extension # is enabled or not, which is useful to show - when they may be considering removing/updating # extensions based on them being used or not. - sub.label( - text=( - item.name if (is_enabled or is_installed is False) else - item.name + iface_(" (disabled)") - ), - translate=False, - ) + sub.label(text=item.name, translate=False) + del sub # Add a top-level row so `row_right` can have a grayed out button/label @@ -1113,10 +1144,6 @@ def extensions_panel_draw_impl( props.repo_index = repo_index props.pkg_id = pkg_id del props - else: - # Right space for alignment with the button. - row_right.label(text="Installed ") - row_right.active = False else: props = row_right.operator("extensions.package_install", text="Install") props.repo_index = repo_index @@ -1127,8 +1154,6 @@ def extensions_panel_draw_impl( if has_remote and (item_remote is None): # There is a local item with no remote row_right.label(text="Orphan ") - else: - row_right.label(text="Installed ") row_right.active = False @@ -1224,11 +1249,7 @@ class USERPREF_PT_extensions_filter(Panel): col = layout.column(heading="Show Only") col.use_property_split = True - col.prop(wm, "extension_enabled_only", text="Enabled Extensions") col.prop(wm, "extension_updates_only", text="Updates Available") - sub = col.column() - sub.active = (not wm.extension_enabled_only) and (not wm.extension_updates_only) - sub.prop(wm, "extension_installed_only", text="Installed Extensions") class USERPREF_PT_addons_tags(Panel): @@ -1587,9 +1608,7 @@ def extensions_panel_draw(panel, context): wm.extension_search.casefold(), blender_filter_by_type_map[wm.extension_type], extension_tags_exclude, - wm.extension_enabled_only, wm.extension_updates_only, - wm.extension_installed_only, operation_in_progress, show_development, ) @@ -1649,17 +1668,23 @@ def tags_current(wm, tags_attr): if tags_attr == "addon_tags": filter_by_type = "add-on" - only_enabled = prefs.view.show_addons_enabled_only + show_enabled = prefs.view.show_addons_enabled_only + show_installed = True + show_available = False else: filter_by_type = blender_filter_by_type_map[wm.extension_type] - only_enabled = wm.extension_enabled_only - - # Currently only add-ons can make use of enabled by type (usefully) for tags. - if only_enabled and (filter_by_type == "add-on"): - addons_enabled = {addon.module for addon in prefs.addons} + show_enabled = wm.extension_show_panel_enabled + show_installed = wm.extension_show_panel_installed + show_available = wm.extension_show_panel_available repos_all = extension_repos_read() + # Currently only add-ons can make use of enabled by type (usefully) for tags. + if filter_by_type == "add-on": + addons_enabled = {addon.module for addon in prefs.addons} + elif filter_by_type == "theme": + active_theme_info = pkg_repo_and_id_from_theme_path(repos_all, prefs.themes[0].filepath) + tags = set() for repo_index, ( @@ -1673,9 +1698,8 @@ def tags_current(wm, tags_attr): ((None,) * len(repos_all)), strict=True, )): - if only_enabled: - if filter_by_type == "add-on": - repo_module_prefix = pkg_repo_module_prefix(repos_all[repo_index]) + if filter_by_type == "add-on": + repo_module_prefix = pkg_repo_module_prefix(repos_all[repo_index]) for pkg_id, (item_local, item_remote) in pkg_manifest_zip_all_items(pkg_manifest_local, pkg_manifest_remote): item = item_local or item_remote @@ -1684,16 +1708,31 @@ def tags_current(wm, tags_attr): if filter_by_type != item.type: continue - # Filter using `Only Enabled`. - # NOTE: this is only supported by add-ons currently. - # This could be made to work for themes too however there is only ever one enabled theme at a time. - # The use case for that is weak at best. "Only Enabled" can be supported by other types as needed. - if only_enabled: - if filter_by_type == "add-on": - if item_local is not None: - addon_module_name = repo_module_prefix + pkg_id - if addon_module_name not in addons_enabled: - continue + is_installed = item_local is not None + + # Filter using panel toggles. + if filter_by_type == "add-on": + is_enabled = False + if item_local is not None: + addon_module_name = repo_module_prefix + pkg_id + if addon_module_name in addons_enabled: + is_enabled = True + elif filter_by_type == "theme": + is_enabled = (repo_index, pkg_id) == active_theme_info + else: + assert False, "Unreachable" + continue + + if is_enabled: + if not show_enabled: + continue + elif is_installed: + if not show_installed: + continue + else: + if not show_available: + continue + if pkg_tags := item.tags: tags.update(pkg_tags) @@ -1707,7 +1746,7 @@ def tags_current(wm, tags_attr): is_extension = addon_utils.check_extension(module_name) if is_extension: continue - if only_enabled: # No need to check `filter_by_type` here. + if show_enabled: # No need to check `filter_by_type` here. if module_name not in addons_enabled: continue bl_info = addon_utils.module_bl_info(mod)