From a82f3a0fe0df2898822f1e212d00b34deda03761 Mon Sep 17 00:00:00 2001 From: Richard Antalik Date: Thu, 9 Oct 2025 04:51:18 +0200 Subject: [PATCH] UI: Add tab filter popover to vertical tabs This adds unobtrusive tab button for selecting displayed tabs to the tabs region. The idea is, that this way, the filter is much more discoverable than it being hidden in options popover. The button is grayed out, when all tabs are visible. To only draw arrow, is achieved by setting `UILayout.emboss` to `NONE` or `NONE_OR_STATUS` Pull Request: https://projects.blender.org/blender/blender/pulls/135132 --- scripts/startup/bl_ui/space_properties.py | 115 +++++++++--------- .../editors/interface/interface_layout.cc | 6 + .../editors/interface/interface_widgets.cc | 47 ++++--- 3 files changed, 97 insertions(+), 71 deletions(-) diff --git a/scripts/startup/bl_ui/space_properties.py b/scripts/startup/bl_ui/space_properties.py index a672daacba9..280b970f657 100644 --- a/scripts/startup/bl_ui/space_properties.py +++ b/scripts/startup/bl_ui/space_properties.py @@ -10,32 +10,36 @@ from bpy.app.translations import ( ) +tab_list = [ + ("show_properties_tool", "Tool", 'TOOL_SETTINGS'), + ("show_properties_render", "Render", 'SCENE'), + ("show_properties_output", "Output", 'OUTPUT'), + ("show_properties_view_layer", "View Layer", 'RENDERLAYERS'), + ("show_properties_scene", "Scene", 'SCENE_DATA'), + ("show_properties_world", "World", 'WORLD'), + ("show_properties_collection", "Collection", 'OUTLINER_COLLECTION'), + ("show_properties_object", "Object", 'OBJECT_DATA'), + ("show_properties_modifiers", "Modifiers", 'MODIFIER'), + ("show_properties_effects", "Effects", 'SHADERFX'), + ("show_properties_particles", "Particles", 'PARTICLES'), + ("show_properties_physics", "Physics", 'PHYSICS'), + ("show_properties_constraints", "Constraints", 'CONSTRAINT'), + ("show_properties_data", "Data", 'MESH_DATA'), + ("show_properties_bone", "Bone", 'BONE_DATA'), + ("show_properties_bone_constraints", "Bone Constraints", 'CONSTRAINT_BONE'), + ("show_properties_material", "Material", 'MATERIAL'), + ("show_properties_texture", "Texture", 'TEXTURE'), + ("show_properties_strip", "Strip", 'SEQ_SEQUENCER'), + ("show_properties_strip_modifier", "Strip Modifiers", 'SEQ_STRIP_MODIFIER'), +] + + class PROPERTIES_HT_header(Header): bl_space_type = 'PROPERTIES' @staticmethod def _search_poll(space): - return (space.show_properties_tool or - space.show_properties_render or - space.show_properties_output or - space.show_properties_view_layer or - space.show_properties_scene or - space.show_properties_world or - space.show_properties_collection or - space.show_properties_object or - space.show_properties_modifiers or - space.show_properties_effects or - space.show_properties_particles or - space.show_properties_physics or - space.show_properties_constraints or - space.show_properties_data or - space.show_properties_bone or - space.show_properties_bone_constraints or - space.show_properties_material or - space.show_properties_texture or - space.show_properties_strip or - space.show_properties_strip_modifier - ) + return any(getattr(space, tab_info[0]) for tab_info in tab_list) def draw(self, context): layout = self.layout @@ -64,6 +68,10 @@ class PROPERTIES_HT_header(Header): layout.popover(panel="PROPERTIES_PT_options", text="") +def has_hidden_tabs(space): + return not all(getattr(space, tab_info[0]) for tab_info in tab_list) + + class PROPERTIES_PT_navigation_bar(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'NAVIGATION_BAR' @@ -85,6 +93,17 @@ class PROPERTIES_PT_navigation_bar(Panel): else: layout.prop_tabs_enum(view, "context", icon_only=True) + # Scale sub layout to make the popover button smaller and use separator to + # offset it, such that it is centered. + sub = layout.row(align=True) + sub.alignment = 'CENTER' + sub.emboss = 'NONE' + sub.scale_x = 0.8 + sub.scale_y = 0.8 + sub.separator(factor=0.7) + sub.popover(panel="PROPERTIES_PT_visibility", text="") + sub.active = has_hidden_tabs(view) + class PROPERTIES_PT_options(Panel): """Show options for the properties editor""" @@ -101,40 +120,6 @@ class PROPERTIES_PT_options(Panel): col.label(text="Sync with Outliner") col.row().prop(space, "outliner_sync", expand=True) - layout.separator() - - layout.use_property_decorate = False - - visible_tabs = [ - ("show_properties_tool", "Tool", 'TOOL_SETTINGS'), - ("show_properties_render", "Render", 'SCENE'), - ("show_properties_output", "Output", 'OUTPUT'), - ("show_properties_view_layer", "View Layer", 'RENDERLAYERS'), - ("show_properties_scene", "Scene", 'SCENE_DATA'), - ("show_properties_world", "World", 'WORLD'), - ("show_properties_collection", "Collection", 'GROUP'), - ("show_properties_object", "Object", 'OBJECT_DATA'), - ("show_properties_modifiers", "Modifiers", 'MODIFIER'), - ("show_properties_effects", "Effects", 'SHADERFX'), - ("show_properties_particles", "Particles", 'PARTICLES'), - ("show_properties_physics", "Physics", 'PHYSICS'), - ("show_properties_constraints", "Constraints", 'CONSTRAINT'), - ("show_properties_data", "Data", 'MESH_DATA'), - ("show_properties_bone", "Bone", 'BONE_DATA'), - ("show_properties_bone_constraints", "Bone Constraints", 'CONSTRAINT_BONE'), - ("show_properties_material", "Material", 'MATERIAL'), - ("show_properties_texture", "Texture", 'TEXTURE'), - ("show_properties_strip", "Strip", 'SEQ_SEQUENCER'), - ("show_properties_strip_modifier", "Strip Modifiers", 'SEQ_STRIP_MODIFIER') - ] - - col = layout.column(align=True) - col.label(text="Visible Tabs") - for prop, name, icon in visible_tabs: - row = col.row(align=True) - row.label(text=iface_(name), icon=icon) - row.prop(space, prop, text="") - class PropertiesAnimationMixin: """Mix-in class for Animation panels. @@ -188,10 +173,30 @@ class PropertiesAnimationMixin: anim.draw_action_and_slot_selector_for_id(layout, animated_id) +class PROPERTIES_PT_visibility(Panel): + """Choose visibility of tabs in the properties editor""" + bl_space_type = 'PROPERTIES' + bl_region_type = 'HEADER' + bl_label = "Visibility" + + def draw(self, context): + space = context.space_data + layout = self.layout + layout.use_property_decorate = False + + col = layout.column(align=True) + col.label(text="Visible Tabs") + for prop, name, icon in tab_list: + row = col.row(align=True) + row.label(text=iface_(name), icon=icon) + row.prop(space, prop, text="") + + classes = ( PROPERTIES_HT_header, PROPERTIES_PT_navigation_bar, PROPERTIES_PT_options, + PROPERTIES_PT_visibility, ) if __name__ == "__main__": # only for live edit. diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc index 1d1a8aad281..2bd489bc99e 100644 --- a/source/blender/editors/interface/interface_layout.cc +++ b/source/blender/editors/interface/interface_layout.cc @@ -3049,6 +3049,12 @@ void uiLayout::popover(const bContext *C, uiBut *but = ui_item_menu( layout, name, icon, ui_item_paneltype_func, pt, nullptr, TIP_(pt->description), true); but->type = ButType::Popover; + + /* Override button size when there is no icon or label. */ + if (layout->root()->type == blender::ui::LayoutType::VerticalBar && !icon && name.is_empty()) { + but->rect.xmax = but->rect.xmin + UI_UNIT_X; + } + if (!ok) { but->flag |= UI_BUT_DISABLED; } diff --git a/source/blender/editors/interface/interface_widgets.cc b/source/blender/editors/interface/interface_widgets.cc index 5ffd245fae6..2282ab9bc4b 100644 --- a/source/blender/editors/interface/interface_widgets.cc +++ b/source/blender/editors/interface/interface_widgets.cc @@ -124,6 +124,8 @@ struct uiWidgetStateInfo { int but_flag; /** Copy of #uiBut.drawflag (possibly with overrides for drawing). */ int but_drawflag; + /** Copy of #uiBut.emboss. */ + blender::ui::EmbossType emboss; /** Show that holding the button opens a menu. */ bool has_hold_action : 1; @@ -3709,7 +3711,7 @@ static void widget_numbut(uiBut *but, static void widget_menubut(uiWidgetColors *wcol, rcti *rect, - const uiWidgetStateInfo * /*state*/, + const uiWidgetStateInfo *state, int roundboxalign, const float zoom) { @@ -3724,6 +3726,12 @@ static void widget_menubut(uiWidgetColors *wcol, /* copy size and center to 2nd tria */ wtb.tria2 = wtb.tria1; + if (ELEM(state->emboss, blender::ui::EmbossType::NoneOrStatus, blender::ui::EmbossType::None)) { + wtb.draw_inner = false; + wtb.draw_outline = false; + wtb.draw_emboss = false; + } + widgetbase_draw(&wtb, wcol); /* text space, arrows are about 0.6 height of button */ @@ -5019,6 +5027,22 @@ static int widget_roundbox_set(uiBut *but, rcti *rect) return roundbox; } +static uiWidgetType *popover_widget_type(uiBut *but, rcti *rect) +{ + /* We could use a flag for this, but for now just check size, + * add up/down arrows if there is room. */ + if ((but->str.empty() && but->icon && (BLI_rcti_size_x(rect) < BLI_rcti_size_y(rect) + 2)) || + /* disable for brushes also */ + (but->flag & UI_BUT_ICON_PREVIEW)) + { + /* No arrows. */ + return widget_type(UI_WTYPE_MENU_ICON_RADIO); + } + + /* With menu arrows. */ + return widget_type(UI_WTYPE_MENU_RADIO); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -5068,6 +5092,9 @@ void ui_draw_but(const bContext *C, ARegion *region, uiStyle *style, uiBut *but, case ButType::PreviewTile: wt = widget_type(UI_WTYPE_PREVIEW_TILE); break; + case ButType::Popover: + wt = popover_widget_type(but, rect); + break; case ButType::NodeSocket: wt = widget_type(UI_WTYPE_NODESOCKET); break; @@ -5184,21 +5211,8 @@ void ui_draw_but(const bContext *C, ARegion *region, uiStyle *style, uiBut *but, wt = widget_type(UI_WTYPE_MENU_NODE_LINK); } else { - /* with menu arrows */ - - /* We could use a flag for this, but for now just check size, - * add up/down arrows if there is room. */ - if ((but->str.empty() && but->icon && - (BLI_rcti_size_x(rect) < BLI_rcti_size_y(rect) + 2)) || - /* disable for brushes also */ - (but->flag & UI_BUT_ICON_PREVIEW)) - { - /* no arrows */ - wt = widget_type(UI_WTYPE_MENU_ICON_RADIO); - } - else { - wt = widget_type(UI_WTYPE_MENU_RADIO); - } + /* Popover button. */ + wt = popover_widget_type(but, rect); } break; @@ -5322,6 +5336,7 @@ void ui_draw_but(const bContext *C, ARegion *region, uiStyle *style, uiBut *but, uiWidgetStateInfo state = {0}; state.but_flag = but->flag; state.but_drawflag = but->drawflag; + state.emboss = but->emboss; /* Override selected flag for drawing. */ if (but->flag & UI_SELECT_DRAW) {