diff options
Diffstat (limited to 'source/blender/editors/interface')
-rw-r--r-- | source/blender/editors/interface/interface.c | 27 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 15 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_layout.c | 215 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_panel.c | 121 |
4 files changed, 355 insertions, 23 deletions
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index b9a001a9571..5d20846c528 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -1995,8 +1995,12 @@ void UI_block_draw(const bContext *C, uiBlock *block) } } } - ui_draw_aligned_panel( - &style, block, &rect, UI_panel_category_is_visible(region), show_background); + ui_draw_aligned_panel(&style, + block, + &rect, + UI_panel_category_is_visible(region), + show_background, + region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE); } BLF_batch_draw_begin(); @@ -3541,6 +3545,25 @@ void UI_block_theme_style_set(uiBlock *block, char theme_style) block->theme_style = theme_style; } +bool UI_block_is_search_only(const uiBlock *block) +{ + return block->flag & UI_BLOCK_SEARCH_ONLY; +} + +/** + * Use when a block must be searched to give accurate results + * for the whole region but shouldn't be displayed. + */ +void UI_block_set_search_only(uiBlock *block, bool search_only) +{ + SET_FLAG_FROM_TEST(block->flag, search_only, UI_BLOCK_SEARCH_ONLY); +} + +void UI_block_set_search_filter(uiBlock *block, const char *search_filter) +{ + block->search_filter = search_filter; +} + static void ui_but_build_drawstr_float(uiBut *but, double value) { size_t slen = 0; diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index edaadd880d7..d62b8a0258a 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -81,6 +81,11 @@ enum { UI_HAS_ICON = (1 << 3), UI_HIDDEN = (1 << 4), UI_SELECT_DRAW = (1 << 5), /* Display selected, doesn't impact interaction. */ + /** + * The button matches the search filter. When property search is active, this + * is used to determine which items to keep enabled and which to disable. + */ + UI_SEARCH_FILTER_MATCHES = (1 << 12), /* warn: rest of uiBut->flag in UI_interface.h */ }; @@ -517,6 +522,12 @@ struct uiBlock { */ char display_device[64]; + /** + * Pointer to the space's property search string. + * The block doesn't allocate this or change it. + */ + const char *search_filter; + struct PieMenuData pie_data; }; @@ -805,7 +816,9 @@ extern void ui_draw_aligned_panel(const struct uiStyle *style, const uiBlock *block, const rcti *rect, const bool show_pin, - const bool show_background); + const bool show_background, + const bool region_search_filter_active); +void ui_panel_set_search_filter_match(struct Panel *panel, const bool value); /* interface_draw.c */ extern void ui_draw_dropshadow( diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index c3bcaff0d0c..38f2a2fb3c0 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -97,6 +97,13 @@ typedef struct uiLayoutRoot { int type; int opcontext; + /** + * If true, the root will be removed as part of the property search process. + * Necessary for cases like searching the contents of closed panels, where the + * block-level tag isn't enough, as there might be visible buttons in the header. + */ + bool search_only; + ListBase button_groups; /* #uiButtonGroup. */ int emw, emh; @@ -5132,6 +5139,208 @@ int uiLayoutGetEmboss(uiLayout *layout) return layout->emboss; } +/** + * Tags the layout root as search only, meaning the search process will run, but not the rest of + * the layout process. Use in situations where part of the block's contents normally wouldn't be + * drawn, but must be searched anyway, like the contents of closed panels with headers. + */ +void uiLayoutRootSetSearchOnly(uiLayout *layout, bool search_only) +{ + layout->root->search_only = search_only; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Block Layout Search Filtering + * \{ */ + +/* Disabled for performance reasons, but this could be turned on in the future. */ +// #define PROPERTY_SEARCH_USE_TOOLTIPS + +static bool block_search_panel_label_matches(const uiBlock *block) +{ + if ((block->panel != NULL) && (block->panel->type != NULL)) { + if (BLI_strcasestr(block->panel->type->label, block->search_filter)) { + return true; + } + } + return false; +} + +/** + * Buttons for search only layouts (closed panel subpanels) have still been added from the + * layout functions, but they need to be hidden. Theoretically they could be removed too. + */ +static void layout_free_and_hide_buttons(uiLayout *layout) +{ + LISTBASE_FOREACH_MUTABLE (uiItem *, item, &layout->items) { + if (item->type == ITEM_BUTTON) { + uiButtonItem *button_item = (uiButtonItem *)item; + BLI_assert(button_item->but != NULL); + button_item->but->flag |= UI_HIDDEN; + MEM_freeN(item); + } + else { + layout_free_and_hide_buttons((uiLayout *)item); + } + } + + MEM_freeN(layout); +} + +/* Prototype of function below. */ +static void layout_root_free(uiLayoutRoot *root); + +/** + * Remove layouts used only for search and hide their buttons. + * (See comment for #uiLayoutRootSetSearchOnly and in #uiLayoutRoot). + */ +static void block_search_remove_search_only_roots(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) { + if (root->search_only) { + layout_free_and_hide_buttons(root->layout); + BLI_remlink(&block->layouts, root); + layout_root_free(root); + } + } +} + +/** + * Returns true if a button or the data / operator it represents matches the search filter. + */ +static bool button_matches_search_filter(uiBut *but, const char *search_filter) +{ + /* Do the shorter checks first for better performance in case there is a match. */ + if (BLI_strcasestr(but->str, search_filter)) { + return true; + } + + if (but->optype != NULL) { + if (BLI_strcasestr(but->optype->name, search_filter)) { + return true; + } + } + + if (but->rnaprop != NULL) { + if (BLI_strcasestr(RNA_property_ui_name(but->rnaprop), search_filter)) { + return true; + } +#ifdef PROPERTY_SEARCH_USE_TOOLTIPS + if (BLI_strcasestr(RNA_property_description(but->rnaprop), search_filter)) { + return true; + } +#endif + + /* Search through labels of enum property items if they are in a dropdown menu. + * Unfortunately we have no #bContext here so we cannot search through RNA enums + * with dynamic entries (or "itemf" functions) which require context. */ + if (but->type == UI_BTYPE_MENU) { + PointerRNA *ptr = &but->rnapoin; + PropertyRNA *enum_prop = but->rnaprop; + + int items_len; + const EnumPropertyItem *items_array = NULL; + bool free; + RNA_property_enum_items_gettexted(NULL, ptr, enum_prop, &items_array, &items_len, &free); + + if (items_array == NULL) { + return false; + } + + for (int i = 0; i < items_len; i++) { + if (BLI_strcasestr(items_array[i].name, search_filter)) { + return true; + } + } + if (free) { + MEM_freeN((EnumPropertyItem *)items_array); + } + } + } + + return false; +} + +/** + * Test for a search result within a specific button group. + */ +static bool button_group_has_search_match(uiButtonGroup *button_group, const char *search_filter) +{ + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = link->data; + if (button_matches_search_filter(but, search_filter)) { + return true; + } + } + + return false; +} + +/** + * Apply the search filter, tagging all buttons with whether they match or not. + * Tag every button in the group as a result if any button in the group matches. + * + * \note It would be great to return early here if we found a match, but because + * the results may be visible we have to continue searching the entire block. + * + * \return True if the block has any search results. + */ +static bool block_search_filter_tag_buttons(uiBlock *block) +{ + bool has_result = false; + LISTBASE_FOREACH (uiLayoutRoot *, root, &block->layouts) { + LISTBASE_FOREACH (uiButtonGroup *, button_group, &root->button_groups) { + if (button_group_has_search_match(button_group, block->search_filter)) { + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = link->data; + but->flag |= UI_SEARCH_FILTER_MATCHES; + } + has_result = true; + } + } + } + return has_result; +} + +static void block_search_deactivate_buttons(uiBlock *block) +{ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (!(but->flag & UI_SEARCH_FILTER_MATCHES)) { + but->flag |= UI_BUT_INACTIVE; + } + } +} + +/** + * Apply property search behavior, setting panel flags and deactivating buttons that don't match. + * + * \note Must not be run after #UI_block_layout_resolve. + */ +bool UI_block_apply_search_filter(uiBlock *block) +{ + if (!(block->search_filter && block->search_filter[0])) { + return false; + } + + const bool panel_label_matches = block_search_panel_label_matches(block); + + const bool has_result = panel_label_matches ? true : block_search_filter_tag_buttons(block); + + block_search_remove_search_only_roots(block); + + if (block->panel != NULL) { + ui_panel_set_search_filter_match(block->panel, has_result); + } + + if (!panel_label_matches) { + block_search_deactivate_buttons(block); + } + + return has_result; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -5378,8 +5587,6 @@ static void ui_layout_free(uiLayout *layout) static void layout_root_free(uiLayoutRoot *root) { - ui_layout_free(root->layout); - LISTBASE_FOREACH_MUTABLE (uiButtonGroup *, button_group, &root->button_groups) { button_group_free(button_group); } @@ -5575,10 +5782,14 @@ void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y) block->curlayout = NULL; LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) { + /* Seach only roots should be removed by #UI_block_apply_search_filter. */ + BLI_assert(!root->search_only); + ui_layout_add_padding_button(root); /* NULL in advance so we don't interfere when adding button */ ui_layout_end(block, root->layout, r_x, r_y); + ui_layout_free(root->layout); layout_root_free(root); } diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index 6e2229ce3e3..c11e4397b4c 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -77,6 +77,7 @@ #define PNL_ANIM_ALIGN 8 #define PNL_NEW_ADDED 16 #define PNL_FIRST 32 +#define PNL_SEARCH_FILTER_MATCHES 64 /* the state of the mouse position relative to the panel */ typedef enum uiPanelMouseState { @@ -125,16 +126,32 @@ static bool panel_type_context_poll(ARegion *region, /** \name Local Functions * \{ */ -static void panel_title_color_get(bool show_background, uchar color[4]) +static void panel_title_color_get(const Panel *panel, + const bool show_background, + const bool use_search_color, + const bool region_search_filter_active, + uchar r_color[4]) { - if (show_background) { - UI_GetThemeColor4ubv(TH_TITLE, color); - } - else { + if (!show_background) { /* Use menu colors for floating panels. */ bTheme *btheme = UI_GetTheme(); const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; - copy_v4_v4_uchar(color, (const uchar *)wcol->text); + copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); + return; + } + + const bool search_match = UI_panel_matches_search_filter(panel); + + if (region_search_filter_active && use_search_color && search_match) { + UI_GetThemeColor4ubv(TH_MATCH, r_color); + } + else { + UI_GetThemeColor4ubv(TH_TITLE, r_color); + if (region_search_filter_active && !search_match) { + r_color[0] *= 0.5; + r_color[1] *= 0.5; + r_color[2] *= 0.5; + } } } @@ -230,6 +247,7 @@ static Panel *UI_panel_add_instanced_ex(ARegion *region, BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); panel->runtime.custom_data_ptr = custom_data; + panel->runtime_flag |= PNL_NEW_ADDED; /* Add the panel's children too. Although they aren't instanced panels, we can still use this * function to create them, as UI_panel_begin does other things we don't need to do. */ @@ -786,6 +804,65 @@ static void ui_offset_panel_block(uiBlock *block) block->rect.xmin = block->rect.ymin = 0.0; } +void ui_panel_set_search_filter_match(struct Panel *panel, const bool value) +{ + SET_FLAG_FROM_TEST(panel->runtime_flag, value, PNL_SEARCH_FILTER_MATCHES); +} + +static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) +{ + *filter_matches |= panel->runtime_flag & PNL_SEARCH_FILTER_MATCHES; + + /* If the panel has no match we need to make sure that its children are too. */ + if (!*filter_matches) { + LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { + panel_matches_search_filter_recursive(child_panel, filter_matches); + } + } +} + +/** + * Find whether a panel or any of its subpanels contain a property that matches the search filter, + * depending on the search process running in #UI_block_apply_search_filter earlier. + */ +bool UI_panel_matches_search_filter(const Panel *panel) +{ + bool search_filter_matches = false; + panel_matches_search_filter_recursive(panel, &search_filter_matches); + return search_filter_matches; +} + +static void panel_set_expansion_from_seach_filter_recursive(const bContext *C, Panel *panel) +{ + short start_flag = panel->flag; + SET_FLAG_FROM_TEST(panel->flag, !UI_panel_matches_search_filter(panel), PNL_CLOSED); + if (start_flag != panel->flag) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + + /* If the panel is filtered (removed) we need to check that its children are too. */ + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (panel->type == NULL || (panel->type->flag & PNL_NO_HEADER)) { + continue; + } + panel_set_expansion_from_seach_filter_recursive(C, child_panel); + } +} + +/** + * Uses the panel's search filter flag to set its expansion, activating animation if it was closed + * or opened. Note that this can't be set too often, or manual interaction becomes impossible. + */ +void UI_panels_set_expansion_from_seach_filter(const bContext *C, ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type == NULL || (panel->type->flag & PNL_NO_HEADER)) { + continue; + } + panel_set_expansion_from_seach_filter_recursive(C, panel); + } +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -829,7 +906,8 @@ void UI_panel_label_offset(uiBlock *block, int *r_x, int *r_y) static void ui_draw_aligned_panel_header(const uiStyle *style, const uiBlock *block, const rcti *rect, - const bool show_background) + const bool show_background, + const bool region_search_filter_active) { const Panel *panel = block->panel; const bool is_subpanel = (panel->type && panel->type->parent); @@ -840,7 +918,8 @@ static void ui_draw_aligned_panel_header(const uiStyle *style, /* draw text label */ uchar col_title[4]; - panel_title_color_get(show_background, col_title); + panel_title_color_get( + panel, show_background, is_subpanel, region_search_filter_active, col_title); col_title[3] = 255; rcti hrect = *rect; @@ -862,7 +941,8 @@ void ui_draw_aligned_panel(const uiStyle *style, const uiBlock *block, const rcti *rect, const bool show_pin, - const bool show_background) + const bool show_background, + const bool region_search_filter_active) { const Panel *panel = block->panel; float color[4]; @@ -937,7 +1017,7 @@ void ui_draw_aligned_panel(const uiStyle *style, GPU_blend(GPU_BLEND_ALPHA); /* draw with background color */ - immUniformThemeColor(TH_PANEL_HEADER); + immUniformThemeColor(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER); immRectf(pos, minx, headrect.ymin, rect->xmax, y); immBegin(GPU_PRIM_LINES, 4); @@ -957,7 +1037,7 @@ void ui_draw_aligned_panel(const uiStyle *style, /* draw optional pin icon */ if (show_pin && (block->panel->flag & PNL_PIN)) { uchar col_title[4]; - panel_title_color_get(show_background, col_title); + panel_title_color_get(panel, show_background, false, region_search_filter_active, col_title); GPU_blend(GPU_BLEND_ALPHA); UI_icon_draw_ex(headrect.xmax - ((PNL_ICON * 2.2f) / block->aspect), @@ -976,7 +1056,8 @@ void ui_draw_aligned_panel(const uiStyle *style, if (is_subpanel) { titlerect.xmin += (0.7f * UI_UNIT_X) / block->aspect + 0.001f; } - ui_draw_aligned_panel_header(style, block, &titlerect, show_background); + ui_draw_aligned_panel_header( + style, block, &titlerect, show_background, region_search_filter_active); if (show_drag) { /* Make `itemrect` smaller. */ @@ -1071,7 +1152,7 @@ void ui_draw_aligned_panel(const uiStyle *style, BLI_rctf_scale(&itemrect, 0.25f); uchar col_title[4]; - panel_title_color_get(show_background, col_title); + panel_title_color_get(panel, show_background, false, region_search_filter_active, col_title); float tria_color[4]; rgb_uchar_to_float(tria_color, col_title); tria_color[3] = 1.0f; @@ -1853,19 +1934,23 @@ void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) ui_panels_size(region, r_x, r_y); } +/** + * Draw panels, selected (panels currently being dragged) on top. + */ void UI_panels_draw(const bContext *C, ARegion *region) { - /* Draw panels, selected on top. Also in reverse order, because - * UI blocks are added in reverse order and we need child panels - * to draw on top. */ + /* Draw in reverse order, because #uiBlocks are added in reverse order + * and we need child panels to draw on top. */ LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && !(block->panel->flag & PNL_SELECT)) { + if (block->active && block->panel && !(block->panel->flag & PNL_SELECT) && + !UI_block_is_search_only(block)) { UI_block_draw(C, block); } } LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && (block->panel->flag & PNL_SELECT)) { + if (block->active && block->panel && (block->panel->flag & PNL_SELECT) && + !UI_block_is_search_only(block)) { UI_block_draw(C, block); } } |