Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/interface')
-rw-r--r--source/blender/editors/interface/CMakeLists.txt3
-rw-r--r--source/blender/editors/interface/interface.c140
-rw-r--r--source/blender/editors/interface/interface_anim.c56
-rw-r--r--source/blender/editors/interface/interface_context_menu.c2
-rw-r--r--source/blender/editors/interface/interface_handlers.c190
-rw-r--r--source/blender/editors/interface/interface_icons.c3
-rw-r--r--source/blender/editors/interface/interface_intern.h53
-rw-r--r--source/blender/editors/interface/interface_layout.c360
-rw-r--r--source/blender/editors/interface/interface_ops.c7
-rw-r--r--source/blender/editors/interface/interface_panel.c43
-rw-r--r--source/blender/editors/interface/interface_region_menu_popup.c17
-rw-r--r--source/blender/editors/interface/interface_region_search.c97
-rw-r--r--source/blender/editors/interface/interface_template_search_menu.c1098
-rw-r--r--source/blender/editors/interface/interface_template_search_operator.c151
-rw-r--r--source/blender/editors/interface/interface_templates.c1281
-rw-r--r--source/blender/editors/interface/interface_undo.c139
-rw-r--r--source/blender/editors/interface/interface_utils.c41
-rw-r--r--source/blender/editors/interface/interface_widgets.c167
-rw-r--r--source/blender/editors/interface/view2d.c98
19 files changed, 2581 insertions, 1365 deletions
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt
index d33023c69a1..c2c27af9770 100644
--- a/source/blender/editors/interface/CMakeLists.txt
+++ b/source/blender/editors/interface/CMakeLists.txt
@@ -69,6 +69,9 @@ set(SRC
interface_regions.c
interface_style.c
interface_templates.c
+ interface_template_search_menu.c
+ interface_template_search_operator.c
+ interface_undo.c
interface_utils.c
interface_widgets.c
resources.c
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index 9ec660a9714..04c259ab092 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -641,6 +641,26 @@ static int ui_but_calc_float_precision(uiBut *but, double value)
/* ************** BLOCK ENDING FUNCTION ************* */
+bool ui_but_rna_equals(const uiBut *a, const uiBut *b)
+{
+ return ui_but_rna_equals_ex(a, &b->rnapoin, b->rnaprop, b->rnaindex);
+}
+
+bool ui_but_rna_equals_ex(const uiBut *but,
+ const PointerRNA *ptr,
+ const PropertyRNA *prop,
+ int index)
+{
+ if (but->rnapoin.data != ptr->data) {
+ return false;
+ }
+ if (but->rnaprop != prop || but->rnaindex != index) {
+ return false;
+ }
+
+ return true;
+}
+
/* NOTE: if but->poin is allocated memory for every defbut, things fail... */
static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
{
@@ -649,10 +669,7 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
if (but->retval != oldbut->retval) {
return false;
}
- if (but->rnapoin.data != oldbut->rnapoin.data) {
- return false;
- }
- if (but->rnaprop != oldbut->rnaprop || but->rnaindex != oldbut->rnaindex) {
+ if (!ui_but_rna_equals(but, oldbut)) {
return false;
}
if (but->func != oldbut->func) {
@@ -790,8 +807,7 @@ static bool ui_but_update_from_old_block(const bContext *C,
SWAP(ListBase, but->extra_op_icons, oldbut->extra_op_icons);
- SWAP(uiButSearchArgFreeFunc, oldbut->search_arg_free_func, but->search_arg_free_func);
- SWAP(void *, oldbut->search_arg, but->search_arg);
+ SWAP(struct uiButSearchData *, oldbut->search, but->search);
/* copy hardmin for list rows to prevent 'sticking' highlight to mouse position
* when scrolling without moving mouse (see [#28432]) */
@@ -979,7 +995,9 @@ static void ui_menu_block_set_keyaccels(uiBlock *block)
UI_BTYPE_BUT_MENU,
UI_BTYPE_MENU,
UI_BTYPE_BLOCK,
- UI_BTYPE_PULLDOWN) ||
+ UI_BTYPE_PULLDOWN,
+ /* For PIE-menus. */
+ UI_BTYPE_ROW) ||
(but->flag & UI_HIDDEN)) {
/* pass */
}
@@ -3208,9 +3226,12 @@ static void ui_but_free(const bContext *C, uiBut *but)
MEM_freeN(but->hold_argN);
}
- if (but->search_arg_free_func) {
- but->search_arg_free_func(but->search_arg);
- but->search_arg = NULL;
+ if (but->search != NULL) {
+ if (but->search->arg_free_fn) {
+ but->search->arg_free_fn(but->search->arg);
+ but->search->arg = NULL;
+ }
+ MEM_freeN(but->search);
}
if (but->active) {
@@ -6330,40 +6351,51 @@ uiBut *uiDefSearchBut(uiBlock *block,
}
/**
- * \param search_func, bfunc: both get it as \a arg.
- * \param arg: user value,
- * \param active: when set, button opens with this item visible and selected.
- * \param separator_string: when not NULL, this string is used as a separator,
- * showing the icon and highlighted text after the last instance of this string.
+ * \note The item-pointer (referred to below) is a per search item user pointer
+ * passed to #UI_search_item_add (stored in #uiSearchItems.pointers).
+ *
+ * \param search_create_fn: Function to create the menu.
+ * \param search_update_fn: Function to refresh search content after the search text has changed.
+ * \param arg: user value.
+ * \param search_arg_free_fn: When non-null, use this function to free \a arg.
+ * \param search_exec_fn: Function that executes the action, gets \a arg as the first argument.
+ * The second argument as the active item-pointer
+ * \param active: When non-null, this item-pointer item will be visible and selected,
+ * otherwise the first item will be selected.
*/
void UI_but_func_search_set(uiBut *but,
- uiButSearchCreateFunc search_create_func,
- uiButSearchFunc search_func,
+ uiButSearchCreateFn search_create_fn,
+ uiButSearchUpdateFn search_update_fn,
void *arg,
- uiButSearchArgFreeFunc search_arg_free_func,
- uiButHandleFunc bfunc,
- const char *search_sep_string,
+ uiButSearchArgFreeFn search_arg_free_fn,
+ uiButHandleFunc search_exec_fn,
void *active)
{
/* needed since callers don't have access to internal functions
* (as an alternative we could expose it) */
- if (search_create_func == NULL) {
- search_create_func = ui_searchbox_create_generic;
+ if (search_create_fn == NULL) {
+ search_create_fn = ui_searchbox_create_generic;
}
- if (but->search_arg_free_func != NULL) {
- but->search_arg_free_func(but->search_arg);
- but->search_arg = NULL;
+ struct uiButSearchData *search = but->search;
+ if (search != NULL) {
+ if (search->arg_free_fn != NULL) {
+ search->arg_free_fn(but->search->arg);
+ search->arg = NULL;
+ }
+ }
+ else {
+ search = MEM_callocN(sizeof(*but->search), __func__);
+ but->search = search;
}
- but->search_create_func = search_create_func;
- but->search_func = search_func;
+ search->create_fn = search_create_fn;
+ search->update_fn = search_update_fn;
- but->search_arg = arg;
- but->search_arg_free_func = search_arg_free_func;
- but->search_sep_string = search_sep_string;
+ search->arg = arg;
+ search->arg_free_fn = search_arg_free_fn;
- if (bfunc) {
+ if (search_exec_fn) {
#ifdef DEBUG
if (but->func) {
/* watch this, can be cause of much confusion, see: T47691 */
@@ -6371,7 +6403,7 @@ void UI_but_func_search_set(uiBut *but,
__func__);
}
#endif
- UI_but_func_set(but, bfunc, arg, active);
+ UI_but_func_set(but, search_exec_fn, search->arg, active);
}
/* search buttons show red-alert if item doesn't exist, not for menus */
@@ -6383,11 +6415,33 @@ void UI_but_func_search_set(uiBut *but,
}
}
+void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn)
+{
+ struct uiButSearchData *search = but->search;
+ search->context_menu_fn = context_menu_fn;
+}
+
+/**
+ * \param separator_string: when not NULL, this string is used as a separator,
+ * showing the icon and highlighted text after the last instance of this string.
+ */
+void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string)
+{
+ struct uiButSearchData *search = but->search;
+ search->sep_string = search_sep_string;
+}
+
+void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn)
+{
+ struct uiButSearchData *search = but->search;
+ search->tooltip_fn = tooltip_fn;
+}
+
/* Callbacks for operator search button. */
-static void operator_enum_search_cb(const struct bContext *C,
- void *but,
- const char *str,
- uiSearchItems *items)
+static void operator_enum_search_update_fn(const struct bContext *C,
+ void *but,
+ const char *str,
+ uiSearchItems *items)
{
wmOperatorType *ot = ((uiBut *)but)->optype;
PropertyRNA *prop = ot->prop;
@@ -6424,7 +6478,7 @@ static void operator_enum_search_cb(const struct bContext *C,
}
}
-static void operator_enum_call_cb(struct bContext *UNUSED(C), void *but, void *arg2)
+static void operator_enum_search_exec_fn(struct bContext *UNUSED(C), void *but, void *arg2)
{
wmOperatorType *ot = ((uiBut *)but)->optype;
PointerRNA *opptr = UI_but_operator_ptr_get(but); /* Will create it if needed! */
@@ -6467,11 +6521,10 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block,
but = uiDefSearchBut(block, arg, retval, icon, maxlen, x, y, width, height, a1, a2, tip);
UI_but_func_search_set(but,
ui_searchbox_create_generic,
- operator_enum_search_cb,
+ operator_enum_search_update_fn,
but,
NULL,
- operator_enum_call_cb,
- NULL,
+ operator_enum_search_exec_fn,
NULL);
but->optype = ot;
@@ -6486,6 +6539,13 @@ uiBut *uiDefSearchButO_ptr(uiBlock *block,
return but;
}
+void UI_but_node_link_set(uiBut *but, bNodeSocket *socket, const float draw_color[4])
+{
+ but->flag |= UI_BUT_NODE_LINK;
+ but->custom_data = socket;
+ rgba_float_to_uchar(but->col, draw_color);
+}
+
/**
* push a new event onto event queue to activate the given button
* (usually a text-field) upon entering a popup
diff --git a/source/blender/editors/interface/interface_anim.c b/source/blender/editors/interface/interface_anim.c
index 15fc23bc539..8c9768f523d 100644
--- a/source/blender/editors/interface/interface_anim.c
+++ b/source/blender/editors/interface/interface_anim.c
@@ -28,12 +28,14 @@
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
+#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
#include "BKE_context.h"
#include "BKE_fcurve.h"
+#include "BKE_fcurve_driver.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_nla.h"
@@ -114,10 +116,40 @@ void ui_but_anim_flag(uiBut *but, float cfra)
}
}
+static uiBut *ui_but_anim_decorate_find_attached_button(uiBut *but_decorate)
+{
+ uiBut *but_iter = NULL;
+
+ BLI_assert(UI_but_is_decorator(but_decorate));
+ BLI_assert(but_decorate->rnasearchpoin.data && but_decorate->rnasearchprop);
+
+ LISTBASE_CIRCULAR_BACKWARD_BEGIN (&but_decorate->block->buttons, but_iter, but_decorate->prev) {
+ if (but_iter != but_decorate &&
+ ui_but_rna_equals_ex(but_iter,
+ &but_decorate->rnasearchpoin,
+ but_decorate->rnasearchprop,
+ POINTER_AS_INT(but_decorate->custom_data))) {
+ return but_iter;
+ }
+ }
+ LISTBASE_CIRCULAR_BACKWARD_END(&but_decorate->block->buttons, but_iter, but_decorate->prev);
+
+ return NULL;
+}
+
void ui_but_anim_decorate_update_from_flag(uiBut *but)
{
- BLI_assert(UI_but_is_decorator(but) && but->prev);
- int flag = but->prev->flag;
+ const uiBut *but_anim = ui_but_anim_decorate_find_attached_button(but);
+
+ if (!but_anim) {
+ printf("Could not find button with matching property to decorate (%s.%s)\n",
+ RNA_struct_identifier(but->rnasearchpoin.type),
+ RNA_property_identifier(but->rnasearchprop));
+ return;
+ }
+
+ int flag = but_anim->flag;
+
if (flag & UI_BUT_DRIVEN) {
but->icon = ICON_DECORATE_DRIVER;
}
@@ -289,22 +321,26 @@ void ui_but_anim_paste_driver(bContext *C)
void ui_but_anim_decorate_cb(bContext *C, void *arg_but, void *UNUSED(arg_dummy))
{
wmWindowManager *wm = CTX_wm_manager(C);
- uiBut *but = arg_but;
- but = but->prev;
+ uiBut *but_decorate = arg_but;
+ uiBut *but_anim = ui_but_anim_decorate_find_attached_button(but_decorate);
+
+ if (!but_anim) {
+ return;
+ }
/* FIXME(campbell), swapping active pointer is weak. */
- SWAP(struct uiHandleButtonData *, but->active, but->next->active);
+ SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->active);
wm->op_undo_depth++;
- if (but->flag & UI_BUT_DRIVEN) {
+ if (but_anim->flag & UI_BUT_DRIVEN) {
/* pass */
/* TODO: report? */
}
- else if (but->flag & UI_BUT_ANIMATED_KEY) {
+ else if (but_anim->flag & UI_BUT_ANIMATED_KEY) {
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find("ANIM_OT_keyframe_delete_button", false);
WM_operator_properties_create_ptr(&props_ptr, ot);
- RNA_boolean_set(&props_ptr, "all", but->rnaindex == -1);
+ RNA_boolean_set(&props_ptr, "all", but_anim->rnaindex == -1);
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
WM_operator_properties_free(&props_ptr);
}
@@ -312,11 +348,11 @@ void ui_but_anim_decorate_cb(bContext *C, void *arg_but, void *UNUSED(arg_dummy)
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find("ANIM_OT_keyframe_insert_button", false);
WM_operator_properties_create_ptr(&props_ptr, ot);
- RNA_boolean_set(&props_ptr, "all", but->rnaindex == -1);
+ RNA_boolean_set(&props_ptr, "all", but_anim->rnaindex == -1);
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr);
WM_operator_properties_free(&props_ptr);
}
- SWAP(struct uiHandleButtonData *, but->active, but->next->active);
+ SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->active);
wm->op_undo_depth--;
}
diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c
index 5245b724da4..cc370113422 100644
--- a/source/blender/editors/interface/interface_context_menu.c
+++ b/source/blender/editors/interface/interface_context_menu.c
@@ -962,7 +962,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
const PropertyType prop_type = RNA_property_type(but->rnaprop);
if (((prop_type == PROP_POINTER) ||
(prop_type == PROP_STRING && but->type == UI_BTYPE_SEARCH_MENU &&
- but->search_func == ui_rna_collection_search_cb)) &&
+ but->search->update_fn == ui_rna_collection_search_update_fn)) &&
ui_jump_to_target_button_poll(C)) {
uiItemO(layout,
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Jump to Target"),
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 9a7e189406c..ebde1d54c07 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -381,6 +381,9 @@ typedef struct uiHandleButtonData {
uiSelectContextStore select_others;
#endif
+ /* Text field undo. */
+ struct uiUndoStack_Text *undo_stack_text;
+
/* post activate */
uiButtonActivateType posttype;
uiBut *postbut;
@@ -417,7 +420,7 @@ typedef struct uiAfterFunc {
PropertyRNA *rnaprop;
void *search_arg;
- uiButSearchArgFreeFunc search_arg_free_func;
+ uiButSearchArgFreeFn search_arg_free_fn;
bContextStore *context;
@@ -753,10 +756,12 @@ static void ui_apply_but_func(bContext *C, uiBut *but)
after->rnapoin = but->rnapoin;
after->rnaprop = but->rnaprop;
- after->search_arg_free_func = but->search_arg_free_func;
- after->search_arg = but->search_arg;
- but->search_arg_free_func = NULL;
- but->search_arg = NULL;
+ if (but->search != NULL) {
+ after->search_arg_free_fn = but->search->arg_free_fn;
+ after->search_arg = but->search->arg;
+ but->search->arg_free_fn = NULL;
+ but->search->arg = NULL;
+ }
if (but->context) {
after->context = CTX_store_copy(but->context);
@@ -924,8 +929,8 @@ static void ui_apply_but_funcs_after(bContext *C)
MEM_freeN(after.rename_orig);
}
- if (after.search_arg_free_func) {
- after.search_arg_free_func(after.search_arg);
+ if (after.search_arg_free_fn) {
+ after.search_arg_free_fn(after.search_arg);
}
ui_afterfunc_update_preferences_dirty(&after);
@@ -2848,6 +2853,23 @@ static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data)
return changed;
}
+static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str),
+ const size_t str_step_ofs,
+ const rcti *glyph_step_bounds,
+ const int UNUSED(glyph_advance_x),
+ const rctf *glyph_bounds,
+ const int UNUSED(glyph_bearing[2]),
+ void *user_data)
+{
+ int *cursor_data = user_data;
+ float center = glyph_step_bounds->xmin + (BLI_rctf_size_x(glyph_bounds) / 2.0f);
+ if (cursor_data[0] < center) {
+ cursor_data[1] = str_step_ofs;
+ return false;
+ }
+ return true;
+}
+
/**
* \param x: Screen space cursor location - #wmEvent.x
*
@@ -2883,8 +2905,7 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con
startx += UI_DPI_ICON_SIZE / aspect;
}
}
- /* But this extra .05 makes clicks in between characters feel nicer. */
- startx += ((UI_TEXT_MARGIN_X + 0.05f) * U.widget_unit) / aspect;
+ startx += (UI_TEXT_MARGIN_X * U.widget_unit) / aspect;
/* mouse dragged outside the widget to the left */
if (x < startx) {
@@ -2907,48 +2928,24 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con
but->pos = but->ofs;
}
/* mouse inside the widget, mouse coords mapped in widget space */
- else { /* (x >= startx) */
- int pos_i;
-
- /* keep track of previous distance from the cursor to the char */
- float cdist, cdist_prev = 0.0f;
- short pos_prev;
-
- str_last = &str[strlen(str)];
-
- but->pos = pos_prev = ((str_last - str) - but->ofs);
-
- while (true) {
- cdist = startx + BLF_width(fstyle.uifont_id, str + but->ofs, (str_last - str) - but->ofs);
-
- /* check if position is found */
- if (cdist < x) {
- /* check is previous location was in fact closer */
- if ((x - cdist) > (cdist_prev - x)) {
- but->pos = pos_prev;
- }
- break;
- }
- cdist_prev = cdist;
- pos_prev = but->pos;
- /* done with tricky distance checks */
-
- pos_i = but->pos;
- if (but->pos <= 0) {
- break;
- }
- if (BLI_str_cursor_step_prev_utf8(str + but->ofs, but->ofs, &pos_i)) {
- but->pos = pos_i;
- str_last = &str[but->pos + but->ofs];
- }
- else {
- break; /* unlikely but possible */
- }
- }
- but->pos += but->ofs;
- if (but->pos < 0) {
- but->pos = 0;
- }
+ else {
+ str_last = &str[but->ofs];
+ const int str_last_len = strlen(str_last);
+ int x_pos = (int)(x - startx);
+ int glyph_data[2] = {
+ x_pos, /* horizontal position to test. */
+ -1, /* Write the character offset here. */
+ };
+ BLF_boundbox_foreach_glyph(fstyle.uifont_id,
+ str + but->ofs,
+ INT_MAX,
+ ui_textedit_set_cursor_pos_foreach_glyph,
+ glyph_data);
+ /* If value untouched then we are to the right. */
+ if (glyph_data[1] == -1) {
+ glyph_data[1] = str_last_len;
+ }
+ but->pos = glyph_data[1] + but->ofs;
}
if (fstyle.kerning == 1) {
@@ -3314,9 +3311,13 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
but->selsta = 0;
but->selend = len;
+ /* Initialize undo history tracking. */
+ data->undo_stack_text = ui_textedit_undo_stack_create();
+ ui_textedit_undo_push(data->undo_stack_text, but->editstr, but->pos);
+
/* optional searchbox */
if (but->type == UI_BTYPE_SEARCH_MENU) {
- data->searchbox = but->search_create_func(C, data->region, but);
+ data->searchbox = but->search->create_fn(C, data->region, but);
ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
}
@@ -3356,6 +3357,9 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
/* ensure menu (popup) too is closed! */
data->escapecancel = true;
+
+ WM_reportf(RPT_ERROR, "Failed to find '%s'", but->editstr);
+ WM_report_banner_show();
}
}
@@ -3369,6 +3373,10 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
WM_cursor_modal_restore(win);
+ /* Free text undo history text blocks. */
+ ui_textedit_undo_stack_destroy(data->undo_stack_text);
+ data->undo_stack_text = NULL;
+
#ifdef WITH_INPUT_IME
if (win->ime_data) {
ui_textedit_ime_end(win, but);
@@ -3448,7 +3456,7 @@ static void ui_do_but_textedit(
bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
{
int retval = WM_UI_HANDLER_CONTINUE;
- bool changed = false, inbox = false, update = false;
+ bool changed = false, inbox = false, update = false, skip_undo_push = false;
#ifdef WITH_INPUT_IME
wmWindow *win = CTX_wm_window(C);
@@ -3468,7 +3476,7 @@ static void ui_do_but_textedit(
/* pass */
}
else {
- ui_searchbox_event(C, data->searchbox, but, event);
+ ui_searchbox_event(C, data->searchbox, but, data->region, event);
}
#else
ui_searchbox_event(C, data->searchbox, but, event);
@@ -3479,6 +3487,16 @@ static void ui_do_but_textedit(
case RIGHTMOUSE:
case EVT_ESCKEY:
if (event->val == KM_PRESS) {
+ /* Support search context menu. */
+ if (event->type == RIGHTMOUSE) {
+ if (data->searchbox) {
+ if (ui_searchbox_event(C, data->searchbox, but, data->region, event)) {
+ /* Only break if the event was handled. */
+ break;
+ }
+ }
+ }
+
#ifdef WITH_INPUT_IME
/* skips button handling since it is not wanted */
if (is_ime_composing) {
@@ -3587,7 +3605,7 @@ static void ui_do_but_textedit(
#ifdef USE_KEYNAV_LIMIT
ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
#endif
- ui_searchbox_event(C, data->searchbox, but, event);
+ ui_searchbox_event(C, data->searchbox, but, data->region, event);
break;
}
if (event->type == WHEELDOWNMOUSE) {
@@ -3604,7 +3622,7 @@ static void ui_do_but_textedit(
#ifdef USE_KEYNAV_LIMIT
ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
#endif
- ui_searchbox_event(C, data->searchbox, but, event);
+ ui_searchbox_event(C, data->searchbox, but, data->region, event);
break;
}
if (event->type == WHEELUPMOUSE) {
@@ -3670,6 +3688,32 @@ static void ui_do_but_textedit(
}
retval = WM_UI_HANDLER_BREAK;
break;
+ case EVT_ZKEY: {
+ /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */
+
+ const bool is_redo = (event->shift != 0);
+ if (
+#if defined(__APPLE__)
+ (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) ||
+#endif
+ (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) {
+ int undo_pos;
+ const char *undo_str = ui_textedit_undo(
+ data->undo_stack_text, is_redo ? 1 : -1, &undo_pos);
+ if (undo_str != NULL) {
+ ui_textedit_string_set(but, data, undo_str);
+
+ /* Set the cursor & clear selection. */
+ but->pos = undo_pos;
+ but->selsta = but->pos;
+ but->selend = but->pos;
+ changed = true;
+ }
+ retval = WM_UI_HANDLER_BREAK;
+ skip_undo_push = true;
+ }
+ break;
+ }
}
if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)
@@ -3723,6 +3767,11 @@ static void ui_do_but_textedit(
#endif
if (changed) {
+ /* The undo stack may be NULL if an event exits editing. */
+ if ((skip_undo_push == false) && (data->undo_stack_text != NULL)) {
+ ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos);
+ }
+
/* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */
if (update && data->interactive) {
ui_apply_but(C, block, but, data, true);
@@ -4397,7 +4446,8 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons
do_activate = (event->val == KM_RELEASE);
}
else {
- do_activate = (event->val == KM_PRESS);
+ /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */
+ do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK);
}
}
@@ -8213,11 +8263,22 @@ static uiBut *ui_context_rna_button_active(const bContext *C)
return ui_context_button_active(CTX_wm_region(C), ui_context_rna_button_active_test);
}
-uiBut *UI_context_active_but_get(const struct bContext *C)
+uiBut *UI_context_active_but_get(const bContext *C)
{
return ui_context_button_active(CTX_wm_region(C), NULL);
}
+/*
+ * Version of #UI_context_active_get() that uses the result of #CTX_wm_menu()
+ * if set. Does not traverse into parent menus, which may be wanted in some
+ * cases.
+ */
+uiBut *UI_context_active_but_get_respect_menu(const bContext *C)
+{
+ ARegion *ar_menu = CTX_wm_menu(C);
+ return ui_context_button_active(ar_menu ? ar_menu : CTX_wm_region(C), NULL);
+}
+
uiBut *UI_region_active_but_get(ARegion *region)
{
return ui_context_button_active(region, NULL);
@@ -8814,7 +8875,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
* This is needed to make sure if a button was active,
* it stays active while the mouse is over it.
* This avoids adding mousemoves, see: [#33466] */
- if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT)) {
+ if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT, BUTTON_STATE_WAIT_DRAG)) {
if (ui_but_find_mouse_over(region, event) == but) {
button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER);
}
@@ -9339,6 +9400,11 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock
if (event->val == KM_RELEASE) {
/* pass, needed so we can exit active menu-items when click-dragging out of them */
}
+ else if (but->type == UI_BTYPE_SEARCH_MENU) {
+ /* Pass, needed so search popup can have RMB context menu.
+ * This may be useful for other interactions which happen in the search popup
+ * without being directly over the search button. */
+ }
else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) {
/* pass, skip for dialogs */
}
@@ -10772,9 +10838,6 @@ static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata)
if (temp.popup_func) {
temp.popup_func(C, temp.popup_arg, temp.retvalue);
}
- if (temp.optype) {
- WM_operator_name_call_ptr(C, temp.optype, temp.opcontext, NULL);
- }
}
else if (temp.cancel_func) {
temp.cancel_func(C, temp.popup_arg);
@@ -10938,8 +11001,7 @@ void UI_screen_free_active_but(const bContext *C, bScreen *screen)
{
wmWindow *win = CTX_wm_window(C);
- ED_screen_areas_iter(win, screen, area)
- {
+ ED_screen_areas_iter (win, screen, area) {
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
uiBut *but = ui_region_find_active_but(region);
if (but) {
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index 9f9e04eea82..c94a95890c0 100644
--- a/source/blender/editors/interface/interface_icons.c
+++ b/source/blender/editors/interface/interface_icons.c
@@ -2320,6 +2320,9 @@ int UI_idcode_icon_get(const int idcode)
return ICON_WORLD_DATA;
case ID_WS:
return ICON_WORKSPACE;
+ case ID_SIM:
+ /* TODO: Use correct icon. */
+ return ICON_PHYSICS;
default:
return ICON_NONE;
}
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 3ded62dca7f..3748dbab519 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -41,6 +41,7 @@ struct bContextStore;
struct uiHandleButtonData;
struct uiLayout;
struct uiStyle;
+struct uiUndoStack_Text;
struct uiWidgetColors;
struct wmEvent;
struct wmKeyConfig;
@@ -147,6 +148,17 @@ enum {
/* max amount of items a radial menu (pie menu) can contain */
#define PIE_MAX_ITEMS 8
+struct uiButSearchData {
+ uiButSearchCreateFn create_fn;
+ uiButSearchUpdateFn update_fn;
+ void *arg;
+ uiButSearchArgFreeFn arg_free_fn;
+ uiButSearchContextMenuFn context_menu_fn;
+ uiButSearchTooltipFn tooltip_fn;
+
+ const char *sep_string;
+};
+
struct uiBut {
struct uiBut *next, *prev;
int flag, drawflag;
@@ -203,11 +215,7 @@ struct uiBut {
uiButCompleteFunc autocomplete_func;
void *autofunc_arg;
- uiButSearchCreateFunc search_create_func;
- uiButSearchFunc search_func;
- void *search_arg;
- uiButSearchArgFreeFunc search_arg_free_func;
- const char *search_sep_string;
+ struct uiButSearchData *search;
uiButHandleRenameFunc rename_func;
void *rename_arg1;
@@ -601,10 +609,8 @@ struct uiPopupBlockHandle {
/* for operator popups */
struct wmOperator *popup_op;
- struct wmOperatorType *optype;
struct ScrArea *ctx_area;
struct ARegion *ctx_region;
- int opcontext;
/* return values */
int butretval;
@@ -666,9 +672,10 @@ bool ui_searchbox_inside(struct ARegion *region, int x, int y);
int ui_searchbox_find_index(struct ARegion *region, const char *name);
void ui_searchbox_update(struct bContext *C, struct ARegion *region, uiBut *but, const bool reset);
int ui_searchbox_autocomplete(struct bContext *C, struct ARegion *region, uiBut *but, char *str);
-void ui_searchbox_event(struct bContext *C,
+bool ui_searchbox_event(struct bContext *C,
struct ARegion *region,
uiBut *but,
+ struct ARegion *butregion,
const struct wmEvent *event);
bool ui_searchbox_apply(uiBut *but, struct ARegion *region);
void ui_searchbox_free(struct bContext *C, struct ARegion *region);
@@ -709,7 +716,7 @@ uiPopupBlockHandle *ui_popover_panel_create(struct bContext *C,
void ui_pie_menu_level_create(uiBlock *block,
struct wmOperatorType *ot,
const char *propname,
- IDProperty *properties,
+ struct IDProperty *properties,
const EnumPropertyItem *items,
int totitem,
int context,
@@ -774,6 +781,16 @@ void ui_draw_but_TRACKPREVIEW(struct ARegion *region,
const struct uiWidgetColors *wcol,
const rcti *rect);
+/* interface_undo.c */
+struct uiUndoStack_Text *ui_textedit_undo_stack_create(void);
+void ui_textedit_undo_stack_destroy(struct uiUndoStack_Text *undo_stack);
+void ui_textedit_undo_push(struct uiUndoStack_Text *undo_stack,
+ const char *text,
+ int cursor_index);
+const char *ui_textedit_undo(struct uiUndoStack_Text *undo_stack,
+ int direction,
+ int *r_cursor_index);
+
/* interface_handlers.c */
PointerRNA *ui_handle_afterfunc_add_operator(struct wmOperatorType *ot,
int opcontext,
@@ -800,6 +817,11 @@ float ui_block_calc_pie_segment(struct uiBlock *block, const float event_xy[2]);
void ui_but_add_shortcut(uiBut *but, const char *key_str, const bool do_strip);
void ui_but_clipboard_free(void);
+bool ui_but_rna_equals(const uiBut *a, const uiBut *b);
+bool ui_but_rna_equals_ex(const uiBut *but,
+ const PointerRNA *ptr,
+ const PropertyRNA *prop,
+ int index);
uiBut *ui_but_find_old(uiBlock *block_old, const uiBut *but_new);
uiBut *ui_but_find_new(uiBlock *block_old, const uiBut *but_new);
@@ -869,7 +891,7 @@ void ui_draw_menu_item(const struct uiFontStyle *fstyle,
int iconid,
int state,
bool use_sep,
- int *r_name_width);
+ int *r_xmax);
void ui_draw_preview_item(
const struct uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int state);
@@ -1023,9 +1045,10 @@ void UI_OT_eyedropper_driver(struct wmOperatorType *ot);
void UI_OT_eyedropper_gpencil_color(struct wmOperatorType *ot);
/* interface_util.c */
+bool ui_str_has_word_prefix(const char *haystack, const char *needle, size_t needle_len);
/**
- * For use with #ui_rna_collection_search_cb.
+ * For use with #ui_rna_collection_search_update_fn.
*/
typedef struct uiRNACollectionSearch {
PointerRNA target_ptr;
@@ -1040,10 +1063,10 @@ typedef struct uiRNACollectionSearch {
/* Block has to be stored for freeing butstore (uiBut.block doesn't work with undo). */
uiBlock *butstore_block;
} uiRNACollectionSearch;
-void ui_rna_collection_search_cb(const struct bContext *C,
- void *arg,
- const char *str,
- uiSearchItems *items);
+void ui_rna_collection_search_update_fn(const struct bContext *C,
+ void *arg,
+ const char *str,
+ uiSearchItems *items);
/* interface_ops.c */
bool ui_jump_to_target_button_poll(struct bContext *C);
diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c
index cbea32f179a..884e43b4026 100644
--- a/source/blender/editors/interface/interface_layout.c
+++ b/source/blender/editors/interface/interface_layout.c
@@ -75,7 +75,7 @@
} \
(void)0
-#define UI_ITEM_PROP_SEP_DIVIDE 0.5f
+#define UI_ITEM_PROP_SEP_DIVIDE 0.4f
/* uiLayoutRoot */
@@ -135,10 +135,11 @@ enum {
UI_ITEM_BOX_ITEM = 1 << 2, /* The item is "inside" a box item */
UI_ITEM_PROP_SEP = 1 << 3,
+ UI_ITEM_INSIDE_PROP_SEP = 1 << 4,
/* Show an icon button next to each property (to set keyframes, show status).
* Enabled by default, depends on 'UI_ITEM_PROP_SEP'. */
- UI_ITEM_PROP_DECORATE = 1 << 4,
- UI_ITEM_PROP_DECORATE_NO_PAD = 1 << 5,
+ UI_ITEM_PROP_DECORATE = 1 << 5,
+ UI_ITEM_PROP_DECORATE_NO_PAD = 1 << 6,
};
typedef struct uiButtonItem {
@@ -151,8 +152,11 @@ struct uiLayout {
uiLayoutRoot *root;
bContextStore *context;
+ uiLayout *parent;
ListBase items;
+ char heading[UI_MAX_NAME_STR];
+
/** Sub layout to add child items, if not the layout itself. */
uiLayout *child_items_layout;
@@ -1780,6 +1784,7 @@ static void ui_item_rna_size(uiLayout *layout,
PropertyType type;
PropertySubType subtype;
int len, w = 0, h;
+ bool is_checkbox_only = false;
/* arbitrary extended width by type */
type = RNA_property_type(prop);
@@ -1791,6 +1796,10 @@ static void ui_item_rna_size(uiLayout *layout,
name = "non-empty text";
}
else if (type == PROP_BOOLEAN) {
+ if (icon == ICON_NONE) {
+ /* Exception for checkboxes, they need a little less space to align nicely. */
+ is_checkbox_only = true;
+ }
icon = ICON_DOT;
}
else if (type == PROP_ENUM) {
@@ -1850,6 +1859,9 @@ static void ui_item_rna_size(uiLayout *layout,
if (type == PROP_BOOLEAN && name[0]) {
w += UI_UNIT_X / 5;
}
+ else if (is_checkbox_only) {
+ w -= UI_UNIT_X / 4;
+ }
else if (type == PROP_ENUM && !icon_only) {
w += UI_UNIT_X / 4;
}
@@ -1862,6 +1874,57 @@ static void ui_item_rna_size(uiLayout *layout,
*r_h = h;
}
+static bool ui_item_rna_is_expand(PropertyRNA *prop, int index, int item_flag)
+{
+ const bool is_array = RNA_property_array_check(prop);
+ const int subtype = RNA_property_subtype(prop);
+ return is_array && (index == RNA_NO_INDEX) &&
+ ((item_flag & UI_ITEM_R_EXPAND) ||
+ !ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA, PROP_DIRECTION));
+}
+
+/**
+ * Find first layout ancestor (or self) with a heading set.
+ *
+ * \returns the layout to add the heading to as fallback (i.e. if it can't be placed in a split
+ * layout). Its #uiLayout.heading member can be cleared to mark the heading as added (so
+ * it's not added multiple times). Returns a pointer to the heading
+ */
+static uiLayout *ui_layout_heading_find(uiLayout *cur_layout)
+{
+ for (uiLayout *parent = cur_layout; parent; parent = parent->parent) {
+ if (parent->heading[0]) {
+ return parent;
+ }
+ }
+
+ return NULL;
+}
+
+static void ui_layout_heading_label_add(uiLayout *layout,
+ uiLayout *heading_layout,
+ bool right_align,
+ bool respect_prop_split)
+{
+ const int prev_alignment = layout->alignment;
+
+ if (right_align) {
+ uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_RIGHT);
+ }
+
+ if (respect_prop_split) {
+ uiItemL_respect_property_split(layout, heading_layout->heading, ICON_NONE);
+ }
+ else {
+ uiItemL(layout, heading_layout->heading, ICON_NONE);
+ }
+ /* After adding the heading label, we have to mark it somehow as added, so it's not added again
+ * for other items in this layout. For now just clear it. */
+ heading_layout->heading[0] = '\0';
+
+ layout->alignment = prev_alignment;
+}
+
/**
* Hack to add further items in a row into the second part of the split layout, so the label part
* keeps a fixed size.
@@ -1869,7 +1932,14 @@ static void ui_item_rna_size(uiLayout *layout,
*/
static uiLayout *ui_item_prop_split_layout_hack(uiLayout *layout_parent, uiLayout *layout_split)
{
+ /* Tag item as using property split layout, this is inherited to children so they can get special
+ * treatment if needed. */
+ layout_parent->item.flag |= UI_ITEM_INSIDE_PROP_SEP;
+
if (layout_parent->item.type == ITEM_LAYOUT_ROW) {
+ /* Prevent further splits within the row. */
+ uiLayoutSetPropSep(layout_parent, false);
+
layout_parent->child_items_layout = uiLayoutRow(layout_split, true);
return layout_parent->child_items_layout;
}
@@ -1888,13 +1958,18 @@ void uiItemFullR(uiLayout *layout,
uiBlock *block = layout->root->block;
char namestr[UI_MAX_NAME_STR];
const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0);
-
- /* By default 'use_prop_sep' uses a separate column for labels.
- * This is an exception for check-boxes otherwise only the small checkbox region is clickable.
+ const bool inside_prop_sep = ((layout->item.flag & UI_ITEM_INSIDE_PROP_SEP) != 0);
+ /* Columns can define a heading to insert. If the first item added to a split layout doesn't have
+ * a label to display in the first column, the heading is inserted there. Otherwise it's inserted
+ * as a new row before the first item. */
+ uiLayout *heading_layout = ui_layout_heading_find(layout);
+ /* Although checkboxes use the split layout, they are an exception and should only place their
+ * label in the second column, to not make that almost empty.
*
* Keep using 'use_prop_sep' instead of disabling it entirely because
* we need the ability to have decorators still. */
bool use_prop_sep_split_label = use_prop_sep;
+ bool use_split_empty_name = (flag & UI_ITEM_R_SPLIT_EMPTY_NAME);
#ifdef UI_PROP_DECORATE
struct {
@@ -2005,6 +2080,9 @@ void uiItemFullR(uiLayout *layout,
if (use_prop_sep) {
if (type == PROP_BOOLEAN && (icon == ICON_NONE) && !icon_only) {
use_prop_sep_split_label = false;
+ /* For check-boxes we make an exception: We allow showing them in a split row even without
+ * label. It typically relates to its neighbor items, so no need for an extra label. */
+ use_split_empty_name = true;
}
}
#endif
@@ -2031,6 +2109,7 @@ void uiItemFullR(uiLayout *layout,
/* Split the label / property. */
uiLayout *layout_parent = layout;
+
if (use_prop_sep) {
uiLayout *layout_row = NULL;
#ifdef UI_PROP_DECORATE
@@ -2041,21 +2120,26 @@ void uiItemFullR(uiLayout *layout,
}
#endif /* UI_PROP_DECORATE */
- if ((name[0] == '\0') || (use_prop_sep_split_label == false)) {
+ if ((name[0] == '\0') && !use_split_empty_name) {
/* Ensure we get a column when text is not set. */
layout = uiLayoutColumn(layout_row ? layout_row : layout, true);
layout->space = 0;
+ if (heading_layout) {
+ ui_layout_heading_label_add(layout, heading_layout, false, false);
+ }
}
else {
- const PropertySubType subtype = RNA_property_subtype(prop);
uiLayout *layout_split = uiLayoutSplit(
layout_row ? layout_row : layout, UI_ITEM_PROP_SEP_DIVIDE, true);
+ bool label_added = false;
layout_split->space = 0;
uiLayout *layout_sub = uiLayoutColumn(layout_split, true);
layout_sub->space = 0;
- if ((index == RNA_NO_INDEX && is_array) &&
- ((!expand && ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA, PROP_DIRECTION)) == 0)) {
+ if (!use_prop_sep_split_label) {
+ /* Pass */
+ }
+ else if (ui_item_rna_is_expand(prop, index, flag)) {
char name_with_suffix[UI_MAX_DRAW_STR + 2];
char str[2] = {'\0'};
for (int a = 0; a < len; a++) {
@@ -2084,6 +2168,8 @@ void uiItemFullR(uiLayout *layout,
"");
but->drawflag |= UI_BUT_TEXT_RIGHT;
but->drawflag &= ~UI_BUT_TEXT_LEFT;
+
+ label_added = true;
}
}
else {
@@ -2092,13 +2178,17 @@ void uiItemFullR(uiLayout *layout,
block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, "");
but->drawflag |= UI_BUT_TEXT_RIGHT;
but->drawflag &= ~UI_BUT_TEXT_LEFT;
+
+ label_added = true;
}
}
- if (layout_parent) {
- layout_split = ui_item_prop_split_layout_hack(layout_parent, layout_split);
+ if (!label_added && heading_layout) {
+ ui_layout_heading_label_add(layout_sub, heading_layout, true, false);
}
+ layout_split = ui_item_prop_split_layout_hack(layout_parent, layout_split);
+
/* Watch out! We can only write into the new layout now. */
if ((type == PROP_ENUM) && (flag & UI_ITEM_R_EXPAND)) {
/* Expanded enums each have their own name. */
@@ -2113,7 +2203,9 @@ void uiItemFullR(uiLayout *layout,
}
}
else {
- name = "";
+ if (use_prop_sep_split_label) {
+ name = "";
+ }
layout = uiLayoutColumn(layout_split, true);
}
layout->space = 0;
@@ -2132,9 +2224,20 @@ void uiItemFullR(uiLayout *layout,
#endif /* UI_PROP_DECORATE */
}
/* End split. */
+ else if (heading_layout) {
+ /* Could not add heading to split layout, fallback to inserting it to the layout with the
+ * heading itself. */
+ ui_layout_heading_label_add(heading_layout, heading_layout, false, false);
+ }
/* array property */
if (index == RNA_NO_INDEX && is_array) {
+ if (inside_prop_sep) {
+ /* Within a split row, add array items to a column so they match the column layout of
+ * previous items (e.g. transform vector with lock icon for each item). */
+ layout = uiLayoutColumn(layout, true);
+ }
+
ui_item_array(layout,
block,
name,
@@ -2214,12 +2317,6 @@ void uiItemFullR(uiLayout *layout,
if (layout->activate_init) {
UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT);
}
-
- if (use_prop_sep && (use_prop_sep_split_label == false)) {
- /* When the button uses it's own text right align it. */
- but->drawflag |= UI_BUT_TEXT_RIGHT;
- but->drawflag &= ~UI_BUT_TEXT_LEFT;
- }
}
/* The resulting button may have the icon set since boolean button drawing
@@ -2242,50 +2339,21 @@ void uiItemFullR(uiLayout *layout,
#ifdef UI_PROP_DECORATE
if (ui_decorate.use_prop_decorate) {
- const bool is_anim = RNA_property_animateable(ptr, prop);
uiBut *but_decorate = ui_decorate.but ? ui_decorate.but->next : block->buttons.first;
+ const bool use_blank_decorator = (flag & UI_ITEM_R_FORCE_BLANK_DECORATE);
uiLayout *layout_col = uiLayoutColumn(ui_decorate.layout, false);
layout_col->space = 0;
layout_col->emboss = UI_EMBOSS_NONE;
+
int i;
for (i = 0; i < ui_decorate.len && but_decorate; i++) {
+ PointerRNA *ptr_dec = use_blank_decorator ? NULL : &but_decorate->rnapoin;
+ PropertyRNA *prop_dec = use_blank_decorator ? NULL : but_decorate->rnaprop;
+
/* The icons are set in 'ui_but_anim_flag' */
- if (is_anim) {
- but = uiDefIconBut(block,
- UI_BTYPE_BUT,
- 0,
- ICON_DOT,
- 0,
- 0,
- UI_UNIT_X,
- UI_UNIT_Y,
- NULL,
- 0.0,
- 0.0,
- 0.0,
- 0.0,
- TIP_("Animate property"));
- UI_but_func_set(but, ui_but_anim_decorate_cb, but, NULL);
- but->flag |= UI_BUT_UNDO | UI_BUT_DRAG_LOCK;
- }
- else {
- /* We may show other information here in future, for now use empty space. */
- but = uiDefIconBut(block,
- UI_BTYPE_BUT,
- 0,
- ICON_BLANK1,
- 0,
- 0,
- UI_UNIT_X,
- UI_UNIT_Y,
- NULL,
- 0.0,
- 0.0,
- 0.0,
- 0.0,
- "");
- but->flag |= UI_BUT_DISABLED;
- }
+ uiItemDecoratorR_prop(layout_col, ptr_dec, prop_dec, but_decorate->rnaindex);
+ but = block->buttons.last;
+
/* Order the decorator after the button we decorate, this is used so we can always
* do a quick lookup. */
BLI_remlink(&block->buttons, but);
@@ -2577,7 +2645,7 @@ static void search_id_collection(StructRNA *ptype, PointerRNA *r_ptr, PropertyRN
RNA_STRUCT_END;
}
-static void ui_rna_collection_search_free_cb(void *ptr)
+static void ui_rna_collection_search_arg_free_fn(void *ptr)
{
uiRNACollectionSearch *coll_search = ptr;
UI_butstore_free(coll_search->butstore_block, coll_search->butstore);
@@ -2629,10 +2697,9 @@ void ui_but_add_search(
UI_but_func_search_set(but,
ui_searchbox_create_generic,
- ui_rna_collection_search_cb,
+ ui_rna_collection_search_update_fn,
coll_search,
- ui_rna_collection_search_free_cb,
- NULL,
+ ui_rna_collection_search_arg_free_fn,
NULL,
NULL);
}
@@ -2759,6 +2826,7 @@ static uiBut *ui_item_menu(uiLayout *layout,
bool force_menu)
{
uiBlock *block = layout->root->block;
+ uiLayout *heading_layout = ui_layout_heading_find(layout);
uiBut *but;
int w, h;
@@ -2788,6 +2856,10 @@ static uiBut *ui_item_menu(uiLayout *layout,
}
}
+ if (heading_layout) {
+ ui_layout_heading_label_add(layout, heading_layout, true, true);
+ }
+
if (name[0] && icon) {
but = uiDefIconTextMenuBut(block, func, arg, icon, name, 0, 0, w, h, tip);
}
@@ -2861,6 +2933,91 @@ void uiItemMContents(uiLayout *layout, const char *menuname)
UI_menutype_draw(C, mt, layout);
}
+/**
+ * Insert a decorator item for a button with the same property as \a prop.
+ * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a prop.
+ */
+void uiItemDecoratorR_prop(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop, int index)
+{
+ uiBlock *block = layout->root->block;
+ uiBut *but = NULL;
+
+ uiLayout *col;
+ UI_block_layout_set_current(block, layout);
+ col = uiLayoutColumn(layout, false);
+ col->space = 0;
+ col->emboss = UI_EMBOSS_NONE;
+
+ if (ELEM(NULL, ptr, prop) || !RNA_property_animateable(ptr, prop)) {
+ but = uiDefIconBut(block,
+ UI_BTYPE_BUT,
+ 0,
+ ICON_BLANK1,
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ NULL,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ "");
+ but->flag |= UI_BUT_DISABLED;
+ return;
+ }
+
+ const bool is_expand = ui_item_rna_is_expand(prop, index, 0);
+ const bool is_array = RNA_property_array_check(prop);
+
+ /* Loop for the array-case, but only do in case of an expanded array. */
+ for (int i = 0; i < (is_expand ? RNA_property_array_length(ptr, prop) : 1); i++) {
+ but = uiDefIconBut(block,
+ UI_BTYPE_BUT,
+ 0,
+ ICON_DOT,
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ NULL,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ TIP_("Animate property"));
+ UI_but_func_set(but, ui_but_anim_decorate_cb, but, NULL);
+ but->flag |= UI_BUT_UNDO | UI_BUT_DRAG_LOCK;
+ /* Reusing RNA search members, setting actual RNA data has many side-effects. */
+ but->rnasearchpoin = *ptr;
+ but->rnasearchprop = prop;
+ /* ui_def_but_rna() sets non-array buttons to have a RNA index of 0. */
+ but->custom_data = POINTER_FROM_INT((!is_array || is_expand) ? i : index);
+ }
+}
+
+/**
+ * Insert a decorator item for a button with the same property as \a prop.
+ * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a propname.
+ */
+void uiItemDecoratorR(uiLayout *layout, PointerRNA *ptr, const char *propname, int index)
+{
+ PropertyRNA *prop = NULL;
+
+ if (ptr && propname) {
+ /* validate arguments */
+ prop = RNA_struct_find_property(ptr, propname);
+ if (!prop) {
+ ui_item_disabled(layout, propname);
+ RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname);
+ return;
+ }
+ }
+
+ /* ptr and prop are allowed to be NULL here. */
+ uiItemDecoratorR_prop(layout, ptr, prop, index);
+}
+
/* popover */
void uiItemPopoverPanel_ptr(
uiLayout *layout, bContext *C, PanelType *pt, const char *name, int icon)
@@ -3004,33 +3161,40 @@ void uiItemL(uiLayout *layout, const char *name, int icon)
}
/**
- * Helper to add a label, which handles logic for split property layout if needed.
- *
- * Normally, we handle the split layout in #uiItemFullR(), but there are other cases where we may
- * want to use the logic. For those this helper was added, although it will likely have to be
- * extended to support more cases.
- * Ideally, #uiItemFullR() could just call this, but it currently has too many special needs.
- *
- * \return A layout placed in the row after the split layout. Used to place decorator items.
+ * Normally, we handle the split layout in #uiItemFullR(), but there are other cases where the
+ * logic is needed. Ideally, #uiItemFullR() could just call this, but it currently has too many
+ * special needs.
+ */
+uiPropertySplitWrapper uiItemPropertySplitWrapperCreate(uiLayout *parent_layout)
+{
+ uiPropertySplitWrapper split_wrapper = {NULL};
+
+ uiLayout *layout_row = uiLayoutRow(parent_layout, true);
+ uiLayout *layout_split = uiLayoutSplit(layout_row, UI_ITEM_PROP_SEP_DIVIDE, true);
+
+ layout_split->space = 0;
+ split_wrapper.label_column = uiLayoutColumn(layout_split, true);
+ split_wrapper.label_column->alignment = UI_LAYOUT_ALIGN_RIGHT;
+ split_wrapper.property_row = ui_item_prop_split_layout_hack(parent_layout, layout_split);
+ split_wrapper.decorate_column = uiLayoutColumn(layout_row, true);
+
+ return split_wrapper;
+}
+
+/*
+ * Helper to add a label and creates a property split layout if needed.
*/
uiLayout *uiItemL_respect_property_split(uiLayout *layout, const char *text, int icon)
{
if (layout->item.flag & UI_ITEM_PROP_SEP) {
uiBlock *block = uiLayoutGetBlock(layout);
- uiLayout *layout_row = uiLayoutRow(layout, true);
- uiLayout *layout_split = uiLayoutSplit(layout_row, UI_ITEM_PROP_SEP_DIVIDE, true);
- uiLayout *layout_sub = uiLayoutColumn(layout_split, true);
-
- layout_split->space = layout_sub->space = layout_row->space = 0;
- layout_sub->alignment = UI_LAYOUT_ALIGN_RIGHT;
+ uiPropertySplitWrapper split_wrapper = uiItemPropertySplitWrapperCreate(layout);
+ /* Further items added to 'layout' will automatically be added to split_wrapper.property_row */
- uiItemL_(layout_sub, text, icon);
+ uiItemL_(split_wrapper.label_column, text, icon);
+ UI_block_layout_set_current(block, split_wrapper.property_row);
- layout_split = ui_item_prop_split_layout_hack(layout, layout_split);
- UI_block_layout_set_current(block, layout_split);
-
- /* Give caller a new sub-row to place items in. */
- return layout_row;
+ return split_wrapper.decorate_column;
}
else {
char namestr[UI_MAX_NAME_STR];
@@ -4471,13 +4635,24 @@ static void ui_litem_init_from_parent(uiLayout *litem, uiLayout *layout, int ali
litem->redalert = layout->redalert;
litem->w = layout->w;
litem->emboss = layout->emboss;
- litem->item.flag = (layout->item.flag & (UI_ITEM_PROP_SEP | UI_ITEM_PROP_DECORATE));
+ litem->item.flag = (layout->item.flag &
+ (UI_ITEM_PROP_SEP | UI_ITEM_PROP_DECORATE | UI_ITEM_INSIDE_PROP_SEP));
if (layout->child_items_layout) {
BLI_addtail(&layout->child_items_layout->items, litem);
+ litem->parent = layout->child_items_layout;
}
else {
BLI_addtail(&layout->items, litem);
+ litem->parent = layout;
+ }
+}
+
+static void ui_layout_heading_set(uiLayout *layout, const char *heading)
+{
+ BLI_assert(layout->heading[0] == '\0');
+ if (heading) {
+ STRNCPY(layout->heading, heading);
}
}
@@ -4497,6 +4672,16 @@ uiLayout *uiLayoutRow(uiLayout *layout, bool align)
return litem;
}
+/**
+ * See #uiLayoutColumnWithHeading().
+ */
+uiLayout *uiLayoutRowWithHeading(uiLayout *layout, bool align, const char *heading)
+{
+ uiLayout *litem = uiLayoutRow(layout, align);
+ ui_layout_heading_set(litem, heading);
+ return litem;
+}
+
uiLayout *uiLayoutColumn(uiLayout *layout, bool align)
{
uiLayout *litem;
@@ -4512,6 +4697,19 @@ uiLayout *uiLayoutColumn(uiLayout *layout, bool align)
return litem;
}
+/**
+ * Variant of #uiLayoutColumn() that sets a heading label for the layout if the first item is
+ * added through #uiItemFullR(). If split layout is used and the item has no string to add to the
+ * first split-column, the heading is added there instead. Otherwise the heading inserted with a
+ * new row.
+ */
+uiLayout *uiLayoutColumnWithHeading(uiLayout *layout, bool align, const char *heading)
+{
+ uiLayout *litem = uiLayoutColumn(layout, align);
+ ui_layout_heading_set(litem, heading);
+ return litem;
+}
+
uiLayout *uiLayoutColumnFlow(uiLayout *layout, int number, bool align)
{
uiLayoutItemFlow *flow;
diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c
index 53ea51c9e97..909da434554 100644
--- a/source/blender/editors/interface/interface_ops.c
+++ b/source/blender/editors/interface/interface_ops.c
@@ -1135,8 +1135,9 @@ static bool jump_to_target_button(bContext *C, bool poll)
else if (type == PROP_STRING) {
const uiBut *but = UI_context_active_but_get(C);
- if (but->type == UI_BTYPE_SEARCH_MENU && but->search_func == ui_rna_collection_search_cb) {
- uiRNACollectionSearch *coll_search = but->search_arg;
+ if (but->type == UI_BTYPE_SEARCH_MENU && but->search &&
+ but->search->update_fn == ui_rna_collection_search_update_fn) {
+ uiRNACollectionSearch *coll_search = but->search->arg;
char str_buf[MAXBONENAME];
char *str_ptr = RNA_property_string_get_alloc(&ptr, prop, str_buf, sizeof(str_buf), NULL);
@@ -1678,7 +1679,7 @@ static void UI_OT_button_execute(wmOperatorType *ot)
static int button_string_clear_exec(bContext *C, wmOperator *UNUSED(op))
{
- uiBut *but = UI_context_active_but_get(C);
+ uiBut *but = UI_context_active_but_get_respect_menu(C);
if (but) {
ui_but_active_string_clear_and_exit(C, but);
diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c
index 2ad1c25305c..04179721305 100644
--- a/source/blender/editors/interface/interface_panel.c
+++ b/source/blender/editors/interface/interface_panel.c
@@ -102,6 +102,7 @@ typedef struct uiHandlePanelData {
double starttime;
/* dragging */
+ bool is_drag_drop;
int startx, starty;
int startofsx, startofsy;
int startsizex, startsizey;
@@ -743,7 +744,7 @@ void ui_draw_aligned_panel(uiStyle *style,
/* an open panel */
else {
/* in some occasions, draw a border */
- if (panel->flag & PNL_SELECT) {
+ if (panel->flag & PNL_SELECT && !is_subpanel) {
if (panel->control & UI_PNL_SOLID) {
UI_draw_roundbox_corner_set(UI_CNR_ALL);
}
@@ -876,6 +877,16 @@ static int get_panel_real_ofsx(Panel *panel)
}
}
+bool UI_panel_is_dragging(const struct Panel *panel)
+{
+ uiHandlePanelData *data = panel->activedata;
+ if (!data) {
+ return false;
+ }
+
+ return data->is_drag_drop;
+}
+
typedef struct PanelSort {
Panel *panel, *orig;
} PanelSort;
@@ -2458,6 +2469,24 @@ static void ui_handler_remove_panel(bContext *C, void *userdata)
panel_activate_state(C, panel, PANEL_STATE_EXIT);
}
+/**
+ * Set selection state for a panel and its subpanels. The subpanels need to know they are selected
+ * too so they can be drawn above their parent when it is dragged.
+ */
+static void set_panel_selection(Panel *panel, bool value)
+{
+ if (value) {
+ panel->flag |= PNL_SELECT;
+ }
+ else {
+ panel->flag &= ~PNL_SELECT;
+ }
+
+ LISTBASE_FOREACH (Panel *, child, &panel->children) {
+ set_panel_selection(child, value);
+ }
+}
+
static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state)
{
uiHandlePanelData *data = panel->activedata;
@@ -2468,6 +2497,8 @@ static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelS
return;
}
+ bool was_drag_drop = (data && data->state == PANEL_STATE_DRAG);
+
if (state == PANEL_STATE_EXIT || state == PANEL_STATE_ANIMATION) {
if (data && data->state != PANEL_STATE_ANIMATION) {
/* XXX:
@@ -2480,10 +2511,10 @@ static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelS
check_panel_overlap(region, NULL); /* clears */
}
- panel->flag &= ~PNL_SELECT;
+ set_panel_selection(panel, false);
}
else {
- panel->flag |= PNL_SELECT;
+ set_panel_selection(panel, true);
}
if (data && data->animtimer) {
@@ -2519,6 +2550,12 @@ static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelS
data->startsizex = panel->sizex;
data->startsizey = panel->sizey;
data->starttime = PIL_check_seconds_timer();
+
+ /* Remember drag drop state even when animating to the aligned position after dragging. */
+ data->is_drag_drop = was_drag_drop;
+ if (state == PANEL_STATE_DRAG) {
+ data->is_drag_drop = true;
+ }
}
ED_region_tag_redraw(region);
diff --git a/source/blender/editors/interface/interface_region_menu_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c
index 31ef7261eb3..3e34b7f3f8a 100644
--- a/source/blender/editors/interface/interface_region_menu_popup.c
+++ b/source/blender/editors/interface/interface_region_menu_popup.c
@@ -581,21 +581,18 @@ int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports)
/** \name Popup Block API
* \{ */
-void UI_popup_block_invoke_ex(bContext *C,
- uiBlockCreateFunc func,
- void *arg,
- void (*arg_free)(void *arg),
- const char *opname,
- int opcontext)
+void UI_popup_block_invoke_ex(
+ bContext *C, uiBlockCreateFunc func, void *arg, void (*arg_free)(void *arg), bool can_refresh)
{
wmWindow *window = CTX_wm_window(C);
uiPopupBlockHandle *handle;
handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg, arg_free);
handle->popup = true;
- handle->can_refresh = true;
- handle->optype = (opname) ? WM_operatortype_find(opname, 0) : NULL;
- handle->opcontext = opcontext;
+
+ /* It can be useful to disable refresh (even though it will work)
+ * as this exists text fields which can be disruptive if refresh isn't needed. */
+ handle->can_refresh = can_refresh;
UI_popup_handlers_add(C, &window->modalhandlers, handle, 0);
UI_block_active_only_flagged_buttons(C, handle->region, handle->region->uiblocks.first);
@@ -607,7 +604,7 @@ void UI_popup_block_invoke(bContext *C,
void *arg,
void (*arg_free)(void *arg))
{
- UI_popup_block_invoke_ex(C, func, arg, arg_free, NULL, WM_OP_INVOKE_DEFAULT);
+ UI_popup_block_invoke_ex(C, func, arg, arg_free, true);
}
void UI_popup_block_ex(bContext *C,
diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c
index e2c87891169..0007f6ab9a2 100644
--- a/source/blender/editors/interface/interface_region_search.c
+++ b/source/blender/editors/interface/interface_region_search.c
@@ -35,6 +35,7 @@
#include "BLI_math.h"
+#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
@@ -153,7 +154,8 @@ bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int
/* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set
* which will cause problems, add others as needed. */
- BLI_assert((state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT)) == 0);
+ BLI_assert(
+ (state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)) == 0);
if (items->states) {
items->states[items->totitem] = state;
}
@@ -295,10 +297,31 @@ bool ui_searchbox_apply(uiBut *but, ARegion *region)
}
}
-void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent *event)
+static struct ARegion *wm_searchbox_tooltip_init(struct bContext *C,
+ struct ARegion *region,
+ int *UNUSED(r_pass),
+ double *UNUSED(pass_delay),
+ bool *r_exit_on_event)
+{
+ *r_exit_on_event = true;
+
+ LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ if (but->search && but->search->tooltip_fn) {
+ return but->search->tooltip_fn(C, region, but->search->arg, but->func_arg2);
+ }
+ }
+ }
+ return NULL;
+}
+
+bool ui_searchbox_event(
+ bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
{
uiSearchboxData *data = region->regiondata;
int type = event->type, val = event->val;
+ bool handled = false;
+ bool tooltip_timer_started = false;
if (type == MOUSEPAN) {
ui_pan_to_scroll(event, &type, &val);
@@ -308,12 +331,36 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent
case WHEELUPMOUSE:
case EVT_UPARROWKEY:
ui_searchbox_select(C, region, but, -1);
+ handled = true;
break;
case WHEELDOWNMOUSE:
case EVT_DOWNARROWKEY:
ui_searchbox_select(C, region, but, 1);
+ handled = true;
break;
- case MOUSEMOVE:
+ case RIGHTMOUSE:
+ if (val) {
+ if (but->search->context_menu_fn) {
+ if (data->active != -1) {
+ /* Check the cursor is over the active element
+ * (a little confusing if this isn't the case, although it does work). */
+ rcti rect;
+ ui_searchbox_butrect(&rect, data, data->active);
+ if (BLI_rcti_isect_pt(
+ &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) {
+
+ void *active = data->items.pointers[data->active];
+ if (but->search->context_menu_fn(C, but->search->arg, active, event)) {
+ handled = true;
+ }
+ }
+ }
+ }
+ }
+ break;
+ case MOUSEMOVE: {
+ bool is_inside = false;
+
if (BLI_rcti_isect_pt(&region->winrct, event->x, event->y)) {
rcti rect;
int a;
@@ -322,16 +369,46 @@ void ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, const wmEvent
ui_searchbox_butrect(&rect, data, a);
if (BLI_rcti_isect_pt(
&rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) {
+ is_inside = true;
if (data->active != a) {
data->active = a;
ui_searchbox_select(C, region, but, 0);
+ handled = true;
break;
}
}
}
}
+
+ if (U.flag & USER_TOOLTIPS) {
+ if (is_inside) {
+ if (data->active != -1) {
+ ScrArea *area = CTX_wm_area(C);
+ but->func_arg2 = data->items.pointers[data->active];
+ WM_tooltip_timer_init(C, CTX_wm_window(C), area, butregion, wm_searchbox_tooltip_init);
+ tooltip_timer_started = true;
+ }
+ }
+ }
+
break;
+ }
}
+
+ if (handled && (tooltip_timer_started == false)) {
+ wmWindow *win = CTX_wm_window(C);
+ WM_tooltip_clear(C, win);
+ }
+
+ return handled;
+}
+
+/** Wrap #uiButSearchUpdateFn callback. */
+static void ui_searchbox_update_fn(bContext *C, uiBut *but, const char *str, uiSearchItems *items)
+{
+ wmWindow *win = CTX_wm_window(C);
+ WM_tooltip_clear(C, win);
+ but->search->update_fn(C, but->search->arg, str, items);
}
/* region is the search box itself */
@@ -350,9 +427,9 @@ void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool re
data->active = -1;
/* handle active */
- if (but->search_func && but->func_arg2) {
+ if (but->search->update_fn && but->func_arg2) {
data->items.active = but->func_arg2;
- but->search_func(C, but->search_arg, but->editstr, &data->items);
+ ui_searchbox_update_fn(C, but, but->editstr, &data->items);
data->items.active = NULL;
/* found active item, calculate real offset by centering it */
@@ -381,8 +458,8 @@ void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool re
}
/* callback */
- if (but->search_func) {
- but->search_func(C, but->search_arg, but->editstr, &data->items);
+ if (but->search->update_fn) {
+ ui_searchbox_update_fn(C, but, but->editstr, &data->items);
}
/* handle case where editstr is equal to one of items */
@@ -416,7 +493,7 @@ int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *st
if (str[0]) {
data->items.autocpl = UI_autocomplete_begin(str, ui_but_string_get_max_length(but));
- but->search_func(C, but->search_arg, but->editstr, &data->items);
+ ui_searchbox_update_fn(C, but, but->editstr, &data->items);
match = UI_autocomplete_end(data->items.autocpl, str);
data->items.autocpl = NULL;
@@ -603,7 +680,7 @@ ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiBut *but
if (but->optype != NULL || (but->drawflag & UI_BUT_HAS_SHORTCUT) != 0) {
data->use_sep = true;
}
- data->sep_string = but->search_sep_string;
+ data->sep_string = but->search->sep_string;
/* compute position */
if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
@@ -881,7 +958,7 @@ void ui_but_search_refresh(uiBut *but)
items->names[x1] = MEM_callocN(but->hardmax + 1, "search names");
}
- but->search_func(but->block->evil_C, but->search_arg, but->drawstr, items);
+ ui_searchbox_update_fn(but->block->evil_C, but, but->drawstr, items);
/* only redalert when we are sure of it, this can miss cases when >10 matches */
if (items->totitem == 0) {
diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c
new file mode 100644
index 00000000000..0a06f765c0e
--- /dev/null
+++ b/source/blender/editors/interface/interface_template_search_menu.c
@@ -0,0 +1,1098 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edinterface
+ *
+ * Search available menu items via the user interface & key-maps.
+ * Accessed via the #WM_OT_search_menu operator.
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_action_types.h"
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_shader_fx_types.h"
+#include "DNA_texture_types.h"
+
+#include "BLI_alloca.h"
+#include "BLI_dynstr.h"
+#include "BLI_ghash.h"
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math_matrix.h"
+#include "BLI_memarena.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_screen.h"
+
+#include "ED_screen.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "UI_interface.h"
+#include "interface_intern.h"
+
+/* For key-map item access. */
+#include "wm_event_system.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Menu Search Template Implementation
+ * \{ */
+
+/* Unicode arrow. */
+#define MENU_SEP "\xe2\x96\xb6"
+
+/**
+ * Use when #menu_items_from_ui_create is called with `include_all_areas`.
+ * so we can run the menu item in the area it was extracted from.
+ */
+struct MenuSearch_Context {
+ /**
+ * Index into `Area.ui_type` #EnumPropertyItem or the top-bar when -1.
+ * Needed to get the display-name to use as a prefix for each menu item.
+ */
+ int space_type_ui_index;
+
+ ScrArea *area;
+ ARegion *region;
+};
+
+struct MenuSearch_Parent {
+ struct MenuSearch_Parent *parent;
+ MenuType *parent_mt;
+ const char *drawstr;
+
+ /** Set while writing menu items only. */
+ struct MenuSearch_Parent *temp_child;
+};
+
+struct MenuSearch_Item {
+ struct MenuSearch_Item *next, *prev;
+ const char *drawstr;
+ const char *drawwstr_full;
+ /** Support a single level sub-menu nesting (for operator buttons that expand). */
+ const char *drawstr_submenu;
+ int icon;
+ int state;
+
+ struct MenuSearch_Parent *menu_parent;
+ MenuType *mt;
+
+ enum {
+ MENU_SEARCH_TYPE_OP = 1,
+ MENU_SEARCH_TYPE_RNA = 2,
+ } type;
+
+ union {
+ /** Operator menu item. */
+ struct {
+ wmOperatorType *type;
+ PointerRNA *opptr;
+ short opcontext;
+ bContextStore *context;
+ } op;
+
+ /** Property (only for check-box/boolean). */
+ struct {
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ int index;
+ /** Only for enum buttons. */
+ int enum_value;
+ } rna;
+ };
+
+ /** Set when we need each menu item to be able to set it's own context. may be NULL. */
+ struct MenuSearch_Context *wm_context;
+};
+
+struct MenuSearch_Data {
+ /** MenuSearch_Item */
+ ListBase items;
+ /** Use for all small allocations. */
+ MemArena *memarena;
+
+ /** Use for context menu, to fake a button to create a context menu. */
+ struct {
+ uiBut but;
+ uiBlock block;
+ } context_menu_data;
+};
+
+static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
+{
+ const struct MenuSearch_Item *menu_item_a = menu_item_a_v;
+ const struct MenuSearch_Item *menu_item_b = menu_item_b_v;
+ return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full);
+}
+
+static const char *strdup_memarena(MemArena *memarena, const char *str)
+{
+ const uint str_size = strlen(str) + 1;
+ char *str_dst = BLI_memarena_alloc(memarena, str_size);
+ memcpy(str_dst, str, str_size);
+ return str_dst;
+}
+
+static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str)
+{
+ const uint str_size = BLI_dynstr_get_len(dyn_str) + 1;
+ char *str_dst = BLI_memarena_alloc(memarena, str_size);
+ BLI_dynstr_get_cstring_ex(dyn_str, str_dst);
+ return str_dst;
+}
+
+static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data,
+ MemArena *memarena,
+ struct MenuType *mt,
+ const char *drawstr_submenu,
+ uiBut *but,
+ struct MenuSearch_Context *wm_context)
+{
+ struct MenuSearch_Item *item = NULL;
+ if (but->optype != NULL) {
+ item = BLI_memarena_calloc(memarena, sizeof(*item));
+ item->type = MENU_SEARCH_TYPE_OP;
+
+ item->op.type = but->optype;
+ item->op.opcontext = but->opcontext;
+ item->op.context = but->context;
+ item->op.opptr = but->opptr;
+ but->opptr = NULL;
+ }
+ else if (but->rnaprop != NULL) {
+ const int prop_type = RNA_property_type(but->rnaprop);
+ if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
+ /* Note that these buttons are not prevented,
+ * but aren't typically used in menus. */
+ printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
+ but->drawstr,
+ mt->idname,
+ prop_type);
+ }
+ else {
+ item = BLI_memarena_calloc(memarena, sizeof(*item));
+ item->type = MENU_SEARCH_TYPE_RNA;
+
+ item->rna.ptr = but->rnapoin;
+ item->rna.prop = but->rnaprop;
+ item->rna.index = but->rnaindex;
+
+ if (prop_type == PROP_ENUM) {
+ item->rna.enum_value = (int)but->hardmax;
+ }
+ }
+ }
+
+ if (item != NULL) {
+ /* Handle shared settings. */
+ item->drawstr = strdup_memarena(memarena, but->drawstr);
+ item->icon = ui_but_icon(but);
+ item->state = (but->flag &
+ (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR));
+ item->mt = mt;
+ item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
+
+ item->wm_context = wm_context;
+
+ BLI_addtail(&data->items, item);
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * Populate a fake button from a menu item (use for context menu).
+ */
+static bool menu_items_to_ui_button(struct MenuSearch_Item *item, uiBut *but)
+{
+ bool changed = false;
+ switch (item->type) {
+ case MENU_SEARCH_TYPE_OP: {
+ but->optype = item->op.type;
+ but->opcontext = item->op.opcontext;
+ but->context = item->op.context;
+ but->opptr = item->op.opptr;
+ changed = true;
+ break;
+ }
+ case MENU_SEARCH_TYPE_RNA: {
+ const int prop_type = RNA_property_type(item->rna.prop);
+
+ but->rnapoin = item->rna.ptr;
+ but->rnaprop = item->rna.prop;
+ but->rnaindex = item->rna.index;
+
+ if (prop_type == PROP_ENUM) {
+ but->hardmax = item->rna.enum_value;
+ }
+ changed = true;
+ break;
+ }
+ }
+
+ if (changed) {
+ STRNCPY(but->drawstr, item->drawstr);
+ char *drawstr_sep = (item->state & UI_BUT_HAS_SEP_CHAR) ? strrchr(but->drawstr, UI_SEP_CHAR) :
+ NULL;
+ if (drawstr_sep) {
+ *drawstr_sep = '\0';
+ }
+
+ but->icon = item->icon;
+ but->str = but->strdata;
+ }
+
+ return changed;
+}
+
+/**
+ * Populate \a menu_stack with menus from inspecting active key-maps for this context.
+ */
+static void menu_types_add_from_keymap_items(bContext *C,
+ wmWindow *win,
+ ScrArea *area,
+ ARegion *region,
+ LinkNode **menuid_stack_p,
+ GHash *menu_to_kmi,
+ GSet *menu_tagged)
+{
+ wmWindowManager *wm = CTX_wm_manager(C);
+ ListBase *handlers[] = {
+ region ? &region->handlers : NULL,
+ area ? &area->handlers : NULL,
+ &win->handlers,
+ };
+
+ for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
+ if (handlers[handler_index] == NULL) {
+ continue;
+ }
+ LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
+ /* During this loop, UI handlers for nested menus can tag multiple handlers free. */
+ if (handler_base->flag & WM_HANDLER_DO_FREE) {
+ continue;
+ }
+ if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
+ continue;
+ }
+
+ else if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
+ wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
+ wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
+ if (keymap && WM_keymap_poll(C, keymap)) {
+ LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
+ if (kmi->flag & KMI_INACTIVE) {
+ continue;
+ }
+ if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
+ char menu_idname[MAX_NAME];
+ RNA_string_get(kmi->ptr, "name", menu_idname);
+ MenuType *mt = WM_menutype_find(menu_idname, false);
+
+ if (mt && BLI_gset_add(menu_tagged, mt)) {
+ /* Unlikely, but possible this will be included twice. */
+ BLI_linklist_prepend(menuid_stack_p, mt);
+
+ void **kmi_p;
+ if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
+ *kmi_p = kmi;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Display all operators (last). Developer-only convenience feature.
+ */
+static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *data)
+{
+ /* Add to temporary list so we can sort them separately. */
+ ListBase operator_items = {NULL, NULL};
+
+ MemArena *memarena = data->memarena;
+ GHashIterator iter;
+ for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
+ BLI_ghashIterator_step(&iter)) {
+ wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
+
+ if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
+ continue;
+ }
+
+ if (WM_operator_poll((bContext *)C, ot)) {
+ const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
+
+ struct MenuSearch_Item *item = NULL;
+ item = BLI_memarena_calloc(memarena, sizeof(*item));
+ item->type = MENU_SEARCH_TYPE_OP;
+
+ item->op.type = ot;
+ item->op.opcontext = WM_OP_INVOKE_DEFAULT;
+ item->op.context = NULL;
+
+ char idname_as_py[OP_MAX_TYPENAME];
+ char uiname[256];
+ WM_operator_py_idname(idname_as_py, ot->idname);
+
+ SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name);
+
+ item->drawwstr_full = strdup_memarena(memarena, uiname);
+ item->drawstr = ot_ui_name;
+
+ item->wm_context = NULL;
+
+ BLI_addtail(&operator_items, item);
+ }
+ }
+
+ BLI_listbase_sort(&operator_items, menu_item_sort_by_drawstr_full);
+
+ BLI_movelisttolist(&data->items, &operator_items);
+}
+
+/**
+ * Create #MenuSearch_Data by inspecting the current context, this uses two methods:
+ *
+ * - Look-up pre-defined editor-menus.
+ * - Look-up key-map items which call menus.
+ */
+static struct MenuSearch_Data *menu_items_from_ui_create(
+ bContext *C, wmWindow *win, ScrArea *area_init, ARegion *region_init, bool include_all_areas)
+{
+ MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
+ /** Map (#MenuType to #MenuSearch_Parent) */
+ GHash *menu_parent_map = BLI_ghash_ptr_new(__func__);
+ GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__);
+ const uiStyle *style = UI_style_get_dpi();
+
+ /* Convert into non-ui structure. */
+ struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__);
+
+ DynStr *dyn_str = BLI_dynstr_new_memarena();
+
+ /* Use a stack of menus to handle and discover new menus in passes. */
+ LinkNode *menu_stack = NULL;
+
+ /* Tag menu types not to add, either because they have already been added
+ * or they have been blacklisted.
+ * Set of #MenuType. */
+ GSet *menu_tagged = BLI_gset_ptr_new(__func__);
+ /** Map (#MenuType -> #wmKeyMapItem). */
+ GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__);
+
+ /* Blacklist menus we don't want to show. */
+ {
+ const char *idname_array[] = {
+ /* While we could include this, it's just showing filenames to load. */
+ "TOPBAR_MT_file_open_recent",
+ };
+ for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
+ MenuType *mt = WM_menutype_find(idname_array[i], false);
+ if (mt != NULL) {
+ BLI_gset_add(menu_tagged, mt);
+ }
+ }
+ }
+
+ /* Collect contexts, one for each 'ui_type'. */
+ struct MenuSearch_Context *wm_contexts = NULL;
+
+ const EnumPropertyItem *space_type_ui_items = NULL;
+ int space_type_ui_items_len = 0;
+ bool space_type_ui_items_free = false;
+
+ /* Text used as prefix for top-bar menu items. */
+ const char *global_menu_prefix = NULL;
+
+ if (include_all_areas) {
+ /* First create arrays for ui_type. */
+ PropertyRNA *prop_ui_type = NULL;
+ {
+ PointerRNA ptr;
+ RNA_pointer_create(NULL, &RNA_Area, NULL, &ptr);
+ prop_ui_type = RNA_struct_find_property(&ptr, "ui_type");
+ RNA_property_enum_items(C,
+ &ptr,
+ prop_ui_type,
+ &space_type_ui_items,
+ &space_type_ui_items_len,
+ &space_type_ui_items_free);
+
+ wm_contexts = BLI_memarena_calloc(memarena, sizeof(*wm_contexts) * space_type_ui_items_len);
+ for (int i = 0; i < space_type_ui_items_len; i++) {
+ wm_contexts[i].space_type_ui_index = -1;
+ }
+ }
+
+ bScreen *screen = WM_window_get_active_screen(win);
+ LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
+ ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
+ if (region != NULL) {
+ PointerRNA ptr;
+ RNA_pointer_create(&screen->id, &RNA_Area, area, &ptr);
+ const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type);
+
+ int space_type_ui_index = RNA_enum_from_value(space_type_ui_items, space_type_ui);
+ if (space_type_ui_index == -1) {
+ continue;
+ }
+
+ if (wm_contexts[space_type_ui_index].space_type_ui_index != -1) {
+ ScrArea *area_best = wm_contexts[space_type_ui_index].area;
+ const uint value_best = (uint)area_best->winx * (uint)area_best->winy;
+ const uint value_test = (uint)area->winx * (uint)area->winy;
+ if (value_best > value_test) {
+ continue;
+ }
+ }
+
+ wm_contexts[space_type_ui_index].space_type_ui_index = space_type_ui_index;
+ wm_contexts[space_type_ui_index].area = area;
+ wm_contexts[space_type_ui_index].region = region;
+ }
+ }
+
+ global_menu_prefix = CTX_IFACE_(RNA_property_translation_context(prop_ui_type), "Top Bar");
+ }
+
+ GHashIterator iter;
+
+ for (int space_type_ui_index = -1; space_type_ui_index < space_type_ui_items_len;
+ space_type_ui_index += 1) {
+
+ ScrArea *area = NULL;
+ ARegion *region = NULL;
+ struct MenuSearch_Context *wm_context = NULL;
+
+ if (include_all_areas) {
+ if (space_type_ui_index == -1) {
+ /* First run without any context, to populate the top-bar without. */
+ wm_context = NULL;
+ area = NULL;
+ region = NULL;
+ }
+ else {
+ wm_context = &wm_contexts[space_type_ui_index];
+ if (wm_context->space_type_ui_index == -1) {
+ continue;
+ }
+
+ area = wm_context->area;
+ region = wm_context->region;
+
+ CTX_wm_area_set(C, area);
+ CTX_wm_region_set(C, region);
+ }
+ }
+ else {
+ area = area_init;
+ region = region_init;
+ }
+
+ /* Populate menus from the editors,
+ * note that we could create a fake header, draw the header and extract the menus
+ * from the buttons, however this is quite involved and can be avoided as by convention
+ * each space-type has a single root-menu that headers use. */
+ {
+ const char *idname_array[2] = {NULL};
+ int idname_array_len = 0;
+
+ /* Use negative for global (no area) context, populate the top-bar. */
+ if (space_type_ui_index == -1) {
+ idname_array[idname_array_len++] = "TOPBAR_MT_editor_menus";
+ }
+
+#define SPACE_MENU_MAP(space_type, menu_id) \
+ case space_type: \
+ idname_array[idname_array_len++] = menu_id; \
+ break
+#define SPACE_MENU_NOP(space_type) \
+ case space_type: \
+ break
+
+ if (area != NULL) {
+ SpaceLink *sl = area->spacedata.first;
+ switch ((eSpace_Type)area->spacetype) {
+ SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
+ SPACE_MENU_NOP(SPACE_PROPERTIES);
+ SPACE_MENU_MAP(SPACE_FILE, "FILEBROWSER_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_ACTION,
+ (((const SpaceAction *)sl)->mode == SACTCONT_TIMELINE) ?
+ "TIME_MT_editor_menus" :
+ "DOPESHEET_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
+ SPACE_MENU_MAP(SPACE_CLIP,
+ (((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ?
+ "CLIP_MT_tracking_editor_menus" :
+ "CLIP_MT_masking_editor_menus");
+ SPACE_MENU_NOP(SPACE_EMPTY);
+ SPACE_MENU_NOP(SPACE_SCRIPT);
+ SPACE_MENU_NOP(SPACE_STATUSBAR);
+ SPACE_MENU_NOP(SPACE_TOPBAR);
+ }
+ }
+ for (int i = 0; i < idname_array_len; i++) {
+ MenuType *mt = WM_menutype_find(idname_array[i], false);
+ if (mt != NULL) {
+ /* Check if this exists because of 'include_all_areas'. */
+ if (BLI_gset_add(menu_tagged, mt)) {
+ BLI_linklist_prepend(&menu_stack, mt);
+ }
+ }
+ }
+ }
+#undef SPACE_MENU_MAP
+#undef SPACE_MENU_NOP
+
+ bool has_keymap_menu_items = false;
+
+ while (menu_stack != NULL) {
+ MenuType *mt = BLI_linklist_pop(&menu_stack);
+ if (!WM_menutype_poll(C, mt)) {
+ continue;
+ }
+
+ uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
+ uiLayout *layout = UI_block_layout(
+ block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
+
+ UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
+
+ uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN);
+ UI_menutype_draw(C, mt, layout);
+
+ UI_block_end(C, block);
+
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ MenuType *mt_from_but = NULL;
+ /* Support menu titles with dynamic from initial labels
+ * (used by edit-mesh context menu). */
+ if (but->type == UI_BTYPE_LABEL) {
+
+ /* Check if the label is the title. */
+ uiBut *but_test = but->prev;
+ while (but_test && but_test->type == UI_BTYPE_SEPR) {
+ but_test = but_test->prev;
+ }
+
+ if (but_test == NULL) {
+ BLI_ghash_insert(
+ menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr));
+ }
+ }
+ else if (menu_items_from_ui_create_item_from_button(
+ data, memarena, mt, NULL, but, wm_context)) {
+ /* pass */
+ }
+ else if ((mt_from_but = UI_but_menutype_get(but))) {
+
+ if (BLI_gset_add(menu_tagged, mt_from_but)) {
+ BLI_linklist_prepend(&menu_stack, mt_from_but);
+ }
+
+ if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) {
+ struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena,
+ sizeof(*menu_parent));
+ /* Use brackets for menu key shortcuts,
+ * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
+ * This is needed so we don't right align sub-menu contents
+ * we only want to do that for the last menu item, not the path that leads to it.
+ */
+ const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
+ strrchr(but->drawstr, UI_SEP_CHAR) :
+ NULL;
+ bool drawstr_is_empty = false;
+ if (drawstr_sep != NULL) {
+ BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
+ /* Detect empty string, fallback to menu name. */
+ const char *drawstr = but->drawstr;
+ int drawstr_len = drawstr_sep - but->drawstr;
+ if (UNLIKELY(drawstr_len == 0)) {
+ drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
+ drawstr_len = strlen(drawstr);
+ if (drawstr[0] == '\0') {
+ drawstr_is_empty = true;
+ }
+ }
+ BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len);
+ BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1);
+ menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str);
+ BLI_dynstr_clear(dyn_str);
+ }
+ else {
+ const char *drawstr = but->drawstr;
+ if (UNLIKELY(drawstr[0] == '\0')) {
+ drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
+ if (drawstr[0] == '\0') {
+ drawstr_is_empty = true;
+ }
+ }
+ menu_parent->drawstr = strdup_memarena(memarena, drawstr);
+ }
+ menu_parent->parent_mt = mt;
+ BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent);
+
+ if (drawstr_is_empty) {
+ printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
+ }
+ }
+ }
+ else if (but->menu_create_func != NULL) {
+ /* A non 'MenuType' menu button. */
+
+ /* Only expand one level deep, this is mainly for expanding operator menus. */
+ const char *drawstr_submenu = but->drawstr;
+
+ /* +1 to avoid overlap with the current 'block'. */
+ uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS);
+ uiLayout *sub_layout = UI_block_layout(
+ sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
+
+ UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
+
+ uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN);
+
+ but->menu_create_func(C, sub_layout, but->poin);
+
+ UI_block_end(C, sub_block);
+
+ LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) {
+ menu_items_from_ui_create_item_from_button(
+ data, memarena, mt, drawstr_submenu, sub_but, wm_context);
+ }
+
+ if (region) {
+ BLI_remlink(&region->uiblocks, sub_block);
+ }
+ UI_block_free(NULL, sub_block);
+ }
+ }
+ if (region) {
+ BLI_remlink(&region->uiblocks, block);
+ }
+ UI_block_free(NULL, block);
+
+ /* Add key-map items as a second pass,
+ * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */
+ if ((menu_stack == NULL) && (has_keymap_menu_items == false)) {
+ has_keymap_menu_items = true;
+ menu_types_add_from_keymap_items(
+ C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged);
+ }
+ }
+ }
+
+ LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
+ item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt);
+ }
+
+ GHASH_ITER (iter, menu_parent_map) {
+ struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter);
+ menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt);
+ }
+
+ /* NOTE: currently this builds the full path for each menu item,
+ * that could be moved into the parent menu. */
+
+ /* Set names as full paths. */
+ LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
+ BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
+
+ if (include_all_areas) {
+ BLI_dynstr_appendf(dyn_str,
+ "%s: ",
+ (item->wm_context != NULL) ?
+ space_type_ui_items[item->wm_context->space_type_ui_index].name :
+ global_menu_prefix);
+ }
+
+ if (item->menu_parent != NULL) {
+ struct MenuSearch_Parent *menu_parent = item->menu_parent;
+ menu_parent->temp_child = NULL;
+ while (menu_parent && menu_parent->parent) {
+ menu_parent->parent->temp_child = menu_parent;
+ menu_parent = menu_parent->parent;
+ }
+ while (menu_parent) {
+ BLI_dynstr_append(dyn_str, menu_parent->drawstr);
+ BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ menu_parent = menu_parent->temp_child;
+ }
+ }
+ else {
+ const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt);
+ if (drawstr == NULL) {
+ drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label);
+ }
+ BLI_dynstr_append(dyn_str, drawstr);
+
+ wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt);
+ if (kmi != NULL) {
+ char kmi_str[128];
+ WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str));
+ BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
+ }
+
+ BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ }
+
+ /* Optional nested menu. */
+ if (item->drawstr_submenu != NULL) {
+ BLI_dynstr_append(dyn_str, item->drawstr_submenu);
+ BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ }
+
+ BLI_dynstr_append(dyn_str, item->drawstr);
+
+ item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str);
+ BLI_dynstr_clear(dyn_str);
+ }
+ BLI_dynstr_free(dyn_str);
+
+ /* Finally sort menu items.
+ *
+ * Note: we might want to keep the in-menu order, for now sort all. */
+ BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full);
+
+ BLI_ghash_free(menu_parent_map, NULL, NULL);
+ BLI_ghash_free(menu_display_name_map, NULL, NULL);
+
+ BLI_ghash_free(menu_to_kmi, NULL, NULL);
+
+ BLI_gset_free(menu_tagged, NULL);
+
+ data->memarena = memarena;
+
+ if (include_all_areas) {
+ CTX_wm_area_set(C, area_init);
+ CTX_wm_region_set(C, region_init);
+
+ if (space_type_ui_items_free) {
+ MEM_freeN((void *)space_type_ui_items);
+ }
+ }
+
+ /* Include all operators for developers,
+ * since it can be handy to have a quick way to access any operator,
+ * including operators being developed which haven't yet been added into the interface.
+ *
+ * These are added after all menu items so developers still get normal behavior by default,
+ * unless searching for something that isn't already in a menu (or scroll down).
+ *
+ * Keep this behind a developer only check:
+ * - Many operators need options to be set to give useful results, see: T74157.
+ * - User who really prefer to list all operators can use #WM_OT_search_operator.
+ */
+ if (U.flag & USER_DEVELOPER_UI) {
+ menu_items_from_all_operators(C, data);
+ }
+
+ return data;
+}
+
+static void menu_search_arg_free_fn(void *data_v)
+{
+ struct MenuSearch_Data *data = data_v;
+ LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
+ switch (item->type) {
+ case MENU_SEARCH_TYPE_OP: {
+ if (item->op.opptr != NULL) {
+ WM_operator_properties_free(item->op.opptr);
+ MEM_freeN(item->op.opptr);
+ }
+ }
+ case MENU_SEARCH_TYPE_RNA: {
+ break;
+ }
+ }
+ }
+
+ BLI_memarena_free(data->memarena);
+
+ MEM_freeN(data);
+}
+
+static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
+{
+ struct MenuSearch_Item *item = arg2;
+ if (item == NULL) {
+ return;
+ }
+ if (item->state & UI_BUT_DISABLED) {
+ return;
+ }
+
+ ScrArea *area_prev = CTX_wm_area(C);
+ ARegion *region_prev = CTX_wm_region(C);
+
+ if (item->wm_context != NULL) {
+ CTX_wm_area_set(C, item->wm_context->area);
+ CTX_wm_region_set(C, item->wm_context->region);
+ }
+
+ switch (item->type) {
+ case MENU_SEARCH_TYPE_OP: {
+ CTX_store_set(C, item->op.context);
+ WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr);
+ CTX_store_set(C, NULL);
+ break;
+ }
+ case MENU_SEARCH_TYPE_RNA: {
+ PointerRNA *ptr = &item->rna.ptr;
+ PropertyRNA *prop = item->rna.prop;
+ int index = item->rna.index;
+ const int prop_type = RNA_property_type(prop);
+ bool changed = false;
+
+ if (prop_type == PROP_BOOLEAN) {
+ const bool is_array = RNA_property_array_check(prop);
+ if (is_array) {
+ const bool value = RNA_property_boolean_get_index(ptr, prop, index);
+ RNA_property_boolean_set_index(ptr, prop, index, !value);
+ }
+ else {
+ const bool value = RNA_property_boolean_get(ptr, prop);
+ RNA_property_boolean_set(ptr, prop, !value);
+ }
+ changed = true;
+ }
+ else if (prop_type == PROP_ENUM) {
+ RNA_property_enum_set(ptr, prop, item->rna.enum_value);
+ changed = true;
+ }
+
+ if (changed) {
+ RNA_property_update(C, ptr, prop);
+ }
+ break;
+ }
+ }
+
+ if (item->wm_context != NULL) {
+ CTX_wm_area_set(C, area_prev);
+ CTX_wm_region_set(C, region_prev);
+ }
+}
+
+static void menu_search_update_fn(const bContext *UNUSED(C),
+ void *arg,
+ const char *str,
+ uiSearchItems *items)
+{
+ struct MenuSearch_Data *data = arg;
+ const size_t str_len = strlen(str);
+ const int words_max = (str_len / 2) + 1;
+ int(*words)[2] = BLI_array_alloca(words, words_max);
+
+ const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
+
+ for (struct MenuSearch_Item *item = data->items.first; item; item = item->next) {
+ int index;
+
+ /* match name against all search words */
+ for (index = 0; index < words_len; index++) {
+ if (!ui_str_has_word_prefix(item->drawwstr_full, str + words[index][0], words[index][1])) {
+ break;
+ }
+ }
+
+ if (index == words_len) {
+ if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state)) {
+ break;
+ }
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Context Menu
+ *
+ * This uses a fake button to create a context menu,
+ * if this ever causes hard to solve bugs we may need to create
+ * a separate context menu just for the search, however this is fairly involved.
+ * \{ */
+
+static bool ui_search_menu_create_context_menu(struct bContext *C,
+ void *arg,
+ void *active,
+ const struct wmEvent *UNUSED(event))
+{
+ struct MenuSearch_Data *data = arg;
+ struct MenuSearch_Item *item = active;
+ bool has_menu = false;
+
+ memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
+ uiBut *but = &data->context_menu_data.but;
+ uiBlock *block = &data->context_menu_data.block;
+
+ but->block = block;
+
+ if (menu_items_to_ui_button(item, but)) {
+ ScrArea *area_prev = CTX_wm_area(C);
+ ARegion *region_prev = CTX_wm_region(C);
+
+ if (item->wm_context != NULL) {
+ CTX_wm_area_set(C, item->wm_context->area);
+ CTX_wm_region_set(C, item->wm_context->region);
+ }
+
+ if (ui_popup_context_menu_for_button(C, but)) {
+ has_menu = true;
+ }
+
+ if (item->wm_context != NULL) {
+ CTX_wm_area_set(C, area_prev);
+ CTX_wm_region_set(C, region_prev);
+ }
+ }
+
+ return has_menu;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Tooltip
+ * \{ */
+
+static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C,
+ struct ARegion *region,
+ void *arg,
+ void *active)
+{
+ struct MenuSearch_Data *data = arg;
+ struct MenuSearch_Item *item = active;
+
+ memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data));
+ uiBut *but = &data->context_menu_data.but;
+ uiBlock *block = &data->context_menu_data.block;
+ unit_m4(block->winmat);
+ block->aspect = 1;
+
+ but->block = block;
+
+ /* Place the fake button at the cursor so the tool-tip is places properly. */
+ float tip_init[2];
+ const wmEvent *event = CTX_wm_window(C)->eventstate;
+ tip_init[0] = event->x;
+ tip_init[1] = event->y - (UI_UNIT_Y / 2);
+ ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]);
+
+ but->rect.xmin = tip_init[0];
+ but->rect.xmax = tip_init[0];
+ but->rect.ymin = tip_init[1];
+ but->rect.ymax = tip_init[1];
+
+ if (menu_items_to_ui_button(item, but)) {
+ ScrArea *area_prev = CTX_wm_area(C);
+ ARegion *region_prev = CTX_wm_region(C);
+
+ if (item->wm_context != NULL) {
+ CTX_wm_area_set(C, item->wm_context->area);
+ CTX_wm_region_set(C, item->wm_context->region);
+ }
+
+ ARegion *region_tip = UI_tooltip_create_from_button(C, region, but, false);
+
+ if (item->wm_context != NULL) {
+ CTX_wm_area_set(C, area_prev);
+ CTX_wm_region_set(C, region_prev);
+ }
+ return region_tip;
+ }
+
+ return NULL;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Menu Search Template Public API
+ * \{ */
+
+void UI_but_func_menu_search(uiBut *but)
+{
+ bContext *C = but->block->evil_C;
+ wmWindow *win = CTX_wm_window(C);
+ ScrArea *area = CTX_wm_area(C);
+ ARegion *region = CTX_wm_region(C);
+ /* When run from top-bar scan all areas in the current window. */
+ bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR));
+ struct MenuSearch_Data *data = menu_items_from_ui_create(
+ C, win, area, region, include_all_areas);
+ UI_but_func_search_set(but,
+ /* Generic callback. */
+ ui_searchbox_create_menu,
+ menu_search_update_fn,
+ data,
+ menu_search_arg_free_fn,
+ menu_search_exec_fn,
+ NULL);
+
+ UI_but_func_search_set_context_menu(but, ui_search_menu_create_context_menu);
+ UI_but_func_search_set_tooltip(but, ui_search_menu_create_tooltip);
+ UI_but_func_search_set_sep_string(but, MENU_SEP);
+}
+
+void uiTemplateMenuSearch(uiLayout *layout)
+{
+ uiBlock *block;
+ uiBut *but;
+ static char search[256] = "";
+
+ block = uiLayoutGetBlock(layout);
+ UI_block_layout_set_current(block, layout);
+
+ but = uiDefSearchBut(
+ block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
+ UI_but_func_menu_search(but);
+}
+
+#undef MENU_SEP
+
+/** \} */
diff --git a/source/blender/editors/interface/interface_template_search_operator.c b/source/blender/editors/interface/interface_template_search_operator.c
new file mode 100644
index 00000000000..cdf87103587
--- /dev/null
+++ b/source/blender/editors/interface/interface_template_search_operator.c
@@ -0,0 +1,151 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edinterface
+ *
+ * Search available operators by scanning all and checking their poll function.
+ * accessed via the #WM_OT_search_operator operator.
+ */
+
+#include <string.h>
+
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_node_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_shader_fx_types.h"
+#include "DNA_texture_types.h"
+
+#include "BLI_alloca.h"
+#include "BLI_ghash.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "UI_interface.h"
+#include "interface_intern.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Operator Search Template Implementation
+ * \{ */
+
+static void operator_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
+{
+ wmOperatorType *ot = arg2;
+
+ if (ot) {
+ WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL);
+ }
+}
+
+static void operator_search_update_fn(const bContext *C,
+ void *UNUSED(arg),
+ const char *str,
+ uiSearchItems *items)
+{
+ GHashIterator iter;
+ const size_t str_len = strlen(str);
+ const int words_max = (str_len / 2) + 1;
+ int(*words)[2] = BLI_array_alloca(words, words_max);
+
+ const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
+
+ for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
+ BLI_ghashIterator_step(&iter)) {
+ wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
+ const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
+ int index;
+
+ if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
+ continue;
+ }
+
+ /* match name against all search words */
+ for (index = 0; index < words_len; index++) {
+ if (!ui_str_has_word_prefix(ot_ui_name, str + words[index][0], words[index][1])) {
+ break;
+ }
+ }
+
+ if (index == words_len) {
+ if (WM_operator_poll((bContext *)C, ot)) {
+ char name[256];
+ int len = strlen(ot_ui_name);
+
+ /* display name for menu, can hold hotkey */
+ BLI_strncpy(name, ot_ui_name, sizeof(name));
+
+ /* check for hotkey */
+ if (len < sizeof(name) - 6) {
+ if (WM_key_event_operator_string(C,
+ ot->idname,
+ WM_OP_EXEC_DEFAULT,
+ NULL,
+ true,
+ &name[len + 1],
+ sizeof(name) - len - 1)) {
+ name[len] = UI_SEP_CHAR;
+ }
+ }
+
+ if (!UI_search_item_add(items, name, ot, ICON_NONE, 0)) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name Operator Search Template API
+ * \{ */
+
+void UI_but_func_operator_search(uiBut *but)
+{
+ UI_but_func_search_set(but,
+ ui_searchbox_create_operator,
+ operator_search_update_fn,
+ NULL,
+ false,
+ operator_search_exec_fn,
+ NULL);
+}
+
+void uiTemplateOperatorSearch(uiLayout *layout)
+{
+ uiBlock *block;
+ uiBut *but;
+ static char search[256] = "";
+
+ block = uiLayoutGetBlock(layout);
+ UI_block_layout_set_current(block, layout);
+
+ but = uiDefSearchBut(
+ block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
+ UI_but_func_operator_search(but);
+}
+
+/** \} */
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index ca9f12a4219..0e67f943ee6 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -37,18 +37,12 @@
#include "DNA_shader_fx_types.h"
#include "DNA_texture_types.h"
-#include "BLI_alloca.h"
-#include "BLI_dynstr.h"
#include "BLI_fnmatch.h"
-#include "BLI_ghash.h"
-#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
-#include "BLI_memarena.h"
#include "BLI_path_util.h"
#include "BLI_rect.h"
#include "BLI_string.h"
-#include "BLI_string_utils.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"
@@ -73,7 +67,6 @@
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_packedFile.h"
-#include "BKE_paint.h"
#include "BKE_particle.h"
#include "BKE_report.h"
#include "BKE_scene.h"
@@ -103,9 +96,6 @@
#include "PIL_time.h"
-/* For key-map item access. */
-#include "wm_event_system.h"
-
// #define USE_OP_RESET_BUT // we may want to make this optional, disable for now.
/* defines for templateID/TemplateSearch */
@@ -213,9 +203,9 @@ static void template_add_button_search_menu(const bContext *C,
static uiBlock *template_common_search_menu(const bContext *C,
ARegion *region,
- uiButSearchFunc search_func,
+ uiButSearchUpdateFn search_update_fn,
void *search_arg,
- uiButHandleFunc handle_func,
+ uiButHandleFunc search_exec_fn,
void *active_item,
const int preview_rows,
const int preview_cols,
@@ -289,11 +279,10 @@ static uiBlock *template_common_search_menu(const bContext *C,
}
UI_but_func_search_set(but,
ui_searchbox_create_generic,
- search_func,
+ search_update_fn,
search_arg,
NULL,
- handle_func,
- NULL,
+ search_exec_fn,
active_item);
UI_block_bounds_set_normal(block, 0.3f * U.widget_unit);
@@ -326,7 +315,7 @@ typedef struct TemplateID {
} TemplateID;
/* Search browse menu, assign */
-static void template_ID_set_property_cb(bContext *C, void *arg_template, void *item)
+static void template_ID_set_property_exec_fn(bContext *C, void *arg_template, void *item)
{
TemplateID *template_ui = (TemplateID *)arg_template;
@@ -461,7 +450,8 @@ static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem)
{
static TemplateID template_ui;
PointerRNA active_item_ptr;
- void (*id_search_cb_p)(const bContext *, void *, const char *, uiSearchItems *) = id_search_cb;
+ void (*id_search_update_fn)(
+ const bContext *, void *, const char *, uiSearchItems *) = id_search_cb;
/* arg_litem is malloced, can be freed by parent button */
template_ui = *((TemplateID *)arg_litem);
@@ -471,16 +461,16 @@ static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem)
/* Currently only used for objects. */
if (template_ui.idcode == ID_OB) {
if (template_ui.filter == UI_TEMPLATE_ID_FILTER_AVAILABLE) {
- id_search_cb_p = id_search_cb_objects_from_scene;
+ id_search_update_fn = id_search_cb_objects_from_scene;
}
}
}
return template_common_search_menu(C,
region,
- id_search_cb_p,
+ id_search_update_fn,
&template_ui,
- template_ID_set_property_cb,
+ template_ID_set_property_exec_fn,
active_item_ptr.data,
template_ui.prv_rows,
template_ui.prv_cols,
@@ -688,6 +678,8 @@ static const char *template_id_browse_tip(const StructRNA *type)
return N_("Browse Point Cloud Data to be linked");
case ID_VO:
return N_("Browse Volume Data to be linked");
+ case ID_SIM:
+ return N_("Browse Simulation to be linked");
}
}
return N_("Browse ID data to be linked");
@@ -705,6 +697,8 @@ static const char *template_id_context(StructRNA *type)
}
return BLT_I18NCONTEXT_DEFAULT;
}
+#else
+# define template_id_context(type) 0
#endif
static uiBut *template_id_def_new_but(uiBlock *block,
@@ -753,7 +747,8 @@ static uiBut *template_id_def_new_but(uiBlock *block,
BLT_I18NCONTEXT_ID_LIGHTPROBE,
BLT_I18NCONTEXT_ID_HAIR,
BLT_I18NCONTEXT_ID_POINTCLOUD,
- BLT_I18NCONTEXT_ID_VOLUME, );
+ BLT_I18NCONTEXT_ID_VOLUME,
+ BLT_I18NCONTEXT_ID_SIMULATION, );
/* Note: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters,
* check the definition to see if a new call must be added when the limit
* is exceeded. */
@@ -1187,7 +1182,7 @@ static void template_ID_tabs(bContext *C,
0.0f,
0.0f,
"");
- UI_but_funcN_set(&tab->but, template_ID_set_property_cb, MEM_dupallocN(template), id);
+ UI_but_funcN_set(&tab->but, template_ID_set_property_exec_fn, MEM_dupallocN(template), id);
tab->but.custom_data = (void *)id;
tab->but.dragpoin = id;
tab->menu = mt;
@@ -1533,7 +1528,7 @@ typedef struct TemplateSearch {
int preview_rows, preview_cols;
} TemplateSearch;
-static void template_search_handle_cb(bContext *C, void *arg_template, void *item)
+static void template_search_exec_fn(bContext *C, void *arg_template, void *item)
{
TemplateSearch *template_search = arg_template;
uiRNACollectionSearch *coll_search = &template_search->search_data;
@@ -1557,9 +1552,9 @@ static uiBlock *template_search_menu(bContext *C, ARegion *region, void *arg_tem
return template_common_search_menu(C,
region,
- ui_rna_collection_search_cb,
+ ui_rna_collection_search_update_fn,
&template_search,
- template_search_handle_cb,
+ template_search_exec_fn,
active_ptr.data,
template_search.preview_rows,
template_search.preview_cols,
@@ -1823,14 +1818,14 @@ static void modifiers_convertToReal(bContext *C, void *ob_v, void *md_v)
{
Object *ob = ob_v;
ModifierData *md = md_v;
- ModifierData *nmd = modifier_new(md->type);
+ ModifierData *nmd = BKE_modifier_new(md->type);
- modifier_copyData(md, nmd);
+ BKE_modifier_copydata(md, nmd);
nmd->mode &= ~eModifierMode_Virtual;
BLI_addhead(&ob->modifiers, nmd);
- modifier_unique_name(&ob->modifiers, nmd);
+ BKE_modifier_unique_name(&ob->modifiers, nmd);
ob->partype = PAROBJECT;
@@ -1840,20 +1835,26 @@ static void modifiers_convertToReal(bContext *C, void *ob_v, void *md_v)
ED_undo_push(C, "Modifier convert to real");
}
-static int modifier_can_delete(ModifierData *md)
+static bool modifier_can_delete(ModifierData *md)
{
/* fluid particle modifier can't be deleted here */
if (md->type == eModifierType_ParticleSystem) {
short particle_type = ((ParticleSystemModifierData *)md)->psys->part->type;
- if (particle_type == PART_FLUID || particle_type == PART_FLUID_FLIP ||
- particle_type == PART_FLUID_FOAM || particle_type == PART_FLUID_SPRAY ||
- particle_type == PART_FLUID_BUBBLE || particle_type == PART_FLUID_TRACER ||
- particle_type == PART_FLUID_SPRAYFOAM || particle_type == PART_FLUID_SPRAYBUBBLE ||
- particle_type == PART_FLUID_FOAMBUBBLE || particle_type == PART_FLUID_SPRAYFOAMBUBBLE) {
- return 0;
+ if (ELEM(particle_type,
+ PART_FLUID,
+ PART_FLUID_FLIP,
+ PART_FLUID_FOAM,
+ PART_FLUID_SPRAY,
+ PART_FLUID_BUBBLE,
+ PART_FLUID_TRACER,
+ PART_FLUID_SPRAYFOAM,
+ PART_FLUID_SPRAYBUBBLE,
+ PART_FLUID_FOAMBUBBLE,
+ PART_FLUID_SPRAYFOAMBUBBLE)) {
+ return false;
}
}
- return 1;
+ return true;
}
/* Check whether Modifier is a simulation or not,
@@ -1888,7 +1889,7 @@ static uiLayout *draw_modifier(uiLayout *layout,
int cageIndex,
int lastCageIndex)
{
- const ModifierTypeInfo *mti = modifierType_getInfo(md->type);
+ const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type);
PointerRNA ptr;
uiBut *but;
uiBlock *block;
@@ -1982,9 +1983,9 @@ static uiLayout *draw_modifier(uiLayout *layout,
}
if (ob->type == OB_MESH) {
- if (modifier_supportsCage(scene, md) && (index <= lastCageIndex)) {
+ if (BKE_modifier_supports_cage(scene, md) && (index <= lastCageIndex)) {
sub = uiLayoutRow(row, true);
- if (index < cageIndex || !modifier_couldBeCage(scene, md)) {
+ if (index < cageIndex || !BKE_modifier_couldbe_cage(scene, md)) {
uiLayoutSetActive(sub, false);
}
uiItemR(sub, &ptr, "show_on_cage", 0, "", ICON_NONE);
@@ -2080,7 +2081,7 @@ static uiLayout *draw_modifier(uiLayout *layout,
"apply_as",
MODIFIER_APPLY_DATA);
- if (modifier_isSameTopology(md) && !modifier_isNonGeometrical(md)) {
+ if (BKE_modifier_is_same_topology(md) && !BKE_modifier_is_non_geometrical(md)) {
uiItemEnumO(row,
"OBJECT_OT_modifier_apply",
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply as Shape Key"),
@@ -2147,10 +2148,10 @@ uiLayout *uiTemplateModifier(uiLayout *layout, bContext *C, PointerRNA *ptr)
UI_block_lock_set(uiLayoutGetBlock(layout), (ob && ID_IS_LINKED(ob)), ERROR_LIBDATA_MESSAGE);
/* find modifier and draw it */
- cageIndex = modifiers_getCageIndex(scene, ob, &lastCageIndex, 0);
+ cageIndex = BKE_modifiers_get_cage_index(scene, ob, &lastCageIndex, 0);
/* XXX virtual modifiers are not accessible for python */
- vmd = modifiers_getVirtualModifierList(ob, &virtualModifierData);
+ vmd = BKE_modifiers_get_virtual_modifierlist(ob, &virtualModifierData);
for (i = 0; vmd; i++, vmd = vmd->next) {
if (md == vmd) {
@@ -2172,7 +2173,7 @@ uiLayout *uiTemplateModifier(uiLayout *layout, bContext *C, PointerRNA *ptr)
static uiLayout *gpencil_draw_modifier(uiLayout *layout, Object *ob, GpencilModifierData *md)
{
- const GpencilModifierTypeInfo *mti = BKE_gpencil_modifierType_getInfo(md->type);
+ const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type);
PointerRNA ptr;
uiBlock *block;
uiLayout *box, *column, *row, *sub;
@@ -2315,7 +2316,7 @@ uiLayout *uiTemplateGpencilModifier(uiLayout *layout, bContext *UNUSED(C), Point
static uiLayout *gpencil_draw_shaderfx(uiLayout *layout, Object *ob, ShaderFxData *md)
{
- const ShaderFxTypeInfo *mti = BKE_shaderfxType_getInfo(md->type);
+ const ShaderFxTypeInfo *mti = BKE_shaderfx_get_info(md->type);
PointerRNA ptr;
uiBlock *block;
uiLayout *box, *column, *row, *sub;
@@ -2432,21 +2433,196 @@ uiLayout *uiTemplateShaderFx(uiLayout *layout, bContext *UNUSED(C), PointerRNA *
/** \} */
/* -------------------------------------------------------------------- */
-/** \name Operator Redo Buttons Template
+/** \name Operator Property Buttons Template
* \{ */
-static void template_operator_redo_property_buts_draw(
- const bContext *C, wmOperator *op, uiLayout *layout, int layout_flags, bool *r_has_advanced)
+typedef struct uiTemplateOperatorPropertyPollParam {
+ const bContext *C;
+ wmOperator *op;
+ short flag;
+} uiTemplateOperatorPropertyPollParam;
+
+#ifdef USE_OP_RESET_BUT
+static void ui_layout_operator_buts__reset_cb(bContext *UNUSED(C),
+ void *op_pt,
+ void *UNUSED(arg_dummy2))
+{
+ WM_operator_properties_reset((wmOperator *)op_pt);
+}
+#endif
+
+static bool ui_layout_operator_buts_poll_property(struct PointerRNA *UNUSED(ptr),
+ struct PropertyRNA *prop,
+ void *user_data)
+{
+ uiTemplateOperatorPropertyPollParam *params = user_data;
+
+ if ((params->flag & UI_TEMPLATE_OP_PROPS_HIDE_ADVANCED) &&
+ (RNA_property_tags(prop) & OP_PROP_TAG_ADVANCED)) {
+ return false;
+ }
+ return params->op->type->poll_property(params->C, params->op, prop);
+}
+
+static eAutoPropButsReturn template_operator_property_buts_draw_single(
+ const bContext *C,
+ wmOperator *op,
+ uiLayout *layout,
+ const eButLabelAlign label_align,
+ int layout_flags)
+{
+ uiBlock *block = uiLayoutGetBlock(layout);
+ eAutoPropButsReturn return_info = 0;
+
+ if (!op->properties) {
+ IDPropertyTemplate val = {0};
+ op->properties = IDP_New(IDP_GROUP, &val, "wmOperatorProperties");
+ }
+
+ /* poll() on this operator may still fail,
+ * at the moment there is no nice feedback when this happens just fails silently. */
+ if (!WM_operator_repeat_check(C, op)) {
+ UI_block_lock_set(block, true, "Operator can't' redo");
+ return return_info;
+ }
+ else {
+ /* useful for macros where only one of the steps can't be re-done */
+ UI_block_lock_clear(block);
+ }
+
+ if (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_TITLE) {
+ uiItemL(layout, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
+ }
+
+ /* menu */
+ if (op->type->flag & OPTYPE_PRESET) {
+ /* XXX, no simple way to get WM_MT_operator_presets.bl_label
+ * from python! Label remains the same always! */
+ PointerRNA op_ptr;
+ uiLayout *row;
+
+ block->ui_operator = op;
+
+ row = uiLayoutRow(layout, true);
+ uiItemM(row, "WM_MT_operator_presets", NULL, ICON_NONE);
+
+ wmOperatorType *ot = WM_operatortype_find("WM_OT_operator_preset_add", false);
+ uiItemFullO_ptr(row, ot, "", ICON_ADD, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
+ RNA_string_set(&op_ptr, "operator", op->type->idname);
+
+ uiItemFullO_ptr(row, ot, "", ICON_REMOVE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
+ RNA_string_set(&op_ptr, "operator", op->type->idname);
+ RNA_boolean_set(&op_ptr, "remove_active", true);
+ }
+
+ if (op->type->ui) {
+ op->layout = layout;
+ op->type->ui((bContext *)C, op);
+ op->layout = NULL;
+
+ /* UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too. We could
+ * allow ot.ui callback to return this, but not needed right now. */
+ }
+ else {
+ wmWindowManager *wm = CTX_wm_manager(C);
+ PointerRNA ptr;
+ uiTemplateOperatorPropertyPollParam user_data = {.C = C, .op = op, .flag = layout_flags};
+ const bool use_prop_split = (layout_flags & UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT) == 0;
+
+ RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
+
+ uiLayoutSetPropSep(layout, use_prop_split);
+ uiLayoutSetPropDecorate(layout, false);
+
+ /* main draw call */
+ return_info = uiDefAutoButsRNA(
+ layout,
+ &ptr,
+ op->type->poll_property ? ui_layout_operator_buts_poll_property : NULL,
+ op->type->poll_property ? &user_data : NULL,
+ op->type->prop,
+ label_align,
+ (layout_flags & UI_TEMPLATE_OP_PROPS_COMPACT));
+
+ if ((return_info & UI_PROP_BUTS_NONE_ADDED) &&
+ (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_EMPTY)) {
+ uiItemL(layout, IFACE_("No Properties"), ICON_NONE);
+ }
+ }
+
+#ifdef USE_OP_RESET_BUT
+ /* its possible that reset can do nothing if all have PROP_SKIP_SAVE enabled
+ * but this is not so important if this button is drawn in those cases
+ * (which isn't all that likely anyway) - campbell */
+ if (op->properties->len) {
+ uiBut *but;
+ uiLayout *col; /* needed to avoid alignment errors with previous buttons */
+
+ col = uiLayoutColumn(layout, false);
+ block = uiLayoutGetBlock(col);
+ but = uiDefIconTextBut(block,
+ UI_BTYPE_BUT,
+ 0,
+ ICON_FILE_REFRESH,
+ IFACE_("Reset"),
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ NULL,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ TIP_("Reset operator defaults"));
+ UI_but_func_set(but, ui_layout_operator_buts__reset_cb, op, NULL);
+ }
+#endif
+
+ /* set various special settings for buttons */
+
+ /* Only do this if we're not refreshing an existing UI. */
+ if (block->oldblock == NULL) {
+ const bool is_popup = (block->flag & UI_BLOCK_KEEP_OPEN) != 0;
+ uiBut *but;
+
+ for (but = block->buttons.first; but; but = but->next) {
+ /* no undo for buttons for operator redo panels */
+ UI_but_flag_disable(but, UI_BUT_UNDO);
+
+ /* only for popups, see [#36109] */
+
+ /* if button is operator's default property, and a text-field, enable focus for it
+ * - this is used for allowing operators with popups to rename stuff with fewer clicks
+ */
+ if (is_popup) {
+ if ((but->rnaprop == op->type->prop) && (but->type == UI_BTYPE_TEXT)) {
+ UI_but_focus_on_enter_event(CTX_wm_window(C), but);
+ }
+ }
+ }
+ }
+
+ return return_info;
+}
+
+static void template_operator_property_buts_draw_recursive(const bContext *C,
+ wmOperator *op,
+ uiLayout *layout,
+ const eButLabelAlign label_align,
+ int layout_flags,
+ bool *r_has_advanced)
{
if (op->type->flag & OPTYPE_MACRO) {
LISTBASE_FOREACH (wmOperator *, macro_op, &op->macro) {
- template_operator_redo_property_buts_draw(C, macro_op, layout, layout_flags, r_has_advanced);
+ template_operator_property_buts_draw_recursive(
+ C, macro_op, layout, label_align, layout_flags, r_has_advanced);
}
}
else {
/* Might want to make label_align adjustable somehow. */
- eAutoPropButsReturn return_info = uiTemplateOperatorPropertyButs(
- C, layout, op, UI_BUT_LABEL_ALIGN_NONE, layout_flags);
+ eAutoPropButsReturn return_info = template_operator_property_buts_draw_single(
+ C, op, layout, label_align, layout_flags);
if (return_info & UI_PROP_BUTS_ANY_FAILED_CHECK) {
if (r_has_advanced) {
*r_has_advanced = true;
@@ -2455,6 +2631,61 @@ static void template_operator_redo_property_buts_draw(
}
}
+static bool ui_layout_operator_properties_only_booleans(const bContext *C,
+ wmWindowManager *wm,
+ wmOperator *op,
+ int layout_flags)
+{
+ if (op->type->flag & OPTYPE_MACRO) {
+ LISTBASE_FOREACH (wmOperator *, macro_op, &op->macro) {
+ if (!ui_layout_operator_properties_only_booleans(C, wm, macro_op, layout_flags)) {
+ return false;
+ }
+ }
+ }
+ else {
+ uiTemplateOperatorPropertyPollParam user_data = {.C = C, .op = op, .flag = layout_flags};
+ PointerRNA ptr;
+
+ RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
+
+ RNA_STRUCT_BEGIN (&ptr, prop) {
+ if (RNA_property_flag(prop) & PROP_HIDDEN) {
+ continue;
+ }
+ if (op->type->poll_property &&
+ !ui_layout_operator_buts_poll_property(&ptr, prop, &user_data)) {
+ continue;
+ }
+ if (RNA_property_type(prop) != PROP_BOOLEAN) {
+ return false;
+ }
+ }
+ RNA_STRUCT_END;
+ }
+
+ return true;
+}
+
+/**
+ * Draw Operator property buttons for redoing execution with different settings.
+ * This function does not initialize the layout,
+ * functions can be called on the layout before and after.
+ */
+void uiTemplateOperatorPropertyButs(
+ const bContext *C, uiLayout *layout, wmOperator *op, eButLabelAlign label_align, short flag)
+{
+ wmWindowManager *wm = CTX_wm_manager(C);
+
+ /* If there are only checkbox items, don't use split layout by default. It looks weird if the
+ * checkboxes only use half the width. */
+ if (ui_layout_operator_properties_only_booleans(C, wm, op, flag)) {
+ flag |= UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT;
+ }
+
+ template_operator_property_buts_draw_recursive(C, op, layout, label_align, flag, NULL);
+}
+
void uiTemplateOperatorRedoProperties(uiLayout *layout, const bContext *C)
{
wmOperator *op = WM_operator_last_redo(C);
@@ -2487,8 +2718,8 @@ void uiTemplateOperatorRedoProperties(uiLayout *layout, const bContext *C)
#endif
UI_block_func_handle_set(block, ED_undo_operator_repeat_cb_evt, op);
- template_operator_redo_property_buts_draw(
- C, op, layout, layout_flags, NULL /* &has_advanced */);
+ template_operator_property_buts_draw_recursive(
+ C, op, layout, UI_BUT_LABEL_ALIGN_NONE, layout_flags, NULL /* &has_advanced */);
/* Warning! this leaves the handle function for any other users of this block. */
#if 0
@@ -6583,952 +6814,6 @@ void uiTemplateList(uiLayout *layout,
/** \} */
/* -------------------------------------------------------------------- */
-/** \name Operator Search Template
- * \{ */
-
-static void operator_call_cb(bContext *C, void *UNUSED(arg1), void *arg2)
-{
- wmOperatorType *ot = arg2;
-
- if (ot) {
- WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL);
- }
-}
-
-static bool has_word_prefix(const char *haystack, const char *needle, size_t needle_len)
-{
- const char *match = BLI_strncasestr(haystack, needle, needle_len);
- if (match) {
- if ((match == haystack) || (*(match - 1) == ' ') || ispunct(*(match - 1))) {
- return true;
- }
- else {
- return has_word_prefix(match + 1, needle, needle_len);
- }
- }
- else {
- return false;
- }
-}
-
-static void operator_search_cb(const bContext *C,
- void *UNUSED(arg),
- const char *str,
- uiSearchItems *items)
-{
- GHashIterator iter;
- const size_t str_len = strlen(str);
- const int words_max = (str_len / 2) + 1;
- int(*words)[2] = BLI_array_alloca(words, words_max);
-
- const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
-
- for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter);
- BLI_ghashIterator_step(&iter)) {
- wmOperatorType *ot = BLI_ghashIterator_getValue(&iter);
- const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name);
- int index;
-
- if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) {
- continue;
- }
-
- /* match name against all search words */
- for (index = 0; index < words_len; index++) {
- if (!has_word_prefix(ot_ui_name, str + words[index][0], words[index][1])) {
- break;
- }
- }
-
- if (index == words_len) {
- if (WM_operator_poll((bContext *)C, ot)) {
- char name[256];
- int len = strlen(ot_ui_name);
-
- /* display name for menu, can hold hotkey */
- BLI_strncpy(name, ot_ui_name, sizeof(name));
-
- /* check for hotkey */
- if (len < sizeof(name) - 6) {
- if (WM_key_event_operator_string(C,
- ot->idname,
- WM_OP_EXEC_DEFAULT,
- NULL,
- true,
- &name[len + 1],
- sizeof(name) - len - 1)) {
- name[len] = UI_SEP_CHAR;
- }
- }
-
- if (!UI_search_item_add(items, name, ot, ICON_NONE, 0)) {
- break;
- }
- }
- }
- }
-}
-
-void UI_but_func_operator_search(uiBut *but)
-{
- UI_but_func_search_set(but,
- ui_searchbox_create_operator,
- operator_search_cb,
- NULL,
- false,
- operator_call_cb,
- NULL,
- NULL);
-}
-
-void uiTemplateOperatorSearch(uiLayout *layout)
-{
- uiBlock *block;
- uiBut *but;
- static char search[256] = "";
-
- block = uiLayoutGetBlock(layout);
- UI_block_layout_set_current(block, layout);
-
- but = uiDefSearchBut(
- block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
- UI_but_func_operator_search(but);
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Menu Search Template
- * \{ */
-
-/* Unicode arrow. */
-#define MENU_SEP "\xe2\x96\xb6"
-
-struct MenuSearch_Parent {
- struct MenuSearch_Parent *parent;
- MenuType *parent_mt;
- /* Set while writing menu items only. */
- struct MenuSearch_Parent *temp_child;
- const char *drawstr;
-};
-
-struct MenuSearch_Item {
- struct MenuSearch_Item *next, *prev;
- const char *drawstr;
- const char *drawwstr_full;
- /** Support a single level sub-menu nesting (for operator buttons that expand). */
- const char *drawstr_submenu;
- int icon;
- int state;
-
- struct MenuSearch_Parent *menu_parent;
- MenuType *mt;
-
- enum {
- MENU_SEARCH_TYPE_OP = 1,
- MENU_SEARCH_TYPE_RNA = 2,
- } type;
-
- union {
- /* Operator menu item. */
- struct {
- wmOperatorType *type;
- PointerRNA *opptr;
- short opcontext;
- bContextStore *context;
- } op;
-
- /* Property (only for check-boxe/boolean). */
- struct {
- PointerRNA ptr;
- PropertyRNA *prop;
- int index;
- /** Only for enum buttons. */
- int enum_value;
- } rna;
- };
-};
-
-struct MenuSearch_Data {
- /** MenuSearch_Item */
- ListBase items;
- /** Use for all small allocations. */
- MemArena *memarena;
-};
-
-static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v)
-{
- const struct MenuSearch_Item *menu_item_a = menu_item_a_v;
- const struct MenuSearch_Item *menu_item_b = menu_item_b_v;
- return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full);
-}
-
-static const char *strdup_memarena(MemArena *memarena, const char *str)
-{
- const uint str_size = strlen(str) + 1;
- char *str_dst = BLI_memarena_alloc(memarena, str_size);
- memcpy(str_dst, str, str_size);
- return str_dst;
-}
-
-static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str)
-{
- const uint str_size = BLI_dynstr_get_len(dyn_str) + 1;
- char *str_dst = BLI_memarena_alloc(memarena, str_size);
- BLI_dynstr_get_cstring_ex(dyn_str, str_dst);
- return str_dst;
-}
-
-static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data,
- MemArena *memarena,
- struct MenuType *mt,
- const char *drawstr_submenu,
- uiBut *but)
-{
- struct MenuSearch_Item *item = NULL;
- if (but->optype != NULL) {
- item = BLI_memarena_calloc(memarena, sizeof(*item));
- item->type = MENU_SEARCH_TYPE_OP;
-
- item->op.type = but->optype;
- item->op.opcontext = but->opcontext;
- item->op.context = but->context;
- item->op.opptr = but->opptr;
- but->opptr = NULL;
- }
- else if (but->rnaprop != NULL) {
- const int prop_type = RNA_property_type(but->rnaprop);
- if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) {
- /* Note that these buttons are not prevented,
- * but aren't typically used in menus. */
- printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n",
- but->drawstr,
- mt->idname,
- prop_type);
- }
- else {
- item = BLI_memarena_calloc(memarena, sizeof(*item));
- item->type = MENU_SEARCH_TYPE_RNA;
-
- item->rna.ptr = but->rnapoin;
- item->rna.prop = but->rnaprop;
- item->rna.index = but->rnaindex;
-
- if (prop_type == PROP_ENUM) {
- item->rna.enum_value = (int)but->hardmax;
- }
- }
- }
-
- if (item != NULL) {
- /* Handle shared settings. */
- item->drawstr = strdup_memarena(memarena, but->drawstr);
- item->icon = ui_but_icon(but);
- item->state = (but->flag & (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT));
- item->mt = mt;
- item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL;
- BLI_addtail(&data->items, item);
- return true;
- }
-
- return false;
-}
-
-/**
- * Populate \a menu_stack with menus from inspecting active key-maps for this context.
- */
-static void menu_types_add_from_keymap_items(bContext *C,
- wmWindow *win,
- ScrArea *area,
- ARegion *region,
- LinkNode **menuid_stack_p,
- GHash *menu_to_kmi,
- GSet *menu_tagged)
-{
- wmWindowManager *wm = CTX_wm_manager(C);
- ListBase *handlers[] = {
- region ? &region->handlers : NULL,
- area ? &area->handlers : NULL,
- &win->handlers,
- };
-
- for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
- if (handlers[handler_index] == NULL) {
- continue;
- }
- LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) {
- /* During this loop, ui handlers for nested menus can tag multiple handlers free. */
- if (handler_base->flag & WM_HANDLER_DO_FREE) {
- continue;
- }
- if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) {
- continue;
- }
-
- else if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) {
- wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
- wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler);
- if (keymap && WM_keymap_poll(C, keymap)) {
- LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
- if (kmi->flag & KMI_INACTIVE) {
- continue;
- }
- if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) {
- char menu_idname[MAX_NAME];
- RNA_string_get(kmi->ptr, "name", menu_idname);
- MenuType *mt = WM_menutype_find(menu_idname, false);
-
- if (mt && BLI_gset_add(menu_tagged, mt)) {
- /* Unlikely, but possible this will be included twice. */
- BLI_linklist_prepend(menuid_stack_p, mt);
-
- void **kmi_p;
- if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) {
- *kmi_p = kmi;
- }
- }
- }
- }
- }
- }
- }
- }
-}
-
-/**
- * Create #MenuSearch_Data by inspecting the current context, this uses two methods:
- *
- * - Look-up pre-defined editor-menus.
- * - Look-up key-map items which call menus.
- */
-static struct MenuSearch_Data *menu_items_from_ui_create(bContext *C,
- wmWindow *win,
- ScrArea *area,
- ARegion *region)
-{
- MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
- /** Map (#MenuType to #MenuSearch_Parent) */
- GHash *menu_parent_map = BLI_ghash_ptr_new(__func__);
- GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__);
- const uiStyle *style = UI_style_get_dpi();
-
- /* Convert into non-ui structure. */
- struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__);
-
- DynStr *dyn_str = BLI_dynstr_new_memarena();
-
- /* Use a stack of menus to handle and discover new menus in passes. */
- LinkNode *menu_stack = NULL;
-
- /* Tag menu types not to add, either because they have already been added
- * or they have been blacklisted.
- * Set of #MenuType. */
- GSet *menu_tagged = BLI_gset_ptr_new(__func__);
- /** Map (#MenuType -> #wmKeyMapItem). */
- GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__);
-
- /* Blacklist menus we don't want to show. */
- {
- const char *idname_array[] = {
- /* While we could include this, it's just showing filenames to load. */
- "TOPBAR_MT_file_open_recent",
- };
- for (int i = 0; i < ARRAY_SIZE(idname_array); i++) {
- MenuType *mt = WM_menutype_find(idname_array[i], false);
- if (mt != NULL) {
- BLI_gset_add(menu_tagged, mt);
- }
- }
- }
-
- /* Populate menus from the editors,
- * note that we could create a fake header, draw the header and extract the menus
- * from the buttons, however this is quite involved and can be avoided as by convention
- * each space-type has a single root-menu that headers use. */
- {
- const char *idname_array[] = {
- "TOPBAR_MT_editor_menus",
- /* Optional second menu for the space-type. */
- NULL,
- };
- int idname_array_len = 1;
-
-#define SPACE_MENU_MAP(space_type, menu_id) \
- case space_type: \
- idname_array[idname_array_len++] = menu_id; \
- break
-#define SPACE_MENU_NOP(space_type) \
- case space_type: \
- break
-
- if (area != NULL) {
- switch (area->spacetype) {
- SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus");
- SPACE_MENU_NOP(SPACE_PROPERTIES);
- SPACE_MENU_MAP(SPACE_FILE, "FILE_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_ACTION, "DOPESHEET_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
- SPACE_MENU_MAP(SPACE_CLIP,
- (((const SpaceClip *)area->spacedata.first)->mode == SC_MODE_TRACKING) ?
- "CLIP_MT_tracking_editor_menus" :
- "CLIP_MT_masking_editor_menus");
- SPACE_MENU_NOP(SPACE_TOPBAR);
- SPACE_MENU_NOP(SPACE_STATUSBAR);
- default:
- printf("Unknown space type '%d'\n", area->spacetype);
- }
- }
- for (int i = 0; i < idname_array_len; i++) {
- MenuType *mt = WM_menutype_find(idname_array[i], false);
- if (mt != NULL) {
- BLI_linklist_prepend(&menu_stack, mt);
- BLI_gset_add(menu_tagged, mt);
- }
- }
- }
-#undef SPACE_MENU_MAP
-#undef SPACE_MENU_NOP
-
- bool has_keymap_menu_items = false;
-
- GHashIterator iter;
-
- while (menu_stack != NULL) {
- MenuType *mt = BLI_linklist_pop(&menu_stack);
- if (!WM_menutype_poll(C, mt)) {
- continue;
- }
-
- uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
- uiLayout *layout = UI_block_layout(
- block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
-
- UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
-
- uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN);
- UI_menutype_draw(C, mt, layout);
-
- UI_block_end(C, block);
-
- LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
- MenuType *mt_from_but = NULL;
- /* Support menu titles with dynamic from initial labels
- * (used by edit-mesh context menu). */
- if (but->type == UI_BTYPE_LABEL) {
-
- /* Check if the label is the title. */
- uiBut *but_test = but->prev;
- while (but_test && but_test->type == UI_BTYPE_SEPR) {
- but_test = but_test->prev;
- }
-
- if (but_test == NULL) {
- BLI_ghash_insert(
- menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr));
- }
- }
- else if (menu_items_from_ui_create_item_from_button(data, memarena, mt, NULL, but)) {
- /* pass */
- }
- else if ((mt_from_but = UI_but_menutype_get(but))) {
-
- if (BLI_gset_add(menu_tagged, mt_from_but)) {
- BLI_linklist_prepend(&menu_stack, mt_from_but);
- }
-
- if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) {
- struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena,
- sizeof(*menu_parent));
- /* Use brackets for menu key shortcuts,
- * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)".
- * This is needed so we don't right align sub-menu contents
- * we only want to do that for the last menu item, not the path that leads to it.
- */
- const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ?
- strrchr(but->drawstr, UI_SEP_CHAR) :
- NULL;
- bool drawstr_is_empty = false;
- if (drawstr_sep != NULL) {
- BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
- /* Detect empty string, fallback to menu name. */
- const char *drawstr = but->drawstr;
- int drawstr_len = drawstr_sep - but->drawstr;
- if (UNLIKELY(drawstr_len == 0)) {
- drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
- drawstr_len = strlen(drawstr);
- if (drawstr[0] == '\0') {
- drawstr_is_empty = true;
- }
- }
- BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len);
- BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1);
- menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str);
- BLI_dynstr_clear(dyn_str);
- }
- else {
- const char *drawstr = but->drawstr;
- if (UNLIKELY(drawstr[0] == '\0')) {
- drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label);
- if (drawstr[0] == '\0') {
- drawstr_is_empty = true;
- }
- }
- menu_parent->drawstr = strdup_memarena(memarena, drawstr);
- }
- menu_parent->parent_mt = mt;
- BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent);
-
- if (drawstr_is_empty) {
- printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname);
- }
- }
- }
- else if (but->menu_create_func != NULL) {
- /* A non 'MenuType' menu button. */
-
- /* Only expand one level deep, this is mainly for expanding operator menus. */
- const char *drawstr_submenu = but->drawstr;
-
- /* +1 to avoid overlap with the current 'block'. */
- uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS);
- uiLayout *sub_layout = UI_block_layout(
- sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
-
- UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS);
-
- uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN);
-
- but->menu_create_func(C, sub_layout, but->poin);
-
- UI_block_end(C, sub_block);
-
- LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) {
- menu_items_from_ui_create_item_from_button(data, memarena, mt, drawstr_submenu, sub_but);
- }
-
- if (region) {
- BLI_remlink(&region->uiblocks, sub_block);
- }
- UI_block_free(NULL, sub_block);
- }
- }
- if (region) {
- BLI_remlink(&region->uiblocks, block);
- }
- UI_block_free(NULL, block);
-
- /* Add key-map items as a second pass,
- * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */
- if ((menu_stack == NULL) && (has_keymap_menu_items == false)) {
- has_keymap_menu_items = true;
- menu_types_add_from_keymap_items(
- C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged);
- }
- }
-
- LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
- item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt);
- }
-
- GHASH_ITER (iter, menu_parent_map) {
- struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter);
- menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt);
- }
-
- /* NOTE: currently this builds the full path for each menu item,
- * that could be moved into the parent menu. */
-
- /* Set names as full paths. */
- LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
- if (item->menu_parent != NULL) {
- struct MenuSearch_Parent *menu_parent = item->menu_parent;
- menu_parent->temp_child = NULL;
- while (menu_parent && menu_parent->parent) {
- menu_parent->parent->temp_child = menu_parent;
- menu_parent = menu_parent->parent;
- }
- BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
- while (menu_parent) {
- BLI_dynstr_append(dyn_str, menu_parent->drawstr);
- BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
- menu_parent = menu_parent->temp_child;
- }
- }
- else {
- BLI_assert(BLI_dynstr_get_len(dyn_str) == 0);
- const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt);
- if (drawstr == NULL) {
- drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label);
- }
- BLI_dynstr_append(dyn_str, drawstr);
-
- wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt);
- if (kmi != NULL) {
- char kmi_str[128];
- WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str));
- BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
- }
-
- BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
- }
-
- /* Optional nested menu. */
- if (item->drawstr_submenu != NULL) {
- BLI_dynstr_append(dyn_str, item->drawstr_submenu);
- BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
- }
-
- BLI_dynstr_append(dyn_str, item->drawstr);
-
- item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str);
- BLI_dynstr_clear(dyn_str);
- }
- BLI_dynstr_free(dyn_str);
-
- /* Finally sort menu items.
- *
- * Note: we might want to keep the in-menu order, for now sort all. */
- BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full);
-
- BLI_ghash_free(menu_parent_map, NULL, NULL);
- BLI_ghash_free(menu_display_name_map, NULL, NULL);
-
- BLI_ghash_free(menu_to_kmi, NULL, NULL);
-
- BLI_gset_free(menu_tagged, NULL);
-
- data->memarena = memarena;
-
- return data;
-}
-
-static void menu_items_from_ui_destroy(void *data_v)
-{
- struct MenuSearch_Data *data = data_v;
- LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) {
- switch (item->type) {
- case MENU_SEARCH_TYPE_OP: {
- if (item->op.opptr != NULL) {
- WM_operator_properties_free(item->op.opptr);
- MEM_freeN(item->op.opptr);
- }
- }
- case MENU_SEARCH_TYPE_RNA: {
- break;
- }
- }
- }
-
- BLI_memarena_free(data->memarena);
-
- MEM_freeN(data);
-}
-
-static void menu_call_fn(bContext *C, void *UNUSED(arg1), void *arg2)
-{
- struct MenuSearch_Item *item = arg2;
- if (item == NULL) {
- return;
- }
- if (item->state & UI_BUT_DISABLED) {
- return;
- }
-
- switch (item->type) {
- case MENU_SEARCH_TYPE_OP: {
- CTX_store_set(C, item->op.context);
- WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr);
- CTX_store_set(C, NULL);
- break;
- }
- case MENU_SEARCH_TYPE_RNA: {
- PointerRNA *ptr = &item->rna.ptr;
- PropertyRNA *prop = item->rna.prop;
- int index = item->rna.index;
- const int prop_type = RNA_property_type(prop);
- bool changed = false;
-
- if (prop_type == PROP_BOOLEAN) {
- const bool is_array = RNA_property_array_check(prop);
- if (is_array) {
- const bool value = RNA_property_boolean_get_index(ptr, prop, index);
- RNA_property_boolean_set_index(ptr, prop, index, !value);
- }
- else {
- const bool value = RNA_property_boolean_get(ptr, prop);
- RNA_property_boolean_set(ptr, prop, !value);
- }
- changed = true;
- }
- else if (prop_type == PROP_ENUM) {
- RNA_property_enum_set(ptr, prop, item->rna.enum_value);
- changed = true;
- }
-
- if (changed) {
- RNA_property_update(C, ptr, prop);
- }
- break;
- }
- }
-}
-
-static void menu_search_cb(const bContext *UNUSED(C),
- void *arg,
- const char *str,
- uiSearchItems *items)
-{
- struct MenuSearch_Data *data = arg;
- const size_t str_len = strlen(str);
- const int words_max = (str_len / 2) + 1;
- int(*words)[2] = BLI_array_alloca(words, words_max);
-
- const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max);
-
- for (struct MenuSearch_Item *item = data->items.first; item; item = item->next) {
- int index;
-
- /* match name against all search words */
- for (index = 0; index < words_len; index++) {
- if (!has_word_prefix(item->drawwstr_full, str + words[index][0], words[index][1])) {
- break;
- }
- }
-
- if (index == words_len) {
- if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state)) {
- break;
- }
- }
- }
-}
-
-void UI_but_func_menu_search(uiBut *but)
-{
- bContext *C = but->block->evil_C;
- wmWindow *win = CTX_wm_window(C);
- ScrArea *area = CTX_wm_area(C);
- ARegion *region = CTX_wm_region(C);
- struct MenuSearch_Data *data = menu_items_from_ui_create(C, win, area, region);
- UI_but_func_search_set(but,
- ui_searchbox_create_menu,
- menu_search_cb,
- data,
- menu_items_from_ui_destroy,
- menu_call_fn,
- MENU_SEP,
- NULL);
-}
-
-void uiTemplateMenuSearch(uiLayout *layout)
-{
- uiBlock *block;
- uiBut *but;
- static char search[256] = "";
-
- block = uiLayoutGetBlock(layout);
- UI_block_layout_set_current(block, layout);
-
- but = uiDefSearchBut(
- block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, "");
- UI_but_func_menu_search(but);
-}
-
-#undef MENU_SEP
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
-/** \name Operator Redo Properties Template
- * \{ */
-
-#ifdef USE_OP_RESET_BUT
-static void ui_layout_operator_buts__reset_cb(bContext *UNUSED(C),
- void *op_pt,
- void *UNUSED(arg_dummy2))
-{
- WM_operator_properties_reset((wmOperator *)op_pt);
-}
-#endif
-
-struct uiTemplateOperatorPropertyPollParam {
- const bContext *C;
- wmOperator *op;
- short flag;
-};
-
-static bool ui_layout_operator_buts_poll_property(struct PointerRNA *UNUSED(ptr),
- struct PropertyRNA *prop,
- void *user_data)
-{
- struct uiTemplateOperatorPropertyPollParam *params = user_data;
- if ((params->flag & UI_TEMPLATE_OP_PROPS_HIDE_ADVANCED) &&
- (RNA_property_tags(prop) & OP_PROP_TAG_ADVANCED)) {
- return false;
- }
- return params->op->type->poll_property(params->C, params->op, prop);
-}
-
-/**
- * Draw Operator property buttons for redoing execution with different settings.
- * This function does not initialize the layout,
- * functions can be called on the layout before and after.
- */
-eAutoPropButsReturn uiTemplateOperatorPropertyButs(const bContext *C,
- uiLayout *layout,
- wmOperator *op,
- const eButLabelAlign label_align,
- const short flag)
-{
- uiBlock *block = uiLayoutGetBlock(layout);
- eAutoPropButsReturn return_info = 0;
-
- if (!op->properties) {
- IDPropertyTemplate val = {0};
- op->properties = IDP_New(IDP_GROUP, &val, "wmOperatorProperties");
- }
-
- /* poll() on this operator may still fail,
- * at the moment there is no nice feedback when this happens just fails silently. */
- if (!WM_operator_repeat_check(C, op)) {
- UI_block_lock_set(block, true, "Operator can't' redo");
- return return_info;
- }
- else {
- /* useful for macros where only one of the steps can't be re-done */
- UI_block_lock_clear(block);
- }
-
- if (flag & UI_TEMPLATE_OP_PROPS_SHOW_TITLE) {
- uiItemL(layout, WM_operatortype_name(op->type, op->ptr), ICON_NONE);
- }
-
- /* menu */
- if (op->type->flag & OPTYPE_PRESET) {
- /* XXX, no simple way to get WM_MT_operator_presets.bl_label
- * from python! Label remains the same always! */
- PointerRNA op_ptr;
- uiLayout *row;
-
- block->ui_operator = op;
-
- row = uiLayoutRow(layout, true);
- uiItemM(row, "WM_MT_operator_presets", NULL, ICON_NONE);
-
- wmOperatorType *ot = WM_operatortype_find("WM_OT_operator_preset_add", false);
- uiItemFullO_ptr(row, ot, "", ICON_ADD, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
- RNA_string_set(&op_ptr, "operator", op->type->idname);
-
- uiItemFullO_ptr(row, ot, "", ICON_REMOVE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr);
- RNA_string_set(&op_ptr, "operator", op->type->idname);
- RNA_boolean_set(&op_ptr, "remove_active", true);
- }
-
- if (op->type->ui) {
- op->layout = layout;
- op->type->ui((bContext *)C, op);
- op->layout = NULL;
-
- /* UI_LAYOUT_OP_SHOW_EMPTY ignored. return_info is ignored too. We could
- * allow ot.ui callback to return this, but not needed right now. */
- }
- else {
- wmWindowManager *wm = CTX_wm_manager(C);
- PointerRNA ptr;
- struct uiTemplateOperatorPropertyPollParam user_data = {
- .C = C,
- .op = op,
- .flag = flag,
- };
-
- RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr);
-
- uiLayoutSetPropSep(layout, true);
- uiLayoutSetPropDecorate(layout, false);
-
- /* main draw call */
- return_info = uiDefAutoButsRNA(
- layout,
- &ptr,
- op->type->poll_property ? ui_layout_operator_buts_poll_property : NULL,
- op->type->poll_property ? &user_data : NULL,
- op->type->prop,
- label_align,
- (flag & UI_TEMPLATE_OP_PROPS_COMPACT));
-
- if ((return_info & UI_PROP_BUTS_NONE_ADDED) && (flag & UI_TEMPLATE_OP_PROPS_SHOW_EMPTY)) {
- uiItemL(layout, IFACE_("No Properties"), ICON_NONE);
- }
- }
-
-#ifdef USE_OP_RESET_BUT
- /* its possible that reset can do nothing if all have PROP_SKIP_SAVE enabled
- * but this is not so important if this button is drawn in those cases
- * (which isn't all that likely anyway) - campbell */
- if (op->properties->len) {
- uiBut *but;
- uiLayout *col; /* needed to avoid alignment errors with previous buttons */
-
- col = uiLayoutColumn(layout, false);
- block = uiLayoutGetBlock(col);
- but = uiDefIconTextBut(block,
- UI_BTYPE_BUT,
- 0,
- ICON_FILE_REFRESH,
- IFACE_("Reset"),
- 0,
- 0,
- UI_UNIT_X,
- UI_UNIT_Y,
- NULL,
- 0.0,
- 0.0,
- 0.0,
- 0.0,
- TIP_("Reset operator defaults"));
- UI_but_func_set(but, ui_layout_operator_buts__reset_cb, op, NULL);
- }
-#endif
-
- /* set various special settings for buttons */
-
- /* Only do this if we're not refreshing an existing UI. */
- if (block->oldblock == NULL) {
- const bool is_popup = (block->flag & UI_BLOCK_KEEP_OPEN) != 0;
- uiBut *but;
-
- for (but = block->buttons.first; but; but = but->next) {
- /* no undo for buttons for operator redo panels */
- UI_but_flag_disable(but, UI_BUT_UNDO);
-
- /* only for popups, see [#36109] */
-
- /* if button is operator's default property, and a text-field, enable focus for it
- * - this is used for allowing operators with popups to rename stuff with fewer clicks
- */
- if (is_popup) {
- if ((but->rnaprop == op->type->prop) && (but->type == UI_BTYPE_TEXT)) {
- UI_but_focus_on_enter_event(CTX_wm_window(C), but);
- }
- }
- }
- }
-
- return return_info;
-}
-
-/** \} */
-
-/* -------------------------------------------------------------------- */
/** \name Running Jobs Template
* \{ */
diff --git a/source/blender/editors/interface/interface_undo.c b/source/blender/editors/interface/interface_undo.c
new file mode 100644
index 00000000000..016bc4159db
--- /dev/null
+++ b/source/blender/editors/interface/interface_undo.c
@@ -0,0 +1,139 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edinterface
+ *
+ * Undo stack to use for UI widgets that manage their own editing state.
+ */
+
+#include <string.h>
+
+#include "BLI_listbase.h"
+
+#include "DNA_listBase.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "interface_intern.h"
+
+/* -------------------------------------------------------------------- */
+/** \name Text Field Undo Stack
+ * \{ */
+
+typedef struct uiUndoStack_Text_State {
+ struct uiUndoStack_Text_State *next, *prev;
+ int cursor_index;
+ char text[0];
+} uiUndoStack_Text_State;
+
+typedef struct uiUndoStack_Text {
+ ListBase states;
+ uiUndoStack_Text_State *current;
+} uiUndoStack_Text;
+
+static const char *ui_textedit_undo_impl(uiUndoStack_Text *stack, int *r_cursor_index)
+{
+ /* Don't undo if no data has been pushed yet. */
+ if (stack->current == NULL) {
+ return NULL;
+ }
+
+ /* Travel backwards in the stack and copy information to the caller. */
+ if (stack->current->prev != NULL) {
+ stack->current = stack->current->prev;
+
+ *r_cursor_index = stack->current->cursor_index;
+ return stack->current->text;
+ }
+ return NULL;
+}
+
+static const char *ui_textedit_redo_impl(uiUndoStack_Text *stack, int *r_cursor_index)
+{
+ /* Don't redo if no data has been pushed yet. */
+ if (stack->current == NULL) {
+ return NULL;
+ }
+
+ /* Only redo if new data has not been entered since the last undo. */
+ if (stack->current->next) {
+ stack->current = stack->current->next;
+
+ *r_cursor_index = stack->current->cursor_index;
+ return stack->current->text;
+ }
+ return NULL;
+}
+
+const char *ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index)
+{
+ BLI_assert(ELEM(direction, -1, 1));
+ if (direction < 0) {
+ return ui_textedit_undo_impl(stack, r_cursor_index);
+ }
+ else {
+ return ui_textedit_redo_impl(stack, r_cursor_index);
+ }
+}
+
+/**
+ * Push the information in the arguments to a new state in the undo stack.
+ *
+ * \note Currently the total length of the undo stack is not limited.
+ */
+void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index)
+{
+ /* Clear all redo actions from the current state. */
+ if (stack->current != NULL) {
+ while (stack->current->next) {
+ uiUndoStack_Text_State *state = stack->current->next;
+ BLI_remlink(&stack->states, state);
+ MEM_freeN(state);
+ }
+ }
+
+ /* Create the new state */
+ const int text_size = strlen(text) + 1;
+ stack->current = MEM_mallocN(sizeof(uiUndoStack_Text_State) + text_size, __func__);
+ stack->current->cursor_index = cursor_index;
+ memcpy(stack->current->text, text, text_size);
+ BLI_addtail(&stack->states, stack->current);
+}
+/**
+ * Start the undo stack.
+ *
+ * \note The current state should be pushed immediately after calling this.
+ */
+uiUndoStack_Text *ui_textedit_undo_stack_create(void)
+{
+ uiUndoStack_Text *stack = MEM_mallocN(sizeof(uiUndoStack_Text), __func__);
+ stack->current = NULL;
+ BLI_listbase_clear(&stack->states);
+
+ return stack;
+}
+
+void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack)
+{
+ BLI_freelistN(&stack->states);
+ MEM_freeN(stack);
+}
+
+/** \} */
diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c
index 781c8f73b0a..4013e962ce5 100644
--- a/source/blender/editors/interface/interface_utils.c
+++ b/source/blender/editors/interface/interface_utils.c
@@ -22,6 +22,7 @@
*/
#include <assert.h>
+#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -51,6 +52,22 @@
#include "interface_intern.h"
+bool ui_str_has_word_prefix(const char *haystack, const char *needle, size_t needle_len)
+{
+ const char *match = BLI_strncasestr(haystack, needle, needle_len);
+ if (match) {
+ if ((match == haystack) || (*(match - 1) == ' ') || ispunct(*(match - 1))) {
+ return true;
+ }
+ else {
+ return ui_str_has_word_prefix(match + 1, needle, needle_len);
+ }
+ }
+ else {
+ return false;
+ }
+}
+
/*************************** RNA Utilities ******************************/
uiBut *uiDefAutoButR(uiBlock *block,
@@ -294,7 +311,7 @@ eAutoPropButsReturn uiDefAutoButsRNA(uiLayout *layout,
const bool compact)
{
eAutoPropButsReturn return_info = UI_PROP_BUTS_NONE_ADDED;
- uiLayout *split, *col;
+ uiLayout *col;
const char *name;
RNA_STRUCT_BEGIN (ptr, prop) {
@@ -325,19 +342,11 @@ eAutoPropButsReturn uiDefAutoButsRNA(uiLayout *layout,
}
else {
BLI_assert(label_align == UI_BUT_LABEL_ALIGN_SPLIT_COLUMN);
- split = uiLayoutSplit(layout, 0.5f, false);
-
- col = uiLayoutColumn(split, false);
- uiItemL(col, (is_boolean) ? "" : name, ICON_NONE);
- col = uiLayoutColumn(split, false);
+ col = uiLayoutColumn(layout, true);
+ /* Let uiItemFullR() create the split layout. */
+ uiLayoutSetPropSep(col, true);
}
- /* May need to add more cases here.
- * don't override enum flag names */
-
- /* name is shown above, empty name for button below */
- name = (flag & PROP_ENUM_FLAG || is_boolean) ? NULL : "";
-
break;
}
case UI_BUT_LABEL_ALIGN_NONE:
@@ -390,10 +399,10 @@ static int sort_search_items_list(const void *a, const void *b)
}
}
-void ui_rna_collection_search_cb(const struct bContext *C,
- void *arg,
- const char *str,
- uiSearchItems *items)
+void ui_rna_collection_search_update_fn(const struct bContext *C,
+ void *arg,
+ const char *str,
+ uiSearchItems *items)
{
uiRNACollectionSearch *data = arg;
int i = 0, iconid = 0, flag = RNA_property_flag(data->target_prop);
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index ed0f50f4113..4706be205e1 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -41,6 +41,8 @@
#include "BLF_api.h"
+#include "ED_node.h"
+
#include "UI_interface.h"
#include "UI_interface_icons.h"
@@ -2092,15 +2094,18 @@ static void widget_draw_text_ime_underline(const uiFontStyle *fstyle,
#endif /* WITH_INPUT_IME */
static bool widget_draw_text_underline_calc_center_x(const char *UNUSED(str),
- const size_t str_ofs,
- const rcti *glyph_bounds,
- const int glyph_advance_x,
+ const size_t str_step_ofs,
+ const rcti *glyph_step_bounds,
+ const int UNUSED(glyph_advance_x),
+ const rctf *glyph_bounds,
+ const int glyph_bearing[2],
void *user_data)
{
/* The index of the character to get, set to the x-position. */
int *ul_data = user_data;
- if (ul_data[0] == (int)str_ofs) {
- ul_data[1] = glyph_bounds->xmin + (glyph_advance_x / 2);
+ if (ul_data[0] == (int)str_step_ofs) {
+ ul_data[1] = glyph_step_bounds->xmin + glyph_bearing[0] +
+ (BLI_rctf_size_x(glyph_bounds) / 2.0f);
/* Early exit. */
return false;
}
@@ -2418,6 +2423,30 @@ static void widget_draw_extra_icons(const uiWidgetColors *wcol,
}
}
+static void widget_draw_node_link_socket(const uiWidgetColors *wcol,
+ const rcti *rect,
+ uiBut *but,
+ float alpha)
+{
+ /* Node socket pointer can be passed as custom_data, see UI_but_node_link_set(). */
+ if (but->custom_data) {
+ const float scale = 0.9f / but->block->aspect;
+
+ float col[4];
+ rgba_uchar_to_float(col, but->col);
+ col[3] *= alpha;
+
+ GPU_blend(true);
+ UI_widgetbase_draw_cache_flush();
+ GPU_blend(false);
+
+ ED_node_socket_draw(but->custom_data, rect, col, scale);
+ }
+ else {
+ widget_draw_icon(but, ICON_LAYER_USED, alpha, rect, wcol->text);
+ }
+}
+
/* draws text and icons for buttons */
static void widget_draw_text_icon(const uiFontStyle *fstyle,
const uiWidgetColors *wcol,
@@ -2427,15 +2456,27 @@ static void widget_draw_text_icon(const uiFontStyle *fstyle,
const bool show_menu_icon = ui_but_draw_menu_icon(but);
float alpha = (float)wcol->text[3] / 255.0f;
char password_str[UI_MAX_DRAW_STR];
+ bool no_text_padding = but->drawflag & UI_BUT_NO_TEXT_PADDING;
ui_but_text_password_hide(password_str, but, false);
/* check for button text label */
if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_POPOVER) && (but->flag & UI_BUT_NODE_LINK)) {
rcti temp = *rect;
- temp.xmin = rect->xmax - BLI_rcti_size_y(rect) - 1;
- widget_draw_icon(but, ICON_LAYER_USED, alpha, &temp, wcol->text);
- rect->xmax = temp.xmin;
+ const int size = BLI_rcti_size_y(rect) + 1; /* Not the icon size! */
+
+ if (but->drawflag & UI_BUT_ICON_LEFT) {
+ temp.xmax = rect->xmin + size;
+ rect->xmin = temp.xmax;
+ /* Further padding looks off. */
+ no_text_padding = true;
+ }
+ else {
+ temp.xmin = rect->xmax - size;
+ rect->xmax = temp.xmin;
+ }
+
+ widget_draw_node_link_socket(wcol, &temp, but, alpha);
}
/* If there's an icon too (made with uiDefIconTextBut) then draw the icon
@@ -2520,28 +2561,30 @@ static void widget_draw_text_icon(const uiFontStyle *fstyle,
rect->xmin += icon_size + icon_padding;
}
- int text_padding = (UI_TEXT_MARGIN_X * U.widget_unit) / but->block->aspect;
- if (but->editstr) {
- rect->xmin += text_padding;
- }
- else if (but->flag & UI_BUT_DRAG_MULTI) {
- bool text_is_edited = ui_but_drag_multi_edit_get(but) != NULL;
- if (text_is_edited) {
+ if (!no_text_padding) {
+ int text_padding = (UI_TEXT_MARGIN_X * U.widget_unit) / but->block->aspect;
+ if (but->editstr) {
rect->xmin += text_padding;
}
- }
- else if (but->drawflag & UI_BUT_TEXT_LEFT) {
-
- /* Reduce the left padding for labels without an icon. */
- if ((but->type == UI_BTYPE_LABEL) && !(but->flag & UI_HAS_ICON) &&
- !ui_block_is_menu(but->block)) {
- text_padding /= 2;
+ else if (but->flag & UI_BUT_DRAG_MULTI) {
+ bool text_is_edited = ui_but_drag_multi_edit_get(but) != NULL;
+ if (text_is_edited) {
+ rect->xmin += text_padding;
+ }
}
+ else if (but->drawflag & UI_BUT_TEXT_LEFT) {
- rect->xmin += text_padding;
- }
- else if (but->drawflag & UI_BUT_TEXT_RIGHT) {
- rect->xmax -= text_padding;
+ /* Reduce the left padding for labels without an icon. */
+ if ((but->type == UI_BTYPE_LABEL) && !(but->flag & UI_HAS_ICON) &&
+ !ui_block_is_menu(but->block)) {
+ text_padding /= 2;
+ }
+
+ rect->xmin += text_padding;
+ }
+ else if (but->drawflag & UI_BUT_TEXT_RIGHT) {
+ rect->xmax -= text_padding;
+ }
}
/* Menu contains sub-menu items with triangle icon on their right. Shortcut
@@ -4145,10 +4188,10 @@ static void widget_optionbut(uiWidgetColors *wcol,
/* smaller */
delta = 1 + BLI_rcti_size_y(&recttemp) / 8;
- recttemp.xmin += delta;
- recttemp.ymin += delta;
- recttemp.xmax -= delta;
- recttemp.ymax -= delta;
+ BLI_rcti_resize(
+ &recttemp, BLI_rcti_size_x(&recttemp) - delta * 2, BLI_rcti_size_y(&recttemp) - delta * 2);
+ /* Keep one edge in place. */
+ BLI_rcti_translate(&recttemp, text_before_widget ? delta : -delta, 0);
rad = wcol->roundness * BLI_rcti_size_y(&recttemp);
round_box_edges(&wtb, UI_CNR_ALL, &recttemp, rad);
@@ -4160,13 +4203,13 @@ static void widget_optionbut(uiWidgetColors *wcol,
widgetbase_draw(&wtb, wcol);
- /* text space */
- const float offset = BLI_rcti_size_y(rect) * 0.7 + delta;
+ /* Text space - factor is really just eyeballed. */
+ const float offset = delta * 0.9;
if (text_before_widget) {
- rect->xmax -= offset;
+ rect->xmax = recttemp.xmin - offset;
}
else {
- rect->xmin += offset;
+ rect->xmin = recttemp.xmax + offset;
}
}
@@ -4723,9 +4766,14 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
case UI_BTYPE_CHECKBOX_N:
if (!(but->flag & UI_HAS_ICON)) {
wt = widget_type(UI_WTYPE_CHECKBOX);
+
if ((but->drawflag & (UI_BUT_TEXT_LEFT | UI_BUT_TEXT_RIGHT)) == 0) {
but->drawflag |= UI_BUT_TEXT_LEFT;
}
+ /* widget_optionbut() carefully sets the text rectangle for fine tuned paddings. If the
+ * text drawing were to add its own padding, DPI and zoom factor would be applied twice
+ * in the final padding, so it's difficult to control it. */
+ but->drawflag |= UI_BUT_NO_TEXT_PADDING;
}
else {
wt = widget_type(UI_WTYPE_TOGGLE);
@@ -5279,15 +5327,23 @@ void ui_draw_tooltip_background(const uiStyle *UNUSED(style), uiBlock *UNUSED(bl
wt->draw(&wt->wcol, rect, 0, 0);
}
-/* helper call to draw a menu item without button */
-/* state: UI_ACTIVE or 0 */
+/**
+ * Helper call to draw a menu item without a button.
+ *
+ * \param state: The state of the button,
+ * typically #UI_ACTIVE, #UI_BUT_DISABLED, #UI_BUT_INACTIVE.
+ * \param use_sep: When true, characters after the last #UI_SEP_CHAR are right aligned,
+ * use for displaying key shortcuts.
+ * \param r_xmax: The right hand position of the text, this takes into the icon,
+ * padding and text clipping when there is not enough room to display the full text.
+ */
void ui_draw_menu_item(const uiFontStyle *fstyle,
rcti *rect,
const char *name,
int iconid,
int state,
bool use_sep,
- int *r_name_width)
+ int *r_xmax)
{
uiWidgetType *wt = widget_type(UI_WTYPE_MENU_ITEM);
rcti _rect = *rect;
@@ -5350,23 +5406,8 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
&xofs,
&yofs,
&info);
- if (r_name_width != NULL) {
- *r_name_width = xofs + info.width;
- }
- }
-
- /* part text right aligned */
- if (use_sep) {
- if (cpoin) {
- rect->xmax = _rect.xmax - 5;
- UI_fontstyle_draw(fstyle,
- rect,
- cpoin + 1,
- wt->wcol.text,
- &(struct uiFontStyleDraw_Params){
- .align = UI_STYLE_TEXT_RIGHT,
- });
- *cpoin = UI_SEP_CHAR;
+ if (r_xmax != NULL) {
+ *r_xmax = xofs + info.width;
}
}
@@ -5386,6 +5427,24 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
UI_icon_draw_ex(xs, ys, iconid, aspect, 1.0f, 0.0f, wt->wcol.text, false);
GPU_blend(false);
}
+
+ /* part text right aligned */
+ if (use_sep) {
+ if (cpoin) {
+ /* Set inactive state for grayed out text. */
+ wt->state(wt, state | UI_BUT_INACTIVE, 0);
+
+ rect->xmax = _rect.xmax - 5;
+ UI_fontstyle_draw(fstyle,
+ rect,
+ cpoin + 1,
+ wt->wcol.text,
+ &(struct uiFontStyleDraw_Params){
+ .align = UI_STYLE_TEXT_RIGHT,
+ });
+ *cpoin = UI_SEP_CHAR;
+ }
+ }
}
void ui_draw_preview_item(
diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c
index c07166b9ad2..f8419ba3eba 100644
--- a/source/blender/editors/interface/view2d.c
+++ b/source/blender/editors/interface/view2d.c
@@ -60,7 +60,7 @@
#include "interface_intern.h"
-static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mask_scrollers);
+static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize);
/* -------------------------------------------------------------------- */
/** \name Internal Utilities
@@ -134,7 +134,7 @@ void UI_view2d_mask_from_win(const View2D *v2d, rcti *r_mask)
*
* \param mask_scroll: Optionally clamp scrollbars by this region.
*/
-static void view2d_masks(View2D *v2d, bool check_scrollers, const rcti *mask_scroll)
+static void view2d_masks(View2D *v2d, const rcti *mask_scroll)
{
int scroll;
@@ -144,26 +144,24 @@ static void view2d_masks(View2D *v2d, bool check_scrollers, const rcti *mask_scr
mask_scroll = &v2d->mask;
}
- if (check_scrollers) {
- /* check size if hiding flag is set: */
- if (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE) {
- if (!(v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES)) {
- if (BLI_rctf_size_x(&v2d->tot) > BLI_rctf_size_x(&v2d->cur)) {
- v2d->scroll &= ~V2D_SCROLL_HORIZONTAL_FULLR;
- }
- else {
- v2d->scroll |= V2D_SCROLL_HORIZONTAL_FULLR;
- }
+ /* check size if hiding flag is set: */
+ if (v2d->scroll & V2D_SCROLL_HORIZONTAL_HIDE) {
+ if (!(v2d->scroll & V2D_SCROLL_HORIZONTAL_HANDLES)) {
+ if (BLI_rctf_size_x(&v2d->tot) > BLI_rctf_size_x(&v2d->cur)) {
+ v2d->scroll &= ~V2D_SCROLL_HORIZONTAL_FULLR;
+ }
+ else {
+ v2d->scroll |= V2D_SCROLL_HORIZONTAL_FULLR;
}
}
- if (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE) {
- if (!(v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES)) {
- if (BLI_rctf_size_y(&v2d->tot) + 0.01f > BLI_rctf_size_y(&v2d->cur)) {
- v2d->scroll &= ~V2D_SCROLL_VERTICAL_FULLR;
- }
- else {
- v2d->scroll |= V2D_SCROLL_VERTICAL_FULLR;
- }
+ }
+ if (v2d->scroll & V2D_SCROLL_VERTICAL_HIDE) {
+ if (!(v2d->scroll & V2D_SCROLL_VERTICAL_HANDLES)) {
+ if (BLI_rctf_size_y(&v2d->tot) + 0.01f > BLI_rctf_size_y(&v2d->cur)) {
+ v2d->scroll &= ~V2D_SCROLL_VERTICAL_FULLR;
+ }
+ else {
+ v2d->scroll |= V2D_SCROLL_VERTICAL_FULLR;
}
}
}
@@ -385,8 +383,7 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
v2d->winx = winx;
v2d->winy = winy;
- /* set masks (always do), but leave scroller scheck to totrect_set */
- view2d_masks(v2d, 0, NULL);
+ view2d_masks(v2d, NULL);
if (do_init) {
/* Visible by default. */
@@ -394,13 +391,12 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
}
/* set 'tot' rect before setting cur? */
- /* XXX confusing stuff here still -
- * I made this function not check scroller hide - that happens in totrect_set */
+ /* XXX confusing stuff here still */
if (tot_changed) {
UI_view2d_totRect_set_resize(v2d, winx, winy, !do_init);
}
else {
- ui_view2d_curRect_validate_resize(v2d, !do_init, 0);
+ ui_view2d_curRect_validate_resize(v2d, !do_init);
}
}
@@ -409,7 +405,7 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
* 'cur' is not allowed to be: larger than max, smaller than min, or outside of 'tot'
*/
// XXX pre2.5 -> this used to be called test_view2d()
-static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mask_scrollers)
+static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize)
{
float totwidth, totheight, curwidth, curheight, width, height;
float winx, winy;
@@ -851,12 +847,12 @@ static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize, bool mas
}
/* set masks */
- view2d_masks(v2d, mask_scrollers, NULL);
+ view2d_masks(v2d, NULL);
}
void UI_view2d_curRect_validate(View2D *v2d)
{
- ui_view2d_curRect_validate_resize(v2d, 0, 1);
+ ui_view2d_curRect_validate_resize(v2d, false);
}
/* ------------------ */
@@ -982,22 +978,10 @@ void UI_view2d_curRect_reset(View2D *v2d)
/* Change the size of the maximum viewable area (i.e. 'tot' rect) */
void UI_view2d_totRect_set_resize(View2D *v2d, int width, int height, bool resize)
{
- // int scroll = view2d_scroll_mapped(v2d->scroll);
-
/* don't do anything if either value is 0 */
width = abs(width);
height = abs(height);
- /* hrumf! */
- /* XXX: there are work arounds for this in the panel and file browse code. */
- /* round to int, because this is called with width + V2D_SCROLL_WIDTH */
- // if (scroll & V2D_SCROLL_HORIZONTAL) {
- // width -= (int)V2D_SCROLL_WIDTH;
- // }
- // if (scroll & V2D_SCROLL_VERTICAL) {
- // height -= (int)V2D_SCROLL_HEIGHT;
- // }
-
if (ELEM(0, width, height)) {
if (G.debug & G_DEBUG) {
printf("Error: View2D totRect set exiting: v2d=%p width=%d height=%d\n",
@@ -1047,20 +1031,12 @@ void UI_view2d_totRect_set_resize(View2D *v2d, int width, int height, bool resiz
}
/* make sure that 'cur' rect is in a valid state as a result of these changes */
- ui_view2d_curRect_validate_resize(v2d, resize, 1);
+ ui_view2d_curRect_validate_resize(v2d, resize);
}
void UI_view2d_totRect_set(View2D *v2d, int width, int height)
{
- int scroll = view2d_scroll_mapped(v2d->scroll);
-
- UI_view2d_totRect_set_resize(v2d, width, height, 0);
-
- /* solve bad recursion... if scroller state changed,
- * mask is different, so you get different rects */
- if (scroll != view2d_scroll_mapped(v2d->scroll)) {
- UI_view2d_totRect_set_resize(v2d, width, height, 0);
- }
+ UI_view2d_totRect_set_resize(v2d, width, height, false);
}
bool UI_view2d_tab_set(View2D *v2d, int tab)
@@ -1258,7 +1234,7 @@ void UI_view2d_view_restore(const bContext *C)
* \{ */
/* Draw a constant grid in given 2d-region */
-void UI_view2d_constant_grid_draw(View2D *v2d, float step)
+void UI_view2d_constant_grid_draw(const View2D *v2d, float step)
{
float start_x, start_y;
int count_x, count_y;
@@ -1330,7 +1306,8 @@ void UI_view2d_constant_grid_draw(View2D *v2d, float step)
}
/* Draw a multi-level grid in given 2d-region */
-void UI_view2d_multi_grid_draw(View2D *v2d, int colorid, float step, int level_size, int totlevels)
+void UI_view2d_multi_grid_draw(
+ const View2D *v2d, int colorid, float step, int level_size, int totlevels)
{
/* Exit if there is nothing to draw */
if (totlevels == 0) {
@@ -1448,7 +1425,7 @@ View2DScrollers *UI_view2d_scrollers_calc(View2D *v2d, const rcti *mask_custom)
scrollers = MEM_callocN(sizeof(View2DScrollers), "View2DScrollers");
/* Always update before drawing (for dynamically sized scrollers). */
- view2d_masks(v2d, false, mask_custom);
+ view2d_masks(v2d, mask_custom);
vert = v2d->vert;
hor = v2d->hor;
@@ -1801,7 +1778,8 @@ bool UI_view2d_view_to_region_clip(
* \param x, y: Coordinates to convert.
* \param r_region_x, r_region_y: Resultant coordinates.
*/
-void UI_view2d_view_to_region(View2D *v2d, float x, float y, int *r_region_x, int *r_region_y)
+void UI_view2d_view_to_region(
+ const View2D *v2d, float x, float y, int *r_region_x, int *r_region_y)
{
/* step 1: express given coordinates as proportional values */
x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
@@ -1817,7 +1795,7 @@ void UI_view2d_view_to_region(View2D *v2d, float x, float y, int *r_region_x, in
}
void UI_view2d_view_to_region_fl(
- View2D *v2d, float x, float y, float *r_region_x, float *r_region_y)
+ const View2D *v2d, float x, float y, float *r_region_x, float *r_region_y)
{
/* express given coordinates as proportional values */
x = (x - v2d->cur.xmin) / BLI_rctf_size_x(&v2d->cur);
@@ -1828,7 +1806,7 @@ void UI_view2d_view_to_region_fl(
*r_region_y = v2d->mask.ymin + (y * BLI_rcti_size_y(&v2d->mask));
}
-void UI_view2d_view_to_region_rcti(View2D *v2d, const rctf *rect_src, rcti *rect_dst)
+void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst)
{
const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
@@ -1849,7 +1827,7 @@ void UI_view2d_view_to_region_rcti(View2D *v2d, const rctf *rect_src, rcti *rect
clamp_rctf_to_rcti(rect_dst, &rect_tmp);
}
-void UI_view2d_view_to_region_m4(View2D *v2d, float matrix[4][4])
+void UI_view2d_view_to_region_m4(const View2D *v2d, float matrix[4][4])
{
rctf mask;
unit_m4(matrix);
@@ -1857,7 +1835,7 @@ void UI_view2d_view_to_region_m4(View2D *v2d, float matrix[4][4])
BLI_rctf_transform_calc_m4_pivot_min(&v2d->cur, &mask, matrix);
}
-bool UI_view2d_view_to_region_rcti_clip(View2D *v2d, const rctf *rect_src, rcti *rect_dst)
+bool UI_view2d_view_to_region_rcti_clip(const View2D *v2d, const rctf *rect_src, rcti *rect_dst)
{
const float cur_size[2] = {BLI_rctf_size_x(&v2d->cur), BLI_rctf_size_y(&v2d->cur)};
const float mask_size[2] = {BLI_rcti_size_x(&v2d->mask), BLI_rcti_size_y(&v2d->mask)};
@@ -1983,7 +1961,7 @@ float UI_view2d_scale_get_y(const View2D *v2d)
/**
* Same as ``UI_view2d_scale_get() - 1.0f / x, y``
*/
-void UI_view2d_scale_get_inverse(View2D *v2d, float *r_x, float *r_y)
+void UI_view2d_scale_get_inverse(const View2D *v2d, float *r_x, float *r_y)
{
if (r_x) {
*r_x = BLI_rctf_size_x(&v2d->cur) / BLI_rcti_size_x(&v2d->mask);
@@ -1997,7 +1975,7 @@ void UI_view2d_scale_get_inverse(View2D *v2d, float *r_x, float *r_y)
* Simple functions for consistent center offset access.
* Used by node editor to shift view center for each individual node tree.
*/
-void UI_view2d_center_get(struct View2D *v2d, float *r_x, float *r_y)
+void UI_view2d_center_get(const struct View2D *v2d, float *r_x, float *r_y)
{
/* get center */
if (r_x) {