From 599a7ddf1784882ae796d94f148aa2a830639bc0 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Fri, 29 Jul 2022 23:16:58 -0500 Subject: Cleanup: Move five interface files to C++ Builds on all four platforms on the buildbot. Includes clang tidy fixes. --- source/blender/blenkernel/intern/workspace.c | 4 +- source/blender/blenlib/BLI_listbase.h | 8 +- source/blender/editors/interface/CMakeLists.txt | 10 +- source/blender/editors/interface/interface_anim.c | 349 --- source/blender/editors/interface/interface_anim.cc | 355 +++ .../blender/editors/interface/interface_intern.h | 1 + source/blender/editors/interface/interface_ops.c | 2258 ----------------- source/blender/editors/interface/interface_ops.cc | 2273 +++++++++++++++++ source/blender/editors/interface/interface_panel.c | 2602 -------------------- .../blender/editors/interface/interface_panel.cc | 2581 +++++++++++++++++++ .../interface/interface_template_search_operator.c | 129 - .../interface_template_search_operator.cc | 131 + source/blender/editors/interface/interface_undo.c | 112 - source/blender/editors/interface/interface_undo.cc | 113 + .../editors/sculpt_paint/paint_image_proj.c | 4 +- 15 files changed, 5467 insertions(+), 5463 deletions(-) delete mode 100644 source/blender/editors/interface/interface_anim.c create mode 100644 source/blender/editors/interface/interface_anim.cc delete mode 100644 source/blender/editors/interface/interface_ops.c create mode 100644 source/blender/editors/interface/interface_ops.cc delete mode 100644 source/blender/editors/interface/interface_panel.c create mode 100644 source/blender/editors/interface/interface_panel.cc delete mode 100644 source/blender/editors/interface/interface_template_search_operator.c create mode 100644 source/blender/editors/interface/interface_template_search_operator.cc delete mode 100644 source/blender/editors/interface/interface_undo.c create mode 100644 source/blender/editors/interface/interface_undo.cc (limited to 'source/blender') diff --git a/source/blender/blenkernel/intern/workspace.c b/source/blender/blenkernel/intern/workspace.c index 0265dd037b1..88e7db1fe6c 100644 --- a/source/blender/blenkernel/intern/workspace.c +++ b/source/blender/blenkernel/intern/workspace.c @@ -456,12 +456,12 @@ WorkSpaceLayout *BKE_workspace_layout_iter_circular(const WorkSpace *workspace, WorkSpaceLayout *iter_layout; if (iter_backward) { - LISTBASE_CIRCULAR_BACKWARD_BEGIN (&workspace->layouts, iter_layout, start) { + LISTBASE_CIRCULAR_BACKWARD_BEGIN (WorkSpaceLayout *, &workspace->layouts, iter_layout, start) { if (!callback(iter_layout, arg)) { return iter_layout; } } - LISTBASE_CIRCULAR_BACKWARD_END(&workspace->layouts, iter_layout, start); + LISTBASE_CIRCULAR_BACKWARD_END(WorkSpaceLayout *, &workspace->layouts, iter_layout, start); } else { LISTBASE_CIRCULAR_FORWARD_BEGIN (&workspace->layouts, iter_layout, start) { diff --git a/source/blender/blenlib/BLI_listbase.h b/source/blender/blenlib/BLI_listbase.h index 237fcdae8b9..9322fa4c85b 100644 --- a/source/blender/blenlib/BLI_listbase.h +++ b/source/blender/blenlib/BLI_listbase.h @@ -320,13 +320,13 @@ struct LinkData *BLI_genericNodeN(void *data); } \ ((void)0) -#define LISTBASE_CIRCULAR_BACKWARD_BEGIN(lb, lb_iter, lb_init) \ - if ((lb)->last && (lb_init || (lb_init = (lb)->last))) { \ +#define LISTBASE_CIRCULAR_BACKWARD_BEGIN(type, lb, lb_iter, lb_init) \ + if ((lb)->last && (lb_init || (lb_init = (type)(lb)->last))) { \ lb_iter = lb_init; \ do { -#define LISTBASE_CIRCULAR_BACKWARD_END(lb, lb_iter, lb_init) \ +#define LISTBASE_CIRCULAR_BACKWARD_END(type, lb, lb_iter, lb_init) \ } \ - while ((lb_iter = (lb_iter)->prev ? (lb_iter)->prev : (lb)->last), (lb_iter != lb_init)) \ + while ((lb_iter = (lb_iter)->prev ? (lb_iter)->prev : (type)(lb)->last), (lb_iter != lb_init)) \ ; \ } \ ((void)0) diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 08d1d3d9c61..56a639920a7 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -36,7 +36,7 @@ set(SRC eyedroppers/eyedropper_gpencil_color.c interface.cc interface_align.c - interface_anim.c + interface_anim.cc interface_button_group.c interface_context_menu.c interface_context_path.cc @@ -47,8 +47,8 @@ set(SRC interface_icons.c interface_icons_event.c interface_layout.c - interface_ops.c - interface_panel.c + interface_ops.cc + interface_panel.cc interface_query.cc interface_region_color_picker.cc interface_region_hud.cc @@ -64,9 +64,9 @@ set(SRC interface_template_attribute_search.cc interface_template_list.cc interface_template_search_menu.cc - interface_template_search_operator.c + interface_template_search_operator.cc interface_templates.c - interface_undo.c + interface_undo.cc interface_utils.cc interface_widgets.c resources.c diff --git a/source/blender/editors/interface/interface_anim.c b/source/blender/editors/interface/interface_anim.c deleted file mode 100644 index d7d1d3ce260..00000000000 --- a/source/blender/editors/interface/interface_anim.c +++ /dev/null @@ -1,349 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_anim_types.h" -#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_animsys.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" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "ED_keyframing.h" - -#include "UI_interface.h" - -#include "RNA_access.h" -#include "RNA_path.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -static FCurve *ui_but_get_fcurve( - uiBut *but, AnimData **adt, bAction **action, bool *r_driven, bool *r_special) -{ - /* for entire array buttons we check the first component, it's not perfect - * but works well enough in typical cases */ - const int rnaindex = (but->rnaindex == -1) ? 0 : but->rnaindex; - - return BKE_fcurve_find_by_rna_context_ui( - but->block->evil_C, &but->rnapoin, but->rnaprop, rnaindex, adt, action, r_driven, r_special); -} - -void ui_but_anim_flag(uiBut *but, const AnimationEvalContext *anim_eval_context) -{ - AnimData *adt; - bAction *act; - FCurve *fcu; - bool driven; - bool special; - - but->flag &= ~(UI_BUT_ANIMATED | UI_BUT_ANIMATED_KEY | UI_BUT_DRIVEN); - but->drawflag &= ~UI_BUT_ANIMATED_CHANGED; - - /* NOTE: "special" is reserved for special F-Curves stored on the animation data - * itself (which are used to animate properties of the animation data). - * We count those as "animated" too for now - */ - fcu = ui_but_get_fcurve(but, &adt, &act, &driven, &special); - - if (fcu) { - if (!driven) { - /* Empty curves are ignored by the animation evaluation system. */ - if (BKE_fcurve_is_empty(fcu)) { - return; - } - - but->flag |= UI_BUT_ANIMATED; - - /* T41525 - When the active action is a NLA strip being edited, - * we need to correct the frame number to "look inside" the - * remapped action - */ - float cfra = anim_eval_context->eval_time; - if (adt) { - cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); - } - - if (fcurve_frame_has_keyframe(fcu, cfra, 0)) { - but->flag |= UI_BUT_ANIMATED_KEY; - } - - /* XXX: this feature is totally broken and useless with NLA */ - if (adt == NULL || adt->nla_tracks.first == NULL) { - const AnimationEvalContext remapped_context = BKE_animsys_eval_context_construct_at( - anim_eval_context, cfra); - if (fcurve_is_changed(but->rnapoin, but->rnaprop, fcu, &remapped_context)) { - but->drawflag |= UI_BUT_ANIMATED_CHANGED; - } - } - } - else { - but->flag |= UI_BUT_DRIVEN; - } - } -} - -static uiBut *ui_but_anim_decorate_find_attached_button(uiButDecorator *but_decorate) -{ - uiBut *but_iter = NULL; - - BLI_assert(UI_but_is_decorator(&but_decorate->but)); - BLI_assert(but_decorate->rnapoin.data && but_decorate->rnaprop); - - LISTBASE_CIRCULAR_BACKWARD_BEGIN ( - &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev) { - if (but_iter != (uiBut *)but_decorate && - ui_but_rna_equals_ex( - but_iter, &but_decorate->rnapoin, but_decorate->rnaprop, but_decorate->rnaindex)) { - return but_iter; - } - } - LISTBASE_CIRCULAR_BACKWARD_END( - &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev); - - return NULL; -} - -void ui_but_anim_decorate_update_from_flag(uiButDecorator *decorator_but) -{ - if (!decorator_but->rnapoin.data || !decorator_but->rnaprop) { - /* Nothing to do. */ - return; - } - - const uiBut *but_anim = ui_but_anim_decorate_find_attached_button(decorator_but); - uiBut *but = &decorator_but->but; - - if (!but_anim) { - printf("Could not find button with matching property to decorate (%s.%s)\n", - RNA_struct_identifier(decorator_but->rnapoin.type), - RNA_property_identifier(decorator_but->rnaprop)); - return; - } - - const int flag = but_anim->flag; - - if (flag & UI_BUT_DRIVEN) { - but->icon = ICON_DECORATE_DRIVER; - } - else if (flag & UI_BUT_ANIMATED_KEY) { - but->icon = ICON_DECORATE_KEYFRAME; - } - else if (flag & UI_BUT_ANIMATED) { - but->icon = ICON_DECORATE_ANIMATE; - } - else if (flag & UI_BUT_OVERRIDDEN) { - but->icon = ICON_DECORATE_OVERRIDE; - } - else { - but->icon = ICON_DECORATE; - } - - const int flag_copy = (UI_BUT_DISABLED | UI_BUT_INACTIVE); - but->flag = (but->flag & ~flag_copy) | (flag & flag_copy); -} - -bool ui_but_anim_expression_get(uiBut *but, char *str, size_t maxlen) -{ - FCurve *fcu; - ChannelDriver *driver; - bool driven, special; - - fcu = ui_but_get_fcurve(but, NULL, NULL, &driven, &special); - - if (fcu && driven) { - driver = fcu->driver; - - if (driver && driver->type == DRIVER_TYPE_PYTHON) { - if (str) { - BLI_strncpy(str, driver->expression, maxlen); - } - return true; - } - } - - return false; -} - -bool ui_but_anim_expression_set(uiBut *but, const char *str) -{ - FCurve *fcu; - ChannelDriver *driver; - bool driven, special; - - fcu = ui_but_get_fcurve(but, NULL, NULL, &driven, &special); - - if (fcu && driven) { - driver = fcu->driver; - - if (driver && (driver->type == DRIVER_TYPE_PYTHON)) { - bContext *C = but->block->evil_C; - - BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); - - /* tag driver as needing to be recompiled */ - BKE_driver_invalidate_expression(driver, true, false); - - /* clear invalid flags which may prevent this from working */ - driver->flag &= ~DRIVER_FLAG_INVALID; - fcu->flag &= ~FCURVE_DISABLED; - - /* this notifier should update the Graph Editor and trigger depsgraph refresh? */ - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, NULL); - - DEG_relations_tag_update(CTX_data_main(C)); - - return true; - } - } - - return false; -} - -bool ui_but_anim_expression_create(uiBut *but, const char *str) -{ - bContext *C = but->block->evil_C; - ID *id; - FCurve *fcu; - char *path; - bool ok = false; - - /* button must have RNA-pointer to a numeric-capable property */ - if (ELEM(NULL, but->rnapoin.data, but->rnaprop)) { - if (G.debug & G_DEBUG) { - printf("ERROR: create expression failed - button has no RNA info attached\n"); - } - return false; - } - - if (RNA_property_array_check(but->rnaprop) != 0) { - if (but->rnaindex == -1) { - if (G.debug & G_DEBUG) { - printf("ERROR: create expression failed - can't create expression for entire array\n"); - } - return false; - } - } - - /* make sure we have animdata for this */ - /* FIXME: until materials can be handled by depsgraph, - * don't allow drivers to be created for them */ - id = but->rnapoin.owner_id; - if ((id == NULL) || (GS(id->name) == ID_MA) || (GS(id->name) == ID_TE)) { - if (G.debug & G_DEBUG) { - printf("ERROR: create expression failed - invalid data-block for adding drivers (%p)\n", id); - } - return false; - } - - /* get path */ - path = RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop); - if (path == NULL) { - return false; - } - - /* create driver */ - fcu = verify_driver_fcurve(id, path, but->rnaindex, DRIVER_FCURVE_KEYFRAMES); - if (fcu) { - ChannelDriver *driver = fcu->driver; - - if (driver) { - /* set type of driver */ - driver->type = DRIVER_TYPE_PYTHON; - - /* set the expression */ - /* TODO: need some way of identifying variables used */ - BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); - - /* updates */ - BKE_driver_invalidate_expression(driver, true, false); - DEG_relations_tag_update(CTX_data_main(C)); - WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, NULL); - ok = true; - } - } - - MEM_freeN(path); - - return ok; -} - -void ui_but_anim_autokey(bContext *C, uiBut *but, Scene *scene, float cfra) -{ - ED_autokeyframe_property(C, scene, &but->rnapoin, but->rnaprop, but->rnaindex, cfra, true); -} - -void ui_but_anim_copy_driver(bContext *C) -{ - /* this operator calls UI_context_active_but_prop_get */ - WM_operator_name_call(C, "ANIM_OT_copy_driver_button", WM_OP_INVOKE_DEFAULT, NULL, NULL); -} - -void ui_but_anim_paste_driver(bContext *C) -{ - /* this operator calls UI_context_active_but_prop_get */ - WM_operator_name_call(C, "ANIM_OT_paste_driver_button", WM_OP_INVOKE_DEFAULT, NULL, NULL); -} - -void ui_but_anim_decorate_cb(bContext *C, void *arg_but, void *UNUSED(arg_dummy)) -{ - wmWindowManager *wm = CTX_wm_manager(C); - uiButDecorator *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_anim->active, but_decorate->but.active); - wm->op_undo_depth++; - - if (but_anim->flag & UI_BUT_DRIVEN) { - /* pass */ - /* TODO: report? */ - } - 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_anim->rnaindex == -1); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, NULL); - WM_operator_properties_free(&props_ptr); - } - else { - 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_anim->rnaindex == -1); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, NULL); - WM_operator_properties_free(&props_ptr); - } - - SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->but.active); - wm->op_undo_depth--; -} diff --git a/source/blender/editors/interface/interface_anim.cc b/source/blender/editors/interface/interface_anim.cc new file mode 100644 index 00000000000..8e898b7fe66 --- /dev/null +++ b/source/blender/editors/interface/interface_anim.cc @@ -0,0 +1,355 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#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_animsys.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" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "ED_keyframing.h" + +#include "UI_interface.h" + +#include "RNA_access.h" +#include "RNA_path.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +static FCurve *ui_but_get_fcurve( + uiBut *but, AnimData **adt, bAction **action, bool *r_driven, bool *r_special) +{ + /* for entire array buttons we check the first component, it's not perfect + * but works well enough in typical cases */ + const int rnaindex = (but->rnaindex == -1) ? 0 : but->rnaindex; + + return BKE_fcurve_find_by_rna_context_ui(static_cast(but->block->evil_C), + &but->rnapoin, + but->rnaprop, + rnaindex, + adt, + action, + r_driven, + r_special); +} + +void ui_but_anim_flag(uiBut *but, const AnimationEvalContext *anim_eval_context) +{ + AnimData *adt; + bAction *act; + FCurve *fcu; + bool driven; + bool special; + + but->flag &= ~(UI_BUT_ANIMATED | UI_BUT_ANIMATED_KEY | UI_BUT_DRIVEN); + but->drawflag &= ~UI_BUT_ANIMATED_CHANGED; + + /* NOTE: "special" is reserved for special F-Curves stored on the animation data + * itself (which are used to animate properties of the animation data). + * We count those as "animated" too for now + */ + fcu = ui_but_get_fcurve(but, &adt, &act, &driven, &special); + + if (fcu) { + if (!driven) { + /* Empty curves are ignored by the animation evaluation system. */ + if (BKE_fcurve_is_empty(fcu)) { + return; + } + + but->flag |= UI_BUT_ANIMATED; + + /* T41525 - When the active action is a NLA strip being edited, + * we need to correct the frame number to "look inside" the + * remapped action + */ + float cfra = anim_eval_context->eval_time; + if (adt) { + cfra = BKE_nla_tweakedit_remap(adt, cfra, NLATIME_CONVERT_UNMAP); + } + + if (fcurve_frame_has_keyframe(fcu, cfra, 0)) { + but->flag |= UI_BUT_ANIMATED_KEY; + } + + /* XXX: this feature is totally broken and useless with NLA */ + if (adt == nullptr || adt->nla_tracks.first == nullptr) { + const AnimationEvalContext remapped_context = BKE_animsys_eval_context_construct_at( + anim_eval_context, cfra); + if (fcurve_is_changed(but->rnapoin, but->rnaprop, fcu, &remapped_context)) { + but->drawflag |= UI_BUT_ANIMATED_CHANGED; + } + } + } + else { + but->flag |= UI_BUT_DRIVEN; + } + } +} + +static uiBut *ui_but_anim_decorate_find_attached_button(uiButDecorator *but_decorate) +{ + uiBut *but_iter = nullptr; + + BLI_assert(UI_but_is_decorator(&but_decorate->but)); + BLI_assert(but_decorate->rnapoin.data && but_decorate->rnaprop); + + LISTBASE_CIRCULAR_BACKWARD_BEGIN ( + uiBut *, &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev) { + if (but_iter != (uiBut *)but_decorate && + ui_but_rna_equals_ex( + but_iter, &but_decorate->rnapoin, but_decorate->rnaprop, but_decorate->rnaindex)) { + return but_iter; + } + } + LISTBASE_CIRCULAR_BACKWARD_END( + uiBut *, &but_decorate->but.block->buttons, but_iter, but_decorate->but.prev); + + return nullptr; +} + +void ui_but_anim_decorate_update_from_flag(uiButDecorator *decorator_but) +{ + if (!decorator_but->rnapoin.data || !decorator_but->rnaprop) { + /* Nothing to do. */ + return; + } + + const uiBut *but_anim = ui_but_anim_decorate_find_attached_button(decorator_but); + uiBut *but = &decorator_but->but; + + if (!but_anim) { + printf("Could not find button with matching property to decorate (%s.%s)\n", + RNA_struct_identifier(decorator_but->rnapoin.type), + RNA_property_identifier(decorator_but->rnaprop)); + return; + } + + const int flag = but_anim->flag; + + if (flag & UI_BUT_DRIVEN) { + but->icon = ICON_DECORATE_DRIVER; + } + else if (flag & UI_BUT_ANIMATED_KEY) { + but->icon = ICON_DECORATE_KEYFRAME; + } + else if (flag & UI_BUT_ANIMATED) { + but->icon = ICON_DECORATE_ANIMATE; + } + else if (flag & UI_BUT_OVERRIDDEN) { + but->icon = ICON_DECORATE_OVERRIDE; + } + else { + but->icon = ICON_DECORATE; + } + + const int flag_copy = (UI_BUT_DISABLED | UI_BUT_INACTIVE); + but->flag = (but->flag & ~flag_copy) | (flag & flag_copy); +} + +bool ui_but_anim_expression_get(uiBut *but, char *str, size_t maxlen) +{ + FCurve *fcu; + ChannelDriver *driver; + bool driven, special; + + fcu = ui_but_get_fcurve(but, nullptr, nullptr, &driven, &special); + + if (fcu && driven) { + driver = fcu->driver; + + if (driver && driver->type == DRIVER_TYPE_PYTHON) { + if (str) { + BLI_strncpy(str, driver->expression, maxlen); + } + return true; + } + } + + return false; +} + +bool ui_but_anim_expression_set(uiBut *but, const char *str) +{ + FCurve *fcu; + ChannelDriver *driver; + bool driven, special; + + fcu = ui_but_get_fcurve(but, nullptr, nullptr, &driven, &special); + + if (fcu && driven) { + driver = fcu->driver; + + if (driver && (driver->type == DRIVER_TYPE_PYTHON)) { + bContext *C = static_cast(but->block->evil_C); + + BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); + + /* tag driver as needing to be recompiled */ + BKE_driver_invalidate_expression(driver, true, false); + + /* clear invalid flags which may prevent this from working */ + driver->flag &= ~DRIVER_FLAG_INVALID; + fcu->flag &= ~FCURVE_DISABLED; + + /* this notifier should update the Graph Editor and trigger depsgraph refresh? */ + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, nullptr); + + DEG_relations_tag_update(CTX_data_main(C)); + + return true; + } + } + + return false; +} + +bool ui_but_anim_expression_create(uiBut *but, const char *str) +{ + bContext *C = static_cast(but->block->evil_C); + ID *id; + FCurve *fcu; + char *path; + bool ok = false; + + /* button must have RNA-pointer to a numeric-capable property */ + if (ELEM(nullptr, but->rnapoin.data, but->rnaprop)) { + if (G.debug & G_DEBUG) { + printf("ERROR: create expression failed - button has no RNA info attached\n"); + } + return false; + } + + if (RNA_property_array_check(but->rnaprop) != 0) { + if (but->rnaindex == -1) { + if (G.debug & G_DEBUG) { + printf("ERROR: create expression failed - can't create expression for entire array\n"); + } + return false; + } + } + + /* make sure we have animdata for this */ + /* FIXME: until materials can be handled by depsgraph, + * don't allow drivers to be created for them */ + id = but->rnapoin.owner_id; + if ((id == nullptr) || (GS(id->name) == ID_MA) || (GS(id->name) == ID_TE)) { + if (G.debug & G_DEBUG) { + printf("ERROR: create expression failed - invalid data-block for adding drivers (%p)\n", id); + } + return false; + } + + /* get path */ + path = RNA_path_from_ID_to_property(&but->rnapoin, but->rnaprop); + if (path == nullptr) { + return false; + } + + /* create driver */ + fcu = verify_driver_fcurve(id, path, but->rnaindex, DRIVER_FCURVE_KEYFRAMES); + if (fcu) { + ChannelDriver *driver = fcu->driver; + + if (driver) { + /* set type of driver */ + driver->type = DRIVER_TYPE_PYTHON; + + /* set the expression */ + /* TODO: need some way of identifying variables used */ + BLI_strncpy_utf8(driver->expression, str, sizeof(driver->expression)); + + /* updates */ + BKE_driver_invalidate_expression(driver, true, false); + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME, nullptr); + ok = true; + } + } + + MEM_freeN(path); + + return ok; +} + +void ui_but_anim_autokey(bContext *C, uiBut *but, Scene *scene, float cfra) +{ + ED_autokeyframe_property(C, scene, &but->rnapoin, but->rnaprop, but->rnaindex, cfra, true); +} + +void ui_but_anim_copy_driver(bContext *C) +{ + /* this operator calls UI_context_active_but_prop_get */ + WM_operator_name_call(C, "ANIM_OT_copy_driver_button", WM_OP_INVOKE_DEFAULT, nullptr, nullptr); +} + +void ui_but_anim_paste_driver(bContext *C) +{ + /* this operator calls UI_context_active_but_prop_get */ + WM_operator_name_call(C, "ANIM_OT_paste_driver_button", WM_OP_INVOKE_DEFAULT, nullptr, nullptr); +} + +void ui_but_anim_decorate_cb(bContext *C, void *arg_but, void *UNUSED(arg_dummy)) +{ + wmWindowManager *wm = CTX_wm_manager(C); + uiButDecorator *but_decorate = static_cast(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_anim->active, but_decorate->but.active); + wm->op_undo_depth++; + + if (but_anim->flag & UI_BUT_DRIVEN) { + /* pass */ + /* TODO: report? */ + } + 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_anim->rnaindex == -1); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); + WM_operator_properties_free(&props_ptr); + } + else { + 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_anim->rnaindex == -1); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr, nullptr); + WM_operator_properties_free(&props_ptr); + } + + SWAP(struct uiHandleButtonData *, but_anim->active, but_decorate->but.active); + wm->op_undo_depth--; +} diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 03b9d03a6e3..d75d86c2665 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -468,6 +468,7 @@ typedef enum uiButtonGroupFlag { /** The buttons in this group are inside a panel header. */ UI_BUTTON_GROUP_PANEL_HEADER = (1 << 1), } uiButtonGroupFlag; +ENUM_OPERATORS(uiButtonGroupFlag, UI_BUTTON_GROUP_PANEL_HEADER); struct uiBlock { uiBlock *next, *prev; diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c deleted file mode 100644 index 4a5919864c7..00000000000 --- a/source/blender/editors/interface/interface_ops.c +++ /dev/null @@ -1,2258 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_armature_types.h" -#include "DNA_material_types.h" -#include "DNA_modifier_types.h" /* for handling geometry nodes properties */ -#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */ -#include "DNA_screen_types.h" -#include "DNA_text_types.h" - -#include "BLI_blenlib.h" -#include "BLI_math_color.h" - -#include "BLF_api.h" -#include "BLT_lang.h" - -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_idprop.h" -#include "BKE_layer.h" -#include "BKE_lib_id.h" -#include "BKE_lib_override.h" -#include "BKE_material.h" -#include "BKE_node.h" -#include "BKE_report.h" -#include "BKE_screen.h" -#include "BKE_text.h" - -#include "IMB_colormanagement.h" - -#include "DEG_depsgraph.h" - -#include "RNA_access.h" -#include "RNA_define.h" -#include "RNA_path.h" -#include "RNA_prototypes.h" -#include "RNA_types.h" - -#include "UI_interface.h" - -#include "interface_intern.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_object.h" -#include "ED_paint.h" - -/* for Copy As Driver */ -#include "ED_keyframing.h" - -/* only for UI_OT_editsource */ -#include "BKE_main.h" -#include "BLI_ghash.h" -#include "ED_screen.h" -#include "ED_text.h" - -/* -------------------------------------------------------------------- */ -/** \name Immediate redraw helper - * - * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That - * violates the Model-View-Controller pattern. - * - * But there are some operators which really need to re-run the layout definitions for various - * reasons. For example, "Edit Source" does it to find out which exact Python code added a button. - * Other operators may need to access buttons that aren't currently visible. In Blender's UI code - * design that typically means just not adding the button in the first place, for a particular - * redraw. So the operator needs to change context and re-create the layout, so the button becomes - * available to act on. - * - * \{ */ - -static void ui_region_redraw_immediately(bContext *C, ARegion *region) -{ - ED_region_do_layout(C, region); - WM_draw_region_viewport_bind(region); - ED_region_do_draw(C, region); - WM_draw_region_viewport_unbind(region); - region->do_draw = false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy Data Path Operator - * \{ */ - -static bool copy_data_path_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - char *path; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id && ptr.data && prop) { - path = RNA_path_from_ID_to_property(&ptr, prop); - - if (path) { - MEM_freeN(path); - return true; - } - } - - return false; -} - -static int copy_data_path_button_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr; - PropertyRNA *prop; - char *path; - int index; - ID *id; - - const bool full_path = RNA_boolean_get(op->ptr, "full_path"); - - /* try to create driver using property retrieved from UI */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id != NULL) { - if (full_path) { - if (prop) { - path = RNA_path_full_property_py_ex(bmain, &ptr, prop, index, true); - } - else { - path = RNA_path_full_struct_py(bmain, &ptr); - } - } - else { - path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, 0, -1, &id); - - if (!path) { - path = RNA_path_from_ID_to_property(&ptr, prop); - } - } - - if (path) { - WM_clipboard_text_set(path, false); - MEM_freeN(path); - return OPERATOR_FINISHED; - } - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_copy_data_path_button(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Copy Data Path"; - ot->idname = "UI_OT_copy_data_path_button"; - ot->description = "Copy the RNA data path for this property to the clipboard"; - - /* callbacks */ - ot->exec = copy_data_path_button_exec; - ot->poll = copy_data_path_button_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER; - - /* properties */ - prop = RNA_def_boolean(ot->srna, "full_path", false, "full_path", "Copy full data path"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy As Driver Operator - * \{ */ - -static bool copy_as_driver_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - char *path; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id && ptr.data && prop && - ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM) && - (index >= 0 || !RNA_property_array_check(prop))) { - path = RNA_path_from_ID_to_property(&ptr, prop); - - if (path) { - MEM_freeN(path); - return true; - } - } - - return false; -} - -static int copy_as_driver_button_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr; - PropertyRNA *prop; - int index; - - /* try to create driver using property retrieved from UI */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.owner_id && ptr.data && prop) { - ID *id; - const int dim = RNA_property_array_dimension(&ptr, prop, NULL); - char *path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, dim, index, &id); - - if (path) { - ANIM_copy_as_driver(id, path, RNA_property_identifier(prop)); - MEM_freeN(path); - return OPERATOR_FINISHED; - } - - BKE_reportf(op->reports, RPT_ERROR, "Could not compute a valid data path"); - return OPERATOR_CANCELLED; - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_copy_as_driver_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy as New Driver"; - ot->idname = "UI_OT_copy_as_driver_button"; - ot->description = - "Create a new driver with this property as input, and copy it to the " - "clipboard. Use Paste Driver to add it to the target property, or Paste " - "Driver Variables to extend an existing driver"; - - /* callbacks */ - ot->exec = copy_as_driver_button_exec; - ot->poll = copy_as_driver_button_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy Python Command Operator - * \{ */ - -static bool copy_python_command_button_poll(bContext *C) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but && (but->optype != NULL)) { - return 1; - } - - return 0; -} - -static int copy_python_command_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but && (but->optype != NULL)) { - PointerRNA *opptr; - char *str; - opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */ - - str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr); - - WM_clipboard_text_set(str, 0); - - MEM_freeN(str); - - return OPERATOR_FINISHED; - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_copy_python_command_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy Python Command"; - ot->idname = "UI_OT_copy_python_command_button"; - ot->description = "Copy the Python command matching this button"; - - /* callbacks */ - ot->exec = copy_python_command_button_exec; - ot->poll = copy_python_command_button_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Reset to Default Values Button Operator - * \{ */ - -static int operator_button_property_finish(bContext *C, PointerRNA *ptr, PropertyRNA *prop) -{ - ID *id = ptr->owner_id; - - /* perform updates required for this property */ - RNA_property_update(C, ptr, prop); - - /* as if we pressed the button */ - UI_context_active_but_prop_handle(C, false); - - /* Since we don't want to undo _all_ edits to settings, eg window - * edits on the screen or on operator settings. - * it might be better to move undo's inline - campbell */ - if (id && ID_CHECK_UNDO(id)) { - /* do nothing, go ahead with undo */ - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static int operator_button_property_finish_with_undo(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop) -{ - /* Perform updates required for this property. */ - RNA_property_update(C, ptr, prop); - - /* As if we pressed the button. */ - UI_context_active_but_prop_handle(C, true); - - return OPERATOR_FINISHED; -} - -static bool reset_default_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - return (ptr.data && prop && RNA_property_editable(&ptr, prop)); -} - -static int reset_default_button_exec(bContext *C, wmOperator *op) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - const bool all = RNA_boolean_get(op->ptr, "all"); - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { - if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) { - return operator_button_property_finish_with_undo(C, &ptr, prop); - } - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_reset_default_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Reset to Default Value"; - ot->idname = "UI_OT_reset_default_button"; - ot->description = "Reset this property's value to its default value"; - - /* callbacks */ - ot->poll = reset_default_button_poll; - ot->exec = reset_default_button_exec; - - /* flags */ - /* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo - * is responsible for the undo push. */ - ot->flag = 0; - - /* properties */ - RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Assign Value as Default Button Operator - * \{ */ - -static bool assign_default_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { - const PropertyType type = RNA_property_type(prop); - - return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) && - ELEM(type, PROP_INT, PROP_FLOAT); - } - - return false; -} - -static int assign_default_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { - if (RNA_property_assign_default(&ptr, prop)) { - return operator_button_property_finish(C, &ptr, prop); - } - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_assign_default_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Assign Value as Default"; - ot->idname = "UI_OT_assign_default_button"; - ot->description = "Set this property's current value as the new default"; - - /* callbacks */ - ot->poll = assign_default_button_poll; - ot->exec = assign_default_button_exec; - - /* flags */ - ot->flag = OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Unset Property Button Operator - * \{ */ - -static int unset_property_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - /* try to unset the nominated property */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data && prop && RNA_property_editable(&ptr, prop) && - /* RNA_property_is_idprop(prop) && */ - RNA_property_is_set(&ptr, prop)) { - RNA_property_unset(&ptr, prop); - return operator_button_property_finish(C, &ptr, prop); - } - - return OPERATOR_CANCELLED; -} - -static void UI_OT_unset_property_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Unset Property"; - ot->idname = "UI_OT_unset_property_button"; - ot->description = "Clear the property and use default or generated value in operators"; - - /* callbacks */ - ot->poll = ED_operator_regionactive; - ot->exec = unset_property_button_exec; - - /* flags */ - ot->flag = OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Define Override Type Operator - * \{ */ - -/* Note that we use different values for UI/UX than 'real' override operations, user does not care - * whether it's added or removed for the differential operation e.g. */ -enum { - UIOverride_Type_NOOP = 0, - UIOverride_Type_Replace = 1, - UIOverride_Type_Difference = 2, /* Add/subtract */ - UIOverride_Type_Factor = 3, /* Multiply */ - /* TODO: should/can we expose insert/remove ones for collections? Doubt it... */ -}; - -static EnumPropertyItem override_type_items[] = { - {UIOverride_Type_NOOP, - "NOOP", - 0, - "NoOp", - "'No-Operation', place holder preventing automatic override to ever affect the property"}, - {UIOverride_Type_Replace, - "REPLACE", - 0, - "Replace", - "Completely replace value from linked data by local one"}, - {UIOverride_Type_Difference, - "DIFFERENCE", - 0, - "Difference", - "Store difference to linked data value"}, - {UIOverride_Type_Factor, - "FACTOR", - 0, - "Factor", - "Store factor to linked data value (useful e.g. for scale)"}, - {0, NULL, 0, NULL, NULL}, -}; - -static bool override_type_set_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - const uint override_status = RNA_property_override_library_status( - CTX_data_main(C), &ptr, prop, index); - - return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE)); -} - -static int override_type_set_button_exec(bContext *C, wmOperator *op) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - bool created; - const bool all = RNA_boolean_get(op->ptr, "all"); - const int op_type = RNA_enum_get(op->ptr, "type"); - - short operation; - - switch (op_type) { - case UIOverride_Type_NOOP: - operation = IDOVERRIDE_LIBRARY_OP_NOOP; - break; - case UIOverride_Type_Replace: - operation = IDOVERRIDE_LIBRARY_OP_REPLACE; - break; - case UIOverride_Type_Difference: - /* override code will automatically switch to subtract if needed. */ - operation = IDOVERRIDE_LIBRARY_OP_ADD; - break; - case UIOverride_Type_Factor: - operation = IDOVERRIDE_LIBRARY_OP_MULTIPLY; - break; - default: - operation = IDOVERRIDE_LIBRARY_OP_REPLACE; - BLI_assert(0); - break; - } - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - BLI_assert(ptr.owner_id != NULL); - - if (all) { - index = -1; - } - - IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_get( - CTX_data_main(C), &ptr, prop, operation, index, true, NULL, &created); - - if (opop == NULL) { - /* Sometimes e.g. RNA cannot generate a path to the given property. */ - BKE_reportf(op->reports, RPT_WARNING, "Failed to create the override operation"); - return OPERATOR_CANCELLED; - } - - if (!created) { - opop->operation = operation; - } - - /* Outliner e.g. has to be aware of this change. */ - WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); - - return operator_button_property_finish(C, &ptr, prop); -} - -static int override_type_set_button_invoke(bContext *C, - wmOperator *op, - const wmEvent *UNUSED(event)) -{ -#if 0 /* Disabled for now */ - return WM_menu_invoke_ex(C, op, WM_OP_INVOKE_DEFAULT); -#else - RNA_enum_set(op->ptr, "type", IDOVERRIDE_LIBRARY_OP_REPLACE); - return override_type_set_button_exec(C, op); -#endif -} - -static void UI_OT_override_type_set_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Define Override Type"; - ot->idname = "UI_OT_override_type_set_button"; - ot->description = "Create an override operation, or set the type of an existing one"; - - /* callbacks */ - ot->poll = override_type_set_button_poll; - ot->exec = override_type_set_button_exec; - ot->invoke = override_type_set_button_invoke; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); - ot->prop = RNA_def_enum(ot->srna, - "type", - override_type_items, - UIOverride_Type_Replace, - "Type", - "Type of override operation"); - /* TODO: add itemf callback, not all options are available for all data types... */ -} - -static bool override_remove_button_poll(bContext *C) -{ - PointerRNA ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - const uint override_status = RNA_property_override_library_status( - CTX_data_main(C), &ptr, prop, index); - - return (ptr.data && ptr.owner_id && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN)); -} - -static int override_remove_button_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr, id_refptr, src; - PropertyRNA *prop; - int index; - const bool all = RNA_boolean_get(op->ptr, "all"); - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - ID *id = ptr.owner_id; - IDOverrideLibraryProperty *oprop = RNA_property_override_property_find(bmain, &ptr, prop, &id); - BLI_assert(oprop != NULL); - BLI_assert(id != NULL && id->override_library != NULL); - - const bool is_template = ID_IS_OVERRIDE_LIBRARY_TEMPLATE(id); - - /* We need source (i.e. linked data) to restore values of deleted overrides... - * If this is an override template, we obviously do not need to restore anything. */ - if (!is_template) { - PropertyRNA *src_prop; - RNA_id_pointer_create(id->override_library->reference, &id_refptr); - if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) { - BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer"); - } - } - - if (!all && index != -1) { - bool is_strict_find; - /* Remove override operation for given item, - * add singular operations for the other items as needed. */ - IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find( - oprop, NULL, NULL, index, index, false, &is_strict_find); - BLI_assert(opop != NULL); - if (!is_strict_find) { - /* No specific override operation, we have to get generic one, - * and create item-specific override operations for all but given index, - * before removing generic one. */ - for (int idx = RNA_property_array_length(&ptr, prop); idx--;) { - if (idx != index) { - BKE_lib_override_library_property_operation_get( - oprop, opop->operation, NULL, NULL, idx, idx, true, NULL, NULL); - } - } - } - BKE_lib_override_library_property_operation_delete(oprop, opop); - if (!is_template) { - RNA_property_copy(bmain, &ptr, &src, prop, index); - } - if (BLI_listbase_is_empty(&oprop->operations)) { - BKE_lib_override_library_property_delete(id->override_library, oprop); - } - } - else { - /* Just remove whole generic override operation of this property. */ - BKE_lib_override_library_property_delete(id->override_library, oprop); - if (!is_template) { - RNA_property_copy(bmain, &ptr, &src, prop, -1); - } - } - - /* Outliner e.g. has to be aware of this change. */ - WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); - - return operator_button_property_finish(C, &ptr, prop); -} - -static void UI_OT_override_remove_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Remove Override"; - ot->idname = "UI_OT_override_remove_button"; - ot->description = "Remove an override operation"; - - /* callbacks */ - ot->poll = override_remove_button_poll; - ot->exec = override_remove_button_exec; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "all", 1, "All", "Reset to default values all elements of the array"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Copy To Selected Operator - * \{ */ - -#define NOT_NULL(assignment) ((assignment) != NULL) -#define NOT_RNA_NULL(assignment) ((assignment).data != NULL) - -static void ui_context_selected_bones_via_pose(bContext *C, ListBase *r_lb) -{ - ListBase lb; - lb = CTX_data_collection_get(C, "selected_pose_bones"); - - if (!BLI_listbase_is_empty(&lb)) { - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - bPoseChannel *pchan = link->ptr.data; - RNA_pointer_create(link->ptr.owner_id, &RNA_Bone, pchan->bone, &link->ptr); - } - } - - *r_lb = lb; -} - -bool UI_context_copy_to_selected_list(bContext *C, - PointerRNA *ptr, - PropertyRNA *prop, - ListBase *r_lb, - bool *r_use_path_from_id, - char **r_path) -{ - *r_use_path_from_id = false; - *r_path = NULL; - /* special case for bone constraints */ - char *path_from_bone = NULL; - /* Remove links from the collection list which don't contain 'prop'. */ - bool ensure_list_items_contain_prop = false; - - /* PropertyGroup objects don't have a reference to the struct that actually owns - * them, so it is normally necessary to do a brute force search to find it. This - * handles the search for non-ID owners by using the 'active' reference as a hint - * to preserve efficiency. Only properties defined through RNA are handled, as - * custom properties cannot be assumed to be valid for all instances. - * - * Properties owned by the ID are handled by the 'if (ptr->owner_id)' case below. - */ - if (!RNA_property_is_idprop(prop) && RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) { - PointerRNA owner_ptr; - char *idpath = NULL; - - /* First, check the active PoseBone and PoseBone->Bone. */ - if (NOT_RNA_NULL( - owner_ptr = CTX_data_pointer_get_type(C, "active_pose_bone", &RNA_PoseBone))) { - if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty(&owner_ptr, ptr->data))) { - *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); - } - else { - bPoseChannel *pchan = owner_ptr.data; - RNA_pointer_create(owner_ptr.owner_id, &RNA_Bone, pchan->bone, &owner_ptr); - - if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty(&owner_ptr, ptr->data))) { - ui_context_selected_bones_via_pose(C, r_lb); - } - } - } - - if (idpath == NULL) { - /* Check the active EditBone if in edit mode. */ - if (NOT_RNA_NULL( - owner_ptr = CTX_data_pointer_get_type_silent(C, "active_bone", &RNA_EditBone)) && - NOT_NULL(idpath = RNA_path_from_struct_to_idproperty(&owner_ptr, ptr->data))) { - *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); - } - - /* Add other simple cases here (Node, NodeSocket, Sequence, ViewLayer etc). */ - } - - if (idpath) { - *r_path = BLI_sprintfN("%s.%s", idpath, RNA_property_identifier(prop)); - MEM_freeN(idpath); - return true; - } - } - - if (RNA_struct_is_a(ptr->type, &RNA_EditBone)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_PoseBone)) { - *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) { - ui_context_selected_bones_via_pose(C, r_lb); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) { - /* Special case when we do this for 'Sequence.lock'. - * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ - const char *prop_id = RNA_property_identifier(prop); - if (STREQ(prop_id, "lock")) { - *r_lb = CTX_data_collection_get(C, "selected_sequences"); - } - else { - *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); - } - /* Account for properties only being available for some sequence types. */ - ensure_list_items_contain_prop = true; - } - else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_fcurves"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Keyframe)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_keyframes"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Action)) { - *r_lb = CTX_data_collection_get(C, "selected_editable_actions"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) { - *r_lb = CTX_data_collection_get(C, "selected_nla_strips"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) { - *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks"); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) && - (path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) != - NULL) { - *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); - *r_path = path_from_bone; - } - else if (RNA_struct_is_a(ptr->type, &RNA_Node) || RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { - ListBase lb = {NULL, NULL}; - char *path = NULL; - bNode *node = NULL; - - /* Get the node we're editing */ - if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { - bNodeTree *ntree = (bNodeTree *)ptr->owner_id; - bNodeSocket *sock = ptr->data; - if (nodeFindNode(ntree, sock, &node, NULL)) { - if ((path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node)) != NULL) { - /* we're good! */ - } - else { - node = NULL; - } - } - } - else { - node = ptr->data; - } - - /* Now filter by type */ - if (node) { - lb = CTX_data_collection_get(C, "selected_nodes"); - - LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { - bNode *node_data = link->ptr.data; - - if (node_data->type != node->type) { - BLI_remlink(&lb, link); - MEM_freeN(link); - } - } - } - - *r_lb = lb; - *r_path = path; - } - else if (ptr->owner_id) { - ID *id = ptr->owner_id; - - if (GS(id->name) == ID_OB) { - *r_lb = CTX_data_collection_get(C, "selected_editable_objects"); - *r_use_path_from_id = true; - *r_path = RNA_path_from_ID_to_property(ptr, prop); - } - else if (OB_DATA_SUPPORT_ID(GS(id->name))) { - /* check we're using the active object */ - const short id_code = GS(id->name); - ListBase lb = CTX_data_collection_get(C, "selected_editable_objects"); - char *path = RNA_path_from_ID_to_property(ptr, prop); - - /* de-duplicate obdata */ - if (!BLI_listbase_is_empty(&lb)) { - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - Object *ob = (Object *)link->ptr.owner_id; - if (ob->data) { - ID *id_data = ob->data; - id_data->tag |= LIB_TAG_DOIT; - } - } - - LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { - Object *ob = (Object *)link->ptr.owner_id; - ID *id_data = ob->data; - - if ((id_data == NULL) || (id_data->tag & LIB_TAG_DOIT) == 0 || ID_IS_LINKED(id_data) || - (GS(id_data->name) != id_code)) { - BLI_remlink(&lb, link); - MEM_freeN(link); - } - else { - /* Avoid prepending 'data' to the path. */ - RNA_id_pointer_create(id_data, &link->ptr); - } - - if (id_data) { - id_data->tag &= ~LIB_TAG_DOIT; - } - } - } - - *r_lb = lb; - *r_path = path; - } - else if (GS(id->name) == ID_SCE) { - /* Sequencer's ID is scene :/ */ - /* Try to recursively find an RNA_Sequence ancestor, - * to handle situations like T41062... */ - if ((*r_path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Sequence)) != NULL) { - /* Special case when we do this for 'Sequence.lock'. - * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ - const char *prop_id = RNA_property_identifier(prop); - if (STREQ(prop_id, "lock")) { - *r_lb = CTX_data_collection_get(C, "selected_sequences"); - } - else { - *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); - } - /* Account for properties only being available for some sequence types. */ - ensure_list_items_contain_prop = true; - } - } - return (*r_path != NULL); - } - else { - return false; - } - - if (ensure_list_items_contain_prop) { - const char *prop_id = RNA_property_identifier(prop); - LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, r_lb) { - if ((ptr->type != link->ptr.type) && - (RNA_struct_type_find_property(link->ptr.type, prop_id) != prop)) { - BLI_remlink(r_lb, link); - MEM_freeN(link); - } - } - } - - return true; -} - -bool UI_context_copy_to_selected_check(PointerRNA *ptr, - PointerRNA *ptr_link, - PropertyRNA *prop, - const char *path, - bool use_path_from_id, - PointerRNA *r_ptr, - PropertyRNA **r_prop) -{ - PointerRNA idptr; - PropertyRNA *lprop; - PointerRNA lptr; - - if (ptr_link->data == ptr->data) { - return false; - } - - if (use_path_from_id) { - /* Path relative to ID. */ - lprop = NULL; - RNA_id_pointer_create(ptr_link->owner_id, &idptr); - RNA_path_resolve_property(&idptr, path, &lptr, &lprop); - } - else if (path) { - /* Path relative to elements from list. */ - lprop = NULL; - RNA_path_resolve_property(ptr_link, path, &lptr, &lprop); - } - else { - lptr = *ptr_link; - lprop = prop; - } - - if (lptr.data == ptr->data) { - /* temp_ptr might not be the same as ptr_link! */ - return false; - } - - /* Skip non-existing properties on link. This was previously covered with the `lprop != prop` - * check but we are now more permissive when it comes to ID properties, see below. */ - if (lprop == NULL) { - return false; - } - - if (RNA_property_type(lprop) != RNA_property_type(prop)) { - return false; - } - - /* Check property pointers matching. - * For ID properties, these pointers match: - * - If the property is API defined on an existing class (and they are equally named). - * - Never for ID properties on specific ID (even if they are equally named). - * - Never for NodesModifierSettings properties (even if they are equally named). - * - * Be permissive on ID properties in the following cases: - * - #NodesModifierSettings properties - * - (special check: only if the node-group matches, since the 'Input_n' properties are name - * based and similar on potentially very different node-groups). - * - ID properties on specific ID - * - (no special check, copying seems OK [even if type does not match -- does not do anything - * then]) - */ - bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop); - if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) && - RNA_struct_is_a(ptr->type, &RNA_NodesModifier)) { - ignore_prop_eq = false; - - NodesModifierData *nmd_link = (NodesModifierData *)lptr.data; - NodesModifierData *nmd_src = (NodesModifierData *)ptr->data; - if (nmd_link->node_group == nmd_src->node_group) { - ignore_prop_eq = true; - } - } - - if ((lprop != prop) && !ignore_prop_eq) { - return false; - } - - if (!RNA_property_editable(&lptr, lprop)) { - return false; - } - - if (r_ptr) { - *r_ptr = lptr; - } - if (r_prop) { - *r_prop = lprop; - } - - return true; -} - -/** - * Called from both exec & poll. - * - * \note Normally we wouldn't call a loop from within a poll function, - * however this is a special case, and for regular poll calls, getting - * the context from the button will fail early. - */ -static bool copy_to_selected_button(bContext *C, bool all, bool poll) -{ - Main *bmain = CTX_data_main(C); - PointerRNA ptr, lptr; - PropertyRNA *prop, *lprop; - bool success = false; - int index; - - /* try to reset the nominated setting to its default value */ - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* if there is a valid property that is editable... */ - if (ptr.data == NULL || prop == NULL) { - return false; - } - - char *path = NULL; - bool use_path_from_id; - ListBase lb = {NULL}; - - if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) { - return false; - } - if (BLI_listbase_is_empty(&lb)) { - MEM_SAFE_FREE(path); - return false; - } - - LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { - if (link->ptr.data == ptr.data) { - continue; - } - - if (!UI_context_copy_to_selected_check( - &ptr, &link->ptr, prop, path, use_path_from_id, &lptr, &lprop)) { - continue; - } - - if (poll) { - success = true; - break; - } - if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { - RNA_property_update(C, &lptr, prop); - success = true; - } - } - - MEM_SAFE_FREE(path); - BLI_freelistN(&lb); - - return success; -} - -static bool copy_to_selected_button_poll(bContext *C) -{ - return copy_to_selected_button(C, false, true); -} - -static int copy_to_selected_button_exec(bContext *C, wmOperator *op) -{ - bool success; - - const bool all = RNA_boolean_get(op->ptr, "all"); - - success = copy_to_selected_button(C, all, false); - - return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; -} - -static void UI_OT_copy_to_selected_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Copy to Selected"; - ot->idname = "UI_OT_copy_to_selected_button"; - ot->description = - "Copy the property's value from the active item to the same property of all selected items " - "if the same property exists"; - - /* callbacks */ - ot->poll = copy_to_selected_button_poll; - ot->exec = copy_to_selected_button_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Jump to Target Operator - * \{ */ - -/** Jump to the object or bone referenced by the pointer, or check if it is possible. */ -static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll) -{ - if (RNA_pointer_is_null(&ptr)) { - return false; - } - - /* Verify pointer type. */ - char bone_name[MAXBONENAME]; - const StructRNA *target_type = NULL; - - if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) { - RNA_string_get(&ptr, "name", bone_name); - if (bone_name[0] != '\0') { - target_type = &RNA_Bone; - } - } - else if (RNA_struct_is_a(ptr.type, &RNA_Object)) { - target_type = &RNA_Object; - } - - if (target_type == NULL) { - return false; - } - - /* Find the containing Object. */ - ViewLayer *view_layer = CTX_data_view_layer(C); - Base *base = NULL; - const short id_type = GS(ptr.owner_id->name); - if (id_type == ID_OB) { - base = BKE_view_layer_base_find(view_layer, (Object *)ptr.owner_id); - } - else if (OB_DATA_SUPPORT_ID(id_type)) { - base = ED_object_find_first_by_data_id(view_layer, ptr.owner_id); - } - - bool ok = false; - if ((base == NULL) || ((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE))) { - /* pass */ - } - else if (poll) { - ok = true; - } - else { - /* Make optional. */ - const bool reveal_hidden = true; - /* Select and activate the target. */ - if (target_type == &RNA_Bone) { - ok = ED_object_jump_to_bone(C, base->object, bone_name, reveal_hidden); - } - else if (target_type == &RNA_Object) { - ok = ED_object_jump_to_object(C, base->object, reveal_hidden); - } - else { - BLI_assert(0); - } - } - return ok; -} - -/** - * Jump to the object or bone referred to by the current UI field value. - * - * \note quite heavy for a poll callback, but the operator is only - * used as a right click menu item for certain UI field types, and - * this will fail quickly if the context is completely unsuitable. - */ -static bool jump_to_target_button(bContext *C, bool poll) -{ - PointerRNA ptr, target_ptr; - PropertyRNA *prop; - int index; - - UI_context_active_but_prop_get(C, &ptr, &prop, &index); - - /* If there is a valid property... */ - if (ptr.data && prop) { - const PropertyType type = RNA_property_type(prop); - - /* For pointer properties, use their value directly. */ - if (type == PROP_POINTER) { - target_ptr = RNA_property_pointer_get(&ptr, prop); - - return jump_to_target_ptr(C, target_ptr, poll); - } - /* For string properties with prop_search, look up the search collection item. */ - if (type == PROP_STRING) { - const uiBut *but = UI_context_active_but_get(C); - const uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : - NULL; - - if (search_but && search_but->items_update_fn == ui_rna_collection_search_update_fn) { - uiRNACollectionSearch *coll_search = search_but->arg; - - char str_buf[MAXBONENAME]; - char *str_ptr = RNA_property_string_get_alloc(&ptr, prop, str_buf, sizeof(str_buf), NULL); - - int found = RNA_property_collection_lookup_string( - &coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr); - - if (str_ptr != str_buf) { - MEM_freeN(str_ptr); - } - - if (found) { - return jump_to_target_ptr(C, target_ptr, poll); - } - } - } - } - - return false; -} - -bool ui_jump_to_target_button_poll(bContext *C) -{ - return jump_to_target_button(C, true); -} - -static int jump_to_target_button_exec(bContext *C, wmOperator *UNUSED(op)) -{ - const bool success = jump_to_target_button(C, false); - - return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; -} - -static void UI_OT_jump_to_target_button(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Jump to Target"; - ot->idname = "UI_OT_jump_to_target_button"; - ot->description = "Switch to the target object or bone"; - - /* callbacks */ - ot->poll = ui_jump_to_target_button_poll; - ot->exec = jump_to_target_button_exec; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit Python Source Operator - * \{ */ - -#ifdef WITH_PYTHON - -/* ------------------------------------------------------------------------- */ -/* EditSource Utility funcs and operator, - * NOTE: this includes utility functions and button matching checks. */ - -typedef struct uiEditSourceStore { - uiBut but_orig; - GHash *hash; -} uiEditSourceStore; - -typedef struct uiEditSourceButStore { - char py_dbg_fn[FILE_MAX]; - int py_dbg_line_number; -} uiEditSourceButStore; - -/* should only ever be set while the edit source operator is running */ -static struct uiEditSourceStore *ui_editsource_info = NULL; - -bool UI_editsource_enable_check(void) -{ - return (ui_editsource_info != NULL); -} - -static void ui_editsource_active_but_set(uiBut *but) -{ - BLI_assert(ui_editsource_info == NULL); - - ui_editsource_info = MEM_callocN(sizeof(uiEditSourceStore), __func__); - memcpy(&ui_editsource_info->but_orig, but, sizeof(uiBut)); - - ui_editsource_info->hash = BLI_ghash_ptr_new(__func__); -} - -static void ui_editsource_active_but_clear(void) -{ - BLI_ghash_free(ui_editsource_info->hash, NULL, MEM_freeN); - MEM_freeN(ui_editsource_info); - ui_editsource_info = NULL; -} - -static bool ui_editsource_uibut_match(uiBut *but_a, uiBut *but_b) -{ -# if 0 - printf("matching buttons: '%s' == '%s'\n", but_a->drawstr, but_b->drawstr); -# endif - - /* this just needs to be a 'good-enough' comparison so we can know beyond - * reasonable doubt that these buttons are the same between redraws. - * if this fails it only means edit-source fails - campbell */ - if (BLI_rctf_compare(&but_a->rect, &but_b->rect, FLT_EPSILON) && (but_a->type == but_b->type) && - (but_a->rnaprop == but_b->rnaprop) && (but_a->optype == but_b->optype) && - (but_a->unit_type == but_b->unit_type) && - STREQLEN(but_a->drawstr, but_b->drawstr, UI_MAX_DRAW_STR)) { - return true; - } - return false; -} - -void UI_editsource_active_but_test(uiBut *but) -{ - extern void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno); - - struct uiEditSourceButStore *but_store = MEM_callocN(sizeof(uiEditSourceButStore), __func__); - - const char *fn; - int line_number = -1; - -# if 0 - printf("comparing buttons: '%s' == '%s'\n", but->drawstr, ui_editsource_info->but_orig.drawstr); -# endif - - PyC_FileAndNum_Safe(&fn, &line_number); - - if (line_number != -1) { - BLI_strncpy(but_store->py_dbg_fn, fn, sizeof(but_store->py_dbg_fn)); - but_store->py_dbg_line_number = line_number; - } - else { - but_store->py_dbg_fn[0] = '\0'; - but_store->py_dbg_line_number = -1; - } - - BLI_ghash_insert(ui_editsource_info->hash, but, but_store); -} - -void UI_editsource_but_replace(const uiBut *old_but, uiBut *new_but) -{ - uiEditSourceButStore *but_store = BLI_ghash_lookup(ui_editsource_info->hash, old_but); - if (but_store) { - BLI_ghash_remove(ui_editsource_info->hash, old_but, NULL, NULL); - BLI_ghash_insert(ui_editsource_info->hash, new_but, but_store); - } -} - -static int editsource_text_edit(bContext *C, - wmOperator *op, - const char filepath[FILE_MAX], - const int line) -{ - struct Main *bmain = CTX_data_main(C); - Text *text = NULL; - - /* Developers may wish to copy-paste to an external editor. */ - printf("%s:%d\n", filepath, line); - - LISTBASE_FOREACH (Text *, text_iter, &bmain->texts) { - if (text_iter->filepath && BLI_path_cmp(text_iter->filepath, filepath) == 0) { - text = text_iter; - break; - } - } - - if (text == NULL) { - text = BKE_text_load(bmain, filepath, BKE_main_blendfile_path(bmain)); - } - - if (text == NULL) { - BKE_reportf(op->reports, RPT_WARNING, "File '%s' cannot be opened", filepath); - return OPERATOR_CANCELLED; - } - - txt_move_toline(text, line - 1, false); - - /* naughty!, find text area to set, not good behavior - * but since this is a developer tool lets allow it - campbell */ - if (!ED_text_activate_in_screen(C, text)) { - BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); - } - - WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); - - return OPERATOR_FINISHED; -} - -static int editsource_exec(bContext *C, wmOperator *op) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but) { - GHashIterator ghi; - struct uiEditSourceButStore *but_store = NULL; - - ARegion *region = CTX_wm_region(C); - int ret; - - /* needed else the active button does not get tested */ - UI_screen_free_active_but_highlight(C, CTX_wm_screen(C)); - - // printf("%s: begin\n", __func__); - - /* take care not to return before calling ui_editsource_active_but_clear */ - ui_editsource_active_but_set(but); - - /* redraw and get active button python info */ - ui_region_redraw_immediately(C, region); - - for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash); - BLI_ghashIterator_done(&ghi) == false; - BLI_ghashIterator_step(&ghi)) { - uiBut *but_key = BLI_ghashIterator_getKey(&ghi); - if (but_key && ui_editsource_uibut_match(&ui_editsource_info->but_orig, but_key)) { - but_store = BLI_ghashIterator_getValue(&ghi); - break; - } - } - - if (but_store) { - if (but_store->py_dbg_line_number != -1) { - ret = editsource_text_edit(C, op, but_store->py_dbg_fn, but_store->py_dbg_line_number); - } - else { - BKE_report( - op->reports, RPT_ERROR, "Active button is not from a script, cannot edit source"); - ret = OPERATOR_CANCELLED; - } - } - else { - BKE_report(op->reports, RPT_ERROR, "Active button match cannot be found"); - ret = OPERATOR_CANCELLED; - } - - ui_editsource_active_but_clear(); - - // printf("%s: end\n", __func__); - - return ret; - } - - BKE_report(op->reports, RPT_ERROR, "Active button not found"); - return OPERATOR_CANCELLED; -} - -static void UI_OT_editsource(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Edit Source"; - ot->idname = "UI_OT_editsource"; - ot->description = "Edit UI source code of the active button"; - - /* callbacks */ - ot->exec = editsource_exec; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit Translation Operator - * \{ */ - -/** - * EditTranslation utility funcs and operator, - * - * \note this includes utility functions and button matching checks. - * this only works in conjunction with a Python operator! - */ -static void edittranslation_find_po_file(const char *root, - const char *uilng, - char *path, - const size_t maxlen) -{ - char tstr[32]; /* Should be more than enough! */ - - /* First, full lang code. */ - BLI_snprintf(tstr, sizeof(tstr), "%s.po", uilng); - BLI_join_dirfile(path, maxlen, root, uilng); - BLI_path_append(path, maxlen, tstr); - if (BLI_is_file(path)) { - return; - } - - /* Now try without the second iso code part (_ES in es_ES). */ - { - const char *tc = NULL; - size_t szt = 0; - tstr[0] = '\0'; - - tc = strchr(uilng, '_'); - if (tc) { - szt = tc - uilng; - if (szt < sizeof(tstr)) { /* Paranoid, should always be true! */ - BLI_strncpy(tstr, uilng, szt + 1); /* +1 for '\0' char! */ - } - } - if (tstr[0]) { - /* Because of some codes like sr_SR@latin... */ - tc = strchr(uilng, '@'); - if (tc) { - BLI_strncpy(tstr + szt, tc, sizeof(tstr) - szt); - } - - BLI_join_dirfile(path, maxlen, root, tstr); - strcat(tstr, ".po"); - BLI_path_append(path, maxlen, tstr); - if (BLI_is_file(path)) { - return; - } - } - } - - /* Else no po file! */ - path[0] = '\0'; -} - -static int edittranslation_exec(bContext *C, wmOperator *op) -{ - uiBut *but = UI_context_active_but_get(C); - if (but == NULL) { - BKE_report(op->reports, RPT_ERROR, "Active button not found"); - return OPERATOR_CANCELLED; - } - - wmOperatorType *ot; - PointerRNA ptr; - char popath[FILE_MAX]; - const char *root = U.i18ndir; - const char *uilng = BLT_lang_get(); - - uiStringInfo but_label = {BUT_GET_LABEL, NULL}; - uiStringInfo rna_label = {BUT_GET_RNA_LABEL, NULL}; - uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, NULL}; - uiStringInfo but_tip = {BUT_GET_TIP, NULL}; - uiStringInfo rna_tip = {BUT_GET_RNA_TIP, NULL}; - uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, NULL}; - uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, NULL}; - uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, NULL}; - uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, NULL}; - uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, NULL}; - - if (!BLI_is_dir(root)) { - BKE_report(op->reports, - RPT_ERROR, - "Please set your Preferences' 'Translation Branches " - "Directory' path to a valid directory"); - return OPERATOR_CANCELLED; - } - ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, 0); - if (ot == NULL) { - BKE_reportf(op->reports, - RPT_ERROR, - "Could not find operator '%s'! Please enable ui_translate add-on " - "in the User Preferences", - EDTSRC_I18N_OP_NAME); - return OPERATOR_CANCELLED; - } - /* Try to find a valid po file for current language... */ - edittranslation_find_po_file(root, uilng, popath, FILE_MAX); - // printf("po path: %s\n", popath); - if (popath[0] == '\0') { - BKE_reportf( - op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); - return OPERATOR_CANCELLED; - } - - UI_but_string_info_get(C, - but, - &but_label, - &rna_label, - &enum_label, - &but_tip, - &rna_tip, - &enum_tip, - &rna_struct, - &rna_prop, - &rna_enum, - &rna_ctxt, - NULL); - - WM_operator_properties_create_ptr(&ptr, ot); - RNA_string_set(&ptr, "lang", uilng); - RNA_string_set(&ptr, "po_file", popath); - RNA_string_set(&ptr, "but_label", but_label.strinfo); - RNA_string_set(&ptr, "rna_label", rna_label.strinfo); - RNA_string_set(&ptr, "enum_label", enum_label.strinfo); - RNA_string_set(&ptr, "but_tip", but_tip.strinfo); - RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); - RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); - RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); - RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); - RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); - RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); - const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, NULL); - - /* Clean up */ - if (but_label.strinfo) { - MEM_freeN(but_label.strinfo); - } - if (rna_label.strinfo) { - MEM_freeN(rna_label.strinfo); - } - if (enum_label.strinfo) { - MEM_freeN(enum_label.strinfo); - } - if (but_tip.strinfo) { - MEM_freeN(but_tip.strinfo); - } - if (rna_tip.strinfo) { - MEM_freeN(rna_tip.strinfo); - } - if (enum_tip.strinfo) { - MEM_freeN(enum_tip.strinfo); - } - if (rna_struct.strinfo) { - MEM_freeN(rna_struct.strinfo); - } - if (rna_prop.strinfo) { - MEM_freeN(rna_prop.strinfo); - } - if (rna_enum.strinfo) { - MEM_freeN(rna_enum.strinfo); - } - if (rna_ctxt.strinfo) { - MEM_freeN(rna_ctxt.strinfo); - } - - return ret; -} - -static void UI_OT_edittranslation_init(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Edit Translation"; - ot->idname = "UI_OT_edittranslation_init"; - ot->description = "Edit i18n in current language for the active button"; - - /* callbacks */ - ot->exec = edittranslation_exec; -} - -#endif /* WITH_PYTHON */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Reload Translation Operator - * \{ */ - -static int reloadtranslation_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) -{ - BLT_lang_init(); - BLF_cache_clear(); - BLT_lang_set(NULL); - UI_reinit_font(); - return OPERATOR_FINISHED; -} - -static void UI_OT_reloadtranslation(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Reload Translation"; - ot->idname = "UI_OT_reloadtranslation"; - ot->description = "Force a full reload of UI translation"; - - /* callbacks */ - ot->exec = reloadtranslation_exec; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Press Button Operator - * \{ */ - -static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - bScreen *screen = CTX_wm_screen(C); - const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed"); - ARegion *region_prev = CTX_wm_region(C); - ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->xy) : NULL; - - if (region == NULL) { - region = region_prev; - } - - if (region == NULL) { - return OPERATOR_PASS_THROUGH; - } - - CTX_wm_region_set(C, region); - uiBut *but = UI_context_active_but_get(C); - CTX_wm_region_set(C, region_prev); - - if (but == NULL) { - return OPERATOR_PASS_THROUGH; - } - if (skip_depressed && (but->flag & (UI_SELECT | UI_SELECT_DRAW))) { - return OPERATOR_PASS_THROUGH; - } - - /* Weak, this is a workaround for 'UI_but_is_tool', which checks the operator type, - * having this avoids a minor drawing glitch. */ - void *but_optype = but->optype; - - UI_but_execute(C, region, but); - - but->optype = but_optype; - - WM_event_add_mousemove(CTX_wm_window(C)); - - return OPERATOR_FINISHED; -} - -static void UI_OT_button_execute(wmOperatorType *ot) -{ - ot->name = "Press Button"; - ot->idname = "UI_OT_button_execute"; - ot->description = "Presses active button"; - - ot->invoke = ui_button_press_invoke; - ot->flag = OPTYPE_INTERNAL; - - RNA_def_boolean(ot->srna, "skip_depressed", 0, "Skip Depressed", ""); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Text Button Clear Operator - * \{ */ - -static int button_string_clear_exec(bContext *C, wmOperator *UNUSED(op)) -{ - uiBut *but = UI_context_active_but_get_respect_menu(C); - - if (but) { - ui_but_active_string_clear_and_exit(C, but); - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_button_string_clear(wmOperatorType *ot) -{ - ot->name = "Clear Button String"; - ot->idname = "UI_OT_button_string_clear"; - ot->description = "Unsets the text of the active button"; - - ot->poll = ED_operator_regionactive; - ot->exec = button_string_clear_exec; - ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drop Color Operator - * \{ */ - -bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) -{ - /* should only return true for regions that include buttons, for now - * return true always */ - if (drag->type == WM_DRAG_COLOR) { - SpaceImage *sima = CTX_wm_space_image(C); - ARegion *region = CTX_wm_region(C); - - if (UI_but_active_drop_color(C)) { - return 1; - } - - if (sima && (sima->mode == SI_MODE_PAINT) && sima->image && - (region && region->regiontype == RGN_TYPE_WINDOW)) { - return 1; - } - } - - return 0; -} - -void UI_drop_color_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) -{ - uiDragColorHandle *drag_info = drag->poin; - - RNA_float_set_array(drop->ptr, "color", drag_info->color); - RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected); -} - -static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - uiBut *but = NULL; - float color[4]; - bool gamma; - - RNA_float_get_array(op->ptr, "color", color); - gamma = RNA_boolean_get(op->ptr, "gamma"); - - /* find button under mouse, check if it has RNA color property and - * if it does copy the data */ - but = ui_region_find_active_but(region); - - if (but && but->type == UI_BTYPE_COLOR && but->rnaprop) { - const int color_len = RNA_property_array_length(&but->rnapoin, but->rnaprop); - BLI_assert(color_len <= 4); - - /* keep alpha channel as-is */ - if (color_len == 4) { - color[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); - } - - if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - if (!gamma) { - IMB_colormanagement_scene_linear_to_srgb_v3(color, color); - } - RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); - RNA_property_update(C, &but->rnapoin, but->rnaprop); - } - else if (RNA_property_subtype(but->rnaprop) == PROP_COLOR) { - if (gamma) { - IMB_colormanagement_srgb_to_scene_linear_v3(color, color); - } - RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); - RNA_property_update(C, &but->rnapoin, but->rnaprop); - } - } - else { - if (gamma) { - srgb_to_linearrgb_v3_v3(color, color); - } - - ED_imapaint_bucket_fill(C, color, op, event->mval); - } - - ED_region_tag_redraw(region); - - return OPERATOR_FINISHED; -} - -static void UI_OT_drop_color(wmOperatorType *ot) -{ - ot->name = "Drop Color"; - ot->idname = "UI_OT_drop_color"; - ot->description = "Drop colors to buttons"; - - ot->invoke = drop_color_invoke; - ot->flag = OPTYPE_INTERNAL; - - RNA_def_float_color(ot->srna, "color", 3, NULL, 0.0, FLT_MAX, "Color", "Source color", 0.0, 1.0); - RNA_def_boolean(ot->srna, "gamma", 0, "Gamma Corrected", "The source color is gamma corrected"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drop Name Operator - * \{ */ - -static bool drop_name_poll(bContext *C) -{ - if (!ED_operator_regionactive(C)) { - return false; - } - - const uiBut *but = UI_but_active_drop_name_button(C); - if (!but) { - return false; - } - - if (but->flag & UI_BUT_DISABLED) { - return false; - } - - return true; -} - -static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) -{ - uiBut *but = UI_but_active_drop_name_button(C); - char *str = RNA_string_get_alloc(op->ptr, "string", NULL, 0, NULL); - - if (str) { - ui_but_set_string_interactive(C, but, str); - MEM_freeN(str); - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_drop_name(wmOperatorType *ot) -{ - ot->name = "Drop Name"; - ot->idname = "UI_OT_drop_name"; - ot->description = "Drop name to button"; - - ot->poll = drop_name_poll; - ot->invoke = drop_name_invoke; - ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; - - RNA_def_string( - ot->srna, "string", NULL, 0, "String", "The string value to drop into the button"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI List Search Operator - * \{ */ - -static bool ui_list_focused_poll(bContext *C) -{ - const ARegion *region = CTX_wm_region(C); - if (!region) { - return false; - } - const wmWindow *win = CTX_wm_window(C); - const uiList *list = UI_list_find_mouse_over(region, win->eventstate); - - return list != NULL; -} - -/** - * Ensure the filter options are set to be visible in the UI list. - * \return if the visibility changed, requiring a redraw. - */ -static bool ui_list_unhide_filter_options(uiList *list) -{ - if (list->filter_flag & UILST_FLT_SHOW) { - /* Nothing to be done. */ - return false; - } - - list->filter_flag |= UILST_FLT_SHOW; - return true; -} - -static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - uiList *list = UI_list_find_mouse_over(region, event); - /* Poll should check. */ - BLI_assert(list != NULL); - - if (ui_list_unhide_filter_options(list)) { - ui_region_redraw_immediately(C, region); - } - - if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) { - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_list_start_filter(wmOperatorType *ot) -{ - ot->name = "List Filter"; - ot->idname = "UI_OT_list_start_filter"; - ot->description = "Start entering filter text for the list in focus"; - - ot->invoke = ui_list_start_filter_invoke; - ot->poll = ui_list_focused_poll; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI Tree-View Drop Operator - * \{ */ - -static bool ui_view_drop_poll(bContext *C) -{ - const wmWindow *win = CTX_wm_window(C); - const ARegion *region = CTX_wm_region(C); - const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, win->eventstate->xy); - - return hovered_item != NULL; -} - -static int ui_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) -{ - if (event->custom != EVT_DATA_DRAGDROP) { - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; - } - - const ARegion *region = CTX_wm_region(C); - uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); - - if (!UI_view_item_drop_handle(C, hovered_item, event->customdata)) { - return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; - } - - return OPERATOR_FINISHED; -} - -static void UI_OT_view_drop(wmOperatorType *ot) -{ - ot->name = "View drop"; - ot->idname = "UI_OT_view_drop"; - ot->description = "Drag and drop items onto a data-set item"; - - ot->invoke = ui_view_drop_invoke; - ot->poll = ui_view_drop_poll; - - ot->flag = OPTYPE_INTERNAL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI View Item Rename Operator - * - * General purpose renaming operator for views. Thanks to this, to add a rename button to context - * menus for example, view API users don't have to implement their own renaming operators with the - * same logic as they already have for their #ui::AbstractViewItem::rename() override. - * - * \{ */ - -static bool ui_view_item_rename_poll(bContext *C) -{ - const ARegion *region = CTX_wm_region(C); - const uiViewItemHandle *active_item = UI_region_views_find_active_item(region); - return active_item != NULL && UI_view_item_can_rename(active_item); -} - -static int ui_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) -{ - ARegion *region = CTX_wm_region(C); - uiViewItemHandle *active_item = UI_region_views_find_active_item(region); - - UI_view_item_begin_rename(active_item); - ED_region_tag_redraw(region); - - return OPERATOR_FINISHED; -} - -static void UI_OT_view_item_rename(wmOperatorType *ot) -{ - ot->name = "Rename View Item"; - ot->idname = "UI_OT_view_item_rename"; - ot->description = "Rename the active item in the data-set view"; - - ot->exec = ui_view_item_rename_exec; - ot->poll = ui_view_item_rename_poll; - /* Could get a custom tooltip via the `get_description()` callback and another overridable - * function of the view. */ - - ot->flag = OPTYPE_INTERNAL; -} -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Material Drag/Drop Operator - * - * \{ */ - -static bool ui_drop_material_poll(bContext *C) -{ - PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); - Object *ob = ptr.data; - if (ob == NULL) { - return false; - } - - PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); - if (RNA_pointer_is_null(&mat_slot)) { - return false; - } - - return true; -} - -static int ui_drop_material_exec(bContext *C, wmOperator *op) -{ - Main *bmain = CTX_data_main(C); - - Material *ma = (Material *)WM_operator_properties_id_lookup_from_name_or_session_uuid( - bmain, op->ptr, ID_MA); - if (ma == NULL) { - return OPERATOR_CANCELLED; - } - - PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); - Object *ob = ptr.data; - BLI_assert(ob); - - PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); - BLI_assert(mat_slot.data); - const int target_slot = RNA_int_get(&mat_slot, "slot_index") + 1; - - /* only drop grease pencil material on grease pencil objects */ - if ((ma->gp_style != NULL) && (ob->type != OB_GPENCIL)) { - return OPERATOR_CANCELLED; - } - - BKE_object_material_assign(bmain, ob, ma, target_slot, BKE_MAT_ASSIGN_USERPREF); - - WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob); - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, NULL); - WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); - DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); - - return OPERATOR_FINISHED; -} - -static void UI_OT_drop_material(wmOperatorType *ot) -{ - ot->name = "Drop Material in Material slots"; - ot->description = "Drag material to Material slots in Properties"; - ot->idname = "UI_OT_drop_material"; - - ot->poll = ui_drop_material_poll; - ot->exec = ui_drop_material_exec; - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; - - WM_operator_properties_id_lookup(ot, false); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Operator & Keymap Registration - * \{ */ - -void ED_operatortypes_ui(void) -{ - WM_operatortype_append(UI_OT_copy_data_path_button); - WM_operatortype_append(UI_OT_copy_as_driver_button); - WM_operatortype_append(UI_OT_copy_python_command_button); - WM_operatortype_append(UI_OT_reset_default_button); - WM_operatortype_append(UI_OT_assign_default_button); - WM_operatortype_append(UI_OT_unset_property_button); - WM_operatortype_append(UI_OT_override_type_set_button); - WM_operatortype_append(UI_OT_override_remove_button); - WM_operatortype_append(UI_OT_copy_to_selected_button); - WM_operatortype_append(UI_OT_jump_to_target_button); - WM_operatortype_append(UI_OT_drop_color); - WM_operatortype_append(UI_OT_drop_name); - WM_operatortype_append(UI_OT_drop_material); -#ifdef WITH_PYTHON - WM_operatortype_append(UI_OT_editsource); - WM_operatortype_append(UI_OT_edittranslation_init); -#endif - WM_operatortype_append(UI_OT_reloadtranslation); - WM_operatortype_append(UI_OT_button_execute); - WM_operatortype_append(UI_OT_button_string_clear); - - WM_operatortype_append(UI_OT_list_start_filter); - - WM_operatortype_append(UI_OT_view_drop); - WM_operatortype_append(UI_OT_view_item_rename); - - /* external */ - WM_operatortype_append(UI_OT_eyedropper_color); - WM_operatortype_append(UI_OT_eyedropper_colorramp); - WM_operatortype_append(UI_OT_eyedropper_colorramp_point); - WM_operatortype_append(UI_OT_eyedropper_id); - WM_operatortype_append(UI_OT_eyedropper_depth); - WM_operatortype_append(UI_OT_eyedropper_driver); - WM_operatortype_append(UI_OT_eyedropper_gpencil_color); -} - -void ED_keymap_ui(wmKeyConfig *keyconf) -{ - WM_keymap_ensure(keyconf, "User Interface", 0, 0); - - eyedropper_modal_keymap(keyconf); - eyedropper_colorband_modal_keymap(keyconf); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_ops.cc b/source/blender/editors/interface/interface_ops.cc new file mode 100644 index 00000000000..2533a5454a5 --- /dev/null +++ b/source/blender/editors/interface/interface_ops.cc @@ -0,0 +1,2273 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_armature_types.h" +#include "DNA_material_types.h" +#include "DNA_modifier_types.h" /* for handling geometry nodes properties */ +#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */ +#include "DNA_screen_types.h" +#include "DNA_text_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math_color.h" + +#include "BLF_api.h" +#include "BLT_lang.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_lib_override.h" +#include "BKE_material.h" +#include "BKE_node.h" +#include "BKE_report.h" +#include "BKE_screen.h" +#include "BKE_text.h" + +#include "IMB_colormanagement.h" + +#include "DEG_depsgraph.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_path.h" +#include "RNA_prototypes.h" +#include "RNA_types.h" + +#include "UI_interface.h" + +#include "interface_intern.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_paint.h" + +/* for Copy As Driver */ +#include "ED_keyframing.h" + +/* only for UI_OT_editsource */ +#include "BKE_main.h" +#include "BLI_ghash.h" +#include "ED_screen.h" +#include "ED_text.h" + +/* -------------------------------------------------------------------- */ +/** \name Immediate redraw helper + * + * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That + * violates the Model-View-Controller pattern. + * + * But there are some operators which really need to re-run the layout definitions for various + * reasons. For example, "Edit Source" does it to find out which exact Python code added a button. + * Other operators may need to access buttons that aren't currently visible. In Blender's UI code + * design that typically means just not adding the button in the first place, for a particular + * redraw. So the operator needs to change context and re-create the layout, so the button becomes + * available to act on. + * + * \{ */ + +static void ui_region_redraw_immediately(bContext *C, ARegion *region) +{ + ED_region_do_layout(C, region); + WM_draw_region_viewport_bind(region); + ED_region_do_draw(C, region); + WM_draw_region_viewport_unbind(region); + region->do_draw = false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Data Path Operator + * \{ */ + +static bool copy_data_path_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop) { + path = RNA_path_from_ID_to_property(&ptr, prop); + + if (path) { + MEM_freeN(path); + return true; + } + } + + return false; +} + +static int copy_data_path_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + ID *id; + + const bool full_path = RNA_boolean_get(op->ptr, "full_path"); + + /* try to create driver using property retrieved from UI */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id != nullptr) { + if (full_path) { + if (prop) { + path = RNA_path_full_property_py_ex(bmain, &ptr, prop, index, true); + } + else { + path = RNA_path_full_struct_py(bmain, &ptr); + } + } + else { + path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, 0, -1, &id); + + if (!path) { + path = RNA_path_from_ID_to_property(&ptr, prop); + } + } + + if (path) { + WM_clipboard_text_set(path, false); + MEM_freeN(path); + return OPERATOR_FINISHED; + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_data_path_button(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Copy Data Path"; + ot->idname = "UI_OT_copy_data_path_button"; + ot->description = "Copy the RNA data path for this property to the clipboard"; + + /* callbacks */ + ot->exec = copy_data_path_button_exec; + ot->poll = copy_data_path_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "full_path", false, "full_path", "Copy full data path"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy As Driver Operator + * \{ */ + +static bool copy_as_driver_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop && + ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM) && + (index >= 0 || !RNA_property_array_check(prop))) { + path = RNA_path_from_ID_to_property(&ptr, prop); + + if (path) { + MEM_freeN(path); + return true; + } + } + + return false; +} + +static int copy_as_driver_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to create driver using property retrieved from UI */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop) { + ID *id; + const int dim = RNA_property_array_dimension(&ptr, prop, nullptr); + char *path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, dim, index, &id); + + if (path) { + ANIM_copy_as_driver(id, path, RNA_property_identifier(prop)); + MEM_freeN(path); + return OPERATOR_FINISHED; + } + + BKE_reportf(op->reports, RPT_ERROR, "Could not compute a valid data path"); + return OPERATOR_CANCELLED; + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_as_driver_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy as New Driver"; + ot->idname = "UI_OT_copy_as_driver_button"; + ot->description = + "Create a new driver with this property as input, and copy it to the " + "clipboard. Use Paste Driver to add it to the target property, or Paste " + "Driver Variables to extend an existing driver"; + + /* callbacks */ + ot->exec = copy_as_driver_button_exec; + ot->poll = copy_as_driver_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Python Command Operator + * \{ */ + +static bool copy_python_command_button_poll(bContext *C) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && (but->optype != nullptr)) { + return true; + } + + return false; +} + +static int copy_python_command_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && (but->optype != nullptr)) { + PointerRNA *opptr; + char *str; + opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */ + + str = WM_operator_pystring_ex(C, nullptr, false, true, but->optype, opptr); + + WM_clipboard_text_set(str, false); + + MEM_freeN(str); + + return OPERATOR_FINISHED; + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_python_command_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Python Command"; + ot->idname = "UI_OT_copy_python_command_button"; + ot->description = "Copy the Python command matching this button"; + + /* callbacks */ + ot->exec = copy_python_command_button_exec; + ot->poll = copy_python_command_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reset to Default Values Button Operator + * \{ */ + +static int operator_button_property_finish(bContext *C, PointerRNA *ptr, PropertyRNA *prop) +{ + ID *id = ptr->owner_id; + + /* perform updates required for this property */ + RNA_property_update(C, ptr, prop); + + /* as if we pressed the button */ + UI_context_active_but_prop_handle(C, false); + + /* Since we don't want to undo _all_ edits to settings, eg window + * edits on the screen or on operator settings. + * it might be better to move undo's inline - campbell */ + if (id && ID_CHECK_UNDO(id)) { + /* do nothing, go ahead with undo */ + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static int operator_button_property_finish_with_undo(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop) +{ + /* Perform updates required for this property. */ + RNA_property_update(C, ptr, prop); + + /* As if we pressed the button. */ + UI_context_active_but_prop_handle(C, true); + + return OPERATOR_FINISHED; +} + +static bool reset_default_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + return (ptr.data && prop && RNA_property_editable(&ptr, prop)); +} + +static int reset_default_button_exec(bContext *C, wmOperator *op) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + const bool all = RNA_boolean_get(op->ptr, "all"); + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) { + return operator_button_property_finish_with_undo(C, &ptr, prop); + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_reset_default_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset to Default Value"; + ot->idname = "UI_OT_reset_default_button"; + ot->description = "Reset this property's value to its default value"; + + /* callbacks */ + ot->poll = reset_default_button_poll; + ot->exec = reset_default_button_exec; + + /* flags */ + /* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo + * is responsible for the undo push. */ + ot->flag = 0; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Assign Value as Default Button Operator + * \{ */ + +static bool assign_default_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + const PropertyType type = RNA_property_type(prop); + + return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) && + ELEM(type, PROP_INT, PROP_FLOAT); + } + + return false; +} + +static int assign_default_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + if (RNA_property_assign_default(&ptr, prop)) { + return operator_button_property_finish(C, &ptr, prop); + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_assign_default_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Assign Value as Default"; + ot->idname = "UI_OT_assign_default_button"; + ot->description = "Set this property's current value as the new default"; + + /* callbacks */ + ot->poll = assign_default_button_poll; + ot->exec = assign_default_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unset Property Button Operator + * \{ */ + +static int unset_property_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to unset the nominated property */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop) && + /* RNA_property_is_idprop(prop) && */ + RNA_property_is_set(&ptr, prop)) { + RNA_property_unset(&ptr, prop); + return operator_button_property_finish(C, &ptr, prop); + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_unset_property_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Unset Property"; + ot->idname = "UI_OT_unset_property_button"; + ot->description = "Clear the property and use default or generated value in operators"; + + /* callbacks */ + ot->poll = ED_operator_regionactive; + ot->exec = unset_property_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Define Override Type Operator + * \{ */ + +/* Note that we use different values for UI/UX than 'real' override operations, user does not care + * whether it's added or removed for the differential operation e.g. */ +enum { + UIOverride_Type_NOOP = 0, + UIOverride_Type_Replace = 1, + UIOverride_Type_Difference = 2, /* Add/subtract */ + UIOverride_Type_Factor = 3, /* Multiply */ + /* TODO: should/can we expose insert/remove ones for collections? Doubt it... */ +}; + +static EnumPropertyItem override_type_items[] = { + {UIOverride_Type_NOOP, + "NOOP", + 0, + "NoOp", + "'No-Operation', place holder preventing automatic override to ever affect the property"}, + {UIOverride_Type_Replace, + "REPLACE", + 0, + "Replace", + "Completely replace value from linked data by local one"}, + {UIOverride_Type_Difference, + "DIFFERENCE", + 0, + "Difference", + "Store difference to linked data value"}, + {UIOverride_Type_Factor, + "FACTOR", + 0, + "Factor", + "Store factor to linked data value (useful e.g. for scale)"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static bool override_type_set_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), &ptr, prop, index); + + return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE)); +} + +static int override_type_set_button_exec(bContext *C, wmOperator *op) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool created; + const bool all = RNA_boolean_get(op->ptr, "all"); + const int op_type = RNA_enum_get(op->ptr, "type"); + + short operation; + + switch (op_type) { + case UIOverride_Type_NOOP: + operation = IDOVERRIDE_LIBRARY_OP_NOOP; + break; + case UIOverride_Type_Replace: + operation = IDOVERRIDE_LIBRARY_OP_REPLACE; + break; + case UIOverride_Type_Difference: + /* override code will automatically switch to subtract if needed. */ + operation = IDOVERRIDE_LIBRARY_OP_ADD; + break; + case UIOverride_Type_Factor: + operation = IDOVERRIDE_LIBRARY_OP_MULTIPLY; + break; + default: + operation = IDOVERRIDE_LIBRARY_OP_REPLACE; + BLI_assert(0); + break; + } + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + BLI_assert(ptr.owner_id != nullptr); + + if (all) { + index = -1; + } + + IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_get( + CTX_data_main(C), &ptr, prop, operation, index, true, nullptr, &created); + + if (opop == nullptr) { + /* Sometimes e.g. RNA cannot generate a path to the given property. */ + BKE_reportf(op->reports, RPT_WARNING, "Failed to create the override operation"); + return OPERATOR_CANCELLED; + } + + if (!created) { + opop->operation = operation; + } + + /* Outliner e.g. has to be aware of this change. */ + WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + + return operator_button_property_finish(C, &ptr, prop); +} + +static int override_type_set_button_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ +#if 0 /* Disabled for now */ + return WM_menu_invoke_ex(C, op, WM_OP_INVOKE_DEFAULT); +#else + RNA_enum_set(op->ptr, "type", IDOVERRIDE_LIBRARY_OP_REPLACE); + return override_type_set_button_exec(C, op); +#endif +} + +static void UI_OT_override_type_set_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Define Override Type"; + ot->idname = "UI_OT_override_type_set_button"; + ot->description = "Create an override operation, or set the type of an existing one"; + + /* callbacks */ + ot->poll = override_type_set_button_poll; + ot->exec = override_type_set_button_exec; + ot->invoke = override_type_set_button_invoke; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); + ot->prop = RNA_def_enum(ot->srna, + "type", + override_type_items, + UIOverride_Type_Replace, + "Type", + "Type of override operation"); + /* TODO: add itemf callback, not all options are available for all data types... */ +} + +static bool override_remove_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), &ptr, prop, index); + + return (ptr.data && ptr.owner_id && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN)); +} + +static int override_remove_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr, id_refptr, src; + PropertyRNA *prop; + int index; + const bool all = RNA_boolean_get(op->ptr, "all"); + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + ID *id = ptr.owner_id; + IDOverrideLibraryProperty *oprop = RNA_property_override_property_find(bmain, &ptr, prop, &id); + BLI_assert(oprop != nullptr); + BLI_assert(id != nullptr && id->override_library != nullptr); + + const bool is_template = ID_IS_OVERRIDE_LIBRARY_TEMPLATE(id); + + /* We need source (i.e. linked data) to restore values of deleted overrides... + * If this is an override template, we obviously do not need to restore anything. */ + if (!is_template) { + PropertyRNA *src_prop; + RNA_id_pointer_create(id->override_library->reference, &id_refptr); + if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) { + BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer"); + } + } + + if (!all && index != -1) { + bool is_strict_find; + /* Remove override operation for given item, + * add singular operations for the other items as needed. */ + IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find( + oprop, nullptr, nullptr, index, index, false, &is_strict_find); + BLI_assert(opop != nullptr); + if (!is_strict_find) { + /* No specific override operation, we have to get generic one, + * and create item-specific override operations for all but given index, + * before removing generic one. */ + for (int idx = RNA_property_array_length(&ptr, prop); idx--;) { + if (idx != index) { + BKE_lib_override_library_property_operation_get( + oprop, opop->operation, nullptr, nullptr, idx, idx, true, nullptr, nullptr); + } + } + } + BKE_lib_override_library_property_operation_delete(oprop, opop); + if (!is_template) { + RNA_property_copy(bmain, &ptr, &src, prop, index); + } + if (BLI_listbase_is_empty(&oprop->operations)) { + BKE_lib_override_library_property_delete(id->override_library, oprop); + } + } + else { + /* Just remove whole generic override operation of this property. */ + BKE_lib_override_library_property_delete(id->override_library, oprop); + if (!is_template) { + RNA_property_copy(bmain, &ptr, &src, prop, -1); + } + } + + /* Outliner e.g. has to be aware of this change. */ + WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + + return operator_button_property_finish(C, &ptr, prop); +} + +static void UI_OT_override_remove_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Override"; + ot->idname = "UI_OT_override_remove_button"; + ot->description = "Remove an override operation"; + + /* callbacks */ + ot->poll = override_remove_button_poll; + ot->exec = override_remove_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy To Selected Operator + * \{ */ + +#define NOT_NULL(assignment) ((assignment) != nullptr) +#define NOT_RNA_NULL(assignment) ((assignment).data != nullptr) + +static void ui_context_selected_bones_via_pose(bContext *C, ListBase *r_lb) +{ + ListBase lb; + lb = CTX_data_collection_get(C, "selected_pose_bones"); + + if (!BLI_listbase_is_empty(&lb)) { + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + bPoseChannel *pchan = static_cast(link->ptr.data); + RNA_pointer_create(link->ptr.owner_id, &RNA_Bone, pchan->bone, &link->ptr); + } + } + + *r_lb = lb; +} + +bool UI_context_copy_to_selected_list(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + ListBase *r_lb, + bool *r_use_path_from_id, + char **r_path) +{ + *r_use_path_from_id = false; + *r_path = nullptr; + /* special case for bone constraints */ + char *path_from_bone = nullptr; + /* Remove links from the collection list which don't contain 'prop'. */ + bool ensure_list_items_contain_prop = false; + + /* PropertyGroup objects don't have a reference to the struct that actually owns + * them, so it is normally necessary to do a brute force search to find it. This + * handles the search for non-ID owners by using the 'active' reference as a hint + * to preserve efficiency. Only properties defined through RNA are handled, as + * custom properties cannot be assumed to be valid for all instances. + * + * Properties owned by the ID are handled by the 'if (ptr->owner_id)' case below. + */ + if (!RNA_property_is_idprop(prop) && RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) { + PointerRNA owner_ptr; + char *idpath = nullptr; + + /* First, check the active PoseBone and PoseBone->Bone. */ + if (NOT_RNA_NULL( + owner_ptr = CTX_data_pointer_get_type(C, "active_pose_bone", &RNA_PoseBone))) { + if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast(ptr->data)))) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + } + else { + bPoseChannel *pchan = static_cast(owner_ptr.data); + RNA_pointer_create(owner_ptr.owner_id, &RNA_Bone, pchan->bone, &owner_ptr); + + if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast(ptr->data)))) { + ui_context_selected_bones_via_pose(C, r_lb); + } + } + } + + if (idpath == nullptr) { + /* Check the active EditBone if in edit mode. */ + if (NOT_RNA_NULL( + owner_ptr = CTX_data_pointer_get_type_silent(C, "active_bone", &RNA_EditBone)) && + NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast(ptr->data)))) { + *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); + } + + /* Add other simple cases here (Node, NodeSocket, Sequence, ViewLayer etc). */ + } + + if (idpath) { + *r_path = BLI_sprintfN("%s.%s", idpath, RNA_property_identifier(prop)); + MEM_freeN(idpath); + return true; + } + } + + if (RNA_struct_is_a(ptr->type, &RNA_EditBone)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_PoseBone)) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) { + ui_context_selected_bones_via_pose(C, r_lb); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) { + /* Special case when we do this for 'Sequence.lock'. + * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ + const char *prop_id = RNA_property_identifier(prop); + if (STREQ(prop_id, "lock")) { + *r_lb = CTX_data_collection_get(C, "selected_sequences"); + } + else { + *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); + } + /* Account for properties only being available for some sequence types. */ + ensure_list_items_contain_prop = true; + } + else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_fcurves"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Keyframe)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_keyframes"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Action)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_actions"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) { + *r_lb = CTX_data_collection_get(C, "selected_nla_strips"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) { + *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) && + (path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) != + nullptr) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + *r_path = path_from_bone; + } + else if (RNA_struct_is_a(ptr->type, &RNA_Node) || RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { + ListBase lb = {nullptr, nullptr}; + char *path = nullptr; + bNode *node = nullptr; + + /* Get the node we're editing */ + if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNodeSocket *sock = static_cast(ptr->data); + if (nodeFindNode(ntree, sock, &node, nullptr)) { + if ((path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node)) != nullptr) { + /* we're good! */ + } + else { + node = nullptr; + } + } + } + else { + node = static_cast(ptr->data); + } + + /* Now filter by type */ + if (node) { + lb = CTX_data_collection_get(C, "selected_nodes"); + + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { + bNode *node_data = static_cast(link->ptr.data); + + if (node_data->type != node->type) { + BLI_remlink(&lb, link); + MEM_freeN(link); + } + } + } + + *r_lb = lb; + *r_path = path; + } + else if (ptr->owner_id) { + ID *id = ptr->owner_id; + + if (GS(id->name) == ID_OB) { + *r_lb = CTX_data_collection_get(C, "selected_editable_objects"); + *r_use_path_from_id = true; + *r_path = RNA_path_from_ID_to_property(ptr, prop); + } + else if (OB_DATA_SUPPORT_ID(GS(id->name))) { + /* check we're using the active object */ + const short id_code = GS(id->name); + ListBase lb = CTX_data_collection_get(C, "selected_editable_objects"); + char *path = RNA_path_from_ID_to_property(ptr, prop); + + /* de-duplicate obdata */ + if (!BLI_listbase_is_empty(&lb)) { + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + Object *ob = (Object *)link->ptr.owner_id; + if (ob->data) { + ID *id_data = static_cast(ob->data); + id_data->tag |= LIB_TAG_DOIT; + } + } + + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { + Object *ob = (Object *)link->ptr.owner_id; + ID *id_data = static_cast(ob->data); + + if ((id_data == nullptr) || (id_data->tag & LIB_TAG_DOIT) == 0 || + ID_IS_LINKED(id_data) || (GS(id_data->name) != id_code)) { + BLI_remlink(&lb, link); + MEM_freeN(link); + } + else { + /* Avoid prepending 'data' to the path. */ + RNA_id_pointer_create(id_data, &link->ptr); + } + + if (id_data) { + id_data->tag &= ~LIB_TAG_DOIT; + } + } + } + + *r_lb = lb; + *r_path = path; + } + else if (GS(id->name) == ID_SCE) { + /* Sequencer's ID is scene :/ */ + /* Try to recursively find an RNA_Sequence ancestor, + * to handle situations like T41062... */ + if ((*r_path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Sequence)) != + nullptr) { + /* Special case when we do this for 'Sequence.lock'. + * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ + const char *prop_id = RNA_property_identifier(prop); + if (STREQ(prop_id, "lock")) { + *r_lb = CTX_data_collection_get(C, "selected_sequences"); + } + else { + *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); + } + /* Account for properties only being available for some sequence types. */ + ensure_list_items_contain_prop = true; + } + } + return (*r_path != nullptr); + } + else { + return false; + } + + if (ensure_list_items_contain_prop) { + const char *prop_id = RNA_property_identifier(prop); + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, r_lb) { + if ((ptr->type != link->ptr.type) && + (RNA_struct_type_find_property(link->ptr.type, prop_id) != prop)) { + BLI_remlink(r_lb, link); + MEM_freeN(link); + } + } + } + + return true; +} + +bool UI_context_copy_to_selected_check(PointerRNA *ptr, + PointerRNA *ptr_link, + PropertyRNA *prop, + const char *path, + bool use_path_from_id, + PointerRNA *r_ptr, + PropertyRNA **r_prop) +{ + PointerRNA idptr; + PropertyRNA *lprop; + PointerRNA lptr; + + if (ptr_link->data == ptr->data) { + return false; + } + + if (use_path_from_id) { + /* Path relative to ID. */ + lprop = nullptr; + RNA_id_pointer_create(ptr_link->owner_id, &idptr); + RNA_path_resolve_property(&idptr, path, &lptr, &lprop); + } + else if (path) { + /* Path relative to elements from list. */ + lprop = nullptr; + RNA_path_resolve_property(ptr_link, path, &lptr, &lprop); + } + else { + lptr = *ptr_link; + lprop = prop; + } + + if (lptr.data == ptr->data) { + /* temp_ptr might not be the same as ptr_link! */ + return false; + } + + /* Skip non-existing properties on link. This was previously covered with the `lprop != prop` + * check but we are now more permissive when it comes to ID properties, see below. */ + if (lprop == nullptr) { + return false; + } + + if (RNA_property_type(lprop) != RNA_property_type(prop)) { + return false; + } + + /* Check property pointers matching. + * For ID properties, these pointers match: + * - If the property is API defined on an existing class (and they are equally named). + * - Never for ID properties on specific ID (even if they are equally named). + * - Never for NodesModifierSettings properties (even if they are equally named). + * + * Be permissive on ID properties in the following cases: + * - #NodesModifierSettings properties + * - (special check: only if the node-group matches, since the 'Input_n' properties are name + * based and similar on potentially very different node-groups). + * - ID properties on specific ID + * - (no special check, copying seems OK [even if type does not match -- does not do anything + * then]) + */ + bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop); + if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) && + RNA_struct_is_a(ptr->type, &RNA_NodesModifier)) { + ignore_prop_eq = false; + + NodesModifierData *nmd_link = (NodesModifierData *)lptr.data; + NodesModifierData *nmd_src = (NodesModifierData *)ptr->data; + if (nmd_link->node_group == nmd_src->node_group) { + ignore_prop_eq = true; + } + } + + if ((lprop != prop) && !ignore_prop_eq) { + return false; + } + + if (!RNA_property_editable(&lptr, lprop)) { + return false; + } + + if (r_ptr) { + *r_ptr = lptr; + } + if (r_prop) { + *r_prop = lprop; + } + + return true; +} + +/** + * Called from both exec & poll. + * + * \note Normally we wouldn't call a loop from within a poll function, + * however this is a special case, and for regular poll calls, getting + * the context from the button will fail early. + */ +static bool copy_to_selected_button(bContext *C, bool all, bool poll) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr, lptr; + PropertyRNA *prop, *lprop; + bool success = false; + int index; + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data == nullptr || prop == nullptr) { + return false; + } + + char *path = nullptr; + bool use_path_from_id; + ListBase lb = {nullptr}; + + if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) { + return false; + } + if (BLI_listbase_is_empty(&lb)) { + MEM_SAFE_FREE(path); + return false; + } + + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + if (link->ptr.data == ptr.data) { + continue; + } + + if (!UI_context_copy_to_selected_check( + &ptr, &link->ptr, prop, path, use_path_from_id, &lptr, &lprop)) { + continue; + } + + if (poll) { + success = true; + break; + } + if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { + RNA_property_update(C, &lptr, prop); + success = true; + } + } + + MEM_SAFE_FREE(path); + BLI_freelistN(&lb); + + return success; +} + +static bool copy_to_selected_button_poll(bContext *C) +{ + return copy_to_selected_button(C, false, true); +} + +static int copy_to_selected_button_exec(bContext *C, wmOperator *op) +{ + bool success; + + const bool all = RNA_boolean_get(op->ptr, "all"); + + success = copy_to_selected_button(C, all, false); + + return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void UI_OT_copy_to_selected_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy to Selected"; + ot->idname = "UI_OT_copy_to_selected_button"; + ot->description = + "Copy the property's value from the active item to the same property of all selected items " + "if the same property exists"; + + /* callbacks */ + ot->poll = copy_to_selected_button_poll; + ot->exec = copy_to_selected_button_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Jump to Target Operator + * \{ */ + +/** Jump to the object or bone referenced by the pointer, or check if it is possible. */ +static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll) +{ + if (RNA_pointer_is_null(&ptr)) { + return false; + } + + /* Verify pointer type. */ + char bone_name[MAXBONENAME]; + const StructRNA *target_type = nullptr; + + if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) { + RNA_string_get(&ptr, "name", bone_name); + if (bone_name[0] != '\0') { + target_type = &RNA_Bone; + } + } + else if (RNA_struct_is_a(ptr.type, &RNA_Object)) { + target_type = &RNA_Object; + } + + if (target_type == nullptr) { + return false; + } + + /* Find the containing Object. */ + ViewLayer *view_layer = CTX_data_view_layer(C); + Base *base = nullptr; + const short id_type = GS(ptr.owner_id->name); + if (id_type == ID_OB) { + base = BKE_view_layer_base_find(view_layer, (Object *)ptr.owner_id); + } + else if (OB_DATA_SUPPORT_ID(id_type)) { + base = ED_object_find_first_by_data_id(view_layer, ptr.owner_id); + } + + bool ok = false; + if ((base == nullptr) || ((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE))) { + /* pass */ + } + else if (poll) { + ok = true; + } + else { + /* Make optional. */ + const bool reveal_hidden = true; + /* Select and activate the target. */ + if (target_type == &RNA_Bone) { + ok = ED_object_jump_to_bone(C, base->object, bone_name, reveal_hidden); + } + else if (target_type == &RNA_Object) { + ok = ED_object_jump_to_object(C, base->object, reveal_hidden); + } + else { + BLI_assert(0); + } + } + return ok; +} + +/** + * Jump to the object or bone referred to by the current UI field value. + * + * \note quite heavy for a poll callback, but the operator is only + * used as a right click menu item for certain UI field types, and + * this will fail quickly if the context is completely unsuitable. + */ +static bool jump_to_target_button(bContext *C, bool poll) +{ + PointerRNA ptr, target_ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* If there is a valid property... */ + if (ptr.data && prop) { + const PropertyType type = RNA_property_type(prop); + + /* For pointer properties, use their value directly. */ + if (type == PROP_POINTER) { + target_ptr = RNA_property_pointer_get(&ptr, prop); + + return jump_to_target_ptr(C, target_ptr, poll); + } + /* For string properties with prop_search, look up the search collection item. */ + if (type == PROP_STRING) { + const uiBut *but = UI_context_active_but_get(C); + const uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : + nullptr; + + if (search_but && search_but->items_update_fn == ui_rna_collection_search_update_fn) { + uiRNACollectionSearch *coll_search = static_cast(search_but->arg); + + char str_buf[MAXBONENAME]; + char *str_ptr = RNA_property_string_get_alloc( + &ptr, prop, str_buf, sizeof(str_buf), nullptr); + + int found = RNA_property_collection_lookup_string( + &coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr); + + if (str_ptr != str_buf) { + MEM_freeN(str_ptr); + } + + if (found) { + return jump_to_target_ptr(C, target_ptr, poll); + } + } + } + } + + return false; +} + +bool ui_jump_to_target_button_poll(bContext *C) +{ + return jump_to_target_button(C, true); +} + +static int jump_to_target_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const bool success = jump_to_target_button(C, false); + + return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void UI_OT_jump_to_target_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Jump to Target"; + ot->idname = "UI_OT_jump_to_target_button"; + ot->description = "Switch to the target object or bone"; + + /* callbacks */ + ot->poll = ui_jump_to_target_button_poll; + ot->exec = jump_to_target_button_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit Python Source Operator + * \{ */ + +#ifdef WITH_PYTHON + +/* ------------------------------------------------------------------------- */ +/* EditSource Utility funcs and operator, + * NOTE: this includes utility functions and button matching checks. */ + +struct uiEditSourceStore { + uiBut but_orig; + GHash *hash; +}; + +struct uiEditSourceButStore { + char py_dbg_fn[FILE_MAX]; + int py_dbg_line_number; +}; + +/* should only ever be set while the edit source operator is running */ +static uiEditSourceStore *ui_editsource_info = nullptr; + +bool UI_editsource_enable_check(void) +{ + return (ui_editsource_info != nullptr); +} + +static void ui_editsource_active_but_set(uiBut *but) +{ + BLI_assert(ui_editsource_info == nullptr); + + ui_editsource_info = MEM_cnew(__func__); + memcpy(&ui_editsource_info->but_orig, but, sizeof(uiBut)); + + ui_editsource_info->hash = BLI_ghash_ptr_new(__func__); +} + +static void ui_editsource_active_but_clear() +{ + BLI_ghash_free(ui_editsource_info->hash, nullptr, MEM_freeN); + MEM_freeN(ui_editsource_info); + ui_editsource_info = nullptr; +} + +static bool ui_editsource_uibut_match(uiBut *but_a, uiBut *but_b) +{ +# if 0 + printf("matching buttons: '%s' == '%s'\n", but_a->drawstr, but_b->drawstr); +# endif + + /* this just needs to be a 'good-enough' comparison so we can know beyond + * reasonable doubt that these buttons are the same between redraws. + * if this fails it only means edit-source fails - campbell */ + if (BLI_rctf_compare(&but_a->rect, &but_b->rect, FLT_EPSILON) && (but_a->type == but_b->type) && + (but_a->rnaprop == but_b->rnaprop) && (but_a->optype == but_b->optype) && + (but_a->unit_type == but_b->unit_type) && + STREQLEN(but_a->drawstr, but_b->drawstr, UI_MAX_DRAW_STR)) { + return true; + } + return false; +} + +extern "C" { +void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno); +} + +void UI_editsource_active_but_test(uiBut *but) +{ + + uiEditSourceButStore *but_store = MEM_cnew(__func__); + + const char *fn; + int line_number = -1; + +# if 0 + printf("comparing buttons: '%s' == '%s'\n", but->drawstr, ui_editsource_info->but_orig.drawstr); +# endif + + PyC_FileAndNum_Safe(&fn, &line_number); + + if (line_number != -1) { + BLI_strncpy(but_store->py_dbg_fn, fn, sizeof(but_store->py_dbg_fn)); + but_store->py_dbg_line_number = line_number; + } + else { + but_store->py_dbg_fn[0] = '\0'; + but_store->py_dbg_line_number = -1; + } + + BLI_ghash_insert(ui_editsource_info->hash, but, but_store); +} + +void UI_editsource_but_replace(const uiBut *old_but, uiBut *new_but) +{ + uiEditSourceButStore *but_store = static_cast( + BLI_ghash_lookup(ui_editsource_info->hash, old_but)); + if (but_store) { + BLI_ghash_remove(ui_editsource_info->hash, old_but, nullptr, nullptr); + BLI_ghash_insert(ui_editsource_info->hash, new_but, but_store); + } +} + +static int editsource_text_edit(bContext *C, + wmOperator *op, + const char filepath[FILE_MAX], + const int line) +{ + Main *bmain = CTX_data_main(C); + Text *text = nullptr; + + /* Developers may wish to copy-paste to an external editor. */ + printf("%s:%d\n", filepath, line); + + LISTBASE_FOREACH (Text *, text_iter, &bmain->texts) { + if (text_iter->filepath && BLI_path_cmp(text_iter->filepath, filepath) == 0) { + text = text_iter; + break; + } + } + + if (text == nullptr) { + text = BKE_text_load(bmain, filepath, BKE_main_blendfile_path(bmain)); + } + + if (text == nullptr) { + BKE_reportf(op->reports, RPT_WARNING, "File '%s' cannot be opened", filepath); + return OPERATOR_CANCELLED; + } + + txt_move_toline(text, line - 1, false); + + /* naughty!, find text area to set, not good behavior + * but since this is a developer tool lets allow it - campbell */ + if (!ED_text_activate_in_screen(C, text)) { + BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); + } + + WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); + + return OPERATOR_FINISHED; +} + +static int editsource_exec(bContext *C, wmOperator *op) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but) { + GHashIterator ghi; + uiEditSourceButStore *but_store = nullptr; + + ARegion *region = CTX_wm_region(C); + int ret; + + /* needed else the active button does not get tested */ + UI_screen_free_active_but_highlight(C, CTX_wm_screen(C)); + + // printf("%s: begin\n", __func__); + + /* take care not to return before calling ui_editsource_active_but_clear */ + ui_editsource_active_but_set(but); + + /* redraw and get active button python info */ + ui_region_redraw_immediately(C, region); + + for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash); + BLI_ghashIterator_done(&ghi) == false; + BLI_ghashIterator_step(&ghi)) { + uiBut *but_key = static_cast(BLI_ghashIterator_getKey(&ghi)); + if (but_key && ui_editsource_uibut_match(&ui_editsource_info->but_orig, but_key)) { + but_store = static_cast(BLI_ghashIterator_getValue(&ghi)); + break; + } + } + + if (but_store) { + if (but_store->py_dbg_line_number != -1) { + ret = editsource_text_edit(C, op, but_store->py_dbg_fn, but_store->py_dbg_line_number); + } + else { + BKE_report( + op->reports, RPT_ERROR, "Active button is not from a script, cannot edit source"); + ret = OPERATOR_CANCELLED; + } + } + else { + BKE_report(op->reports, RPT_ERROR, "Active button match cannot be found"); + ret = OPERATOR_CANCELLED; + } + + ui_editsource_active_but_clear(); + + // printf("%s: end\n", __func__); + + return ret; + } + + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; +} + +static void UI_OT_editsource(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Edit Source"; + ot->idname = "UI_OT_editsource"; + ot->description = "Edit UI source code of the active button"; + + /* callbacks */ + ot->exec = editsource_exec; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit Translation Operator + * \{ */ + +/** + * EditTranslation utility funcs and operator, + * + * \note this includes utility functions and button matching checks. + * this only works in conjunction with a Python operator! + */ +static void edittranslation_find_po_file(const char *root, + const char *uilng, + char *path, + const size_t maxlen) +{ + char tstr[32]; /* Should be more than enough! */ + + /* First, full lang code. */ + BLI_snprintf(tstr, sizeof(tstr), "%s.po", uilng); + BLI_join_dirfile(path, maxlen, root, uilng); + BLI_path_append(path, maxlen, tstr); + if (BLI_is_file(path)) { + return; + } + + /* Now try without the second iso code part (_ES in es_ES). */ + { + const char *tc = nullptr; + size_t szt = 0; + tstr[0] = '\0'; + + tc = strchr(uilng, '_'); + if (tc) { + szt = tc - uilng; + if (szt < sizeof(tstr)) { /* Paranoid, should always be true! */ + BLI_strncpy(tstr, uilng, szt + 1); /* +1 for '\0' char! */ + } + } + if (tstr[0]) { + /* Because of some codes like sr_SR@latin... */ + tc = strchr(uilng, '@'); + if (tc) { + BLI_strncpy(tstr + szt, tc, sizeof(tstr) - szt); + } + + BLI_join_dirfile(path, maxlen, root, tstr); + strcat(tstr, ".po"); + BLI_path_append(path, maxlen, tstr); + if (BLI_is_file(path)) { + return; + } + } + } + + /* Else no po file! */ + path[0] = '\0'; +} + +static int edittranslation_exec(bContext *C, wmOperator *op) +{ + uiBut *but = UI_context_active_but_get(C); + if (but == nullptr) { + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; + } + + wmOperatorType *ot; + PointerRNA ptr; + char popath[FILE_MAX]; + const char *root = U.i18ndir; + const char *uilng = BLT_lang_get(); + + uiStringInfo but_label = {BUT_GET_LABEL, nullptr}; + uiStringInfo rna_label = {BUT_GET_RNA_LABEL, nullptr}; + uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, nullptr}; + uiStringInfo but_tip = {BUT_GET_TIP, nullptr}; + uiStringInfo rna_tip = {BUT_GET_RNA_TIP, nullptr}; + uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, nullptr}; + uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, nullptr}; + uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, nullptr}; + uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, nullptr}; + uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, nullptr}; + + if (!BLI_is_dir(root)) { + BKE_report(op->reports, + RPT_ERROR, + "Please set your Preferences' 'Translation Branches " + "Directory' path to a valid directory"); + return OPERATOR_CANCELLED; + } + ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, false); + if (ot == nullptr) { + BKE_reportf(op->reports, + RPT_ERROR, + "Could not find operator '%s'! Please enable ui_translate add-on " + "in the User Preferences", + EDTSRC_I18N_OP_NAME); + return OPERATOR_CANCELLED; + } + /* Try to find a valid po file for current language... */ + edittranslation_find_po_file(root, uilng, popath, FILE_MAX); + // printf("po path: %s\n", popath); + if (popath[0] == '\0') { + BKE_reportf( + op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); + return OPERATOR_CANCELLED; + } + + UI_but_string_info_get(C, + but, + &but_label, + &rna_label, + &enum_label, + &but_tip, + &rna_tip, + &enum_tip, + &rna_struct, + &rna_prop, + &rna_enum, + &rna_ctxt, + nullptr); + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_string_set(&ptr, "lang", uilng); + RNA_string_set(&ptr, "po_file", popath); + RNA_string_set(&ptr, "but_label", but_label.strinfo); + RNA_string_set(&ptr, "rna_label", rna_label.strinfo); + RNA_string_set(&ptr, "enum_label", enum_label.strinfo); + RNA_string_set(&ptr, "but_tip", but_tip.strinfo); + RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); + RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); + RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); + RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); + RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); + RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); + const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); + + /* Clean up */ + if (but_label.strinfo) { + MEM_freeN(but_label.strinfo); + } + if (rna_label.strinfo) { + MEM_freeN(rna_label.strinfo); + } + if (enum_label.strinfo) { + MEM_freeN(enum_label.strinfo); + } + if (but_tip.strinfo) { + MEM_freeN(but_tip.strinfo); + } + if (rna_tip.strinfo) { + MEM_freeN(rna_tip.strinfo); + } + if (enum_tip.strinfo) { + MEM_freeN(enum_tip.strinfo); + } + if (rna_struct.strinfo) { + MEM_freeN(rna_struct.strinfo); + } + if (rna_prop.strinfo) { + MEM_freeN(rna_prop.strinfo); + } + if (rna_enum.strinfo) { + MEM_freeN(rna_enum.strinfo); + } + if (rna_ctxt.strinfo) { + MEM_freeN(rna_ctxt.strinfo); + } + + return ret; +} + +static void UI_OT_edittranslation_init(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Edit Translation"; + ot->idname = "UI_OT_edittranslation_init"; + ot->description = "Edit i18n in current language for the active button"; + + /* callbacks */ + ot->exec = edittranslation_exec; +} + +#endif /* WITH_PYTHON */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reload Translation Operator + * \{ */ + +static int reloadtranslation_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + BLT_lang_init(); + BLF_cache_clear(); + BLT_lang_set(nullptr); + UI_reinit_font(); + return OPERATOR_FINISHED; +} + +static void UI_OT_reloadtranslation(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reload Translation"; + ot->idname = "UI_OT_reloadtranslation"; + ot->description = "Force a full reload of UI translation"; + + /* callbacks */ + ot->exec = reloadtranslation_exec; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Press Button Operator + * \{ */ + +static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + bScreen *screen = CTX_wm_screen(C); + const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed"); + ARegion *region_prev = CTX_wm_region(C); + ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->xy) : nullptr; + + if (region == nullptr) { + region = region_prev; + } + + if (region == nullptr) { + return OPERATOR_PASS_THROUGH; + } + + CTX_wm_region_set(C, region); + uiBut *but = UI_context_active_but_get(C); + CTX_wm_region_set(C, region_prev); + + if (but == nullptr) { + return OPERATOR_PASS_THROUGH; + } + if (skip_depressed && (but->flag & (UI_SELECT | UI_SELECT_DRAW))) { + return OPERATOR_PASS_THROUGH; + } + + /* Weak, this is a workaround for 'UI_but_is_tool', which checks the operator type, + * having this avoids a minor drawing glitch. */ + void *but_optype = but->optype; + + UI_but_execute(C, region, but); + + but->optype = static_cast(but_optype); + + WM_event_add_mousemove(CTX_wm_window(C)); + + return OPERATOR_FINISHED; +} + +static void UI_OT_button_execute(wmOperatorType *ot) +{ + ot->name = "Press Button"; + ot->idname = "UI_OT_button_execute"; + ot->description = "Presses active button"; + + ot->invoke = ui_button_press_invoke; + ot->flag = OPTYPE_INTERNAL; + + RNA_def_boolean(ot->srna, "skip_depressed", false, "Skip Depressed", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Text Button Clear Operator + * \{ */ + +static int button_string_clear_exec(bContext *C, wmOperator *UNUSED(op)) +{ + uiBut *but = UI_context_active_but_get_respect_menu(C); + + if (but) { + ui_but_active_string_clear_and_exit(C, but); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_button_string_clear(wmOperatorType *ot) +{ + ot->name = "Clear Button String"; + ot->idname = "UI_OT_button_string_clear"; + ot->description = "Unsets the text of the active button"; + + ot->poll = ED_operator_regionactive; + ot->exec = button_string_clear_exec; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drop Color Operator + * \{ */ + +bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) +{ + /* should only return true for regions that include buttons, for now + * return true always */ + if (drag->type == WM_DRAG_COLOR) { + SpaceImage *sima = CTX_wm_space_image(C); + ARegion *region = CTX_wm_region(C); + + if (UI_but_active_drop_color(C)) { + return true; + } + + if (sima && (sima->mode == SI_MODE_PAINT) && sima->image && + (region && region->regiontype == RGN_TYPE_WINDOW)) { + return true; + } + } + + return false; +} + +void UI_drop_color_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) +{ + uiDragColorHandle *drag_info = static_cast(drag->poin); + + RNA_float_set_array(drop->ptr, "color", drag_info->color); + RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected); +} + +static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiBut *but = nullptr; + float color[4]; + bool gamma; + + RNA_float_get_array(op->ptr, "color", color); + gamma = RNA_boolean_get(op->ptr, "gamma"); + + /* find button under mouse, check if it has RNA color property and + * if it does copy the data */ + but = ui_region_find_active_but(region); + + if (but && but->type == UI_BTYPE_COLOR && but->rnaprop) { + const int color_len = RNA_property_array_length(&but->rnapoin, but->rnaprop); + BLI_assert(color_len <= 4); + + /* keep alpha channel as-is */ + if (color_len == 4) { + color[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); + } + + if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + if (!gamma) { + IMB_colormanagement_scene_linear_to_srgb_v3(color, color); + } + RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); + RNA_property_update(C, &but->rnapoin, but->rnaprop); + } + else if (RNA_property_subtype(but->rnaprop) == PROP_COLOR) { + if (gamma) { + IMB_colormanagement_srgb_to_scene_linear_v3(color, color); + } + RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); + RNA_property_update(C, &but->rnapoin, but->rnaprop); + } + } + else { + if (gamma) { + srgb_to_linearrgb_v3_v3(color, color); + } + + ED_imapaint_bucket_fill(C, color, op, event->mval); + } + + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_color(wmOperatorType *ot) +{ + ot->name = "Drop Color"; + ot->idname = "UI_OT_drop_color"; + ot->description = "Drop colors to buttons"; + + ot->invoke = drop_color_invoke; + ot->flag = OPTYPE_INTERNAL; + + RNA_def_float_color( + ot->srna, "color", 3, nullptr, 0.0, FLT_MAX, "Color", "Source color", 0.0, 1.0); + RNA_def_boolean( + ot->srna, "gamma", false, "Gamma Corrected", "The source color is gamma corrected"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drop Name Operator + * \{ */ + +static bool drop_name_poll(bContext *C) +{ + if (!ED_operator_regionactive(C)) { + return false; + } + + const uiBut *but = UI_but_active_drop_name_button(C); + if (!but) { + return false; + } + + if (but->flag & UI_BUT_DISABLED) { + return false; + } + + return true; +} + +static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + uiBut *but = UI_but_active_drop_name_button(C); + char *str = RNA_string_get_alloc(op->ptr, "string", nullptr, 0, nullptr); + + if (str) { + ui_but_set_string_interactive(C, but, str); + MEM_freeN(str); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_name(wmOperatorType *ot) +{ + ot->name = "Drop Name"; + ot->idname = "UI_OT_drop_name"; + ot->description = "Drop name to button"; + + ot->poll = drop_name_poll; + ot->invoke = drop_name_invoke; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; + + RNA_def_string( + ot->srna, "string", nullptr, 0, "String", "The string value to drop into the button"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI List Search Operator + * \{ */ + +static bool ui_list_focused_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + if (!region) { + return false; + } + const wmWindow *win = CTX_wm_window(C); + const uiList *list = UI_list_find_mouse_over(region, win->eventstate); + + return list != nullptr; +} + +/** + * Ensure the filter options are set to be visible in the UI list. + * \return if the visibility changed, requiring a redraw. + */ +static bool ui_list_unhide_filter_options(uiList *list) +{ + if (list->filter_flag & UILST_FLT_SHOW) { + /* Nothing to be done. */ + return false; + } + + list->filter_flag |= UILST_FLT_SHOW; + return true; +} + +static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiList *list = UI_list_find_mouse_over(region, event); + /* Poll should check. */ + BLI_assert(list != nullptr); + + if (ui_list_unhide_filter_options(list)) { + ui_region_redraw_immediately(C, region); + } + + if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_list_start_filter(wmOperatorType *ot) +{ + ot->name = "List Filter"; + ot->idname = "UI_OT_list_start_filter"; + ot->description = "Start entering filter text for the list in focus"; + + ot->invoke = ui_list_start_filter_invoke; + ot->poll = ui_list_focused_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Tree-View Drop Operator + * \{ */ + +static bool ui_view_drop_poll(bContext *C) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, win->eventstate->xy); + + return hovered_item != nullptr; +} + +static int ui_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + const ARegion *region = CTX_wm_region(C); + uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); + + if (!UI_view_item_drop_handle( + C, hovered_item, static_cast(event->customdata))) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_view_drop(wmOperatorType *ot) +{ + ot->name = "View drop"; + ot->idname = "UI_OT_view_drop"; + ot->description = "Drag and drop items onto a data-set item"; + + ot->invoke = ui_view_drop_invoke; + ot->poll = ui_view_drop_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI View Item Rename Operator + * + * General purpose renaming operator for views. Thanks to this, to add a rename button to context + * menus for example, view API users don't have to implement their own renaming operators with the + * same logic as they already have for their #ui::AbstractViewItem::rename() override. + * + * \{ */ + +static bool ui_view_item_rename_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + const uiViewItemHandle *active_item = UI_region_views_find_active_item(region); + return active_item != nullptr && UI_view_item_can_rename(active_item); +} + +static int ui_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ARegion *region = CTX_wm_region(C); + uiViewItemHandle *active_item = UI_region_views_find_active_item(region); + + UI_view_item_begin_rename(active_item); + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_view_item_rename(wmOperatorType *ot) +{ + ot->name = "Rename View Item"; + ot->idname = "UI_OT_view_item_rename"; + ot->description = "Rename the active item in the data-set view"; + + ot->exec = ui_view_item_rename_exec; + ot->poll = ui_view_item_rename_poll; + /* Could get a custom tooltip via the `get_description()` callback and another overridable + * function of the view. */ + + ot->flag = OPTYPE_INTERNAL; +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Material Drag/Drop Operator + * + * \{ */ + +static bool ui_drop_material_poll(bContext *C) +{ + PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); + const Object *ob = static_cast(ptr.data); + if (ob == nullptr) { + return false; + } + + PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); + if (RNA_pointer_is_null(&mat_slot)) { + return false; + } + + return true; +} + +static int ui_drop_material_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + + Material *ma = (Material *)WM_operator_properties_id_lookup_from_name_or_session_uuid( + bmain, op->ptr, ID_MA); + if (ma == nullptr) { + return OPERATOR_CANCELLED; + } + + PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); + Object *ob = static_cast(ptr.data); + BLI_assert(ob); + + PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); + BLI_assert(mat_slot.data); + const int target_slot = RNA_int_get(&mat_slot, "slot_index") + 1; + + /* only drop grease pencil material on grease pencil objects */ + if ((ma->gp_style != nullptr) && (ob->type != OB_GPENCIL)) { + return OPERATOR_CANCELLED; + } + + BKE_object_material_assign(bmain, ob, ma, target_slot, BKE_MAT_ASSIGN_USERPREF); + + WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_material(wmOperatorType *ot) +{ + ot->name = "Drop Material in Material slots"; + ot->description = "Drag material to Material slots in Properties"; + ot->idname = "UI_OT_drop_material"; + + ot->poll = ui_drop_material_poll; + ot->exec = ui_drop_material_exec; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + + WM_operator_properties_id_lookup(ot, false); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator & Keymap Registration + * \{ */ + +void ED_operatortypes_ui(void) +{ + WM_operatortype_append(UI_OT_copy_data_path_button); + WM_operatortype_append(UI_OT_copy_as_driver_button); + WM_operatortype_append(UI_OT_copy_python_command_button); + WM_operatortype_append(UI_OT_reset_default_button); + WM_operatortype_append(UI_OT_assign_default_button); + WM_operatortype_append(UI_OT_unset_property_button); + WM_operatortype_append(UI_OT_override_type_set_button); + WM_operatortype_append(UI_OT_override_remove_button); + WM_operatortype_append(UI_OT_copy_to_selected_button); + WM_operatortype_append(UI_OT_jump_to_target_button); + WM_operatortype_append(UI_OT_drop_color); + WM_operatortype_append(UI_OT_drop_name); + WM_operatortype_append(UI_OT_drop_material); +#ifdef WITH_PYTHON + WM_operatortype_append(UI_OT_editsource); + WM_operatortype_append(UI_OT_edittranslation_init); +#endif + WM_operatortype_append(UI_OT_reloadtranslation); + WM_operatortype_append(UI_OT_button_execute); + WM_operatortype_append(UI_OT_button_string_clear); + + WM_operatortype_append(UI_OT_list_start_filter); + + WM_operatortype_append(UI_OT_view_drop); + WM_operatortype_append(UI_OT_view_item_rename); + + /* external */ + WM_operatortype_append(UI_OT_eyedropper_color); + WM_operatortype_append(UI_OT_eyedropper_colorramp); + WM_operatortype_append(UI_OT_eyedropper_colorramp_point); + WM_operatortype_append(UI_OT_eyedropper_id); + WM_operatortype_append(UI_OT_eyedropper_depth); + WM_operatortype_append(UI_OT_eyedropper_driver); + WM_operatortype_append(UI_OT_eyedropper_gpencil_color); +} + +void ED_keymap_ui(wmKeyConfig *keyconf) +{ + WM_keymap_ensure(keyconf, "User Interface", 0, 0); + + eyedropper_modal_keymap(keyconf); + eyedropper_colorband_modal_keymap(keyconf); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c deleted file mode 100644 index d4a9a4ca4cd..00000000000 --- a/source/blender/editors/interface/interface_panel.c +++ /dev/null @@ -1,2602 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -/* a full doc with API notes can be found in - * bf-blender/trunk/blender/doc/guides/interface_API.txt */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "PIL_time.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "DNA_screen_types.h" -#include "DNA_userdef_types.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "RNA_access.h" - -#include "BLF_api.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" -#include "UI_resources.h" -#include "UI_view2d.h" - -#include "GPU_batch_presets.h" -#include "GPU_immediate.h" -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Defines & Structs - * \{ */ - -#define ANIMATION_TIME 0.30 -#define ANIMATION_INTERVAL 0.02 - -typedef enum uiPanelRuntimeFlag { - PANEL_LAST_ADDED = (1 << 0), - PANEL_ACTIVE = (1 << 2), - PANEL_WAS_ACTIVE = (1 << 3), - PANEL_ANIM_ALIGN = (1 << 4), - PANEL_NEW_ADDED = (1 << 5), - PANEL_SEARCH_FILTER_MATCH = (1 << 7), - /** - * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH) - * instead of #PNL_CLOSED. Set to true on every property search update. - */ - PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8), - /** The Panel was before the start of the current / latest layout pass. */ - PANEL_WAS_CLOSED = (1 << 9), - /** - * Set when the panel is being dragged and while it animates back to its aligned - * position. Unlike #PANEL_STATE_ANIMATION, this is applied to sub-panels as well. - */ - PANEL_IS_DRAG_DROP = (1 << 10), - /** Draw a border with the active color around the panel. */ - PANEL_ACTIVE_BORDER = (1 << 11), -} uiPanelRuntimeFlag; - -/* The state of the mouse position relative to the panel. */ -typedef enum uiPanelMouseState { - PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */ - PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */ - PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */ -} uiPanelMouseState; - -typedef enum uiHandlePanelState { - PANEL_STATE_DRAG, - PANEL_STATE_ANIMATION, - PANEL_STATE_EXIT, -} uiHandlePanelState; - -typedef struct uiHandlePanelData { - uiHandlePanelState state; - - /* Animation. */ - wmTimer *animtimer; - double starttime; - - /* Dragging. */ - int startx, starty; - int startofsx, startofsy; - float start_cur_xmin, start_cur_ymin; -} uiHandlePanelData; - -typedef struct PanelSort { - Panel *panel; - int new_offset_x; - int new_offset_y; -} PanelSort; - -static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel); -static int get_panel_real_size_y(const Panel *panel); -static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state); -static int compare_panel(const void *a, const void *b); -static bool panel_type_context_poll(ARegion *region, - const PanelType *panel_type, - const char *context); - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Local Functions - * \{ */ - -static bool panel_active_animation_changed(ListBase *lb, - Panel **r_panel_animation, - bool *r_no_animation) -{ - LISTBASE_FOREACH (Panel *, panel, lb) { - /* Detect panel active flag changes. */ - if (!(panel->type && panel->type->parent)) { - if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) { - return true; - } - if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) { - return true; - } - } - - /* Detect changes in panel expansions. */ - if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) { - *r_panel_animation = panel; - return false; - } - - if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) { - if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) { - return true; - } - } - - /* Detect animation. */ - if (panel->activedata) { - uiHandlePanelData *data = panel->activedata; - if (data->state == PANEL_STATE_ANIMATION) { - *r_panel_animation = panel; - } - else { - /* Don't animate while handling other interaction. */ - *r_no_animation = true; - } - } - if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) { - *r_panel_animation = panel; - } - } - - return false; -} - -/** - * \return True if the properties editor switch tabs since the last layout pass. - */ -static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region) -{ - if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) { - SpaceProperties *sbuts = area->spacedata.first; - - if (sbuts->mainbo != sbuts->mainb) { - return true; - } - } - - return false; -} - -static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation) -{ - *r_panel_animation = NULL; - - if (properties_space_needs_realign(area, region)) { - return true; - } - - /* Detect if a panel was added or removed. */ - Panel *panel_animation = NULL; - bool no_animation = false; - if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) { - return true; - } - - /* Detect panel marked for animation, if we're not already animating. */ - if (panel_animation) { - if (!no_animation) { - *r_panel_animation = panel_animation; - } - return true; - } - - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Functions for Instanced Panels - * \{ */ - -static Panel *panel_add_instanced(ARegion *region, - ListBase *panels, - PanelType *panel_type, - PointerRNA *custom_data) -{ - Panel *panel = MEM_callocN(sizeof(Panel), __func__); - panel->type = panel_type; - BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); - - panel->runtime.custom_data_ptr = custom_data; - panel->runtime_flag |= PANEL_NEW_ADDED; - - /* Add the panel's children too. Although they aren't instanced panels, we can still use this - * function to create them, as UI_panel_begin does other things we don't need to do. */ - LISTBASE_FOREACH (LinkData *, child, &panel_type->children) { - PanelType *child_type = child->data; - panel_add_instanced(region, &panel->children, child_type, custom_data); - } - - /* Make sure the panel is added to the end of the display-order as well. This is needed for - * loading existing files. - * - * NOTE: We could use special behavior to place it after the panel that starts the list of - * instanced panels, but that would add complexity that isn't needed for now. */ - int max_sortorder = 0; - LISTBASE_FOREACH (Panel *, existing_panel, panels) { - if (existing_panel->sortorder > max_sortorder) { - max_sortorder = existing_panel->sortorder; - } - } - panel->sortorder = max_sortorder + 1; - - BLI_addtail(panels, panel); - - return panel; -} - -Panel *UI_panel_add_instanced(const bContext *C, - ARegion *region, - ListBase *panels, - const char *panel_idname, - PointerRNA *custom_data) -{ - ARegionType *region_type = region->type; - - PanelType *panel_type = BLI_findstring( - ®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname)); - - if (panel_type == NULL) { - printf("Panel type '%s' not found.\n", panel_idname); - return NULL; - } - - Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data); - - /* Do this after #panel_add_instatnced so all sub-panels are added. */ - panel_set_expansion_from_list_data(C, new_panel); - - return new_panel; -} - -void UI_list_panel_unique_str(Panel *panel, char *r_name) -{ - /* The panel sort-order will be unique for a specific panel type because the instanced - * panel list is regenerated for every change in the data order / length. */ - snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder); -} - -/** - * Free a panel and its children. Custom data is shared by the panel and its children - * and is freed by #UI_panels_free_instanced. - * - * \note The only panels that should need to be deleted at runtime are panels with the - * #PANEL_TYPE_INSTANCED flag set. - */ -static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel) -{ - /* Recursively delete children. */ - LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) { - panel_delete(C, region, &panel->children, child); - } - BLI_freelistN(&panel->children); - - BLI_remlink(panels, panel); - if (panel->activedata) { - MEM_freeN(panel->activedata); - } - MEM_freeN(panel); -} - -void UI_panels_free_instanced(const bContext *C, ARegion *region) -{ - /* Delete panels with the instanced flag. */ - LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) { - if ((panel->type != NULL) && (panel->type->flag & PANEL_TYPE_INSTANCED)) { - /* Make sure the panel's handler is removed before deleting it. */ - if (C != NULL && panel->activedata != NULL) { - panel_activate_state(C, panel, PANEL_STATE_EXIT); - } - - /* Free panel's custom data. */ - if (panel->runtime.custom_data_ptr != NULL) { - MEM_freeN(panel->runtime.custom_data_ptr); - } - - /* Free the panel and its sub-panels. */ - panel_delete(C, region, ®ion->panels, panel); - } - } -} - -bool UI_panel_list_matches_data(ARegion *region, - ListBase *data, - uiListPanelIDFromDataFunc panel_idname_func) -{ - /* Check for NULL data. */ - int data_len = 0; - Link *data_link = NULL; - if (data == NULL) { - data_len = 0; - data_link = NULL; - } - else { - data_len = BLI_listbase_count(data); - data_link = data->first; - } - - int i = 0; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { - /* The panels were reordered by drag and drop. */ - if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) { - return false; - } - - /* We reached the last data item before the last instanced panel. */ - if (data_link == NULL) { - return false; - } - - /* Check if the panel type matches the panel type from the data item. */ - char panel_idname[MAX_NAME]; - panel_idname_func(data_link, panel_idname); - if (!STREQ(panel_idname, panel->type->idname)) { - return false; - } - - data_link = data_link->next; - i++; - } - } - - /* If we didn't make it to the last list item, the panel list isn't complete. */ - if (i != data_len) { - return false; - } - - return true; -} - -static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel) -{ - /* Without a type we cannot access the reorder callback. */ - if (drag_panel->type == NULL) { - return; - } - /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */ - if (drag_panel->type->reorder == NULL) { - return; - } - - char *context = NULL; - if (!UI_panel_category_is_visible(region)) { - context = drag_panel->type->context; - } - - /* Find how many instanced panels with this context string. */ - int list_panels_len = 0; - int start_index = -1; - LISTBASE_FOREACH (const Panel *, panel, ®ion->panels) { - if (panel->type) { - if (panel->type->flag & PANEL_TYPE_INSTANCED) { - if (panel_type_context_poll(region, panel->type, context)) { - if (panel == drag_panel) { - BLI_assert(start_index == -1); /* This panel should only appear once. */ - start_index = list_panels_len; - } - list_panels_len++; - } - } - } - } - BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */ - - /* Sort the matching instanced panels by their display order. */ - PanelSort *panel_sort = MEM_callocN(list_panels_len * sizeof(*panel_sort), __func__); - PanelSort *sort_index = panel_sort; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->type) { - if (panel->type->flag & PANEL_TYPE_INSTANCED) { - if (panel_type_context_poll(region, panel->type, context)) { - sort_index->panel = panel; - sort_index++; - } - } - } - } - qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel); - - /* Find how many of those panels are above this panel. */ - int move_to_index = 0; - for (; move_to_index < list_panels_len; move_to_index++) { - if (panel_sort[move_to_index].panel == drag_panel) { - break; - } - } - - MEM_freeN(panel_sort); - - if (move_to_index == start_index) { - /* In this case, the reorder was not changed, so don't do any updates or call the callback. */ - return; - } - - /* Set the bit to tell the interface to instanced the list. */ - drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; - - CTX_store_set(C, drag_panel->runtime.context); - - /* Finally, move this panel's list item to the new index in its list. */ - drag_panel->type->reorder(C, drag_panel, move_to_index); - - CTX_store_set(C, NULL); -} - -/** - * Recursive implementation for #panel_set_expansion_from_list_data. - * - * \return Whether the closed flag for the panel or any sub-panels changed. - */ -static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index) -{ - const bool open = (flag & (1 << *flag_index)); - bool changed = (open == UI_panel_is_closed(panel)); - - SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED); - - LISTBASE_FOREACH (Panel *, child, &panel->children) { - *flag_index = *flag_index + 1; - changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index); - } - return changed; -} - -/** - * Set the expansion of the panel and its sub-panels from the flag stored in the - * corresponding list data. The flag has expansion stored in each bit in depth first order. - */ -static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel) -{ - BLI_assert(panel->type != NULL); - BLI_assert(panel->type->flag & PANEL_TYPE_INSTANCED); - if (panel->type->get_list_data_expand_flag == NULL) { - /* Instanced panel doesn't support loading expansion. */ - return; - } - - const short expand_flag = panel->type->get_list_data_expand_flag(C, panel); - short flag_index = 0; - - /* Start panel animation if the open state was changed. */ - if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } -} - -/** - * Set expansion based on the data for instanced panels. - */ -static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - PanelType *panel_type = panel->type; - if (panel_type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { - panel_set_expansion_from_list_data(C, panel); - } - } - } -} - -/** - * Recursive implementation for #set_panels_list_data_expand_flag. - */ -static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index) -{ - const bool open = !(panel->flag & PNL_CLOSED); - SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index)); - - LISTBASE_FOREACH (const Panel *, child, &panel->children) { - *flag_index = *flag_index + 1; - get_panel_expand_flag(child, flag, flag_index); - } -} - -/** - * Call the callback to store the panel and sub-panel expansion settings in the list item that - * corresponds to each instanced panel. - * - * \note This needs to iterate through all of the region's panels because the panel with changed - * expansion might have been the sub-panel of an instanced panel, meaning it might not know - * which list item it corresponds to. - */ -static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - PanelType *panel_type = panel->type; - if (panel_type == NULL) { - continue; - } - - /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */ - if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) { - short expand_flag; - short flag_index = 0; - get_panel_expand_flag(panel, &expand_flag, &flag_index); - if (panel->type->set_list_data_expand_flag) { - panel->type->set_list_data_expand_flag(C, panel, expand_flag); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panels - * \{ */ - -static bool panel_custom_data_active_get(const Panel *panel) -{ - /* The caller should make sure the panel is active and has a type. */ - BLI_assert(UI_panel_is_active(panel)); - BLI_assert(panel->type != NULL); - - if (panel->type->active_property[0] != '\0') { - PointerRNA *ptr = UI_panel_custom_data_get(panel); - if (ptr != NULL && !RNA_pointer_is_null(ptr)) { - return RNA_boolean_get(ptr, panel->type->active_property); - } - } - - return false; -} - -static void panel_custom_data_active_set(Panel *panel) -{ - /* Since the panel is interacted with, it should be active and have a type. */ - BLI_assert(UI_panel_is_active(panel)); - BLI_assert(panel->type != NULL); - - if (panel->type->active_property[0] != '\0') { - PointerRNA *ptr = UI_panel_custom_data_get(panel); - BLI_assert(RNA_struct_find_property(ptr, panel->type->active_property) != NULL); - if (ptr != NULL && !RNA_pointer_is_null(ptr)) { - RNA_boolean_set(ptr, panel->type->active_property, true); - } - } -} - -/** - * Set flag state for a panel and its sub-panels. - */ -static void panel_set_flag_recursive(Panel *panel, short flag, bool value) -{ - SET_FLAG_FROM_TEST(panel->flag, value, flag); - - LISTBASE_FOREACH (Panel *, child, &panel->children) { - panel_set_flag_recursive(child, flag, value); - } -} - -/** - * Set runtime flag state for a panel and its sub-panels. - */ -static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value) -{ - SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag); - - LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) { - panel_set_runtime_flag_recursive(sub_panel, flag, value); - } -} - -static void panels_collapse_all(ARegion *region, const Panel *from_panel) -{ - const bool has_category_tabs = UI_panel_category_is_visible(region); - const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : NULL; - const PanelType *from_pt = from_panel->type; - - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - PanelType *pt = panel->type; - - /* Close panels with headers in the same context. */ - if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) { - if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) { - if ((panel->flag & PNL_PIN) || !category || !pt->category[0] || - STREQ(pt->category, category)) { - panel->flag |= PNL_CLOSED; - } - } - } - } -} - -static bool panel_type_context_poll(ARegion *region, - const PanelType *panel_type, - const char *context) -{ - if (!BLI_listbase_is_empty(®ion->panels_category)) { - return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); - } - - if (panel_type->context[0] && STREQ(panel_type->context, context)) { - return true; - } - - return false; -} - -Panel *UI_panel_find_by_type(ListBase *lb, const PanelType *pt) -{ - const char *idname = pt->idname; - - LISTBASE_FOREACH (Panel *, panel, lb) { - if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) { - return panel; - } - } - return NULL; -} - -Panel *UI_panel_begin( - ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open) -{ - Panel *panel_last; - const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); - const char *idname = pt->idname; - const bool newpanel = (panel == NULL); - - if (newpanel) { - panel = MEM_callocN(sizeof(Panel), __func__); - panel->type = pt; - BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname)); - - if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { - panel->flag |= PNL_CLOSED; - panel->runtime_flag |= PANEL_WAS_CLOSED; - } - - panel->ofsx = 0; - panel->ofsy = 0; - panel->sizex = 0; - panel->sizey = 0; - panel->blocksizex = 0; - panel->blocksizey = 0; - panel->runtime_flag |= PANEL_NEW_ADDED; - - BLI_addtail(lb, panel); - } - else { - /* Panel already exists. */ - panel->type = pt; - } - - panel->runtime.block = block; - - BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname)); - - /* If a new panel is added, we insert it right after the panel that was last added. - * This way new panels are inserted in the right place between versions. */ - for (panel_last = lb->first; panel_last; panel_last = panel_last->next) { - if (panel_last->runtime_flag & PANEL_LAST_ADDED) { - BLI_remlink(lb, panel); - BLI_insertlinkafter(lb, panel_last, panel); - break; - } - } - - if (newpanel) { - panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0; - - LISTBASE_FOREACH (Panel *, panel_next, lb) { - if (panel_next != panel && panel_next->sortorder >= panel->sortorder) { - panel_next->sortorder++; - } - } - } - - if (panel_last) { - panel_last->runtime_flag &= ~PANEL_LAST_ADDED; - } - - /* Assign the new panel to the block. */ - block->panel = panel; - panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED; - if (region->alignment == RGN_ALIGN_FLOAT) { - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - } - - *r_open = false; - - if (UI_panel_is_closed(panel)) { - return panel; - } - - *r_open = true; - - return panel; -} - -void UI_panel_header_buttons_begin(Panel *panel) -{ - uiBlock *block = panel->runtime.block; - - ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); -} - -void UI_panel_header_buttons_end(Panel *panel) -{ - uiBlock *block = panel->runtime.block; - - /* A button group should always be created in #UI_panel_header_buttons_begin. */ - BLI_assert(!BLI_listbase_is_empty(&block->button_groups)); - - uiButtonGroup *button_group = block->button_groups.last; - - button_group->flag &= ~UI_BUTTON_GROUP_LOCK; - - /* Repurpose the first header button group if it is empty, in case the first button added to - * the panel doesn't add a new group (if the button is created directly rather than through an - * interface layout call). */ - if (BLI_listbase_is_single(&block->button_groups) && - BLI_listbase_is_empty(&button_group->buttons)) { - button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER; - } - else { - /* Always add a new button group. Although this may result in many empty groups, without it, - * new buttons in the panel body not protected with a #ui_block_new_button_group call would - * end up in the panel header group. */ - ui_block_new_button_group(block, 0); - } -} - -static float panel_region_offset_x_get(const ARegion *region) -{ - if (UI_panel_category_is_visible(region)) { - if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) { - return UI_PANEL_CATEGORY_MARGIN_WIDTH; - } - } - - return 0.0f; -} - -/** - * Starting from the "block size" set in #UI_panel_end, calculate the full size - * of the panel including the sub-panel headers and buttons. - */ -static void panel_calculate_size_recursive(ARegion *region, Panel *panel) -{ - int width = panel->blocksizex; - int height = panel->blocksizey; - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - if (child_panel->runtime_flag & PANEL_ACTIVE) { - panel_calculate_size_recursive(region, child_panel); - width = max_ii(width, child_panel->sizex); - height += get_panel_real_size_y(child_panel); - } - } - - /* Update total panel size. */ - if (panel->runtime_flag & PANEL_NEW_ADDED) { - panel->runtime_flag &= ~PANEL_NEW_ADDED; - panel->sizex = width; - panel->sizey = height; - } - else { - const int old_sizex = panel->sizex, old_sizey = panel->sizey; - const int old_region_ofsx = panel->runtime.region_ofsx; - - /* Update width/height if non-zero. */ - if (width != 0) { - panel->sizex = width; - } - if (height != 0 || !UI_panel_is_closed(panel)) { - panel->sizey = height; - } - - /* Check if we need to do an animation. */ - if (panel->sizex != old_sizex || panel->sizey != old_sizey) { - panel->runtime_flag |= PANEL_ANIM_ALIGN; - panel->ofsy += old_sizey - panel->sizey; - } - - panel->runtime.region_ofsx = panel_region_offset_x_get(region); - if (old_region_ofsx != panel->runtime.region_ofsx) { - panel->runtime_flag |= PANEL_ANIM_ALIGN; - } - } -} - -void UI_panel_end(Panel *panel, int width, int height) -{ - /* Store the size of the buttons layout in the panel. The actual panel size - * (including sub-panels) is calculated in #UI_panels_end. */ - panel->blocksizex = width; - panel->blocksizey = height; -} - -static void ui_offset_panel_block(uiBlock *block) -{ - const uiStyle *style = UI_style_get_dpi(); - - /* Compute bounds and offset. */ - ui_block_bounds_calc(block); - - const int ofsy = block->panel->sizey - style->panelspace; - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->rect.ymin += ofsy; - but->rect.ymax += ofsy; - } - - block->rect.xmax = block->panel->sizex; - block->rect.ymax = block->panel->sizey; - block->rect.xmin = block->rect.ymin = 0.0; -} - -void ui_panel_tag_search_filter_match(Panel *panel) -{ - panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH; -} - -static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) -{ - *filter_matches |= panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH; - - /* If the panel has no match we need to make sure that its children are too. */ - if (!*filter_matches) { - LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { - panel_matches_search_filter_recursive(child_panel, filter_matches); - } - } -} - -bool UI_panel_matches_search_filter(const Panel *panel) -{ - bool search_filter_matches = false; - panel_matches_search_filter_recursive(panel, &search_filter_matches); - return search_filter_matches; -} - -/** - * Set the flag telling the panel to use its search result status for its expansion. - */ -static void panel_set_expansion_from_search_filter_recursive(const bContext *C, - Panel *panel, - const bool use_search_closed) -{ - /* This has to run on inactive panels that may not have a type, - * but we can prevent running on header-less panels in some cases. */ - if (panel->type == NULL || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) { - SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH); - } - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - /* Don't check if the sub-panel is active, otherwise the - * expansion won't be reset when the parent is closed. */ - panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed); - } -} - -/** - * Set the flag telling every panel to override its expansion with its search result status. - */ -static void region_panels_set_expansion_from_search_filter(const bContext *C, - ARegion *region, - const bool use_search_closed) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - /* Don't check if the panel is active, otherwise the expansion won't - * be correct when switching back to tab after exiting search. */ - panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed); - } - set_panels_list_data_expand_flag(C, region); -} - -/** - * Hide buttons in invisible layouts, which are created because buttons must be - * added for all panels in order to search, even panels that will end up closed. - */ -static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) -{ - uiBlock *block = panel->runtime.block; - BLI_assert(block != NULL); - BLI_assert(block->active); - if (parent_panel != NULL && UI_panel_is_closed(parent_panel)) { - /* The parent panel is closed, so this panel can be completely removed. */ - UI_block_set_search_only(block, true); - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->flag |= UI_HIDDEN; - } - } - else if (UI_panel_is_closed(panel)) { - /* If sub-panels have no search results but the parent panel does, then the parent panel open - * and the sub-panels will close. In that case there must be a way to hide the buttons in the - * panel but keep the header buttons. */ - LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { - if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) { - continue; - } - LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { - uiBut *but = link->data; - but->flag |= UI_HIDDEN; - } - } - } - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - if (child_panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(child_panel->runtime.block != NULL); - panel_remove_invisible_layouts_recursive(child_panel, panel); - } - } -} - -static void region_panels_remove_invisible_layouts(ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime.block != NULL); - panel_remove_invisible_layouts_recursive(panel, NULL); - } - } -} - -bool UI_panel_is_closed(const Panel *panel) -{ - /* Header-less panels can never be closed, otherwise they could disappear. */ - if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) { - return false; - } - - if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) { - return !UI_panel_matches_search_filter(panel); - } - - return panel->flag & PNL_CLOSED; -} - -bool UI_panel_is_active(const Panel *panel) -{ - return panel->runtime_flag & PANEL_ACTIVE; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drawing - * \{ */ - -void UI_panels_draw(const bContext *C, ARegion *region) -{ - /* Draw in reverse order, because #uiBlocks are added in reverse order - * and we need child panels to draw on top. */ - LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && !UI_panel_is_dragging(block->panel) && - !UI_block_is_search_only(block)) { - UI_block_draw(C, block); - } - } - - LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && UI_panel_is_dragging(block->panel) && - !UI_block_is_search_only(block)) { - UI_block_draw(C, block); - } - } -} - -#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */ - -void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y) -{ - Panel *panel = block->panel; - const bool is_subpanel = (panel->type && panel->type->parent); - - *r_x = UI_UNIT_X * 1.0f; - *r_y = UI_UNIT_Y * 1.5f; - - if (is_subpanel) { - *r_x += (0.7f * UI_UNIT_X); - } -} - -static void panel_title_color_get(const Panel *panel, - const bool show_background, - const bool region_search_filter_active, - uchar r_color[4]) -{ - if (!show_background) { - /* Use menu colors for floating panels. */ - bTheme *btheme = UI_GetTheme(); - const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; - copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); - return; - } - - const bool search_match = UI_panel_matches_search_filter(panel); - - UI_GetThemeColor4ubv(TH_TITLE, r_color); - if (region_search_filter_active && !search_match) { - r_color[0] *= 0.5; - r_color[1] *= 0.5; - r_color[2] *= 0.5; - } -} - -static void panel_draw_highlight_border(const Panel *panel, - const rcti *rect, - const rcti *header_rect) -{ - const bool is_subpanel = panel->type->parent != NULL; - if (is_subpanel) { - return; - } - - const bTheme *btheme = UI_GetTheme(); - const float aspect = panel->runtime.block->aspect; - const float radius = (btheme->tui.panel_roundness * U.widget_unit * 0.5f) / aspect; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - - float color[4]; - UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin, - .ymax = header_rect->ymax, - }, - false, - radius, - color); -} - -static void panel_draw_aligned_widgets(const uiStyle *style, - const Panel *panel, - const rcti *header_rect, - const float aspect, - const bool show_pin, - const bool show_background, - const bool region_search_filter_active) -{ - const bool is_subpanel = panel->type->parent != NULL; - const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle; - - const int header_height = BLI_rcti_size_y(header_rect); - const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect); - - /* Offset triangle and text to the right for subpanels. */ - const rcti widget_rect = { - .xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0), - .xmax = header_rect->xmax, - .ymin = header_rect->ymin, - .ymax = header_rect->ymax, - }; - - uchar title_color[4]; - panel_title_color_get(panel, show_background, region_search_filter_active, title_color); - title_color[3] = 255; - - /* Draw collapse icon. */ - { - const float size_y = BLI_rcti_size_y(&widget_rect); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f, - widget_rect.ymin + size_y * 0.2f, - UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT, - aspect * U.inv_dpi_fac, - 0.7f, - 0.0f, - title_color, - false); - GPU_blend(GPU_BLEND_NONE); - } - - /* Draw text label. */ - if (panel->drawname[0] != '\0') { - const rcti title_rect = { - .xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f, - .xmax = widget_rect.xmax, - .ymin = widget_rect.ymin - 2.0f / aspect, - .ymax = widget_rect.ymax, - }; - UI_fontstyle_draw(fontstyle, - &title_rect, - panel->drawname, - sizeof(panel->drawname), - title_color, - &(struct uiFontStyleDraw_Params){ - .align = UI_STYLE_TEXT_LEFT, - }); - } - - /* Draw the pin icon. */ - if (show_pin && (panel->flag & PNL_PIN)) { - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f, - widget_rect.ymin + 5.0f / aspect, - ICON_PINNED, - aspect * U.inv_dpi_fac, - 1.0f, - 0.0f, - title_color, - false); - GPU_blend(GPU_BLEND_NONE); - } - - /* Draw drag widget. */ - if (!is_subpanel && show_background) { - const int drag_widget_size = header_height * 0.7f; - GPU_matrix_push(); - /* The magic numbers here center the widget vertically and offset it to the left. - * Currently this depends on the height of the header, although it could be independent. */ - GPU_matrix_translate_2f(widget_rect.xmax - scaled_unit * 1.15, - widget_rect.ymin + (header_height - drag_widget_size) * 0.5f); - - const int col_tint = 84; - float color_high[4], color_dark[4]; - UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, color_high); - UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, color_dark); - - GPUBatch *batch = GPU_batch_preset_panel_drag_widget( - U.pixelsize, color_high, color_dark, drag_widget_size); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_FLAT_COLOR); - GPU_batch_draw(batch); - GPU_matrix_pop(); - } -} - -static void panel_draw_aligned_backdrop(const Panel *panel, - const rcti *rect, - const rcti *header_rect) -{ - const bool is_subpanel = panel->type->parent != NULL; - const bool is_open = !UI_panel_is_closed(panel); - - if (is_subpanel && !is_open) { - return; - } - - const bTheme *btheme = UI_GetTheme(); - const float aspect = panel->runtime.block->aspect; - const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect; - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Panel backdrop. */ - if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { - float panel_backcolor[4]; - UI_draw_roundbox_corner_set(is_open ? UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT : UI_CNR_ALL); - UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor); - - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = rect->ymin, - .ymax = rect->ymax, - }, - true, - radius, - panel_backcolor); - } - - /* Panel header backdrops for non sub-panels. */ - if (!is_subpanel) { - float panel_headercolor[4]; - UI_GetThemeColor4fv(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER, - panel_headercolor); - UI_draw_roundbox_corner_set(is_open ? UI_CNR_TOP_RIGHT | UI_CNR_TOP_LEFT : UI_CNR_ALL); - - /* Change the width a little bit to line up with the sides. */ - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = header_rect->ymin, - .ymax = header_rect->ymax, - }, - true, - radius, - panel_headercolor); - } - - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); -} - -void ui_draw_aligned_panel(const uiStyle *style, - const uiBlock *block, - const rcti *rect, - const bool show_pin, - const bool show_background, - const bool region_search_filter_active) -{ - const Panel *panel = block->panel; - - /* Add 0.001f to prevent flicker from float inaccuracy. */ - const rcti header_rect = { - rect->xmin, - rect->xmax, - rect->ymax, - rect->ymax + floor(PNL_HEADER / block->aspect + 0.001f), - }; - - if (show_background) { - panel_draw_aligned_backdrop(panel, rect, &header_rect); - } - - /* Draw the widgets and text in the panel header. */ - if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) { - panel_draw_aligned_widgets(style, - panel, - &header_rect, - block->aspect, - show_pin, - show_background, - region_search_filter_active); - } - - if (panel_custom_data_active_get(panel)) { - panel_draw_highlight_border(panel, rect, &header_rect); - } -} - -bool UI_panel_should_show_background(const ARegion *region, const PanelType *panel_type) -{ - if (region->alignment == RGN_ALIGN_FLOAT) { - return false; - } - - if (panel_type && panel_type->flag & PANEL_TYPE_NO_HEADER) { - if (region->regiontype == RGN_TYPE_TOOLS) { - /* We never want a background around active tools. */ - return false; - } - /* Without a header there is no background except for region overlap. */ - return region->overlap != 0; - } - - return true; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Category Drawing (Tabs) - * \{ */ - -#define TABS_PADDING_BETWEEN_FACTOR 4.0f -#define TABS_PADDING_TEXT_FACTOR 6.0f - -void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) -{ - // #define USE_FLAT_INACTIVE - const bool is_left = RGN_ALIGN_ENUM_FROM_MASK(region->alignment != RGN_ALIGN_RIGHT); - View2D *v2d = ®ion->v2d; - const uiStyle *style = UI_style_get(); - const uiFontStyle *fstyle = &style->widget; - const int fontid = fstyle->uifont_id; - float fstyle_points = fstyle->points; - const float aspect = ((uiBlock *)region->uiblocks.first)->aspect; - const float zoom = 1.0f / aspect; - const int px = U.pixelsize; - const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom); - const float dpi_fac = UI_DPI_FAC; - /* Padding of tabs around text. */ - const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px; - /* Padding between tabs. */ - const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom); - bTheme *btheme = UI_GetTheme(); - const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom; - const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) : - (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT); - bool is_alpha; - bool do_scaletabs = false; -#ifdef USE_FLAT_INACTIVE - bool is_active_prev = false; -#endif - float scaletabs = 1.0f; - /* Same for all tabs. */ - /* Intentionally don't scale by 'px'. */ - const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width); - const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3); - const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f; - - int y_ofs = tab_v_pad; - - /* Primary theme colors. */ - uchar theme_col_back[4]; - uchar theme_col_text[3]; - uchar theme_col_text_hi[3]; - - /* Tab colors. */ - uchar theme_col_tab_bg[4]; - float theme_col_tab_active[4]; - float theme_col_tab_inactive[4]; - float theme_col_tab_outline[4]; - - UI_GetThemeColor4ubv(TH_BACK, theme_col_back); - UI_GetThemeColor3ubv(TH_TEXT, theme_col_text); - UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi); - - UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg); - UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active); - UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive); - UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline); - - is_alpha = (region->overlap && (theme_col_back[3] != 255)); - - BLF_enable(fontid, BLF_ROTATION); - BLF_rotation(fontid, M_PI_2); - ui_fontscale(&fstyle_points, aspect); - BLF_size(fontid, fstyle_points * U.pixelsize, U.dpi); - - /* Check the region type supports categories to avoid an assert - * for showing 3D view panels in the properties space. */ - if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) { - BLI_assert(UI_panel_category_is_visible(region)); - } - - /* Calculate tab rectangle and check if we need to scale down. */ - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - rcti *rct = &pc_dyn->rect; - const char *category_id = pc_dyn->idname; - const char *category_id_draw = IFACE_(category_id); - const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); - - rct->xmin = rct_xmin; - rct->xmax = rct_xmax; - - rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2)); - rct->ymax = v2d->mask.ymax - (y_ofs); - - y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2); - } - - if (y_ofs > BLI_rcti_size_y(&v2d->mask)) { - scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs; - - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - rcti *rct = &pc_dyn->rect; - rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; - rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; - } - - do_scaletabs = true; - } - - /* Begin drawing. */ - GPU_line_smooth(true); - - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* Draw the background. */ - if (is_alpha) { - GPU_blend(GPU_BLEND_ALPHA); - immUniformColor4ubv(theme_col_tab_bg); - } - else { - immUniformColor3ubv(theme_col_tab_bg); - } - - if (is_left) { - immRecti( - pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax); - } - else { - immRecti( - pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax); - } - - if (is_alpha) { - GPU_blend(GPU_BLEND_NONE); - } - - immUnbindProgram(); - - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - const rcti *rct = &pc_dyn->rect; - const char *category_id = pc_dyn->idname; - const char *category_id_draw = IFACE_(category_id); - const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2); - size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX; -#if 0 - int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); -#endif - - const bool is_active = STREQ(category_id, category_id_active); - - GPU_blend(GPU_BLEND_ALPHA); - -#ifdef USE_FLAT_INACTIVE - /* Draw line between inactive tabs. */ - if (is_active == false && is_active_prev == false && pc_dyn->prev) { - pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f); - immRecti(pos, - is_left ? v2d->mask.xmin + (category_tabs_width / 5) : - v2d->mask.xmax - (category_tabs_width / 5), - rct->ymax + px, - is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) : - (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5), - rct->ymax + (px * 3)); - immUnbindProgram(); - } - - is_active_prev = is_active; - - if (is_active) -#endif - { - /* Draw filled rectangle and outline for tab. */ - UI_draw_roundbox_corner_set(roundboxtype); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin, - .xmax = rct->xmax, - .ymin = rct->ymin, - .ymax = rct->ymax, - }, - true, - tab_curve_radius, - is_active ? theme_col_tab_active : theme_col_tab_inactive); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin, - .xmax = rct->xmax, - .ymin = rct->ymin, - .ymax = rct->ymax, - }, - false, - tab_curve_radius, - theme_col_tab_outline); - - /* Disguise the outline on one side to join the tab to the panel. */ - pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive); - immRecti(pos, - is_left ? rct->xmax - px : rct->xmin, - rct->ymin + px, - is_left ? rct->xmax : rct->xmin + px, - rct->ymax - px); - immUnbindProgram(); - } - - /* Tab titles. */ - - if (do_scaletabs) { - category_draw_len = BLF_width_to_strlen( - fontid, category_id_draw, category_draw_len, category_width, NULL); - } - - BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); - BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text); - BLF_draw(fontid, category_id_draw, category_draw_len); - - GPU_blend(GPU_BLEND_NONE); - - /* Not essential, but allows events to be handled right up to the region edge (T38171). */ - if (is_left) { - pc_dyn->rect.xmin = v2d->mask.xmin; - } - else { - pc_dyn->rect.xmax = v2d->mask.xmax; - } - } - - GPU_line_smooth(false); - - BLF_disable(fontid, BLF_ROTATION); -} - -#undef TABS_PADDING_BETWEEN_FACTOR -#undef TABS_PADDING_TEXT_FACTOR - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Alignment - * \{ */ - -static int get_panel_size_y(const Panel *panel) -{ - if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { - return panel->sizey; - } - - return PNL_HEADER + panel->sizey; -} - -static int get_panel_real_size_y(const Panel *panel) -{ - const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey; - - if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { - return sizey; - } - - return PNL_HEADER + sizey; -} - -int UI_panel_size_y(const Panel *panel) -{ - return get_panel_real_size_y(panel); -} - -/** - * This function is needed because #uiBlock and Panel itself don't - * change #Panel.sizey or location when closed. - */ -static int get_panel_real_ofsy(Panel *panel) -{ - if (UI_panel_is_closed(panel)) { - return panel->ofsy + panel->sizey; - } - return panel->ofsy; -} - -bool UI_panel_is_dragging(const Panel *panel) -{ - return panel->runtime_flag & PANEL_IS_DRAG_DROP; -} - -/** - * \note about sorting: - * The #Panel.sortorder has a lower value for new panels being added. - * however, that only works to insert a single panel, when more new panels get - * added the coordinates of existing panels and the previously stored to-be-inserted - * panels do not match for sorting. - */ - -static int find_highest_panel(const void *a, const void *b) -{ - const Panel *panel_a = ((PanelSort *)a)->panel; - const Panel *panel_b = ((PanelSort *)b)->panel; - - /* Stick uppermost header-less panels to the top of the region - - * prevent them from being sorted (multiple header-less panels have to be sorted though). */ - if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) { - /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */ - } - else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) { - return -1; - } - else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) { - return 1; - } - - if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) { - return 1; - } - if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) { - return -1; - } - if (panel_a->sortorder > panel_b->sortorder) { - return 1; - } - if (panel_a->sortorder < panel_b->sortorder) { - return -1; - } - - return 0; -} - -static int compare_panel(const void *a, const void *b) -{ - const Panel *panel_a = ((PanelSort *)a)->panel; - const Panel *panel_b = ((PanelSort *)b)->panel; - - if (panel_a->sortorder > panel_b->sortorder) { - return 1; - } - if (panel_a->sortorder < panel_b->sortorder) { - return -1; - } - - return 0; -} - -static void align_sub_panels(Panel *panel) -{ - /* Position sub panels. */ - int ofsy = panel->ofsy + panel->sizey - panel->blocksizey; - - LISTBASE_FOREACH (Panel *, pachild, &panel->children) { - if (pachild->runtime_flag & PANEL_ACTIVE) { - pachild->ofsx = panel->ofsx; - pachild->ofsy = ofsy - get_panel_size_y(pachild); - ofsy -= get_panel_real_size_y(pachild); - - if (pachild->children.first) { - align_sub_panels(pachild); - } - } - } -} - -/** - * Calculate the position and order of panels as they are opened, closed, and dragged. - */ -static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag) -{ - /* Count active panels. */ - int active_panels_len = 0; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - /* These panels should have types since they are currently displayed to the user. */ - BLI_assert(panel->type != NULL); - active_panels_len++; - } - } - if (active_panels_len == 0) { - return false; - } - - /* Sort panels. */ - PanelSort *panel_sort = MEM_mallocN(sizeof(PanelSort) * active_panels_len, __func__); - { - PanelSort *ps = panel_sort; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - ps->panel = panel; - ps++; - } - } - } - - if (drag) { - /* While dragging, sort based on location and update #Panel.sortorder. */ - qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel); - for (int i = 0; i < active_panels_len; i++) { - panel_sort[i].panel->sortorder = i; - } - } - else { - /* Otherwise use #Panel.sortorder. */ - qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel); - } - - /* X offset. */ - const int region_offset_x = panel_region_offset_x_get(region); - for (int i = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - const bool show_background = UI_panel_should_show_background(region, ps->panel->type); - ps->panel->runtime.region_ofsx = region_offset_x; - ps->new_offset_x = region_offset_x + (show_background ? UI_PANEL_MARGIN_X : 0); - } - - /* Y offset. */ - for (int i = 0, y = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - const bool show_background = UI_panel_should_show_background(region, ps->panel->type); - - y -= get_panel_real_size_y(ps->panel); - - /* Separate panel boxes a bit further (if they are drawn). */ - if (show_background) { - y -= UI_PANEL_MARGIN_Y; - } - ps->new_offset_y = y; - /* The header still draws offset by the size of closed panels, so apply the offset here. */ - if (UI_panel_is_closed(ps->panel)) { - panel_sort[i].new_offset_y -= ps->panel->sizey; - } - } - - /* Interpolate based on the input factor. */ - bool changed = false; - for (int i = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - if (ps->panel->flag & PNL_SELECT) { - continue; - } - - if (ps->new_offset_x != ps->panel->ofsx) { - const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor); - ps->panel->ofsx = round_fl_to_int(x); - changed = true; - } - if (ps->new_offset_y != ps->panel->ofsy) { - const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor); - ps->panel->ofsy = round_fl_to_int(y); - changed = true; - } - } - - /* Set locations for tabbed and sub panels. */ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - if (panel->children.first) { - align_sub_panels(panel); - } - } - } - - MEM_freeN(panel_sort); - - return changed; -} - -static void ui_panels_size(ARegion *region, int *r_x, int *r_y) -{ - int sizex = 0; - int sizey = 0; - bool has_panel_with_background = false; - - /* Compute size taken up by panels, for setting in view2d. */ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - const int pa_sizex = panel->ofsx + panel->sizex; - const int pa_sizey = get_panel_real_ofsy(panel); - - sizex = max_ii(sizex, pa_sizex); - sizey = min_ii(sizey, pa_sizey); - if (UI_panel_should_show_background(region, panel->type)) { - has_panel_with_background = true; - } - } - } - - if (sizex == 0) { - sizex = UI_PANEL_WIDTH; - } - if (sizey == 0) { - sizey = -UI_PANEL_WIDTH; - } - /* Extra margin after the list so the view scrolls a few pixels further than the panel border. - * Also makes the bottom match the top margin. */ - if (has_panel_with_background) { - sizey -= UI_PANEL_MARGIN_Y; - } - - *r_x = sizex; - *r_y = sizey; -} - -static void ui_do_animate(bContext *C, Panel *panel) -{ - uiHandlePanelData *data = panel->activedata; - ARegion *region = CTX_wm_region(C); - - float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME; - fac = min_ff(sqrtf(fac), 1.0f); - - if (uiAlignPanelStep(region, fac, false)) { - ED_region_tag_redraw(region); - } - else { - if (UI_panel_is_dragging(panel)) { - /* NOTE: doing this in #panel_activate_state would require - * removing `const` for context in many other places. */ - reorder_instanced_panel_list(C, region, panel); - } - - panel_activate_state(C, panel, PANEL_STATE_EXIT); - } -} - -static void panels_layout_begin_clear_flags(ListBase *lb) -{ - LISTBASE_FOREACH (Panel *, panel, lb) { - /* Flags to copy over to the next layout pass. */ - const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP; - - const bool was_active = panel->runtime_flag & PANEL_ACTIVE; - const bool was_closed = UI_panel_is_closed(panel); - panel->runtime_flag &= flag_copy; - SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE); - SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED); - - panels_layout_begin_clear_flags(&panel->children); - } -} - -void UI_panels_begin(const bContext *UNUSED(C), ARegion *region) -{ - /* Set all panels as inactive, so that at the end we know which ones were used. Also - * clear other flags so we know later that their values were set for the current redraw. */ - panels_layout_begin_clear_flags(®ion->panels); -} - -void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) -{ - ScrArea *area = CTX_wm_area(C); - - region_panels_set_expansion_from_list_data(C, region); - - const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE; - - if (properties_space_needs_realign(area, region)) { - region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); - } - else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { - region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); - } - - if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { - /* Clean up the extra panels and buttons created for searching. */ - region_panels_remove_invisible_layouts(region); - } - - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime.block != NULL); - panel_calculate_size_recursive(region, panel); - } - } - - /* Offset contents. */ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel) { - ui_offset_panel_block(block); - } - } - - /* Re-align, possibly with animation. */ - Panel *panel; - if (panels_need_realign(area, region, &panel)) { - if (panel) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - else { - uiAlignPanelStep(region, 1.0, false); - } - } - - /* Compute size taken up by panels. */ - ui_panels_size(region, r_x, r_y); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Dragging - * \{ */ - -#define DRAG_REGION_PAD (PNL_HEADER * 0.5) -static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) -{ - uiHandlePanelData *data = panel->activedata; - ARegion *region = CTX_wm_region(C); - - /* Keep the drag position in the region with a small pad to keep the panel visible. */ - const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); - - float dy = (float)(y - data->starty); - - /* Adjust for region zoom. */ - dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); - - /* Add the movement of the view due to edge scrolling while dragging. */ - dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); - - panel->ofsy = data->startofsy + round_fl_to_int(dy); - - uiAlignPanelStep(region, 0.2f, true); - - ED_region_tag_redraw(region); -} -#undef DRAG_REGION_PAD - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Region Level Panel Interaction - * \{ */ - -static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, - const Panel *panel, - const int mx, - const int my) -{ - if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) { - return PANEL_MOUSE_OUTSIDE; - } - - if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) { - return PANEL_MOUSE_INSIDE_HEADER; - } - - if (!UI_panel_is_closed(panel)) { - if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) { - return PANEL_MOUSE_INSIDE_CONTENT; - } - } - - return PANEL_MOUSE_OUTSIDE; -} - -typedef struct uiPanelDragCollapseHandle { - bool was_first_open; - int xy_init[2]; -} uiPanelDragCollapseHandle; - -static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata) -{ - uiPanelDragCollapseHandle *dragcol_data = userdata; - MEM_freeN(dragcol_data); -} - -static void ui_panel_drag_collapse(const bContext *C, - const uiPanelDragCollapseHandle *dragcol_data, - const int xy_dst[2]) -{ - ARegion *region = CTX_wm_region(C); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float xy_a_block[2] = {UNPACK2(dragcol_data->xy_init)}; - float xy_b_block[2] = {UNPACK2(xy_dst)}; - Panel *panel = block->panel; - - if (panel == NULL || (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER))) { - continue; - } - const int oldflag = panel->flag; - - /* Lock axis. */ - xy_b_block[0] = dragcol_data->xy_init[0]; - - /* Use cursor coords in block space. */ - ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); - ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); - - /* Set up `rect` to match header size. */ - rctf rect = block->rect; - rect.ymin = rect.ymax; - rect.ymax = rect.ymin + PNL_HEADER; - - /* Touch all panels between last mouse coordinate and the current one. */ - if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) { - /* Force panel to open or close. */ - panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; - SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED); - - /* If panel->flag has changed this means a panel was opened/closed here. */ - if (panel->flag != oldflag) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - } - } - /* Update the instanced panel data expand flags with the changes made here. */ - set_panels_list_data_expand_flag(C, region); -} - -/** - * Panel drag-collapse (modal handler). - * Clicking and dragging over panels toggles their collapse state based on the panel - * that was first dragged over. If it was open all affected panels including the initial - * one are closed and vice versa. - */ -static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata) -{ - wmWindow *win = CTX_wm_window(C); - uiPanelDragCollapseHandle *dragcol_data = userdata; - short retval = WM_UI_HANDLER_CONTINUE; - - switch (event->type) { - case MOUSEMOVE: - ui_panel_drag_collapse(C, dragcol_data, event->xy); - - retval = WM_UI_HANDLER_BREAK; - break; - case LEFTMOUSE: - if (event->val == KM_RELEASE) { - /* Done! */ - WM_event_remove_ui_handler(&win->modalhandlers, - ui_panel_drag_collapse_handler, - ui_panel_drag_collapse_handler_remove, - dragcol_data, - true); - ui_panel_drag_collapse_handler_remove(C, dragcol_data); - } - /* Don't let any left-mouse event fall through! */ - retval = WM_UI_HANDLER_BREAK; - break; - } - - return retval; -} - -static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open) -{ - wmWindow *win = CTX_wm_window(C); - const wmEvent *event = win->eventstate; - uiPanelDragCollapseHandle *dragcol_data = MEM_mallocN(sizeof(*dragcol_data), __func__); - - dragcol_data->was_first_open = was_open; - copy_v2_v2_int(dragcol_data->xy_init, event->xy); - - WM_event_add_ui_handler(C, - &win->modalhandlers, - ui_panel_drag_collapse_handler, - ui_panel_drag_collapse_handler_remove, - dragcol_data, - 0); -} - -/** - * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc. - * Code currently assumes layout style for location of widgets - * - * \param mx: The mouse x coordinate, in panel space. - */ -static void ui_handle_panel_header(const bContext *C, - const uiBlock *block, - const int mx, - const int event_type, - const bool ctrl, - const bool shift) -{ - Panel *panel = block->panel; - ARegion *region = CTX_wm_region(C); - - BLI_assert(panel->type != NULL); - BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); - - const bool is_subpanel = (panel->type->parent != NULL); - const bool use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel); - const bool show_pin = use_pin && (panel->flag & PNL_PIN); - const bool show_drag = !is_subpanel; - - /* Handle panel pinning. */ - if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) { - panel->flag ^= PNL_PIN; - ED_region_tag_redraw(region); - return; - } - - float expansion_area_xmax = block->rect.xmax; - if (show_drag) { - expansion_area_xmax -= (PNL_ICON * 1.5f); - } - if (show_pin) { - expansion_area_xmax -= PNL_ICON; - } - - /* Collapse and expand panels. */ - if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) { - if (ctrl && !is_subpanel) { - /* For parent panels, collapse all other panels or toggle children. */ - if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) { - panels_collapse_all(region, panel); - - /* Reset the view - we don't want to display a view without content. */ - UI_view2d_offset(®ion->v2d, 0.0f, 1.0f); - } - else { - /* If a panel has sub-panels and it's open, toggle the expansion - * of the sub-panels (based on the expansion of the first sub-panel). */ - Panel *first_child = panel->children.first; - BLI_assert(first_child != NULL); - panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child)); - panel->flag |= PNL_CLOSED; - } - } - - SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED); - - if (event_type == LEFTMOUSE) { - ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel)); - } - - /* Set panel custom data (modifier) active when expanding subpanels, but not top-level - * panels to allow collapsing and expanding without setting the active element. */ - if (is_subpanel) { - panel_custom_data_active_set(panel); - } - - set_panels_list_data_expand_flag(C, region); - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - return; - } - - /* Handle panel dragging. For now don't allow dragging in floating regions. */ - if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) { - const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f); - const float drag_area_xmax = block->rect.xmax; - if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) { - panel_activate_state(C, panel, PANEL_STATE_DRAG); - return; - } - } - - /* Handle panel unpinning. */ - if (show_pin) { - const float pin_area_xmin = expansion_area_xmax; - const float pin_area_xmax = pin_area_xmin + PNL_ICON; - if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) { - panel->flag ^= PNL_PIN; - ED_region_tag_redraw(region); - return; - } - } -} - -bool UI_panel_category_is_visible(const ARegion *region) -{ - /* Check for more than one category. */ - return region->panels_category.first && - region->panels_category.first != region->panels_category.last; -} - -PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname) -{ - return BLI_findstring(®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname)); -} - -PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname) -{ - return BLI_findstring( - ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname)); -} - -static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback) -{ - ListBase *lb = ®ion->panels_category_active; - PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname); - - if (pc_act) { - BLI_remlink(lb, pc_act); - } - else { - pc_act = MEM_callocN(sizeof(PanelCategoryStack), __func__); - BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname)); - } - - if (fallback) { - /* For fall-backs, add at the end so explicitly chosen categories have priority. */ - BLI_addtail(lb, pc_act); - } - else { - BLI_addhead(lb, pc_act); - } - - /* Validate all active panels. We could do this on load, they are harmless - - * but we should remove them somewhere. - * (Add-ons could define panels and gather cruft over time). */ - { - PanelCategoryStack *pc_act_next; - /* intentionally skip first */ - pc_act_next = pc_act->next; - while ((pc_act = pc_act_next)) { - pc_act_next = pc_act->next; - if (!BLI_findstring( - ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) { - BLI_remlink(lb, pc_act); - MEM_freeN(pc_act); - } - } - } -} - -void UI_panel_category_active_set(ARegion *region, const char *idname) -{ - ui_panel_category_active_set(region, idname, false); -} - -void UI_panel_category_active_set_default(ARegion *region, const char *idname) -{ - if (!UI_panel_category_active_find(region, idname)) { - ui_panel_category_active_set(region, idname, true); - } -} - -const char *UI_panel_category_active_get(ARegion *region, bool set_fallback) -{ - LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) { - if (UI_panel_category_find(region, pc_act->idname)) { - return pc_act->idname; - } - } - - if (set_fallback) { - PanelCategoryDyn *pc_dyn = region->panels_category.first; - if (pc_dyn) { - ui_panel_category_active_set(region, pc_dyn->idname, true); - return pc_dyn->idname; - } - } - - return NULL; -} - -static PanelCategoryDyn *panel_categories_find_mouse_over(ARegion *region, const wmEvent *event) -{ - LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) { - if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) { - return ptd; - } - } - - return NULL; -} - -void UI_panel_category_add(ARegion *region, const char *name) -{ - PanelCategoryDyn *pc_dyn = MEM_callocN(sizeof(*pc_dyn), __func__); - BLI_addtail(®ion->panels_category, pc_dyn); - - BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname)); - - /* 'pc_dyn->rect' must be set on draw. */ -} - -void UI_panel_category_clear_all(ARegion *region) -{ - BLI_freelistN(®ion->panels_category); -} - -static int ui_handle_panel_category_cycling(const wmEvent *event, - ARegion *region, - const uiBut *active_but) -{ - const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE); - const bool inside_tabregion = - ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ? - (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) : - (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin)); - - /* If mouse is inside non-tab region, ctrl key is required. */ - if (is_mousewheel && (event->modifier & KM_CTRL) == 0 && !inside_tabregion) { - return WM_UI_HANDLER_CONTINUE; - } - - if (active_but && ui_but_supports_cycling(active_but)) { - /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */ - } - else { - const char *category = UI_panel_category_active_get(region, false); - if (LIKELY(category)) { - PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category); - if (LIKELY(pc_dyn)) { - if (is_mousewheel) { - /* We can probably get rid of this and only allow Ctrl-Tabbing. */ - pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev; - } - else { - const bool backwards = event->modifier & KM_SHIFT; - pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next; - if (!pc_dyn) { - /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */ - pc_dyn = backwards ? region->panels_category.last : region->panels_category.first; - } - } - - if (pc_dyn) { - /* Intentionally don't reset scroll in this case, - * allowing for quick browsing between tabs. */ - UI_panel_category_active_set(region, pc_dyn->idname); - ED_region_tag_redraw(region); - } - } - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -int ui_handler_panel_region(bContext *C, - const wmEvent *event, - ARegion *region, - const uiBut *active_but) -{ - /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ - if (ISMOUSE_MOTION(event->type)) { - return WM_UI_HANDLER_CONTINUE; - } - - /* We only use KM_PRESS events in this function, so it's simpler to return early. */ - if (event->val != KM_PRESS) { - return WM_UI_HANDLER_CONTINUE; - } - - /* Scroll-bars can overlap panels now, they have handling priority. */ - if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->xy)) { - return WM_UI_HANDLER_CONTINUE; - } - - int retval = WM_UI_HANDLER_CONTINUE; - - /* Handle category tabs. */ - if (UI_panel_category_is_visible(region)) { - if (event->type == LEFTMOUSE) { - PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event); - if (pc_dyn) { - UI_panel_category_active_set(region, pc_dyn->idname); - ED_region_tag_redraw(region); - - /* Reset scroll to the top (T38348). */ - UI_view2d_offset(®ion->v2d, -1.0f, 1.0f); - - retval = WM_UI_HANDLER_BREAK; - } - } - else if (((event->type == EVT_TABKEY) && (event->modifier & KM_CTRL)) || - ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { - /* Cycle tabs. */ - retval = ui_handle_panel_category_cycling(event, region, active_but); - } - } - - if (retval == WM_UI_HANDLER_BREAK) { - return retval; - } - - const bool region_has_active_button = (ui_region_find_active_but(region) != NULL); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - Panel *panel = block->panel; - if (panel == NULL || panel->type == NULL) { - continue; - } - /* We can't expand or collapse panels without headers, they would disappear. */ - if (panel->type->flag & PANEL_TYPE_NO_HEADER) { - continue; - } - - int mx = event->xy[0]; - int my = event->xy[1]; - ui_window_to_block(region, block, &mx, &my); - - const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); - - if (mouse_state != PANEL_MOUSE_OUTSIDE) { - /* Mark panels that have been interacted with so their expansion - * doesn't reset when property search finishes. */ - SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED); - panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; - - /* The panel collapse / expand key "A" is special as it takes priority over - * active button handling. */ - if (event->type == EVT_AKEY && - ((event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0)) { - retval = WM_UI_HANDLER_BREAK; - ui_handle_panel_header( - C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); - break; - } - } - - /* Don't do any other panel handling with an active button. */ - if (region_has_active_button) { - continue; - } - - if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) { - /* All mouse clicks inside panel headers should return in break. */ - if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) { - retval = WM_UI_HANDLER_BREAK; - ui_handle_panel_header( - C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); - } - else if (event->type == RIGHTMOUSE) { - retval = WM_UI_HANDLER_BREAK; - ui_popup_context_menu_for_panel(C, region, block->panel); - } - break; - } - } - - return retval; -} - -static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) -{ - panel->runtime.custom_data_ptr = custom_data; - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - ui_panel_custom_data_set_recursive(child_panel, custom_data); - } -} - -void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr) -{ - uiLayoutSetContextPointer(panel->layout, name, ptr); - panel->runtime.context = uiLayoutGetContextStore(panel->layout); -} - -void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) -{ - BLI_assert(panel->type != NULL); - - /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ - if (panel->runtime.custom_data_ptr != NULL) { - MEM_freeN(panel->runtime.custom_data_ptr); - } - - ui_panel_custom_data_set_recursive(panel, custom_data); -} - -PointerRNA *UI_panel_custom_data_get(const Panel *panel) -{ - return panel->runtime.custom_data_ptr; -} - -PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - Panel *panel = block->panel; - if (panel == NULL) { - continue; - } - - int mx = event->xy[0]; - int my = event->xy[1]; - ui_window_to_block(region, block, &mx, &my); - const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); - if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { - return UI_panel_custom_data_get(panel); - } - } - - return NULL; -} - -bool UI_panel_can_be_pinned(const Panel *panel) -{ - return (panel->type->parent == NULL) && !(panel->type->flag & PANEL_TYPE_INSTANCED); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Window Level Modal Panel Interaction - * \{ */ - -/* NOTE: this is modal handler and should not swallow events for animation. */ -static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) -{ - Panel *panel = userdata; - uiHandlePanelData *data = panel->activedata; - - /* Verify if we can stop. */ - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - else if (event->type == MOUSEMOVE) { - if (data->state == PANEL_STATE_DRAG) { - ui_do_drag(C, event, panel); - } - } - else if (event->type == TIMER && event->customdata == data->animtimer) { - if (data->state == PANEL_STATE_ANIMATION) { - ui_do_animate(C, panel); - } - else if (data->state == PANEL_STATE_DRAG) { - ui_do_drag(C, event, panel); - } - } - - data = panel->activedata; - - if (data && data->state == PANEL_STATE_ANIMATION) { - return WM_UI_HANDLER_CONTINUE; - } - return WM_UI_HANDLER_BREAK; -} - -static void ui_handler_remove_panel(bContext *C, void *userdata) -{ - Panel *panel = userdata; - - panel_activate_state(C, panel, PANEL_STATE_EXIT); -} - -static void panel_handle_data_ensure(const bContext *C, - wmWindow *win, - const ARegion *region, - Panel *panel, - const uiHandlePanelState state) -{ - if (panel->activedata == NULL) { - panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__); - WM_event_add_ui_handler( - C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0); - } - - uiHandlePanelData *data = panel->activedata; - - data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); - - data->state = state; - data->startx = win->eventstate->xy[0]; - data->starty = win->eventstate->xy[1]; - data->startofsx = panel->ofsx; - data->startofsy = panel->ofsy; - data->start_cur_xmin = region->v2d.cur.xmin; - data->start_cur_ymin = region->v2d.cur.ymin; - data->starttime = PIL_check_seconds_timer(); -} - -/** - * \note "select" and "drag drop" flags: First, the panel is "picked up" and both flags are set. - * Then when the mouse releases and the panel starts animating to its aligned position, PNL_SELECT - * is unset. When the animation finishes, PANEL_IS_DRAG_DROP is cleared. - */ -static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state) -{ - uiHandlePanelData *data = panel->activedata; - wmWindow *win = CTX_wm_window(C); - ARegion *region = CTX_wm_region(C); - - if (data != NULL && data->state == state) { - return; - } - - if (state == PANEL_STATE_DRAG) { - panel_custom_data_active_set(panel); - - panel_set_flag_recursive(panel, PNL_SELECT, true); - panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, true); - - panel_handle_data_ensure(C, win, region, panel, state); - - /* 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); - } - else if (state == PANEL_STATE_ANIMATION) { - panel_set_flag_recursive(panel, PNL_SELECT, false); - - panel_handle_data_ensure(C, win, region, panel, state); - } - else if (state == PANEL_STATE_EXIT) { - panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, false); - - BLI_assert(data != NULL); - - if (data->animtimer) { - WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer); - data->animtimer = NULL; - } - - MEM_freeN(data); - panel->activedata = NULL; - - WM_event_remove_ui_handler( - &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false); - } - - ED_region_tag_redraw(region); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_panel.cc b/source/blender/editors/interface/interface_panel.cc new file mode 100644 index 00000000000..53f1265ee74 --- /dev/null +++ b/source/blender/editors/interface/interface_panel.cc @@ -0,0 +1,2581 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +/* a full doc with API notes can be found in + * bf-blender/trunk/blender/doc/guides/interface_API.txt */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "PIL_time.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "BLF_api.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Defines & Structs + * \{ */ + +#define ANIMATION_TIME 0.30 +#define ANIMATION_INTERVAL 0.02 + +enum uiPanelRuntimeFlag { + PANEL_LAST_ADDED = (1 << 0), + PANEL_ACTIVE = (1 << 2), + PANEL_WAS_ACTIVE = (1 << 3), + PANEL_ANIM_ALIGN = (1 << 4), + PANEL_NEW_ADDED = (1 << 5), + PANEL_SEARCH_FILTER_MATCH = (1 << 7), + /** + * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH) + * instead of #PNL_CLOSED. Set to true on every property search update. + */ + PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8), + /** The Panel was before the start of the current / latest layout pass. */ + PANEL_WAS_CLOSED = (1 << 9), + /** + * Set when the panel is being dragged and while it animates back to its aligned + * position. Unlike #PANEL_STATE_ANIMATION, this is applied to sub-panels as well. + */ + PANEL_IS_DRAG_DROP = (1 << 10), + /** Draw a border with the active color around the panel. */ + PANEL_ACTIVE_BORDER = (1 << 11), +}; + +/* The state of the mouse position relative to the panel. */ +enum uiPanelMouseState { + PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */ + PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */ + PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */ +}; + +enum uiHandlePanelState { + PANEL_STATE_DRAG, + PANEL_STATE_ANIMATION, + PANEL_STATE_EXIT, +}; + +struct uiHandlePanelData { + uiHandlePanelState state; + + /* Animation. */ + wmTimer *animtimer; + double starttime; + + /* Dragging. */ + int startx, starty; + int startofsx, startofsy; + float start_cur_xmin, start_cur_ymin; +}; + +struct PanelSort { + Panel *panel; + int new_offset_x; + int new_offset_y; +}; + +static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel); +static int get_panel_real_size_y(const Panel *panel); +static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state); +static int compare_panel(const void *a, const void *b); +static bool panel_type_context_poll(ARegion *region, + const PanelType *panel_type, + const char *context); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Local Functions + * \{ */ + +static bool panel_active_animation_changed(ListBase *lb, + Panel **r_panel_animation, + bool *r_no_animation) +{ + LISTBASE_FOREACH (Panel *, panel, lb) { + /* Detect panel active flag changes. */ + if (!(panel->type && panel->type->parent)) { + if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) { + return true; + } + if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) { + return true; + } + } + + /* Detect changes in panel expansions. */ + if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) { + *r_panel_animation = panel; + return false; + } + + if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) { + if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) { + return true; + } + } + + /* Detect animation. */ + if (panel->activedata) { + uiHandlePanelData *data = static_cast(panel->activedata); + if (data->state == PANEL_STATE_ANIMATION) { + *r_panel_animation = panel; + } + else { + /* Don't animate while handling other interaction. */ + *r_no_animation = true; + } + } + if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) { + *r_panel_animation = panel; + } + } + + return false; +} + +/** + * \return True if the properties editor switch tabs since the last layout pass. + */ +static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region) +{ + if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) { + const SpaceProperties *sbuts = static_cast(area->spacedata.first); + + if (sbuts->mainbo != sbuts->mainb) { + return true; + } + } + + return false; +} + +static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation) +{ + *r_panel_animation = nullptr; + + if (properties_space_needs_realign(area, region)) { + return true; + } + + /* Detect if a panel was added or removed. */ + Panel *panel_animation = nullptr; + bool no_animation = false; + if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) { + return true; + } + + /* Detect panel marked for animation, if we're not already animating. */ + if (panel_animation) { + if (!no_animation) { + *r_panel_animation = panel_animation; + } + return true; + } + + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Functions for Instanced Panels + * \{ */ + +static Panel *panel_add_instanced(ARegion *region, + ListBase *panels, + PanelType *panel_type, + PointerRNA *custom_data) +{ + Panel *panel = MEM_cnew(__func__); + panel->type = panel_type; + BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); + + panel->runtime.custom_data_ptr = custom_data; + panel->runtime_flag |= PANEL_NEW_ADDED; + + /* Add the panel's children too. Although they aren't instanced panels, we can still use this + * function to create them, as UI_panel_begin does other things we don't need to do. */ + LISTBASE_FOREACH (LinkData *, child, &panel_type->children) { + PanelType *child_type = static_cast(child->data); + panel_add_instanced(region, &panel->children, child_type, custom_data); + } + + /* Make sure the panel is added to the end of the display-order as well. This is needed for + * loading existing files. + * + * NOTE: We could use special behavior to place it after the panel that starts the list of + * instanced panels, but that would add complexity that isn't needed for now. */ + int max_sortorder = 0; + LISTBASE_FOREACH (Panel *, existing_panel, panels) { + if (existing_panel->sortorder > max_sortorder) { + max_sortorder = existing_panel->sortorder; + } + } + panel->sortorder = max_sortorder + 1; + + BLI_addtail(panels, panel); + + return panel; +} + +Panel *UI_panel_add_instanced(const bContext *C, + ARegion *region, + ListBase *panels, + const char *panel_idname, + PointerRNA *custom_data) +{ + ARegionType *region_type = region->type; + + PanelType *panel_type = static_cast( + BLI_findstring(®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname))); + + if (panel_type == nullptr) { + printf("Panel type '%s' not found.\n", panel_idname); + return nullptr; + } + + Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data); + + /* Do this after #panel_add_instatnced so all sub-panels are added. */ + panel_set_expansion_from_list_data(C, new_panel); + + return new_panel; +} + +void UI_list_panel_unique_str(Panel *panel, char *r_name) +{ + /* The panel sort-order will be unique for a specific panel type because the instanced + * panel list is regenerated for every change in the data order / length. */ + snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder); +} + +/** + * Free a panel and its children. Custom data is shared by the panel and its children + * and is freed by #UI_panels_free_instanced. + * + * \note The only panels that should need to be deleted at runtime are panels with the + * #PANEL_TYPE_INSTANCED flag set. + */ +static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel) +{ + /* Recursively delete children. */ + LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) { + panel_delete(C, region, &panel->children, child); + } + BLI_freelistN(&panel->children); + + BLI_remlink(panels, panel); + if (panel->activedata) { + MEM_freeN(panel->activedata); + } + MEM_freeN(panel); +} + +void UI_panels_free_instanced(const bContext *C, ARegion *region) +{ + /* Delete panels with the instanced flag. */ + LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) { + if ((panel->type != nullptr) && (panel->type->flag & PANEL_TYPE_INSTANCED)) { + /* Make sure the panel's handler is removed before deleting it. */ + if (C != nullptr && panel->activedata != nullptr) { + panel_activate_state(C, panel, PANEL_STATE_EXIT); + } + + /* Free panel's custom data. */ + if (panel->runtime.custom_data_ptr != nullptr) { + MEM_freeN(panel->runtime.custom_data_ptr); + } + + /* Free the panel and its sub-panels. */ + panel_delete(C, region, ®ion->panels, panel); + } + } +} + +bool UI_panel_list_matches_data(ARegion *region, + ListBase *data, + uiListPanelIDFromDataFunc panel_idname_func) +{ + /* Check for nullptr data. */ + int data_len = 0; + Link *data_link = nullptr; + if (data == nullptr) { + data_len = 0; + data_link = nullptr; + } + else { + data_len = BLI_listbase_count(data); + data_link = static_cast(data->first); + } + + int i = 0; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type != nullptr && panel->type->flag & PANEL_TYPE_INSTANCED) { + /* The panels were reordered by drag and drop. */ + if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) { + return false; + } + + /* We reached the last data item before the last instanced panel. */ + if (data_link == nullptr) { + return false; + } + + /* Check if the panel type matches the panel type from the data item. */ + char panel_idname[MAX_NAME]; + panel_idname_func(data_link, panel_idname); + if (!STREQ(panel_idname, panel->type->idname)) { + return false; + } + + data_link = data_link->next; + i++; + } + } + + /* If we didn't make it to the last list item, the panel list isn't complete. */ + if (i != data_len) { + return false; + } + + return true; +} + +static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel) +{ + /* Without a type we cannot access the reorder callback. */ + if (drag_panel->type == nullptr) { + return; + } + /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */ + if (drag_panel->type->reorder == nullptr) { + return; + } + + char *context = nullptr; + if (!UI_panel_category_is_visible(region)) { + context = drag_panel->type->context; + } + + /* Find how many instanced panels with this context string. */ + int list_panels_len = 0; + int start_index = -1; + LISTBASE_FOREACH (const Panel *, panel, ®ion->panels) { + if (panel->type) { + if (panel->type->flag & PANEL_TYPE_INSTANCED) { + if (panel_type_context_poll(region, panel->type, context)) { + if (panel == drag_panel) { + BLI_assert(start_index == -1); /* This panel should only appear once. */ + start_index = list_panels_len; + } + list_panels_len++; + } + } + } + } + BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */ + + /* Sort the matching instanced panels by their display order. */ + PanelSort *panel_sort = static_cast( + MEM_callocN(list_panels_len * sizeof(*panel_sort), __func__)); + PanelSort *sort_index = panel_sort; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type) { + if (panel->type->flag & PANEL_TYPE_INSTANCED) { + if (panel_type_context_poll(region, panel->type, context)) { + sort_index->panel = panel; + sort_index++; + } + } + } + } + qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel); + + /* Find how many of those panels are above this panel. */ + int move_to_index = 0; + for (; move_to_index < list_panels_len; move_to_index++) { + if (panel_sort[move_to_index].panel == drag_panel) { + break; + } + } + + MEM_freeN(panel_sort); + + if (move_to_index == start_index) { + /* In this case, the reorder was not changed, so don't do any updates or call the callback. */ + return; + } + + /* Set the bit to tell the interface to instanced the list. */ + drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; + + CTX_store_set(C, drag_panel->runtime.context); + + /* Finally, move this panel's list item to the new index in its list. */ + drag_panel->type->reorder(C, drag_panel, move_to_index); + + CTX_store_set(C, nullptr); +} + +/** + * Recursive implementation for #panel_set_expansion_from_list_data. + * + * \return Whether the closed flag for the panel or any sub-panels changed. + */ +static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index) +{ + const bool open = (flag & (1 << *flag_index)); + bool changed = (open == UI_panel_is_closed(panel)); + + SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED); + + LISTBASE_FOREACH (Panel *, child, &panel->children) { + *flag_index = *flag_index + 1; + changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index); + } + return changed; +} + +/** + * Set the expansion of the panel and its sub-panels from the flag stored in the + * corresponding list data. The flag has expansion stored in each bit in depth first order. + */ +static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel) +{ + BLI_assert(panel->type != nullptr); + BLI_assert(panel->type->flag & PANEL_TYPE_INSTANCED); + if (panel->type->get_list_data_expand_flag == nullptr) { + /* Instanced panel doesn't support loading expansion. */ + return; + } + + const short expand_flag = panel->type->get_list_data_expand_flag(C, panel); + short flag_index = 0; + + /* Start panel animation if the open state was changed. */ + if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } +} + +/** + * Set expansion based on the data for instanced panels. + */ +static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + PanelType *panel_type = panel->type; + if (panel_type != nullptr && panel->type->flag & PANEL_TYPE_INSTANCED) { + panel_set_expansion_from_list_data(C, panel); + } + } + } +} + +/** + * Recursive implementation for #set_panels_list_data_expand_flag. + */ +static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index) +{ + const bool open = !(panel->flag & PNL_CLOSED); + SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index)); + + LISTBASE_FOREACH (const Panel *, child, &panel->children) { + *flag_index = *flag_index + 1; + get_panel_expand_flag(child, flag, flag_index); + } +} + +/** + * Call the callback to store the panel and sub-panel expansion settings in the list item that + * corresponds to each instanced panel. + * + * \note This needs to iterate through all of the region's panels because the panel with changed + * expansion might have been the sub-panel of an instanced panel, meaning it might not know + * which list item it corresponds to. + */ +static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + PanelType *panel_type = panel->type; + if (panel_type == nullptr) { + continue; + } + + /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */ + if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) { + short expand_flag; + short flag_index = 0; + get_panel_expand_flag(panel, &expand_flag, &flag_index); + if (panel->type->set_list_data_expand_flag) { + panel->type->set_list_data_expand_flag(C, panel, expand_flag); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panels + * \{ */ + +static bool panel_custom_data_active_get(const Panel *panel) +{ + /* The caller should make sure the panel is active and has a type. */ + BLI_assert(UI_panel_is_active(panel)); + BLI_assert(panel->type != nullptr); + + if (panel->type->active_property[0] != '\0') { + PointerRNA *ptr = UI_panel_custom_data_get(panel); + if (ptr != nullptr && !RNA_pointer_is_null(ptr)) { + return RNA_boolean_get(ptr, panel->type->active_property); + } + } + + return false; +} + +static void panel_custom_data_active_set(Panel *panel) +{ + /* Since the panel is interacted with, it should be active and have a type. */ + BLI_assert(UI_panel_is_active(panel)); + BLI_assert(panel->type != nullptr); + + if (panel->type->active_property[0] != '\0') { + PointerRNA *ptr = UI_panel_custom_data_get(panel); + BLI_assert(RNA_struct_find_property(ptr, panel->type->active_property) != nullptr); + if (ptr != nullptr && !RNA_pointer_is_null(ptr)) { + RNA_boolean_set(ptr, panel->type->active_property, true); + } + } +} + +/** + * Set flag state for a panel and its sub-panels. + */ +static void panel_set_flag_recursive(Panel *panel, short flag, bool value) +{ + SET_FLAG_FROM_TEST(panel->flag, value, flag); + + LISTBASE_FOREACH (Panel *, child, &panel->children) { + panel_set_flag_recursive(child, flag, value); + } +} + +/** + * Set runtime flag state for a panel and its sub-panels. + */ +static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value) +{ + SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag); + + LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) { + panel_set_runtime_flag_recursive(sub_panel, flag, value); + } +} + +static void panels_collapse_all(ARegion *region, const Panel *from_panel) +{ + const bool has_category_tabs = UI_panel_category_is_visible(region); + const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : nullptr; + const PanelType *from_pt = from_panel->type; + + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + PanelType *pt = panel->type; + + /* Close panels with headers in the same context. */ + if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) { + if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) { + if ((panel->flag & PNL_PIN) || !category || !pt->category[0] || + STREQ(pt->category, category)) { + panel->flag |= PNL_CLOSED; + } + } + } + } +} + +static bool panel_type_context_poll(ARegion *region, + const PanelType *panel_type, + const char *context) +{ + if (!BLI_listbase_is_empty(®ion->panels_category)) { + return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); + } + + if (panel_type->context[0] && STREQ(panel_type->context, context)) { + return true; + } + + return false; +} + +Panel *UI_panel_find_by_type(ListBase *lb, const PanelType *pt) +{ + const char *idname = pt->idname; + + LISTBASE_FOREACH (Panel *, panel, lb) { + if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) { + return panel; + } + } + return nullptr; +} + +Panel *UI_panel_begin( + ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open) +{ + Panel *panel_last; + const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); + const char *idname = pt->idname; + const bool newpanel = (panel == nullptr); + + if (newpanel) { + panel = MEM_cnew(__func__); + panel->type = pt; + BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname)); + + if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { + panel->flag |= PNL_CLOSED; + panel->runtime_flag |= PANEL_WAS_CLOSED; + } + + panel->ofsx = 0; + panel->ofsy = 0; + panel->sizex = 0; + panel->sizey = 0; + panel->blocksizex = 0; + panel->blocksizey = 0; + panel->runtime_flag |= PANEL_NEW_ADDED; + + BLI_addtail(lb, panel); + } + else { + /* Panel already exists. */ + panel->type = pt; + } + + panel->runtime.block = block; + + BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname)); + + /* If a new panel is added, we insert it right after the panel that was last added. + * This way new panels are inserted in the right place between versions. */ + for (panel_last = static_cast(lb->first); panel_last; panel_last = panel_last->next) { + if (panel_last->runtime_flag & PANEL_LAST_ADDED) { + BLI_remlink(lb, panel); + BLI_insertlinkafter(lb, panel_last, panel); + break; + } + } + + if (newpanel) { + panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0; + + LISTBASE_FOREACH (Panel *, panel_next, lb) { + if (panel_next != panel && panel_next->sortorder >= panel->sortorder) { + panel_next->sortorder++; + } + } + } + + if (panel_last) { + panel_last->runtime_flag &= ~PANEL_LAST_ADDED; + } + + /* Assign the new panel to the block. */ + block->panel = panel; + panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED; + if (region->alignment == RGN_ALIGN_FLOAT) { + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + } + + *r_open = false; + + if (UI_panel_is_closed(panel)) { + return panel; + } + + *r_open = true; + + return panel; +} + +void UI_panel_header_buttons_begin(Panel *panel) +{ + uiBlock *block = panel->runtime.block; + + ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); +} + +void UI_panel_header_buttons_end(Panel *panel) +{ + uiBlock *block = panel->runtime.block; + + /* A button group should always be created in #UI_panel_header_buttons_begin. */ + BLI_assert(!BLI_listbase_is_empty(&block->button_groups)); + + uiButtonGroup *button_group = static_cast(block->button_groups.last); + + button_group->flag &= ~UI_BUTTON_GROUP_LOCK; + + /* Repurpose the first header button group if it is empty, in case the first button added to + * the panel doesn't add a new group (if the button is created directly rather than through an + * interface layout call). */ + if (BLI_listbase_is_single(&block->button_groups) && + BLI_listbase_is_empty(&button_group->buttons)) { + button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER; + } + else { + /* Always add a new button group. Although this may result in many empty groups, without it, + * new buttons in the panel body not protected with a #ui_block_new_button_group call would + * end up in the panel header group. */ + ui_block_new_button_group(block, (uiButtonGroupFlag)0); + } +} + +static float panel_region_offset_x_get(const ARegion *region) +{ + if (UI_panel_category_is_visible(region)) { + if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) { + return UI_PANEL_CATEGORY_MARGIN_WIDTH; + } + } + + return 0.0f; +} + +/** + * Starting from the "block size" set in #UI_panel_end, calculate the full size + * of the panel including the sub-panel headers and buttons. + */ +static void panel_calculate_size_recursive(ARegion *region, Panel *panel) +{ + int width = panel->blocksizex; + int height = panel->blocksizey; + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (child_panel->runtime_flag & PANEL_ACTIVE) { + panel_calculate_size_recursive(region, child_panel); + width = max_ii(width, child_panel->sizex); + height += get_panel_real_size_y(child_panel); + } + } + + /* Update total panel size. */ + if (panel->runtime_flag & PANEL_NEW_ADDED) { + panel->runtime_flag &= ~PANEL_NEW_ADDED; + panel->sizex = width; + panel->sizey = height; + } + else { + const int old_sizex = panel->sizex, old_sizey = panel->sizey; + const int old_region_ofsx = panel->runtime.region_ofsx; + + /* Update width/height if non-zero. */ + if (width != 0) { + panel->sizex = width; + } + if (height != 0 || !UI_panel_is_closed(panel)) { + panel->sizey = height; + } + + /* Check if we need to do an animation. */ + if (panel->sizex != old_sizex || panel->sizey != old_sizey) { + panel->runtime_flag |= PANEL_ANIM_ALIGN; + panel->ofsy += old_sizey - panel->sizey; + } + + panel->runtime.region_ofsx = panel_region_offset_x_get(region); + if (old_region_ofsx != panel->runtime.region_ofsx) { + panel->runtime_flag |= PANEL_ANIM_ALIGN; + } + } +} + +void UI_panel_end(Panel *panel, int width, int height) +{ + /* Store the size of the buttons layout in the panel. The actual panel size + * (including sub-panels) is calculated in #UI_panels_end. */ + panel->blocksizex = width; + panel->blocksizey = height; +} + +static void ui_offset_panel_block(uiBlock *block) +{ + const uiStyle *style = UI_style_get_dpi(); + + /* Compute bounds and offset. */ + ui_block_bounds_calc(block); + + const int ofsy = block->panel->sizey - style->panelspace; + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->rect.ymin += ofsy; + but->rect.ymax += ofsy; + } + + block->rect.xmax = block->panel->sizex; + block->rect.ymax = block->panel->sizey; + block->rect.xmin = block->rect.ymin = 0.0; +} + +void ui_panel_tag_search_filter_match(Panel *panel) +{ + panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH; +} + +static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) +{ + *filter_matches |= bool(panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH); + + /* If the panel has no match we need to make sure that its children are too. */ + if (!*filter_matches) { + LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { + panel_matches_search_filter_recursive(child_panel, filter_matches); + } + } +} + +bool UI_panel_matches_search_filter(const Panel *panel) +{ + bool search_filter_matches = false; + panel_matches_search_filter_recursive(panel, &search_filter_matches); + return search_filter_matches; +} + +/** + * Set the flag telling the panel to use its search result status for its expansion. + */ +static void panel_set_expansion_from_search_filter_recursive(const bContext *C, + Panel *panel, + const bool use_search_closed) +{ + /* This has to run on inactive panels that may not have a type, + * but we can prevent running on header-less panels in some cases. */ + if (panel->type == nullptr || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) { + SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH); + } + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + /* Don't check if the sub-panel is active, otherwise the + * expansion won't be reset when the parent is closed. */ + panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed); + } +} + +/** + * Set the flag telling every panel to override its expansion with its search result status. + */ +static void region_panels_set_expansion_from_search_filter(const bContext *C, + ARegion *region, + const bool use_search_closed) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + /* Don't check if the panel is active, otherwise the expansion won't + * be correct when switching back to tab after exiting search. */ + panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed); + } + set_panels_list_data_expand_flag(C, region); +} + +/** + * Hide buttons in invisible layouts, which are created because buttons must be + * added for all panels in order to search, even panels that will end up closed. + */ +static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) +{ + uiBlock *block = panel->runtime.block; + BLI_assert(block != nullptr); + BLI_assert(block->active); + if (parent_panel != nullptr && UI_panel_is_closed(parent_panel)) { + /* The parent panel is closed, so this panel can be completely removed. */ + UI_block_set_search_only(block, true); + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->flag |= UI_HIDDEN; + } + } + else if (UI_panel_is_closed(panel)) { + /* If sub-panels have no search results but the parent panel does, then the parent panel open + * and the sub-panels will close. In that case there must be a way to hide the buttons in the + * panel but keep the header buttons. */ + LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { + if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) { + continue; + } + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = static_cast(link->data); + but->flag |= UI_HIDDEN; + } + } + } + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (child_panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(child_panel->runtime.block != nullptr); + panel_remove_invisible_layouts_recursive(child_panel, panel); + } + } +} + +static void region_panels_remove_invisible_layouts(ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(panel->runtime.block != nullptr); + panel_remove_invisible_layouts_recursive(panel, nullptr); + } + } +} + +bool UI_panel_is_closed(const Panel *panel) +{ + /* Header-less panels can never be closed, otherwise they could disappear. */ + if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) { + return false; + } + + if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) { + return !UI_panel_matches_search_filter(panel); + } + + return panel->flag & PNL_CLOSED; +} + +bool UI_panel_is_active(const Panel *panel) +{ + return panel->runtime_flag & PANEL_ACTIVE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drawing + * \{ */ + +void UI_panels_draw(const bContext *C, ARegion *region) +{ + /* Draw in reverse order, because #uiBlocks are added in reverse order + * and we need child panels to draw on top. */ + LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel && !UI_panel_is_dragging(block->panel) && + !UI_block_is_search_only(block)) { + UI_block_draw(C, block); + } + } + + LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel && UI_panel_is_dragging(block->panel) && + !UI_block_is_search_only(block)) { + UI_block_draw(C, block); + } + } +} + +#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */ + +void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y) +{ + Panel *panel = block->panel; + const bool is_subpanel = (panel->type && panel->type->parent); + + *r_x = UI_UNIT_X * 1.0f; + *r_y = UI_UNIT_Y * 1.5f; + + if (is_subpanel) { + *r_x += (0.7f * UI_UNIT_X); + } +} + +static void panel_title_color_get(const Panel *panel, + const bool show_background, + const bool region_search_filter_active, + uchar r_color[4]) +{ + if (!show_background) { + /* Use menu colors for floating panels. */ + bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; + copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); + return; + } + + const bool search_match = UI_panel_matches_search_filter(panel); + + UI_GetThemeColor4ubv(TH_TITLE, r_color); + if (region_search_filter_active && !search_match) { + r_color[0] *= 0.5; + r_color[1] *= 0.5; + r_color[2] *= 0.5; + } +} + +static void panel_draw_highlight_border(const Panel *panel, + const rcti *rect, + const rcti *header_rect) +{ + const bool is_subpanel = panel->type->parent != nullptr; + if (is_subpanel) { + return; + } + + const bTheme *btheme = UI_GetTheme(); + const float aspect = panel->runtime.block->aspect; + const float radius = (btheme->tui.panel_roundness * U.widget_unit * 0.5f) / aspect; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + + rctf box_rect; + box_rect.xmin = rect->xmin; + box_rect.xmax = rect->xmax; + box_rect.ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin; + box_rect.ymax = header_rect->ymax; + + float color[4]; + UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); + UI_draw_roundbox_4fv(&box_rect, false, radius, color); +} + +static void panel_draw_aligned_widgets(const uiStyle *style, + const Panel *panel, + const rcti *header_rect, + const float aspect, + const bool show_pin, + const bool show_background, + const bool region_search_filter_active) +{ + const bool is_subpanel = panel->type->parent != nullptr; + const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle; + + const int header_height = BLI_rcti_size_y(header_rect); + const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect); + + /* Offset triangle and text to the right for subpanels. */ + rcti widget_rect; + widget_rect.xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0); + widget_rect.xmax = header_rect->xmax; + widget_rect.ymin = header_rect->ymin; + widget_rect.ymax = header_rect->ymax; + + uchar title_color[4]; + panel_title_color_get(panel, show_background, region_search_filter_active, title_color); + title_color[3] = 255; + + /* Draw collapse icon. */ + { + const float size_y = BLI_rcti_size_y(&widget_rect); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f, + widget_rect.ymin + size_y * 0.2f, + UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT, + aspect * U.inv_dpi_fac, + 0.7f, + 0.0f, + title_color, + false); + GPU_blend(GPU_BLEND_NONE); + } + + /* Draw text label. */ + if (panel->drawname[0] != '\0') { + rcti title_rect; + title_rect.xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f; + title_rect.xmax = widget_rect.xmax; + title_rect.ymin = widget_rect.ymin - 2.0f / aspect; + title_rect.ymax = widget_rect.ymax; + + uiFontStyleDraw_Params params{}; + params.align = UI_STYLE_TEXT_LEFT; + UI_fontstyle_draw( + fontstyle, &title_rect, panel->drawname, sizeof(panel->drawname), title_color, ¶ms); + } + + /* Draw the pin icon. */ + if (show_pin && (panel->flag & PNL_PIN)) { + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f, + widget_rect.ymin + 5.0f / aspect, + ICON_PINNED, + aspect * U.inv_dpi_fac, + 1.0f, + 0.0f, + title_color, + false); + GPU_blend(GPU_BLEND_NONE); + } + + /* Draw drag widget. */ + if (!is_subpanel && show_background) { + const int drag_widget_size = header_height * 0.7f; + GPU_matrix_push(); + /* The magic numbers here center the widget vertically and offset it to the left. + * Currently this depends on the height of the header, although it could be independent. */ + GPU_matrix_translate_2f(widget_rect.xmax - scaled_unit * 1.15, + widget_rect.ymin + (header_height - drag_widget_size) * 0.5f); + + const int col_tint = 84; + float color_high[4], color_dark[4]; + UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, color_high); + UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, color_dark); + + GPUBatch *batch = GPU_batch_preset_panel_drag_widget( + U.pixelsize, color_high, color_dark, drag_widget_size); + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_FLAT_COLOR); + GPU_batch_draw(batch); + GPU_matrix_pop(); + } +} + +static void panel_draw_aligned_backdrop(const Panel *panel, + const rcti *rect, + const rcti *header_rect) +{ + const bool is_subpanel = panel->type->parent != nullptr; + const bool is_open = !UI_panel_is_closed(panel); + + if (is_subpanel && !is_open) { + return; + } + + const bTheme *btheme = UI_GetTheme(); + const float aspect = panel->runtime.block->aspect; + const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f / aspect; + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + + /* Panel backdrop. */ + if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { + float panel_backcolor[4]; + UI_draw_roundbox_corner_set(is_open ? UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT : UI_CNR_ALL); + UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor); + + rctf box_rect; + box_rect.xmin = rect->xmin; + box_rect.xmax = rect->xmax; + box_rect.ymin = rect->ymin; + box_rect.ymax = rect->ymax; + UI_draw_roundbox_4fv(&box_rect, true, radius, panel_backcolor); + } + + /* Panel header backdrops for non sub-panels. */ + if (!is_subpanel) { + float panel_headercolor[4]; + UI_GetThemeColor4fv(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER, + panel_headercolor); + UI_draw_roundbox_corner_set(is_open ? UI_CNR_TOP_RIGHT | UI_CNR_TOP_LEFT : UI_CNR_ALL); + + /* Change the width a little bit to line up with the sides. */ + rctf box_rect; + box_rect.xmin = rect->xmin; + box_rect.xmax = rect->xmax; + box_rect.ymin = header_rect->ymin; + box_rect.ymax = header_rect->ymax; + UI_draw_roundbox_4fv(&box_rect, true, radius, panel_headercolor); + } + + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); +} + +void ui_draw_aligned_panel(const uiStyle *style, + const uiBlock *block, + const rcti *rect, + const bool show_pin, + const bool show_background, + const bool region_search_filter_active) +{ + const Panel *panel = block->panel; + + /* Add 0.001f to prevent flicker from float inaccuracy. */ + const rcti header_rect = { + rect->xmin, + rect->xmax, + rect->ymax, + rect->ymax + (int)floor(PNL_HEADER / block->aspect + 0.001f), + }; + + if (show_background) { + panel_draw_aligned_backdrop(panel, rect, &header_rect); + } + + /* Draw the widgets and text in the panel header. */ + if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) { + panel_draw_aligned_widgets(style, + panel, + &header_rect, + block->aspect, + show_pin, + show_background, + region_search_filter_active); + } + + if (panel_custom_data_active_get(panel)) { + panel_draw_highlight_border(panel, rect, &header_rect); + } +} + +bool UI_panel_should_show_background(const ARegion *region, const PanelType *panel_type) +{ + if (region->alignment == RGN_ALIGN_FLOAT) { + return false; + } + + if (panel_type && panel_type->flag & PANEL_TYPE_NO_HEADER) { + if (region->regiontype == RGN_TYPE_TOOLS) { + /* We never want a background around active tools. */ + return false; + } + /* Without a header there is no background except for region overlap. */ + return region->overlap != 0; + } + + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Category Drawing (Tabs) + * \{ */ + +#define TABS_PADDING_BETWEEN_FACTOR 4.0f +#define TABS_PADDING_TEXT_FACTOR 6.0f + +void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) +{ + // #define USE_FLAT_INACTIVE + const bool is_left = (bool)RGN_ALIGN_ENUM_FROM_MASK(region->alignment != RGN_ALIGN_RIGHT); + View2D *v2d = ®ion->v2d; + const uiStyle *style = UI_style_get(); + const uiFontStyle *fstyle = &style->widget; + const int fontid = fstyle->uifont_id; + float fstyle_points = fstyle->points; + const float aspect = ((uiBlock *)region->uiblocks.first)->aspect; + const float zoom = 1.0f / aspect; + const int px = U.pixelsize; + const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom); + const float dpi_fac = UI_DPI_FAC; + /* Padding of tabs around text. */ + const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px; + /* Padding between tabs. */ + const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom); + bTheme *btheme = UI_GetTheme(); + const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom; + const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) : + (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT); + bool is_alpha; + bool do_scaletabs = false; +#ifdef USE_FLAT_INACTIVE + bool is_active_prev = false; +#endif + float scaletabs = 1.0f; + /* Same for all tabs. */ + /* Intentionally don't scale by 'px'. */ + const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width); + const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3); + const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f; + + int y_ofs = tab_v_pad; + + /* Primary theme colors. */ + uchar theme_col_back[4]; + uchar theme_col_text[3]; + uchar theme_col_text_hi[3]; + + /* Tab colors. */ + uchar theme_col_tab_bg[4]; + float theme_col_tab_active[4]; + float theme_col_tab_inactive[4]; + float theme_col_tab_outline[4]; + + UI_GetThemeColor4ubv(TH_BACK, theme_col_back); + UI_GetThemeColor3ubv(TH_TEXT, theme_col_text); + UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi); + + UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg); + UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active); + UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive); + UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline); + + is_alpha = (region->overlap && (theme_col_back[3] != 255)); + + BLF_enable(fontid, BLF_ROTATION); + BLF_rotation(fontid, M_PI_2); + ui_fontscale(&fstyle_points, aspect); + BLF_size(fontid, fstyle_points * U.pixelsize, U.dpi); + + /* Check the region type supports categories to avoid an assert + * for showing 3D view panels in the properties space. */ + if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) { + BLI_assert(UI_panel_category_is_visible(region)); + } + + /* Calculate tab rectangle and check if we need to scale down. */ + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + rcti *rct = &pc_dyn->rect; + const char *category_id = pc_dyn->idname; + const char *category_id_draw = IFACE_(category_id); + const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); + + rct->xmin = rct_xmin; + rct->xmax = rct_xmax; + + rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2)); + rct->ymax = v2d->mask.ymax - (y_ofs); + + y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2); + } + + if (y_ofs > BLI_rcti_size_y(&v2d->mask)) { + scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs; + + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + rcti *rct = &pc_dyn->rect; + rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; + rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; + } + + do_scaletabs = true; + } + + /* Begin drawing. */ + GPU_line_smooth(true); + + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* Draw the background. */ + if (is_alpha) { + GPU_blend(GPU_BLEND_ALPHA); + immUniformColor4ubv(theme_col_tab_bg); + } + else { + immUniformColor3ubv(theme_col_tab_bg); + } + + if (is_left) { + immRecti( + pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax); + } + else { + immRecti( + pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax); + } + + if (is_alpha) { + GPU_blend(GPU_BLEND_NONE); + } + + immUnbindProgram(); + + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + const rcti *rct = &pc_dyn->rect; + const char *category_id = pc_dyn->idname; + const char *category_id_draw = IFACE_(category_id); + const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2); + size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX; +#if 0 + int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); +#endif + + const bool is_active = STREQ(category_id, category_id_active); + + GPU_blend(GPU_BLEND_ALPHA); + +#ifdef USE_FLAT_INACTIVE + /* Draw line between inactive tabs. */ + if (is_active == false && is_active_prev == false && pc_dyn->prev) { + pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f); + immRecti(pos, + is_left ? v2d->mask.xmin + (category_tabs_width / 5) : + v2d->mask.xmax - (category_tabs_width / 5), + rct->ymax + px, + is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) : + (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5), + rct->ymax + (px * 3)); + immUnbindProgram(); + } + + is_active_prev = is_active; + + if (is_active) +#endif + { + /* Draw filled rectangle and outline for tab. */ + UI_draw_roundbox_corner_set(roundboxtype); + rctf box_rect; + box_rect.xmin = rct->xmin; + box_rect.xmax = rct->xmax; + box_rect.ymin = rct->ymin; + box_rect.ymax = rct->ymax; + + UI_draw_roundbox_4fv(&box_rect, + true, + tab_curve_radius, + is_active ? theme_col_tab_active : theme_col_tab_inactive); + UI_draw_roundbox_4fv(&box_rect, false, tab_curve_radius, theme_col_tab_outline); + + /* Disguise the outline on one side to join the tab to the panel. */ + pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive); + immRecti(pos, + is_left ? rct->xmax - px : rct->xmin, + rct->ymin + px, + is_left ? rct->xmax : rct->xmin + px, + rct->ymax - px); + immUnbindProgram(); + } + + /* Tab titles. */ + + if (do_scaletabs) { + category_draw_len = BLF_width_to_strlen( + fontid, category_id_draw, category_draw_len, category_width, nullptr); + } + + BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); + BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text); + BLF_draw(fontid, category_id_draw, category_draw_len); + + GPU_blend(GPU_BLEND_NONE); + + /* Not essential, but allows events to be handled right up to the region edge (T38171). */ + if (is_left) { + pc_dyn->rect.xmin = v2d->mask.xmin; + } + else { + pc_dyn->rect.xmax = v2d->mask.xmax; + } + } + + GPU_line_smooth(false); + + BLF_disable(fontid, BLF_ROTATION); +} + +#undef TABS_PADDING_BETWEEN_FACTOR +#undef TABS_PADDING_TEXT_FACTOR + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Alignment + * \{ */ + +static int get_panel_size_y(const Panel *panel) +{ + if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { + return panel->sizey; + } + + return PNL_HEADER + panel->sizey; +} + +static int get_panel_real_size_y(const Panel *panel) +{ + const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey; + + if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { + return sizey; + } + + return PNL_HEADER + sizey; +} + +int UI_panel_size_y(const Panel *panel) +{ + return get_panel_real_size_y(panel); +} + +/** + * This function is needed because #uiBlock and Panel itself don't + * change #Panel.sizey or location when closed. + */ +static int get_panel_real_ofsy(Panel *panel) +{ + if (UI_panel_is_closed(panel)) { + return panel->ofsy + panel->sizey; + } + return panel->ofsy; +} + +bool UI_panel_is_dragging(const Panel *panel) +{ + return panel->runtime_flag & PANEL_IS_DRAG_DROP; +} + +/** + * \note about sorting: + * The #Panel.sortorder has a lower value for new panels being added. + * however, that only works to insert a single panel, when more new panels get + * added the coordinates of existing panels and the previously stored to-be-inserted + * panels do not match for sorting. + */ + +static int find_highest_panel(const void *a, const void *b) +{ + const Panel *panel_a = ((PanelSort *)a)->panel; + const Panel *panel_b = ((PanelSort *)b)->panel; + + /* Stick uppermost header-less panels to the top of the region - + * prevent them from being sorted (multiple header-less panels have to be sorted though). */ + if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) { + /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */ + } + else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) { + return -1; + } + else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) { + return 1; + } + + if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) { + return 1; + } + if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) { + return -1; + } + if (panel_a->sortorder > panel_b->sortorder) { + return 1; + } + if (panel_a->sortorder < panel_b->sortorder) { + return -1; + } + + return 0; +} + +static int compare_panel(const void *a, const void *b) +{ + const Panel *panel_a = ((PanelSort *)a)->panel; + const Panel *panel_b = ((PanelSort *)b)->panel; + + if (panel_a->sortorder > panel_b->sortorder) { + return 1; + } + if (panel_a->sortorder < panel_b->sortorder) { + return -1; + } + + return 0; +} + +static void align_sub_panels(Panel *panel) +{ + /* Position sub panels. */ + int ofsy = panel->ofsy + panel->sizey - panel->blocksizey; + + LISTBASE_FOREACH (Panel *, pachild, &panel->children) { + if (pachild->runtime_flag & PANEL_ACTIVE) { + pachild->ofsx = panel->ofsx; + pachild->ofsy = ofsy - get_panel_size_y(pachild); + ofsy -= get_panel_real_size_y(pachild); + + if (pachild->children.first) { + align_sub_panels(pachild); + } + } + } +} + +/** + * Calculate the position and order of panels as they are opened, closed, and dragged. + */ +static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag) +{ + /* Count active panels. */ + int active_panels_len = 0; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + /* These panels should have types since they are currently displayed to the user. */ + BLI_assert(panel->type != nullptr); + active_panels_len++; + } + } + if (active_panels_len == 0) { + return false; + } + + /* Sort panels. */ + PanelSort *panel_sort = static_cast( + MEM_mallocN(sizeof(PanelSort) * active_panels_len, __func__)); + { + PanelSort *ps = panel_sort; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + ps->panel = panel; + ps++; + } + } + } + + if (drag) { + /* While dragging, sort based on location and update #Panel.sortorder. */ + qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel); + for (int i = 0; i < active_panels_len; i++) { + panel_sort[i].panel->sortorder = i; + } + } + else { + /* Otherwise use #Panel.sortorder. */ + qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel); + } + + /* X offset. */ + const int region_offset_x = panel_region_offset_x_get(region); + for (int i = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + const bool show_background = UI_panel_should_show_background(region, ps->panel->type); + ps->panel->runtime.region_ofsx = region_offset_x; + ps->new_offset_x = region_offset_x + (show_background ? UI_PANEL_MARGIN_X : 0); + } + + /* Y offset. */ + for (int i = 0, y = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + const bool show_background = UI_panel_should_show_background(region, ps->panel->type); + + y -= get_panel_real_size_y(ps->panel); + + /* Separate panel boxes a bit further (if they are drawn). */ + if (show_background) { + y -= UI_PANEL_MARGIN_Y; + } + ps->new_offset_y = y; + /* The header still draws offset by the size of closed panels, so apply the offset here. */ + if (UI_panel_is_closed(ps->panel)) { + panel_sort[i].new_offset_y -= ps->panel->sizey; + } + } + + /* Interpolate based on the input factor. */ + bool changed = false; + for (int i = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + if (ps->panel->flag & PNL_SELECT) { + continue; + } + + if (ps->new_offset_x != ps->panel->ofsx) { + const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor); + ps->panel->ofsx = round_fl_to_int(x); + changed = true; + } + if (ps->new_offset_y != ps->panel->ofsy) { + const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor); + ps->panel->ofsy = round_fl_to_int(y); + changed = true; + } + } + + /* Set locations for tabbed and sub panels. */ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + if (panel->children.first) { + align_sub_panels(panel); + } + } + } + + MEM_freeN(panel_sort); + + return changed; +} + +static void ui_panels_size(ARegion *region, int *r_x, int *r_y) +{ + int sizex = 0; + int sizey = 0; + bool has_panel_with_background = false; + + /* Compute size taken up by panels, for setting in view2d. */ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + const int pa_sizex = panel->ofsx + panel->sizex; + const int pa_sizey = get_panel_real_ofsy(panel); + + sizex = max_ii(sizex, pa_sizex); + sizey = min_ii(sizey, pa_sizey); + if (UI_panel_should_show_background(region, panel->type)) { + has_panel_with_background = true; + } + } + } + + if (sizex == 0) { + sizex = UI_PANEL_WIDTH; + } + if (sizey == 0) { + sizey = -UI_PANEL_WIDTH; + } + /* Extra margin after the list so the view scrolls a few pixels further than the panel border. + * Also makes the bottom match the top margin. */ + if (has_panel_with_background) { + sizey -= UI_PANEL_MARGIN_Y; + } + + *r_x = sizex; + *r_y = sizey; +} + +static void ui_do_animate(bContext *C, Panel *panel) +{ + uiHandlePanelData *data = static_cast(panel->activedata); + ARegion *region = CTX_wm_region(C); + + float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME; + fac = min_ff(sqrtf(fac), 1.0f); + + if (uiAlignPanelStep(region, fac, false)) { + ED_region_tag_redraw(region); + } + else { + if (UI_panel_is_dragging(panel)) { + /* NOTE: doing this in #panel_activate_state would require + * removing `const` for context in many other places. */ + reorder_instanced_panel_list(C, region, panel); + } + + panel_activate_state(C, panel, PANEL_STATE_EXIT); + } +} + +static void panels_layout_begin_clear_flags(ListBase *lb) +{ + LISTBASE_FOREACH (Panel *, panel, lb) { + /* Flags to copy over to the next layout pass. */ + const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP; + + const bool was_active = panel->runtime_flag & PANEL_ACTIVE; + const bool was_closed = UI_panel_is_closed(panel); + panel->runtime_flag &= flag_copy; + SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE); + SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED); + + panels_layout_begin_clear_flags(&panel->children); + } +} + +void UI_panels_begin(const bContext *UNUSED(C), ARegion *region) +{ + /* Set all panels as inactive, so that at the end we know which ones were used. Also + * clear other flags so we know later that their values were set for the current redraw. */ + panels_layout_begin_clear_flags(®ion->panels); +} + +void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) +{ + ScrArea *area = CTX_wm_area(C); + + region_panels_set_expansion_from_list_data(C, region); + + const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE; + + if (properties_space_needs_realign(area, region)) { + region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); + } + else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { + region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); + } + + if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { + /* Clean up the extra panels and buttons created for searching. */ + region_panels_remove_invisible_layouts(region); + } + + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(panel->runtime.block != nullptr); + panel_calculate_size_recursive(region, panel); + } + } + + /* Offset contents. */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel) { + ui_offset_panel_block(block); + } + } + + /* Re-align, possibly with animation. */ + Panel *panel; + if (panels_need_realign(area, region, &panel)) { + if (panel) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + else { + uiAlignPanelStep(region, 1.0, false); + } + } + + /* Compute size taken up by panels. */ + ui_panels_size(region, r_x, r_y); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Dragging + * \{ */ + +#define DRAG_REGION_PAD (PNL_HEADER * 0.5) +static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) +{ + uiHandlePanelData *data = static_cast(panel->activedata); + ARegion *region = CTX_wm_region(C); + + /* Keep the drag position in the region with a small pad to keep the panel visible. */ + const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); + + float dy = (float)(y - data->starty); + + /* Adjust for region zoom. */ + dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); + + /* Add the movement of the view due to edge scrolling while dragging. */ + dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); + + panel->ofsy = data->startofsy + round_fl_to_int(dy); + + uiAlignPanelStep(region, 0.2f, true); + + ED_region_tag_redraw(region); +} +#undef DRAG_REGION_PAD + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Region Level Panel Interaction + * \{ */ + +static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, + const Panel *panel, + const int mx, + const int my) +{ + if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) { + return PANEL_MOUSE_OUTSIDE; + } + + if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) { + return PANEL_MOUSE_INSIDE_HEADER; + } + + if (!UI_panel_is_closed(panel)) { + if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) { + return PANEL_MOUSE_INSIDE_CONTENT; + } + } + + return PANEL_MOUSE_OUTSIDE; +} + +struct uiPanelDragCollapseHandle { + bool was_first_open; + int xy_init[2]; +}; + +static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata) +{ + uiPanelDragCollapseHandle *dragcol_data = static_cast(userdata); + MEM_freeN(dragcol_data); +} + +static void ui_panel_drag_collapse(const bContext *C, + const uiPanelDragCollapseHandle *dragcol_data, + const int xy_dst[2]) +{ + ARegion *region = CTX_wm_region(C); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float xy_a_block[2] = {(float)dragcol_data->xy_init[0], (float)dragcol_data->xy_init[1]}; + float xy_b_block[2] = {(float)xy_dst[0], (float)xy_dst[1]}; + Panel *panel = block->panel; + + if (panel == nullptr || (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER))) { + continue; + } + const int oldflag = panel->flag; + + /* Lock axis. */ + xy_b_block[0] = dragcol_data->xy_init[0]; + + /* Use cursor coords in block space. */ + ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); + ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); + + /* Set up `rect` to match header size. */ + rctf rect = block->rect; + rect.ymin = rect.ymax; + rect.ymax = rect.ymin + PNL_HEADER; + + /* Touch all panels between last mouse coordinate and the current one. */ + if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) { + /* Force panel to open or close. */ + panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; + SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED); + + /* If panel->flag has changed this means a panel was opened/closed here. */ + if (panel->flag != oldflag) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + } + } + /* Update the instanced panel data expand flags with the changes made here. */ + set_panels_list_data_expand_flag(C, region); +} + +/** + * Panel drag-collapse (modal handler). + * Clicking and dragging over panels toggles their collapse state based on the panel + * that was first dragged over. If it was open all affected panels including the initial + * one are closed and vice versa. + */ +static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata) +{ + wmWindow *win = CTX_wm_window(C); + uiPanelDragCollapseHandle *dragcol_data = static_cast(userdata); + short retval = WM_UI_HANDLER_CONTINUE; + + switch (event->type) { + case MOUSEMOVE: + ui_panel_drag_collapse(C, dragcol_data, event->xy); + + retval = WM_UI_HANDLER_BREAK; + break; + case LEFTMOUSE: + if (event->val == KM_RELEASE) { + /* Done! */ + WM_event_remove_ui_handler(&win->modalhandlers, + ui_panel_drag_collapse_handler, + ui_panel_drag_collapse_handler_remove, + dragcol_data, + true); + ui_panel_drag_collapse_handler_remove(C, dragcol_data); + } + /* Don't let any left-mouse event fall through! */ + retval = WM_UI_HANDLER_BREAK; + break; + } + + return retval; +} + +static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open) +{ + wmWindow *win = CTX_wm_window(C); + const wmEvent *event = win->eventstate; + uiPanelDragCollapseHandle *dragcol_data = MEM_new(__func__); + + dragcol_data->was_first_open = was_open; + copy_v2_v2_int(dragcol_data->xy_init, event->xy); + + WM_event_add_ui_handler(C, + &win->modalhandlers, + ui_panel_drag_collapse_handler, + ui_panel_drag_collapse_handler_remove, + dragcol_data, + 0); +} + +/** + * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc. + * Code currently assumes layout style for location of widgets + * + * \param mx: The mouse x coordinate, in panel space. + */ +static void ui_handle_panel_header(const bContext *C, + const uiBlock *block, + const int mx, + const int event_type, + const bool ctrl, + const bool shift) +{ + Panel *panel = block->panel; + ARegion *region = CTX_wm_region(C); + + BLI_assert(panel->type != nullptr); + BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); + + const bool is_subpanel = (panel->type->parent != nullptr); + const bool use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel); + const bool show_pin = use_pin && (panel->flag & PNL_PIN); + const bool show_drag = !is_subpanel; + + /* Handle panel pinning. */ + if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) { + panel->flag ^= PNL_PIN; + ED_region_tag_redraw(region); + return; + } + + float expansion_area_xmax = block->rect.xmax; + if (show_drag) { + expansion_area_xmax -= (PNL_ICON * 1.5f); + } + if (show_pin) { + expansion_area_xmax -= PNL_ICON; + } + + /* Collapse and expand panels. */ + if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) { + if (ctrl && !is_subpanel) { + /* For parent panels, collapse all other panels or toggle children. */ + if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) { + panels_collapse_all(region, panel); + + /* Reset the view - we don't want to display a view without content. */ + UI_view2d_offset(®ion->v2d, 0.0f, 1.0f); + } + else { + /* If a panel has sub-panels and it's open, toggle the expansion + * of the sub-panels (based on the expansion of the first sub-panel). */ + Panel *first_child = static_cast(panel->children.first); + BLI_assert(first_child != nullptr); + panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child)); + panel->flag |= PNL_CLOSED; + } + } + + SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED); + + if (event_type == LEFTMOUSE) { + ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel)); + } + + /* Set panel custom data (modifier) active when expanding subpanels, but not top-level + * panels to allow collapsing and expanding without setting the active element. */ + if (is_subpanel) { + panel_custom_data_active_set(panel); + } + + set_panels_list_data_expand_flag(C, region); + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + return; + } + + /* Handle panel dragging. For now don't allow dragging in floating regions. */ + if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) { + const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f); + const float drag_area_xmax = block->rect.xmax; + if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) { + panel_activate_state(C, panel, PANEL_STATE_DRAG); + return; + } + } + + /* Handle panel unpinning. */ + if (show_pin) { + const float pin_area_xmin = expansion_area_xmax; + const float pin_area_xmax = pin_area_xmin + PNL_ICON; + if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) { + panel->flag ^= PNL_PIN; + ED_region_tag_redraw(region); + return; + } + } +} + +bool UI_panel_category_is_visible(const ARegion *region) +{ + /* Check for more than one category. */ + return region->panels_category.first && + region->panels_category.first != region->panels_category.last; +} + +PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname) +{ + return static_cast( + BLI_findstring(®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname))); +} + +PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname) +{ + return static_cast(BLI_findstring( + ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname))); +} + +static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback) +{ + ListBase *lb = ®ion->panels_category_active; + PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname); + + if (pc_act) { + BLI_remlink(lb, pc_act); + } + else { + pc_act = MEM_cnew(__func__); + BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname)); + } + + if (fallback) { + /* For fall-backs, add at the end so explicitly chosen categories have priority. */ + BLI_addtail(lb, pc_act); + } + else { + BLI_addhead(lb, pc_act); + } + + /* Validate all active panels. We could do this on load, they are harmless - + * but we should remove them somewhere. + * (Add-ons could define panels and gather cruft over time). */ + { + PanelCategoryStack *pc_act_next; + /* intentionally skip first */ + pc_act_next = pc_act->next; + while ((pc_act = pc_act_next)) { + pc_act_next = pc_act->next; + if (!BLI_findstring( + ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) { + BLI_remlink(lb, pc_act); + MEM_freeN(pc_act); + } + } + } +} + +void UI_panel_category_active_set(ARegion *region, const char *idname) +{ + ui_panel_category_active_set(region, idname, false); +} + +void UI_panel_category_active_set_default(ARegion *region, const char *idname) +{ + if (!UI_panel_category_active_find(region, idname)) { + ui_panel_category_active_set(region, idname, true); + } +} + +const char *UI_panel_category_active_get(ARegion *region, bool set_fallback) +{ + LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) { + if (UI_panel_category_find(region, pc_act->idname)) { + return pc_act->idname; + } + } + + if (set_fallback) { + PanelCategoryDyn *pc_dyn = static_cast(region->panels_category.first); + if (pc_dyn) { + ui_panel_category_active_set(region, pc_dyn->idname, true); + return pc_dyn->idname; + } + } + + return nullptr; +} + +static PanelCategoryDyn *panel_categories_find_mouse_over(ARegion *region, const wmEvent *event) +{ + LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) { + if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) { + return ptd; + } + } + + return nullptr; +} + +void UI_panel_category_add(ARegion *region, const char *name) +{ + PanelCategoryDyn *pc_dyn = MEM_cnew(__func__); + BLI_addtail(®ion->panels_category, pc_dyn); + + BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname)); + + /* 'pc_dyn->rect' must be set on draw. */ +} + +void UI_panel_category_clear_all(ARegion *region) +{ + BLI_freelistN(®ion->panels_category); +} + +static int ui_handle_panel_category_cycling(const wmEvent *event, + ARegion *region, + const uiBut *active_but) +{ + const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE); + const bool inside_tabregion = + ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ? + (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) : + (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin)); + + /* If mouse is inside non-tab region, ctrl key is required. */ + if (is_mousewheel && (event->modifier & KM_CTRL) == 0 && !inside_tabregion) { + return WM_UI_HANDLER_CONTINUE; + } + + if (active_but && ui_but_supports_cycling(active_but)) { + /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */ + } + else { + const char *category = UI_panel_category_active_get(region, false); + if (LIKELY(category)) { + PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category); + if (LIKELY(pc_dyn)) { + if (is_mousewheel) { + /* We can probably get rid of this and only allow Ctrl-Tabbing. */ + pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev; + } + else { + const bool backwards = event->modifier & KM_SHIFT; + pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next; + if (!pc_dyn) { + /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */ + pc_dyn = backwards ? static_cast(region->panels_category.last) : + static_cast(region->panels_category.first); + } + } + + if (pc_dyn) { + /* Intentionally don't reset scroll in this case, + * allowing for quick browsing between tabs. */ + UI_panel_category_active_set(region, pc_dyn->idname); + ED_region_tag_redraw(region); + } + } + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +int ui_handler_panel_region(bContext *C, + const wmEvent *event, + ARegion *region, + const uiBut *active_but) +{ + /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ + if (ISMOUSE_MOTION(event->type)) { + return WM_UI_HANDLER_CONTINUE; + } + + /* We only use KM_PRESS events in this function, so it's simpler to return early. */ + if (event->val != KM_PRESS) { + return WM_UI_HANDLER_CONTINUE; + } + + /* Scroll-bars can overlap panels now, they have handling priority. */ + if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->xy)) { + return WM_UI_HANDLER_CONTINUE; + } + + int retval = WM_UI_HANDLER_CONTINUE; + + /* Handle category tabs. */ + if (UI_panel_category_is_visible(region)) { + if (event->type == LEFTMOUSE) { + PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event); + if (pc_dyn) { + UI_panel_category_active_set(region, pc_dyn->idname); + ED_region_tag_redraw(region); + + /* Reset scroll to the top (T38348). */ + UI_view2d_offset(®ion->v2d, -1.0f, 1.0f); + + retval = WM_UI_HANDLER_BREAK; + } + } + else if (((event->type == EVT_TABKEY) && (event->modifier & KM_CTRL)) || + ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { + /* Cycle tabs. */ + retval = ui_handle_panel_category_cycling(event, region, active_but); + } + } + + if (retval == WM_UI_HANDLER_BREAK) { + return retval; + } + + const bool region_has_active_button = (ui_region_find_active_but(region) != nullptr); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + Panel *panel = block->panel; + if (panel == nullptr || panel->type == nullptr) { + continue; + } + /* We can't expand or collapse panels without headers, they would disappear. */ + if (panel->type->flag & PANEL_TYPE_NO_HEADER) { + continue; + } + + int mx = event->xy[0]; + int my = event->xy[1]; + ui_window_to_block(region, block, &mx, &my); + + const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); + + if (mouse_state != PANEL_MOUSE_OUTSIDE) { + /* Mark panels that have been interacted with so their expansion + * doesn't reset when property search finishes. */ + SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED); + panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; + + /* The panel collapse / expand key "A" is special as it takes priority over + * active button handling. */ + if (event->type == EVT_AKEY && + ((event->modifier & (KM_SHIFT | KM_CTRL | KM_ALT | KM_OSKEY)) == 0)) { + retval = WM_UI_HANDLER_BREAK; + ui_handle_panel_header( + C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); + break; + } + } + + /* Don't do any other panel handling with an active button. */ + if (region_has_active_button) { + continue; + } + + if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) { + /* All mouse clicks inside panel headers should return in break. */ + if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) { + retval = WM_UI_HANDLER_BREAK; + ui_handle_panel_header( + C, block, mx, event->type, event->modifier & KM_CTRL, event->modifier & KM_SHIFT); + } + else if (event->type == RIGHTMOUSE) { + retval = WM_UI_HANDLER_BREAK; + ui_popup_context_menu_for_panel(C, region, block->panel); + } + break; + } + } + + return retval; +} + +static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) +{ + panel->runtime.custom_data_ptr = custom_data; + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + ui_panel_custom_data_set_recursive(child_panel, custom_data); + } +} + +void UI_panel_context_pointer_set(Panel *panel, const char *name, PointerRNA *ptr) +{ + uiLayoutSetContextPointer(panel->layout, name, ptr); + panel->runtime.context = uiLayoutGetContextStore(panel->layout); +} + +void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) +{ + BLI_assert(panel->type != nullptr); + + /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ + if (panel->runtime.custom_data_ptr != nullptr) { + MEM_freeN(panel->runtime.custom_data_ptr); + } + + ui_panel_custom_data_set_recursive(panel, custom_data); +} + +PointerRNA *UI_panel_custom_data_get(const Panel *panel) +{ + return panel->runtime.custom_data_ptr; +} + +PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + Panel *panel = block->panel; + if (panel == nullptr) { + continue; + } + + int mx = event->xy[0]; + int my = event->xy[1]; + ui_window_to_block(region, block, &mx, &my); + const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); + if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { + return UI_panel_custom_data_get(panel); + } + } + + return nullptr; +} + +bool UI_panel_can_be_pinned(const Panel *panel) +{ + return (panel->type->parent == nullptr) && !(panel->type->flag & PANEL_TYPE_INSTANCED); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Window Level Modal Panel Interaction + * \{ */ + +/* NOTE: this is modal handler and should not swallow events for animation. */ +static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) +{ + Panel *panel = static_cast(userdata); + uiHandlePanelData *data = static_cast(panel->activedata); + + /* Verify if we can stop. */ + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + else if (event->type == MOUSEMOVE) { + if (data->state == PANEL_STATE_DRAG) { + ui_do_drag(C, event, panel); + } + } + else if (event->type == TIMER && event->customdata == data->animtimer) { + if (data->state == PANEL_STATE_ANIMATION) { + ui_do_animate(C, panel); + } + else if (data->state == PANEL_STATE_DRAG) { + ui_do_drag(C, event, panel); + } + } + + data = static_cast(panel->activedata); + + if (data && data->state == PANEL_STATE_ANIMATION) { + return WM_UI_HANDLER_CONTINUE; + } + return WM_UI_HANDLER_BREAK; +} + +static void ui_handler_remove_panel(bContext *C, void *userdata) +{ + Panel *panel = static_cast(userdata); + + panel_activate_state(C, panel, PANEL_STATE_EXIT); +} + +static void panel_handle_data_ensure(const bContext *C, + wmWindow *win, + const ARegion *region, + Panel *panel, + const uiHandlePanelState state) +{ + if (panel->activedata == nullptr) { + panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__); + WM_event_add_ui_handler( + C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0); + } + + uiHandlePanelData *data = static_cast(panel->activedata); + + data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); + + data->state = state; + data->startx = win->eventstate->xy[0]; + data->starty = win->eventstate->xy[1]; + data->startofsx = panel->ofsx; + data->startofsy = panel->ofsy; + data->start_cur_xmin = region->v2d.cur.xmin; + data->start_cur_ymin = region->v2d.cur.ymin; + data->starttime = PIL_check_seconds_timer(); +} + +/** + * \note "select" and "drag drop" flags: First, the panel is "picked up" and both flags are set. + * Then when the mouse releases and the panel starts animating to its aligned position, PNL_SELECT + * is unset. When the animation finishes, PANEL_IS_DRAG_DROP is cleared. + */ +static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state) +{ + uiHandlePanelData *data = static_cast(panel->activedata); + wmWindow *win = CTX_wm_window(C); + ARegion *region = CTX_wm_region(C); + + if (data != nullptr && data->state == state) { + return; + } + + if (state == PANEL_STATE_DRAG) { + panel_custom_data_active_set(panel); + + panel_set_flag_recursive(panel, PNL_SELECT, true); + panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, true); + + panel_handle_data_ensure(C, win, region, panel, state); + + /* 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); + } + else if (state == PANEL_STATE_ANIMATION) { + panel_set_flag_recursive(panel, PNL_SELECT, false); + + panel_handle_data_ensure(C, win, region, panel, state); + } + else if (state == PANEL_STATE_EXIT) { + panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, false); + + BLI_assert(data != nullptr); + + if (data->animtimer) { + WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer); + data->animtimer = nullptr; + } + + MEM_freeN(data); + panel->activedata = nullptr; + + WM_event_remove_ui_handler( + &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false); + } + + ED_region_tag_redraw(region); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_template_search_operator.c b/source/blender/editors/interface/interface_template_search_operator.c deleted file mode 100644 index 41de2ab197d..00000000000 --- a/source/blender/editors/interface/interface_template_search_operator.c +++ /dev/null @@ -1,129 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * Search available operators by scanning all and checking their poll function. - * accessed via the #WM_OT_search_operator operator. - */ - -#include - -#include "DNA_object_types.h" -#include "DNA_scene_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, NULL); - } -} - -static void operator_search_update_fn(const bContext *C, - void *UNUSED(arg), - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - GHashIterator iter; - - /* Prepare BLI_string_all_words_matched. */ - const size_t str_len = strlen(str); - const int words_max = BLI_string_max_possible_word_count(str_len); - 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); - - if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { - continue; - } - - if (BLI_string_all_words_matched(ot_ui_name, str, words, words_len)) { - if (WM_operator_poll((bContext *)C, ot)) { - char name[256]; - const 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, 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, - NULL, - 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_template_search_operator.cc b/source/blender/editors/interface/interface_template_search_operator.cc new file mode 100644 index 00000000000..0d0a5f01744 --- /dev/null +++ b/source/blender/editors/interface/interface_template_search_operator.cc @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Search available operators by scanning all and checking their poll function. + * accessed via the #WM_OT_search_operator operator. + */ + +#include + +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_texture_types.h" + +#include "BLI_array.hh" +#include "BLI_ghash.h" +#include "BLI_math_vec_types.hh" +#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 = static_cast(arg2); + + if (ot) { + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, nullptr, nullptr); + } +} + +static void operator_search_update_fn(const bContext *C, + void *UNUSED(arg), + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + GHashIterator iter; + + /* Prepare BLI_string_all_words_matched. */ + const size_t str_len = strlen(str); + const int words_max = BLI_string_max_possible_word_count(str_len); + blender::Array words(words_max); + const int words_len = BLI_string_find_split_words( + str, str_len, ' ', (int(*)[2])words.data(), words_max); + + for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); + BLI_ghashIterator_step(&iter)) { + wmOperatorType *ot = static_cast(BLI_ghashIterator_getValue(&iter)); + const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); + + if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { + continue; + } + + if (BLI_string_all_words_matched(ot_ui_name, str, (int(*)[2])words.data(), words_len)) { + if (WM_operator_poll((bContext *)C, ot)) { + char name[256]; + const 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, + nullptr, + true, + &name[len + 1], + sizeof(name) - len - 1)) { + name[len] = UI_SEP_CHAR; + } + } + + if (!UI_search_item_add(items, name, ot, ICON_NONE, 0, 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, + nullptr, + false, + nullptr, + operator_search_exec_fn, + nullptr); +} + +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_undo.c b/source/blender/editors/interface/interface_undo.c deleted file mode 100644 index e998eb6dbed..00000000000 --- a/source/blender/editors/interface/interface_undo.c +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2020 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Undo stack to use for UI widgets that manage their own editing state. - */ - -#include - -#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); - } - return ui_textedit_redo_impl(stack, r_cursor_index); -} - -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); -} - -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_undo.cc b/source/blender/editors/interface/interface_undo.cc new file mode 100644 index 00000000000..ec54b695cf7 --- /dev/null +++ b/source/blender/editors/interface/interface_undo.cc @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2020 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Undo stack to use for UI widgets that manage their own editing state. + */ + +#include + +#include "BLI_listbase.h" + +#include "DNA_listBase.h" + +#include "MEM_guardedalloc.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Text Field Undo Stack + * \{ */ + +struct uiUndoStack_Text_State { + struct uiUndoStack_Text_State *next, *prev; + int cursor_index; + char text[0]; +}; + +struct uiUndoStack_Text { + ListBase states; + uiUndoStack_Text_State *current; +}; + +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 == nullptr) { + return nullptr; + } + + /* Travel backwards in the stack and copy information to the caller. */ + if (stack->current->prev != nullptr) { + stack->current = stack->current->prev; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return nullptr; +} + +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 == nullptr) { + return nullptr; + } + + /* 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 nullptr; +} + +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); + } + return ui_textedit_redo_impl(stack, r_cursor_index); +} + +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 != nullptr) { + 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 = static_cast( + 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); +} + +uiUndoStack_Text *ui_textedit_undo_stack_create(void) +{ + uiUndoStack_Text *stack = MEM_new(__func__); + stack->current = nullptr; + 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/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c index 9449cc6eb8d..3e5ad9bdc2d 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.c +++ b/source/blender/editors/sculpt_paint/paint_image_proj.c @@ -1223,12 +1223,12 @@ static VertSeam *find_adjacent_seam(const ProjPaintState *ps, /* Circulate through the (sorted) vert seam array, in the direction of the seam normal, * until we find the first opposing seam, matching in UV space. */ if (seam->normal_cw) { - LISTBASE_CIRCULAR_BACKWARD_BEGIN (vert_seams, adjacent, seam) { + LISTBASE_CIRCULAR_BACKWARD_BEGIN (VertSeam *, vert_seams, adjacent, seam) { if ((adjacent->normal_cw != seam->normal_cw) && cmp_uv(adjacent->uv, seam->uv)) { break; } } - LISTBASE_CIRCULAR_BACKWARD_END(vert_seams, adjacent, seam); + LISTBASE_CIRCULAR_BACKWARD_END(VertSeam *, vert_seams, adjacent, seam); } else { LISTBASE_CIRCULAR_FORWARD_BEGIN (vert_seams, adjacent, seam) { -- cgit v1.2.3