From 4537eb0c3b1678a623510570fb7528bf53174b2f Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sat, 2 Apr 2022 16:17:48 -0500 Subject: Cleanup: Remove interface region files to C++ Moves all `interface_region*` files to C++ except for the tooptip region which is slightly more complicated. Also move a few other files as well. This helps to simplify and speed up code, especially through the use of better C++ data structures. This change builds on all platforms on the buildbot. --- source/blender/editors/include/UI_interface.h | 6 +- source/blender/editors/interface/CMakeLists.txt | 20 +- .../blender/editors/interface/interface_intern.h | 6 +- source/blender/editors/interface/interface_query.c | 809 --------------- .../blender/editors/interface/interface_query.cc | 806 +++++++++++++++ .../interface/interface_region_color_picker.c | 910 ----------------- .../interface/interface_region_color_picker.cc | 911 +++++++++++++++++ .../editors/interface/interface_region_hud.c | 387 -------- .../editors/interface/interface_region_hud.cc | 381 ++++++++ .../editors/interface/interface_region_menu_pie.c | 407 -------- .../editors/interface/interface_region_menu_pie.cc | 406 ++++++++ .../interface/interface_region_menu_popup.c | 670 ------------- .../interface/interface_region_menu_popup.cc | 675 +++++++++++++ .../editors/interface/interface_region_popover.c | 416 -------- .../editors/interface/interface_region_popover.cc | 421 ++++++++ .../editors/interface/interface_region_popup.c | 838 ---------------- .../editors/interface/interface_region_popup.cc | 836 ++++++++++++++++ .../blender/editors/interface/interface_regions.c | 50 - .../blender/editors/interface/interface_regions.cc | 50 + source/blender/editors/interface/interface_style.c | 512 ---------- .../blender/editors/interface/interface_style.cc | 508 ++++++++++ source/blender/editors/interface/interface_utils.c | 1014 ------------------- .../blender/editors/interface/interface_utils.cc | 1025 ++++++++++++++++++++ 23 files changed, 6036 insertions(+), 6028 deletions(-) delete mode 100644 source/blender/editors/interface/interface_query.c create mode 100644 source/blender/editors/interface/interface_query.cc delete mode 100644 source/blender/editors/interface/interface_region_color_picker.c create mode 100644 source/blender/editors/interface/interface_region_color_picker.cc delete mode 100644 source/blender/editors/interface/interface_region_hud.c create mode 100644 source/blender/editors/interface/interface_region_hud.cc delete mode 100644 source/blender/editors/interface/interface_region_menu_pie.c create mode 100644 source/blender/editors/interface/interface_region_menu_pie.cc delete mode 100644 source/blender/editors/interface/interface_region_menu_popup.c create mode 100644 source/blender/editors/interface/interface_region_menu_popup.cc delete mode 100644 source/blender/editors/interface/interface_region_popover.c create mode 100644 source/blender/editors/interface/interface_region_popover.cc delete mode 100644 source/blender/editors/interface/interface_region_popup.c create mode 100644 source/blender/editors/interface/interface_region_popup.cc delete mode 100644 source/blender/editors/interface/interface_regions.c create mode 100644 source/blender/editors/interface/interface_regions.cc delete mode 100644 source/blender/editors/interface/interface_style.c create mode 100644 source/blender/editors/interface/interface_style.cc delete mode 100644 source/blender/editors/interface/interface_utils.c create mode 100644 source/blender/editors/interface/interface_utils.cc diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 74797f91046..a4508cb5b6d 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -634,7 +634,7 @@ uiPopupMenu *UI_popup_menu_begin_ex(struct bContext *C, * Set the whole structure to work. */ void UI_popup_menu_end(struct bContext *C, struct uiPopupMenu *pup); -bool UI_popup_menu_end_or_cancel(struct bContext *C, struct uiPopupMenu *head); +bool UI_popup_menu_end_or_cancel(struct bContext *C, struct uiPopupMenu *pup); struct uiLayout *UI_popup_menu_layout(uiPopupMenu *pup); void UI_popup_menu_reports(struct bContext *C, struct ReportList *reports) ATTR_NONNULL(); @@ -1595,13 +1595,15 @@ typedef enum { } eButLabelAlign; /* Return info for uiDefAutoButsRNA */ -typedef enum { +typedef enum eAutoPropButsReturn { /* Returns when no buttons were added */ UI_PROP_BUTS_NONE_ADDED = 1 << 0, /* Returned when any property failed the custom check callback (check_prop) */ UI_PROP_BUTS_ANY_FAILED_CHECK = 1 << 1, } eAutoPropButsReturn; +ENUM_OPERATORS(eAutoPropButsReturn, UI_PROP_BUTS_ANY_FAILED_CHECK); + uiBut *uiDefAutoButR(uiBlock *block, struct PointerRNA *ptr, struct PropertyRNA *prop, diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 100be2c10c9..a1ee5c38838 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -46,17 +46,17 @@ set(SRC interface_layout.c interface_ops.c interface_panel.c - interface_query.c - interface_region_color_picker.c - interface_region_hud.c - interface_region_menu_pie.c - interface_region_menu_popup.c - interface_region_popover.c - interface_region_popup.c + interface_query.cc + interface_region_color_picker.cc + interface_region_hud.cc + interface_region_menu_pie.cc + interface_region_menu_popup.cc + interface_region_popover.cc + interface_region_popup.cc interface_region_search.cc interface_region_tooltip.c - interface_regions.c - interface_style.c + interface_regions.cc + interface_style.cc interface_template_asset_view.cc interface_template_attribute_search.cc interface_template_list.cc @@ -64,7 +64,7 @@ set(SRC interface_template_search_operator.c interface_templates.c interface_undo.c - interface_utils.c + interface_utils.cc interface_view.cc interface_widgets.c resources.c diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index a7a2409ef17..e619b14fb69 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -214,8 +214,8 @@ struct uiBut { BIFIconID icon; /** Copied from the #uiBlock.emboss */ eUIEmbossType emboss; - /** direction in a pie menu, used for collision detection (RadialDirection) */ - signed char pie_dir; + /** direction in a pie menu, used for collision detection. */ + RadialDirection pie_dir; /** could be made into a single flag */ bool changed; /** so buttons can support unit systems which are not RNA */ @@ -954,7 +954,7 @@ void ui_pie_menu_level_create(uiBlock *block, const EnumPropertyItem *items, int totitem, wmOperatorCallContext context, - int flag); + wmOperatorCallContext flag); /* interface_region_popup.c */ diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c deleted file mode 100644 index 4703367671d..00000000000 --- a/source/blender/editors/interface/interface_query.c +++ /dev/null @@ -1,809 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup edinterface - * - * Utilities to inspect the interface, extract information. - */ - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "DNA_screen_types.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "RNA_access.h" - -#include "interface_intern.h" - -#include "WM_api.h" -#include "WM_types.h" - -/* -------------------------------------------------------------------- */ -/** \name Button (#uiBut) State - * \{ */ - -bool ui_but_is_editable(const uiBut *but) -{ - return !ELEM(but->type, - UI_BTYPE_LABEL, - UI_BTYPE_SEPR, - UI_BTYPE_SEPR_LINE, - UI_BTYPE_ROUNDBOX, - UI_BTYPE_LISTBOX, - UI_BTYPE_PROGRESS_BAR); -} - -bool ui_but_is_editable_as_text(const uiBut *but) -{ - return ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_SEARCH_MENU); -} - -bool ui_but_is_toggle(const uiBut *but) -{ - return ELEM(but->type, - UI_BTYPE_BUT_TOGGLE, - UI_BTYPE_TOGGLE, - UI_BTYPE_ICON_TOGGLE, - UI_BTYPE_ICON_TOGGLE_N, - UI_BTYPE_TOGGLE_N, - UI_BTYPE_CHECKBOX, - UI_BTYPE_CHECKBOX_N, - UI_BTYPE_ROW, - UI_BTYPE_TREEROW); -} - -bool ui_but_is_interactive(const uiBut *but, const bool labeledit) -{ - /* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */ - if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == NULL) { - return false; - } - if (ELEM(but->type, UI_BTYPE_ROUNDBOX, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_LISTBOX)) { - return false; - } - if (but->flag & UI_HIDDEN) { - return false; - } - if (but->flag & UI_SCROLLED) { - return false; - } - if ((but->type == UI_BTYPE_TEXT) && - (ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) && !labeledit) { - return false; - } - if ((but->type == UI_BTYPE_LISTROW) && labeledit) { - return false; - } - - return true; -} - -bool UI_but_is_utf8(const uiBut *but) -{ - if (but->rnaprop) { - const int subtype = RNA_property_subtype(but->rnaprop); - return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING)); - } - return !(but->flag & UI_BUT_NO_UTF8); -} - -#ifdef USE_UI_POPOVER_ONCE -bool ui_but_is_popover_once_compat(const uiBut *but) -{ - return (ELEM(but->type, UI_BTYPE_BUT, UI_BTYPE_DECORATOR) || ui_but_is_toggle(but)); -} -#endif - -bool ui_but_has_array_value(const uiBut *but) -{ - return (but->rnapoin.data && but->rnaprop && - ELEM(RNA_property_subtype(but->rnaprop), - PROP_COLOR, - PROP_TRANSLATION, - PROP_DIRECTION, - PROP_VELOCITY, - PROP_ACCELERATION, - PROP_MATRIX, - PROP_EULER, - PROP_QUATERNION, - PROP_AXISANGLE, - PROP_XYZ, - PROP_XYZ_LENGTH, - PROP_COLOR_GAMMA, - PROP_COORDS)); -} - -static wmOperatorType *g_ot_tool_set_by_id = NULL; -bool UI_but_is_tool(const uiBut *but) -{ - /* very evil! */ - if (but->optype != NULL) { - if (g_ot_tool_set_by_id == NULL) { - g_ot_tool_set_by_id = WM_operatortype_find("WM_OT_tool_set_by_id", false); - } - if (but->optype == g_ot_tool_set_by_id) { - return true; - } - } - return false; -} - -bool UI_but_has_tooltip_label(const uiBut *but) -{ - if ((but->drawstr[0] == '\0') && !ui_block_is_popover(but->block)) { - return UI_but_is_tool(but); - } - return false; -} - -int ui_but_icon(const uiBut *but) -{ - if (!(but->flag & UI_HAS_ICON)) { - return ICON_NONE; - } - - /* Consecutive icons can be toggle between. */ - if (but->drawflag & UI_BUT_ICON_REVERSE) { - return but->icon - but->iconadd; - } - return but->icon + but->iconadd; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button (#uiBut) Spatial - * \{ */ - -void ui_but_pie_dir(RadialDirection dir, float vec[2]) -{ - float angle; - - BLI_assert(dir != UI_RADIAL_NONE); - - angle = DEG2RADF((float)ui_radial_dir_to_angle[dir]); - vec[0] = cosf(angle); - vec[1] = sinf(angle); -} - -static bool ui_but_isect_pie_seg(const uiBlock *block, const uiBut *but) -{ - const float angle_range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? M_PI_4 : - M_PI_4 / 2.0; - float vec[2]; - - if (block->pie_data.flags & UI_PIE_INVALID_DIR) { - return false; - } - - ui_but_pie_dir(but->pie_dir, vec); - - if (saacos(dot_v2v2(vec, block->pie_data.pie_dir)) < angle_range) { - return true; - } - - return false; -} - -bool ui_but_contains_pt(const uiBut *but, float mx, float my) -{ - return BLI_rctf_isect_pt(&but->rect, mx, my); -} - -bool ui_but_contains_rect(const uiBut *but, const rctf *rect) -{ - return BLI_rctf_isect(&but->rect, rect, NULL); -} - -bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, const int xy[2]) -{ - uiBlock *block = but->block; - if (!ui_region_contains_point_px(region, xy)) { - return false; - } - - float mx = xy[0], my = xy[1]; - ui_window_to_block_fl(region, block, &mx, &my); - - if (but->pie_dir != UI_RADIAL_NONE) { - if (!ui_but_isect_pie_seg(block, but)) { - return false; - } - } - else if (!ui_but_contains_pt(but, mx, my)) { - return false; - } - - return true; -} - -bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEvent *event) -{ - rcti rect; - int x = event->xy[0], y = event->xy[1]; - - ui_window_to_block(region, but->block, &x, &y); - - BLI_rcti_rctf_copy(&rect, &but->rect); - - if (but->imb || but->type == UI_BTYPE_COLOR) { - /* use button size itself */ - } - else if (but->drawflag & UI_BUT_ICON_LEFT) { - rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect)); - } - else { - const int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect); - rect.xmin += delta / 2; - rect.xmax -= delta / 2; - } - - return BLI_rcti_isect_pt(&rect, x, y); -} - -static uiBut *ui_but_find(const ARegion *region, - const uiButFindPollFn find_poll, - const void *find_custom_data) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { - if (find_poll && find_poll(but, find_custom_data) == false) { - continue; - } - return but; - } - } - - return NULL; -} - -uiBut *ui_but_find_mouse_over_ex(const ARegion *region, - const int xy[2], - const bool labeledit, - const uiButFindPollFn find_poll, - const void *find_custom_data) -{ - uiBut *butover = NULL; - - if (!ui_region_contains_point_px(region, xy)) { - return NULL; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float mx = xy[0], my = xy[1]; - ui_window_to_block_fl(region, block, &mx, &my); - - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { - if (find_poll && find_poll(but, find_custom_data) == false) { - continue; - } - if (ui_but_is_interactive(but, labeledit)) { - if (but->pie_dir != UI_RADIAL_NONE) { - if (ui_but_isect_pie_seg(block, but)) { - butover = but; - break; - } - } - else if (ui_but_contains_pt(but, mx, my)) { - butover = but; - break; - } - } - } - - /* CLIP_EVENTS prevents the event from reaching other blocks */ - if (block->flag & UI_BLOCK_CLIP_EVENTS) { - /* check if mouse is inside block */ - if (BLI_rctf_isect_pt(&block->rect, mx, my)) { - break; - } - } - } - - return butover; -} - -uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event) -{ - return ui_but_find_mouse_over_ex(region, event->xy, event->modifier & KM_CTRL, NULL, NULL); -} - -uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) -{ - if (!ui_region_contains_rect_px(region, rect_px)) { - return NULL; - } - - /* Currently no need to expose this at the moment. */ - const bool labeledit = true; - rctf rect_px_fl; - BLI_rctf_rcti_copy(&rect_px_fl, rect_px); - uiBut *butover = NULL; - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - rctf rect_block; - ui_window_to_block_rctf(region, block, &rect_block, &rect_px_fl); - - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { - if (ui_but_is_interactive(but, labeledit)) { - /* No pie menu support. */ - BLI_assert(but->pie_dir == UI_RADIAL_NONE); - if (ui_but_contains_rect(but, &rect_block)) { - butover = but; - break; - } - } - } - - /* CLIP_EVENTS prevents the event from reaching other blocks */ - if (block->flag & UI_BLOCK_CLIP_EVENTS) { - /* check if mouse is inside block */ - if (BLI_rctf_isect(&block->rect, &rect_block, NULL)) { - break; - } - } - } - return butover; -} - -uiBut *ui_list_find_mouse_over_ex(const ARegion *region, const int xy[2]) -{ - if (!ui_region_contains_point_px(region, xy)) { - return NULL; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float mx = xy[0], my = xy[1]; - ui_window_to_block_fl(region, block, &mx, &my); - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { - if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) { - return but; - } - } - } - - return NULL; -} - -uiBut *ui_list_find_mouse_over(const ARegion *region, const wmEvent *event) -{ - if (event == NULL) { - /* If there is no info about the mouse, just act as if there is nothing underneath it. */ - return NULL; - } - return ui_list_find_mouse_over_ex(region, event->xy); -} - -uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event) -{ - uiBut *list_but = ui_list_find_mouse_over(region, event); - if (!list_but) { - return NULL; - } - - return list_but->custom_data; -} - -static bool ui_list_contains_row(const uiBut *listbox_but, const uiBut *listrow_but) -{ - BLI_assert(listbox_but->type == UI_BTYPE_LISTBOX); - BLI_assert(listrow_but->type == UI_BTYPE_LISTROW); - /* The list box and its rows have the same RNA data (active data pointer/prop). */ - return ui_but_rna_equals(listbox_but, listrow_but); -} - -static bool ui_but_is_listbox_with_row(const uiBut *but, const void *customdata) -{ - const uiBut *row_but = customdata; - return (but->type == UI_BTYPE_LISTBOX) && ui_list_contains_row(but, row_but); -} - -uiBut *ui_list_find_from_row(const ARegion *region, const uiBut *row_but) -{ - return ui_but_find(region, ui_but_is_listbox_with_row, row_but); -} - -static bool ui_but_is_listrow(const uiBut *but, const void *UNUSED(customdata)) -{ - return but->type == UI_BTYPE_LISTROW; -} - -uiBut *ui_list_row_find_mouse_over(const ARegion *region, const int xy[2]) -{ - return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_listrow, NULL); -} - -struct ListRowFindIndexData { - int index; - uiBut *listbox; -}; - -static bool ui_but_is_listrow_at_index(const uiBut *but, const void *customdata) -{ - const struct ListRowFindIndexData *find_data = customdata; - - return ui_but_is_listrow(but, NULL) && ui_list_contains_row(find_data->listbox, but) && - (but->hardmax == find_data->index); -} - -uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut *listbox) -{ - BLI_assert(listbox->type == UI_BTYPE_LISTBOX); - struct ListRowFindIndexData data = { - .index = index, - .listbox = listbox, - }; - return ui_but_find(region, ui_but_is_listrow_at_index, &data); -} - -static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) -{ - return but->type == UI_BTYPE_TREEROW; -} - -uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2]) -{ - return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_treerow, NULL); -} - -static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata) -{ - if (!ui_but_is_treerow(but, customdata)) { - return false; - } - - const uiButTreeRow *treerow_but = (const uiButTreeRow *)but; - return UI_tree_view_item_is_active(treerow_but->tree_item); -} - -uiBut *ui_tree_row_find_active(const ARegion *region) -{ - return ui_but_find(region, ui_but_is_active_treerow, NULL); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button (#uiBut) Relations - * \{ */ - -uiBut *ui_but_prev(uiBut *but) -{ - while (but->prev) { - but = but->prev; - if (ui_but_is_editable(but)) { - return but; - } - } - return NULL; -} - -uiBut *ui_but_next(uiBut *but) -{ - while (but->next) { - but = but->next; - if (ui_but_is_editable(but)) { - return but; - } - } - return NULL; -} - -uiBut *ui_but_first(uiBlock *block) -{ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (ui_but_is_editable(but)) { - return but; - } - } - return NULL; -} - -uiBut *ui_but_last(uiBlock *block) -{ - uiBut *but; - - but = block->buttons.last; - while (but) { - if (ui_but_is_editable(but)) { - return but; - } - but = but->prev; - } - return NULL; -} - -bool ui_but_is_cursor_warp(const uiBut *but) -{ - if (U.uiflag & USER_CONTINUOUS_MOUSE) { - if (ELEM(but->type, - UI_BTYPE_NUM, - UI_BTYPE_NUM_SLIDER, - UI_BTYPE_TRACK_PREVIEW, - UI_BTYPE_HSVCUBE, - UI_BTYPE_HSVCIRCLE, - UI_BTYPE_CURVE, - UI_BTYPE_CURVEPROFILE)) { - return true; - } - } - - return false; -} - -bool ui_but_contains_password(const uiBut *but) -{ - return but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button (#uiBut) Text - * \{ */ - -size_t ui_but_drawstr_len_without_sep_char(const uiBut *but) -{ - if (but->flag & UI_BUT_HAS_SEP_CHAR) { - const char *str_sep = strrchr(but->drawstr, UI_SEP_CHAR); - if (str_sep != NULL) { - return (str_sep - but->drawstr); - } - } - return strlen(but->drawstr); -} - -size_t ui_but_drawstr_without_sep_char(const uiBut *but, char *str, size_t str_maxlen) -{ - size_t str_len_clip = ui_but_drawstr_len_without_sep_char(but); - return BLI_strncpy_rlen(str, but->drawstr, min_zz(str_len_clip + 1, str_maxlen)); -} - -size_t ui_but_tip_len_only_first_line(const uiBut *but) -{ - if (but->tip == NULL) { - return 0; - } - - const char *str_sep = strchr(but->tip, '\n'); - if (str_sep != NULL) { - return (str_sep - but->tip); - } - return strlen(but->tip); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Block (#uiBlock) State - * \{ */ - -uiBut *ui_block_active_but_get(const uiBlock *block) -{ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->active) { - return but; - } - } - - return NULL; -} - -bool ui_block_is_menu(const uiBlock *block) -{ - return (((block->flag & UI_BLOCK_LOOP) != 0) && - /* non-menu popups use keep-open, so check this is off */ - ((block->flag & UI_BLOCK_KEEP_OPEN) == 0)); -} - -bool ui_block_is_popover(const uiBlock *block) -{ - return (block->flag & UI_BLOCK_POPOVER) != 0; -} - -bool ui_block_is_pie_menu(const uiBlock *block) -{ - return ((block->flag & UI_BLOCK_RADIAL) != 0); -} - -bool ui_block_is_popup_any(const uiBlock *block) -{ - return (ui_block_is_menu(block) || ui_block_is_popover(block) || ui_block_is_pie_menu(block)); -} - -static const uiBut *ui_but_next_non_separator(const uiBut *but) -{ - for (; but; but = but->next) { - if (!ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) { - return but; - } - } - return NULL; -} - -bool UI_block_is_empty_ex(const uiBlock *block, const bool skip_title) -{ - const uiBut *but = block->buttons.first; - if (skip_title) { - /* Skip the first label, since popups often have a title, - * we may want to consider the block empty in this case. */ - but = ui_but_next_non_separator(but); - if (but && but->type == UI_BTYPE_LABEL) { - but = but->next; - } - } - return (ui_but_next_non_separator(but) == NULL); -} - -bool UI_block_is_empty(const uiBlock *block) -{ - return UI_block_is_empty_ex(block, false); -} - -bool UI_block_can_add_separator(const uiBlock *block) -{ - if (ui_block_is_menu(block) && !ui_block_is_pie_menu(block)) { - const uiBut *but = block->buttons.last; - return (but && !ELEM(but->type, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR)); - } - return true; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Block (#uiBlock) Spatial - * \{ */ - -uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, const int xy[2], bool only_clip) -{ - if (!ui_region_contains_point_px(region, xy)) { - return NULL; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (only_clip) { - if ((block->flag & UI_BLOCK_CLIP_EVENTS) == 0) { - continue; - } - } - float mx = xy[0], my = xy[1]; - ui_window_to_block_fl(region, block, &mx, &my); - if (BLI_rctf_isect_pt(&block->rect, mx, my)) { - return block; - } - } - return NULL; -} - -uiBlock *ui_block_find_mouse_over(const ARegion *region, const wmEvent *event, bool only_clip) -{ - return ui_block_find_mouse_over_ex(region, event->xy, only_clip); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Region (#ARegion) State - * \{ */ - -uiBut *ui_region_find_active_but(ARegion *region) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - uiBut *but = ui_block_active_but_get(block); - if (but) { - return but; - } - } - - return NULL; -} - -uiBut *ui_region_find_first_but_test_flag(ARegion *region, int flag_include, int flag_exclude) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (((but->flag & flag_include) == flag_include) && ((but->flag & flag_exclude) == 0)) { - return but; - } - } - } - - return NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Region (#ARegion) Spatial - * \{ */ - -bool ui_region_contains_point_px(const ARegion *region, const int xy[2]) -{ - rcti winrct; - ui_region_winrct_get_no_margin(region, &winrct); - if (!BLI_rcti_isect_pt_v(&winrct, xy)) { - return false; - } - - /* also, check that with view2d, that the mouse is not over the scroll-bars - * NOTE: care is needed here, since the mask rect may include the scroll-bars - * even when they are not visible, so we need to make a copy of the mask to - * use to check - */ - if (region->v2d.mask.xmin != region->v2d.mask.xmax) { - const View2D *v2d = ®ion->v2d; - int mx = xy[0], my = xy[1]; - - ui_window_to_region(region, &mx, &my); - if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) || - UI_view2d_mouse_in_scrollers(region, ®ion->v2d, xy)) { - return false; - } - } - - return true; -} - -bool ui_region_contains_rect_px(const ARegion *region, const rcti *rect_px) -{ - rcti winrct; - ui_region_winrct_get_no_margin(region, &winrct); - if (!BLI_rcti_isect(&winrct, rect_px, NULL)) { - return false; - } - - /* See comment in 'ui_region_contains_point_px' */ - if (region->v2d.mask.xmin != region->v2d.mask.xmax) { - const View2D *v2d = ®ion->v2d; - rcti rect_region; - ui_window_to_region_rcti(region, &rect_region, rect_px); - if (!BLI_rcti_isect(&v2d->mask, &rect_region, NULL) || - UI_view2d_rect_in_scrollers(region, ®ion->v2d, rect_px)) { - return false; - } - } - - return true; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Screen (#bScreen) Spatial - * \{ */ - -ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, const int xy[2]) -{ - LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { - rcti winrct; - - ui_region_winrct_get_no_margin(region, &winrct); - - if (BLI_rcti_isect_pt_v(&winrct, xy)) { - return region; - } - } - return NULL; -} - -ARegion *ui_screen_region_find_mouse_over(bScreen *screen, const wmEvent *event) -{ - return ui_screen_region_find_mouse_over_ex(screen, event->xy); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Manage Internal State - * \{ */ - -void ui_interface_tag_script_reload_queries(void) -{ - g_ot_tool_set_by_id = NULL; -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_query.cc b/source/blender/editors/interface/interface_query.cc new file mode 100644 index 00000000000..2767a184619 --- /dev/null +++ b/source/blender/editors/interface/interface_query.cc @@ -0,0 +1,806 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup edinterface + * + * Utilities to inspect the interface, extract information. + */ + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "DNA_screen_types.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "RNA_access.h" + +#include "interface_intern.h" + +#include "WM_api.h" +#include "WM_types.h" + +/* -------------------------------------------------------------------- */ +/** \name Button (#uiBut) State + * \{ */ + +bool ui_but_is_editable(const uiBut *but) +{ + return !ELEM(but->type, + UI_BTYPE_LABEL, + UI_BTYPE_SEPR, + UI_BTYPE_SEPR_LINE, + UI_BTYPE_ROUNDBOX, + UI_BTYPE_LISTBOX, + UI_BTYPE_PROGRESS_BAR); +} + +bool ui_but_is_editable_as_text(const uiBut *but) +{ + return ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_SEARCH_MENU); +} + +bool ui_but_is_toggle(const uiBut *but) +{ + return ELEM(but->type, + UI_BTYPE_BUT_TOGGLE, + UI_BTYPE_TOGGLE, + UI_BTYPE_ICON_TOGGLE, + UI_BTYPE_ICON_TOGGLE_N, + UI_BTYPE_TOGGLE_N, + UI_BTYPE_CHECKBOX, + UI_BTYPE_CHECKBOX_N, + UI_BTYPE_ROW, + UI_BTYPE_TREEROW); +} + +bool ui_but_is_interactive(const uiBut *but, const bool labeledit) +{ + /* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */ + if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == nullptr) { + return false; + } + if (ELEM(but->type, UI_BTYPE_ROUNDBOX, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_LISTBOX)) { + return false; + } + if (but->flag & UI_HIDDEN) { + return false; + } + if (but->flag & UI_SCROLLED) { + return false; + } + if ((but->type == UI_BTYPE_TEXT) && + (ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) && !labeledit) { + return false; + } + if ((but->type == UI_BTYPE_LISTROW) && labeledit) { + return false; + } + + return true; +} + +bool UI_but_is_utf8(const uiBut *but) +{ + if (but->rnaprop) { + const int subtype = RNA_property_subtype(but->rnaprop); + return !(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH, PROP_FILENAME, PROP_BYTESTRING)); + } + return !(but->flag & UI_BUT_NO_UTF8); +} + +#ifdef USE_UI_POPOVER_ONCE +bool ui_but_is_popover_once_compat(const uiBut *but) +{ + return (ELEM(but->type, UI_BTYPE_BUT, UI_BTYPE_DECORATOR) || ui_but_is_toggle(but)); +} +#endif + +bool ui_but_has_array_value(const uiBut *but) +{ + return (but->rnapoin.data && but->rnaprop && + ELEM(RNA_property_subtype(but->rnaprop), + PROP_COLOR, + PROP_TRANSLATION, + PROP_DIRECTION, + PROP_VELOCITY, + PROP_ACCELERATION, + PROP_MATRIX, + PROP_EULER, + PROP_QUATERNION, + PROP_AXISANGLE, + PROP_XYZ, + PROP_XYZ_LENGTH, + PROP_COLOR_GAMMA, + PROP_COORDS)); +} + +static wmOperatorType *g_ot_tool_set_by_id = nullptr; +bool UI_but_is_tool(const uiBut *but) +{ + /* very evil! */ + if (but->optype != nullptr) { + if (g_ot_tool_set_by_id == nullptr) { + g_ot_tool_set_by_id = WM_operatortype_find("WM_OT_tool_set_by_id", false); + } + if (but->optype == g_ot_tool_set_by_id) { + return true; + } + } + return false; +} + +bool UI_but_has_tooltip_label(const uiBut *but) +{ + if ((but->drawstr[0] == '\0') && !ui_block_is_popover(but->block)) { + return UI_but_is_tool(but); + } + return false; +} + +int ui_but_icon(const uiBut *but) +{ + if (!(but->flag & UI_HAS_ICON)) { + return ICON_NONE; + } + + /* Consecutive icons can be toggle between. */ + if (but->drawflag & UI_BUT_ICON_REVERSE) { + return but->icon - but->iconadd; + } + return but->icon + but->iconadd; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button (#uiBut) Spatial + * \{ */ + +void ui_but_pie_dir(RadialDirection dir, float vec[2]) +{ + float angle; + + BLI_assert(dir != UI_RADIAL_NONE); + + angle = DEG2RADF((float)ui_radial_dir_to_angle[dir]); + vec[0] = cosf(angle); + vec[1] = sinf(angle); +} + +static bool ui_but_isect_pie_seg(const uiBlock *block, const uiBut *but) +{ + const float angle_range = (block->pie_data.flags & UI_PIE_DEGREES_RANGE_LARGE) ? M_PI_4 : + M_PI_4 / 2.0; + float vec[2]; + + if (block->pie_data.flags & UI_PIE_INVALID_DIR) { + return false; + } + + ui_but_pie_dir(but->pie_dir, vec); + + if (saacos(dot_v2v2(vec, block->pie_data.pie_dir)) < angle_range) { + return true; + } + + return false; +} + +bool ui_but_contains_pt(const uiBut *but, float mx, float my) +{ + return BLI_rctf_isect_pt(&but->rect, mx, my); +} + +bool ui_but_contains_rect(const uiBut *but, const rctf *rect) +{ + return BLI_rctf_isect(&but->rect, rect, nullptr); +} + +bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, const int xy[2]) +{ + uiBlock *block = but->block; + if (!ui_region_contains_point_px(region, xy)) { + return false; + } + + float mx = xy[0], my = xy[1]; + ui_window_to_block_fl(region, block, &mx, &my); + + if (but->pie_dir != UI_RADIAL_NONE) { + if (!ui_but_isect_pie_seg(block, but)) { + return false; + } + } + else if (!ui_but_contains_pt(but, mx, my)) { + return false; + } + + return true; +} + +bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEvent *event) +{ + rcti rect; + int x = event->xy[0], y = event->xy[1]; + + ui_window_to_block(region, but->block, &x, &y); + + BLI_rcti_rctf_copy(&rect, &but->rect); + + if (but->imb || but->type == UI_BTYPE_COLOR) { + /* use button size itself */ + } + else if (but->drawflag & UI_BUT_ICON_LEFT) { + rect.xmax = rect.xmin + (BLI_rcti_size_y(&rect)); + } + else { + const int delta = BLI_rcti_size_x(&rect) - BLI_rcti_size_y(&rect); + rect.xmin += delta / 2; + rect.xmax -= delta / 2; + } + + return BLI_rcti_isect_pt(&rect, x, y); +} + +static uiBut *ui_but_find(const ARegion *region, + const uiButFindPollFn find_poll, + const void *find_custom_data) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + if (find_poll && find_poll(but, find_custom_data) == false) { + continue; + } + return but; + } + } + + return nullptr; +} + +uiBut *ui_but_find_mouse_over_ex(const ARegion *region, + const int xy[2], + const bool labeledit, + const uiButFindPollFn find_poll, + const void *find_custom_data) +{ + uiBut *butover = nullptr; + + if (!ui_region_contains_point_px(region, xy)) { + return nullptr; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float mx = xy[0], my = xy[1]; + ui_window_to_block_fl(region, block, &mx, &my); + + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + if (find_poll && find_poll(but, find_custom_data) == false) { + continue; + } + if (ui_but_is_interactive(but, labeledit)) { + if (but->pie_dir != UI_RADIAL_NONE) { + if (ui_but_isect_pie_seg(block, but)) { + butover = but; + break; + } + } + else if (ui_but_contains_pt(but, mx, my)) { + butover = but; + break; + } + } + } + + /* CLIP_EVENTS prevents the event from reaching other blocks */ + if (block->flag & UI_BLOCK_CLIP_EVENTS) { + /* check if mouse is inside block */ + if (BLI_rctf_isect_pt(&block->rect, mx, my)) { + break; + } + } + } + + return butover; +} + +uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event) +{ + return ui_but_find_mouse_over_ex(region, event->xy, event->modifier & KM_CTRL, nullptr, nullptr); +} + +uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px) +{ + if (!ui_region_contains_rect_px(region, rect_px)) { + return nullptr; + } + + /* Currently no need to expose this at the moment. */ + const bool labeledit = true; + rctf rect_px_fl; + BLI_rctf_rcti_copy(&rect_px_fl, rect_px); + uiBut *butover = nullptr; + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + rctf rect_block; + ui_window_to_block_rctf(region, block, &rect_block, &rect_px_fl); + + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + if (ui_but_is_interactive(but, labeledit)) { + /* No pie menu support. */ + BLI_assert(but->pie_dir == UI_RADIAL_NONE); + if (ui_but_contains_rect(but, &rect_block)) { + butover = but; + break; + } + } + } + + /* CLIP_EVENTS prevents the event from reaching other blocks */ + if (block->flag & UI_BLOCK_CLIP_EVENTS) { + /* check if mouse is inside block */ + if (BLI_rctf_isect(&block->rect, &rect_block, nullptr)) { + break; + } + } + } + return butover; +} + +uiBut *ui_list_find_mouse_over_ex(const ARegion *region, const int xy[2]) +{ + if (!ui_region_contains_point_px(region, xy)) { + return nullptr; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float mx = xy[0], my = xy[1]; + ui_window_to_block_fl(region, block, &mx, &my); + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) { + return but; + } + } + } + + return nullptr; +} + +uiBut *ui_list_find_mouse_over(const ARegion *region, const wmEvent *event) +{ + if (event == nullptr) { + /* If there is no info about the mouse, just act as if there is nothing underneath it. */ + return nullptr; + } + return ui_list_find_mouse_over_ex(region, event->xy); +} + +uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event) +{ + uiBut *list_but = ui_list_find_mouse_over(region, event); + if (!list_but) { + return nullptr; + } + + return static_cast(list_but->custom_data); +} + +static bool ui_list_contains_row(const uiBut *listbox_but, const uiBut *listrow_but) +{ + BLI_assert(listbox_but->type == UI_BTYPE_LISTBOX); + BLI_assert(listrow_but->type == UI_BTYPE_LISTROW); + /* The list box and its rows have the same RNA data (active data pointer/prop). */ + return ui_but_rna_equals(listbox_but, listrow_but); +} + +static bool ui_but_is_listbox_with_row(const uiBut *but, const void *customdata) +{ + const uiBut *row_but = static_cast(customdata); + return (but->type == UI_BTYPE_LISTBOX) && ui_list_contains_row(but, row_but); +} + +uiBut *ui_list_find_from_row(const ARegion *region, const uiBut *row_but) +{ + return ui_but_find(region, ui_but_is_listbox_with_row, row_but); +} + +static bool ui_but_is_listrow(const uiBut *but, const void *UNUSED(customdata)) +{ + return but->type == UI_BTYPE_LISTROW; +} + +uiBut *ui_list_row_find_mouse_over(const ARegion *region, const int xy[2]) +{ + return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_listrow, nullptr); +} + +struct ListRowFindIndexData { + int index; + uiBut *listbox; +}; + +static bool ui_but_is_listrow_at_index(const uiBut *but, const void *customdata) +{ + const ListRowFindIndexData *find_data = static_cast(customdata); + + return ui_but_is_listrow(but, nullptr) && ui_list_contains_row(find_data->listbox, but) && + (but->hardmax == find_data->index); +} + +uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut *listbox) +{ + BLI_assert(listbox->type == UI_BTYPE_LISTBOX); + ListRowFindIndexData data = {}; + data.index = index; + data.listbox = listbox; + return ui_but_find(region, ui_but_is_listrow_at_index, &data); +} + +static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata)) +{ + return but->type == UI_BTYPE_TREEROW; +} + +uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2]) +{ + return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_treerow, nullptr); +} + +static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata) +{ + if (!ui_but_is_treerow(but, customdata)) { + return false; + } + + const uiButTreeRow *treerow_but = (const uiButTreeRow *)but; + return UI_tree_view_item_is_active(treerow_but->tree_item); +} + +uiBut *ui_tree_row_find_active(const ARegion *region) +{ + return ui_but_find(region, ui_but_is_active_treerow, nullptr); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button (#uiBut) Relations + * \{ */ + +uiBut *ui_but_prev(uiBut *but) +{ + while (but->prev) { + but = but->prev; + if (ui_but_is_editable(but)) { + return but; + } + } + return nullptr; +} + +uiBut *ui_but_next(uiBut *but) +{ + while (but->next) { + but = but->next; + if (ui_but_is_editable(but)) { + return but; + } + } + return nullptr; +} + +uiBut *ui_but_first(uiBlock *block) +{ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (ui_but_is_editable(but)) { + return but; + } + } + return nullptr; +} + +uiBut *ui_but_last(uiBlock *block) +{ + uiBut *but = static_cast(block->buttons.last); + while (but) { + if (ui_but_is_editable(but)) { + return but; + } + but = but->prev; + } + return nullptr; +} + +bool ui_but_is_cursor_warp(const uiBut *but) +{ + if (U.uiflag & USER_CONTINUOUS_MOUSE) { + if (ELEM(but->type, + UI_BTYPE_NUM, + UI_BTYPE_NUM_SLIDER, + UI_BTYPE_TRACK_PREVIEW, + UI_BTYPE_HSVCUBE, + UI_BTYPE_HSVCIRCLE, + UI_BTYPE_CURVE, + UI_BTYPE_CURVEPROFILE)) { + return true; + } + } + + return false; +} + +bool ui_but_contains_password(const uiBut *but) +{ + return but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button (#uiBut) Text + * \{ */ + +size_t ui_but_drawstr_len_without_sep_char(const uiBut *but) +{ + if (but->flag & UI_BUT_HAS_SEP_CHAR) { + const char *str_sep = strrchr(but->drawstr, UI_SEP_CHAR); + if (str_sep != nullptr) { + return (str_sep - but->drawstr); + } + } + return strlen(but->drawstr); +} + +size_t ui_but_drawstr_without_sep_char(const uiBut *but, char *str, size_t str_maxlen) +{ + size_t str_len_clip = ui_but_drawstr_len_without_sep_char(but); + return BLI_strncpy_rlen(str, but->drawstr, min_zz(str_len_clip + 1, str_maxlen)); +} + +size_t ui_but_tip_len_only_first_line(const uiBut *but) +{ + if (but->tip == nullptr) { + return 0; + } + + const char *str_sep = strchr(but->tip, '\n'); + if (str_sep != nullptr) { + return (str_sep - but->tip); + } + return strlen(but->tip); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Block (#uiBlock) State + * \{ */ + +uiBut *ui_block_active_but_get(const uiBlock *block) +{ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->active) { + return but; + } + } + + return nullptr; +} + +bool ui_block_is_menu(const uiBlock *block) +{ + return (((block->flag & UI_BLOCK_LOOP) != 0) && + /* non-menu popups use keep-open, so check this is off */ + ((block->flag & UI_BLOCK_KEEP_OPEN) == 0)); +} + +bool ui_block_is_popover(const uiBlock *block) +{ + return (block->flag & UI_BLOCK_POPOVER) != 0; +} + +bool ui_block_is_pie_menu(const uiBlock *block) +{ + return ((block->flag & UI_BLOCK_RADIAL) != 0); +} + +bool ui_block_is_popup_any(const uiBlock *block) +{ + return (ui_block_is_menu(block) || ui_block_is_popover(block) || ui_block_is_pie_menu(block)); +} + +static const uiBut *ui_but_next_non_separator(const uiBut *but) +{ + for (; but; but = but->next) { + if (!ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) { + return but; + } + } + return nullptr; +} + +bool UI_block_is_empty_ex(const uiBlock *block, const bool skip_title) +{ + const uiBut *but = static_cast(block->buttons.first); + if (skip_title) { + /* Skip the first label, since popups often have a title, + * we may want to consider the block empty in this case. */ + but = ui_but_next_non_separator(but); + if (but && but->type == UI_BTYPE_LABEL) { + but = but->next; + } + } + return (ui_but_next_non_separator(but) == nullptr); +} + +bool UI_block_is_empty(const uiBlock *block) +{ + return UI_block_is_empty_ex(block, false); +} + +bool UI_block_can_add_separator(const uiBlock *block) +{ + if (ui_block_is_menu(block) && !ui_block_is_pie_menu(block)) { + const uiBut *but = static_cast(block->buttons.last); + return (but && !ELEM(but->type, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR)); + } + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Block (#uiBlock) Spatial + * \{ */ + +uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, const int xy[2], bool only_clip) +{ + if (!ui_region_contains_point_px(region, xy)) { + return nullptr; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (only_clip) { + if ((block->flag & UI_BLOCK_CLIP_EVENTS) == 0) { + continue; + } + } + float mx = xy[0], my = xy[1]; + ui_window_to_block_fl(region, block, &mx, &my); + if (BLI_rctf_isect_pt(&block->rect, mx, my)) { + return block; + } + } + return nullptr; +} + +uiBlock *ui_block_find_mouse_over(const ARegion *region, const wmEvent *event, bool only_clip) +{ + return ui_block_find_mouse_over_ex(region, event->xy, only_clip); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Region (#ARegion) State + * \{ */ + +uiBut *ui_region_find_active_but(ARegion *region) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + uiBut *but = ui_block_active_but_get(block); + if (but) { + return but; + } + } + + return nullptr; +} + +uiBut *ui_region_find_first_but_test_flag(ARegion *region, int flag_include, int flag_exclude) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (((but->flag & flag_include) == flag_include) && ((but->flag & flag_exclude) == 0)) { + return but; + } + } + } + + return nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Region (#ARegion) Spatial + * \{ */ + +bool ui_region_contains_point_px(const ARegion *region, const int xy[2]) +{ + rcti winrct; + ui_region_winrct_get_no_margin(region, &winrct); + if (!BLI_rcti_isect_pt_v(&winrct, xy)) { + return false; + } + + /* also, check that with view2d, that the mouse is not over the scroll-bars + * NOTE: care is needed here, since the mask rect may include the scroll-bars + * even when they are not visible, so we need to make a copy of the mask to + * use to check + */ + if (region->v2d.mask.xmin != region->v2d.mask.xmax) { + const View2D *v2d = ®ion->v2d; + int mx = xy[0], my = xy[1]; + + ui_window_to_region(region, &mx, &my); + if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) || + UI_view2d_mouse_in_scrollers(region, ®ion->v2d, xy)) { + return false; + } + } + + return true; +} + +bool ui_region_contains_rect_px(const ARegion *region, const rcti *rect_px) +{ + rcti winrct; + ui_region_winrct_get_no_margin(region, &winrct); + if (!BLI_rcti_isect(&winrct, rect_px, nullptr)) { + return false; + } + + /* See comment in 'ui_region_contains_point_px' */ + if (region->v2d.mask.xmin != region->v2d.mask.xmax) { + const View2D *v2d = ®ion->v2d; + rcti rect_region; + ui_window_to_region_rcti(region, &rect_region, rect_px); + if (!BLI_rcti_isect(&v2d->mask, &rect_region, nullptr) || + UI_view2d_rect_in_scrollers(region, ®ion->v2d, rect_px)) { + return false; + } + } + + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Screen (#bScreen) Spatial + * \{ */ + +ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, const int xy[2]) +{ + LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { + rcti winrct; + + ui_region_winrct_get_no_margin(region, &winrct); + + if (BLI_rcti_isect_pt_v(&winrct, xy)) { + return region; + } + } + return nullptr; +} + +ARegion *ui_screen_region_find_mouse_over(bScreen *screen, const wmEvent *event) +{ + return ui_screen_region_find_mouse_over_ex(screen, event->xy); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Manage Internal State + * \{ */ + +void ui_interface_tag_script_reload_queries(void) +{ + g_ot_tool_set_by_id = nullptr; +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_region_color_picker.c b/source/blender/editors/interface/interface_region_color_picker.c deleted file mode 100644 index 9fb6f538191..00000000000 --- a/source/blender/editors/interface/interface_region_color_picker.c +++ /dev/null @@ -1,910 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Color Picker Region & Color Utils - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" - -#include "WM_types.h" - -#include "RNA_access.h" - -#include "UI_interface.h" - -#include "BLT_translation.h" - -#include "ED_screen.h" - -#include "IMB_colormanagement.h" - -#include "interface_intern.h" - -enum ePickerType { - PICKER_TYPE_RGB = 0, - PICKER_TYPE_HSV = 1, - PICKER_TYPE_HEX = 2, -}; - -/* -------------------------------------------------------------------- */ -/** \name Color Conversion - * \{ */ - -static void ui_color_picker_rgb_round(float rgb[3]) -{ - /* Handle small rounding errors in color space conversions. Doing these for - * all color space conversions would be expensive, but for the color picker - * we can do the extra work. */ - for (int i = 0; i < 3; i++) { - if (fabsf(rgb[i]) < 1e-6f) { - rgb[i] = 0.0f; - } - else if (fabsf(1.0f - rgb[i]) < 1e-6f) { - rgb[i] = 1.0f; - } - } -} - -void ui_color_picker_rgb_to_hsv_compat(const float rgb[3], float r_cp[3]) -{ - /* Convert RGB to HSV, remaining as compatible as possible with the existing - * r_hsv value (for example when value goes to zero, preserve the hue). */ - switch (U.color_picker_type) { - case USER_CP_CIRCLE_HSL: - rgb_to_hsl_compat_v(rgb, r_cp); - break; - default: - rgb_to_hsv_compat_v(rgb, r_cp); - break; - } -} - -void ui_color_picker_rgb_to_hsv(const float rgb[3], float r_cp[3]) -{ - switch (U.color_picker_type) { - case USER_CP_CIRCLE_HSL: - rgb_to_hsl_v(rgb, r_cp); - break; - default: - rgb_to_hsv_v(rgb, r_cp); - break; - } -} - -void ui_color_picker_hsv_to_rgb(const float r_cp[3], float rgb[3]) -{ - switch (U.color_picker_type) { - case USER_CP_CIRCLE_HSL: - hsl_to_rgb_v(r_cp, rgb); - break; - default: - hsv_to_rgb_v(r_cp, rgb); - break; - } -} - -bool ui_but_is_color_gamma(uiBut *but) -{ - if (but->rnaprop) { - if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - return true; - } - } - - return but->block->is_color_gamma_picker; -} - -void ui_scene_linear_to_perceptual_space(uiBut *but, float rgb[3]) -{ - /* Map to color picking space for HSV values and HSV cube/circle, - * assuming it is more perceptually linear than the scene linear - * space for intuitive color picking. */ - if (!ui_but_is_color_gamma(but)) { - IMB_colormanagement_scene_linear_to_color_picking_v3(rgb); - ui_color_picker_rgb_round(rgb); - } -} - -void ui_perceptual_to_scene_linear_space(uiBut *but, float rgb[3]) -{ - if (!ui_but_is_color_gamma(but)) { - IMB_colormanagement_color_picking_to_scene_linear_v3(rgb); - ui_color_picker_rgb_round(rgb); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Color Picker - * \{ */ - -static void ui_color_picker_update_hsv(ColorPicker *cpicker, - uiBut *from_but, - const float rgb_scene_linear[3]) -{ - /* Convert from RGB to HSV in scene linear space color for number editing. */ - if (cpicker->is_init == false) { - ui_color_picker_rgb_to_hsv(rgb_scene_linear, cpicker->hsv_scene_linear); - } - else { - ui_color_picker_rgb_to_hsv_compat(rgb_scene_linear, cpicker->hsv_scene_linear); - } - - /* Convert from RGB to HSV in perceptually linear space for picker widgets. */ - float rgb_perceptual[3]; - copy_v3_v3(rgb_perceptual, rgb_scene_linear); - if (from_but) { - ui_scene_linear_to_perceptual_space(from_but, rgb_perceptual); - } - - if (cpicker->is_init == false) { - ui_color_picker_rgb_to_hsv(rgb_perceptual, cpicker->hsv_perceptual); - copy_v3_v3(cpicker->hsv_perceptual_init, cpicker->hsv_perceptual); - } - else { - ui_color_picker_rgb_to_hsv_compat(rgb_perceptual, cpicker->hsv_perceptual); - } - - cpicker->is_init = true; -} - -void ui_but_hsv_set(uiBut *but) -{ - float rgb_perceptual[3]; - ColorPicker *cpicker = but->custom_data; - float *hsv_perceptual = cpicker->hsv_perceptual; - - ui_color_picker_hsv_to_rgb(hsv_perceptual, rgb_perceptual); - - ui_but_v3_set(but, rgb_perceptual); -} - -/* Updates all buttons who share the same color picker as the one passed - * also used by small picker, be careful with name checks below... */ -static void ui_update_color_picker_buts_rgb(uiBut *from_but, - uiBlock *block, - ColorPicker *cpicker, - const float rgb_scene_linear[3]) -{ - ui_color_picker_update_hsv(cpicker, from_but, rgb_scene_linear); - - /* this updates button strings, - * is hackish... but button pointers are on stack of caller function */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (bt->custom_data != cpicker) { - continue; - } - - if (bt->rnaprop) { - ui_but_v3_set(bt, rgb_scene_linear); - - /* original button that created the color picker already does undo - * push, so disable it on RNA buttons in the color picker block */ - UI_but_flag_disable(bt, UI_BUT_UNDO); - } - else if (STREQ(bt->str, "Hex: ")) { - float rgb_hex[3]; - uchar rgb_hex_uchar[3]; - char col[16]; - - /* Hex code is assumed to be in sRGB space - * (coming from other applications, web, etc) */ - copy_v3_v3(rgb_hex, rgb_scene_linear); - if (from_but && !ui_but_is_color_gamma(from_but)) { - IMB_colormanagement_scene_linear_to_srgb_v3(rgb_hex); - ui_color_picker_rgb_round(rgb_hex); - } - - rgb_float_to_uchar(rgb_hex_uchar, rgb_hex); - BLI_snprintf(col, sizeof(col), "%02X%02X%02X", UNPACK3_EX((uint), rgb_hex_uchar, )); - - strcpy(bt->poin, col); - } - else if (bt->str[1] == ' ') { - if (bt->str[0] == 'R') { - ui_but_value_set(bt, rgb_scene_linear[0]); - } - else if (bt->str[0] == 'G') { - ui_but_value_set(bt, rgb_scene_linear[1]); - } - else if (bt->str[0] == 'B') { - ui_but_value_set(bt, rgb_scene_linear[2]); - } - else if (bt->str[0] == 'H') { - ui_but_value_set(bt, cpicker->hsv_scene_linear[0]); - } - else if (bt->str[0] == 'S') { - ui_but_value_set(bt, cpicker->hsv_scene_linear[1]); - } - else if (bt->str[0] == 'V') { - ui_but_value_set(bt, cpicker->hsv_scene_linear[2]); - } - else if (bt->str[0] == 'L') { - ui_but_value_set(bt, cpicker->hsv_scene_linear[2]); - } - } - - ui_but_update(bt); - } -} - -static void ui_colorpicker_rgba_update_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) -{ - uiBut *but = (uiBut *)bt1; - uiPopupBlockHandle *popup = but->block->handle; - PropertyRNA *prop = but->rnaprop; - PointerRNA ptr = but->rnapoin; - float rgb_scene_linear[4]; - - if (prop) { - RNA_property_float_get_array(&ptr, prop, rgb_scene_linear); - ui_update_color_picker_buts_rgb(but, but->block, but->custom_data, rgb_scene_linear); - } - - if (popup) { - popup->menuretval = UI_RETURN_UPDATE; - } -} - -static void ui_colorpicker_hsv_update_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) -{ - uiBut *but = (uiBut *)bt1; - uiPopupBlockHandle *popup = but->block->handle; - float rgb_scene_linear[3]; - ColorPicker *cpicker = but->custom_data; - - ui_color_picker_hsv_to_rgb(cpicker->hsv_scene_linear, rgb_scene_linear); - ui_update_color_picker_buts_rgb(but, but->block, cpicker, rgb_scene_linear); - - if (popup) { - popup->menuretval = UI_RETURN_UPDATE; - } -} - -static void ui_colorpicker_hex_rna_cb(bContext *UNUSED(C), void *bt1, void *hexcl) -{ - uiBut *but = (uiBut *)bt1; - uiPopupBlockHandle *popup = but->block->handle; - ColorPicker *cpicker = but->custom_data; - char *hexcol = (char *)hexcl; - float rgb[3]; - - hex_to_rgb(hexcol, rgb, rgb + 1, rgb + 2); - - /* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */ - if (!ui_but_is_color_gamma(but)) { - IMB_colormanagement_srgb_to_scene_linear_v3(rgb); - ui_color_picker_rgb_round(rgb); - } - - ui_update_color_picker_buts_rgb(but, but->block, cpicker, rgb); - - if (popup) { - popup->menuretval = UI_RETURN_UPDATE; - } -} - -static void ui_popup_close_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) -{ - uiBut *but = (uiBut *)bt1; - uiPopupBlockHandle *popup = but->block->handle; - - if (popup) { - ColorPicker *cpicker = but->custom_data; - BLI_assert(cpicker->is_init); - popup->menuretval = (equals_v3v3(cpicker->hsv_perceptual, cpicker->hsv_perceptual_init) ? - UI_RETURN_CANCEL : - UI_RETURN_OK); - } -} - -static void ui_colorpicker_hide_reveal(uiBlock *block, enum ePickerType colormode) -{ - /* tag buttons */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if ((bt->func == ui_colorpicker_rgba_update_cb) && (bt->type == UI_BTYPE_NUM_SLIDER) && - (bt->rnaindex != 3)) { - /* RGB sliders (color circle and alpha are always shown) */ - SET_FLAG_FROM_TEST(bt->flag, (colormode != PICKER_TYPE_RGB), UI_HIDDEN); - } - else if (bt->func == ui_colorpicker_hsv_update_cb) { - /* HSV sliders */ - SET_FLAG_FROM_TEST(bt->flag, (colormode != PICKER_TYPE_HSV), UI_HIDDEN); - } - else if (bt->func == ui_colorpicker_hex_rna_cb || bt->type == UI_BTYPE_LABEL) { - /* HEX input or gamma correction status label */ - SET_FLAG_FROM_TEST(bt->flag, (colormode != PICKER_TYPE_HEX), UI_HIDDEN); - } - } -} - -static void ui_colorpicker_create_mode_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) -{ - uiBut *bt = bt1; - const short colormode = ui_but_value_get(bt); - ui_colorpicker_hide_reveal(bt->block, colormode); -} - -#define PICKER_H (7.5f * U.widget_unit) -#define PICKER_W (7.5f * U.widget_unit) -#define PICKER_SPACE (0.3f * U.widget_unit) -#define PICKER_BAR (0.7f * U.widget_unit) - -#define PICKER_TOTAL_W (PICKER_W + PICKER_SPACE + PICKER_BAR) - -static void ui_colorpicker_circle(uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - ColorPicker *cpicker) -{ - uiBut *bt; - uiButHSVCube *hsv_but; - - /* HS circle */ - bt = uiDefButR_prop(block, - UI_BTYPE_HSVCIRCLE, - 0, - "", - 0, - 0, - PICKER_H, - PICKER_W, - ptr, - prop, - -1, - 0.0, - 0.0, - 0.0, - 0, - TIP_("Color")); - UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, NULL); - bt->custom_data = cpicker; - - /* value */ - if (U.color_picker_type == USER_CP_CIRCLE_HSL) { - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - PICKER_W + PICKER_SPACE, - 0, - PICKER_BAR, - PICKER_H, - ptr, - prop, - -1, - 0.0, - 0.0, - 0, - 0, - "Lightness"); - hsv_but->gradient_type = UI_GRAD_L_ALT; - UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, NULL); - } - else { - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - PICKER_W + PICKER_SPACE, - 0, - PICKER_BAR, - PICKER_H, - ptr, - prop, - -1, - 0.0, - 0.0, - 0, - 0, - TIP_("Value")); - hsv_but->gradient_type = UI_GRAD_V_ALT; - UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, NULL); - } - hsv_but->but.custom_data = cpicker; -} - -static void ui_colorpicker_square(uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - eButGradientType type, - ColorPicker *cpicker) -{ - uiButHSVCube *hsv_but; - - BLI_assert(type <= UI_GRAD_HS); - - /* HS square */ - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - 0, - PICKER_BAR + PICKER_SPACE, - PICKER_TOTAL_W, - PICKER_H, - ptr, - prop, - -1, - 0.0, - 0.0, - 0, - 0, - TIP_("Color")); - hsv_but->gradient_type = type; - UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, NULL); - hsv_but->but.custom_data = cpicker; - - /* value */ - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - 0, - 0, - PICKER_TOTAL_W, - PICKER_BAR, - ptr, - prop, - -1, - 0.0, - 0.0, - 0, - 0, - TIP_("Value")); - hsv_but->gradient_type = type + 3; - UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, NULL); - hsv_but->but.custom_data = cpicker; -} - -/* a HS circle, V slider, rgb/hsv/hex sliders */ -static void ui_block_colorpicker(uiBlock *block, - uiBut *from_but, - float rgba_scene_linear[4], - bool show_picker) -{ - /* ePickerType */ - static char colormode = 1; - uiBut *bt; - int width, butwidth; - static char hexcol[128]; - float softmin, softmax, hardmin, hardmax, step, precision; - int yco; - ColorPicker *cpicker = ui_block_colorpicker_create(block); - PointerRNA *ptr = &from_but->rnapoin; - PropertyRNA *prop = from_but->rnaprop; - - width = PICKER_TOTAL_W; - butwidth = width - 1.5f * UI_UNIT_X; - - /* sneaky way to check for alpha */ - rgba_scene_linear[3] = FLT_MAX; - - RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); - RNA_property_float_range(ptr, prop, &hardmin, &hardmax); - RNA_property_float_get_array(ptr, prop, rgba_scene_linear); - - ui_color_picker_update_hsv(cpicker, from_but, rgba_scene_linear); - - /* when the softmax isn't defined in the RNA, - * using very large numbers causes sRGB/linear round trip to fail. */ - if (softmax == FLT_MAX) { - softmax = 1.0f; - } - - switch (U.color_picker_type) { - case USER_CP_SQUARE_SV: - ui_colorpicker_square(block, ptr, prop, UI_GRAD_SV, cpicker); - break; - case USER_CP_SQUARE_HS: - ui_colorpicker_square(block, ptr, prop, UI_GRAD_HS, cpicker); - break; - case USER_CP_SQUARE_HV: - ui_colorpicker_square(block, ptr, prop, UI_GRAD_HV, cpicker); - break; - - /* user default */ - case USER_CP_CIRCLE_HSV: - case USER_CP_CIRCLE_HSL: - default: - ui_colorpicker_circle(block, ptr, prop, cpicker); - break; - } - - /* mode */ - yco = -1.5f * UI_UNIT_Y; - UI_block_align_begin(block); - bt = uiDefButC(block, - UI_BTYPE_ROW, - 0, - IFACE_("RGB"), - 0, - yco, - width / 3, - UI_UNIT_Y, - &colormode, - 0.0, - (float)PICKER_TYPE_RGB, - 0, - 0, - ""); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_drawflag_disable(bt, UI_BUT_TEXT_LEFT); - UI_but_func_set(bt, ui_colorpicker_create_mode_cb, bt, NULL); - bt->custom_data = cpicker; - bt = uiDefButC(block, - UI_BTYPE_ROW, - 0, - IFACE_((U.color_picker_type == USER_CP_CIRCLE_HSL) ? "HSL" : "HSV"), - width / 3, - yco, - width / 3, - UI_UNIT_Y, - &colormode, - 0.0, - PICKER_TYPE_HSV, - 0, - 0, - ""); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_drawflag_disable(bt, UI_BUT_TEXT_LEFT); - UI_but_func_set(bt, ui_colorpicker_create_mode_cb, bt, NULL); - bt->custom_data = cpicker; - bt = uiDefButC(block, - UI_BTYPE_ROW, - 0, - IFACE_("Hex"), - 2 * width / 3, - yco, - width / 3, - UI_UNIT_Y, - &colormode, - 0.0, - PICKER_TYPE_HEX, - 0, - 0, - ""); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_drawflag_disable(bt, UI_BUT_TEXT_LEFT); - UI_but_func_set(bt, ui_colorpicker_create_mode_cb, bt, NULL); - bt->custom_data = cpicker; - UI_block_align_end(block); - - yco = -3.0f * UI_UNIT_Y; - if (show_picker) { - bt = uiDefIconButO(block, - UI_BTYPE_BUT, - "UI_OT_eyedropper_color", - WM_OP_INVOKE_DEFAULT, - ICON_EYEDROPPER, - butwidth + 10, - yco, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_drawflag_disable(bt, UI_BUT_ICON_LEFT); - UI_but_func_set(bt, ui_popup_close_cb, bt, NULL); - bt->custom_data = cpicker; - } - - /* NOTE: don't disable UI_BUT_UNDO for RGBA values, since these don't add undo steps. */ - - /* RGB values */ - UI_block_align_begin(block); - bt = uiDefButR_prop(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("R:"), - 0, - yco, - butwidth, - UI_UNIT_Y, - ptr, - prop, - 0, - 0.0, - 0.0, - 0, - 3, - TIP_("Red")); - UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, NULL); - bt->custom_data = cpicker; - bt = uiDefButR_prop(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("G:"), - 0, - yco -= UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - ptr, - prop, - 1, - 0.0, - 0.0, - 0, - 3, - TIP_("Green")); - UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, NULL); - bt->custom_data = cpicker; - bt = uiDefButR_prop(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("B:"), - 0, - yco -= UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - ptr, - prop, - 2, - 0.0, - 0.0, - 0, - 3, - TIP_("Blue")); - UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, NULL); - bt->custom_data = cpicker; - - /* Could use: - * uiItemFullR(col, ptr, prop, -1, 0, UI_ITEM_R_EXPAND | UI_ITEM_R_SLIDER, "", ICON_NONE); - * but need to use UI_but_func_set for updating other fake buttons */ - - /* HSV values */ - yco = -3.0f * UI_UNIT_Y; - UI_block_align_begin(block); - bt = uiDefButF(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("H:"), - 0, - yco, - butwidth, - UI_UNIT_Y, - cpicker->hsv_scene_linear, - 0.0, - 1.0, - 10, - 3, - TIP_("Hue")); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, NULL); - bt->custom_data = cpicker; - bt = uiDefButF(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("S:"), - 0, - yco -= UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - cpicker->hsv_scene_linear + 1, - 0.0, - 1.0, - 10, - 3, - TIP_("Saturation")); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, NULL); - bt->custom_data = cpicker; - if (U.color_picker_type == USER_CP_CIRCLE_HSL) { - bt = uiDefButF(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("L:"), - 0, - yco -= UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - cpicker->hsv_scene_linear + 2, - 0.0, - 1.0, - 10, - 3, - TIP_("Lightness")); - } - else { - bt = uiDefButF(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("V:"), - 0, - yco -= UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - cpicker->hsv_scene_linear + 2, - 0.0, - softmax, - 10, - 3, - TIP_("Value")); - } - UI_but_flag_disable(bt, UI_BUT_UNDO); - - bt->hardmax = hardmax; /* not common but rgb may be over 1.0 */ - UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, NULL); - bt->custom_data = cpicker; - - UI_block_align_end(block); - - if (rgba_scene_linear[3] != FLT_MAX) { - bt = uiDefButR_prop(block, - UI_BTYPE_NUM_SLIDER, - 0, - IFACE_("A: "), - 0, - yco -= UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - ptr, - prop, - 3, - 0.0, - 0.0, - 0, - 3, - TIP_("Alpha")); - UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, NULL); - bt->custom_data = cpicker; - } - else { - rgba_scene_linear[3] = 1.0f; - } - - /* Hex color is in sRGB space. */ - float rgb_hex[3]; - uchar rgb_hex_uchar[3]; - - copy_v3_v3(rgb_hex, rgba_scene_linear); - - if (!ui_but_is_color_gamma(from_but)) { - IMB_colormanagement_scene_linear_to_srgb_v3(rgb_hex); - ui_color_picker_rgb_round(rgb_hex); - } - - rgb_float_to_uchar(rgb_hex_uchar, rgb_hex); - BLI_snprintf(hexcol, sizeof(hexcol), "%02X%02X%02X", UNPACK3_EX((uint), rgb_hex_uchar, )); - - yco = -3.0f * UI_UNIT_Y; - bt = uiDefBut(block, - UI_BTYPE_TEXT, - 0, - IFACE_("Hex: "), - 0, - yco, - butwidth, - UI_UNIT_Y, - hexcol, - 0, - 8, - 0, - 0, - TIP_("Hex triplet for color (#RRGGBB)")); - UI_but_flag_disable(bt, UI_BUT_UNDO); - UI_but_func_set(bt, ui_colorpicker_hex_rna_cb, bt, hexcol); - bt->custom_data = cpicker; - uiDefBut(block, - UI_BTYPE_LABEL, - 0, - IFACE_("(Gamma Corrected)"), - 0, - yco - UI_UNIT_Y, - butwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - - ui_colorpicker_hide_reveal(block, colormode); -} - -static int ui_colorpicker_small_wheel_cb(const bContext *UNUSED(C), - uiBlock *block, - const wmEvent *event) -{ - float add = 0.0f; - - if (event->type == WHEELUPMOUSE) { - add = 0.05f; - } - else if (event->type == WHEELDOWNMOUSE) { - add = -0.05f; - } - - if (add != 0.0f) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->type == UI_BTYPE_HSVCUBE && but->active == NULL) { - uiPopupBlockHandle *popup = block->handle; - ColorPicker *cpicker = but->custom_data; - float *hsv_perceptual = cpicker->hsv_perceptual; - - float rgb_perceptual[3]; - ui_but_v3_get(but, rgb_perceptual); - ui_scene_linear_to_perceptual_space(but, rgb_perceptual); - ui_color_picker_rgb_to_hsv_compat(rgb_perceptual, hsv_perceptual); - - hsv_perceptual[2] = clamp_f(hsv_perceptual[2] + add, 0.0f, 1.0f); - - float rgb_scene_linear[3]; - ui_color_picker_hsv_to_rgb(hsv_perceptual, rgb_scene_linear); - ui_perceptual_to_scene_linear_space(but, rgb_scene_linear); - ui_but_v3_set(but, rgb_scene_linear); - - ui_update_color_picker_buts_rgb(but, block, cpicker, rgb_scene_linear); - if (popup) { - popup->menuretval = UI_RETURN_UPDATE; - } - - return 1; - } - } - } - return 0; -} - -uiBlock *ui_block_func_COLOR(bContext *C, uiPopupBlockHandle *handle, void *arg_but) -{ - uiBut *but = arg_but; - uiBlock *block; - bool show_picker = true; - - block = UI_block_begin(C, handle->region, __func__, UI_EMBOSS); - - if (ui_but_is_color_gamma(but)) { - block->is_color_gamma_picker = true; - } - - if (but->block) { - /* if color block is invoked from a popup we wouldn't be able to set color properly - * this is because color picker will close popups first and then will try to figure - * out active button RNA, and of course it'll fail - */ - show_picker = (but->block->flag & UI_BLOCK_POPUP) == 0; - } - - copy_v3_v3(handle->retvec, but->editvec); - - ui_block_colorpicker(block, but, handle->retvec, show_picker); - - block->flag = UI_BLOCK_LOOP | UI_BLOCK_KEEP_OPEN | UI_BLOCK_OUT_1 | UI_BLOCK_MOVEMOUSE_QUIT; - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - UI_block_bounds_set_normal(block, 0.5 * UI_UNIT_X); - - block->block_event_func = ui_colorpicker_small_wheel_cb; - - /* and lets go */ - block->direction = UI_DIR_UP; - - return block; -} - -ColorPicker *ui_block_colorpicker_create(struct uiBlock *block) -{ - ColorPicker *cpicker = MEM_callocN(sizeof(ColorPicker), "color_picker"); - BLI_addhead(&block->color_pickers.list, cpicker); - - return cpicker; -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_color_picker.cc b/source/blender/editors/interface/interface_region_color_picker.cc new file mode 100644 index 00000000000..ab0a6039cdc --- /dev/null +++ b/source/blender/editors/interface/interface_region_color_picker.cc @@ -0,0 +1,911 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Color Picker Region & Color Utils + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" + +#include "WM_types.h" + +#include "RNA_access.h" + +#include "UI_interface.h" + +#include "BLT_translation.h" + +#include "ED_screen.h" + +#include "IMB_colormanagement.h" + +#include "interface_intern.h" + +enum ePickerType { + PICKER_TYPE_RGB = 0, + PICKER_TYPE_HSV = 1, + PICKER_TYPE_HEX = 2, +}; + +/* -------------------------------------------------------------------- */ +/** \name Color Conversion + * \{ */ + +static void ui_color_picker_rgb_round(float rgb[3]) +{ + /* Handle small rounding errors in color space conversions. Doing these for + * all color space conversions would be expensive, but for the color picker + * we can do the extra work. */ + for (int i = 0; i < 3; i++) { + if (fabsf(rgb[i]) < 1e-6f) { + rgb[i] = 0.0f; + } + else if (fabsf(1.0f - rgb[i]) < 1e-6f) { + rgb[i] = 1.0f; + } + } +} + +void ui_color_picker_rgb_to_hsv_compat(const float rgb[3], float r_cp[3]) +{ + /* Convert RGB to HSV, remaining as compatible as possible with the existing + * r_hsv value (for example when value goes to zero, preserve the hue). */ + switch (U.color_picker_type) { + case USER_CP_CIRCLE_HSL: + rgb_to_hsl_compat_v(rgb, r_cp); + break; + default: + rgb_to_hsv_compat_v(rgb, r_cp); + break; + } +} + +void ui_color_picker_rgb_to_hsv(const float rgb[3], float r_cp[3]) +{ + switch (U.color_picker_type) { + case USER_CP_CIRCLE_HSL: + rgb_to_hsl_v(rgb, r_cp); + break; + default: + rgb_to_hsv_v(rgb, r_cp); + break; + } +} + +void ui_color_picker_hsv_to_rgb(const float r_cp[3], float rgb[3]) +{ + switch (U.color_picker_type) { + case USER_CP_CIRCLE_HSL: + hsl_to_rgb_v(r_cp, rgb); + break; + default: + hsv_to_rgb_v(r_cp, rgb); + break; + } +} + +bool ui_but_is_color_gamma(uiBut *but) +{ + if (but->rnaprop) { + if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + return true; + } + } + + return but->block->is_color_gamma_picker; +} + +void ui_scene_linear_to_perceptual_space(uiBut *but, float rgb[3]) +{ + /* Map to color picking space for HSV values and HSV cube/circle, + * assuming it is more perceptually linear than the scene linear + * space for intuitive color picking. */ + if (!ui_but_is_color_gamma(but)) { + IMB_colormanagement_scene_linear_to_color_picking_v3(rgb); + ui_color_picker_rgb_round(rgb); + } +} + +void ui_perceptual_to_scene_linear_space(uiBut *but, float rgb[3]) +{ + if (!ui_but_is_color_gamma(but)) { + IMB_colormanagement_color_picking_to_scene_linear_v3(rgb); + ui_color_picker_rgb_round(rgb); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Color Picker + * \{ */ + +static void ui_color_picker_update_hsv(ColorPicker *cpicker, + uiBut *from_but, + const float rgb_scene_linear[3]) +{ + /* Convert from RGB to HSV in scene linear space color for number editing. */ + if (cpicker->is_init == false) { + ui_color_picker_rgb_to_hsv(rgb_scene_linear, cpicker->hsv_scene_linear); + } + else { + ui_color_picker_rgb_to_hsv_compat(rgb_scene_linear, cpicker->hsv_scene_linear); + } + + /* Convert from RGB to HSV in perceptually linear space for picker widgets. */ + float rgb_perceptual[3]; + copy_v3_v3(rgb_perceptual, rgb_scene_linear); + if (from_but) { + ui_scene_linear_to_perceptual_space(from_but, rgb_perceptual); + } + + if (cpicker->is_init == false) { + ui_color_picker_rgb_to_hsv(rgb_perceptual, cpicker->hsv_perceptual); + copy_v3_v3(cpicker->hsv_perceptual_init, cpicker->hsv_perceptual); + } + else { + ui_color_picker_rgb_to_hsv_compat(rgb_perceptual, cpicker->hsv_perceptual); + } + + cpicker->is_init = true; +} + +void ui_but_hsv_set(uiBut *but) +{ + float rgb_perceptual[3]; + ColorPicker *cpicker = static_cast(but->custom_data); + float *hsv_perceptual = cpicker->hsv_perceptual; + + ui_color_picker_hsv_to_rgb(hsv_perceptual, rgb_perceptual); + + ui_but_v3_set(but, rgb_perceptual); +} + +/* Updates all buttons who share the same color picker as the one passed + * also used by small picker, be careful with name checks below... */ +static void ui_update_color_picker_buts_rgb(uiBut *from_but, + uiBlock *block, + ColorPicker *cpicker, + const float rgb_scene_linear[3]) +{ + ui_color_picker_update_hsv(cpicker, from_but, rgb_scene_linear); + + /* this updates button strings, + * is hackish... but button pointers are on stack of caller function */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (bt->custom_data != cpicker) { + continue; + } + + if (bt->rnaprop) { + ui_but_v3_set(bt, rgb_scene_linear); + + /* original button that created the color picker already does undo + * push, so disable it on RNA buttons in the color picker block */ + UI_but_flag_disable(bt, UI_BUT_UNDO); + } + else if (STREQ(bt->str, "Hex: ")) { + float rgb_hex[3]; + uchar rgb_hex_uchar[3]; + char col[16]; + + /* Hex code is assumed to be in sRGB space + * (coming from other applications, web, etc) */ + copy_v3_v3(rgb_hex, rgb_scene_linear); + if (from_but && !ui_but_is_color_gamma(from_but)) { + IMB_colormanagement_scene_linear_to_srgb_v3(rgb_hex); + ui_color_picker_rgb_round(rgb_hex); + } + + rgb_float_to_uchar(rgb_hex_uchar, rgb_hex); + BLI_snprintf(col, sizeof(col), "%02X%02X%02X", UNPACK3_EX((uint), rgb_hex_uchar, )); + + strcpy(bt->poin, col); + } + else if (bt->str[1] == ' ') { + if (bt->str[0] == 'R') { + ui_but_value_set(bt, rgb_scene_linear[0]); + } + else if (bt->str[0] == 'G') { + ui_but_value_set(bt, rgb_scene_linear[1]); + } + else if (bt->str[0] == 'B') { + ui_but_value_set(bt, rgb_scene_linear[2]); + } + else if (bt->str[0] == 'H') { + ui_but_value_set(bt, cpicker->hsv_scene_linear[0]); + } + else if (bt->str[0] == 'S') { + ui_but_value_set(bt, cpicker->hsv_scene_linear[1]); + } + else if (bt->str[0] == 'V') { + ui_but_value_set(bt, cpicker->hsv_scene_linear[2]); + } + else if (bt->str[0] == 'L') { + ui_but_value_set(bt, cpicker->hsv_scene_linear[2]); + } + } + + ui_but_update(bt); + } +} + +static void ui_colorpicker_rgba_update_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) +{ + uiBut *but = (uiBut *)bt1; + uiPopupBlockHandle *popup = but->block->handle; + PropertyRNA *prop = but->rnaprop; + PointerRNA ptr = but->rnapoin; + float rgb_scene_linear[4]; + + if (prop) { + RNA_property_float_get_array(&ptr, prop, rgb_scene_linear); + ui_update_color_picker_buts_rgb( + but, but->block, static_cast(but->custom_data), rgb_scene_linear); + } + + if (popup) { + popup->menuretval = UI_RETURN_UPDATE; + } +} + +static void ui_colorpicker_hsv_update_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) +{ + uiBut *but = (uiBut *)bt1; + uiPopupBlockHandle *popup = but->block->handle; + float rgb_scene_linear[3]; + ColorPicker *cpicker = static_cast(but->custom_data); + + ui_color_picker_hsv_to_rgb(cpicker->hsv_scene_linear, rgb_scene_linear); + ui_update_color_picker_buts_rgb(but, but->block, cpicker, rgb_scene_linear); + + if (popup) { + popup->menuretval = UI_RETURN_UPDATE; + } +} + +static void ui_colorpicker_hex_rna_cb(bContext *UNUSED(C), void *bt1, void *hexcl) +{ + uiBut *but = (uiBut *)bt1; + uiPopupBlockHandle *popup = but->block->handle; + ColorPicker *cpicker = static_cast(but->custom_data); + char *hexcol = (char *)hexcl; + float rgb[3]; + + hex_to_rgb(hexcol, rgb, rgb + 1, rgb + 2); + + /* Hex code is assumed to be in sRGB space (coming from other applications, web, etc) */ + if (!ui_but_is_color_gamma(but)) { + IMB_colormanagement_srgb_to_scene_linear_v3(rgb); + ui_color_picker_rgb_round(rgb); + } + + ui_update_color_picker_buts_rgb(but, but->block, cpicker, rgb); + + if (popup) { + popup->menuretval = UI_RETURN_UPDATE; + } +} + +static void ui_popup_close_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) +{ + uiBut *but = (uiBut *)bt1; + uiPopupBlockHandle *popup = but->block->handle; + + if (popup) { + ColorPicker *cpicker = static_cast(but->custom_data); + BLI_assert(cpicker->is_init); + popup->menuretval = (equals_v3v3(cpicker->hsv_perceptual, cpicker->hsv_perceptual_init) ? + UI_RETURN_CANCEL : + UI_RETURN_OK); + } +} + +static void ui_colorpicker_hide_reveal(uiBlock *block, ePickerType colormode) +{ + /* tag buttons */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if ((bt->func == ui_colorpicker_rgba_update_cb) && (bt->type == UI_BTYPE_NUM_SLIDER) && + (bt->rnaindex != 3)) { + /* RGB sliders (color circle and alpha are always shown) */ + SET_FLAG_FROM_TEST(bt->flag, (colormode != PICKER_TYPE_RGB), UI_HIDDEN); + } + else if (bt->func == ui_colorpicker_hsv_update_cb) { + /* HSV sliders */ + SET_FLAG_FROM_TEST(bt->flag, (colormode != PICKER_TYPE_HSV), UI_HIDDEN); + } + else if (bt->func == ui_colorpicker_hex_rna_cb || bt->type == UI_BTYPE_LABEL) { + /* HEX input or gamma correction status label */ + SET_FLAG_FROM_TEST(bt->flag, (colormode != PICKER_TYPE_HEX), UI_HIDDEN); + } + } +} + +static void ui_colorpicker_create_mode_cb(bContext *UNUSED(C), void *bt1, void *UNUSED(arg)) +{ + uiBut *bt = static_cast(bt1); + const short colormode = ui_but_value_get(bt); + ui_colorpicker_hide_reveal(bt->block, (ePickerType)colormode); +} + +#define PICKER_H (7.5f * U.widget_unit) +#define PICKER_W (7.5f * U.widget_unit) +#define PICKER_SPACE (0.3f * U.widget_unit) +#define PICKER_BAR (0.7f * U.widget_unit) + +#define PICKER_TOTAL_W (PICKER_W + PICKER_SPACE + PICKER_BAR) + +static void ui_colorpicker_circle(uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + ColorPicker *cpicker) +{ + uiBut *bt; + uiButHSVCube *hsv_but; + + /* HS circle */ + bt = uiDefButR_prop(block, + UI_BTYPE_HSVCIRCLE, + 0, + "", + 0, + 0, + PICKER_H, + PICKER_W, + ptr, + prop, + -1, + 0.0, + 0.0, + 0.0, + 0, + TIP_("Color")); + UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); + bt->custom_data = cpicker; + + /* value */ + if (U.color_picker_type == USER_CP_CIRCLE_HSL) { + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + PICKER_W + PICKER_SPACE, + 0, + PICKER_BAR, + PICKER_H, + ptr, + prop, + -1, + 0.0, + 0.0, + 0, + 0, + "Lightness"); + hsv_but->gradient_type = UI_GRAD_L_ALT; + UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, nullptr); + } + else { + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + PICKER_W + PICKER_SPACE, + 0, + PICKER_BAR, + PICKER_H, + ptr, + prop, + -1, + 0.0, + 0.0, + 0, + 0, + TIP_("Value")); + hsv_but->gradient_type = UI_GRAD_V_ALT; + UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, nullptr); + } + hsv_but->but.custom_data = cpicker; +} + +static void ui_colorpicker_square(uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + eButGradientType type, + ColorPicker *cpicker) +{ + uiButHSVCube *hsv_but; + + BLI_assert(type <= UI_GRAD_HS); + + /* HS square */ + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + 0, + PICKER_BAR + PICKER_SPACE, + PICKER_TOTAL_W, + PICKER_H, + ptr, + prop, + -1, + 0.0, + 0.0, + 0, + 0, + TIP_("Color")); + hsv_but->gradient_type = type; + UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, nullptr); + hsv_but->but.custom_data = cpicker; + + /* value */ + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + 0, + 0, + PICKER_TOTAL_W, + PICKER_BAR, + ptr, + prop, + -1, + 0.0, + 0.0, + 0, + 0, + TIP_("Value")); + hsv_but->gradient_type = (eButGradientType)(type + 3); + UI_but_func_set(&hsv_but->but, ui_colorpicker_rgba_update_cb, &hsv_but->but, nullptr); + hsv_but->but.custom_data = cpicker; +} + +/* a HS circle, V slider, rgb/hsv/hex sliders */ +static void ui_block_colorpicker(uiBlock *block, + uiBut *from_but, + float rgba_scene_linear[4], + bool show_picker) +{ + /* ePickerType */ + static char colormode = 1; + uiBut *bt; + int width, butwidth; + static char hexcol[128]; + float softmin, softmax, hardmin, hardmax, step, precision; + int yco; + ColorPicker *cpicker = ui_block_colorpicker_create(block); + PointerRNA *ptr = &from_but->rnapoin; + PropertyRNA *prop = from_but->rnaprop; + + width = PICKER_TOTAL_W; + butwidth = width - 1.5f * UI_UNIT_X; + + /* sneaky way to check for alpha */ + rgba_scene_linear[3] = FLT_MAX; + + RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); + RNA_property_float_range(ptr, prop, &hardmin, &hardmax); + RNA_property_float_get_array(ptr, prop, rgba_scene_linear); + + ui_color_picker_update_hsv(cpicker, from_but, rgba_scene_linear); + + /* when the softmax isn't defined in the RNA, + * using very large numbers causes sRGB/linear round trip to fail. */ + if (softmax == FLT_MAX) { + softmax = 1.0f; + } + + switch (U.color_picker_type) { + case USER_CP_SQUARE_SV: + ui_colorpicker_square(block, ptr, prop, UI_GRAD_SV, cpicker); + break; + case USER_CP_SQUARE_HS: + ui_colorpicker_square(block, ptr, prop, UI_GRAD_HS, cpicker); + break; + case USER_CP_SQUARE_HV: + ui_colorpicker_square(block, ptr, prop, UI_GRAD_HV, cpicker); + break; + + /* user default */ + case USER_CP_CIRCLE_HSV: + case USER_CP_CIRCLE_HSL: + default: + ui_colorpicker_circle(block, ptr, prop, cpicker); + break; + } + + /* mode */ + yco = -1.5f * UI_UNIT_Y; + UI_block_align_begin(block); + bt = uiDefButC(block, + UI_BTYPE_ROW, + 0, + IFACE_("RGB"), + 0, + yco, + width / 3, + UI_UNIT_Y, + &colormode, + 0.0, + (float)PICKER_TYPE_RGB, + 0, + 0, + ""); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_drawflag_disable(bt, UI_BUT_TEXT_LEFT); + UI_but_func_set(bt, ui_colorpicker_create_mode_cb, bt, nullptr); + bt->custom_data = cpicker; + bt = uiDefButC(block, + UI_BTYPE_ROW, + 0, + IFACE_((U.color_picker_type == USER_CP_CIRCLE_HSL) ? "HSL" : "HSV"), + width / 3, + yco, + width / 3, + UI_UNIT_Y, + &colormode, + 0.0, + PICKER_TYPE_HSV, + 0, + 0, + ""); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_drawflag_disable(bt, UI_BUT_TEXT_LEFT); + UI_but_func_set(bt, ui_colorpicker_create_mode_cb, bt, nullptr); + bt->custom_data = cpicker; + bt = uiDefButC(block, + UI_BTYPE_ROW, + 0, + IFACE_("Hex"), + 2 * width / 3, + yco, + width / 3, + UI_UNIT_Y, + &colormode, + 0.0, + PICKER_TYPE_HEX, + 0, + 0, + ""); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_drawflag_disable(bt, UI_BUT_TEXT_LEFT); + UI_but_func_set(bt, ui_colorpicker_create_mode_cb, bt, nullptr); + bt->custom_data = cpicker; + UI_block_align_end(block); + + yco = -3.0f * UI_UNIT_Y; + if (show_picker) { + bt = uiDefIconButO(block, + UI_BTYPE_BUT, + "UI_OT_eyedropper_color", + WM_OP_INVOKE_DEFAULT, + ICON_EYEDROPPER, + butwidth + 10, + yco, + UI_UNIT_X, + UI_UNIT_Y, + nullptr); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_drawflag_disable(bt, UI_BUT_ICON_LEFT); + UI_but_func_set(bt, ui_popup_close_cb, bt, nullptr); + bt->custom_data = cpicker; + } + + /* NOTE: don't disable UI_BUT_UNDO for RGBA values, since these don't add undo steps. */ + + /* RGB values */ + UI_block_align_begin(block); + bt = uiDefButR_prop(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("R:"), + 0, + yco, + butwidth, + UI_UNIT_Y, + ptr, + prop, + 0, + 0.0, + 0.0, + 0, + 3, + TIP_("Red")); + UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); + bt->custom_data = cpicker; + bt = uiDefButR_prop(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("G:"), + 0, + yco -= UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + ptr, + prop, + 1, + 0.0, + 0.0, + 0, + 3, + TIP_("Green")); + UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); + bt->custom_data = cpicker; + bt = uiDefButR_prop(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("B:"), + 0, + yco -= UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + ptr, + prop, + 2, + 0.0, + 0.0, + 0, + 3, + TIP_("Blue")); + UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); + bt->custom_data = cpicker; + + /* Could use: + * uiItemFullR(col, ptr, prop, -1, 0, UI_ITEM_R_EXPAND | UI_ITEM_R_SLIDER, "", ICON_NONE); + * but need to use UI_but_func_set for updating other fake buttons */ + + /* HSV values */ + yco = -3.0f * UI_UNIT_Y; + UI_block_align_begin(block); + bt = uiDefButF(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("H:"), + 0, + yco, + butwidth, + UI_UNIT_Y, + cpicker->hsv_scene_linear, + 0.0, + 1.0, + 10, + 3, + TIP_("Hue")); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, nullptr); + bt->custom_data = cpicker; + bt = uiDefButF(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("S:"), + 0, + yco -= UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + cpicker->hsv_scene_linear + 1, + 0.0, + 1.0, + 10, + 3, + TIP_("Saturation")); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, nullptr); + bt->custom_data = cpicker; + if (U.color_picker_type == USER_CP_CIRCLE_HSL) { + bt = uiDefButF(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("L:"), + 0, + yco -= UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + cpicker->hsv_scene_linear + 2, + 0.0, + 1.0, + 10, + 3, + TIP_("Lightness")); + } + else { + bt = uiDefButF(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("V:"), + 0, + yco -= UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + cpicker->hsv_scene_linear + 2, + 0.0, + softmax, + 10, + 3, + TIP_("Value")); + } + UI_but_flag_disable(bt, UI_BUT_UNDO); + + bt->hardmax = hardmax; /* not common but rgb may be over 1.0 */ + UI_but_func_set(bt, ui_colorpicker_hsv_update_cb, bt, nullptr); + bt->custom_data = cpicker; + + UI_block_align_end(block); + + if (rgba_scene_linear[3] != FLT_MAX) { + bt = uiDefButR_prop(block, + UI_BTYPE_NUM_SLIDER, + 0, + IFACE_("A: "), + 0, + yco -= UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + ptr, + prop, + 3, + 0.0, + 0.0, + 0, + 3, + TIP_("Alpha")); + UI_but_func_set(bt, ui_colorpicker_rgba_update_cb, bt, nullptr); + bt->custom_data = cpicker; + } + else { + rgba_scene_linear[3] = 1.0f; + } + + /* Hex color is in sRGB space. */ + float rgb_hex[3]; + uchar rgb_hex_uchar[3]; + + copy_v3_v3(rgb_hex, rgba_scene_linear); + + if (!ui_but_is_color_gamma(from_but)) { + IMB_colormanagement_scene_linear_to_srgb_v3(rgb_hex); + ui_color_picker_rgb_round(rgb_hex); + } + + rgb_float_to_uchar(rgb_hex_uchar, rgb_hex); + BLI_snprintf(hexcol, sizeof(hexcol), "%02X%02X%02X", UNPACK3_EX((uint), rgb_hex_uchar, )); + + yco = -3.0f * UI_UNIT_Y; + bt = uiDefBut(block, + UI_BTYPE_TEXT, + 0, + IFACE_("Hex: "), + 0, + yco, + butwidth, + UI_UNIT_Y, + hexcol, + 0, + 8, + 0, + 0, + TIP_("Hex triplet for color (#RRGGBB)")); + UI_but_flag_disable(bt, UI_BUT_UNDO); + UI_but_func_set(bt, ui_colorpicker_hex_rna_cb, bt, hexcol); + bt->custom_data = cpicker; + uiDefBut(block, + UI_BTYPE_LABEL, + 0, + IFACE_("(Gamma Corrected)"), + 0, + yco - UI_UNIT_Y, + butwidth, + UI_UNIT_Y, + nullptr, + 0.0, + 0.0, + 0, + 0, + ""); + + ui_colorpicker_hide_reveal(block, (ePickerType)colormode); +} + +static int ui_colorpicker_small_wheel_cb(const bContext *UNUSED(C), + uiBlock *block, + const wmEvent *event) +{ + float add = 0.0f; + + if (event->type == WHEELUPMOUSE) { + add = 0.05f; + } + else if (event->type == WHEELDOWNMOUSE) { + add = -0.05f; + } + + if (add != 0.0f) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->type == UI_BTYPE_HSVCUBE && but->active == nullptr) { + uiPopupBlockHandle *popup = block->handle; + ColorPicker *cpicker = static_cast(but->custom_data); + float *hsv_perceptual = cpicker->hsv_perceptual; + + float rgb_perceptual[3]; + ui_but_v3_get(but, rgb_perceptual); + ui_scene_linear_to_perceptual_space(but, rgb_perceptual); + ui_color_picker_rgb_to_hsv_compat(rgb_perceptual, hsv_perceptual); + + hsv_perceptual[2] = clamp_f(hsv_perceptual[2] + add, 0.0f, 1.0f); + + float rgb_scene_linear[3]; + ui_color_picker_hsv_to_rgb(hsv_perceptual, rgb_scene_linear); + ui_perceptual_to_scene_linear_space(but, rgb_scene_linear); + ui_but_v3_set(but, rgb_scene_linear); + + ui_update_color_picker_buts_rgb(but, block, cpicker, rgb_scene_linear); + if (popup) { + popup->menuretval = UI_RETURN_UPDATE; + } + + return 1; + } + } + } + return 0; +} + +uiBlock *ui_block_func_COLOR(bContext *C, uiPopupBlockHandle *handle, void *arg_but) +{ + uiBut *but = static_cast(arg_but); + uiBlock *block; + bool show_picker = true; + + block = UI_block_begin(C, handle->region, __func__, UI_EMBOSS); + + if (ui_but_is_color_gamma(but)) { + block->is_color_gamma_picker = true; + } + + if (but->block) { + /* if color block is invoked from a popup we wouldn't be able to set color properly + * this is because color picker will close popups first and then will try to figure + * out active button RNA, and of course it'll fail + */ + show_picker = (but->block->flag & UI_BLOCK_POPUP) == 0; + } + + copy_v3_v3(handle->retvec, but->editvec); + + ui_block_colorpicker(block, but, handle->retvec, show_picker); + + block->flag = UI_BLOCK_LOOP | UI_BLOCK_KEEP_OPEN | UI_BLOCK_OUT_1 | UI_BLOCK_MOVEMOUSE_QUIT; + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + UI_block_bounds_set_normal(block, 0.5 * UI_UNIT_X); + + block->block_event_func = ui_colorpicker_small_wheel_cb; + + /* and lets go */ + block->direction = UI_DIR_UP; + + return block; +} + +ColorPicker *ui_block_colorpicker_create(struct uiBlock *block) +{ + ColorPicker *cpicker = MEM_cnew(__func__); + BLI_addhead(&block->color_pickers.list, cpicker); + + return cpicker; +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_region_hud.c b/source/blender/editors/interface/interface_region_hud.c deleted file mode 100644 index 4d72cd5382c..00000000000 --- a/source/blender/editors/interface/interface_region_hud.c +++ /dev/null @@ -1,387 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Floating Persistent Region - */ - -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_screen_types.h" -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "BLT_translation.h" - -#include "ED_screen.h" -#include "ED_undo.h" - -#include "GPU_framebuffer.h" -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Utilities - * \{ */ - -struct HudRegionData { - short regionid; -}; - -static bool last_redo_poll(const bContext *C, short region_type) -{ - wmOperator *op = WM_operator_last_redo(C); - if (op == NULL) { - return false; - } - - bool success = false; - { - /* Make sure that we are using the same region type as the original - * operator call. Otherwise we would be polling the operator with the - * wrong context. - */ - ScrArea *area = CTX_wm_area(C); - ARegion *region_op = (region_type != -1) ? BKE_area_find_region_type(area, region_type) : NULL; - ARegion *region_prev = CTX_wm_region(C); - CTX_wm_region_set((bContext *)C, region_op); - - if (WM_operator_repeat_check(C, op) && WM_operator_check_ui_empty(op->type) == false) { - success = WM_operator_poll((bContext *)C, op->type); - } - CTX_wm_region_set((bContext *)C, region_prev); - } - return success; -} - -static void hud_region_hide(ARegion *region) -{ - region->flag |= RGN_FLAG_HIDDEN; - /* Avoids setting 'AREA_FLAG_REGION_SIZE_UPDATE' - * since other regions don't depend on this. */ - BLI_rcti_init(®ion->winrct, 0, 0, 0, 0); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Redo Panel - * \{ */ - -static bool hud_panel_operator_redo_poll(const bContext *C, PanelType *UNUSED(pt)) -{ - ScrArea *area = CTX_wm_area(C); - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD); - if (region != NULL) { - struct HudRegionData *hrd = region->regiondata; - if (hrd != NULL) { - return last_redo_poll(C, hrd->regionid); - } - } - return false; -} - -static void hud_panel_operator_redo_draw_header(const bContext *C, Panel *panel) -{ - wmOperator *op = WM_operator_last_redo(C); - BLI_strncpy(panel->drawname, WM_operatortype_name(op->type, op->ptr), sizeof(panel->drawname)); -} - -static void hud_panel_operator_redo_draw(const bContext *C, Panel *panel) -{ - wmOperator *op = WM_operator_last_redo(C); - if (op == NULL) { - return; - } - if (!WM_operator_check_ui_enabled(C, op->type->name)) { - uiLayoutSetEnabled(panel->layout, false); - } - uiLayout *col = uiLayoutColumn(panel->layout, false); - uiTemplateOperatorRedoProperties(col, C); -} - -static void hud_panels_register(ARegionType *art, int space_type, int region_type) -{ - PanelType *pt; - - pt = MEM_callocN(sizeof(PanelType), __func__); - strcpy(pt->idname, "OPERATOR_PT_redo"); - strcpy(pt->label, N_("Redo")); - strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); - pt->draw_header = hud_panel_operator_redo_draw_header; - pt->draw = hud_panel_operator_redo_draw; - pt->poll = hud_panel_operator_redo_poll; - pt->space_type = space_type; - pt->region_type = region_type; - pt->flag |= PANEL_TYPE_DEFAULT_CLOSED; - BLI_addtail(&art->paneltypes, pt); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Callbacks for Floating Region - * \{ */ - -static void hud_region_init(wmWindowManager *wm, ARegion *region) -{ - ED_region_panels_init(wm, region); - UI_region_handlers_add(®ion->handlers); - region->flag |= RGN_FLAG_TEMP_REGIONDATA; -} - -static void hud_region_free(ARegion *region) -{ - MEM_SAFE_FREE(region->regiondata); -} - -static void hud_region_layout(const bContext *C, ARegion *region) -{ - struct HudRegionData *hrd = region->regiondata; - if (hrd == NULL || !last_redo_poll(C, hrd->regionid)) { - ED_region_tag_redraw(region); - hud_region_hide(region); - return; - } - - ScrArea *area = CTX_wm_area(C); - const int size_y = region->sizey; - - ED_region_panels_layout(C, region); - - if (region->panels.first && - ((area->flag & AREA_FLAG_REGION_SIZE_UPDATE) || (region->sizey != size_y))) { - int winx_new = UI_DPI_FAC * (region->sizex + 0.5f); - int winy_new = UI_DPI_FAC * (region->sizey + 0.5f); - View2D *v2d = ®ion->v2d; - - if (region->flag & RGN_FLAG_SIZE_CLAMP_X) { - CLAMP_MAX(winx_new, region->winx); - } - if (region->flag & RGN_FLAG_SIZE_CLAMP_Y) { - CLAMP_MAX(winy_new, region->winy); - } - - region->winx = winx_new; - region->winy = winy_new; - - region->winrct.xmax = (region->winrct.xmin + region->winx) - 1; - region->winrct.ymax = (region->winrct.ymin + region->winy) - 1; - - UI_view2d_region_reinit(v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy); - - /* Weak, but needed to avoid glitches, especially with hi-dpi - * (where resizing the view glitches often). - * Fortunately this only happens occasionally. */ - ED_region_panels_layout(C, region); - } - - /* restore view matrix */ - UI_view2d_view_restore(C); -} - -static void hud_region_draw(const bContext *C, ARegion *region) -{ - UI_view2d_view_ortho(®ion->v2d); - wmOrtho2_region_pixelspace(region); - GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f); - - if ((region->flag & RGN_FLAG_HIDDEN) == 0) { - ui_draw_menu_back(NULL, - NULL, - &(rcti){ - .xmax = region->winx, - .ymax = region->winy, - }); - ED_region_panels_draw(C, region); - } -} - -ARegionType *ED_area_type_hud(int space_type) -{ - ARegionType *art = MEM_callocN(sizeof(ARegionType), __func__); - art->regionid = RGN_TYPE_HUD; - art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D; - art->layout = hud_region_layout; - art->draw = hud_region_draw; - art->init = hud_region_init; - art->free = hud_region_free; - - /* We need to indicate a preferred size to avoid false `RGN_FLAG_TOO_SMALL` - * the first time the region is created. */ - art->prefsizex = AREAMINX; - art->prefsizey = HEADERY; - - hud_panels_register(art, space_type, art->regionid); - - art->lock = 1; /* can become flag, see BKE_spacedata_draw_locks */ - return art; -} - -static ARegion *hud_region_add(ScrArea *area) -{ - ARegion *region = MEM_callocN(sizeof(ARegion), "area region"); - ARegion *region_win = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - if (region_win) { - BLI_insertlinkbefore(&area->regionbase, region_win, region); - } - else { - BLI_addtail(&area->regionbase, region); - } - region->regiontype = RGN_TYPE_HUD; - region->alignment = RGN_ALIGN_FLOAT; - region->overlap = true; - region->flag |= RGN_FLAG_DYNAMIC_SIZE; - - if (region_win) { - float x, y; - - UI_view2d_scroller_size_get(®ion_win->v2d, &x, &y); - region->runtime.offset_x = x; - region->runtime.offset_y = y; - } - - return region; -} - -void ED_area_type_hud_clear(wmWindowManager *wm, ScrArea *area_keep) -{ - LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { - bScreen *screen = WM_window_get_active_screen(win); - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - if (area != area_keep) { - LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - if (region->regiontype == RGN_TYPE_HUD) { - if ((region->flag & RGN_FLAG_HIDDEN) == 0) { - hud_region_hide(region); - ED_region_tag_redraw(region); - ED_area_tag_redraw(area); - } - } - } - } - } - } -} - -void ED_area_type_hud_ensure(bContext *C, ScrArea *area) -{ - wmWindowManager *wm = CTX_wm_manager(C); - ED_area_type_hud_clear(wm, area); - - ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_HUD); - if (art == NULL) { - return; - } - - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD); - - if (region && (region->flag & RGN_FLAG_HIDDEN_BY_USER)) { - /* The region is intentionally hidden by the user, don't show it. */ - hud_region_hide(region); - return; - } - - bool init = false; - const bool was_hidden = region == NULL || region->visible == false; - ARegion *region_op = CTX_wm_region(C); - BLI_assert((region_op == NULL) || (region_op->regiontype != RGN_TYPE_HUD)); - if (!last_redo_poll(C, region_op ? region_op->regiontype : -1)) { - if (region) { - ED_region_tag_redraw(region); - hud_region_hide(region); - } - return; - } - - if (region == NULL) { - init = true; - region = hud_region_add(area); - region->type = art; - } - - /* Let 'ED_area_update_region_sizes' do the work of placing the region. - * Otherwise we could set the 'region->winrct' & 'region->winx/winy' here. */ - if (init) { - area->flag |= AREA_FLAG_REGION_SIZE_UPDATE; - } - else { - if (region->flag & RGN_FLAG_HIDDEN) { - /* Also forces recalculating HUD size in hud_region_layout(). */ - area->flag |= AREA_FLAG_REGION_SIZE_UPDATE; - } - region->flag &= ~RGN_FLAG_HIDDEN; - } - - { - struct HudRegionData *hrd = region->regiondata; - if (hrd == NULL) { - hrd = MEM_callocN(sizeof(*hrd), __func__); - region->regiondata = hrd; - } - if (region_op) { - hrd->regionid = region_op->regiontype; - } - else { - hrd->regionid = -1; - } - } - - if (init) { - /* This is needed or 'winrct' will be invalid. */ - wmWindow *win = CTX_wm_window(C); - ED_area_update_region_sizes(wm, win, area); - } - - ED_region_floating_init(region); - ED_region_tag_redraw(region); - - /* Reset zoom level (not well supported). */ - region->v2d.cur = region->v2d.tot = (rctf){ - .xmax = region->winx, - .ymax = region->winy, - }; - region->v2d.minzoom = 1.0f; - region->v2d.maxzoom = 1.0f; - - region->visible = !(region->flag & RGN_FLAG_HIDDEN); - - /* We shouldn't need to do this every time :S */ - /* XXX, this is evil! - it also makes the menu show on first draw. :( */ - if (region->visible) { - ARegion *region_prev = CTX_wm_region(C); - CTX_wm_region_set((bContext *)C, region); - hud_region_layout(C, region); - if (was_hidden) { - region->winx = region->v2d.winx; - region->winy = region->v2d.winy; - region->v2d.cur = region->v2d.tot = (rctf){ - .xmax = region->winx, - .ymax = region->winy, - }; - } - CTX_wm_region_set((bContext *)C, region_prev); - } - - region->visible = !((region->flag & RGN_FLAG_HIDDEN) || (region->flag & RGN_FLAG_TOO_SMALL)); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_hud.cc b/source/blender/editors/interface/interface_region_hud.cc new file mode 100644 index 00000000000..d6166694a4a --- /dev/null +++ b/source/blender/editors/interface/interface_region_hud.cc @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Floating Persistent Region + */ + +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "BLT_translation.h" + +#include "ED_screen.h" +#include "ED_undo.h" + +#include "GPU_framebuffer.h" +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Utilities + * \{ */ + +struct HudRegionData { + short regionid; +}; + +static bool last_redo_poll(const bContext *C, short region_type) +{ + wmOperator *op = WM_operator_last_redo(C); + if (op == nullptr) { + return false; + } + + bool success = false; + { + /* Make sure that we are using the same region type as the original + * operator call. Otherwise we would be polling the operator with the + * wrong context. + */ + ScrArea *area = CTX_wm_area(C); + ARegion *region_op = (region_type != -1) ? BKE_area_find_region_type(area, region_type) : + nullptr; + ARegion *region_prev = CTX_wm_region(C); + CTX_wm_region_set((bContext *)C, region_op); + + if (WM_operator_repeat_check(C, op) && WM_operator_check_ui_empty(op->type) == false) { + success = WM_operator_poll((bContext *)C, op->type); + } + CTX_wm_region_set((bContext *)C, region_prev); + } + return success; +} + +static void hud_region_hide(ARegion *region) +{ + region->flag |= RGN_FLAG_HIDDEN; + /* Avoids setting 'AREA_FLAG_REGION_SIZE_UPDATE' + * since other regions don't depend on this. */ + BLI_rcti_init(®ion->winrct, 0, 0, 0, 0); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Redo Panel + * \{ */ + +static bool hud_panel_operator_redo_poll(const bContext *C, PanelType *UNUSED(pt)) +{ + ScrArea *area = CTX_wm_area(C); + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD); + if (region != nullptr) { + HudRegionData *hrd = static_cast(region->regiondata); + if (hrd != nullptr) { + return last_redo_poll(C, hrd->regionid); + } + } + return false; +} + +static void hud_panel_operator_redo_draw_header(const bContext *C, Panel *panel) +{ + wmOperator *op = WM_operator_last_redo(C); + BLI_strncpy(panel->drawname, WM_operatortype_name(op->type, op->ptr), sizeof(panel->drawname)); +} + +static void hud_panel_operator_redo_draw(const bContext *C, Panel *panel) +{ + wmOperator *op = WM_operator_last_redo(C); + if (op == nullptr) { + return; + } + if (!WM_operator_check_ui_enabled(C, op->type->name)) { + uiLayoutSetEnabled(panel->layout, false); + } + uiLayout *col = uiLayoutColumn(panel->layout, false); + uiTemplateOperatorRedoProperties(col, C); +} + +static void hud_panels_register(ARegionType *art, int space_type, int region_type) +{ + PanelType *pt = MEM_cnew(__func__); + strcpy(pt->idname, "OPERATOR_PT_redo"); + strcpy(pt->label, N_("Redo")); + strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + pt->draw_header = hud_panel_operator_redo_draw_header; + pt->draw = hud_panel_operator_redo_draw; + pt->poll = hud_panel_operator_redo_poll; + pt->space_type = space_type; + pt->region_type = region_type; + pt->flag |= PANEL_TYPE_DEFAULT_CLOSED; + BLI_addtail(&art->paneltypes, pt); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Callbacks for Floating Region + * \{ */ + +static void hud_region_init(wmWindowManager *wm, ARegion *region) +{ + ED_region_panels_init(wm, region); + UI_region_handlers_add(®ion->handlers); + region->flag |= RGN_FLAG_TEMP_REGIONDATA; +} + +static void hud_region_free(ARegion *region) +{ + MEM_SAFE_FREE(region->regiondata); +} + +static void hud_region_layout(const bContext *C, ARegion *region) +{ + HudRegionData *hrd = static_cast(region->regiondata); + if (hrd == nullptr || !last_redo_poll(C, hrd->regionid)) { + ED_region_tag_redraw(region); + hud_region_hide(region); + return; + } + + ScrArea *area = CTX_wm_area(C); + const int size_y = region->sizey; + + ED_region_panels_layout(C, region); + + if (region->panels.first && + ((area->flag & AREA_FLAG_REGION_SIZE_UPDATE) || (region->sizey != size_y))) { + int winx_new = UI_DPI_FAC * (region->sizex + 0.5f); + int winy_new = UI_DPI_FAC * (region->sizey + 0.5f); + View2D *v2d = ®ion->v2d; + + if (region->flag & RGN_FLAG_SIZE_CLAMP_X) { + CLAMP_MAX(winx_new, region->winx); + } + if (region->flag & RGN_FLAG_SIZE_CLAMP_Y) { + CLAMP_MAX(winy_new, region->winy); + } + + region->winx = winx_new; + region->winy = winy_new; + + region->winrct.xmax = (region->winrct.xmin + region->winx) - 1; + region->winrct.ymax = (region->winrct.ymin + region->winy) - 1; + + UI_view2d_region_reinit(v2d, V2D_COMMONVIEW_LIST, region->winx, region->winy); + + /* Weak, but needed to avoid glitches, especially with hi-dpi + * (where resizing the view glitches often). + * Fortunately this only happens occasionally. */ + ED_region_panels_layout(C, region); + } + + /* restore view matrix */ + UI_view2d_view_restore(C); +} + +static void hud_region_draw(const bContext *C, ARegion *region) +{ + UI_view2d_view_ortho(®ion->v2d); + wmOrtho2_region_pixelspace(region); + GPU_clear_color(0.0f, 0.0f, 0.0f, 0.0f); + + if ((region->flag & RGN_FLAG_HIDDEN) == 0) { + rcti reset_rect = {}; + reset_rect.xmax = region->winx; + reset_rect.ymax = region->winy; + ui_draw_menu_back(nullptr, nullptr, &reset_rect); + ED_region_panels_draw(C, region); + } +} + +ARegionType *ED_area_type_hud(int space_type) +{ + ARegionType *art = MEM_cnew(__func__); + art->regionid = RGN_TYPE_HUD; + art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D; + art->layout = hud_region_layout; + art->draw = hud_region_draw; + art->init = hud_region_init; + art->free = hud_region_free; + + /* We need to indicate a preferred size to avoid false `RGN_FLAG_TOO_SMALL` + * the first time the region is created. */ + art->prefsizex = AREAMINX; + art->prefsizey = HEADERY; + + hud_panels_register(art, space_type, art->regionid); + + art->lock = 1; /* can become flag, see BKE_spacedata_draw_locks */ + return art; +} + +static ARegion *hud_region_add(ScrArea *area) +{ + ARegion *region = MEM_cnew(__func__); + ARegion *region_win = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); + if (region_win) { + BLI_insertlinkbefore(&area->regionbase, region_win, region); + } + else { + BLI_addtail(&area->regionbase, region); + } + region->regiontype = RGN_TYPE_HUD; + region->alignment = RGN_ALIGN_FLOAT; + region->overlap = true; + region->flag |= RGN_FLAG_DYNAMIC_SIZE; + + if (region_win) { + float x, y; + + UI_view2d_scroller_size_get(®ion_win->v2d, &x, &y); + region->runtime.offset_x = x; + region->runtime.offset_y = y; + } + + return region; +} + +void ED_area_type_hud_clear(wmWindowManager *wm, ScrArea *area_keep) +{ + LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { + bScreen *screen = WM_window_get_active_screen(win); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + if (area != area_keep) { + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + if (region->regiontype == RGN_TYPE_HUD) { + if ((region->flag & RGN_FLAG_HIDDEN) == 0) { + hud_region_hide(region); + ED_region_tag_redraw(region); + ED_area_tag_redraw(area); + } + } + } + } + } + } +} + +void ED_area_type_hud_ensure(bContext *C, ScrArea *area) +{ + wmWindowManager *wm = CTX_wm_manager(C); + ED_area_type_hud_clear(wm, area); + + ARegionType *art = BKE_regiontype_from_id(area->type, RGN_TYPE_HUD); + if (art == nullptr) { + return; + } + + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HUD); + + if (region && (region->flag & RGN_FLAG_HIDDEN_BY_USER)) { + /* The region is intentionally hidden by the user, don't show it. */ + hud_region_hide(region); + return; + } + + bool init = false; + const bool was_hidden = region == nullptr || region->visible == false; + ARegion *region_op = CTX_wm_region(C); + BLI_assert((region_op == nullptr) || (region_op->regiontype != RGN_TYPE_HUD)); + if (!last_redo_poll(C, region_op ? region_op->regiontype : -1)) { + if (region) { + ED_region_tag_redraw(region); + hud_region_hide(region); + } + return; + } + + if (region == nullptr) { + init = true; + region = hud_region_add(area); + region->type = art; + } + + /* Let 'ED_area_update_region_sizes' do the work of placing the region. + * Otherwise we could set the 'region->winrct' & 'region->winx/winy' here. */ + if (init) { + area->flag |= AREA_FLAG_REGION_SIZE_UPDATE; + } + else { + if (region->flag & RGN_FLAG_HIDDEN) { + /* Also forces recalculating HUD size in hud_region_layout(). */ + area->flag |= AREA_FLAG_REGION_SIZE_UPDATE; + } + region->flag &= ~RGN_FLAG_HIDDEN; + } + + { + HudRegionData *hrd = static_cast(region->regiondata); + if (hrd == nullptr) { + hrd = MEM_cnew(__func__); + region->regiondata = hrd; + } + if (region_op) { + hrd->regionid = region_op->regiontype; + } + else { + hrd->regionid = -1; + } + } + + if (init) { + /* This is needed or 'winrct' will be invalid. */ + wmWindow *win = CTX_wm_window(C); + ED_area_update_region_sizes(wm, win, area); + } + + ED_region_floating_init(region); + ED_region_tag_redraw(region); + + /* Reset zoom level (not well supported). */ + rctf reset_rect = {}; + reset_rect.xmax = region->winx; + reset_rect.ymax = region->winy; + region->v2d.cur = region->v2d.tot = reset_rect; + region->v2d.minzoom = 1.0f; + region->v2d.maxzoom = 1.0f; + + region->visible = !(region->flag & RGN_FLAG_HIDDEN); + + /* We shouldn't need to do this every time :S */ + /* XXX, this is evil! - it also makes the menu show on first draw. :( */ + if (region->visible) { + ARegion *region_prev = CTX_wm_region(C); + CTX_wm_region_set((bContext *)C, region); + hud_region_layout(C, region); + if (was_hidden) { + region->winx = region->v2d.winx; + region->winy = region->v2d.winy; + region->v2d.cur = region->v2d.tot = reset_rect; + } + CTX_wm_region_set((bContext *)C, region_prev); + } + + region->visible = !((region->flag & RGN_FLAG_HIDDEN) || (region->flag & RGN_FLAG_TOO_SMALL)); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_region_menu_pie.c b/source/blender/editors/interface/interface_region_menu_pie.c deleted file mode 100644 index 74e128f3098..00000000000 --- a/source/blender/editors/interface/interface_region_menu_pie.c +++ /dev/null @@ -1,407 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Pie Menu Region - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_blenlib.h" -#include "BLI_utildefines.h" - -#include "PIL_time.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" -#include "RNA_prototypes.h" - -#include "UI_interface.h" - -#include "BLT_translation.h" - -#include "ED_screen.h" - -#include "interface_intern.h" -#include "interface_regions_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Pie Menu - * \{ */ - -struct uiPieMenu { - uiBlock *block_radial; /* radial block of the pie menu (more could be added later) */ - uiLayout *layout; - int mx, my; -}; - -static uiBlock *ui_block_func_PIE(bContext *UNUSED(C), uiPopupBlockHandle *handle, void *arg_pie) -{ - uiBlock *block; - uiPieMenu *pie = arg_pie; - int minwidth, width, height; - - minwidth = UI_MENU_WIDTH_MIN; - block = pie->block_radial; - - /* in some cases we create the block before the region, - * so we set it delayed here if necessary */ - if (BLI_findindex(&handle->region->uiblocks, block) == -1) { - UI_block_region_set(block, handle->region); - } - - UI_block_layout_resolve(block, &width, &height); - - UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - - block->minbounds = minwidth; - block->bounds = 1; - block->bounds_offset[0] = 0; - block->bounds_offset[1] = 0; - block->bounds_type = UI_BLOCK_BOUNDS_PIE_CENTER; - - block->pie_data.pie_center_spawned[0] = pie->mx; - block->pie_data.pie_center_spawned[1] = pie->my; - - return pie->block_radial; -} - -static float ui_pie_menu_title_width(const char *name, int icon) -{ - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - return (UI_fontstyle_string_width(fstyle, name) + (UI_UNIT_X * (1.50f + (icon ? 0.25f : 0.0f)))); -} - -uiPieMenu *UI_pie_menu_begin(struct bContext *C, const char *title, int icon, const wmEvent *event) -{ - const uiStyle *style = UI_style_get_dpi(); - uiPieMenu *pie; - short event_type; - - wmWindow *win = CTX_wm_window(C); - - pie = MEM_callocN(sizeof(*pie), "pie menu"); - - pie->block_radial = UI_block_begin(C, NULL, __func__, UI_EMBOSS); - /* may be useful later to allow spawning pies - * from old positions */ - /* pie->block_radial->flag |= UI_BLOCK_POPUP_MEMORY; */ - pie->block_radial->puphash = ui_popup_menu_hash(title); - pie->block_radial->flag |= UI_BLOCK_RADIAL; - - /* if pie is spawned by a left click, release or click event, - * it is always assumed to be click style */ - if (event->type == LEFTMOUSE || ELEM(event->val, KM_RELEASE, KM_CLICK)) { - pie->block_radial->pie_data.flags |= UI_PIE_CLICK_STYLE; - pie->block_radial->pie_data.event_type = EVENT_NONE; - win->pie_event_type_lock = EVENT_NONE; - } - else { - if (win->pie_event_type_last != EVENT_NONE) { - /* original pie key has been released, so don't propagate the event */ - if (win->pie_event_type_lock == EVENT_NONE) { - event_type = EVENT_NONE; - pie->block_radial->pie_data.flags |= UI_PIE_CLICK_STYLE; - } - else { - event_type = win->pie_event_type_last; - } - } - else { - event_type = event->type; - } - - pie->block_radial->pie_data.event_type = event_type; - win->pie_event_type_lock = event_type; - } - - pie->layout = UI_block_layout( - pie->block_radial, UI_LAYOUT_VERTICAL, UI_LAYOUT_PIEMENU, 0, 0, 200, 0, 0, style); - - /* NOTE: #wmEvent.xy is where we started dragging in case of #KM_CLICK_DRAG. */ - pie->mx = event->xy[0]; - pie->my = event->xy[1]; - - /* create title button */ - if (title[0]) { - uiBut *but; - char titlestr[256]; - int w; - if (icon) { - BLI_snprintf(titlestr, sizeof(titlestr), " %s", title); - w = ui_pie_menu_title_width(titlestr, icon); - but = uiDefIconTextBut(pie->block_radial, - UI_BTYPE_LABEL, - 0, - icon, - titlestr, - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - } - else { - w = ui_pie_menu_title_width(title, 0); - but = uiDefBut(pie->block_radial, - UI_BTYPE_LABEL, - 0, - title, - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - } - /* do not align left */ - but->drawflag &= ~UI_BUT_TEXT_LEFT; - pie->block_radial->pie_data.title = but->str; - pie->block_radial->pie_data.icon = icon; - } - - return pie; -} - -void UI_pie_menu_end(bContext *C, uiPieMenu *pie) -{ - wmWindow *window = CTX_wm_window(C); - uiPopupBlockHandle *menu; - - menu = ui_popup_block_create(C, NULL, NULL, NULL, ui_block_func_PIE, pie, NULL); - menu->popup = true; - menu->towardstime = PIL_check_seconds_timer(); - - UI_popup_handlers_add(C, &window->modalhandlers, menu, WM_HANDLER_ACCEPT_DBL_CLICK); - WM_event_add_mousemove(window); - - MEM_freeN(pie); -} - -uiLayout *UI_pie_menu_layout(uiPieMenu *pie) -{ - return pie->layout; -} - -int UI_pie_menu_invoke(struct bContext *C, const char *idname, const wmEvent *event) -{ - uiPieMenu *pie; - uiLayout *layout; - MenuType *mt = WM_menutype_find(idname, true); - - if (mt == NULL) { - printf("%s: named menu \"%s\" not found\n", __func__, idname); - return OPERATOR_CANCELLED; - } - - if (WM_menutype_poll(C, mt) == false) { - /* cancel but allow event to pass through, just like operators do */ - return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); - } - - pie = UI_pie_menu_begin(C, IFACE_(mt->label), ICON_NONE, event); - layout = UI_pie_menu_layout(pie); - - UI_menutype_draw(C, mt, layout); - - UI_pie_menu_end(C, pie); - - return OPERATOR_INTERFACE; -} - -int UI_pie_menu_invoke_from_operator_enum(struct bContext *C, - const char *title, - const char *opname, - const char *propname, - const wmEvent *event) -{ - uiPieMenu *pie; - uiLayout *layout; - - pie = UI_pie_menu_begin(C, IFACE_(title), ICON_NONE, event); - layout = UI_pie_menu_layout(pie); - - layout = uiLayoutRadial(layout); - uiItemsEnumO(layout, opname, propname); - - UI_pie_menu_end(C, pie); - - return OPERATOR_INTERFACE; -} - -int UI_pie_menu_invoke_from_rna_enum(struct bContext *C, - const char *title, - const char *path, - const wmEvent *event) -{ - PointerRNA ctx_ptr; - PointerRNA r_ptr; - PropertyRNA *r_prop; - uiPieMenu *pie; - uiLayout *layout; - - RNA_pointer_create(NULL, &RNA_Context, C, &ctx_ptr); - - if (!RNA_path_resolve(&ctx_ptr, path, &r_ptr, &r_prop)) { - return OPERATOR_CANCELLED; - } - - /* invalid property, only accept enums */ - if (RNA_property_type(r_prop) != PROP_ENUM) { - BLI_assert(0); - return OPERATOR_CANCELLED; - } - - pie = UI_pie_menu_begin(C, IFACE_(title), ICON_NONE, event); - - layout = UI_pie_menu_layout(pie); - - layout = uiLayoutRadial(layout); - uiItemFullR(layout, &r_ptr, r_prop, RNA_NO_INDEX, 0, UI_ITEM_R_EXPAND, NULL, 0); - - UI_pie_menu_end(C, pie); - - return OPERATOR_INTERFACE; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Pie Menu Levels - * - * Pie menus can't contain more than 8 items (yet). - * When using #uiItemsFullEnumO, a "More" button is created that calls - * a new pie menu if the enum has too many items. We call this a new "level". - * Indirect recursion is used, so that a theoretically unlimited number of items is supported. - * - * This is a implementation specifically for operator enums, - * needed since the object mode pie now has more than 8 items. - * Ideally we'd have some way of handling this for all kinds of pie items, but that's tricky. - * - * - Julian (Feb 2016) - * \{ */ - -typedef struct PieMenuLevelData { - char title[UI_MAX_NAME_STR]; /* parent pie title, copied for level */ - int icon; /* parent pie icon, copied for level */ - int totitem; /* total count of *remaining* items */ - - /* needed for calling uiItemsFullEnumO_array again for new level */ - wmOperatorType *ot; - const char *propname; - IDProperty *properties; - wmOperatorCallContext context, flag; -} PieMenuLevelData; - -/** - * Invokes a new pie menu for a new level. - */ -static void ui_pie_menu_level_invoke(bContext *C, void *argN, void *arg2) -{ - EnumPropertyItem *item_array = (EnumPropertyItem *)argN; - PieMenuLevelData *lvl = (PieMenuLevelData *)arg2; - wmWindow *win = CTX_wm_window(C); - - uiPieMenu *pie = UI_pie_menu_begin(C, IFACE_(lvl->title), lvl->icon, win->eventstate); - uiLayout *layout = UI_pie_menu_layout(pie); - - layout = uiLayoutRadial(layout); - - PointerRNA ptr; - - WM_operator_properties_create_ptr(&ptr, lvl->ot); - /* so the context is passed to itemf functions (some need it) */ - WM_operator_properties_sanitize(&ptr, false); - PropertyRNA *prop = RNA_struct_find_property(&ptr, lvl->propname); - - if (prop) { - uiItemsFullEnumO_items(layout, - lvl->ot, - ptr, - prop, - lvl->properties, - lvl->context, - lvl->flag, - item_array, - lvl->totitem); - } - else { - RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), lvl->propname); - } - - UI_pie_menu_end(C, pie); -} - -void ui_pie_menu_level_create(uiBlock *block, - wmOperatorType *ot, - const char *propname, - IDProperty *properties, - const EnumPropertyItem *items, - int totitem, - wmOperatorCallContext context, - int flag) -{ - const int totitem_parent = PIE_MAX_ITEMS - 1; - const int totitem_remain = totitem - totitem_parent; - const size_t array_size = sizeof(EnumPropertyItem) * totitem_remain; - - /* used as but->func_argN so freeing is handled elsewhere */ - EnumPropertyItem *remaining = MEM_mallocN(array_size + sizeof(EnumPropertyItem), - "pie_level_item_array"); - memcpy(remaining, items + totitem_parent, array_size); - /* A NULL terminating sentinel element is required. */ - memset(&remaining[totitem_remain], 0, sizeof(EnumPropertyItem)); - - /* yuk, static... issue is we can't reliably free this without doing dangerous changes */ - static PieMenuLevelData lvl; - BLI_strncpy(lvl.title, block->pie_data.title, UI_MAX_NAME_STR); - lvl.totitem = totitem_remain; - lvl.ot = ot; - lvl.propname = propname; - lvl.properties = properties; - lvl.context = context; - lvl.flag = flag; - - /* add a 'more' menu entry */ - uiBut *but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_PLUS, - "More", - 0, - 0, - UI_UNIT_X * 3, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - "Show more items of this menu"); - UI_but_funcN_set(but, ui_pie_menu_level_invoke, remaining, &lvl); -} - -/** \} */ /* Pie Menu Levels */ diff --git a/source/blender/editors/interface/interface_region_menu_pie.cc b/source/blender/editors/interface/interface_region_menu_pie.cc new file mode 100644 index 00000000000..586aeef44fd --- /dev/null +++ b/source/blender/editors/interface/interface_region_menu_pie.cc @@ -0,0 +1,406 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Pie Menu Region + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_blenlib.h" +#include "BLI_utildefines.h" + +#include "PIL_time.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_prototypes.h" + +#include "UI_interface.h" + +#include "BLT_translation.h" + +#include "ED_screen.h" + +#include "interface_intern.h" +#include "interface_regions_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Pie Menu + * \{ */ + +struct uiPieMenu { + uiBlock *block_radial; /* radial block of the pie menu (more could be added later) */ + uiLayout *layout; + int mx, my; +}; + +static uiBlock *ui_block_func_PIE(bContext *UNUSED(C), uiPopupBlockHandle *handle, void *arg_pie) +{ + uiBlock *block; + uiPieMenu *pie = static_cast(arg_pie); + int minwidth, width, height; + + minwidth = UI_MENU_WIDTH_MIN; + block = pie->block_radial; + + /* in some cases we create the block before the region, + * so we set it delayed here if necessary */ + if (BLI_findindex(&handle->region->uiblocks, block) == -1) { + UI_block_region_set(block, handle->region); + } + + UI_block_layout_resolve(block, &width, &height); + + UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + block->minbounds = minwidth; + block->bounds = 1; + block->bounds_offset[0] = 0; + block->bounds_offset[1] = 0; + block->bounds_type = UI_BLOCK_BOUNDS_PIE_CENTER; + + block->pie_data.pie_center_spawned[0] = pie->mx; + block->pie_data.pie_center_spawned[1] = pie->my; + + return pie->block_radial; +} + +static float ui_pie_menu_title_width(const char *name, int icon) +{ + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + return (UI_fontstyle_string_width(fstyle, name) + (UI_UNIT_X * (1.50f + (icon ? 0.25f : 0.0f)))); +} + +uiPieMenu *UI_pie_menu_begin(struct bContext *C, const char *title, int icon, const wmEvent *event) +{ + const uiStyle *style = UI_style_get_dpi(); + short event_type; + + wmWindow *win = CTX_wm_window(C); + + uiPieMenu *pie = MEM_cnew(__func__); + + pie->block_radial = UI_block_begin(C, nullptr, __func__, UI_EMBOSS); + /* may be useful later to allow spawning pies + * from old positions */ + /* pie->block_radial->flag |= UI_BLOCK_POPUP_MEMORY; */ + pie->block_radial->puphash = ui_popup_menu_hash(title); + pie->block_radial->flag |= UI_BLOCK_RADIAL; + + /* if pie is spawned by a left click, release or click event, + * it is always assumed to be click style */ + if (event->type == LEFTMOUSE || ELEM(event->val, KM_RELEASE, KM_CLICK)) { + pie->block_radial->pie_data.flags |= UI_PIE_CLICK_STYLE; + pie->block_radial->pie_data.event_type = EVENT_NONE; + win->pie_event_type_lock = EVENT_NONE; + } + else { + if (win->pie_event_type_last != EVENT_NONE) { + /* original pie key has been released, so don't propagate the event */ + if (win->pie_event_type_lock == EVENT_NONE) { + event_type = EVENT_NONE; + pie->block_radial->pie_data.flags |= UI_PIE_CLICK_STYLE; + } + else { + event_type = win->pie_event_type_last; + } + } + else { + event_type = event->type; + } + + pie->block_radial->pie_data.event_type = event_type; + win->pie_event_type_lock = event_type; + } + + pie->layout = UI_block_layout( + pie->block_radial, UI_LAYOUT_VERTICAL, UI_LAYOUT_PIEMENU, 0, 0, 200, 0, 0, style); + + /* NOTE: #wmEvent.xy is where we started dragging in case of #KM_CLICK_DRAG. */ + pie->mx = event->xy[0]; + pie->my = event->xy[1]; + + /* create title button */ + if (title[0]) { + uiBut *but; + char titlestr[256]; + int w; + if (icon) { + BLI_snprintf(titlestr, sizeof(titlestr), " %s", title); + w = ui_pie_menu_title_width(titlestr, icon); + but = uiDefIconTextBut(pie->block_radial, + UI_BTYPE_LABEL, + 0, + icon, + titlestr, + 0, + 0, + w, + UI_UNIT_Y, + nullptr, + 0.0, + 0.0, + 0, + 0, + ""); + } + else { + w = ui_pie_menu_title_width(title, 0); + but = uiDefBut(pie->block_radial, + UI_BTYPE_LABEL, + 0, + title, + 0, + 0, + w, + UI_UNIT_Y, + nullptr, + 0.0, + 0.0, + 0, + 0, + ""); + } + /* do not align left */ + but->drawflag &= ~UI_BUT_TEXT_LEFT; + pie->block_radial->pie_data.title = but->str; + pie->block_radial->pie_data.icon = icon; + } + + return pie; +} + +void UI_pie_menu_end(bContext *C, uiPieMenu *pie) +{ + wmWindow *window = CTX_wm_window(C); + uiPopupBlockHandle *menu; + + menu = ui_popup_block_create(C, nullptr, nullptr, nullptr, ui_block_func_PIE, pie, nullptr); + menu->popup = true; + menu->towardstime = PIL_check_seconds_timer(); + + UI_popup_handlers_add(C, &window->modalhandlers, menu, WM_HANDLER_ACCEPT_DBL_CLICK); + WM_event_add_mousemove(window); + + MEM_freeN(pie); +} + +uiLayout *UI_pie_menu_layout(uiPieMenu *pie) +{ + return pie->layout; +} + +int UI_pie_menu_invoke(struct bContext *C, const char *idname, const wmEvent *event) +{ + uiPieMenu *pie; + uiLayout *layout; + MenuType *mt = WM_menutype_find(idname, true); + + if (mt == nullptr) { + printf("%s: named menu \"%s\" not found\n", __func__, idname); + return OPERATOR_CANCELLED; + } + + if (WM_menutype_poll(C, mt) == false) { + /* cancel but allow event to pass through, just like operators do */ + return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); + } + + pie = UI_pie_menu_begin(C, IFACE_(mt->label), ICON_NONE, event); + layout = UI_pie_menu_layout(pie); + + UI_menutype_draw(C, mt, layout); + + UI_pie_menu_end(C, pie); + + return OPERATOR_INTERFACE; +} + +int UI_pie_menu_invoke_from_operator_enum(struct bContext *C, + const char *title, + const char *opname, + const char *propname, + const wmEvent *event) +{ + uiPieMenu *pie; + uiLayout *layout; + + pie = UI_pie_menu_begin(C, IFACE_(title), ICON_NONE, event); + layout = UI_pie_menu_layout(pie); + + layout = uiLayoutRadial(layout); + uiItemsEnumO(layout, opname, propname); + + UI_pie_menu_end(C, pie); + + return OPERATOR_INTERFACE; +} + +int UI_pie_menu_invoke_from_rna_enum(struct bContext *C, + const char *title, + const char *path, + const wmEvent *event) +{ + PointerRNA ctx_ptr; + PointerRNA r_ptr; + PropertyRNA *r_prop; + uiPieMenu *pie; + uiLayout *layout; + + RNA_pointer_create(nullptr, &RNA_Context, C, &ctx_ptr); + + if (!RNA_path_resolve(&ctx_ptr, path, &r_ptr, &r_prop)) { + return OPERATOR_CANCELLED; + } + + /* invalid property, only accept enums */ + if (RNA_property_type(r_prop) != PROP_ENUM) { + BLI_assert(0); + return OPERATOR_CANCELLED; + } + + pie = UI_pie_menu_begin(C, IFACE_(title), ICON_NONE, event); + + layout = UI_pie_menu_layout(pie); + + layout = uiLayoutRadial(layout); + uiItemFullR(layout, &r_ptr, r_prop, RNA_NO_INDEX, 0, UI_ITEM_R_EXPAND, nullptr, 0); + + UI_pie_menu_end(C, pie); + + return OPERATOR_INTERFACE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pie Menu Levels + * + * Pie menus can't contain more than 8 items (yet). + * When using #uiItemsFullEnumO, a "More" button is created that calls + * a new pie menu if the enum has too many items. We call this a new "level". + * Indirect recursion is used, so that a theoretically unlimited number of items is supported. + * + * This is a implementation specifically for operator enums, + * needed since the object mode pie now has more than 8 items. + * Ideally we'd have some way of handling this for all kinds of pie items, but that's tricky. + * + * - Julian (Feb 2016) + * \{ */ + + struct PieMenuLevelData { + char title[UI_MAX_NAME_STR]; /* parent pie title, copied for level */ + int icon; /* parent pie icon, copied for level */ + int totitem; /* total count of *remaining* items */ + + /* needed for calling uiItemsFullEnumO_array again for new level */ + wmOperatorType *ot; + const char *propname; + IDProperty *properties; + wmOperatorCallContext context, flag; +} ; + +/** + * Invokes a new pie menu for a new level. + */ +static void ui_pie_menu_level_invoke(bContext *C, void *argN, void *arg2) +{ + EnumPropertyItem *item_array = (EnumPropertyItem *)argN; + PieMenuLevelData *lvl = (PieMenuLevelData *)arg2; + wmWindow *win = CTX_wm_window(C); + + uiPieMenu *pie = UI_pie_menu_begin(C, IFACE_(lvl->title), lvl->icon, win->eventstate); + uiLayout *layout = UI_pie_menu_layout(pie); + + layout = uiLayoutRadial(layout); + + PointerRNA ptr; + + WM_operator_properties_create_ptr(&ptr, lvl->ot); + /* so the context is passed to itemf functions (some need it) */ + WM_operator_properties_sanitize(&ptr, false); + PropertyRNA *prop = RNA_struct_find_property(&ptr, lvl->propname); + + if (prop) { + uiItemsFullEnumO_items(layout, + lvl->ot, + ptr, + prop, + lvl->properties, + lvl->context, + lvl->flag, + item_array, + lvl->totitem); + } + else { + RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), lvl->propname); + } + + UI_pie_menu_end(C, pie); +} + +void ui_pie_menu_level_create(uiBlock *block, + wmOperatorType *ot, + const char *propname, + IDProperty *properties, + const EnumPropertyItem *items, + int totitem, + wmOperatorCallContext context, + wmOperatorCallContext flag) +{ + const int totitem_parent = PIE_MAX_ITEMS - 1; + const int totitem_remain = totitem - totitem_parent; + const size_t array_size = sizeof(EnumPropertyItem) * totitem_remain; + + /* used as but->func_argN so freeing is handled elsewhere */ + EnumPropertyItem *remaining = static_cast( + MEM_mallocN(array_size + sizeof(EnumPropertyItem), "pie_level_item_array")); + memcpy(remaining, items + totitem_parent, array_size); + /* A nullptr terminating sentinel element is required. */ + memset(&remaining[totitem_remain], 0, sizeof(EnumPropertyItem)); + + /* yuk, static... issue is we can't reliably free this without doing dangerous changes */ + static PieMenuLevelData lvl; + BLI_strncpy(lvl.title, block->pie_data.title, UI_MAX_NAME_STR); + lvl.totitem = totitem_remain; + lvl.ot = ot; + lvl.propname = propname; + lvl.properties = properties; + lvl.context = context; + lvl.flag = flag; + + /* add a 'more' menu entry */ + uiBut *but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_PLUS, + "More", + 0, + 0, + UI_UNIT_X * 3, + UI_UNIT_Y, + nullptr, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + "Show more items of this menu"); + UI_but_funcN_set(but, ui_pie_menu_level_invoke, remaining, &lvl); +} + +/** \} */ /* Pie Menu Levels */ diff --git a/source/blender/editors/interface/interface_region_menu_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c deleted file mode 100644 index 7d8f4315710..00000000000 --- a/source/blender/editors/interface/interface_region_menu_popup.c +++ /dev/null @@ -1,670 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * PopUp Menu Region - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" - -#include "BLI_ghash.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" -#include "BKE_report.h" -#include "BKE_screen.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" - -#include "UI_interface.h" - -#include "BLT_translation.h" - -#include "ED_screen.h" - -#include "interface_intern.h" -#include "interface_regions_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Utility Functions - * \{ */ - -bool ui_but_menu_step_poll(const uiBut *but) -{ - BLI_assert(but->type == UI_BTYPE_MENU); - - /* currently only RNA buttons */ - return ((but->menu_step_func != NULL) || - (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM)); -} - -int ui_but_menu_step(uiBut *but, int direction) -{ - if (ui_but_menu_step_poll(but)) { - if (but->menu_step_func) { - return but->menu_step_func(but->block->evil_C, direction, but->poin); - } - - const int curval = RNA_property_enum_get(&but->rnapoin, but->rnaprop); - return RNA_property_enum_step( - but->block->evil_C, &but->rnapoin, but->rnaprop, curval, direction); - } - - printf("%s: cannot cycle button '%s'\n", __func__, but->str); - return 0; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Popup Menu Memory - * - * Support menu-memory, a feature that positions the cursor - * over the previously used menu item. - * - * \note This is stored for each unique menu title. - * \{ */ - -static uint ui_popup_string_hash(const char *str, const bool use_sep) -{ - /* sometimes button contains hotkey, sometimes not, strip for proper compare */ - int hash; - const char *delimit = use_sep ? strrchr(str, UI_SEP_CHAR) : NULL; - - if (delimit) { - hash = BLI_ghashutil_strhash_n(str, delimit - str); - } - else { - hash = BLI_ghashutil_strhash(str); - } - - return hash; -} - -uint ui_popup_menu_hash(const char *str) -{ - return BLI_ghashutil_strhash(str); -} - -/* but == NULL read, otherwise set */ -static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but) -{ - static uint mem[256]; - static bool first = true; - - const uint hash = block->puphash; - const uint hash_mod = hash & 255; - - if (first) { - /* init */ - memset(mem, -1, sizeof(mem)); - first = 0; - } - - if (but) { - /* set */ - mem[hash_mod] = ui_popup_string_hash(but->str, but->flag & UI_BUT_HAS_SEP_CHAR); - return NULL; - } - - /* get */ - LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { - if (mem[hash_mod] == - ui_popup_string_hash(but_iter->str, but_iter->flag & UI_BUT_HAS_SEP_CHAR)) { - return but_iter; - } - } - - return NULL; -} - -uiBut *ui_popup_menu_memory_get(uiBlock *block) -{ - return ui_popup_menu_memory__internal(block, NULL); -} - -void ui_popup_menu_memory_set(uiBlock *block, uiBut *but) -{ - ui_popup_menu_memory__internal(block, but); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Popup Menu with Callback or String - * \{ */ - -struct uiPopupMenu { - uiBlock *block; - uiLayout *layout; - uiBut *but; - ARegion *butregion; - - int mx, my; - bool popup, slideout; - - uiMenuCreateFunc menu_func; - void *menu_arg; -}; - -static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup) -{ - uiBlock *block; - uiPopupMenu *pup = arg_pup; - int minwidth, width, height; - char direction; - bool flip; - - if (pup->menu_func) { - pup->block->handle = handle; - pup->menu_func(C, pup->layout, pup->menu_arg); - pup->block->handle = NULL; - } - - /* Find block minimum width. */ - if (uiLayoutGetUnitsX(pup->layout) != 0.0f) { - /* Use the minimum width from the layout if it's set. */ - minwidth = uiLayoutGetUnitsX(pup->layout) * UI_UNIT_X; - } - else if (pup->but) { - /* Minimum width to enforce. */ - if (pup->but->drawstr[0]) { - minwidth = BLI_rctf_size_x(&pup->but->rect); - } - else { - /* For buttons with no text, use the minimum (typically icon only). */ - minwidth = UI_MENU_WIDTH_MIN; - } - } - else { - minwidth = UI_MENU_WIDTH_MIN; - } - - /* Find block direction. */ - if (pup->but) { - if (pup->block->direction != 0) { - /* allow overriding the direction from menu_func */ - direction = pup->block->direction; - } - else { - direction = UI_DIR_DOWN; - } - } - else { - direction = UI_DIR_DOWN; - } - - flip = (direction == UI_DIR_DOWN); - - block = pup->block; - - /* in some cases we create the block before the region, - * so we set it delayed here if necessary */ - if (BLI_findindex(&handle->region->uiblocks, block) == -1) { - UI_block_region_set(block, handle->region); - } - - block->direction = direction; - - UI_block_layout_resolve(block, &width, &height); - - UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); - - if (pup->popup) { - int offset[2]; - - uiBut *but_activate = NULL; - UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - UI_block_direction_set(block, direction); - - /* offset the mouse position, possibly based on earlier selection */ - uiBut *bt; - if ((block->flag & UI_BLOCK_POPUP_MEMORY) && (bt = ui_popup_menu_memory_get(block))) { - /* position mouse on last clicked item, at 0.8*width of the - * button, so it doesn't overlap the text too much, also note - * the offset is negative because we are inverse moving the - * block to be under the mouse */ - offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)); - offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y); - - if (ui_but_is_editable(bt)) { - but_activate = bt; - } - } - else { - /* position mouse at 0.8*width of the button and below the tile - * on the first item */ - offset[0] = 0; - LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { - offset[0] = min_ii(offset[0], - -(but_iter->rect.xmin + 0.8f * BLI_rctf_size_x(&but_iter->rect))); - } - - offset[1] = 2.1 * UI_UNIT_Y; - - LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { - if (ui_but_is_editable(but_iter)) { - but_activate = but_iter; - break; - } - } - } - - /* in rare cases this is needed since moving the popup - * to be within the window bounds may move it away from the mouse, - * This ensures we set an item to be active. */ - if (but_activate) { - ui_but_activate_over(C, handle->region, but_activate); - } - - block->minbounds = minwidth; - UI_block_bounds_set_menu(block, 1, offset); - } - else { - /* for a header menu we set the direction automatic */ - if (!pup->slideout && flip) { - ARegion *region = CTX_wm_region(C); - if (region) { - if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) { - if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) { - UI_block_direction_set(block, UI_DIR_UP); - UI_block_order_flip(block); - } - } - } - } - - block->minbounds = minwidth; - UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X); - } - - /* if menu slides out of other menu, override direction */ - if (pup->slideout) { - UI_block_direction_set(block, UI_DIR_RIGHT); - } - - return pup->block; -} - -uiPopupBlockHandle *ui_popup_menu_create( - bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg) -{ - wmWindow *window = CTX_wm_window(C); - const uiStyle *style = UI_style_get_dpi(); - uiPopupBlockHandle *handle; - uiPopupMenu *pup; - - pup = MEM_callocN(sizeof(uiPopupMenu), __func__); - pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS_PULLDOWN); - pup->block->flag |= UI_BLOCK_NUMSELECT; /* default menus to numselect */ - pup->layout = UI_block_layout( - pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - pup->slideout = but ? ui_block_is_menu(but->block) : false; - pup->but = but; - uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN); - - if (!but) { - /* no button to start from, means we are a popup */ - pup->mx = window->eventstate->xy[0]; - pup->my = window->eventstate->xy[1]; - pup->popup = true; - pup->block->flag |= UI_BLOCK_NO_FLIP; - } - /* some enums reversing is strange, currently we have no good way to - * reverse some enum's but not others, so reverse all so the first menu - * items are always close to the mouse cursor */ - else { -#if 0 - /* if this is an rna button then we can assume its an enum - * flipping enums is generally not good since the order can be - * important T28786. */ - if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) { - pup->block->flag |= UI_BLOCK_NO_FLIP; - } -#endif - if (but->context) { - uiLayoutContextCopy(pup->layout, but->context); - } - } - - /* menu is created from a callback */ - pup->menu_func = menu_func; - pup->menu_arg = arg; - - handle = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup, NULL); - - if (!but) { - handle->popup = true; - - UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); - WM_event_add_mousemove(window); - } - - MEM_freeN(pup); - - return handle; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Popup Menu API with begin & end - * \{ */ - -uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, - const char *title, - const char *block_name, - int icon) -{ - const uiStyle *style = UI_style_get_dpi(); - uiPopupMenu *pup = MEM_callocN(sizeof(uiPopupMenu), "popup menu"); - uiBut *but; - - pup->block = UI_block_begin(C, NULL, block_name, UI_EMBOSS_PULLDOWN); - pup->block->flag |= UI_BLOCK_POPUP_MEMORY | UI_BLOCK_IS_FLIP; - pup->block->puphash = ui_popup_menu_hash(title); - pup->layout = UI_block_layout( - pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - - /* NOTE: this intentionally differs from the menu & sub-menu default because many operators - * use popups like this to select one of their options - - * where having invoke doesn't make sense */ - uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN); - - /* create in advance so we can let buttons point to retval already */ - pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle"); - - /* create title button */ - if (title[0]) { - char titlestr[256]; - - if (icon) { - BLI_snprintf(titlestr, sizeof(titlestr), " %s", title); - uiDefIconTextBut(pup->block, - UI_BTYPE_LABEL, - 0, - icon, - titlestr, - 0, - 0, - 200, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - } - else { - but = uiDefBut( - pup->block, UI_BTYPE_LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); - but->drawflag = UI_BUT_TEXT_LEFT; - } - - uiItemS(pup->layout); - } - - return pup; -} - -uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon) -{ - return UI_popup_menu_begin_ex(C, title, __func__, icon); -} - -void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but) -{ - pup->but = but; - pup->butregion = butregion; -} - -void UI_popup_menu_end(bContext *C, uiPopupMenu *pup) -{ - wmWindow *window = CTX_wm_window(C); - uiPopupBlockHandle *menu; - uiBut *but = NULL; - ARegion *butregion = NULL; - - pup->popup = true; - pup->mx = window->eventstate->xy[0]; - pup->my = window->eventstate->xy[1]; - - if (pup->but) { - but = pup->but; - butregion = pup->butregion; - } - - menu = ui_popup_block_create(C, butregion, but, NULL, ui_block_func_POPUP, pup, NULL); - menu->popup = true; - - UI_popup_handlers_add(C, &window->modalhandlers, menu, 0); - WM_event_add_mousemove(window); - - MEM_freeN(pup); -} - -bool UI_popup_menu_end_or_cancel(bContext *C, uiPopupMenu *pup) -{ - if (!UI_block_is_empty_ex(pup->block, true)) { - UI_popup_menu_end(C, pup); - return true; - } - UI_block_layout_resolve(pup->block, NULL, NULL); - MEM_freeN(pup->block->handle); - UI_block_free(C, pup->block); - MEM_freeN(pup); - return false; -} - -uiLayout *UI_popup_menu_layout(uiPopupMenu *pup) -{ - return pup->layout; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Standard Popup Menus - * \{ */ - -void UI_popup_menu_reports(bContext *C, ReportList *reports) -{ - uiPopupMenu *pup = NULL; - uiLayout *layout; - - if (!CTX_wm_window(C)) { - return; - } - - LISTBASE_FOREACH (Report *, report, &reports->list) { - int icon; - const char *msg, *msg_next; - - if (report->type < reports->printlevel) { - continue; - } - - if (pup == NULL) { - char title[UI_MAX_DRAW_STR]; - BLI_snprintf(title, sizeof(title), "%s: %s", IFACE_("Report"), report->typestr); - /* popup_menu stuff does just what we need (but pass meaningful block name) */ - pup = UI_popup_menu_begin_ex(C, title, __func__, ICON_NONE); - layout = UI_popup_menu_layout(pup); - } - else { - uiItemS(layout); - } - - /* split each newline into a label */ - msg = report->message; - icon = UI_icon_from_report_type(report->type); - do { - char buf[UI_MAX_DRAW_STR]; - msg_next = strchr(msg, '\n'); - if (msg_next) { - msg_next++; - BLI_strncpy(buf, msg, MIN2(sizeof(buf), msg_next - msg)); - msg = buf; - } - uiItemL(layout, msg, icon); - icon = ICON_NONE; - } while ((msg = msg_next) && *msg); - } - - if (pup) { - UI_popup_menu_end(C, pup); - } -} - -int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) -{ - uiPopupMenu *pup; - uiLayout *layout; - MenuType *mt = WM_menutype_find(idname, true); - - if (mt == NULL) { - BKE_reportf(reports, RPT_ERROR, "Menu \"%s\" not found", idname); - return OPERATOR_CANCELLED; - } - - if (WM_menutype_poll(C, mt) == false) { - /* cancel but allow event to pass through, just like operators do */ - return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); - } - - pup = UI_popup_menu_begin(C, IFACE_(mt->label), ICON_NONE); - layout = UI_popup_menu_layout(pup); - - UI_menutype_draw(C, mt, layout); - - UI_popup_menu_end(C, pup); - - return OPERATOR_INTERFACE; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Popup Block API - * \{ */ - -void UI_popup_block_invoke_ex( - bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free, bool can_refresh) -{ - wmWindow *window = CTX_wm_window(C); - uiPopupBlockHandle *handle; - - handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg, arg_free); - handle->popup = true; - - /* It can be useful to disable refresh (even though it will work) - * as this exists text fields which can be disruptive if refresh isn't needed. */ - handle->can_refresh = can_refresh; - - UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); - UI_block_active_only_flagged_buttons(C, handle->region, handle->region->uiblocks.first); - WM_event_add_mousemove(window); -} - -void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free) -{ - UI_popup_block_invoke_ex(C, func, arg, arg_free, true); -} - -void UI_popup_block_ex(bContext *C, - uiBlockCreateFunc func, - uiBlockHandleFunc popup_func, - uiBlockCancelFunc cancel_func, - void *arg, - wmOperator *op) -{ - wmWindow *window = CTX_wm_window(C); - uiPopupBlockHandle *handle; - - handle = ui_popup_block_create(C, NULL, NULL, func, NULL, arg, NULL); - handle->popup = true; - handle->retvalue = 1; - handle->can_refresh = true; - - handle->popup_op = op; - handle->popup_arg = arg; - handle->popup_func = popup_func; - handle->cancel_func = cancel_func; - // handle->opcontext = opcontext; - - UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); - UI_block_active_only_flagged_buttons(C, handle->region, handle->region->uiblocks.first); - WM_event_add_mousemove(window); -} - -#if 0 /* UNUSED */ -void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, wmOperatorCallContext opcontext) -{ - wmWindow *window = CTX_wm_window(C); - uiPopupBlockHandle *handle; - - handle = ui_popup_block_create(C, NULL, NULL, func, NULL, op, NULL); - handle->popup = 1; - handle->retvalue = 1; - handle->can_refresh = true; - - handle->popup_arg = op; - handle->popup_func = operator_cb; - handle->cancel_func = confirm_cancel_operator; - handle->opcontext = opcontext; - - UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); - WM_event_add_mousemove(C); -} -#endif - -void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block) -{ - /* if loading new .blend while popup is open, window will be NULL */ - if (block->handle) { - if (win) { - const bScreen *screen = WM_window_get_active_screen(win); - - UI_popup_handlers_remove(&win->modalhandlers, block->handle); - ui_popup_block_free(C, block->handle); - - /* In the case we have nested popups, - * closing one may need to redraw another, see: T48874 */ - LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { - ED_region_tag_refresh_ui(region); - } - } - } -} - -bool UI_popup_block_name_exists(const bScreen *screen, const char *name) -{ - LISTBASE_FOREACH (const ARegion *, region, &screen->regionbase) { - LISTBASE_FOREACH (const uiBlock *, block, ®ion->uiblocks) { - if (STREQ(block->name, name)) { - return true; - } - } - } - return false; -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_menu_popup.cc b/source/blender/editors/interface/interface_region_menu_popup.cc new file mode 100644 index 00000000000..e843a275d08 --- /dev/null +++ b/source/blender/editors/interface/interface_region_menu_popup.cc @@ -0,0 +1,675 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * PopUp Menu Region + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "BLI_ghash.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" + +#include "UI_interface.h" + +#include "BLT_translation.h" + +#include "ED_screen.h" + +#include "interface_intern.h" +#include "interface_regions_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Utility Functions + * \{ */ + +bool ui_but_menu_step_poll(const uiBut *but) +{ + BLI_assert(but->type == UI_BTYPE_MENU); + + /* currently only RNA buttons */ + return ((but->menu_step_func != nullptr) || + (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM)); +} + +int ui_but_menu_step(uiBut *but, int direction) +{ + if (ui_but_menu_step_poll(but)) { + if (but->menu_step_func) { + return but->menu_step_func( + static_cast(but->block->evil_C), direction, but->poin); + } + + const int curval = RNA_property_enum_get(&but->rnapoin, but->rnaprop); + return RNA_property_enum_step(static_cast(but->block->evil_C), + &but->rnapoin, + but->rnaprop, + curval, + direction); + } + + printf("%s: cannot cycle button '%s'\n", __func__, but->str); + return 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu Memory + * + * Support menu-memory, a feature that positions the cursor + * over the previously used menu item. + * + * \note This is stored for each unique menu title. + * \{ */ + +static uint ui_popup_string_hash(const char *str, const bool use_sep) +{ + /* sometimes button contains hotkey, sometimes not, strip for proper compare */ + int hash; + const char *delimit = use_sep ? strrchr(str, UI_SEP_CHAR) : nullptr; + + if (delimit) { + hash = BLI_ghashutil_strhash_n(str, delimit - str); + } + else { + hash = BLI_ghashutil_strhash(str); + } + + return hash; +} + +uint ui_popup_menu_hash(const char *str) +{ + return BLI_ghashutil_strhash(str); +} + +/* but == nullptr read, otherwise set */ +static uiBut *ui_popup_menu_memory__internal(uiBlock *block, uiBut *but) +{ + static uint mem[256]; + static bool first = true; + + const uint hash = block->puphash; + const uint hash_mod = hash & 255; + + if (first) { + /* init */ + memset(mem, -1, sizeof(mem)); + first = false; + } + + if (but) { + /* set */ + mem[hash_mod] = ui_popup_string_hash(but->str, but->flag & UI_BUT_HAS_SEP_CHAR); + return nullptr; + } + + /* get */ + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + if (mem[hash_mod] == + ui_popup_string_hash(but_iter->str, but_iter->flag & UI_BUT_HAS_SEP_CHAR)) { + return but_iter; + } + } + + return nullptr; +} + +uiBut *ui_popup_menu_memory_get(uiBlock *block) +{ + return ui_popup_menu_memory__internal(block, nullptr); +} + +void ui_popup_menu_memory_set(uiBlock *block, uiBut *but) +{ + ui_popup_menu_memory__internal(block, but); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu with Callback or String + * \{ */ + +struct uiPopupMenu { + uiBlock *block; + uiLayout *layout; + uiBut *but; + ARegion *butregion; + + int mx, my; + bool popup, slideout; + + uiMenuCreateFunc menu_func; + void *menu_arg; +}; + +static uiBlock *ui_block_func_POPUP(bContext *C, uiPopupBlockHandle *handle, void *arg_pup) +{ + uiBlock *block; + uiPopupMenu *pup = static_cast(arg_pup); + int minwidth, width, height; + char direction; + bool flip; + + if (pup->menu_func) { + pup->block->handle = handle; + pup->menu_func(C, pup->layout, pup->menu_arg); + pup->block->handle = nullptr; + } + + /* Find block minimum width. */ + if (uiLayoutGetUnitsX(pup->layout) != 0.0f) { + /* Use the minimum width from the layout if it's set. */ + minwidth = uiLayoutGetUnitsX(pup->layout) * UI_UNIT_X; + } + else if (pup->but) { + /* Minimum width to enforce. */ + if (pup->but->drawstr[0]) { + minwidth = BLI_rctf_size_x(&pup->but->rect); + } + else { + /* For buttons with no text, use the minimum (typically icon only). */ + minwidth = UI_MENU_WIDTH_MIN; + } + } + else { + minwidth = UI_MENU_WIDTH_MIN; + } + + /* Find block direction. */ + if (pup->but) { + if (pup->block->direction != 0) { + /* allow overriding the direction from menu_func */ + direction = pup->block->direction; + } + else { + direction = UI_DIR_DOWN; + } + } + else { + direction = UI_DIR_DOWN; + } + + flip = (direction == UI_DIR_DOWN); + + block = pup->block; + + /* in some cases we create the block before the region, + * so we set it delayed here if necessary */ + if (BLI_findindex(&handle->region->uiblocks, block) == -1) { + UI_block_region_set(block, handle->region); + } + + block->direction = direction; + + UI_block_layout_resolve(block, &width, &height); + + UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); + + if (pup->popup) { + int offset[2]; + + uiBut *but_activate = nullptr; + UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NUMSELECT); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + UI_block_direction_set(block, direction); + + /* offset the mouse position, possibly based on earlier selection */ + uiBut *bt; + if ((block->flag & UI_BLOCK_POPUP_MEMORY) && (bt = ui_popup_menu_memory_get(block))) { + /* position mouse on last clicked item, at 0.8*width of the + * button, so it doesn't overlap the text too much, also note + * the offset is negative because we are inverse moving the + * block to be under the mouse */ + offset[0] = -(bt->rect.xmin + 0.8f * BLI_rctf_size_x(&bt->rect)); + offset[1] = -(bt->rect.ymin + 0.5f * UI_UNIT_Y); + + if (ui_but_is_editable(bt)) { + but_activate = bt; + } + } + else { + /* position mouse at 0.8*width of the button and below the tile + * on the first item */ + offset[0] = 0; + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + offset[0] = min_ii(offset[0], + -(but_iter->rect.xmin + 0.8f * BLI_rctf_size_x(&but_iter->rect))); + } + + offset[1] = 2.1 * UI_UNIT_Y; + + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + if (ui_but_is_editable(but_iter)) { + but_activate = but_iter; + break; + } + } + } + + /* in rare cases this is needed since moving the popup + * to be within the window bounds may move it away from the mouse, + * This ensures we set an item to be active. */ + if (but_activate) { + ui_but_activate_over(C, handle->region, but_activate); + } + + block->minbounds = minwidth; + UI_block_bounds_set_menu(block, 1, offset); + } + else { + /* for a header menu we set the direction automatic */ + if (!pup->slideout && flip) { + ARegion *region = CTX_wm_region(C); + if (region) { + if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) { + if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) { + UI_block_direction_set(block, UI_DIR_UP); + UI_block_order_flip(block); + } + } + } + } + + block->minbounds = minwidth; + UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X); + } + + /* if menu slides out of other menu, override direction */ + if (pup->slideout) { + UI_block_direction_set(block, UI_DIR_RIGHT); + } + + return pup->block; +} + +uiPopupBlockHandle *ui_popup_menu_create( + bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg) +{ + wmWindow *window = CTX_wm_window(C); + const uiStyle *style = UI_style_get_dpi(); + uiPopupBlockHandle *handle; + + uiPopupMenu *pup = MEM_cnew(__func__); + pup->block = UI_block_begin(C, nullptr, __func__, UI_EMBOSS_PULLDOWN); + pup->block->flag |= UI_BLOCK_NUMSELECT; /* default menus to numselect */ + pup->layout = UI_block_layout( + pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); + pup->slideout = but ? ui_block_is_menu(but->block) : false; + pup->but = but; + uiLayoutSetOperatorContext(pup->layout, WM_OP_INVOKE_REGION_WIN); + + if (!but) { + /* no button to start from, means we are a popup */ + pup->mx = window->eventstate->xy[0]; + pup->my = window->eventstate->xy[1]; + pup->popup = true; + pup->block->flag |= UI_BLOCK_NO_FLIP; + } + /* some enums reversing is strange, currently we have no good way to + * reverse some enum's but not others, so reverse all so the first menu + * items are always close to the mouse cursor */ + else { +#if 0 + /* if this is an rna button then we can assume its an enum + * flipping enums is generally not good since the order can be + * important T28786. */ + if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) { + pup->block->flag |= UI_BLOCK_NO_FLIP; + } +#endif + if (but->context) { + uiLayoutContextCopy(pup->layout, but->context); + } + } + + /* menu is created from a callback */ + pup->menu_func = menu_func; + pup->menu_arg = arg; + + handle = ui_popup_block_create(C, butregion, but, nullptr, ui_block_func_POPUP, pup, nullptr); + + if (!but) { + handle->popup = true; + + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + WM_event_add_mousemove(window); + } + + MEM_freeN(pup); + + return handle; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu API with begin & end + * \{ */ + +uiPopupMenu *UI_popup_menu_begin_ex(bContext *C, + const char *title, + const char *block_name, + int icon) +{ + const uiStyle *style = UI_style_get_dpi(); + uiPopupMenu *pup = MEM_cnew(__func__); + uiBut *but; + + pup->block = UI_block_begin(C, nullptr, block_name, UI_EMBOSS_PULLDOWN); + pup->block->flag |= UI_BLOCK_POPUP_MEMORY | UI_BLOCK_IS_FLIP; + pup->block->puphash = ui_popup_menu_hash(title); + pup->layout = UI_block_layout( + pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); + + /* NOTE: this intentionally differs from the menu & sub-menu default because many operators + * use popups like this to select one of their options - + * where having invoke doesn't make sense */ + uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN); + + /* create in advance so we can let buttons point to retval already */ + pup->block->handle = MEM_cnew(__func__); + + /* create title button */ + if (title[0]) { + char titlestr[256]; + + if (icon) { + BLI_snprintf(titlestr, sizeof(titlestr), " %s", title); + uiDefIconTextBut(pup->block, + UI_BTYPE_LABEL, + 0, + icon, + titlestr, + 0, + 0, + 200, + UI_UNIT_Y, + nullptr, + 0.0, + 0.0, + 0, + 0, + ""); + } + else { + but = uiDefBut( + pup->block, UI_BTYPE_LABEL, 0, title, 0, 0, 200, UI_UNIT_Y, nullptr, 0.0, 0.0, 0, 0, ""); + but->drawflag = UI_BUT_TEXT_LEFT; + } + + uiItemS(pup->layout); + } + + return pup; +} + +uiPopupMenu *UI_popup_menu_begin(bContext *C, const char *title, int icon) +{ + return UI_popup_menu_begin_ex(C, title, __func__, icon); +} + +void UI_popup_menu_but_set(uiPopupMenu *pup, struct ARegion *butregion, uiBut *but) +{ + pup->but = but; + pup->butregion = butregion; +} + +void UI_popup_menu_end(bContext *C, uiPopupMenu *pup) +{ + wmWindow *window = CTX_wm_window(C); + uiPopupBlockHandle *menu; + uiBut *but = nullptr; + ARegion *butregion = nullptr; + + pup->popup = true; + pup->mx = window->eventstate->xy[0]; + pup->my = window->eventstate->xy[1]; + + if (pup->but) { + but = pup->but; + butregion = pup->butregion; + } + + menu = ui_popup_block_create(C, butregion, but, nullptr, ui_block_func_POPUP, pup, nullptr); + menu->popup = true; + + UI_popup_handlers_add(C, &window->modalhandlers, menu, 0); + WM_event_add_mousemove(window); + + MEM_freeN(pup); +} + +bool UI_popup_menu_end_or_cancel(bContext *C, uiPopupMenu *pup) +{ + if (!UI_block_is_empty_ex(pup->block, true)) { + UI_popup_menu_end(C, pup); + return true; + } + UI_block_layout_resolve(pup->block, nullptr, nullptr); + MEM_freeN(pup->block->handle); + UI_block_free(C, pup->block); + MEM_freeN(pup); + return false; +} + +uiLayout *UI_popup_menu_layout(uiPopupMenu *pup) +{ + return pup->layout; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Standard Popup Menus + * \{ */ + +void UI_popup_menu_reports(bContext *C, ReportList *reports) +{ + uiPopupMenu *pup = nullptr; + uiLayout *layout; + + if (!CTX_wm_window(C)) { + return; + } + + LISTBASE_FOREACH (Report *, report, &reports->list) { + int icon; + const char *msg, *msg_next; + + if (report->type < reports->printlevel) { + continue; + } + + if (pup == nullptr) { + char title[UI_MAX_DRAW_STR]; + BLI_snprintf(title, sizeof(title), "%s: %s", IFACE_("Report"), report->typestr); + /* popup_menu stuff does just what we need (but pass meaningful block name) */ + pup = UI_popup_menu_begin_ex(C, title, __func__, ICON_NONE); + layout = UI_popup_menu_layout(pup); + } + else { + uiItemS(layout); + } + + /* split each newline into a label */ + msg = report->message; + icon = UI_icon_from_report_type(report->type); + do { + char buf[UI_MAX_DRAW_STR]; + msg_next = strchr(msg, '\n'); + if (msg_next) { + msg_next++; + BLI_strncpy(buf, msg, MIN2(sizeof(buf), msg_next - msg)); + msg = buf; + } + uiItemL(layout, msg, icon); + icon = ICON_NONE; + } while ((msg = msg_next) && *msg); + } + + if (pup) { + UI_popup_menu_end(C, pup); + } +} + +int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports) +{ + uiPopupMenu *pup; + uiLayout *layout; + MenuType *mt = WM_menutype_find(idname, true); + + if (mt == nullptr) { + BKE_reportf(reports, RPT_ERROR, "Menu \"%s\" not found", idname); + return OPERATOR_CANCELLED; + } + + if (WM_menutype_poll(C, mt) == false) { + /* cancel but allow event to pass through, just like operators do */ + return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); + } + + pup = UI_popup_menu_begin(C, IFACE_(mt->label), ICON_NONE); + layout = UI_popup_menu_layout(pup); + + UI_menutype_draw(C, mt, layout); + + UI_popup_menu_end(C, pup); + + return OPERATOR_INTERFACE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Block API + * \{ */ + +void UI_popup_block_invoke_ex( + bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free, bool can_refresh) +{ + wmWindow *window = CTX_wm_window(C); + uiPopupBlockHandle *handle; + + handle = ui_popup_block_create(C, nullptr, nullptr, func, nullptr, arg, arg_free); + handle->popup = true; + + /* It can be useful to disable refresh (even though it will work) + * as this exists text fields which can be disruptive if refresh isn't needed. */ + handle->can_refresh = can_refresh; + + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + UI_block_active_only_flagged_buttons( + C, handle->region, static_cast(handle->region->uiblocks.first)); + WM_event_add_mousemove(window); +} + +void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free) +{ + UI_popup_block_invoke_ex(C, func, arg, arg_free, true); +} + +void UI_popup_block_ex(bContext *C, + uiBlockCreateFunc func, + uiBlockHandleFunc popup_func, + uiBlockCancelFunc cancel_func, + void *arg, + wmOperator *op) +{ + wmWindow *window = CTX_wm_window(C); + uiPopupBlockHandle *handle; + + handle = ui_popup_block_create(C, nullptr, nullptr, func, nullptr, arg, nullptr); + handle->popup = true; + handle->retvalue = 1; + handle->can_refresh = true; + + handle->popup_op = op; + handle->popup_arg = arg; + handle->popup_func = popup_func; + handle->cancel_func = cancel_func; + // handle->opcontext = opcontext; + + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + UI_block_active_only_flagged_buttons( + C, handle->region, static_cast(handle->region->uiblocks.first)); + WM_event_add_mousemove(window); +} + +#if 0 /* UNUSED */ +void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, wmOperatorCallContext opcontext) +{ + wmWindow *window = CTX_wm_window(C); + uiPopupBlockHandle *handle; + + handle = ui_popup_block_create(C, nullptr, nullptr, func, nullptr, op, nullptr); + handle->popup = 1; + handle->retvalue = 1; + handle->can_refresh = true; + + handle->popup_arg = op; + handle->popup_func = operator_cb; + handle->cancel_func = confirm_cancel_operator; + handle->opcontext = opcontext; + + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + WM_event_add_mousemove(C); +} +#endif + +void UI_popup_block_close(bContext *C, wmWindow *win, uiBlock *block) +{ + /* if loading new .blend while popup is open, window will be nullptr */ + if (block->handle) { + if (win) { + const bScreen *screen = WM_window_get_active_screen(win); + + UI_popup_handlers_remove(&win->modalhandlers, block->handle); + ui_popup_block_free(C, block->handle); + + /* In the case we have nested popups, + * closing one may need to redraw another, see: T48874 */ + LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { + ED_region_tag_refresh_ui(region); + } + } + } +} + +bool UI_popup_block_name_exists(const bScreen *screen, const char *name) +{ + LISTBASE_FOREACH (const ARegion *, region, &screen->regionbase) { + LISTBASE_FOREACH (const uiBlock *, block, ®ion->uiblocks) { + if (STREQ(block->name, name)) { + return true; + } + } + } + return false; +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_region_popover.c b/source/blender/editors/interface/interface_region_popover.c deleted file mode 100644 index 7c7c9e887dd..00000000000 --- a/source/blender/editors/interface/interface_region_popover.c +++ /dev/null @@ -1,416 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * Pop-Over Region - * - * \note This is very close to 'interface_region_menu_popup.c' - * - * We could even merge them, however menu logic is already over-loaded. - * PopOver's have the following differences. - * - * - UI is not constrained to a list. - * - Pressing a button won't close the pop-over. - * - Different draw style (to show this is has different behavior from a menu). - * - #PanelType are used instead of #MenuType. - * - No menu flipping support. - * - No moving the menu to fit the mouse cursor. - * - No key accelerators to access menu items - * (if we add support they would work differently). - * - No arrow key navigation. - * - No menu memory. - * - No title. - */ - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" - -#include "BLI_math_vector.h" -#include "BLI_rect.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" -#include "BKE_report.h" -#include "BKE_screen.h" - -#include "ED_screen.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_interface.h" - -#include "interface_intern.h" -#include "interface_regions_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Popup Menu with Callback or String - * \{ */ - -struct uiPopover { - uiBlock *block; - uiLayout *layout; - uiBut *but; - ARegion *butregion; - - /* Needed for keymap removal. */ - wmWindow *window; - wmKeyMap *keymap; - struct wmEventHandler_Keymap *keymap_handler; - - uiMenuCreateFunc menu_func; - void *menu_arg; - - /* Size in pixels (ui scale applied). */ - int ui_size_x; - -#ifdef USE_UI_POPOVER_ONCE - bool is_once; -#endif -}; - -static void ui_popover_create_block(bContext *C, uiPopover *pup, wmOperatorCallContext opcontext) -{ - BLI_assert(pup->ui_size_x != 0); - - const uiStyle *style = UI_style_get_dpi(); - - pup->block = UI_block_begin(C, NULL, __func__, UI_EMBOSS); - UI_block_flag_enable(pup->block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER); -#ifdef USE_UI_POPOVER_ONCE - if (pup->is_once) { - UI_block_flag_enable(pup->block, UI_BLOCK_POPOVER_ONCE); - } -#endif - - pup->layout = UI_block_layout( - pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, pup->ui_size_x, 0, 0, style); - - uiLayoutSetOperatorContext(pup->layout, opcontext); - - if (pup->but) { - if (pup->but->context) { - uiLayoutContextCopy(pup->layout, pup->but->context); - } - } - - pup->block->flag |= UI_BLOCK_NO_FLIP; -} - -static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, void *arg_pup) -{ - uiPopover *pup = arg_pup; - - /* Create UI block and layout now if it wasn't done between begin/end. */ - if (!pup->layout) { - ui_popover_create_block(C, pup, WM_OP_INVOKE_REGION_WIN); - - if (pup->menu_func) { - pup->block->handle = handle; - pup->menu_func(C, pup->layout, pup->menu_arg); - pup->block->handle = NULL; - } - - pup->layout = NULL; - } - - /* Setup and resolve UI layout for block. */ - uiBlock *block = pup->block; - int width, height; - - UI_block_region_set(block, handle->region); - UI_block_layout_resolve(block, &width, &height); - UI_block_direction_set(block, UI_DIR_DOWN | UI_DIR_CENTER_X); - - const int block_margin = U.widget_unit / 2; - - if (pup->but) { - /* For a header menu we set the direction automatic. */ - block->minbounds = BLI_rctf_size_x(&pup->but->rect); - UI_block_bounds_set_normal(block, block_margin); - - /* If menu slides out of other menu, override direction. */ - const bool slideout = ui_block_is_menu(pup->but->block); - if (slideout) { - UI_block_direction_set(block, UI_DIR_RIGHT); - } - - /* Store the button location for positioning the popover arrow hint. */ - if (!handle->refresh) { - float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)}; - ui_block_to_window_fl(handle->ctx_region, pup->but->block, ¢er[0], ¢er[1]); - /* These variables aren't used for popovers, - * we could add new variables if there is a conflict. */ - block->bounds_offset[0] = (int)center[0]; - block->bounds_offset[1] = (int)center[1]; - copy_v2_v2_int(handle->prev_bounds_offset, block->bounds_offset); - } - else { - copy_v2_v2_int(block->bounds_offset, handle->prev_bounds_offset); - } - - if (!slideout) { - ARegion *region = CTX_wm_region(C); - - if (region && region->panels.first) { - /* For regions with panels, prefer to open to top so we can - * see the values of the buttons below changing. */ - UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X); - } - /* Prefer popover from header to be positioned into the editor. */ - else if (region) { - if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) { - if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) { - UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X); - } - } - } - } - - /* Estimated a maximum size so we don't go off-screen for low height - * areas near the bottom of the window on refreshes. */ - handle->max_size_y = UI_UNIT_Y * 16.0f; - } - else { - /* Not attached to a button. */ - int bounds_offset[2] = {0, 0}; - UI_block_flag_enable(block, UI_BLOCK_LOOP); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - UI_block_direction_set(block, block->direction); - block->minbounds = UI_MENU_WIDTH_MIN; - - if (!handle->refresh) { - uiBut *but = NULL; - uiBut *but_first = NULL; - LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { - if ((but_first == NULL) && ui_but_is_editable(but_iter)) { - but_first = but_iter; - } - if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) { - but = but_iter; - break; - } - } - - if (but) { - bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect)); - bounds_offset[1] = -BLI_rctf_cent_y(&but->rect); - } - else { - bounds_offset[0] = -(pup->ui_size_x / 2); - bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2); - } - copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset); - } - else { - copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset); - } - - UI_block_bounds_set_popup(block, block_margin, bounds_offset); - } - - return block; -} - -static void ui_block_free_func_POPOVER(void *arg_pup) -{ - uiPopover *pup = arg_pup; - if (pup->keymap != NULL) { - wmWindow *window = pup->window; - WM_event_remove_keymap_handler(&window->modalhandlers, pup->keymap); - } - MEM_freeN(pup); -} - -uiPopupBlockHandle *ui_popover_panel_create( - bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg) -{ - wmWindow *window = CTX_wm_window(C); - const uiStyle *style = UI_style_get_dpi(); - const PanelType *panel_type = (PanelType *)arg; - - /* Create popover, buttons are created from callback. */ - uiPopover *pup = MEM_callocN(sizeof(uiPopover), __func__); - pup->but = but; - - /* FIXME: maybe one day we want non panel popovers? */ - { - const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS : - panel_type->ui_units_x; - /* Scale width by changes to Text Style point size. */ - const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points); - pup->ui_size_x = ui_units_x * U.widget_unit * - (text_points_max / (float)UI_DEFAULT_TEXT_POINTS); - } - - pup->menu_func = menu_func; - pup->menu_arg = arg; - -#ifdef USE_UI_POPOVER_ONCE - { - /* Ideally this would be passed in. */ - const wmEvent *event = window->eventstate; - pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS); - } -#endif - - /* Create popup block. */ - uiPopupBlockHandle *handle; - handle = ui_popup_block_create( - C, butregion, but, NULL, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER); - handle->can_refresh = true; - - /* Add handlers. If attached to a button, the button will already - * add a modal handler and pass on events. */ - if (!but) { - UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); - WM_event_add_mousemove(window); - handle->popup = true; - } - - return handle; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Standard Popover Panels - * \{ */ - -int UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports) -{ - uiLayout *layout; - PanelType *pt = WM_paneltype_find(idname, true); - if (pt == NULL) { - BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname); - return OPERATOR_CANCELLED; - } - - if (pt->poll && (pt->poll(C, pt) == false)) { - /* cancel but allow event to pass through, just like operators do */ - return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); - } - - uiBlock *block = NULL; - if (keep_open) { - uiPopupBlockHandle *handle = ui_popover_panel_create( - C, NULL, NULL, ui_item_paneltype_func, pt); - uiPopover *pup = handle->popup_create_vars.arg; - block = pup->block; - } - else { - uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false); - layout = UI_popover_layout(pup); - UI_paneltype_draw(C, pt, layout); - UI_popover_end(C, pup, NULL); - block = pup->block; - } - - if (block) { - uiPopupBlockHandle *handle = block->handle; - UI_block_active_only_flagged_buttons(C, handle->region, block); - } - return OPERATOR_INTERFACE; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Popup Menu API with begin & end - * \{ */ - -uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button) -{ - uiPopover *pup = MEM_callocN(sizeof(uiPopover), "popover menu"); - if (ui_menu_width == 0) { - ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS; - } - pup->ui_size_x = ui_menu_width; - - ARegion *butregion = NULL; - uiBut *but = NULL; - - if (from_active_button) { - butregion = CTX_wm_region(C); - but = UI_region_active_but_get(butregion); - if (but == NULL) { - butregion = NULL; - } - } - - pup->but = but; - pup->butregion = butregion; - - /* Operator context default same as menus, change if needed. */ - ui_popover_create_block(C, pup, WM_OP_EXEC_REGION_WIN); - - /* create in advance so we can let buttons point to retval already */ - pup->block->handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle"); - - return pup; -} - -static void popover_keymap_fn(wmKeyMap *UNUSED(keymap), wmKeyMapItem *UNUSED(kmi), void *user_data) -{ - uiPopover *pup = user_data; - pup->block->handle->menuretval = UI_RETURN_OK; -} - -void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap) -{ - wmWindow *window = CTX_wm_window(C); - /* Create popup block. No refresh support since the buttons were created - * between begin/end and we have no callback to recreate them. */ - uiPopupBlockHandle *handle; - - if (keymap) { - /* Add so we get keymaps shown in the buttons. */ - UI_block_flag_enable(pup->block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); - pup->keymap = keymap; - pup->keymap_handler = WM_event_add_keymap_handler_priority(&window->modalhandlers, keymap, 0); - WM_event_set_keymap_handler_post_callback(pup->keymap_handler, popover_keymap_fn, pup); - } - - handle = ui_popup_block_create( - C, pup->butregion, pup->but, NULL, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER); - - /* Add handlers. */ - UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); - WM_event_add_mousemove(window); - handle->popup = true; - - /* Re-add so it gets priority. */ - if (keymap) { - BLI_remlink(&window->modalhandlers, pup->keymap_handler); - BLI_addhead(&window->modalhandlers, pup->keymap_handler); - } - - pup->window = window; - - /* TODO(campbell): we may want to make this configurable. - * The begin/end stype of calling popups doesn't allow 'can_refresh' to be set. - * For now close this style of popovers when accessed. */ - UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN); - - /* Panels are created flipped (from event handling POV). */ - pup->block->flag ^= UI_BLOCK_IS_FLIP; -} - -uiLayout *UI_popover_layout(uiPopover *pup) -{ - return pup->layout; -} - -#ifdef USE_UI_POPOVER_ONCE -void UI_popover_once_clear(uiPopover *pup) -{ - pup->is_once = false; -} -#endif - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_popover.cc b/source/blender/editors/interface/interface_region_popover.cc new file mode 100644 index 00000000000..2e10261a4f7 --- /dev/null +++ b/source/blender/editors/interface/interface_region_popover.cc @@ -0,0 +1,421 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * Pop-Over Region + * + * \note This is very close to 'interface_region_menu_popup.c' + * + * We could even merge them, however menu logic is already over-loaded. + * PopOver's have the following differences. + * + * - UI is not constrained to a list. + * - Pressing a button won't close the pop-over. + * - Different draw style (to show this is has different behavior from a menu). + * - #PanelType are used instead of #MenuType. + * - No menu flipping support. + * - No moving the menu to fit the mouse cursor. + * - No key accelerators to access menu items + * (if we add support they would work differently). + * - No arrow key navigation. + * - No menu memory. + * - No title. + */ + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" + +#include "BLI_math_vector.h" +#include "BLI_rect.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_report.h" +#include "BKE_screen.h" + +#include "ED_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "interface_intern.h" +#include "interface_regions_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu with Callback or String + * \{ */ + +struct uiPopover { + uiBlock *block; + uiLayout *layout; + uiBut *but; + ARegion *butregion; + + /* Needed for keymap removal. */ + wmWindow *window; + wmKeyMap *keymap; + struct wmEventHandler_Keymap *keymap_handler; + + uiMenuCreateFunc menu_func; + void *menu_arg; + + /* Size in pixels (ui scale applied). */ + int ui_size_x; + +#ifdef USE_UI_POPOVER_ONCE + bool is_once; +#endif +}; + +static void ui_popover_create_block(bContext *C, uiPopover *pup, wmOperatorCallContext opcontext) +{ + BLI_assert(pup->ui_size_x != 0); + + const uiStyle *style = UI_style_get_dpi(); + + pup->block = UI_block_begin(C, nullptr, __func__, UI_EMBOSS); + UI_block_flag_enable(pup->block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_POPOVER); +#ifdef USE_UI_POPOVER_ONCE + if (pup->is_once) { + UI_block_flag_enable(pup->block, UI_BLOCK_POPOVER_ONCE); + } +#endif + + pup->layout = UI_block_layout( + pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, pup->ui_size_x, 0, 0, style); + + uiLayoutSetOperatorContext(pup->layout, opcontext); + + if (pup->but) { + if (pup->but->context) { + uiLayoutContextCopy(pup->layout, pup->but->context); + } + } + + pup->block->flag |= UI_BLOCK_NO_FLIP; +} + +static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, void *arg_pup) +{ + uiPopover *pup = static_cast(arg_pup); + + /* Create UI block and layout now if it wasn't done between begin/end. */ + if (!pup->layout) { + ui_popover_create_block(C, pup, WM_OP_INVOKE_REGION_WIN); + + if (pup->menu_func) { + pup->block->handle = handle; + pup->menu_func(C, pup->layout, pup->menu_arg); + pup->block->handle = nullptr; + } + + pup->layout = nullptr; + } + + /* Setup and resolve UI layout for block. */ + uiBlock *block = pup->block; + int width, height; + + UI_block_region_set(block, handle->region); + UI_block_layout_resolve(block, &width, &height); + UI_block_direction_set(block, UI_DIR_DOWN | UI_DIR_CENTER_X); + + const int block_margin = U.widget_unit / 2; + + if (pup->but) { + /* For a header menu we set the direction automatic. */ + block->minbounds = BLI_rctf_size_x(&pup->but->rect); + UI_block_bounds_set_normal(block, block_margin); + + /* If menu slides out of other menu, override direction. */ + const bool slideout = ui_block_is_menu(pup->but->block); + if (slideout) { + UI_block_direction_set(block, UI_DIR_RIGHT); + } + + /* Store the button location for positioning the popover arrow hint. */ + if (!handle->refresh) { + float center[2] = {BLI_rctf_cent_x(&pup->but->rect), BLI_rctf_cent_y(&pup->but->rect)}; + ui_block_to_window_fl(handle->ctx_region, pup->but->block, ¢er[0], ¢er[1]); + /* These variables aren't used for popovers, + * we could add new variables if there is a conflict. */ + block->bounds_offset[0] = (int)center[0]; + block->bounds_offset[1] = (int)center[1]; + copy_v2_v2_int(handle->prev_bounds_offset, block->bounds_offset); + } + else { + copy_v2_v2_int(block->bounds_offset, handle->prev_bounds_offset); + } + + if (!slideout) { + ARegion *region = CTX_wm_region(C); + + if (region && region->panels.first) { + /* For regions with panels, prefer to open to top so we can + * see the values of the buttons below changing. */ + UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X); + } + /* Prefer popover from header to be positioned into the editor. */ + else if (region) { + if (RGN_TYPE_IS_HEADER_ANY(region->regiontype)) { + if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) == RGN_ALIGN_BOTTOM) { + UI_block_direction_set(block, UI_DIR_UP | UI_DIR_CENTER_X); + } + } + } + } + + /* Estimated a maximum size so we don't go off-screen for low height + * areas near the bottom of the window on refreshes. */ + handle->max_size_y = UI_UNIT_Y * 16.0f; + } + else { + /* Not attached to a button. */ + int bounds_offset[2] = {0, 0}; + UI_block_flag_enable(block, UI_BLOCK_LOOP); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + UI_block_direction_set(block, block->direction); + block->minbounds = UI_MENU_WIDTH_MIN; + + if (!handle->refresh) { + uiBut *but = nullptr; + uiBut *but_first = nullptr; + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + if ((but_first == nullptr) && ui_but_is_editable(but_iter)) { + but_first = but_iter; + } + if (but_iter->flag & (UI_SELECT | UI_SELECT_DRAW)) { + but = but_iter; + break; + } + } + + if (but) { + bounds_offset[0] = -(but->rect.xmin + 0.8f * BLI_rctf_size_x(&but->rect)); + bounds_offset[1] = -BLI_rctf_cent_y(&but->rect); + } + else { + bounds_offset[0] = -(pup->ui_size_x / 2); + bounds_offset[1] = but_first ? -BLI_rctf_cent_y(&but_first->rect) : (UI_UNIT_Y / 2); + } + copy_v2_v2_int(handle->prev_bounds_offset, bounds_offset); + } + else { + copy_v2_v2_int(bounds_offset, handle->prev_bounds_offset); + } + + UI_block_bounds_set_popup(block, block_margin, bounds_offset); + } + + return block; +} + +static void ui_block_free_func_POPOVER(void *arg_pup) +{ + uiPopover *pup = static_cast(arg_pup); + if (pup->keymap != nullptr) { + wmWindow *window = pup->window; + WM_event_remove_keymap_handler(&window->modalhandlers, pup->keymap); + } + MEM_freeN(pup); +} + +uiPopupBlockHandle *ui_popover_panel_create( + bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg) +{ + wmWindow *window = CTX_wm_window(C); + const uiStyle *style = UI_style_get_dpi(); + const PanelType *panel_type = (PanelType *)arg; + + /* Create popover, buttons are created from callback. */ + uiPopover *pup = MEM_cnew(__func__); + pup->but = but; + + /* FIXME: maybe one day we want non panel popovers? */ + { + const int ui_units_x = (panel_type->ui_units_x == 0) ? UI_POPOVER_WIDTH_UNITS : + panel_type->ui_units_x; + /* Scale width by changes to Text Style point size. */ + const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points); + pup->ui_size_x = ui_units_x * U.widget_unit * + (text_points_max / (float)UI_DEFAULT_TEXT_POINTS); + } + + pup->menu_func = menu_func; + pup->menu_arg = arg; + +#ifdef USE_UI_POPOVER_ONCE + { + /* Ideally this would be passed in. */ + const wmEvent *event = window->eventstate; + pup->is_once = (event->type == LEFTMOUSE) && (event->val == KM_PRESS); + } +#endif + + /* Create popup block. */ + uiPopupBlockHandle *handle; + handle = ui_popup_block_create( + C, butregion, but, nullptr, ui_block_func_POPOVER, pup, ui_block_free_func_POPOVER); + handle->can_refresh = true; + + /* Add handlers. If attached to a button, the button will already + * add a modal handler and pass on events. */ + if (!but) { + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + WM_event_add_mousemove(window); + handle->popup = true; + } + + return handle; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Standard Popover Panels + * \{ */ + +int UI_popover_panel_invoke(bContext *C, const char *idname, bool keep_open, ReportList *reports) +{ + uiLayout *layout; + PanelType *pt = WM_paneltype_find(idname, true); + if (pt == nullptr) { + BKE_reportf(reports, RPT_ERROR, "Panel \"%s\" not found", idname); + return OPERATOR_CANCELLED; + } + + if (pt->poll && (pt->poll(C, pt) == false)) { + /* cancel but allow event to pass through, just like operators do */ + return (OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH); + } + + uiBlock *block = nullptr; + if (keep_open) { + uiPopupBlockHandle *handle = ui_popover_panel_create( + C, nullptr, nullptr, ui_item_paneltype_func, pt); + uiPopover *pup = static_cast(handle->popup_create_vars.arg); + block = pup->block; + } + else { + uiPopover *pup = UI_popover_begin(C, U.widget_unit * pt->ui_units_x, false); + layout = UI_popover_layout(pup); + UI_paneltype_draw(C, pt, layout); + UI_popover_end(C, pup, nullptr); + block = pup->block; + } + + if (block) { + uiPopupBlockHandle *handle = static_cast(block->handle); + UI_block_active_only_flagged_buttons(C, handle->region, block); + } + return OPERATOR_INTERFACE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Popup Menu API with begin & end + * \{ */ + +uiPopover *UI_popover_begin(bContext *C, int ui_menu_width, bool from_active_button) +{ + uiPopover *pup = MEM_cnew(__func__); + if (ui_menu_width == 0) { + ui_menu_width = U.widget_unit * UI_POPOVER_WIDTH_UNITS; + } + pup->ui_size_x = ui_menu_width; + + ARegion *butregion = nullptr; + uiBut *but = nullptr; + + if (from_active_button) { + butregion = CTX_wm_region(C); + but = UI_region_active_but_get(butregion); + if (but == nullptr) { + butregion = nullptr; + } + } + + pup->but = but; + pup->butregion = butregion; + + /* Operator context default same as menus, change if needed. */ + ui_popover_create_block(C, pup, WM_OP_EXEC_REGION_WIN); + + /* create in advance so we can let buttons point to retval already */ + pup->block->handle = MEM_cnew(__func__); + + return pup; +} + +static void popover_keymap_fn(wmKeyMap *UNUSED(keymap), wmKeyMapItem *UNUSED(kmi), void *user_data) +{ + uiPopover *pup = static_cast(user_data); + pup->block->handle->menuretval = UI_RETURN_OK; +} + +void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap) +{ + wmWindow *window = CTX_wm_window(C); + /* Create popup block. No refresh support since the buttons were created + * between begin/end and we have no callback to recreate them. */ + uiPopupBlockHandle *handle; + + if (keymap) { + /* Add so we get keymaps shown in the buttons. */ + UI_block_flag_enable(pup->block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); + pup->keymap = keymap; + pup->keymap_handler = WM_event_add_keymap_handler_priority(&window->modalhandlers, keymap, 0); + WM_event_set_keymap_handler_post_callback(pup->keymap_handler, popover_keymap_fn, pup); + } + + handle = ui_popup_block_create(C, + pup->butregion, + pup->but, + nullptr, + ui_block_func_POPOVER, + pup, + ui_block_free_func_POPOVER); + + /* Add handlers. */ + UI_popup_handlers_add(C, &window->modalhandlers, handle, 0); + WM_event_add_mousemove(window); + handle->popup = true; + + /* Re-add so it gets priority. */ + if (keymap) { + BLI_remlink(&window->modalhandlers, pup->keymap_handler); + BLI_addhead(&window->modalhandlers, pup->keymap_handler); + } + + pup->window = window; + + /* TODO(campbell): we may want to make this configurable. + * The begin/end stype of calling popups doesn't allow 'can_refresh' to be set. + * For now close this style of popovers when accessed. */ + UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN); + + /* Panels are created flipped (from event handling POV). */ + pup->block->flag ^= UI_BLOCK_IS_FLIP; +} + +uiLayout *UI_popover_layout(uiPopover *pup) +{ + return pup->layout; +} + +#ifdef USE_UI_POPOVER_ONCE +void UI_popover_once_clear(uiPopover *pup) +{ + pup->is_once = false; +} +#endif + +/** \} */ diff --git a/source/blender/editors/interface/interface_region_popup.c b/source/blender/editors/interface/interface_region_popup.c deleted file mode 100644 index 2f2556225b5..00000000000 --- a/source/blender/editors/interface/interface_region_popup.c +++ /dev/null @@ -1,838 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * PopUp Region (Generic) - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_utildefines.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_interface.h" - -#include "ED_screen.h" - -#include "interface_intern.h" -#include "interface_regions_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Utility Functions - * \{ */ - -void ui_popup_translate(ARegion *region, const int mdiff[2]) -{ - BLI_rcti_translate(®ion->winrct, UNPACK2(mdiff)); - - ED_region_update_rect(region); - - ED_region_tag_redraw(region); - - /* update blocks */ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - uiPopupBlockHandle *handle = block->handle; - /* Make empty, will be initialized on next use, see T60608. */ - BLI_rctf_init(&handle->prev_block_rect, 0, 0, 0, 0); - - LISTBASE_FOREACH (uiSafetyRct *, saferct, &block->saferct) { - BLI_rctf_translate(&saferct->parent, UNPACK2(mdiff)); - BLI_rctf_translate(&saferct->safety, UNPACK2(mdiff)); - } - } -} - -/* position block relative to but, result is in window space */ -static void ui_popup_block_position(wmWindow *window, - ARegion *butregion, - uiBut *but, - uiBlock *block) -{ - uiPopupBlockHandle *handle = block->handle; - - /* Compute button position in window coordinates using the source - * button region/block, to position the popup attached to it. */ - rctf butrct; - - if (!handle->refresh) { - ui_block_to_window_rctf(butregion, but->block, &butrct, &but->rect); - - /* widget_roundbox_set has this correction too, keep in sync */ - if (but->type != UI_BTYPE_PULLDOWN) { - if (but->drawflag & UI_BUT_ALIGN_TOP) { - butrct.ymax += U.pixelsize; - } - if (but->drawflag & UI_BUT_ALIGN_LEFT) { - butrct.xmin -= U.pixelsize; - } - } - - handle->prev_butrct = butrct; - } - else { - /* For refreshes, keep same button position so popup doesn't move. */ - butrct = handle->prev_butrct; - } - - /* Compute block size in window space, based on buttons contained in it. */ - if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { - if (block->buttons.first) { - BLI_rctf_init_minmax(&block->rect); - - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (block->content_hints & UI_BLOCK_CONTAINS_SUBMENU_BUT) { - bt->rect.xmax += UI_MENU_SUBMENU_PADDING; - } - BLI_rctf_union(&block->rect, &bt->rect); - } - } - else { - /* we're nice and allow empty blocks too */ - block->rect.xmin = block->rect.ymin = 0; - block->rect.xmax = block->rect.ymax = 20; - } - } - - ui_block_to_window_rctf(butregion, but->block, &block->rect, &block->rect); - - /* Compute direction relative to button, based on available space. */ - const int size_x = BLI_rctf_size_x(&block->rect) + 0.2f * UI_UNIT_X; /* 4 for shadow */ - const int size_y = BLI_rctf_size_y(&block->rect) + 0.2f * UI_UNIT_Y; - const int center_x = (block->direction & UI_DIR_CENTER_X) ? size_x / 2 : 0; - const int center_y = (block->direction & UI_DIR_CENTER_Y) ? size_y / 2 : 0; - - short dir1 = 0, dir2 = 0; - - if (!handle->refresh) { - bool left = 0, right = 0, top = 0, down = 0; - - const int win_x = WM_window_pixels_x(window); - const int win_y = WM_window_pixels_y(window); - - /* Take into account maximum size so we don't have to flip on refresh. */ - const float max_size_x = max_ff(size_x, handle->max_size_x); - const float max_size_y = max_ff(size_y, handle->max_size_y); - - /* check if there's space at all */ - if (butrct.xmin - max_size_x + center_x > 0.0f) { - left = 1; - } - if (butrct.xmax + max_size_x - center_x < win_x) { - right = 1; - } - if (butrct.ymin - max_size_y + center_y > 0.0f) { - down = 1; - } - if (butrct.ymax + max_size_y - center_y < win_y) { - top = 1; - } - - if (top == 0 && down == 0) { - if (butrct.ymin - max_size_y < win_y - butrct.ymax - max_size_y) { - top = 1; - } - else { - down = 1; - } - } - - dir1 = (block->direction & UI_DIR_ALL); - - /* Secondary directions. */ - if (dir1 & (UI_DIR_UP | UI_DIR_DOWN)) { - if (dir1 & UI_DIR_LEFT) { - dir2 = UI_DIR_LEFT; - } - else if (dir1 & UI_DIR_RIGHT) { - dir2 = UI_DIR_RIGHT; - } - dir1 &= (UI_DIR_UP | UI_DIR_DOWN); - } - - if ((dir2 == 0) && (ELEM(dir1, UI_DIR_LEFT, UI_DIR_RIGHT))) { - dir2 = UI_DIR_DOWN; - } - if ((dir2 == 0) && (ELEM(dir1, UI_DIR_UP, UI_DIR_DOWN))) { - dir2 = UI_DIR_LEFT; - } - - /* no space at all? don't change */ - if (left || right) { - if (dir1 == UI_DIR_LEFT && left == 0) { - dir1 = UI_DIR_RIGHT; - } - if (dir1 == UI_DIR_RIGHT && right == 0) { - dir1 = UI_DIR_LEFT; - } - /* this is aligning, not append! */ - if (dir2 == UI_DIR_LEFT && right == 0) { - dir2 = UI_DIR_RIGHT; - } - if (dir2 == UI_DIR_RIGHT && left == 0) { - dir2 = UI_DIR_LEFT; - } - } - if (down || top) { - if (dir1 == UI_DIR_UP && top == 0) { - dir1 = UI_DIR_DOWN; - } - if (dir1 == UI_DIR_DOWN && down == 0) { - dir1 = UI_DIR_UP; - } - BLI_assert(dir2 != UI_DIR_UP); - // if (dir2 == UI_DIR_UP && top == 0) { dir2 = UI_DIR_DOWN; } - if (dir2 == UI_DIR_DOWN && down == 0) { - dir2 = UI_DIR_UP; - } - } - - handle->prev_dir1 = dir1; - handle->prev_dir2 = dir2; - } - else { - /* For refreshes, keep same popup direct so popup doesn't move - * to a totally different position while editing in it. */ - dir1 = handle->prev_dir1; - dir2 = handle->prev_dir2; - } - - /* Compute offset based on direction. */ - float offset_x = 0, offset_y = 0; - - /* Ensure buttons don't come between the parent button and the popup, see: T63566. */ - const float offset_overlap = max_ff(U.pixelsize, 1.0f); - - if (dir1 == UI_DIR_LEFT) { - offset_x = (butrct.xmin - block->rect.xmax) + offset_overlap; - if (dir2 == UI_DIR_UP) { - offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING; - } - else { - offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING; - } - } - else if (dir1 == UI_DIR_RIGHT) { - offset_x = (butrct.xmax - block->rect.xmin) - offset_overlap; - if (dir2 == UI_DIR_UP) { - offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING; - } - else { - offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING; - } - } - else if (dir1 == UI_DIR_UP) { - offset_y = (butrct.ymax - block->rect.ymin) - offset_overlap; - if (dir2 == UI_DIR_RIGHT) { - offset_x = butrct.xmax - block->rect.xmax + center_x; - } - else { - offset_x = butrct.xmin - block->rect.xmin - center_x; - } - /* changed direction? */ - if ((dir1 & block->direction) == 0) { - /* TODO: still do */ - UI_block_order_flip(block); - } - } - else if (dir1 == UI_DIR_DOWN) { - offset_y = (butrct.ymin - block->rect.ymax) + offset_overlap; - if (dir2 == UI_DIR_RIGHT) { - offset_x = butrct.xmax - block->rect.xmax + center_x; - } - else { - offset_x = butrct.xmin - block->rect.xmin - center_x; - } - /* changed direction? */ - if ((dir1 & block->direction) == 0) { - /* TODO: still do */ - UI_block_order_flip(block); - } - } - - /* Center over popovers for eg. */ - if (block->direction & UI_DIR_CENTER_X) { - offset_x += BLI_rctf_size_x(&butrct) / ((dir2 == UI_DIR_LEFT) ? 2 : -2); - } - - /* Apply offset, buttons in window coords. */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - ui_block_to_window_rctf(butregion, but->block, &bt->rect, &bt->rect); - - BLI_rctf_translate(&bt->rect, offset_x, offset_y); - - /* ui_but_update recalculates drawstring size in pixels */ - ui_but_update(bt); - } - - BLI_rctf_translate(&block->rect, offset_x, offset_y); - - /* Safety calculus. */ - { - const float midx = BLI_rctf_cent_x(&butrct); - const float midy = BLI_rctf_cent_y(&butrct); - - /* when you are outside parent button, safety there should be smaller */ - - const int s1 = 40 * U.dpi_fac; - const int s2 = 3 * U.dpi_fac; - - /* parent button to left */ - if (midx < block->rect.xmin) { - block->safety.xmin = block->rect.xmin - s2; - } - else { - block->safety.xmin = block->rect.xmin - s1; - } - /* parent button to right */ - if (midx > block->rect.xmax) { - block->safety.xmax = block->rect.xmax + s2; - } - else { - block->safety.xmax = block->rect.xmax + s1; - } - - /* parent button on bottom */ - if (midy < block->rect.ymin) { - block->safety.ymin = block->rect.ymin - s2; - } - else { - block->safety.ymin = block->rect.ymin - s1; - } - /* parent button on top */ - if (midy > block->rect.ymax) { - block->safety.ymax = block->rect.ymax + s2; - } - else { - block->safety.ymax = block->rect.ymax + s1; - } - - /* Exception for switched pull-downs. */ - if (dir1 && (dir1 & block->direction) == 0) { - if (dir2 == UI_DIR_RIGHT) { - block->safety.xmax = block->rect.xmax + s2; - } - if (dir2 == UI_DIR_LEFT) { - block->safety.xmin = block->rect.xmin - s2; - } - } - block->direction = dir1; - } - - /* Keep a list of these, needed for pull-down menus. */ - uiSafetyRct *saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct"); - saferct->parent = butrct; - saferct->safety = block->safety; - BLI_freelistN(&block->saferct); - BLI_duplicatelist(&block->saferct, &but->block->saferct); - BLI_addhead(&block->saferct, saferct); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Menu Block Creation - * \{ */ - -static void ui_block_region_refresh(const bContext *C, ARegion *region) -{ - ScrArea *ctx_area = CTX_wm_area(C); - ARegion *ctx_region = CTX_wm_region(C); - - if (region->do_draw & RGN_REFRESH_UI) { - ScrArea *handle_ctx_area; - ARegion *handle_ctx_region; - - region->do_draw &= ~RGN_REFRESH_UI; - LISTBASE_FOREACH_MUTABLE (uiBlock *, block, ®ion->uiblocks) { - uiPopupBlockHandle *handle = block->handle; - - if (handle->can_refresh) { - handle_ctx_area = handle->ctx_area; - handle_ctx_region = handle->ctx_region; - - if (handle_ctx_area) { - CTX_wm_area_set((bContext *)C, handle_ctx_area); - } - if (handle_ctx_region) { - CTX_wm_region_set((bContext *)C, handle_ctx_region); - } - - uiBut *but = handle->popup_create_vars.but; - ARegion *butregion = handle->popup_create_vars.butregion; - ui_popup_block_refresh((bContext *)C, handle, butregion, but); - } - } - } - - CTX_wm_area_set((bContext *)C, ctx_area); - CTX_wm_region_set((bContext *)C, ctx_region); -} - -static void ui_block_region_draw(const bContext *C, ARegion *region) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - UI_block_draw(C, block); - } -} - -/** - * Use to refresh centered popups on screen resizing (for splash). - */ -static void ui_block_region_popup_window_listener(const wmRegionListenerParams *params) -{ - ARegion *region = params->region; - wmNotifier *wmn = params->notifier; - - switch (wmn->category) { - case NC_WINDOW: { - switch (wmn->action) { - case NA_EDITED: { - /* window resize */ - ED_region_tag_refresh_ui(region); - break; - } - } - break; - } - } -} - -static void ui_popup_block_clip(wmWindow *window, uiBlock *block) -{ - const float xmin_orig = block->rect.xmin; - const int margin = UI_SCREEN_MARGIN; - int winx, winy; - - if (block->flag & UI_BLOCK_NO_WIN_CLIP) { - return; - } - - winx = WM_window_pixels_x(window); - winy = WM_window_pixels_y(window); - - /* shift to left if outside of view */ - if (block->rect.xmax > winx - margin) { - const float xofs = winx - margin - block->rect.xmax; - block->rect.xmin += xofs; - block->rect.xmax += xofs; - } - /* shift menus to right if outside of view */ - if (block->rect.xmin < margin) { - const float xofs = (margin - block->rect.xmin); - block->rect.xmin += xofs; - block->rect.xmax += xofs; - } - - if (block->rect.ymin < margin) { - block->rect.ymin = margin; - } - if (block->rect.ymax > winy - UI_POPUP_MENU_TOP) { - block->rect.ymax = winy - UI_POPUP_MENU_TOP; - } - - /* ensure menu items draw inside left/right boundary */ - const float xofs = block->rect.xmin - xmin_orig; - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - bt->rect.xmin += xofs; - bt->rect.xmax += xofs; - } -} - -void ui_popup_block_scrolltest(uiBlock *block) -{ - block->flag &= ~(UI_BLOCK_CLIPBOTTOM | UI_BLOCK_CLIPTOP); - - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - bt->flag &= ~UI_SCROLLED; - } - - if (block->buttons.first == block->buttons.last) { - return; - } - - /* mark buttons that are outside boundary */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (bt->rect.ymin < block->rect.ymin) { - bt->flag |= UI_SCROLLED; - block->flag |= UI_BLOCK_CLIPBOTTOM; - } - if (bt->rect.ymax > block->rect.ymax) { - bt->flag |= UI_SCROLLED; - block->flag |= UI_BLOCK_CLIPTOP; - } - } - - /* mark buttons overlapping arrows, if we have them */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (block->flag & UI_BLOCK_CLIPBOTTOM) { - if (bt->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) { - bt->flag |= UI_SCROLLED; - } - } - if (block->flag & UI_BLOCK_CLIPTOP) { - if (bt->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) { - bt->flag |= UI_SCROLLED; - } - } - } -} - -static void ui_popup_block_remove(bContext *C, uiPopupBlockHandle *handle) -{ - wmWindow *ctx_win = CTX_wm_window(C); - ScrArea *ctx_area = CTX_wm_area(C); - ARegion *ctx_region = CTX_wm_region(C); - - wmWindowManager *wm = CTX_wm_manager(C); - wmWindow *win = ctx_win; - bScreen *screen = CTX_wm_screen(C); - - /* There may actually be a different window active than the one showing the popup, so lookup real - * one. */ - if (BLI_findindex(&screen->regionbase, handle->region) == -1) { - LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) { - screen = WM_window_get_active_screen(win_iter); - if (BLI_findindex(&screen->regionbase, handle->region) != -1) { - win = win_iter; - break; - } - } - } - - BLI_assert(win && screen); - - CTX_wm_window_set(C, win); - ui_region_temp_remove(C, screen, handle->region); - - /* Reset context (area and region were NULL'ed when changing context window). */ - CTX_wm_window_set(C, ctx_win); - CTX_wm_area_set(C, ctx_area); - CTX_wm_region_set(C, ctx_region); - - /* reset to region cursor (only if there's not another menu open) */ - if (BLI_listbase_is_empty(&screen->regionbase)) { - win->tag_cursor_refresh = true; - } - - if (handle->scrolltimer) { - WM_event_remove_timer(wm, win, handle->scrolltimer); - } -} - -uiBlock *ui_popup_block_refresh(bContext *C, - uiPopupBlockHandle *handle, - ARegion *butregion, - uiBut *but) -{ - const int margin = UI_POPUP_MARGIN; - wmWindow *window = CTX_wm_window(C); - ARegion *region = handle->region; - - const uiBlockCreateFunc create_func = handle->popup_create_vars.create_func; - const uiBlockHandleCreateFunc handle_create_func = handle->popup_create_vars.handle_create_func; - void *arg = handle->popup_create_vars.arg; - - uiBlock *block_old = region->uiblocks.first; - uiBlock *block; - - handle->refresh = (block_old != NULL); - - BLI_assert(!handle->refresh || handle->can_refresh); - -#ifdef DEBUG - wmEvent *event_back = window->eventstate; - wmEvent *event_last_back = window->event_last_handled; -#endif - - /* create ui block */ - if (create_func) { - block = create_func(C, region, arg); - } - else { - block = handle_create_func(C, handle, arg); - } - - /* callbacks _must_ leave this for us, otherwise we can't call UI_block_update_from_old */ - BLI_assert(!block->endblock); - - /* ensure we don't use mouse coords here! */ -#ifdef DEBUG - window->eventstate = NULL; -#endif - - if (block->handle) { - memcpy(block->handle, handle, sizeof(uiPopupBlockHandle)); - MEM_freeN(handle); - handle = block->handle; - } - else { - block->handle = handle; - } - - region->regiondata = handle; - - /* set UI_BLOCK_NUMSELECT before UI_block_end() so we get alphanumeric keys assigned */ - if (but == NULL) { - block->flag |= UI_BLOCK_POPUP; - } - - block->flag |= UI_BLOCK_LOOP; - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - - /* defer this until blocks are translated (below) */ - block->oldblock = NULL; - - if (!block->endblock) { - UI_block_end_ex( - C, block, handle->popup_create_vars.event_xy, handle->popup_create_vars.event_xy); - } - - /* if this is being created from a button */ - if (but) { - block->aspect = but->block->aspect; - ui_popup_block_position(window, butregion, but, block); - handle->direction = block->direction; - } - else { - uiSafetyRct *saferct; - /* Keep a list of these, needed for pull-down menus. */ - saferct = MEM_callocN(sizeof(uiSafetyRct), "uiSafetyRct"); - saferct->safety = block->safety; - BLI_addhead(&block->saferct, saferct); - } - - if (block->flag & UI_BLOCK_RADIAL) { - const int win_width = UI_SCREEN_MARGIN; - int winx, winy; - - int x_offset = 0, y_offset = 0; - - winx = WM_window_pixels_x(window); - winy = WM_window_pixels_y(window); - - copy_v2_v2(block->pie_data.pie_center_init, block->pie_data.pie_center_spawned); - - /* only try translation if area is large enough */ - if (BLI_rctf_size_x(&block->rect) < winx - (2.0f * win_width)) { - if (block->rect.xmin < win_width) { - x_offset += win_width - block->rect.xmin; - } - if (block->rect.xmax > winx - win_width) { - x_offset += winx - win_width - block->rect.xmax; - } - } - - if (BLI_rctf_size_y(&block->rect) < winy - (2.0f * win_width)) { - if (block->rect.ymin < win_width) { - y_offset += win_width - block->rect.ymin; - } - if (block->rect.ymax > winy - win_width) { - y_offset += winy - win_width - block->rect.ymax; - } - } - /* if we are offsetting set up initial data for timeout functionality */ - - if ((x_offset != 0) || (y_offset != 0)) { - block->pie_data.pie_center_spawned[0] += x_offset; - block->pie_data.pie_center_spawned[1] += y_offset; - - UI_block_translate(block, x_offset, y_offset); - - if (U.pie_initial_timeout > 0) { - block->pie_data.flags |= UI_PIE_INITIAL_DIRECTION; - } - } - - region->winrct.xmin = 0; - region->winrct.xmax = winx; - region->winrct.ymin = 0; - region->winrct.ymax = winy; - - ui_block_calc_pie_segment(block, block->pie_data.pie_center_init); - - /* lastly set the buttons at the center of the pie menu, ready for animation */ - if (U.pie_animation_timeout > 0) { - LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { - if (but_iter->pie_dir != UI_RADIAL_NONE) { - BLI_rctf_recenter(&but_iter->rect, UNPACK2(block->pie_data.pie_center_spawned)); - } - } - } - } - else { - /* Add an offset to draw the popover arrow. */ - if ((block->flag & UI_BLOCK_POPOVER) && ELEM(block->direction, UI_DIR_UP, UI_DIR_DOWN)) { - /* Keep sync with 'ui_draw_popover_back_impl'. */ - const float unit_size = U.widget_unit / block->aspect; - const float unit_half = unit_size * (block->direction == UI_DIR_DOWN ? 0.5 : -0.5); - - UI_block_translate(block, 0, -unit_half); - } - - /* clip block with window boundary */ - ui_popup_block_clip(window, block); - - /* Avoid menu moving down and losing cursor focus by keeping it at - * the same height. */ - if (handle->refresh && handle->prev_block_rect.ymax > block->rect.ymax) { - if (block->bounds_type != UI_BLOCK_BOUNDS_POPUP_CENTER) { - const float offset = handle->prev_block_rect.ymax - block->rect.ymax; - UI_block_translate(block, 0, offset); - block->rect.ymin = handle->prev_block_rect.ymin; - } - } - - handle->prev_block_rect = block->rect; - - /* the block and buttons were positioned in window space as in 2.4x, now - * these menu blocks are regions so we bring it back to region space. - * additionally we add some padding for the menu shadow or rounded menus */ - region->winrct.xmin = block->rect.xmin - margin; - region->winrct.xmax = block->rect.xmax + margin; - region->winrct.ymin = block->rect.ymin - margin; - region->winrct.ymax = block->rect.ymax + UI_POPUP_MENU_TOP; - - UI_block_translate(block, -region->winrct.xmin, -region->winrct.ymin); - - /* apply scroll offset */ - if (handle->scrolloffset != 0.0f) { - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - bt->rect.ymin += handle->scrolloffset; - bt->rect.ymax += handle->scrolloffset; - } - } - } - - if (block_old) { - block->oldblock = block_old; - UI_block_update_from_old(C, block); - UI_blocklist_free_inactive(C, region); - } - - /* checks which buttons are visible, sets flags to prevent draw (do after region init) */ - ui_popup_block_scrolltest(block); - - /* adds subwindow */ - ED_region_floating_init(region); - - /* get winmat now that we actually have the subwindow */ - wmGetProjectionMatrix(block->winmat, ®ion->winrct); - - /* notify change and redraw */ - ED_region_tag_redraw(region); - - ED_region_update_rect(region); - -#ifdef DEBUG - window->eventstate = event_back; - window->event_last_handled = event_last_back; -#endif - - return block; -} - -uiPopupBlockHandle *ui_popup_block_create(bContext *C, - ARegion *butregion, - uiBut *but, - uiBlockCreateFunc create_func, - uiBlockHandleCreateFunc handle_create_func, - void *arg, - uiFreeArgFunc arg_free) -{ - wmWindow *window = CTX_wm_window(C); - uiBut *activebut = UI_context_active_but_get(C); - static ARegionType type; - ARegion *region; - uiBlock *block; - uiPopupBlockHandle *handle; - - /* disable tooltips from buttons below */ - if (activebut) { - UI_but_tooltip_timer_remove(C, activebut); - } - /* standard cursor by default */ - WM_cursor_set(window, WM_CURSOR_DEFAULT); - - /* create handle */ - handle = MEM_callocN(sizeof(uiPopupBlockHandle), "uiPopupBlockHandle"); - - /* store context for operator */ - handle->ctx_area = CTX_wm_area(C); - handle->ctx_region = CTX_wm_region(C); - - /* store vars to refresh popup (RGN_REFRESH_UI) */ - handle->popup_create_vars.create_func = create_func; - handle->popup_create_vars.handle_create_func = handle_create_func; - handle->popup_create_vars.arg = arg; - handle->popup_create_vars.arg_free = arg_free; - handle->popup_create_vars.but = but; - handle->popup_create_vars.butregion = but ? butregion : NULL; - copy_v2_v2_int(handle->popup_create_vars.event_xy, window->eventstate->xy); - - /* don't allow by default, only if popup type explicitly supports it */ - handle->can_refresh = false; - - /* create area region */ - region = ui_region_temp_add(CTX_wm_screen(C)); - handle->region = region; - - memset(&type, 0, sizeof(ARegionType)); - type.draw = ui_block_region_draw; - type.layout = ui_block_region_refresh; - type.regionid = RGN_TYPE_TEMPORARY; - region->type = &type; - - UI_region_handlers_add(®ion->handlers); - - block = ui_popup_block_refresh(C, handle, butregion, but); - handle = block->handle; - - /* keep centered on window resizing */ - if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) { - type.listener = ui_block_region_popup_window_listener; - } - - return handle; -} - -void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle) -{ - /* If this popup is created from a popover which does NOT have keep-open flag set, - * then close the popover too. We could extend this to other popup types too. */ - ARegion *region = handle->popup_create_vars.butregion; - if (region != NULL) { - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (block->handle && (block->flag & UI_BLOCK_POPOVER) && - (block->flag & UI_BLOCK_KEEP_OPEN) == 0) { - uiPopupBlockHandle *menu = block->handle; - menu->menuretval = UI_RETURN_OK; - } - } - } - - if (handle->popup_create_vars.arg_free) { - handle->popup_create_vars.arg_free(handle->popup_create_vars.arg); - } - - ui_popup_block_remove(C, handle); - - MEM_freeN(handle); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_popup.cc b/source/blender/editors/interface/interface_region_popup.cc new file mode 100644 index 00000000000..74c228e3338 --- /dev/null +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -0,0 +1,836 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * PopUp Region (Generic) + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_utildefines.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" + +#include "ED_screen.h" + +#include "interface_intern.h" +#include "interface_regions_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Utility Functions + * \{ */ + +void ui_popup_translate(ARegion *region, const int mdiff[2]) +{ + BLI_rcti_translate(®ion->winrct, UNPACK2(mdiff)); + + ED_region_update_rect(region); + + ED_region_tag_redraw(region); + + /* update blocks */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + uiPopupBlockHandle *handle = block->handle; + /* Make empty, will be initialized on next use, see T60608. */ + BLI_rctf_init(&handle->prev_block_rect, 0, 0, 0, 0); + + LISTBASE_FOREACH (uiSafetyRct *, saferct, &block->saferct) { + BLI_rctf_translate(&saferct->parent, UNPACK2(mdiff)); + BLI_rctf_translate(&saferct->safety, UNPACK2(mdiff)); + } + } +} + +/* position block relative to but, result is in window space */ +static void ui_popup_block_position(wmWindow *window, + ARegion *butregion, + uiBut *but, + uiBlock *block) +{ + uiPopupBlockHandle *handle = block->handle; + + /* Compute button position in window coordinates using the source + * button region/block, to position the popup attached to it. */ + rctf butrct; + + if (!handle->refresh) { + ui_block_to_window_rctf(butregion, but->block, &butrct, &but->rect); + + /* widget_roundbox_set has this correction too, keep in sync */ + if (but->type != UI_BTYPE_PULLDOWN) { + if (but->drawflag & UI_BUT_ALIGN_TOP) { + butrct.ymax += U.pixelsize; + } + if (but->drawflag & UI_BUT_ALIGN_LEFT) { + butrct.xmin -= U.pixelsize; + } + } + + handle->prev_butrct = butrct; + } + else { + /* For refreshes, keep same button position so popup doesn't move. */ + butrct = handle->prev_butrct; + } + + /* Compute block size in window space, based on buttons contained in it. */ + if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { + if (block->buttons.first) { + BLI_rctf_init_minmax(&block->rect); + + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (block->content_hints & UI_BLOCK_CONTAINS_SUBMENU_BUT) { + bt->rect.xmax += UI_MENU_SUBMENU_PADDING; + } + BLI_rctf_union(&block->rect, &bt->rect); + } + } + else { + /* we're nice and allow empty blocks too */ + block->rect.xmin = block->rect.ymin = 0; + block->rect.xmax = block->rect.ymax = 20; + } + } + + ui_block_to_window_rctf(butregion, but->block, &block->rect, &block->rect); + + /* Compute direction relative to button, based on available space. */ + const int size_x = BLI_rctf_size_x(&block->rect) + 0.2f * UI_UNIT_X; /* 4 for shadow */ + const int size_y = BLI_rctf_size_y(&block->rect) + 0.2f * UI_UNIT_Y; + const int center_x = (block->direction & UI_DIR_CENTER_X) ? size_x / 2 : 0; + const int center_y = (block->direction & UI_DIR_CENTER_Y) ? size_y / 2 : 0; + + short dir1 = 0, dir2 = 0; + + if (!handle->refresh) { + bool left = false, right = false, top = false, down = false; + + const int win_x = WM_window_pixels_x(window); + const int win_y = WM_window_pixels_y(window); + + /* Take into account maximum size so we don't have to flip on refresh. */ + const float max_size_x = max_ff(size_x, handle->max_size_x); + const float max_size_y = max_ff(size_y, handle->max_size_y); + + /* check if there's space at all */ + if (butrct.xmin - max_size_x + center_x > 0.0f) { + left = true; + } + if (butrct.xmax + max_size_x - center_x < win_x) { + right = true; + } + if (butrct.ymin - max_size_y + center_y > 0.0f) { + down = true; + } + if (butrct.ymax + max_size_y - center_y < win_y) { + top = true; + } + + if (top == 0 && down == 0) { + if (butrct.ymin - max_size_y < win_y - butrct.ymax - max_size_y) { + top = true; + } + else { + down = true; + } + } + + dir1 = (block->direction & UI_DIR_ALL); + + /* Secondary directions. */ + if (dir1 & (UI_DIR_UP | UI_DIR_DOWN)) { + if (dir1 & UI_DIR_LEFT) { + dir2 = UI_DIR_LEFT; + } + else if (dir1 & UI_DIR_RIGHT) { + dir2 = UI_DIR_RIGHT; + } + dir1 &= (UI_DIR_UP | UI_DIR_DOWN); + } + + if ((dir2 == 0) && (ELEM(dir1, UI_DIR_LEFT, UI_DIR_RIGHT))) { + dir2 = UI_DIR_DOWN; + } + if ((dir2 == 0) && (ELEM(dir1, UI_DIR_UP, UI_DIR_DOWN))) { + dir2 = UI_DIR_LEFT; + } + + /* no space at all? don't change */ + if (left || right) { + if (dir1 == UI_DIR_LEFT && left == 0) { + dir1 = UI_DIR_RIGHT; + } + if (dir1 == UI_DIR_RIGHT && right == 0) { + dir1 = UI_DIR_LEFT; + } + /* this is aligning, not append! */ + if (dir2 == UI_DIR_LEFT && right == 0) { + dir2 = UI_DIR_RIGHT; + } + if (dir2 == UI_DIR_RIGHT && left == 0) { + dir2 = UI_DIR_LEFT; + } + } + if (down || top) { + if (dir1 == UI_DIR_UP && top == 0) { + dir1 = UI_DIR_DOWN; + } + if (dir1 == UI_DIR_DOWN && down == 0) { + dir1 = UI_DIR_UP; + } + BLI_assert(dir2 != UI_DIR_UP); + // if (dir2 == UI_DIR_UP && top == 0) { dir2 = UI_DIR_DOWN; } + if (dir2 == UI_DIR_DOWN && down == 0) { + dir2 = UI_DIR_UP; + } + } + + handle->prev_dir1 = dir1; + handle->prev_dir2 = dir2; + } + else { + /* For refreshes, keep same popup direct so popup doesn't move + * to a totally different position while editing in it. */ + dir1 = handle->prev_dir1; + dir2 = handle->prev_dir2; + } + + /* Compute offset based on direction. */ + float offset_x = 0, offset_y = 0; + + /* Ensure buttons don't come between the parent button and the popup, see: T63566. */ + const float offset_overlap = max_ff(U.pixelsize, 1.0f); + + if (dir1 == UI_DIR_LEFT) { + offset_x = (butrct.xmin - block->rect.xmax) + offset_overlap; + if (dir2 == UI_DIR_UP) { + offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING; + } + else { + offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING; + } + } + else if (dir1 == UI_DIR_RIGHT) { + offset_x = (butrct.xmax - block->rect.xmin) - offset_overlap; + if (dir2 == UI_DIR_UP) { + offset_y = butrct.ymin - block->rect.ymin - center_y - UI_MENU_PADDING; + } + else { + offset_y = butrct.ymax - block->rect.ymax + center_y + UI_MENU_PADDING; + } + } + else if (dir1 == UI_DIR_UP) { + offset_y = (butrct.ymax - block->rect.ymin) - offset_overlap; + if (dir2 == UI_DIR_RIGHT) { + offset_x = butrct.xmax - block->rect.xmax + center_x; + } + else { + offset_x = butrct.xmin - block->rect.xmin - center_x; + } + /* changed direction? */ + if ((dir1 & block->direction) == 0) { + /* TODO: still do */ + UI_block_order_flip(block); + } + } + else if (dir1 == UI_DIR_DOWN) { + offset_y = (butrct.ymin - block->rect.ymax) + offset_overlap; + if (dir2 == UI_DIR_RIGHT) { + offset_x = butrct.xmax - block->rect.xmax + center_x; + } + else { + offset_x = butrct.xmin - block->rect.xmin - center_x; + } + /* changed direction? */ + if ((dir1 & block->direction) == 0) { + /* TODO: still do */ + UI_block_order_flip(block); + } + } + + /* Center over popovers for eg. */ + if (block->direction & UI_DIR_CENTER_X) { + offset_x += BLI_rctf_size_x(&butrct) / ((dir2 == UI_DIR_LEFT) ? 2 : -2); + } + + /* Apply offset, buttons in window coords. */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + ui_block_to_window_rctf(butregion, but->block, &bt->rect, &bt->rect); + + BLI_rctf_translate(&bt->rect, offset_x, offset_y); + + /* ui_but_update recalculates drawstring size in pixels */ + ui_but_update(bt); + } + + BLI_rctf_translate(&block->rect, offset_x, offset_y); + + /* Safety calculus. */ + { + const float midx = BLI_rctf_cent_x(&butrct); + const float midy = BLI_rctf_cent_y(&butrct); + + /* when you are outside parent button, safety there should be smaller */ + + const int s1 = 40 * U.dpi_fac; + const int s2 = 3 * U.dpi_fac; + + /* parent button to left */ + if (midx < block->rect.xmin) { + block->safety.xmin = block->rect.xmin - s2; + } + else { + block->safety.xmin = block->rect.xmin - s1; + } + /* parent button to right */ + if (midx > block->rect.xmax) { + block->safety.xmax = block->rect.xmax + s2; + } + else { + block->safety.xmax = block->rect.xmax + s1; + } + + /* parent button on bottom */ + if (midy < block->rect.ymin) { + block->safety.ymin = block->rect.ymin - s2; + } + else { + block->safety.ymin = block->rect.ymin - s1; + } + /* parent button on top */ + if (midy > block->rect.ymax) { + block->safety.ymax = block->rect.ymax + s2; + } + else { + block->safety.ymax = block->rect.ymax + s1; + } + + /* Exception for switched pull-downs. */ + if (dir1 && (dir1 & block->direction) == 0) { + if (dir2 == UI_DIR_RIGHT) { + block->safety.xmax = block->rect.xmax + s2; + } + if (dir2 == UI_DIR_LEFT) { + block->safety.xmin = block->rect.xmin - s2; + } + } + block->direction = dir1; + } + + /* Keep a list of these, needed for pull-down menus. */ + uiSafetyRct *saferct = MEM_cnew(__func__); + saferct->parent = butrct; + saferct->safety = block->safety; + BLI_freelistN(&block->saferct); + BLI_duplicatelist(&block->saferct, &but->block->saferct); + BLI_addhead(&block->saferct, saferct); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu Block Creation + * \{ */ + +static void ui_block_region_refresh(const bContext *C, ARegion *region) +{ + ScrArea *ctx_area = CTX_wm_area(C); + ARegion *ctx_region = CTX_wm_region(C); + + if (region->do_draw & RGN_REFRESH_UI) { + ScrArea *handle_ctx_area; + ARegion *handle_ctx_region; + + region->do_draw &= ~RGN_REFRESH_UI; + LISTBASE_FOREACH_MUTABLE (uiBlock *, block, ®ion->uiblocks) { + uiPopupBlockHandle *handle = block->handle; + + if (handle->can_refresh) { + handle_ctx_area = handle->ctx_area; + handle_ctx_region = handle->ctx_region; + + if (handle_ctx_area) { + CTX_wm_area_set((bContext *)C, handle_ctx_area); + } + if (handle_ctx_region) { + CTX_wm_region_set((bContext *)C, handle_ctx_region); + } + + uiBut *but = handle->popup_create_vars.but; + ARegion *butregion = handle->popup_create_vars.butregion; + ui_popup_block_refresh((bContext *)C, handle, butregion, but); + } + } + } + + CTX_wm_area_set((bContext *)C, ctx_area); + CTX_wm_region_set((bContext *)C, ctx_region); +} + +static void ui_block_region_draw(const bContext *C, ARegion *region) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + UI_block_draw(C, block); + } +} + +/** + * Use to refresh centered popups on screen resizing (for splash). + */ +static void ui_block_region_popup_window_listener(const wmRegionListenerParams *params) +{ + ARegion *region = params->region; + wmNotifier *wmn = params->notifier; + + switch (wmn->category) { + case NC_WINDOW: { + switch (wmn->action) { + case NA_EDITED: { + /* window resize */ + ED_region_tag_refresh_ui(region); + break; + } + } + break; + } + } +} + +static void ui_popup_block_clip(wmWindow *window, uiBlock *block) +{ + const float xmin_orig = block->rect.xmin; + const int margin = UI_SCREEN_MARGIN; + int winx, winy; + + if (block->flag & UI_BLOCK_NO_WIN_CLIP) { + return; + } + + winx = WM_window_pixels_x(window); + winy = WM_window_pixels_y(window); + + /* shift to left if outside of view */ + if (block->rect.xmax > winx - margin) { + const float xofs = winx - margin - block->rect.xmax; + block->rect.xmin += xofs; + block->rect.xmax += xofs; + } + /* shift menus to right if outside of view */ + if (block->rect.xmin < margin) { + const float xofs = (margin - block->rect.xmin); + block->rect.xmin += xofs; + block->rect.xmax += xofs; + } + + if (block->rect.ymin < margin) { + block->rect.ymin = margin; + } + if (block->rect.ymax > winy - UI_POPUP_MENU_TOP) { + block->rect.ymax = winy - UI_POPUP_MENU_TOP; + } + + /* ensure menu items draw inside left/right boundary */ + const float xofs = block->rect.xmin - xmin_orig; + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->rect.xmin += xofs; + bt->rect.xmax += xofs; + } +} + +void ui_popup_block_scrolltest(uiBlock *block) +{ + block->flag &= ~(UI_BLOCK_CLIPBOTTOM | UI_BLOCK_CLIPTOP); + + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->flag &= ~UI_SCROLLED; + } + + if (block->buttons.first == block->buttons.last) { + return; + } + + /* mark buttons that are outside boundary */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (bt->rect.ymin < block->rect.ymin) { + bt->flag |= UI_SCROLLED; + block->flag |= UI_BLOCK_CLIPBOTTOM; + } + if (bt->rect.ymax > block->rect.ymax) { + bt->flag |= UI_SCROLLED; + block->flag |= UI_BLOCK_CLIPTOP; + } + } + + /* mark buttons overlapping arrows, if we have them */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (block->flag & UI_BLOCK_CLIPBOTTOM) { + if (bt->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) { + bt->flag |= UI_SCROLLED; + } + } + if (block->flag & UI_BLOCK_CLIPTOP) { + if (bt->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) { + bt->flag |= UI_SCROLLED; + } + } + } +} + +static void ui_popup_block_remove(bContext *C, uiPopupBlockHandle *handle) +{ + wmWindow *ctx_win = CTX_wm_window(C); + ScrArea *ctx_area = CTX_wm_area(C); + ARegion *ctx_region = CTX_wm_region(C); + + wmWindowManager *wm = CTX_wm_manager(C); + wmWindow *win = ctx_win; + bScreen *screen = CTX_wm_screen(C); + + /* There may actually be a different window active than the one showing the popup, so lookup real + * one. */ + if (BLI_findindex(&screen->regionbase, handle->region) == -1) { + LISTBASE_FOREACH (wmWindow *, win_iter, &wm->windows) { + screen = WM_window_get_active_screen(win_iter); + if (BLI_findindex(&screen->regionbase, handle->region) != -1) { + win = win_iter; + break; + } + } + } + + BLI_assert(win && screen); + + CTX_wm_window_set(C, win); + ui_region_temp_remove(C, screen, handle->region); + + /* Reset context (area and region were nullptr'ed when changing context window). */ + CTX_wm_window_set(C, ctx_win); + CTX_wm_area_set(C, ctx_area); + CTX_wm_region_set(C, ctx_region); + + /* reset to region cursor (only if there's not another menu open) */ + if (BLI_listbase_is_empty(&screen->regionbase)) { + win->tag_cursor_refresh = true; + } + + if (handle->scrolltimer) { + WM_event_remove_timer(wm, win, handle->scrolltimer); + } +} + +uiBlock *ui_popup_block_refresh(bContext *C, + uiPopupBlockHandle *handle, + ARegion *butregion, + uiBut *but) +{ + const int margin = UI_POPUP_MARGIN; + wmWindow *window = CTX_wm_window(C); + ARegion *region = handle->region; + + const uiBlockCreateFunc create_func = handle->popup_create_vars.create_func; + const uiBlockHandleCreateFunc handle_create_func = handle->popup_create_vars.handle_create_func; + void *arg = handle->popup_create_vars.arg; + + uiBlock *block_old = static_cast(region->uiblocks.first); + uiBlock *block; + + handle->refresh = (block_old != nullptr); + + BLI_assert(!handle->refresh || handle->can_refresh); + +#ifdef DEBUG + wmEvent *event_back = window->eventstate; + wmEvent *event_last_back = window->event_last_handled; +#endif + + /* create ui block */ + if (create_func) { + block = create_func(C, region, arg); + } + else { + block = handle_create_func(C, handle, arg); + } + + /* callbacks _must_ leave this for us, otherwise we can't call UI_block_update_from_old */ + BLI_assert(!block->endblock); + + /* ensure we don't use mouse coords here! */ +#ifdef DEBUG + window->eventstate = nullptr; +#endif + + if (block->handle) { + memcpy(block->handle, handle, sizeof(uiPopupBlockHandle)); + MEM_freeN(handle); + handle = block->handle; + } + else { + block->handle = handle; + } + + region->regiondata = handle; + + /* set UI_BLOCK_NUMSELECT before UI_block_end() so we get alphanumeric keys assigned */ + if (but == nullptr) { + block->flag |= UI_BLOCK_POPUP; + } + + block->flag |= UI_BLOCK_LOOP; + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + /* defer this until blocks are translated (below) */ + block->oldblock = nullptr; + + if (!block->endblock) { + UI_block_end_ex( + C, block, handle->popup_create_vars.event_xy, handle->popup_create_vars.event_xy); + } + + /* if this is being created from a button */ + if (but) { + block->aspect = but->block->aspect; + ui_popup_block_position(window, butregion, but, block); + handle->direction = block->direction; + } + else { + /* Keep a list of these, needed for pull-down menus. */ + uiSafetyRct *saferct = MEM_cnew(__func__); + saferct->safety = block->safety; + BLI_addhead(&block->saferct, saferct); + } + + if (block->flag & UI_BLOCK_RADIAL) { + const int win_width = UI_SCREEN_MARGIN; + int winx, winy; + + int x_offset = 0, y_offset = 0; + + winx = WM_window_pixels_x(window); + winy = WM_window_pixels_y(window); + + copy_v2_v2(block->pie_data.pie_center_init, block->pie_data.pie_center_spawned); + + /* only try translation if area is large enough */ + if (BLI_rctf_size_x(&block->rect) < winx - (2.0f * win_width)) { + if (block->rect.xmin < win_width) { + x_offset += win_width - block->rect.xmin; + } + if (block->rect.xmax > winx - win_width) { + x_offset += winx - win_width - block->rect.xmax; + } + } + + if (BLI_rctf_size_y(&block->rect) < winy - (2.0f * win_width)) { + if (block->rect.ymin < win_width) { + y_offset += win_width - block->rect.ymin; + } + if (block->rect.ymax > winy - win_width) { + y_offset += winy - win_width - block->rect.ymax; + } + } + /* if we are offsetting set up initial data for timeout functionality */ + + if ((x_offset != 0) || (y_offset != 0)) { + block->pie_data.pie_center_spawned[0] += x_offset; + block->pie_data.pie_center_spawned[1] += y_offset; + + UI_block_translate(block, x_offset, y_offset); + + if (U.pie_initial_timeout > 0) { + block->pie_data.flags |= UI_PIE_INITIAL_DIRECTION; + } + } + + region->winrct.xmin = 0; + region->winrct.xmax = winx; + region->winrct.ymin = 0; + region->winrct.ymax = winy; + + ui_block_calc_pie_segment(block, block->pie_data.pie_center_init); + + /* lastly set the buttons at the center of the pie menu, ready for animation */ + if (U.pie_animation_timeout > 0) { + LISTBASE_FOREACH (uiBut *, but_iter, &block->buttons) { + if (but_iter->pie_dir != UI_RADIAL_NONE) { + BLI_rctf_recenter(&but_iter->rect, UNPACK2(block->pie_data.pie_center_spawned)); + } + } + } + } + else { + /* Add an offset to draw the popover arrow. */ + if ((block->flag & UI_BLOCK_POPOVER) && ELEM(block->direction, UI_DIR_UP, UI_DIR_DOWN)) { + /* Keep sync with 'ui_draw_popover_back_impl'. */ + const float unit_size = U.widget_unit / block->aspect; + const float unit_half = unit_size * (block->direction == UI_DIR_DOWN ? 0.5 : -0.5); + + UI_block_translate(block, 0, -unit_half); + } + + /* clip block with window boundary */ + ui_popup_block_clip(window, block); + + /* Avoid menu moving down and losing cursor focus by keeping it at + * the same height. */ + if (handle->refresh && handle->prev_block_rect.ymax > block->rect.ymax) { + if (block->bounds_type != UI_BLOCK_BOUNDS_POPUP_CENTER) { + const float offset = handle->prev_block_rect.ymax - block->rect.ymax; + UI_block_translate(block, 0, offset); + block->rect.ymin = handle->prev_block_rect.ymin; + } + } + + handle->prev_block_rect = block->rect; + + /* the block and buttons were positioned in window space as in 2.4x, now + * these menu blocks are regions so we bring it back to region space. + * additionally we add some padding for the menu shadow or rounded menus */ + region->winrct.xmin = block->rect.xmin - margin; + region->winrct.xmax = block->rect.xmax + margin; + region->winrct.ymin = block->rect.ymin - margin; + region->winrct.ymax = block->rect.ymax + UI_POPUP_MENU_TOP; + + UI_block_translate(block, -region->winrct.xmin, -region->winrct.ymin); + + /* apply scroll offset */ + if (handle->scrolloffset != 0.0f) { + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->rect.ymin += handle->scrolloffset; + bt->rect.ymax += handle->scrolloffset; + } + } + } + + if (block_old) { + block->oldblock = block_old; + UI_block_update_from_old(C, block); + UI_blocklist_free_inactive(C, region); + } + + /* checks which buttons are visible, sets flags to prevent draw (do after region init) */ + ui_popup_block_scrolltest(block); + + /* adds subwindow */ + ED_region_floating_init(region); + + /* get winmat now that we actually have the subwindow */ + wmGetProjectionMatrix(block->winmat, ®ion->winrct); + + /* notify change and redraw */ + ED_region_tag_redraw(region); + + ED_region_update_rect(region); + +#ifdef DEBUG + window->eventstate = event_back; + window->event_last_handled = event_last_back; +#endif + + return block; +} + +uiPopupBlockHandle *ui_popup_block_create(bContext *C, + ARegion *butregion, + uiBut *but, + uiBlockCreateFunc create_func, + uiBlockHandleCreateFunc handle_create_func, + void *arg, + uiFreeArgFunc arg_free) +{ + wmWindow *window = CTX_wm_window(C); + uiBut *activebut = UI_context_active_but_get(C); + static ARegionType type; + ARegion *region; + uiBlock *block; + + /* disable tooltips from buttons below */ + if (activebut) { + UI_but_tooltip_timer_remove(C, activebut); + } + /* standard cursor by default */ + WM_cursor_set(window, WM_CURSOR_DEFAULT); + + /* create handle */ + uiPopupBlockHandle *handle = MEM_cnew(__func__); + + /* store context for operator */ + handle->ctx_area = CTX_wm_area(C); + handle->ctx_region = CTX_wm_region(C); + + /* store vars to refresh popup (RGN_REFRESH_UI) */ + handle->popup_create_vars.create_func = create_func; + handle->popup_create_vars.handle_create_func = handle_create_func; + handle->popup_create_vars.arg = arg; + handle->popup_create_vars.arg_free = arg_free; + handle->popup_create_vars.but = but; + handle->popup_create_vars.butregion = but ? butregion : nullptr; + copy_v2_v2_int(handle->popup_create_vars.event_xy, window->eventstate->xy); + + /* don't allow by default, only if popup type explicitly supports it */ + handle->can_refresh = false; + + /* create area region */ + region = ui_region_temp_add(CTX_wm_screen(C)); + handle->region = region; + + memset(&type, 0, sizeof(ARegionType)); + type.draw = ui_block_region_draw; + type.layout = ui_block_region_refresh; + type.regionid = RGN_TYPE_TEMPORARY; + region->type = &type; + + UI_region_handlers_add(®ion->handlers); + + block = ui_popup_block_refresh(C, handle, butregion, but); + handle = block->handle; + + /* keep centered on window resizing */ + if (block->bounds_type == UI_BLOCK_BOUNDS_POPUP_CENTER) { + type.listener = ui_block_region_popup_window_listener; + } + + return handle; +} + +void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle) +{ + /* If this popup is created from a popover which does NOT have keep-open flag set, + * then close the popover too. We could extend this to other popup types too. */ + ARegion *region = handle->popup_create_vars.butregion; + if (region != nullptr) { + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (block->handle && (block->flag & UI_BLOCK_POPOVER) && + (block->flag & UI_BLOCK_KEEP_OPEN) == 0) { + uiPopupBlockHandle *menu = block->handle; + menu->menuretval = UI_RETURN_OK; + } + } + } + + if (handle->popup_create_vars.arg_free) { + handle->popup_create_vars.arg_free(handle->popup_create_vars.arg); + } + + ui_popup_block_remove(C, handle); + + MEM_freeN(handle); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c deleted file mode 100644 index ea6d293e52f..00000000000 --- a/source/blender/editors/interface/interface_regions.c +++ /dev/null @@ -1,50 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2008 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - * - * General Interface Region Code - * - * \note Most logic is now in 'interface_region_*.c' - */ - -#include "BLI_listbase.h" -#include "BLI_utildefines.h" -#include "MEM_guardedalloc.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "WM_api.h" -#include "wm_draw.h" - -#include "ED_screen.h" - -#include "interface_regions_intern.h" - -ARegion *ui_region_temp_add(bScreen *screen) -{ - ARegion *region = MEM_callocN(sizeof(ARegion), __func__); - BLI_addtail(&screen->regionbase, region); - - region->regiontype = RGN_TYPE_TEMPORARY; - region->alignment = RGN_ALIGN_FLOAT; - - return region; -} - -void ui_region_temp_remove(bContext *C, bScreen *screen, ARegion *region) -{ - wmWindow *win = CTX_wm_window(C); - - BLI_assert(region->regiontype == RGN_TYPE_TEMPORARY); - BLI_assert(BLI_findindex(&screen->regionbase, region) != -1); - if (win) { - wm_draw_region_clear(win, region); - } - - ED_region_exit(C, region); - BKE_area_region_free(NULL, region); /* NULL: no spacetype */ - BLI_freelinkN(&screen->regionbase, region); -} diff --git a/source/blender/editors/interface/interface_regions.cc b/source/blender/editors/interface/interface_regions.cc new file mode 100644 index 00000000000..1a2c1f7919c --- /dev/null +++ b/source/blender/editors/interface/interface_regions.cc @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2008 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + * + * General Interface Region Code + * + * \note Most logic is now in 'interface_region_*.c' + */ + +#include "BLI_listbase.h" +#include "BLI_utildefines.h" +#include "MEM_guardedalloc.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "WM_api.h" +#include "wm_draw.h" + +#include "ED_screen.h" + +#include "interface_regions_intern.h" + +ARegion *ui_region_temp_add(bScreen *screen) +{ + ARegion *region = MEM_cnew(__func__); + BLI_addtail(&screen->regionbase, region); + + region->regiontype = RGN_TYPE_TEMPORARY; + region->alignment = RGN_ALIGN_FLOAT; + + return region; +} + +void ui_region_temp_remove(bContext *C, bScreen *screen, ARegion *region) +{ + wmWindow *win = CTX_wm_window(C); + + BLI_assert(region->regiontype == RGN_TYPE_TEMPORARY); + BLI_assert(BLI_findindex(&screen->regionbase, region) != -1); + if (win) { + wm_draw_region_clear(win, region); + } + + ED_region_exit(C, region); + BKE_area_region_free(nullptr, region); /* nullptr: no spacetype */ + BLI_freelinkN(&screen->regionbase, region); +} diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c deleted file mode 100644 index 76023f3033f..00000000000 --- a/source/blender/editors/interface/interface_style.c +++ /dev/null @@ -1,512 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_listbase.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BKE_global.h" - -#include "BLF_api.h" - -#include "BLT_translation.h" - -#include "UI_interface.h" - -#include "ED_datafiles.h" - -#include "interface_intern.h" - -#ifdef WIN32 -# include "BLI_math_base.h" /* M_PI */ -#endif - -/* style + theme + layout-engine = UI */ - -/** - * This is a complete set of layout rules, the 'state' of the Layout - * Engine. Multiple styles are possible, defined via C or Python. Styles - * get a name, and will typically get activated per region type, like - * "Header", or "Listview" or "Toolbar". Properties of Style definitions - * are: - * - * - default column properties, internal spacing, aligning, min/max width - * - button alignment rules (for groups) - * - label placement rules - * - internal labeling or external labeling default - * - default minimum widths for buttons/labels (in amount of characters) - * - font types, styles and relative sizes for Panel titles, labels, etc. - */ - -/* ********************************************** */ - -static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id) -{ - uiStyle *style = MEM_callocN(sizeof(uiStyle), "new style"); - - BLI_addtail(styles, style); - BLI_strncpy(style->name, name, MAX_STYLE_NAME); - - style->panelzoom = 1.0; /* unused */ - - style->paneltitle.uifont_id = uifont_id; - style->paneltitle.points = UI_DEFAULT_TITLE_POINTS; - style->paneltitle.shadow = 3; - style->paneltitle.shadx = 0; - style->paneltitle.shady = -1; - style->paneltitle.shadowalpha = 0.5f; - style->paneltitle.shadowcolor = 0.0f; - - style->grouplabel.uifont_id = uifont_id; - style->grouplabel.points = UI_DEFAULT_TITLE_POINTS; - style->grouplabel.shadow = 3; - style->grouplabel.shadx = 0; - style->grouplabel.shady = -1; - style->grouplabel.shadowalpha = 0.5f; - style->grouplabel.shadowcolor = 0.0f; - - style->widgetlabel.uifont_id = uifont_id; - style->widgetlabel.points = UI_DEFAULT_TEXT_POINTS; - style->widgetlabel.shadow = 3; - style->widgetlabel.shadx = 0; - style->widgetlabel.shady = -1; - style->widgetlabel.shadowalpha = 0.5f; - style->widgetlabel.shadowcolor = 0.0f; - - style->widget.uifont_id = uifont_id; - style->widget.points = UI_DEFAULT_TEXT_POINTS; - style->widget.shadow = 1; - style->widget.shady = -1; - style->widget.shadowalpha = 0.5f; - style->widget.shadowcolor = 0.0f; - - style->columnspace = 8; - style->templatespace = 5; - style->boxspace = 5; - style->buttonspacex = 8; - style->buttonspacey = 2; - style->panelspace = 8; - style->panelouter = 4; - - return style; -} - -static uiFont *uifont_to_blfont(int id) -{ - uiFont *font = U.uifonts.first; - - for (; font; font = font->next) { - if (font->uifont_id == id) { - return font; - } - } - return U.uifonts.first; -} - -/* *************** draw ************************ */ - -void UI_fontstyle_draw_ex(const uiFontStyle *fs, - const rcti *rect, - const char *str, - const size_t str_len, - const uchar col[4], - const struct uiFontStyleDraw_Params *fs_params, - int *r_xofs, - int *r_yofs, - struct ResultBLF *r_info) -{ - int xofs = 0, yofs; - int font_flag = BLF_CLIPPING; - - UI_fontstyle_set(fs); - - /* set the flag */ - if (fs->shadow) { - font_flag |= BLF_SHADOW; - const float shadow_color[4] = { - fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; - BLF_shadow(fs->uifont_id, fs->shadow, shadow_color); - BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); - } - if (fs_params->word_wrap == 1) { - font_flag |= BLF_WORD_WRAP; - } - if (fs->bold) { - font_flag |= BLF_BOLD; - } - if (fs->italic) { - font_flag |= BLF_ITALIC; - } - - BLF_enable(fs->uifont_id, font_flag); - - if (fs_params->word_wrap == 1) { - /* Draw from bound-box top. */ - yofs = BLI_rcti_size_y(rect) - BLF_height_max(fs->uifont_id); - } - else { - /* Draw from bound-box center. */ - const float height = BLF_ascender(fs->uifont_id) + BLF_descender(fs->uifont_id); - yofs = ceil(0.5f * (BLI_rcti_size_y(rect) - height)); - } - - if (fs_params->align == UI_STYLE_TEXT_CENTER) { - xofs = floor(0.5f * (BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, str, str_len))); - } - else if (fs_params->align == UI_STYLE_TEXT_RIGHT) { - xofs = BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, str, str_len); - } - - yofs = MAX2(0, yofs); - xofs = MAX2(0, xofs); - - BLF_clipping(fs->uifont_id, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - BLF_position(fs->uifont_id, rect->xmin + xofs, rect->ymin + yofs, 0.0f); - BLF_color4ubv(fs->uifont_id, col); - - BLF_draw_ex(fs->uifont_id, str, str_len, r_info); - - BLF_disable(fs->uifont_id, font_flag); - - if (r_xofs) { - *r_xofs = xofs; - } - if (r_yofs) { - *r_yofs = yofs; - } -} - -void UI_fontstyle_draw(const uiFontStyle *fs, - const rcti *rect, - const char *str, - const size_t str_len, - const uchar col[4], - const struct uiFontStyleDraw_Params *fs_params) -{ - UI_fontstyle_draw_ex(fs, rect, str, str_len, col, fs_params, NULL, NULL, NULL); -} - -void UI_fontstyle_draw_rotated(const uiFontStyle *fs, - const rcti *rect, - const char *str, - const uchar col[4]) -{ - float height; - int xofs, yofs; - float angle; - rcti txtrect; - - UI_fontstyle_set(fs); - - height = BLF_ascender(fs->uifont_id) + BLF_descender(fs->uifont_id); - /* becomes x-offset when rotated */ - xofs = ceil(0.5f * (BLI_rcti_size_y(rect) - height)); - - /* ignore UI_STYLE, always aligned to top */ - - /* Rotate counter-clockwise for now (assumes left-to-right language). */ - xofs += height; - yofs = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX) + 5; - angle = M_PI_2; - - /* translate rect to vertical */ - txtrect.xmin = rect->xmin - BLI_rcti_size_y(rect); - txtrect.ymin = rect->ymin - BLI_rcti_size_x(rect); - txtrect.xmax = rect->xmin; - txtrect.ymax = rect->ymin; - - /* clip is very strict, so we give it some space */ - /* clipping is done without rotation, so make rect big enough to contain both positions */ - BLF_clipping(fs->uifont_id, - txtrect.xmin - 1, - txtrect.ymin - yofs - xofs - 4, - rect->xmax + 1, - rect->ymax + 4); - BLF_enable(fs->uifont_id, BLF_CLIPPING); - BLF_position(fs->uifont_id, txtrect.xmin + xofs, txtrect.ymax - yofs, 0.0f); - - BLF_enable(fs->uifont_id, BLF_ROTATION); - BLF_rotation(fs->uifont_id, angle); - BLF_color4ubv(fs->uifont_id, col); - - if (fs->shadow) { - BLF_enable(fs->uifont_id, BLF_SHADOW); - const float shadow_color[4] = { - fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; - BLF_shadow(fs->uifont_id, fs->shadow, shadow_color); - BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); - } - - BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); - BLF_disable(fs->uifont_id, BLF_ROTATION); - BLF_disable(fs->uifont_id, BLF_CLIPPING); - if (fs->shadow) { - BLF_disable(fs->uifont_id, BLF_SHADOW); - } -} - -void UI_fontstyle_draw_simple( - const uiFontStyle *fs, float x, float y, const char *str, const uchar col[4]) -{ - UI_fontstyle_set(fs); - BLF_position(fs->uifont_id, x, y, 0.0f); - BLF_color4ubv(fs->uifont_id, col); - BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); -} - -void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, - float x, - float y, - const char *str, - const float col_fg[4], - const float col_bg[4]) -{ - UI_fontstyle_set(fs); - - { - const float width = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); - const float height = BLF_height_max(fs->uifont_id); - const float decent = BLF_descender(fs->uifont_id); - const float margin = height / 4.0f; - - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = x - margin, - .xmax = x + width + margin, - .ymin = (y + decent) - margin, - .ymax = (y + decent) + height + margin, - }, - true, - margin, - col_bg); - } - - BLF_position(fs->uifont_id, x, y, 0.0f); - BLF_color4fv(fs->uifont_id, col_fg); - BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); -} - -/* ************** helpers ************************ */ - -const uiStyle *UI_style_get(void) -{ -#if 0 - uiStyle *style = NULL; - /* offset is two struct uiStyle pointers */ - style = BLI_findstring(&U.uistyles, "Unifont Style", sizeof(style) * 2); - return (style != NULL) ? style : U.uistyles.first; -#else - return U.uistyles.first; -#endif -} - -const uiStyle *UI_style_get_dpi(void) -{ - const uiStyle *style = UI_style_get(); - static uiStyle _style; - - _style = *style; - - _style.paneltitle.shadx = (short)(UI_DPI_FAC * _style.paneltitle.shadx); - _style.paneltitle.shady = (short)(UI_DPI_FAC * _style.paneltitle.shady); - _style.grouplabel.shadx = (short)(UI_DPI_FAC * _style.grouplabel.shadx); - _style.grouplabel.shady = (short)(UI_DPI_FAC * _style.grouplabel.shady); - _style.widgetlabel.shadx = (short)(UI_DPI_FAC * _style.widgetlabel.shadx); - _style.widgetlabel.shady = (short)(UI_DPI_FAC * _style.widgetlabel.shady); - - _style.columnspace = (short)(UI_DPI_FAC * _style.columnspace); - _style.templatespace = (short)(UI_DPI_FAC * _style.templatespace); - _style.boxspace = (short)(UI_DPI_FAC * _style.boxspace); - _style.buttonspacex = (short)(UI_DPI_FAC * _style.buttonspacex); - _style.buttonspacey = (short)(UI_DPI_FAC * _style.buttonspacey); - _style.panelspace = (short)(UI_DPI_FAC * _style.panelspace); - _style.panelouter = (short)(UI_DPI_FAC * _style.panelouter); - - return &_style; -} - -int UI_fontstyle_string_width(const uiFontStyle *fs, const char *str) -{ - UI_fontstyle_set(fs); - return (int)BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); -} - -int UI_fontstyle_string_width_with_block_aspect(const uiFontStyle *fs, - const char *str, - const float aspect) -{ - uiFontStyle fs_buf; - if (aspect != 1.0f) { - fs_buf = *fs; - ui_fontscale(&fs_buf.points, aspect); - fs = &fs_buf; - } - - int width = UI_fontstyle_string_width(fs, str); - - if (aspect != 1.0f) { - /* While in most cases rounding up isn't important, it can make a difference - * with small fonts (3px or less), zooming out in the node-editor for e.g. */ - width = (int)ceilf(width * aspect); - } - return width; -} - -int UI_fontstyle_height_max(const uiFontStyle *fs) -{ - UI_fontstyle_set(fs); - return BLF_height_max(fs->uifont_id); -} - -/* ************** init exit ************************ */ - -void uiStyleInit(void) -{ - const uiStyle *style = U.uistyles.first; - - /* recover from uninitialized dpi */ - if (U.dpi == 0) { - U.dpi = 72; - } - CLAMP(U.dpi, 48, 144); - - LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { - BLF_unload_id(font->blf_id); - } - - if (blf_mono_font != -1) { - BLF_unload_id(blf_mono_font); - blf_mono_font = -1; - } - - if (blf_mono_font_render != -1) { - BLF_unload_id(blf_mono_font_render); - blf_mono_font_render = -1; - } - - uiFont *font_first = U.uifonts.first; - - /* default builtin */ - if (font_first == NULL) { - font_first = MEM_callocN(sizeof(uiFont), "ui font"); - BLI_addtail(&U.uifonts, font_first); - } - - if (U.font_path_ui[0]) { - BLI_strncpy(font_first->filepath, U.font_path_ui, sizeof(font_first->filepath)); - font_first->uifont_id = UIFONT_CUSTOM1; - } - else { - BLI_strncpy(font_first->filepath, "default", sizeof(font_first->filepath)); - font_first->uifont_id = UIFONT_DEFAULT; - } - - LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { - const bool unique = false; - - if (font->uifont_id == UIFONT_DEFAULT) { - font->blf_id = BLF_load_default(unique); - } - else { - font->blf_id = BLF_load(font->filepath); - if (font->blf_id == -1) { - font->blf_id = BLF_load_default(unique); - } - } - - BLF_default_set(font->blf_id); - - if (font->blf_id == -1) { - if (G.debug & G_DEBUG) { - printf("%s: error, no fonts available\n", __func__); - } - } - } - - if (style == NULL) { - style = ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); - } - - BLF_cache_flush_set_fn(UI_widgetbase_draw_cache_flush); - - BLF_default_size(style->widgetlabel.points); - - /* XXX, this should be moved into a style, - * but for now best only load the monospaced font once. */ - BLI_assert(blf_mono_font == -1); - /* Use unique font loading to avoid thread safety issues with mono font - * used for render metadata stamp in threads. */ - if (U.font_path_ui_mono[0]) { - blf_mono_font = BLF_load_unique(U.font_path_ui_mono); - } - if (blf_mono_font == -1) { - const bool unique = true; - blf_mono_font = BLF_load_mono_default(unique); - } - - /* Set default flags based on UI preferences (not render fonts) */ - { - const int flag_disable = (BLF_MONOCHROME | BLF_HINTING_NONE | BLF_HINTING_SLIGHT | - BLF_HINTING_FULL); - int flag_enable = 0; - - if (U.text_render & USER_TEXT_HINTING_NONE) { - flag_enable |= BLF_HINTING_NONE; - } - else if (U.text_render & USER_TEXT_HINTING_SLIGHT) { - flag_enable |= BLF_HINTING_SLIGHT; - } - else if (U.text_render & USER_TEXT_HINTING_FULL) { - flag_enable |= BLF_HINTING_FULL; - } - - if (U.text_render & USER_TEXT_DISABLE_AA) { - flag_enable |= BLF_MONOCHROME; - } - - LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { - if (font->blf_id != -1) { - BLF_disable(font->blf_id, flag_disable); - BLF_enable(font->blf_id, flag_enable); - } - } - if (blf_mono_font != -1) { - BLF_disable(blf_mono_font, flag_disable); - BLF_enable(blf_mono_font, flag_enable); - } - } - - /** - * Second for rendering else we get threading problems, - * - * \note This isn't good that the render font depends on the preferences, - * keep for now though, since without this there is no way to display many unicode chars. - */ - if (blf_mono_font_render == -1) { - const bool unique = true; - blf_mono_font_render = BLF_load_mono_default(unique); - } -} - -void UI_fontstyle_set(const uiFontStyle *fs) -{ - uiFont *font = uifont_to_blfont(fs->uifont_id); - - BLF_size(font->blf_id, fs->points * U.pixelsize, U.dpi); -} diff --git a/source/blender/editors/interface/interface_style.cc b/source/blender/editors/interface/interface_style.cc new file mode 100644 index 00000000000..b4e97f8a396 --- /dev/null +++ b/source/blender/editors/interface/interface_style.cc @@ -0,0 +1,508 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_listbase.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BKE_global.h" + +#include "BLF_api.h" + +#include "BLT_translation.h" + +#include "UI_interface.h" + +#include "ED_datafiles.h" + +#include "interface_intern.h" + +#ifdef WIN32 +# include "BLI_math_base.h" /* M_PI */ +#endif + +/* style + theme + layout-engine = UI */ + +/** + * This is a complete set of layout rules, the 'state' of the Layout + * Engine. Multiple styles are possible, defined via C or Python. Styles + * get a name, and will typically get activated per region type, like + * "Header", or "Listview" or "Toolbar". Properties of Style definitions + * are: + * + * - default column properties, internal spacing, aligning, min/max width + * - button alignment rules (for groups) + * - label placement rules + * - internal labeling or external labeling default + * - default minimum widths for buttons/labels (in amount of characters) + * - font types, styles and relative sizes for Panel titles, labels, etc. + */ + +/* ********************************************** */ + +static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id) +{ + uiStyle *style = MEM_cnew(__func__); + + BLI_addtail(styles, style); + BLI_strncpy(style->name, name, MAX_STYLE_NAME); + + style->panelzoom = 1.0; /* unused */ + + style->paneltitle.uifont_id = uifont_id; + style->paneltitle.points = UI_DEFAULT_TITLE_POINTS; + style->paneltitle.shadow = 3; + style->paneltitle.shadx = 0; + style->paneltitle.shady = -1; + style->paneltitle.shadowalpha = 0.5f; + style->paneltitle.shadowcolor = 0.0f; + + style->grouplabel.uifont_id = uifont_id; + style->grouplabel.points = UI_DEFAULT_TITLE_POINTS; + style->grouplabel.shadow = 3; + style->grouplabel.shadx = 0; + style->grouplabel.shady = -1; + style->grouplabel.shadowalpha = 0.5f; + style->grouplabel.shadowcolor = 0.0f; + + style->widgetlabel.uifont_id = uifont_id; + style->widgetlabel.points = UI_DEFAULT_TEXT_POINTS; + style->widgetlabel.shadow = 3; + style->widgetlabel.shadx = 0; + style->widgetlabel.shady = -1; + style->widgetlabel.shadowalpha = 0.5f; + style->widgetlabel.shadowcolor = 0.0f; + + style->widget.uifont_id = uifont_id; + style->widget.points = UI_DEFAULT_TEXT_POINTS; + style->widget.shadow = 1; + style->widget.shady = -1; + style->widget.shadowalpha = 0.5f; + style->widget.shadowcolor = 0.0f; + + style->columnspace = 8; + style->templatespace = 5; + style->boxspace = 5; + style->buttonspacex = 8; + style->buttonspacey = 2; + style->panelspace = 8; + style->panelouter = 4; + + return style; +} + +static uiFont *uifont_to_blfont(int id) +{ + uiFont *font = static_cast(U.uifonts.first); + + for (; font; font = font->next) { + if (font->uifont_id == id) { + return font; + } + } + return static_cast(U.uifonts.first); +} + +/* *************** draw ************************ */ + +void UI_fontstyle_draw_ex(const uiFontStyle *fs, + const rcti *rect, + const char *str, + const size_t str_len, + const uchar col[4], + const struct uiFontStyleDraw_Params *fs_params, + int *r_xofs, + int *r_yofs, + struct ResultBLF *r_info) +{ + int xofs = 0, yofs; + int font_flag = BLF_CLIPPING; + + UI_fontstyle_set(fs); + + /* set the flag */ + if (fs->shadow) { + font_flag |= BLF_SHADOW; + const float shadow_color[4] = { + fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; + BLF_shadow(fs->uifont_id, fs->shadow, shadow_color); + BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); + } + if (fs_params->word_wrap == 1) { + font_flag |= BLF_WORD_WRAP; + } + if (fs->bold) { + font_flag |= BLF_BOLD; + } + if (fs->italic) { + font_flag |= BLF_ITALIC; + } + + BLF_enable(fs->uifont_id, font_flag); + + if (fs_params->word_wrap == 1) { + /* Draw from bound-box top. */ + yofs = BLI_rcti_size_y(rect) - BLF_height_max(fs->uifont_id); + } + else { + /* Draw from bound-box center. */ + const float height = BLF_ascender(fs->uifont_id) + BLF_descender(fs->uifont_id); + yofs = ceil(0.5f * (BLI_rcti_size_y(rect) - height)); + } + + if (fs_params->align == UI_STYLE_TEXT_CENTER) { + xofs = floor(0.5f * (BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, str, str_len))); + } + else if (fs_params->align == UI_STYLE_TEXT_RIGHT) { + xofs = BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, str, str_len); + } + + yofs = MAX2(0, yofs); + xofs = MAX2(0, xofs); + + BLF_clipping(fs->uifont_id, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + BLF_position(fs->uifont_id, rect->xmin + xofs, rect->ymin + yofs, 0.0f); + BLF_color4ubv(fs->uifont_id, col); + + BLF_draw_ex(fs->uifont_id, str, str_len, r_info); + + BLF_disable(fs->uifont_id, font_flag); + + if (r_xofs) { + *r_xofs = xofs; + } + if (r_yofs) { + *r_yofs = yofs; + } +} + +void UI_fontstyle_draw(const uiFontStyle *fs, + const rcti *rect, + const char *str, + const size_t str_len, + const uchar col[4], + const struct uiFontStyleDraw_Params *fs_params) +{ + UI_fontstyle_draw_ex(fs, rect, str, str_len, col, fs_params, nullptr, nullptr, nullptr); +} + +void UI_fontstyle_draw_rotated(const uiFontStyle *fs, + const rcti *rect, + const char *str, + const uchar col[4]) +{ + float height; + int xofs, yofs; + float angle; + rcti txtrect; + + UI_fontstyle_set(fs); + + height = BLF_ascender(fs->uifont_id) + BLF_descender(fs->uifont_id); + /* becomes x-offset when rotated */ + xofs = ceil(0.5f * (BLI_rcti_size_y(rect) - height)); + + /* ignore UI_STYLE, always aligned to top */ + + /* Rotate counter-clockwise for now (assumes left-to-right language). */ + xofs += height; + yofs = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX) + 5; + angle = M_PI_2; + + /* translate rect to vertical */ + txtrect.xmin = rect->xmin - BLI_rcti_size_y(rect); + txtrect.ymin = rect->ymin - BLI_rcti_size_x(rect); + txtrect.xmax = rect->xmin; + txtrect.ymax = rect->ymin; + + /* clip is very strict, so we give it some space */ + /* clipping is done without rotation, so make rect big enough to contain both positions */ + BLF_clipping(fs->uifont_id, + txtrect.xmin - 1, + txtrect.ymin - yofs - xofs - 4, + rect->xmax + 1, + rect->ymax + 4); + BLF_enable(fs->uifont_id, BLF_CLIPPING); + BLF_position(fs->uifont_id, txtrect.xmin + xofs, txtrect.ymax - yofs, 0.0f); + + BLF_enable(fs->uifont_id, BLF_ROTATION); + BLF_rotation(fs->uifont_id, angle); + BLF_color4ubv(fs->uifont_id, col); + + if (fs->shadow) { + BLF_enable(fs->uifont_id, BLF_SHADOW); + const float shadow_color[4] = { + fs->shadowcolor, fs->shadowcolor, fs->shadowcolor, fs->shadowalpha}; + BLF_shadow(fs->uifont_id, fs->shadow, shadow_color); + BLF_shadow_offset(fs->uifont_id, fs->shadx, fs->shady); + } + + BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); + BLF_disable(fs->uifont_id, BLF_ROTATION); + BLF_disable(fs->uifont_id, BLF_CLIPPING); + if (fs->shadow) { + BLF_disable(fs->uifont_id, BLF_SHADOW); + } +} + +void UI_fontstyle_draw_simple( + const uiFontStyle *fs, float x, float y, const char *str, const uchar col[4]) +{ + UI_fontstyle_set(fs); + BLF_position(fs->uifont_id, x, y, 0.0f); + BLF_color4ubv(fs->uifont_id, col); + BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); +} + +void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs, + float x, + float y, + const char *str, + const float col_fg[4], + const float col_bg[4]) +{ + UI_fontstyle_set(fs); + + { + const float width = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); + const float height = BLF_height_max(fs->uifont_id); + const float decent = BLF_descender(fs->uifont_id); + const float margin = height / 4.0f; + + rctf rect; + rect.xmin = x - margin; + rect.xmax = x + width + margin; + rect.ymin = (y + decent) - margin; + rect.ymax = (y + decent) + height + margin; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_4fv(&rect, true, margin, col_bg); + } + + BLF_position(fs->uifont_id, x, y, 0.0f); + BLF_color4fv(fs->uifont_id, col_fg); + BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); +} + +/* ************** helpers ************************ */ + +const uiStyle *UI_style_get(void) +{ +#if 0 + uiStyle *style = nullptr; + /* offset is two struct uiStyle pointers */ + style = BLI_findstring(&U.uistyles, "Unifont Style", sizeof(style) * 2); + return (style != nullptr) ? style : U.uistyles.first; +#else + return static_cast(U.uistyles.first); +#endif +} + +const uiStyle *UI_style_get_dpi(void) +{ + const uiStyle *style = UI_style_get(); + static uiStyle _style; + + _style = *style; + + _style.paneltitle.shadx = (short)(UI_DPI_FAC * _style.paneltitle.shadx); + _style.paneltitle.shady = (short)(UI_DPI_FAC * _style.paneltitle.shady); + _style.grouplabel.shadx = (short)(UI_DPI_FAC * _style.grouplabel.shadx); + _style.grouplabel.shady = (short)(UI_DPI_FAC * _style.grouplabel.shady); + _style.widgetlabel.shadx = (short)(UI_DPI_FAC * _style.widgetlabel.shadx); + _style.widgetlabel.shady = (short)(UI_DPI_FAC * _style.widgetlabel.shady); + + _style.columnspace = (short)(UI_DPI_FAC * _style.columnspace); + _style.templatespace = (short)(UI_DPI_FAC * _style.templatespace); + _style.boxspace = (short)(UI_DPI_FAC * _style.boxspace); + _style.buttonspacex = (short)(UI_DPI_FAC * _style.buttonspacex); + _style.buttonspacey = (short)(UI_DPI_FAC * _style.buttonspacey); + _style.panelspace = (short)(UI_DPI_FAC * _style.panelspace); + _style.panelouter = (short)(UI_DPI_FAC * _style.panelouter); + + return &_style; +} + +int UI_fontstyle_string_width(const uiFontStyle *fs, const char *str) +{ + UI_fontstyle_set(fs); + return (int)BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); +} + +int UI_fontstyle_string_width_with_block_aspect(const uiFontStyle *fs, + const char *str, + const float aspect) +{ + uiFontStyle fs_buf; + if (aspect != 1.0f) { + fs_buf = *fs; + ui_fontscale(&fs_buf.points, aspect); + fs = &fs_buf; + } + + int width = UI_fontstyle_string_width(fs, str); + + if (aspect != 1.0f) { + /* While in most cases rounding up isn't important, it can make a difference + * with small fonts (3px or less), zooming out in the node-editor for e.g. */ + width = (int)ceilf(width * aspect); + } + return width; +} + +int UI_fontstyle_height_max(const uiFontStyle *fs) +{ + UI_fontstyle_set(fs); + return BLF_height_max(fs->uifont_id); +} + +/* ************** init exit ************************ */ + +void uiStyleInit(void) +{ + const uiStyle *style = static_cast(U.uistyles.first); + + /* recover from uninitialized dpi */ + if (U.dpi == 0) { + U.dpi = 72; + } + CLAMP(U.dpi, 48, 144); + + LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { + BLF_unload_id(font->blf_id); + } + + if (blf_mono_font != -1) { + BLF_unload_id(blf_mono_font); + blf_mono_font = -1; + } + + if (blf_mono_font_render != -1) { + BLF_unload_id(blf_mono_font_render); + blf_mono_font_render = -1; + } + + uiFont *font_first = static_cast(U.uifonts.first); + + /* default builtin */ + if (font_first == nullptr) { + font_first = MEM_cnew(__func__); + BLI_addtail(&U.uifonts, font_first); + } + + if (U.font_path_ui[0]) { + BLI_strncpy(font_first->filepath, U.font_path_ui, sizeof(font_first->filepath)); + font_first->uifont_id = UIFONT_CUSTOM1; + } + else { + BLI_strncpy(font_first->filepath, "default", sizeof(font_first->filepath)); + font_first->uifont_id = UIFONT_DEFAULT; + } + + LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { + const bool unique = false; + + if (font->uifont_id == UIFONT_DEFAULT) { + font->blf_id = BLF_load_default(unique); + } + else { + font->blf_id = BLF_load(font->filepath); + if (font->blf_id == -1) { + font->blf_id = BLF_load_default(unique); + } + } + + BLF_default_set(font->blf_id); + + if (font->blf_id == -1) { + if (G.debug & G_DEBUG) { + printf("%s: error, no fonts available\n", __func__); + } + } + } + + if (style == nullptr) { + style = ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); + } + + BLF_cache_flush_set_fn(UI_widgetbase_draw_cache_flush); + + BLF_default_size(style->widgetlabel.points); + + /* XXX, this should be moved into a style, + * but for now best only load the monospaced font once. */ + BLI_assert(blf_mono_font == -1); + /* Use unique font loading to avoid thread safety issues with mono font + * used for render metadata stamp in threads. */ + if (U.font_path_ui_mono[0]) { + blf_mono_font = BLF_load_unique(U.font_path_ui_mono); + } + if (blf_mono_font == -1) { + const bool unique = true; + blf_mono_font = BLF_load_mono_default(unique); + } + + /* Set default flags based on UI preferences (not render fonts) */ + { + const int flag_disable = (BLF_MONOCHROME | BLF_HINTING_NONE | BLF_HINTING_SLIGHT | + BLF_HINTING_FULL); + int flag_enable = 0; + + if (U.text_render & USER_TEXT_HINTING_NONE) { + flag_enable |= BLF_HINTING_NONE; + } + else if (U.text_render & USER_TEXT_HINTING_SLIGHT) { + flag_enable |= BLF_HINTING_SLIGHT; + } + else if (U.text_render & USER_TEXT_HINTING_FULL) { + flag_enable |= BLF_HINTING_FULL; + } + + if (U.text_render & USER_TEXT_DISABLE_AA) { + flag_enable |= BLF_MONOCHROME; + } + + LISTBASE_FOREACH (uiFont *, font, &U.uifonts) { + if (font->blf_id != -1) { + BLF_disable(font->blf_id, flag_disable); + BLF_enable(font->blf_id, flag_enable); + } + } + if (blf_mono_font != -1) { + BLF_disable(blf_mono_font, flag_disable); + BLF_enable(blf_mono_font, flag_enable); + } + } + + /** + * Second for rendering else we get threading problems, + * + * \note This isn't good that the render font depends on the preferences, + * keep for now though, since without this there is no way to display many unicode chars. + */ + if (blf_mono_font_render == -1) { + const bool unique = true; + blf_mono_font_render = BLF_load_mono_default(unique); + } +} + +void UI_fontstyle_set(const uiFontStyle *fs) +{ + uiFont *font = uifont_to_blfont(fs->uifont_id); + + BLF_size(font->blf_id, fs->points * U.pixelsize, U.dpi); +} diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c deleted file mode 100644 index 728d42a9353..00000000000 --- a/source/blender/editors/interface/interface_utils.c +++ /dev/null @@ -1,1014 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2009 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include - -#include "DNA_object_types.h" -#include "DNA_screen_types.h" - -#include "ED_screen.h" - -#include "BLI_alloca.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 = NULL; - - switch (RNA_property_type(prop)) { - case PROP_BOOLEAN: { - if (RNA_property_array_check(prop) && index == -1) { - return NULL; - } - - 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, - NULL); - } - 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, - NULL); - } - else { - but = uiDefButR_prop(block, - UI_BTYPE_CHECKBOX, - 0, - name, - x, - y, - width, - height, - ptr, - prop, - index, - 0, - 0, - -1, - -1, - NULL); - } - 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, - NULL); - } - else { - return NULL; - } - } - 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, - NULL); - } - else { - but = uiDefButR_prop( - block, UI_BTYPE_NUM, 0, name, x, y, width, height, ptr, prop, index, 0, 0, 0, 0, NULL); - } - - 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, - NULL); - } - else if (icon) { - but = uiDefIconTextButR_prop(block, - UI_BTYPE_MENU, - 0, - icon, - NULL, - x, - y, - width, - height, - ptr, - prop, - index, - 0, - 0, - -1, - -1, - NULL); - } - else { - but = uiDefButR_prop(block, - UI_BTYPE_MENU, - 0, - name, - x, - y, - width, - height, - ptr, - prop, - index, - 0, - 0, - -1, - -1, - NULL); - } - 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, - NULL); - } - else if (icon) { - but = uiDefIconTextButR_prop(block, - UI_BTYPE_TEXT, - 0, - icon, - name, - x, - y, - width, - height, - ptr, - prop, - index, - 0, - 0, - -1, - -1, - NULL); - } - else { - but = uiDefButR_prop(block, - UI_BTYPE_TEXT, - 0, - name, - x, - y, - width, - height, - ptr, - prop, - index, - 0, - 0, - -1, - -1, - NULL); - } - - 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, - NULL); - ui_but_add_search(but, ptr, prop, NULL, NULL); - 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, NULL, 0, 0, 0, 0, NULL); - UI_but_flag_enable(but, UI_BUT_DISABLED); - break; - } - default: - but = NULL; - 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 = NULL; /* 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 *** */ - -typedef 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; -} CollItemSearch; - -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(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, 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 struct bContext *C, - void *arg, - const char *str, - uiSearchItems *items, - const bool is_first) -{ - uiRNACollectionSearch *data = arg; - const int flag = RNA_property_flag(data->target_prop); - ListBase *items_list = MEM_callocN(sizeof(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 ? NULL : 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, 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), NULL); - } - else { - const ID *id = itemptr.data; - BKE_id_full_name_ui_prefix_get( - name_buf, itemptr.data, 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), NULL); - } - - if (name) { - CollItemSearch *cis = MEM_callocN(sizeof(CollItemSearch), "CollectionItemSearch"); - 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 == NULL) { - 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(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 struct 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, ®ion_rect, &but->rect); - - rctf view_rect; - UI_view2d_region_to_view_rctf(v2d, ®ion_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 = ®ion->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 NULL 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_callocN(sizeof(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 != NULL) { - 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 != NULL); -} - -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_callocN(sizeof(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 = NULL; - LISTBASE_FOREACH (uiButStoreElem *, bs_elem, &bs_handle->items) { - *bs_elem->but_p = NULL; - } - } -} - -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 == NULL)) { - 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, NULL, 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 NULL 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; - } - } - } - } -} - -/** \} */ 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 +#include +#include +#include + +#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(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(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(arg); + const int flag = RNA_property_flag(data->target_prop); + ListBase *items_list = MEM_cnew("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(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(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(__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(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, ®ion_rect, &but->rect); + + rctf view_rect; + UI_view2d_region_to_view_rctf(v2d, ®ion_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 = ®ion->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(__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(__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; + } + } + } + } +} + +/** \} */ -- cgit v1.2.3