Extensions: split extensions by category Enabled/Installed/Available

Each section (if not empty) now shows a panel header.
Now there is no need to include "installed/disabled" text next to each
extension. This also removes the need for most of the filtering options.

See design task #123660.
This commit is contained in:
Campbell Barton
2024-06-25 21:53:04 +10:00
parent fa39948602
commit 696c997d72
3 changed files with 113 additions and 65 deletions

View File

@@ -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)

View File

@@ -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')

View File

@@ -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)