UI: Internal support for custom UI list item drag & activate operators

For pose libraries, we need to be able to apply a pose whenever
activating (clicking) an item in the Pose Library asset view and blend
it by dragging (press & move). And since we want to allow Python scripts
to define what happens at least when activating an asset (so they can
define for example a custom "Apply" operator for preset assets), it
makes sense to just let them pass an operator name to the asset view
template. The template will be introduced in a following commit.
This commit is contained in:
2021-07-09 21:46:55 +02:00
committed by Sybren A. Stüvel
parent 00c7ea68a8
commit ae1dc8f5f9
8 changed files with 382 additions and 30 deletions

View File

@@ -588,6 +588,8 @@ bool UI_block_is_empty_ex(const uiBlock *block, const bool skip_title);
bool UI_block_is_empty(const uiBlock *block);
bool UI_block_can_add_separator(const uiBlock *block);
struct uiList *UI_list_find_mouse_over(const struct ARegion *region, const struct wmEvent *event);
/* interface_region_menu_popup.c */
/**
* Popup Menus
@@ -2282,6 +2284,13 @@ void uiTemplateFileSelectPath(uiLayout *layout,
struct bContext *C,
struct FileSelectParams *params);
struct PointerRNA *UI_list_custom_activate_operator_set(struct uiList *ui_list,
const char *opname,
bool create_properties);
struct PointerRNA *UI_list_custom_drag_operator_set(struct uiList *ui_list,
const char *opname,
bool create_properties);
/* items */
void uiItemO(uiLayout *layout, const char *name, int icon, const char *opname);
void uiItemEnumO_ptr(uiLayout *layout,

View File

@@ -767,23 +767,34 @@ static uiAfterFunc *ui_afterfunc_new(void)
* For executing operators after the button is pressed.
* (some non operator buttons need to trigger operators), see: T37795.
*
* \param context_but: A button from which to get the context from (`uiBut.context`) for the
* operator execution.
*
* \note Ownership over \a properties is moved here. The after-func owns it now.
* \note Can only call while handling buttons.
*/
PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
static void ui_handle_afterfunc_add_operator_ex(wmOperatorType *ot,
PointerRNA **properties,
int opcontext,
const uiBut *context_but)
{
PointerRNA *ptr = NULL;
uiAfterFunc *after = ui_afterfunc_new();
after->optype = ot;
after->opcontext = opcontext;
if (create_props) {
ptr = MEM_callocN(sizeof(PointerRNA), __func__);
WM_operator_properties_create_ptr(ptr, ot);
after->opptr = ptr;
if (properties) {
after->opptr = *properties;
*properties = NULL;
}
return ptr;
if (context_but && context_but->context) {
after->context = CTX_store_copy(context_but->context);
}
}
void ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext)
{
ui_handle_afterfunc_add_operator_ex(ot, NULL, opcontext, NULL);
}
static void popup_check(bContext *C, wmOperator *op)
@@ -1145,6 +1156,42 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu
data->applied = true;
}
/**
* \note Ownership of \a properties is moved here. The after-func owns it now.
*
* \param context_but: The button to use context from when calling or polling the operator.
*
* \returns true if the operator was executed, otherwise false.
*/
static bool ui_list_invoke_item_operator(bContext *C,
const uiBut *context_but,
wmOperatorType *ot,
PointerRNA **properties)
{
if (!ui_but_context_poll_operator(C, ot, context_but)) {
return false;
}
/* Allow the context to be set from the hovered button, so the list item draw callback can set
* context for the operators. */
ui_handle_afterfunc_add_operator_ex(ot, properties, WM_OP_INVOKE_DEFAULT, context_but);
return true;
}
static void ui_apply_but_LISTROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
{
uiBut *listbox = ui_list_find_from_row(data->region, but);
if (listbox) {
uiList *list = listbox->custom_data;
if (list && list->dyn_data->custom_activate_optype) {
ui_list_invoke_item_operator(
C, but, list->dyn_data->custom_activate_optype, &list->dyn_data->custom_activate_opptr);
}
}
ui_apply_but_ROW(C, block, but, data);
}
static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
{
if (!data->str) {
@@ -1617,7 +1664,7 @@ static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const
*/
if (drag_info->is_xy_lock_init == false) {
/* first store the buttons original coords */
uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true);
uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true, NULL, NULL);
if (but) {
if (but->flag & UI_BUT_DRAG_LOCK) {
@@ -1688,7 +1735,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void
wmWindow *win = CTX_wm_window(C);
const ARegion *region = CTX_wm_region(C);
uiBut *but = ui_but_find_mouse_over_ex(
region, drag_info->xy_init[0], drag_info->xy_init[1], true);
region, drag_info->xy_init[0], drag_info->xy_init[1], true, NULL, NULL);
if (but) {
ui_apply_but_undo(but);
@@ -2250,9 +2297,11 @@ static void ui_apply_but(
ui_apply_but_TOG(C, but, data);
break;
case UI_BTYPE_ROW:
case UI_BTYPE_LISTROW:
ui_apply_but_ROW(C, block, but, data);
break;
case UI_BTYPE_LISTROW:
ui_apply_but_LISTROW(C, block, but, data);
break;
case UI_BTYPE_DATASETROW:
ui_apply_but_ROW(C, block, but, data);
break;
@@ -4284,7 +4333,7 @@ static uiBut *ui_but_list_row_text_activate(bContext *C,
uiButtonActivateType activate_type)
{
ARegion *region = CTX_wm_region(C);
uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true);
uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true, NULL, NULL);
if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) {
/* exit listrow */
@@ -4778,6 +4827,15 @@ static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, con
if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) {
ret = WM_UI_HANDLER_CONTINUE;
}
/* Same special case handling for UI lists. Return CONTINUE so that a tweak or CLICK event
* will be sent for the list to work with. */
const uiBut *listbox = ui_list_find_mouse_over(data->region, event);
if (listbox) {
const uiList *ui_list = listbox->custom_data;
if (ui_list && ui_list->dyn_data->custom_drag_optype) {
ret = WM_UI_HANDLER_CONTINUE;
}
}
button_activate_state(C, but, BUTTON_STATE_EXIT);
return ret;
}
@@ -9289,6 +9347,128 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
return retval;
}
/**
* Activate the underlying list-row button, so the row is highlighted.
* Early exits if \a activate_dragging is true, but the custom drag operator fails to execute.
* Gives the wanted behavior where the item is activated on a tweak event when the custom drag
* operator is executed.
*/
static int ui_list_activate_hovered_row(bContext *C,
ARegion *region,
const uiList *ui_list,
const wmEvent *event,
bool activate_dragging)
{
const bool do_drag = activate_dragging && ui_list->dyn_data->custom_drag_optype;
if (do_drag) {
const uiBut *hovered_but = ui_but_find_mouse_over(region, event);
if (!ui_list_invoke_item_operator(C,
hovered_but,
ui_list->dyn_data->custom_drag_optype,
&ui_list->dyn_data->custom_drag_opptr)) {
return WM_UI_HANDLER_CONTINUE;
}
}
const int *mouse_xy = ISTWEAK(event->type) ? &event->prevclickx : &event->x;
uiBut *listrow = ui_list_row_find_mouse_over(region, mouse_xy[0], mouse_xy[1]);
if (listrow) {
wmOperatorType *custom_activate_optype = ui_list->dyn_data->custom_activate_optype;
/* Hacky: Ensure the custom activate operator is not called when the custom drag operator
* was. Only one should run! */
if (activate_dragging && do_drag) {
((uiList *)ui_list)->dyn_data->custom_activate_optype = NULL;
}
/* Simulate click on listrow button itself (which may be overlapped by another button). Also
* calls the custom activate operator (ui_list->custom_activate_opname). */
UI_but_execute(C, region, listrow);
((uiList *)ui_list)->dyn_data->custom_activate_optype = custom_activate_optype;
}
return WM_UI_HANDLER_BREAK;
}
static bool ui_list_is_hovering_draggable_but(bContext *C,
const uiList *list,
const ARegion *region,
const wmEvent *event)
{
/* On a tweak event, uses the coordinates from where tweaking was started. */
const int *mouse_xy = ISTWEAK(event->type) ? &event->prevclickx : &event->x;
const uiBut *hovered_but = ui_but_find_mouse_over_ex(
region, mouse_xy[0], mouse_xy[1], false, NULL, NULL);
if (list->dyn_data->custom_drag_optype) {
if (ui_but_context_poll_operator(C, list->dyn_data->custom_drag_optype, hovered_but)) {
return true;
}
}
return (hovered_but && hovered_but->dragpoin);
}
static int ui_list_handle_click_drag(bContext *C,
const uiList *ui_list,
ARegion *region,
const wmEvent *event)
{
if (!ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) {
return WM_HANDLER_CONTINUE;
}
int retval = WM_HANDLER_CONTINUE;
const bool is_draggable = ui_list_is_hovering_draggable_but(C, ui_list, region, event);
bool activate = false;
bool activate_dragging = false;
if (event->type == EVT_TWEAK_L) {
if (is_draggable) {
activate_dragging = true;
activate = true;
}
}
/* KM_CLICK is only sent after an uncaught release event, so the forground button gets all
* regular events (including mouse presses to start dragging) and this part only kicks in if it
* hasn't handled the release event. Note that if there's no overlaid button, the row selects
* on the press event already via regular UI_BTYPE_LISTROW handling. */
else if ((event->type == LEFTMOUSE) && (event->val == KM_CLICK)) {
activate = true;
}
if (activate) {
retval = ui_list_activate_hovered_row(C, region, ui_list, event, activate_dragging);
}
return retval;
}
static void ui_list_activate_row_from_index(
bContext *C, ARegion *region, uiBut *listbox, uiList *ui_list, int index)
{
uiBut *new_active_row = ui_list_row_find_from_index(region, index, listbox);
if (new_active_row) {
/* Preferred way to update the active item, also calls the custom activate operator
* (#uiList.custom_activate_opname). */
UI_but_execute(C, region, new_active_row);
}
else {
/* A bit ugly, set the active index in RNA directly. That's because a button that's
* scrolled away in the list box isn't created at all.
* The custom activate operator (#uiList.custom_activate_opname) is not called in this case
* (which may need the row button context).*/
RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, index);
RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
ui_apply_but_undo(listbox);
}
ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
}
static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox)
{
int retval = WM_UI_HANDLER_CONTINUE;
@@ -9322,7 +9502,10 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
}
}
if (val == KM_PRESS) {
if (ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) {
retval = ui_list_handle_click_drag(C, ui_list, region, event);
}
else if (val == KM_PRESS) {
if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) &&
!IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) ||
((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl &&
@@ -9384,12 +9567,7 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
CLAMP(value, min, max);
if (value != value_orig) {
RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value);
RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
ui_apply_but_undo(listbox);
ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
ui_list_activate_row_from_index(C, region, listbox, ui_list, value);
redraw = true;
}
retval = WM_UI_HANDLER_BREAK;

View File

@@ -931,9 +931,7 @@ const char *ui_textedit_undo(struct uiUndoStack_Text *undo_stack,
int *r_cursor_index);
/* interface_handlers.c */
PointerRNA *ui_handle_afterfunc_add_operator(struct wmOperatorType *ot,
int opcontext,
bool create_props);
extern void ui_handle_afterfunc_add_operator(struct wmOperatorType *ot, int opcontext);
extern void ui_pan_to_scroll(const struct wmEvent *event, int *type, int *val);
extern void ui_but_activate_event(struct bContext *C, struct ARegion *region, uiBut *but);
extern void ui_but_activate_over(struct bContext *C, struct ARegion *region, uiBut *but);
@@ -1128,19 +1126,32 @@ bool ui_but_contains_point_px_icon(const uiBut *but,
bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, int x, int y)
ATTR_WARN_UNUSED_RESULT;
uiBut *ui_list_find_mouse_over(struct ARegion *region,
uiBut *ui_list_find_mouse_over(const struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_list_find_from_row(const struct ARegion *region,
const uiBut *row_but) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_list_row_find_mouse_over(const struct ARegion *region,
int x,
int y) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_list_row_find_from_index(const struct ARegion *region,
const int index,
uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata);
uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,
const int x,
const int y,
const bool labeledit) ATTR_WARN_UNUSED_RESULT;
const bool labeledit,
const uiButFindPollFn find_poll,
const void *find_custom_data) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_find_mouse_over(const struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_find_rect_over(const struct ARegion *region,
const rcti *rect_px) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_list_find_mouse_over_ex(struct ARegion *region, int x, int y) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_list_find_mouse_over_ex(const struct ARegion *region,
int x,
int y) ATTR_WARN_UNUSED_RESULT;
bool ui_but_contains_password(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
@@ -1152,6 +1163,7 @@ uiBut *ui_but_next(uiBut *but) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_first(uiBlock *block) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_last(uiBlock *block) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_block_active_but_get(const uiBlock *block);
bool ui_block_is_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;
bool ui_block_is_popover(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;
bool ui_block_is_pie_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;

View File

@@ -2653,7 +2653,7 @@ static void panel_activate_state(const bContext *C, Panel *panel, const uiHandle
/* Initiate edge panning during drags for scrolling beyond the initial region view. */
wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true);
ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true);
ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT);
}
else if (state == PANEL_STATE_ANIMATION) {
panel_set_flag_recursive(panel, PNL_SELECT, false);

View File

@@ -266,11 +266,29 @@ bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEv
return BLI_rcti_isect_pt(&rect, x, y);
}
static uiBut *ui_but_find(const ARegion *region,
const uiButFindPollFn find_poll,
const void *find_custom_data)
{
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
if (find_poll && find_poll(but, find_custom_data) == false) {
continue;
}
return but;
}
}
return NULL;
}
/* x and y are only used in case event is NULL... */
uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
const int x,
const int y,
const bool labeledit)
const bool labeledit,
const uiButFindPollFn find_poll,
const void *find_custom_data)
{
uiBut *butover = NULL;
@@ -282,6 +300,9 @@ uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
ui_window_to_block_fl(region, block, &mx, &my);
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
if (find_poll && find_poll(but, find_custom_data) == false) {
continue;
}
if (ui_but_is_interactive(but, labeledit)) {
if (but->pie_dir != UI_RADIAL_NONE) {
if (ui_but_isect_pie_seg(block, but)) {
@@ -310,7 +331,7 @@ uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event)
{
return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0);
return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0, NULL, NULL);
}
uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px)
@@ -351,7 +372,7 @@ uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px)
return butover;
}
uiBut *ui_list_find_mouse_over_ex(ARegion *region, int x, int y)
uiBut *ui_list_find_mouse_over_ex(const ARegion *region, int x, int y)
{
if (!ui_region_contains_point_px(region, x, y)) {
return NULL;
@@ -369,11 +390,77 @@ uiBut *ui_list_find_mouse_over_ex(ARegion *region, int x, int y)
return NULL;
}
uiBut *ui_list_find_mouse_over(ARegion *region, const wmEvent *event)
uiBut *ui_list_find_mouse_over(const ARegion *region, const wmEvent *event)
{
if (event == NULL) {
/* If there is no info about the mouse, just act as if there is nothing underneath it. */
return NULL;
}
return ui_list_find_mouse_over_ex(region, event->x, event->y);
}
uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event)
{
uiBut *list_but = ui_list_find_mouse_over(region, event);
if (!list_but) {
return NULL;
}
return list_but->custom_data;
}
static bool ui_list_contains_row(const uiBut *listbox_but, const uiBut *listrow_but)
{
BLI_assert(listbox_but->type == UI_BTYPE_LISTBOX);
BLI_assert(listrow_but->type == UI_BTYPE_LISTROW);
/* The list box and its rows have the same RNA data (active data pointer/prop). */
return ui_but_rna_equals(listbox_but, listrow_but);
}
static bool ui_but_is_listbox_with_row(const uiBut *but, const void *customdata)
{
const uiBut *row_but = customdata;
return (but->type == UI_BTYPE_LISTBOX) && ui_list_contains_row(but, row_but);
}
uiBut *ui_list_find_from_row(const ARegion *region, const uiBut *row_but)
{
return ui_but_find(region, ui_but_is_listbox_with_row, row_but);
}
static bool ui_but_is_listrow(const uiBut *but, const void *UNUSED(customdata))
{
return but->type == UI_BTYPE_LISTROW;
}
uiBut *ui_list_row_find_mouse_over(const ARegion *region, const int x, const int y)
{
return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_listrow, NULL);
}
struct ListRowFindIndexData {
int index;
uiBut *listbox;
};
static bool ui_but_is_listrow_at_index(const uiBut *but, const void *customdata)
{
const struct ListRowFindIndexData *find_data = customdata;
return ui_but_is_listrow(but, NULL) && ui_list_contains_row(find_data->listbox, but) &&
(but->hardmax == find_data->index);
}
uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut *listbox)
{
BLI_assert(listbox->type == UI_BTYPE_LISTBOX);
struct ListRowFindIndexData data = {
.index = index,
.listbox = listbox,
};
return ui_but_find(region, ui_but_is_listrow_at_index, &data);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -5835,6 +5835,15 @@ static void uilist_free_dyn_data(uiList *ui_list)
return;
}
if (dyn_data->custom_activate_opptr) {
WM_operator_properties_free(dyn_data->custom_activate_opptr);
MEM_freeN(dyn_data->custom_activate_opptr);
}
if (dyn_data->custom_drag_opptr) {
WM_operator_properties_free(dyn_data->custom_drag_opptr);
MEM_freeN(dyn_data->custom_drag_opptr);
}
MEM_SAFE_FREE(dyn_data->items_filter_flags);
MEM_SAFE_FREE(dyn_data->items_filter_neworder);
MEM_SAFE_FREE(dyn_data->customdata);
@@ -6848,6 +6857,46 @@ void uiTemplateList(uiLayout *layout,
NULL);
}
/**
* \return: A RNA pointer for the operator properties.
*/
PointerRNA *UI_list_custom_activate_operator_set(uiList *ui_list,
const char *opname,
bool create_properties)
{
uiListDyn *dyn_data = ui_list->dyn_data;
dyn_data->custom_activate_optype = WM_operatortype_find(opname, false);
if (!dyn_data->custom_activate_optype) {
return NULL;
}
if (create_properties) {
WM_operator_properties_alloc(&dyn_data->custom_activate_opptr, NULL, opname);
}
return dyn_data->custom_activate_opptr;
}
/**
* \return: A RNA pointer for the operator properties.
*/
PointerRNA *UI_list_custom_drag_operator_set(uiList *ui_list,
const char *opname,
bool create_properties)
{
uiListDyn *dyn_data = ui_list->dyn_data;
dyn_data->custom_drag_optype = WM_operatortype_find(opname, false);
if (!dyn_data->custom_drag_optype) {
return NULL;
}
if (create_properties) {
WM_operator_properties_alloc(&dyn_data->custom_drag_opptr, NULL, opname);
}
return dyn_data->custom_drag_opptr;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -287,6 +287,11 @@ typedef struct uiListDyn {
int *items_filter_flags;
/** Org_idx -> new_idx, items_len length. */
int *items_filter_neworder;
struct wmOperatorType *custom_drag_optype;
struct PointerRNA *custom_drag_opptr;
struct wmOperatorType *custom_activate_optype;
struct PointerRNA *custom_activate_opptr;
} uiListDyn;
typedef struct uiList { /* some list UI data need to be saved in file */
@@ -313,6 +318,12 @@ typedef struct uiList { /* some list UI data need to be saved in file */
int filter_flag;
int filter_sort_flag;
/** Operator executed when activating an item. */
const char *custom_activate_opname;
/** Operator executed when dragging an item (item gets activated too, without running
* custom_activate_opname above). */
const char *custom_drag_opname;
/* Custom sub-classes properties. */
IDProperty *properties;

View File

@@ -604,6 +604,12 @@ void WM_operator_properties_create(PointerRNA *ptr, const char *opstring)
* used for keymaps and macros */
void WM_operator_properties_alloc(PointerRNA **ptr, IDProperty **properties, const char *opstring)
{
IDProperty *tmp_properties = NULL;
/* Allow passing NULL for properties, just create the properties here then. */
if (properties == NULL) {
properties = &tmp_properties;
}
if (*properties == NULL) {
IDPropertyTemplate val = {0};
*properties = IDP_New(IDP_GROUP, &val, "wmOpItemProp");