diff options
-rw-r--r-- | release/scripts/startup/bl_ui/space_properties.py | 6 | ||||
-rw-r--r-- | source/blender/editors/include/ED_screen.h | 5 | ||||
-rw-r--r-- | source/blender/editors/include/UI_interface.h | 1 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_panel.c | 8 | ||||
-rw-r--r-- | source/blender/editors/screen/area.c | 268 | ||||
-rw-r--r-- | source/blender/editors/space_buttons/space_buttons.c | 196 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_space_types.h | 7 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_space.c | 10 |
8 files changed, 409 insertions, 92 deletions
diff --git a/release/scripts/startup/bl_ui/space_properties.py b/release/scripts/startup/bl_ui/space_properties.py index 891f73434e0..07298082f4c 100644 --- a/release/scripts/startup/bl_ui/space_properties.py +++ b/release/scripts/startup/bl_ui/space_properties.py @@ -51,7 +51,11 @@ class PROPERTIES_PT_navigation_bar(Panel): layout.scale_x = 1.4 layout.scale_y = 1.4 - layout.prop_tabs_enum(view, "context", icon_only=True) + if view.search_filter: + layout.prop_tabs_enum(view, "context", data_highlight=view, + property_highlight="context_search_filter_active", icon_only=True) + else: + layout.prop_tabs_enum(view, "context", icon_only=True) classes = ( diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index 1e6f3ed3af1..dfdce21614c 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -91,6 +91,11 @@ void ED_region_panels_layout_ex(const struct bContext *C, struct ListBase *paneltypes, const char *contexts[], const char *category_override); +bool ED_region_property_search(const struct bContext *C, + struct ARegion *region, + struct ListBase *paneltypes, + const char *contexts[], + const char *category_override); void ED_region_panels_layout(const struct bContext *C, struct ARegion *region); void ED_region_panels_draw(const struct bContext *C, struct ARegion *region); diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index f02c34c4085..48112e70f7c 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -1691,6 +1691,7 @@ void UI_panel_label_offset(struct uiBlock *block, int *r_x, int *r_y); int UI_panel_size_y(const struct Panel *panel); bool UI_panel_is_dragging(const struct Panel *panel); bool UI_panel_matches_search_filter(const struct Panel *panel); +bool UI_panel_is_active(const struct Panel *panel); void UI_panels_set_expansion_from_seach_filter(const struct bContext *C, struct ARegion *region); bool UI_panel_category_is_visible(const struct ARegion *region); diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index 8d63bc40ea1..fdfb8a6b177 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -831,6 +831,14 @@ bool UI_panel_matches_search_filter(const Panel *panel) return search_filter_matches; } +/** + * Returns whether a panel is currently active (displayed). + */ +bool UI_panel_is_active(const Panel *panel) +{ + return panel->runtime_flag & PNL_ACTIVE; +} + static void panel_set_expansion_from_seach_filter_recursive(const bContext *C, Panel *panel) { short start_flag = panel->flag; diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index bc60bf32635..229cd80143c 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -2716,7 +2716,6 @@ static void ed_panel_draw(const bContext *C, LISTBASE_FOREACH (LinkData *, link, &pt->children) { PanelType *child_pt = link->data; Panel *child_panel = UI_panel_find_by_type(&panel->children, child_pt); - if (child_pt->draw && (!child_pt->poll || child_pt->poll(C, child_pt))) { ed_panel_draw(C, region, @@ -2736,6 +2735,81 @@ static void ed_panel_draw(const bContext *C, } /** + * Check whether a panel should be added to the region's panel layout. + */ +static bool panel_add_check(const bContext *C, + const WorkSpace *workspace, + const char *contexts[], + const char *category_override, + PanelType *panel_type) +{ + /* Only add top level panels. */ + if (panel_type->parent) { + return false; + } + /* Check the category override first. */ + if (category_override) { + if (!STREQ(panel_type->category, category_override)) { + return false; + } + } + + /* Verify context. */ + if (contexts != NULL && panel_type->context[0]) { + if (!streq_array_any(panel_type->context, contexts)) { + return false; + } + } + + /* If we're tagged, only use compatible. */ + if (panel_type->owner_id[0]) { + if (!BKE_workspace_owner_id_check(workspace, panel_type->owner_id)) { + return false; + } + } + + if (LIKELY(panel_type->draw)) { + if (panel_type->poll && !panel_type->poll(C, panel_type)) { + return false; + } + } + + return true; +} + +static bool region_uses_category_tabs(const ScrArea *area, const ARegion *region) +{ + /* XXX, should use some better check? */ + /* For now also has hardcoded check for clip editor until it supports actual toolbar. */ + return ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) || + (region->regiontype == RGN_TYPE_TOOLS && area->spacetype == SPACE_CLIP); +} + +static const char *region_panels_collect_categories(ARegion *region, + LinkNode *panel_types_stack, + bool *use_category_tabs) +{ + UI_panel_category_clear_all(region); + + /* gather unique categories */ + for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) { + PanelType *pt = pt_link->link; + if (pt->category[0]) { + if (!UI_panel_category_find(region, pt->category)) { + UI_panel_category_add(region, pt->category); + } + } + } + + if (UI_panel_category_is_visible(region)) { + return UI_panel_category_active_get(region, true); + } + + *use_category_tabs = false; + return NULL; +} + +/** * \param contexts: A NULL terminated array of context strings to match against. * Matching against any of these strings will draw the panel. * Can be NULL to skip context checks. @@ -2750,29 +2824,7 @@ void ED_region_panels_layout_ex(const bContext *C, WorkSpace *workspace = CTX_wm_workspace(C); LinkNode *panel_types_stack = NULL; LISTBASE_FOREACH_BACKWARD (PanelType *, pt, paneltypes) { - /* Only draw top level panels. */ - if (pt->parent) { - continue; - } - - if (category_override) { - if (!STREQ(pt->category, category_override)) { - continue; - } - } - - /* verify context */ - if (contexts && pt->context[0] && !streq_array_any(pt->context, contexts)) { - continue; - } - - /* If we're tagged, only use compatible. */ - if (pt->owner_id[0] && BKE_workspace_owner_id_check(workspace, pt->owner_id) == false) { - continue; - } - - /* draw panel */ - if (pt->draw && (!pt->poll || pt->poll(C, pt))) { + if (panel_add_check(C, workspace, contexts, category_override, pt)) { BLI_linklist_prepend_alloca(&panel_types_stack, pt); } } @@ -2783,12 +2835,7 @@ void ED_region_panels_layout_ex(const bContext *C, View2D *v2d = ®ion->v2d; int x, y, w, em; - /* XXX, should use some better check? */ - /* For now also has hardcoded check for clip editor until it supports actual toolbar. */ - bool use_category_tabs = (category_override == NULL) && - ((((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) || - (region->regiontype == RGN_TYPE_TOOLS && - area->spacetype == SPACE_CLIP))); + bool use_category_tabs = (category_override == NULL) && region_uses_category_tabs(area, region); /* offset panels for small vertical tab area */ const char *category = NULL; const int category_tabs_width = UI_PANEL_CATEGORY_MARGIN_WIDTH; @@ -2804,25 +2851,10 @@ void ED_region_panels_layout_ex(const bContext *C, /* collect categories */ if (use_category_tabs) { - UI_panel_category_clear_all(region); - - /* gather unique categories */ - for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) { - PanelType *pt = pt_link->link; - if (pt->category[0]) { - if (!UI_panel_category_find(region, pt->category)) { - UI_panel_category_add(region, pt->category); - } - } - } - - if (!UI_panel_category_is_visible(region)) { - use_category_tabs = false; - } - else { - category = UI_panel_category_active_get(region, true); - margin_x = category_tabs_width; - } + category = region_panels_collect_categories(region, panel_types_stack, &use_category_tabs); + } + if (use_category_tabs) { + margin_x = category_tabs_width; } w = BLI_rctf_size_x(&v2d->cur); @@ -3036,6 +3068,144 @@ void ED_region_panels_init(wmWindowManager *wm, ARegion *region) WM_event_add_keymap_handler(®ion->handlers, keymap); } +static bool panel_property_search(const bContext *C, + ARegion *region, + const uiStyle *style, + Panel *panel, + PanelType *panel_type, + const char *search_filter) +{ + uiBlock *block = UI_block_begin(C, region, panel_type->idname, UI_EMBOSS); + UI_block_set_search_only(block, true); + UI_block_set_search_filter(block, search_filter); + + if (panel == NULL) { + bool open; + panel = UI_panel_begin(region, ®ion->panels, block, panel_type, panel, &open); + } + + /* Build the layouts. Because they are only used for search, + * they don't need any of the proper style or layout information. */ + if (panel->type->draw_header_preset != NULL) { + panel->layout = UI_block_layout( + block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, 0, 0, 0, 0, 0, style); + uiLayoutRootSetSearchOnly(panel->layout, true); + panel_type->draw_header_preset(C, panel); + } + if (panel->type->draw_header != NULL) { + panel->layout = UI_block_layout( + block, UI_LAYOUT_HORIZONTAL, UI_LAYOUT_HEADER, 0, 0, 0, 0, 0, style); + uiLayoutRootSetSearchOnly(panel->layout, true); + panel_type->draw_header(C, panel); + } + if (LIKELY(panel->type->draw != NULL)) { + panel->layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, 0, 0, 0, style); + uiLayoutRootSetSearchOnly(panel->layout, true); + panel_type->draw(C, panel); + } + + /* We could check after each layout to increase the likelyhood of returning early, + * but that probably wouldn't make much of a difference anyway. */ + if (UI_block_apply_search_filter(block)) { + return true; + } + + LISTBASE_FOREACH (LinkData *, link, &panel_type->children) { + PanelType *panel_type_child = link->data; + if (LIKELY(panel->type->draw != NULL)) { + if (!panel_type_child->poll || panel_type_child->poll(C, panel_type_child)) { + /* Search for the existing child panel here because it might be an instanced + * child panel with a custom data field that will be needed to build the layout. */ + Panel *child_panel = UI_panel_find_by_type(&panel->children, panel_type_child); + if (panel_property_search( + C, region, style, child_panel, panel_type_child, search_filter)) { + return true; + } + } + } + } + + return false; +} + +bool ED_region_property_search(const bContext *C, + ARegion *region, + ListBase *paneltypes, + const char *contexts[], + const char *category_override) +{ + ScrArea *area = CTX_wm_area(C); + WorkSpace *workspace = CTX_wm_workspace(C); + const uiStyle *style = UI_style_get_dpi(); + const char *search_filter = ED_area_search_filter_get(area, region); + + LinkNode *panel_types_stack = NULL; + LISTBASE_FOREACH_BACKWARD (PanelType *, pt, paneltypes) { + if (panel_add_check(C, workspace, contexts, category_override, pt)) { + BLI_linklist_prepend_alloca(&panel_types_stack, pt); + } + } + + const char *category = NULL; + bool use_category_tabs = (category_override == NULL) && region_uses_category_tabs(area, region); + if (use_category_tabs) { + category = region_panels_collect_categories(region, panel_types_stack, &use_category_tabs); + } + + /* Run property search for each panel, stopping if a result is found. */ + bool has_result = true; + bool has_instanced_panel = false; + for (LinkNode *pt_link = panel_types_stack; pt_link; pt_link = pt_link->next) { + PanelType *panel_type = pt_link->link; + /* Note that these checks are duplicated from #ED_region_panels_layout_ex. */ + if (panel_type->flag & PNL_INSTANCED) { + has_instanced_panel = true; + continue; + } + + if (use_category_tabs) { + if (panel_type->category[0] && !STREQ(category, panel_type->category)) { + continue; + } + } + + /* We start property search with an empty panel list, so there's + * no point in trying to find an existing panel with this type. */ + has_result = panel_property_search(C, region, style, NULL, panel_type, search_filter); + if (has_result) { + break; + } + } + + /* Run property search for instanced panels (created in the layout calls of previous panels). */ + if (!has_result && has_instanced_panel) { + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + /* Note that these checks are duplicated from #ED_region_panels_layout_ex. */ + if (panel->type == NULL || !(panel->type->flag & PNL_INSTANCED)) { + continue; + } + if (use_category_tabs) { + if (panel->type->category[0] && !STREQ(category, panel->type->category)) { + continue; + } + } + + has_result = panel_property_search(C, region, style, panel, panel->type, search_filter); + if (has_result) { + break; + } + } + } + + /* Free the panels and blocks, as they are only used for search. */ + UI_blocklist_free(C, ®ion->uiblocks); + UI_panels_free_instanced(C, region); + BKE_area_region_panels_free(®ion->panels); + + return has_result; +} + void ED_region_header_layout(const bContext *C, ARegion *region) { const uiStyle *style = UI_style_get_dpi(); diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c index 039439583aa..dd2f1545e47 100644 --- a/source/blender/editors/space_buttons/space_buttons.c +++ b/source/blender/editors/space_buttons/space_buttons.c @@ -48,6 +48,7 @@ #include "RNA_define.h" #include "RNA_enum_types.h" +#include "UI_interface.h" #include "UI_resources.h" #include "buttons_intern.h" /* own include */ @@ -239,71 +240,146 @@ int ED_buttons_tabs_list(SpaceProperties *sbuts, int *context_tabs_array) return length; } -static void buttons_main_region_layout_properties(const bContext *C, - SpaceProperties *sbuts, - ARegion *region) +static const char *buttons_main_region_context_string(const short mainb) { - buttons_context_compute(C, sbuts); - - const char *contexts[2] = {NULL, NULL}; - - switch (sbuts->mainb) { + switch (mainb) { case BCONTEXT_SCENE: - contexts[0] = "scene"; - break; + return "scene"; case BCONTEXT_RENDER: - contexts[0] = "render"; - break; + return "render"; case BCONTEXT_OUTPUT: - contexts[0] = "output"; - break; + return "output"; case BCONTEXT_VIEW_LAYER: - contexts[0] = "view_layer"; - break; + return "view_layer"; case BCONTEXT_WORLD: - contexts[0] = "world"; - break; + return "world"; case BCONTEXT_OBJECT: - contexts[0] = "object"; - break; + return "object"; case BCONTEXT_DATA: - contexts[0] = "data"; - break; + return "data"; case BCONTEXT_MATERIAL: - contexts[0] = "material"; - break; + return "material"; case BCONTEXT_TEXTURE: - contexts[0] = "texture"; - break; + return "texture"; case BCONTEXT_PARTICLE: - contexts[0] = "particle"; - break; + return "particle"; case BCONTEXT_PHYSICS: - contexts[0] = "physics"; - break; + return "physics"; case BCONTEXT_BONE: - contexts[0] = "bone"; - break; + return "bone"; case BCONTEXT_MODIFIER: - contexts[0] = "modifier"; - break; + return "modifier"; case BCONTEXT_SHADERFX: - contexts[0] = "shaderfx"; - break; + return "shaderfx"; case BCONTEXT_CONSTRAINT: - contexts[0] = "constraint"; - break; + return "constraint"; case BCONTEXT_BONE_CONSTRAINT: - contexts[0] = "bone_constraint"; - break; + return "bone_constraint"; case BCONTEXT_TOOL: - contexts[0] = "tool"; - break; + return "tool"; } + /* All the cases should be handled. */ + BLI_assert(false); + return ""; +} + +static void buttons_main_region_layout_properties(const bContext *C, + SpaceProperties *sbuts, + ARegion *region) +{ + buttons_context_compute(C, sbuts); + + const char *contexts[2] = {buttons_main_region_context_string(sbuts->mainb), NULL}; + ED_region_panels_layout_ex(C, region, ®ion->type->paneltypes, contexts, NULL); } +static bool property_search_for_context(const bContext *C, ARegion *region, SpaceProperties *sbuts) +{ + const char *contexts[2] = {buttons_main_region_context_string(sbuts->mainb), NULL}; + + if (sbuts->mainb == BCONTEXT_TOOL) { + return false; + } + else { + buttons_context_compute(C, sbuts); + return ED_region_property_search(C, region, ®ion->type->paneltypes, contexts, NULL); + } +} + +static void property_search_move_to_next_tab_with_results(SpaceProperties *sbuts, + const int *context_tabs_array, + const int tabs_len) +{ + int current_tab_index = 0; + for (int i = 0; i < tabs_len; i++) { + if (sbuts->mainb == context_tabs_array[i]) { + current_tab_index = i; + break; + } + } + + /* Try the tabs after the current tab. */ + for (int i = current_tab_index; i < tabs_len; i++) { + if (sbuts->runtime->context_search_filter_active & (1 << i)) { + sbuts->mainbuser = context_tabs_array[i]; + return; + } + } + + /* Try the tabs before the current tab. */ + for (int i = 0; i < current_tab_index; i++) { + if (sbuts->runtime->context_search_filter_active & (1 << i)) { + sbuts->mainbuser = context_tabs_array[i]; + return; + } + } +} + +static void property_search_all_tabs(const bContext *C, + SpaceProperties *sbuts, + ARegion *main_region, + const int *context_tabs_array, + const int tabs_len) +{ + sbuts->runtime->context_search_filter_active = 0; + + /* Duplicate space and region so we don't change any data for this space. */ + ScrArea *area_copy = MEM_dupallocN(CTX_wm_area(C)); + ARegion *region_copy = BKE_area_region_copy(area_copy->type, main_region); + BKE_area_region_panels_free(®ion_copy->panels); + bContext *C_copy = CTX_copy(C); + CTX_wm_area_set(C_copy, area_copy); + CTX_wm_region_set(C_copy, region_copy); + SpaceProperties *sbuts_copy = MEM_dupallocN(sbuts); + + /* Loop through the tabs added to the properties editor. */ + for (int i = 0; i < tabs_len; i++) { + /* -1 corresponds to a spacer. */ + if (context_tabs_array[i] == -1) { + continue; + } + + /* Handle search for the current tab later in the normal layout pass. */ + if (context_tabs_array[i] == sbuts->mainb) { + continue; + } + + sbuts_copy->mainb = sbuts_copy->mainbo = sbuts_copy->mainbuser = context_tabs_array[i]; + + SET_FLAG_FROM_TEST(sbuts->runtime->context_search_filter_active, + property_search_for_context(C_copy, region_copy, sbuts_copy), + (1 << i)); + } + + BKE_area_region_free(area_copy->type, region_copy); + MEM_freeN(region_copy); + MEM_freeN(sbuts_copy); + MEM_freeN(area_copy); + MEM_freeN(C_copy); +} + static void buttons_main_region_layout(const bContext *C, ARegion *region) { /* draw entirely, view changes should be handled here */ @@ -316,6 +392,42 @@ static void buttons_main_region_layout(const bContext *C, ARegion *region) buttons_main_region_layout_properties(C, sbuts, region); } + if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { + int context_tabs_array[32]; + int tabs_len = ED_buttons_tabs_list(sbuts, context_tabs_array); + + property_search_all_tabs(C, sbuts, region, context_tabs_array, tabs_len); + + /* Check whether the current tab has a search match. */ + bool current_tab_has_search_match = false; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (UI_panel_is_active(panel) && UI_panel_matches_search_filter(panel)) { + current_tab_has_search_match = true; + } + } + + /* Find which index in the list the current tab corresponds to. */ + int current_tab_index = -1; + for (int i = 0; i < tabs_len; i++) { + if (context_tabs_array[i] == sbuts->mainb) { + current_tab_index = i; + } + } + BLI_assert(current_tab_index != -1); + + /* Update the tab search match flag for the current tab. */ + SET_FLAG_FROM_TEST(sbuts->runtime->context_search_filter_active, + current_tab_has_search_match, + (1 << current_tab_index)); + + /* Move to the next tab with a result */ + if (!current_tab_has_search_match) { + if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { + property_search_move_to_next_tab_with_results(sbuts, context_tabs_array, tabs_len); + } + } + } + sbuts->mainbo = sbuts->mainb; } diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index 24d28bf45de..86218eaaf03 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -132,6 +132,13 @@ typedef enum eSpaceInfo_RptMask { typedef struct SpaceProperties_Runtime { /** For filtering properties displayed in the space. Length defined as UI_MAX_NAME_STR. */ char search_string[128]; + /** + * Bitfield flag (in the same order as the tabs) for whether each tab has properties + * that match the search filter. Only valid when #search_string is set. + */ + int context_search_filter_active; + + char _pad[4]; } SpaceProperties_Runtime; /* Properties Editor */ diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index a85593cbd90..49538c4e2b9 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -4495,6 +4495,16 @@ static void rna_def_space_properties(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Pin ID", "Use the pinned context"); /* Property search. */ + prop = RNA_def_property(srna, "context_search_filter_active", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "runtime->context_search_filter_active"); + RNA_def_property_enum_items(prop, buttons_context_items); + RNA_def_property_flag(prop, PROP_ENUM_FLAG); + RNA_def_property_enum_funcs( + prop, NULL, "rna_SpaceProperties_context_set", "rna_SpaceProperties_context_itemf"); + RNA_def_property_ui_text(prop, "", ""); + RNA_def_property_update( + prop, NC_SPACE | ND_SPACE_PROPERTIES, "rna_SpaceProperties_context_update"); + prop = RNA_def_property(srna, "search_filter", PROP_STRING, PROP_NONE); RNA_def_property_string_sdna(prop, NULL, "runtime->search_string"); RNA_def_property_ui_text(prop, "Display Filter", "Live search filtering string"); |