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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/interface/interface_utils.cc')
-rw-r--r--source/blender/editors/interface/interface_utils.cc1025
1 files changed, 1025 insertions, 0 deletions
diff --git a/source/blender/editors/interface/interface_utils.cc b/source/blender/editors/interface/interface_utils.cc
new file mode 100644
index 00000000000..b796f07c24b
--- /dev/null
+++ b/source/blender/editors/interface/interface_utils.cc
@@ -0,0 +1,1025 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright 2009 Blender Foundation. All rights reserved. */
+
+/** \file
+ * \ingroup edinterface
+ */
+
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+#include "DNA_object_types.h"
+#include "DNA_screen_types.h"
+
+#include "ED_screen.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_string_search.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_context.h"
+#include "BKE_lib_id.h"
+#include "BKE_report.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+
+#include "UI_interface.h"
+#include "UI_interface_icons.h"
+#include "UI_resources.h"
+#include "UI_view2d.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "interface_intern.h"
+
+/*************************** RNA Utilities ******************************/
+
+uiBut *uiDefAutoButR(uiBlock *block,
+ PointerRNA *ptr,
+ PropertyRNA *prop,
+ int index,
+ const char *name,
+ int icon,
+ int x,
+ int y,
+ int width,
+ int height)
+{
+ uiBut *but = nullptr;
+
+ switch (RNA_property_type(prop)) {
+ case PROP_BOOLEAN: {
+ if (RNA_property_array_check(prop) && index == -1) {
+ return nullptr;
+ }
+
+ if (icon && name && name[0] == '\0') {
+ but = uiDefIconButR_prop(block,
+ UI_BTYPE_ICON_TOGGLE,
+ 0,
+ icon,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else if (icon) {
+ but = uiDefIconTextButR_prop(block,
+ UI_BTYPE_ICON_TOGGLE,
+ 0,
+ icon,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else {
+ but = uiDefButR_prop(block,
+ UI_BTYPE_CHECKBOX,
+ 0,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ break;
+ }
+ case PROP_INT:
+ case PROP_FLOAT: {
+ if (RNA_property_array_check(prop) && index == -1) {
+ if (ELEM(RNA_property_subtype(prop), PROP_COLOR, PROP_COLOR_GAMMA)) {
+ but = uiDefButR_prop(block,
+ UI_BTYPE_COLOR,
+ 0,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ -1,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+ }
+ else {
+ return nullptr;
+ }
+ }
+ else if (RNA_property_subtype(prop) == PROP_PERCENTAGE ||
+ RNA_property_subtype(prop) == PROP_FACTOR) {
+ but = uiDefButR_prop(block,
+ UI_BTYPE_NUM_SLIDER,
+ 0,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else {
+ but = uiDefButR_prop(block,
+ UI_BTYPE_NUM,
+ 0,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+ }
+
+ if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
+ UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE);
+ }
+ break;
+ }
+ case PROP_ENUM:
+ if (icon && name && name[0] == '\0') {
+ but = uiDefIconButR_prop(block,
+ UI_BTYPE_MENU,
+ 0,
+ icon,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else if (icon) {
+ but = uiDefIconTextButR_prop(block,
+ UI_BTYPE_MENU,
+ 0,
+ icon,
+ nullptr,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else {
+ but = uiDefButR_prop(block,
+ UI_BTYPE_MENU,
+ 0,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ break;
+ case PROP_STRING:
+ if (icon && name && name[0] == '\0') {
+ but = uiDefIconButR_prop(block,
+ UI_BTYPE_TEXT,
+ 0,
+ icon,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else if (icon) {
+ but = uiDefIconTextButR_prop(block,
+ UI_BTYPE_TEXT,
+ 0,
+ icon,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+ else {
+ but = uiDefButR_prop(block,
+ UI_BTYPE_TEXT,
+ 0,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ }
+
+ if (RNA_property_flag(prop) & PROP_TEXTEDIT_UPDATE) {
+ /* TEXTEDIT_UPDATE is usually used for search buttons. For these we also want
+ * the 'x' icon to clear search string, so setting VALUE_CLEAR flag, too. */
+ UI_but_flag_enable(but, UI_BUT_TEXTEDIT_UPDATE | UI_BUT_VALUE_CLEAR);
+ }
+ break;
+ case PROP_POINTER: {
+ if (icon == 0) {
+ const PointerRNA pptr = RNA_property_pointer_get(ptr, prop);
+ icon = RNA_struct_ui_icon(pptr.type ? pptr.type : RNA_property_pointer_type(ptr, prop));
+ }
+ if (icon == ICON_DOT) {
+ icon = 0;
+ }
+
+ but = uiDefIconTextButR_prop(block,
+ UI_BTYPE_SEARCH_MENU,
+ 0,
+ icon,
+ name,
+ x,
+ y,
+ width,
+ height,
+ ptr,
+ prop,
+ index,
+ 0,
+ 0,
+ -1,
+ -1,
+ nullptr);
+ ui_but_add_search(but, ptr, prop, nullptr, nullptr);
+ break;
+ }
+ case PROP_COLLECTION: {
+ char text[256];
+ BLI_snprintf(
+ text, sizeof(text), IFACE_("%d items"), RNA_property_collection_length(ptr, prop));
+ but = uiDefBut(
+ block, UI_BTYPE_LABEL, 0, text, x, y, width, height, nullptr, 0, 0, 0, 0, nullptr);
+ UI_but_flag_enable(but, UI_BUT_DISABLED);
+ break;
+ }
+ default:
+ but = nullptr;
+ break;
+ }
+
+ return but;
+}
+
+void uiDefAutoButsArrayR(uiBlock *block,
+ PointerRNA *ptr,
+ PropertyRNA *prop,
+ const int icon,
+ const int x,
+ const int y,
+ const int tot_width,
+ const int height)
+{
+ const int len = RNA_property_array_length(ptr, prop);
+ if (len == 0) {
+ return;
+ }
+
+ const int item_width = tot_width / len;
+
+ UI_block_align_begin(block);
+ for (int i = 0; i < len; i++) {
+ uiDefAutoButR(block, ptr, prop, i, "", icon, x + i * item_width, y, item_width, height);
+ }
+ UI_block_align_end(block);
+}
+
+eAutoPropButsReturn uiDefAutoButsRNA(uiLayout *layout,
+ PointerRNA *ptr,
+ bool (*check_prop)(PointerRNA *ptr,
+ PropertyRNA *prop,
+ void *user_data),
+ void *user_data,
+ PropertyRNA *prop_activate_init,
+ const eButLabelAlign label_align,
+ const bool compact)
+{
+ eAutoPropButsReturn return_info = UI_PROP_BUTS_NONE_ADDED;
+ uiLayout *col;
+ const char *name;
+
+ RNA_STRUCT_BEGIN (ptr, prop) {
+ const int flag = RNA_property_flag(prop);
+
+ if (flag & PROP_HIDDEN) {
+ continue;
+ }
+ if (check_prop && check_prop(ptr, prop, user_data) == 0) {
+ return_info |= UI_PROP_BUTS_ANY_FAILED_CHECK;
+ continue;
+ }
+
+ const PropertyType type = RNA_property_type(prop);
+ switch (label_align) {
+ case UI_BUT_LABEL_ALIGN_COLUMN:
+ case UI_BUT_LABEL_ALIGN_SPLIT_COLUMN: {
+ const bool is_boolean = (type == PROP_BOOLEAN && !RNA_property_array_check(prop));
+
+ name = RNA_property_ui_name(prop);
+
+ if (label_align == UI_BUT_LABEL_ALIGN_COLUMN) {
+ col = uiLayoutColumn(layout, true);
+
+ if (!is_boolean) {
+ uiItemL(col, name, ICON_NONE);
+ }
+ }
+ else {
+ BLI_assert(label_align == UI_BUT_LABEL_ALIGN_SPLIT_COLUMN);
+ col = uiLayoutColumn(layout, true);
+ /* Let uiItemFullR() create the split layout. */
+ uiLayoutSetPropSep(col, true);
+ }
+
+ break;
+ }
+ case UI_BUT_LABEL_ALIGN_NONE:
+ default:
+ col = layout;
+ name = nullptr; /* no smart label alignment, show default name with button */
+ break;
+ }
+
+ /* Only buttons that can be edited as text. */
+ const bool use_activate_init = ((prop == prop_activate_init) &&
+ (ELEM(type, PROP_STRING, PROP_INT, PROP_FLOAT)));
+
+ if (use_activate_init) {
+ uiLayoutSetActivateInit(col, true);
+ }
+
+ uiItemFullR(col, ptr, prop, -1, 0, compact ? UI_ITEM_R_COMPACT : 0, name, ICON_NONE);
+ return_info &= ~UI_PROP_BUTS_NONE_ADDED;
+
+ if (use_activate_init) {
+ uiLayoutSetActivateInit(col, false);
+ }
+ }
+ RNA_STRUCT_END;
+
+ return return_info;
+}
+
+/* *** RNA collection search menu *** */
+
+struct CollItemSearch {
+ struct CollItemSearch *next, *prev;
+ void *data;
+ char *name;
+ int index;
+ int iconid;
+ bool is_id;
+ int name_prefix_offset;
+ uint has_sep_char : 1;
+};
+
+static bool add_collection_search_item(CollItemSearch *cis,
+ const bool requires_exact_data_name,
+ const bool has_id_icon,
+ uiSearchItems *items)
+{
+ char name_buf[UI_MAX_DRAW_STR];
+
+ /* If no item has an own icon to display, libraries can use the library icons rather than the
+ * name prefix for showing the library status. */
+ int name_prefix_offset = cis->name_prefix_offset;
+ if (!has_id_icon && cis->is_id && !requires_exact_data_name) {
+ cis->iconid = UI_icon_from_library(static_cast<const ID *>(cis->data));
+ /* No need to re-allocate, string should be shorter than before (lib status prefix is
+ * removed). */
+ BKE_id_full_name_ui_prefix_get(
+ name_buf, static_cast<const ID *>(cis->data), false, UI_SEP_CHAR, &name_prefix_offset);
+ BLI_assert(strlen(name_buf) <= MEM_allocN_len(cis->name));
+ strcpy(cis->name, name_buf);
+ }
+
+ return UI_search_item_add(items,
+ cis->name,
+ cis->data,
+ cis->iconid,
+ cis->has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0,
+ name_prefix_offset);
+}
+
+void ui_rna_collection_search_update_fn(
+ const bContext *C, void *arg, const char *str, uiSearchItems *items, const bool is_first)
+{
+ uiRNACollectionSearch *data = static_cast<uiRNACollectionSearch *>(arg);
+ const int flag = RNA_property_flag(data->target_prop);
+ ListBase *items_list = MEM_cnew<ListBase>("items_list");
+ const bool is_ptr_target = (RNA_property_type(data->target_prop) == PROP_POINTER);
+ /* For non-pointer properties, UI code acts entirely based on the item's name. So the name has to
+ * match the RNA name exactly. So only for pointer properties, the name can be modified to add
+ * further UI hints. */
+ const bool requires_exact_data_name = !is_ptr_target;
+ const bool skip_filter = is_first;
+ char name_buf[UI_MAX_DRAW_STR];
+ char *name;
+ bool has_id_icon = false;
+
+ StringSearch *search = skip_filter ? nullptr : BLI_string_search_new();
+
+ /* build a temporary list of relevant items first */
+ int item_index = 0;
+ RNA_PROP_BEGIN (&data->search_ptr, itemptr, data->search_prop) {
+
+ if (flag & PROP_ID_SELF_CHECK) {
+ if (itemptr.data == data->target_ptr.owner_id) {
+ continue;
+ }
+ }
+
+ /* use filter */
+ if (is_ptr_target) {
+ if (RNA_property_pointer_poll(&data->target_ptr, data->target_prop, &itemptr) == 0) {
+ continue;
+ }
+ }
+
+ int name_prefix_offset = 0;
+ int iconid = ICON_NONE;
+ bool has_sep_char = false;
+ const bool is_id = itemptr.type && RNA_struct_is_ID(itemptr.type);
+
+ if (is_id) {
+ iconid = ui_id_icon_get(C, static_cast<ID *>(itemptr.data), false);
+ if (!ELEM(iconid, 0, ICON_BLANK1)) {
+ has_id_icon = true;
+ }
+
+ if (requires_exact_data_name) {
+ name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr);
+ }
+ else {
+ const ID *id = static_cast<ID *>(itemptr.data);
+ BKE_id_full_name_ui_prefix_get(name_buf, id, true, UI_SEP_CHAR, &name_prefix_offset);
+ BLI_STATIC_ASSERT(sizeof(name_buf) >= MAX_ID_FULL_NAME_UI,
+ "Name string buffer should be big enough to hold full UI ID name");
+ name = name_buf;
+ has_sep_char = ID_IS_LINKED(id);
+ }
+ }
+ else {
+ name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), nullptr);
+ }
+
+ if (name) {
+ CollItemSearch *cis = MEM_cnew<CollItemSearch>(__func__);
+ cis->data = itemptr.data;
+ cis->name = BLI_strdup(name);
+ cis->index = item_index;
+ cis->iconid = iconid;
+ cis->is_id = is_id;
+ cis->name_prefix_offset = name_prefix_offset;
+ cis->has_sep_char = has_sep_char;
+ if (!skip_filter) {
+ BLI_string_search_add(search, name, cis, 0);
+ }
+ BLI_addtail(items_list, cis);
+ if (name != name_buf) {
+ MEM_freeN(name);
+ }
+ }
+
+ item_index++;
+ }
+ RNA_PROP_END;
+
+ if (skip_filter) {
+ LISTBASE_FOREACH (CollItemSearch *, cis, items_list) {
+ if (!add_collection_search_item(cis, requires_exact_data_name, has_id_icon, items)) {
+ break;
+ }
+ }
+ }
+ else {
+ CollItemSearch **filtered_items;
+ int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items);
+
+ for (int i = 0; i < filtered_amount; i++) {
+ CollItemSearch *cis = filtered_items[i];
+ if (!add_collection_search_item(cis, requires_exact_data_name, has_id_icon, items)) {
+ break;
+ }
+ }
+
+ MEM_freeN(filtered_items);
+ BLI_string_search_free(search);
+ }
+
+ LISTBASE_FOREACH (CollItemSearch *, cis, items_list) {
+ MEM_freeN(cis->name);
+ }
+ BLI_freelistN(items_list);
+ MEM_freeN(items_list);
+}
+
+int UI_icon_from_id(const ID *id)
+{
+ if (id == nullptr) {
+ return ICON_NONE;
+ }
+
+ /* exception for objects */
+ if (GS(id->name) == ID_OB) {
+ Object *ob = (Object *)id;
+
+ if (ob->type == OB_EMPTY) {
+ return ICON_EMPTY_DATA;
+ }
+ return UI_icon_from_id(static_cast<const ID *>(ob->data));
+ }
+
+ /* otherwise get it through RNA, creating the pointer
+ * will set the right type, also with subclassing */
+ PointerRNA ptr;
+ RNA_id_pointer_create((ID *)id, &ptr);
+
+ return (ptr.type) ? RNA_struct_ui_icon(ptr.type) : ICON_NONE;
+}
+
+int UI_icon_from_report_type(int type)
+{
+ if (type & RPT_ERROR_ALL) {
+ return ICON_CANCEL;
+ }
+ if (type & RPT_WARNING_ALL) {
+ return ICON_ERROR;
+ }
+ if (type & RPT_INFO_ALL) {
+ return ICON_INFO;
+ }
+ if (type & RPT_DEBUG_ALL) {
+ return ICON_SYSTEM;
+ }
+ if (type & RPT_PROPERTY) {
+ return ICON_OPTIONS;
+ }
+ if (type & RPT_OPERATOR) {
+ return ICON_CHECKMARK;
+ }
+ return ICON_INFO;
+}
+
+int UI_icon_colorid_from_report_type(int type)
+{
+ if (type & RPT_ERROR_ALL) {
+ return TH_INFO_ERROR;
+ }
+ if (type & RPT_WARNING_ALL) {
+ return TH_INFO_WARNING;
+ }
+ if (type & RPT_INFO_ALL) {
+ return TH_INFO_INFO;
+ }
+ if (type & RPT_DEBUG_ALL) {
+ return TH_INFO_DEBUG;
+ }
+ if (type & RPT_PROPERTY) {
+ return TH_INFO_PROPERTY;
+ }
+ if (type & RPT_OPERATOR) {
+ return TH_INFO_OPERATOR;
+ }
+ return TH_INFO_WARNING;
+}
+
+int UI_text_colorid_from_report_type(int type)
+{
+ if (type & RPT_ERROR_ALL) {
+ return TH_INFO_ERROR_TEXT;
+ }
+ if (type & RPT_WARNING_ALL) {
+ return TH_INFO_WARNING_TEXT;
+ }
+ if (type & RPT_INFO_ALL) {
+ return TH_INFO_INFO_TEXT;
+ }
+ if (type & RPT_DEBUG_ALL) {
+ return TH_INFO_DEBUG_TEXT;
+ }
+ if (type & RPT_PROPERTY) {
+ return TH_INFO_PROPERTY_TEXT;
+ }
+ if (type & RPT_OPERATOR) {
+ return TH_INFO_OPERATOR_TEXT;
+ }
+ return TH_INFO_WARNING_TEXT;
+}
+
+/********************************** Misc **************************************/
+
+int UI_calc_float_precision(int prec, double value)
+{
+ static const double pow10_neg[UI_PRECISION_FLOAT_MAX + 1] = {
+ 1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6};
+ static const double max_pow = 10000000.0; /* pow(10, UI_PRECISION_FLOAT_MAX) */
+
+ BLI_assert(prec <= UI_PRECISION_FLOAT_MAX);
+ BLI_assert(fabs(pow10_neg[prec] - pow(10, -prec)) < 1e-16);
+
+ /* Check on the number of decimal places need to display the number,
+ * this is so 0.00001 is not displayed as 0.00,
+ * _but_, this is only for small values as 10.0001 will not get the same treatment.
+ */
+ value = fabs(value);
+ if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) {
+ int value_i = (int)((value * max_pow) + 0.5);
+ if (value_i != 0) {
+ const int prec_span = 3; /* show: 0.01001, 5 would allow 0.0100001 for eg. */
+ int test_prec;
+ int prec_min = -1;
+ int dec_flag = 0;
+ int i = UI_PRECISION_FLOAT_MAX;
+ while (i && value_i) {
+ if (value_i % 10) {
+ dec_flag |= 1 << i;
+ prec_min = i;
+ }
+ value_i /= 10;
+ i--;
+ }
+
+ /* even though its a small value, if the second last digit is not 0, use it */
+ test_prec = prec_min;
+
+ dec_flag = (dec_flag >> (prec_min + 1)) & ((1 << prec_span) - 1);
+
+ while (dec_flag) {
+ test_prec++;
+ dec_flag = dec_flag >> 1;
+ }
+
+ if (test_prec > prec) {
+ prec = test_prec;
+ }
+ }
+ }
+
+ CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX);
+
+ return prec;
+}
+
+bool UI_but_online_manual_id(const uiBut *but, char *r_str, size_t maxlength)
+{
+ if (but->rnapoin.owner_id && but->rnapoin.data && but->rnaprop) {
+ BLI_snprintf(r_str,
+ maxlength,
+ "%s.%s",
+ RNA_struct_identifier(but->rnapoin.type),
+ RNA_property_identifier(but->rnaprop));
+ return true;
+ }
+ if (but->optype) {
+ WM_operator_py_idname(r_str, but->optype->idname);
+ return true;
+ }
+
+ *r_str = '\0';
+ return false;
+}
+
+bool UI_but_online_manual_id_from_active(const bContext *C, char *r_str, size_t maxlength)
+{
+ uiBut *but = UI_context_active_but_get(C);
+
+ if (but) {
+ return UI_but_online_manual_id(but, r_str, maxlength);
+ }
+
+ *r_str = '\0';
+ return false;
+}
+
+/* -------------------------------------------------------------------- */
+
+static rctf ui_but_rect_to_view(const uiBut *but, const ARegion *region, const View2D *v2d)
+{
+ rctf region_rect;
+ ui_block_to_region_rctf(region, but->block, &region_rect, &but->rect);
+
+ rctf view_rect;
+ UI_view2d_region_to_view_rctf(v2d, &region_rect, &view_rect);
+
+ return view_rect;
+}
+
+/**
+ * To get a margin (typically wanted), add the margin to \a rect directly.
+ *
+ * Based on #file_ensure_inside_viewbounds(), could probably share code.
+ *
+ * \return true if anything changed.
+ */
+static bool ui_view2d_cur_ensure_rect_in_view(View2D *v2d, const rctf *rect)
+{
+ const float rect_width = BLI_rctf_size_x(rect);
+ const float rect_height = BLI_rctf_size_y(rect);
+
+ rctf *cur = &v2d->cur;
+ const float cur_width = BLI_rctf_size_x(cur);
+ const float cur_height = BLI_rctf_size_y(cur);
+
+ bool changed = false;
+
+ /* Snap to bottom edge. Also use if rect is higher than view bounds (could be a parameter). */
+ if ((cur->ymin > rect->ymin) || (rect_height > cur_height)) {
+ cur->ymin = rect->ymin;
+ cur->ymax = cur->ymin + cur_height;
+ changed = true;
+ }
+ /* Snap to upper edge. */
+ else if (cur->ymax < rect->ymax) {
+ cur->ymax = rect->ymax;
+ cur->ymin = cur->ymax - cur_height;
+ changed = true;
+ }
+ /* Snap to left edge. Also use if rect is wider than view bounds. */
+ else if ((cur->xmin > rect->xmin) || (rect_width > cur_width)) {
+ cur->xmin = rect->xmin;
+ cur->xmax = cur->xmin + cur_width;
+ changed = true;
+ }
+ /* Snap to right edge. */
+ else if (cur->xmax < rect->xmax) {
+ cur->xmax = rect->xmax;
+ cur->xmin = cur->xmax - cur_width;
+ changed = true;
+ }
+ else {
+ BLI_assert(BLI_rctf_inside_rctf(cur, rect));
+ }
+
+ return changed;
+}
+
+void UI_but_ensure_in_view(const bContext *C, ARegion *region, const uiBut *but)
+{
+ View2D *v2d = &region->v2d;
+ /* Uninitialized view or region that doesn't use View2D. */
+ if ((v2d->flag & V2D_IS_INIT) == 0) {
+ return;
+ }
+
+ rctf rect = ui_but_rect_to_view(but, region, v2d);
+
+ const int margin = UI_UNIT_X * 0.5f;
+ BLI_rctf_pad(&rect, margin, margin);
+
+ const bool changed = ui_view2d_cur_ensure_rect_in_view(v2d, &rect);
+ if (changed) {
+ UI_view2d_curRect_changed(C, v2d);
+ ED_region_tag_redraw_no_rebuild(region);
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Button Store
+ *
+ * Modal Button Store API.
+ *
+ * Store for modal operators & handlers to register button pointers
+ * which are maintained while drawing or nullptr when removed.
+ *
+ * This is needed since button pointers are continuously freed and re-allocated.
+ *
+ * \{ */
+
+struct uiButStore {
+ struct uiButStore *next, *prev;
+ uiBlock *block;
+ ListBase items;
+};
+
+struct uiButStoreElem {
+ struct uiButStoreElem *next, *prev;
+ uiBut **but_p;
+};
+
+uiButStore *UI_butstore_create(uiBlock *block)
+{
+ uiButStore *bs_handle = MEM_cnew<uiButStore>(__func__);
+
+ bs_handle->block = block;
+ BLI_addtail(&block->butstore, bs_handle);
+
+ return bs_handle;
+}
+
+void UI_butstore_free(uiBlock *block, uiButStore *bs_handle)
+{
+ /* Workaround for button store being moved into new block,
+ * which then can't use the previous buttons state
+ * ('ui_but_update_from_old_block' fails to find a match),
+ * keeping the active button in the old block holding a reference
+ * to the button-state in the new block: see T49034.
+ *
+ * Ideally we would manage moving the 'uiButStore', keeping a correct state.
+ * All things considered this is the most straightforward fix - Campbell.
+ */
+ if (block != bs_handle->block && bs_handle->block != nullptr) {
+ block = bs_handle->block;
+ }
+
+ BLI_freelistN(&bs_handle->items);
+ BLI_assert(BLI_findindex(&block->butstore, bs_handle) != -1);
+ BLI_remlink(&block->butstore, bs_handle);
+
+ MEM_freeN(bs_handle);
+}
+
+bool UI_butstore_is_valid(uiButStore *bs)
+{
+ return (bs->block != nullptr);
+}
+
+bool UI_butstore_is_registered(uiBlock *block, uiBut *but)
+{
+ LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
+ LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
+ if (*bs_elem->but_p == but) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void UI_butstore_register(uiButStore *bs_handle, uiBut **but_p)
+{
+ uiButStoreElem *bs_elem = MEM_cnew<uiButStoreElem>(__func__);
+ BLI_assert(*but_p);
+ bs_elem->but_p = but_p;
+
+ BLI_addtail(&bs_handle->items, bs_elem);
+}
+
+void UI_butstore_unregister(uiButStore *bs_handle, uiBut **but_p)
+{
+ LISTBASE_FOREACH_MUTABLE (uiButStoreElem *, bs_elem, &bs_handle->items) {
+ if (bs_elem->but_p == but_p) {
+ BLI_remlink(&bs_handle->items, bs_elem);
+ MEM_freeN(bs_elem);
+ }
+ }
+
+ BLI_assert(0);
+}
+
+bool UI_butstore_register_update(uiBlock *block, uiBut *but_dst, const uiBut *but_src)
+{
+ bool found = false;
+
+ LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
+ LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
+ if (*bs_elem->but_p == but_src) {
+ *bs_elem->but_p = but_dst;
+ found = true;
+ }
+ }
+ }
+
+ return found;
+}
+
+void UI_butstore_clear(uiBlock *block)
+{
+ LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
+ bs_handle->block = nullptr;
+ LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
+ *bs_elem->but_p = nullptr;
+ }
+ }
+}
+
+void UI_butstore_update(uiBlock *block)
+{
+ /* move this list to the new block */
+ if (block->oldblock) {
+ if (block->oldblock->butstore.first) {
+ BLI_movelisttolist(&block->butstore, &block->oldblock->butstore);
+ }
+ }
+
+ if (LIKELY(block->butstore.first == nullptr)) {
+ return;
+ }
+
+ /* warning, loop-in-loop, in practice we only store <10 buttons at a time,
+ * so this isn't going to be a problem, if that changes old-new mapping can be cached first */
+ LISTBASE_FOREACH (uiButStore *, bs_handle, &block->butstore) {
+ BLI_assert(ELEM(bs_handle->block, nullptr, block) ||
+ (block->oldblock && block->oldblock == bs_handle->block));
+
+ if (bs_handle->block == block->oldblock) {
+ bs_handle->block = block;
+
+ LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) {
+ if (*bs_elem->but_p) {
+ uiBut *but_new = ui_but_find_new(block, *bs_elem->but_p);
+
+ /* can be nullptr if the buttons removed,
+ * NOTE: we could allow passing in a callback when buttons are removed
+ * so the caller can cleanup */
+ *bs_elem->but_p = but_new;
+ }
+ }
+ }
+ }
+}
+
+/** \} */