WIP: UI: Preferences Search #119072

Draft
Harley Acheson wants to merge 1 commits from Harley/blender:PreferencesSearch into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
7 changed files with 355 additions and 4 deletions

View File

@ -63,9 +63,12 @@ class USERPREF_PT_navigation_bar(Panel):
def draw(self, context):
layout = self.layout
view = context.space_data
prefs = context.preferences
layout.prop(view, "search_filter", icon='VIEWZOOM', text="")
col = layout.column()
col.scale_x = 1.3

View File

@ -9,3 +9,9 @@
#pragma once
void ED_operatortypes_userpref();
int ED_userpref_tabs_list(SpaceUserPref *sprefs, short *context_tabs_array);
bool ED_userpref_tab_has_search_result(SpaceUserPref *sprefs, int index);
void ED_userpref_search_string_set(SpaceUserPref *sprefs, const char *value);
int ED_userpref_search_string_length(SpaceUserPref *sprefs);
const char *ED_userpref_search_string_get(SpaceUserPref *sprefs);

View File

@ -37,6 +37,7 @@
#include "ED_screen_types.hh"
#include "ED_space_api.hh"
#include "ED_time_scrub_ui.hh"
#include "ED_userpref.hh"
#include "GPU_framebuffer.h"
#include "GPU_immediate.h"
@ -776,13 +777,18 @@ void ED_area_tag_region_size_update(ScrArea *area, ARegion *changed_region)
const char *ED_area_region_search_filter_get(const ScrArea *area, const ARegion *region)
{
/* Only the properties editor has a search string for now. */
if (area->spacetype == SPACE_PROPERTIES) {
SpaceProperties *sbuts = static_cast<SpaceProperties *>(area->spacedata.first);
if (region->regiontype == RGN_TYPE_WINDOW) {
return ED_buttons_search_string_get(sbuts);
}
}
else if (area->spacetype == SPACE_USERPREF) {
SpaceUserPref *sprefs = static_cast<SpaceUserPref *>(area->spacedata.first);
if (region->regiontype == RGN_TYPE_WINDOW) {
return ED_userpref_search_string_get(sprefs);
}
}
return nullptr;
}

View File

@ -11,6 +11,7 @@
#include "MEM_guardedalloc.h"
#include "BLI_bitmap.h"
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
@ -29,6 +30,8 @@
#include "BLO_read_write.hh"
#include "userpref_intern.h"
/* ******************** default callbacks for userpref space ***************** */
static SpaceLink *userpref_create(const ScrArea *area, const Scene * /*scene*/)
@ -77,18 +80,39 @@ static SpaceLink *userpref_create(const ScrArea *area, const Scene * /*scene*/)
}
/* Doesn't free the space-link itself. */
static void userpref_free(SpaceLink * /*sl*/)
static void userpref_free(SpaceLink *sl)
{
// SpaceUserPref *spref = (SpaceUserPref *)sl;
SpaceUserPref *spref = (SpaceUserPref *)sl;
if (spref->runtime != nullptr) {
MEM_SAFE_FREE(spref->runtime->tab_search_results);
MEM_freeN(spref->runtime);
}
}
/* spacetype; init callback */
static void userpref_init(wmWindowManager * /*wm*/, ScrArea * /*area*/) {}
static void userpref_init(wmWindowManager * /*wm*/, ScrArea *area)
{
SpaceUserPref *spref = (SpaceUserPref *)area->spacedata.first;
if (spref->runtime == nullptr) {
spref->runtime = static_cast<SpaceUserPref_Runtime *>(
MEM_mallocN(sizeof(SpaceUserPref_Runtime), __func__));
spref->runtime->search_string[0] = '\0';
spref->runtime->tab_search_results = BLI_BITMAP_NEW(BCONTEXT_TOT * 2, __func__);
}
}
static SpaceLink *userpref_duplicate(SpaceLink *sl)
{
SpaceUserPref *sprefn_old = (SpaceUserPref *)sl;
SpaceUserPref *sprefn = static_cast<SpaceUserPref *>(MEM_dupallocN(sl));
if (sprefn_old->runtime != nullptr) {
sprefn->runtime = static_cast<SpaceUserPref_Runtime *>(MEM_dupallocN(sprefn_old->runtime));
sprefn->runtime->search_string[0] = '\0';
sprefn->runtime->tab_search_results = BLI_BITMAP_NEW(BCONTEXT_TOT, __func__);
}
/* clear or remove stuff from old */
return (SpaceLink *)sprefn;
@ -106,10 +130,227 @@ static void userpref_main_region_init(wmWindowManager *wm, ARegion *region)
ED_region_panels_init(wm, region);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Userpref Search Access API
* \{ */
const char *ED_userpref_search_string_get(SpaceUserPref *spref)
{
return spref->runtime->search_string;
}
int ED_userpref_search_string_length(SpaceUserPref *spref)
{
return BLI_strnlen(spref->runtime->search_string, sizeof(spref->runtime->search_string));
}
void ED_userpref_search_string_set(SpaceUserPref *spref, const char *value)
{
STRNCPY(spref->runtime->search_string, value);
}
bool ED_userpref_tab_has_search_result(SpaceUserPref *spref, const int index)
{
return BLI_BITMAP_TEST(spref->runtime->tab_search_results, index);
}
/** \} */
int ED_userpref_tabs_list(SpaceUserPref *prefs, short *context_tabs_array)
{
int length = 0;
const EnumPropertyItem *items = rna_enum_preference_section_items;
for (const EnumPropertyItem *it = rna_enum_preference_section_items; it->identifier != nullptr;
it++)
{
if (it->name) {
context_tabs_array[length] = it->value;
length++;
}
}
return length;
}
/* -------------------------------------------------------------------- */
/** \name "Off Screen" Layout Generation for Userpref Search
* \{ */
static const char *userpref_main_region_context_string(const short section) {}
static bool userpref_search_for_context(const bContext *C,
ARegion *region,
SpaceUserPref *sprefs,
short section)
{
char lower[64];
const char *name = nullptr;
RNA_enum_id_from_value(rna_enum_preference_section_items, section, &name);
STRNCPY(lower, name);
BLI_str_tolower_ascii(lower, strlen(lower));
const char *contexts[2] = {lower, nullptr};
return ED_region_property_search(C, region, &region->type->paneltypes, contexts, nullptr);
}
static void userpref_search_move_to_next_tab_with_results(SpaceUserPref *sbuts,
const short *context_tabs_array,
const int tabs_len)
{
int current_tab_index = 0;
for (int i = 0; i < tabs_len; i++) {
if (U.space_data.section_active == context_tabs_array[i]) {
current_tab_index = i;
break;
}
}
/* Try the tabs after the current tab. */
for (int i = current_tab_index + 1; i < tabs_len; i++) {
if (BLI_BITMAP_TEST(sbuts->runtime->tab_search_results, i)) {
U.space_data.section_active = context_tabs_array[i];
return;
}
}
/* Try the tabs before the current tab. */
for (int i = 0; i < current_tab_index; i++) {
if (BLI_BITMAP_TEST(sbuts->runtime->tab_search_results, i)) {
U.space_data.section_active = context_tabs_array[i];
return;
}
}
}
static bool userpref_search_move_to_first_tab_with_results(SpaceUserPref *sbuts,
const short *context_tabs_array,
const int tabs_len)
{
int current_tab_index = 0;
for (int i = 0; i < tabs_len; i++) {
if (U.space_data.section_active == context_tabs_array[i]) {
current_tab_index = i;
break;
}
}
/* Try the tabs before the current tab. */
for (int i = 0; i < current_tab_index; i++) {
if (BLI_BITMAP_TEST(sbuts->runtime->tab_search_results, i)) {
U.space_data.section_active = context_tabs_array[i];
return true;
}
}
return false;
}
static void userpref_search_all_tabs(const bContext *C,
SpaceUserPref *sprefs,
ARegion *region_original,
const short *context_tabs_array,
const int tabs_len)
{
/* Use local copies of the area and duplicate the region as a mainly-paranoid protection
* against changing any of the space / region data while running the search. */
ScrArea *area_original = CTX_wm_area(C);
ScrArea area_copy = blender::dna::shallow_copy(*area_original);
ARegion *region_copy = BKE_area_region_copy(area_copy.type, region_original);
/* Set the region visible field. Otherwise some layout code thinks we're drawing in a popup.
* This likely isn't necessary, but it's nice to emulate a "real" region where possible. */
region_copy->visible = true;
CTX_wm_area_set((bContext *)C, &area_copy);
CTX_wm_region_set((bContext *)C, region_copy);
SpaceUserPref sprefs_copy = blender::dna::shallow_copy(*sprefs);
sprefs_copy.runtime = static_cast<SpaceUserPref_Runtime *>(MEM_dupallocN(sprefs->runtime));
sprefs_copy.runtime->tab_search_results = nullptr;
BLI_listbase_clear(&area_copy.spacedata);
BLI_addtail(&area_copy.spacedata, &sprefs_copy);
/* Loop through the tabs. */
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 in the normal layout pass. */
if (context_tabs_array[i] == U.space_data.section_active) {
continue;
}
/* Actually do the search and store the result in the bitmap. */
const bool found = userpref_search_for_context(
C, region_copy, &sprefs_copy, context_tabs_array[i]);
BLI_BITMAP_SET(sprefs->runtime->tab_search_results, i, found);
UI_blocklist_free(C, region_copy);
}
BKE_area_region_free(area_copy.type, region_copy);
MEM_freeN(region_copy);
userpref_free((SpaceLink *)&sprefs_copy);
CTX_wm_area_set((bContext *)C, area_original);
CTX_wm_region_set((bContext *)C, region_original);
}
/**
* Handle userpref search for the layout pass, including finding which tabs have
* search results and switching if the current tab doesn't have a result.
*/
static void userpref_main_region_property_search(const bContext *C,
SpaceUserPref *sprefs,
ARegion *region)
{
short context_tabs_array[50];
int tabs_len = 0;
tabs_len = ED_userpref_tabs_list(sprefs, context_tabs_array);
BLI_bitmap_set_all(sprefs->runtime->tab_search_results, false, tabs_len);
userpref_search_all_tabs(C, sprefs, 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, &region->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] == U.space_data.section_active) {
current_tab_index = i;
}
}
BLI_assert(current_tab_index != -1);
/* Update the tab search match flag for the current tab. */
BLI_BITMAP_SET(
sprefs->runtime->tab_search_results, current_tab_index, current_tab_has_search_match);
/* Move to the next tab with a result */
if (!current_tab_has_search_match) {
if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) {
userpref_search_move_to_next_tab_with_results(sprefs, context_tabs_array, tabs_len);
}
else {
U.space_data.section_active = 0;
}
}
}
/** \} */
static void userpref_main_region_layout(const bContext *C, ARegion *region)
{
char id_lower[64];
const char *contexts[2] = {id_lower, nullptr};
SpaceUserPref *spref = CTX_wm_space_userpref(C);
/* Avoid duplicating identifiers, use existing RNA enum. */
{
@ -127,6 +368,10 @@ static void userpref_main_region_layout(const bContext *C, ARegion *region)
ED_region_panels_layout_ex(
C, region, &region->type->paneltypes, WM_OP_INVOKE_REGION_WIN, contexts, nullptr);
if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) {
userpref_main_region_property_search(C, spref, region);
}
}
static void userpref_operatortypes() {}

View File

@ -8,4 +8,16 @@
#pragma once
#include "BLI_bitmap.h"
/* internal exports only */
struct SpaceUserPref_Runtime {
/** For filtering properties displayed in the space. */
char search_string[128];
/**
* Bit-field (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.
*/
BLI_bitmap *tab_search_results;
};

View File

@ -56,6 +56,9 @@ typedef struct AssetRepresentationHandle AssetRepresentationHandle;
/** Defined in `buttons_intern.h`. */
typedef struct SpaceProperties_Runtime SpaceProperties_Runtime;
/** Defined in `userpref_intern.h`. */
typedef struct SpaceUserPref_Runtime SpaceUserPref_Runtime;
#ifdef __cplusplus
namespace blender::ed::space_node {
struct SpaceNode_Runtime;
@ -1723,6 +1726,7 @@ typedef struct SpaceConsole {
* \{ */
typedef struct SpaceUserPref {
DNA_DEFINE_CXX_METHODS(SpaceUserPref)
SpaceLink *next, *prev;
/** Storage of regions for inactive spaces. */
ListBase regionbase;
@ -1735,6 +1739,7 @@ typedef struct SpaceUserPref {
char filter_type;
/** Search term for filtering in the UI. */
char filter[64];
struct SpaceUserPref_Runtime *runtime;
} SpaceUserPref;
/** \} */

View File

@ -26,6 +26,7 @@
#include "ED_asset.hh"
#include "ED_spreadsheet.hh"
#include "ED_text.hh"
#include "ED_userpref.hh"
#include "BLI_listbase.h"
#include "BLI_math_matrix.h"
@ -2133,6 +2134,67 @@ static void rna_SpaceProperties_search_filter_update(Main * /*bmain*/,
ED_region_search_filter_update(area, main_region);
}
/* Space Userpref */
static int rna_SpaceUserPref_tab_search_results_getlength(const PointerRNA *ptr,
int length[RNA_MAX_ARRAY_DIMENSION])
{
SpaceUserPref *sprefs = static_cast<SpaceUserPref *>(ptr->data);
short context_tabs_array[BCONTEXT_TOT * 2]; /* Dummy variable. */
const int tabs_len = ED_userpref_tabs_list(sprefs, context_tabs_array);
length[0] = tabs_len;
return length[0];
}
static void rna_SpaceUserPref_tab_search_results_get(PointerRNA *ptr, bool *values)
{
SpaceUserPref *sprefs = static_cast<SpaceUserPref *>(ptr->data);
short context_tabs_array[BCONTEXT_TOT * 2]; /* Dummy variable. */
const int tabs_len = ED_userpref_tabs_list(sprefs, context_tabs_array);
for (int i = 0; i < tabs_len; i++) {
values[i] = ED_userpref_tab_has_search_result(sprefs, i);
}
}
static void rna_SpaceUserPref_search_filter_get(PointerRNA *ptr, char *value)
{
SpaceUserPref *sprefs = static_cast<SpaceUserPref *>(ptr->data);
const char *search_filter = ED_userpref_search_string_get(sprefs);
strcpy(value, search_filter);
}
static int rna_SpaceUserPref_search_filter_length(PointerRNA *ptr)
{
SpaceUserPref *sprefs = static_cast<SpaceUserPref *>(ptr->data);
return ED_userpref_search_string_length(sprefs);
}
static void rna_SpaceUserPref_search_filter_set(PointerRNA *ptr, const char *value)
{
SpaceUserPref *sprefs = static_cast<SpaceUserPref *>(ptr->data);
ED_userpref_search_string_set(sprefs, value);
}
static void rna_SpaceUserPref_search_filter_update(Main * /*bmain*/,
Scene * /*scene*/,
PointerRNA *ptr)
{
ScrArea *area = rna_area_from_space(ptr);
/* Update the search filter flag for the main region with the panels. */
ARegion *main_region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
BLI_assert(main_region != nullptr);
ED_region_search_filter_update(area, main_region);
}
/* Space Console */
static void rna_ConsoleLine_body_get(PointerRNA *ptr, char *value)
{
@ -7345,6 +7407,18 @@ static void rna_def_space_userpref(BlenderRNA *brna)
RNA_def_property_string_sdna(prop, nullptr, "filter");
RNA_def_property_flag(prop, PROP_TEXTEDIT_UPDATE);
RNA_def_property_ui_text(prop, "Filter", "Search term for filtering in the UI");
prop = RNA_def_property(srna, "search_filter", PROP_STRING, PROP_NONE);
/* The search filter is stored in the property editor's runtime which
* is only defined in an internal header, so use the getter / setter here. */
RNA_def_property_string_funcs(prop,
"rna_SpaceUserPref_search_filter_get",
"rna_SpaceUserPref_search_filter_length",
"rna_SpaceUserPref_search_filter_set");
RNA_def_property_ui_text(prop, "Display Filter", "Live search filtering string");
RNA_def_property_flag(prop, PROP_TEXTEDIT_UPDATE);
RNA_def_property_update(
prop, NC_SPACE | ND_SPACE_PROPERTIES, "rna_SpaceUserPref_search_filter_update");
}
static void rna_def_node_tree_path(BlenderRNA *brna)