From 2038250c62fa3753fcc31129f8dd5f4fdf08e2e7 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 28 Apr 2021 23:32:55 -0500 Subject: WIP changes to compiling interface directory as C++ code --- source/blender/editors/interface/CMakeLists.txt | 42 +- source/blender/editors/interface/interface.c | 7137 ------------ source/blender/editors/interface/interface.cc | 7132 ++++++++++++ .../editors/interface/interface_button_group.c | 90 - .../editors/interface/interface_button_group.cc | 90 + .../editors/interface/interface_context_menu.c | 1289 --- .../editors/interface/interface_context_menu.cc | 1294 +++ source/blender/editors/interface/interface_draw.c | 2397 ---- source/blender/editors/interface/interface_draw.cc | 2382 ++++ .../blender/editors/interface/interface_handlers.c | 11154 ------------------ .../editors/interface/interface_handlers.cc | 11164 +++++++++++++++++++ .../editors/interface/interface_icons_event.c | 236 - .../editors/interface/interface_icons_event.cc | 236 + .../blender/editors/interface/interface_intern.h | 4 +- .../blender/editors/interface/interface_layout.c | 5961 ---------- .../blender/editors/interface/interface_layout.cc | 5967 ++++++++++ source/blender/editors/interface/interface_panel.c | 2684 ----- .../blender/editors/interface/interface_panel.cc | 2692 +++++ source/blender/editors/interface/interface_query.c | 704 -- .../blender/editors/interface/interface_query.cc | 702 ++ .../interface/interface_region_color_picker.c | 929 -- .../interface/interface_region_color_picker.cc | 930 ++ .../editors/interface/interface_region_hud.c | 402 - .../editors/interface/interface_region_hud.cc | 405 + .../editors/interface/interface_region_popup.c | 858 -- .../editors/interface/interface_region_popup.cc | 858 ++ .../editors/interface/interface_region_search.c | 1074 -- .../editors/interface/interface_region_search.cc | 1076 ++ .../blender/editors/interface/interface_regions.c | 66 - .../blender/editors/interface/interface_regions.cc | 66 + source/blender/editors/interface/interface_style.c | 577 - .../blender/editors/interface/interface_style.cc | 577 + .../interface/interface_template_search_menu.c | 1177 -- .../interface/interface_template_search_menu.cc | 1181 ++ .../interface/interface_template_search_operator.c | 143 - .../interface_template_search_operator.cc | 143 + .../editors/interface/interface_templates.c | 7352 ------------ .../editors/interface/interface_templates.cc | 7352 ++++++++++++ source/blender/editors/interface/interface_undo.c | 137 - source/blender/editors/interface/interface_undo.cc | 138 + source/blender/editors/interface/interface_utils.c | 944 -- .../blender/editors/interface/interface_utils.cc | 945 ++ source/blender/editors/interface/resources.c | 1513 --- source/blender/editors/interface/resources.cc | 1513 +++ 44 files changed, 46866 insertions(+), 46847 deletions(-) delete mode 100644 source/blender/editors/interface/interface.c create mode 100644 source/blender/editors/interface/interface.cc delete mode 100644 source/blender/editors/interface/interface_button_group.c create mode 100644 source/blender/editors/interface/interface_button_group.cc delete mode 100644 source/blender/editors/interface/interface_context_menu.c create mode 100644 source/blender/editors/interface/interface_context_menu.cc delete mode 100644 source/blender/editors/interface/interface_draw.c create mode 100644 source/blender/editors/interface/interface_draw.cc delete mode 100644 source/blender/editors/interface/interface_handlers.c create mode 100644 source/blender/editors/interface/interface_handlers.cc delete mode 100644 source/blender/editors/interface/interface_icons_event.c create mode 100644 source/blender/editors/interface/interface_icons_event.cc delete mode 100644 source/blender/editors/interface/interface_layout.c create mode 100644 source/blender/editors/interface/interface_layout.cc delete mode 100644 source/blender/editors/interface/interface_panel.c create mode 100644 source/blender/editors/interface/interface_panel.cc delete mode 100644 source/blender/editors/interface/interface_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_popup.c create mode 100644 source/blender/editors/interface/interface_region_popup.cc delete mode 100644 source/blender/editors/interface/interface_region_search.c create mode 100644 source/blender/editors/interface/interface_region_search.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_template_search_menu.c create mode 100644 source/blender/editors/interface/interface_template_search_menu.cc delete mode 100644 source/blender/editors/interface/interface_template_search_operator.c create mode 100644 source/blender/editors/interface/interface_template_search_operator.cc delete mode 100644 source/blender/editors/interface/interface_templates.c create mode 100644 source/blender/editors/interface/interface_templates.cc delete mode 100644 source/blender/editors/interface/interface_undo.c create mode 100644 source/blender/editors/interface/interface_undo.cc delete mode 100644 source/blender/editors/interface/interface_utils.c create mode 100644 source/blender/editors/interface/interface_utils.cc delete mode 100644 source/blender/editors/interface/resources.c create mode 100644 source/blender/editors/interface/resources.cc diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index 421019bebb8..d221dde4fae 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -36,12 +36,12 @@ set(INC ) set(SRC - interface.c + interface.cc interface_align.c interface_anim.c - interface_button_group.c - interface_context_menu.c - interface_draw.c + interface_button_group.cc + interface_context_menu.cc + interface_draw.cc interface_eyedropper.c interface_eyedropper_color.c interface_eyedropper_colorband.c @@ -49,30 +49,30 @@ set(SRC interface_eyedropper_depth.c interface_eyedropper_driver.c interface_eyedropper_gpencil_color.c - interface_handlers.c + interface_handlers.cc interface_icons.c - interface_icons_event.c - interface_layout.c + interface_icons_event.cc + interface_layout.cc interface_ops.c - interface_panel.c - interface_query.c - interface_region_color_picker.c - interface_region_hud.c + interface_panel.cc + interface_query.cc + interface_region_color_picker.cc + interface_region_hud.cc interface_region_menu_pie.c interface_region_menu_popup.c interface_region_popover.c - interface_region_popup.c - interface_region_search.c + interface_region_popup.cc + interface_region_search.cc interface_region_tooltip.c - interface_regions.c - interface_style.c - interface_template_search_menu.c - interface_template_search_operator.c - interface_templates.c - interface_undo.c - interface_utils.c + interface_regions.cc + interface_style.cc + interface_template_search_menu.cc + interface_template_search_operator.cc + interface_templates.cc + interface_undo.cc + interface_utils.cc interface_widgets.c - resources.c + resources.cc view2d.c view2d_draw.c view2d_gizmo_navigate.c diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c deleted file mode 100644 index 90d604b3190..00000000000 --- a/source/blender/editors/interface/interface.c +++ /dev/null @@ -1,7137 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include -#include /* offsetof() */ -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_screen_types.h" -#include "DNA_userdef_types.h" -#include "DNA_workspace_types.h" - -#include "BLI_alloca.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_string_search.h" -#include "BLI_string_utf8.h" - -#include "BLI_utildefines.h" - -#include "BLO_readfile.h" - -#include "BKE_animsys.h" -#include "BKE_context.h" -#include "BKE_idprop.h" -#include "BKE_main.h" -#include "BKE_report.h" -#include "BKE_scene.h" -#include "BKE_screen.h" -#include "BKE_unit.h" - -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "BLF_api.h" -#include "BLT_translation.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" -#include "UI_view2d.h" - -#include "IMB_imbuf.h" - -#include "WM_api.h" -#include "WM_message.h" -#include "WM_types.h" - -#include "RNA_access.h" - -#ifdef WITH_PYTHON -# include "BPY_extern_run.h" -#endif - -#include "ED_numinput.h" -#include "ED_screen.h" - -#include "IMB_colormanagement.h" - -#include "DEG_depsgraph_query.h" - -#include "interface_intern.h" - -/* prototypes. */ -static void ui_but_to_pixelrect(struct rcti *rect, - const struct ARegion *region, - struct uiBlock *block, - struct uiBut *but); -static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *but_p); -static void ui_def_but_rna__panel_type(bContext *UNUSED(C), uiLayout *layout, void *but_p); -static void ui_def_but_rna__menu_type(bContext *UNUSED(C), uiLayout *layout, void *but_p); - -/* avoid unneeded calls to ui_but_value_get */ -#define UI_BUT_VALUE_UNSET DBL_MAX -#define UI_GET_BUT_VALUE_INIT(_but, _value) \ - if (_value == DBL_MAX) { \ - (_value) = ui_but_value_get(_but); \ - } \ - ((void)0) - -#define B_NOP -1 - -/** - * a full doc with API notes can be found in 'blender/doc/guides/interface_API.txt' - * - * `uiBlahBlah()` external function. - * `ui_blah_blah()` internal function. - */ - -static void ui_but_free(const bContext *C, uiBut *but); - -static bool ui_but_is_unit_radians_ex(UnitSettings *unit, const int unit_type) -{ - return (unit->system_rotation == USER_UNIT_ROT_RADIANS && unit_type == PROP_UNIT_ROTATION); -} - -static bool ui_but_is_unit_radians(const uiBut *but) -{ - UnitSettings *unit = but->block->unit; - const int unit_type = UI_but_unit_type_get(but); - - return ui_but_is_unit_radians_ex(unit, unit_type); -} - -/* ************* window matrix ************** */ - -void ui_block_to_window_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y) -{ - const int getsizex = BLI_rcti_size_x(®ion->winrct) + 1; - const int getsizey = BLI_rcti_size_y(®ion->winrct) + 1; - const int sx = region->winrct.xmin; - const int sy = region->winrct.ymin; - - float gx = *r_x; - float gy = *r_y; - - if (block->panel) { - gx += block->panel->ofsx; - gy += block->panel->ofsy; - } - - *r_x = ((float)sx) + - ((float)getsizex) * (0.5f + 0.5f * (gx * block->winmat[0][0] + gy * block->winmat[1][0] + - block->winmat[3][0])); - *r_y = ((float)sy) + - ((float)getsizey) * (0.5f + 0.5f * (gx * block->winmat[0][1] + gy * block->winmat[1][1] + - block->winmat[3][1])); -} - -void ui_block_to_window(const ARegion *region, uiBlock *block, int *r_x, int *r_y) -{ - float fx = *r_x; - float fy = *r_y; - - ui_block_to_window_fl(region, block, &fx, &fy); - - *r_x = (int)(fx + 0.5f); - *r_y = (int)(fy + 0.5f); -} - -void ui_block_to_window_rctf(const ARegion *region, - uiBlock *block, - rctf *rct_dst, - const rctf *rct_src) -{ - *rct_dst = *rct_src; - ui_block_to_window_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); - ui_block_to_window_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); -} - -float ui_block_to_window_scale(const ARegion *region, uiBlock *block) -{ - /* We could have function for this to avoid dummy arg. */ - float min_y = 0, max_y = 1; - float dummy_x = 0.0f; - ui_block_to_window_fl(region, block, &dummy_x, &min_y); - dummy_x = 0.0f; - ui_block_to_window_fl(region, block, &dummy_x, &max_y); - return max_y - min_y; -} - -/* for mouse cursor */ -void ui_window_to_block_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y) -{ - const int getsizex = BLI_rcti_size_x(®ion->winrct) + 1; - const int getsizey = BLI_rcti_size_y(®ion->winrct) + 1; - const int sx = region->winrct.xmin; - const int sy = region->winrct.ymin; - - const float a = 0.5f * ((float)getsizex) * block->winmat[0][0]; - const float b = 0.5f * ((float)getsizex) * block->winmat[1][0]; - const float c = 0.5f * ((float)getsizex) * (1.0f + block->winmat[3][0]); - - const float d = 0.5f * ((float)getsizey) * block->winmat[0][1]; - const float e = 0.5f * ((float)getsizey) * block->winmat[1][1]; - const float f = 0.5f * ((float)getsizey) * (1.0f + block->winmat[3][1]); - - const float px = *r_x - sx; - const float py = *r_y - sy; - - *r_y = (a * (py - f) + d * (c - px)) / (a * e - d * b); - *r_x = (px - b * (*r_y) - c) / a; - - if (block->panel) { - *r_x -= block->panel->ofsx; - *r_y -= block->panel->ofsy; - } -} - -void ui_window_to_block_rctf(const struct ARegion *region, - uiBlock *block, - rctf *rct_dst, - const rctf *rct_src) -{ - *rct_dst = *rct_src; - ui_window_to_block_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); - ui_window_to_block_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); -} - -void ui_window_to_block(const ARegion *region, uiBlock *block, int *r_x, int *r_y) -{ - float fx = *r_x; - float fy = *r_y; - - ui_window_to_block_fl(region, block, &fx, &fy); - - *r_x = (int)(fx + 0.5f); - *r_y = (int)(fy + 0.5f); -} - -void ui_window_to_region(const ARegion *region, int *r_x, int *r_y) -{ - *r_x -= region->winrct.xmin; - *r_y -= region->winrct.ymin; -} - -void ui_window_to_region_rcti(const ARegion *region, rcti *rect_dst, const rcti *rct_src) -{ - rect_dst->xmin = rct_src->xmin - region->winrct.xmin; - rect_dst->xmax = rct_src->xmax - region->winrct.xmin; - rect_dst->ymin = rct_src->ymin - region->winrct.ymin; - rect_dst->ymax = rct_src->ymax - region->winrct.ymin; -} - -void ui_region_to_window(const ARegion *region, int *r_x, int *r_y) -{ - *r_x += region->winrct.xmin; - *r_y += region->winrct.ymin; -} - -static void ui_update_flexible_spacing(const ARegion *region, uiBlock *block) -{ - int sepr_flex_len = 0; - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->type == UI_BTYPE_SEPR_SPACER) { - sepr_flex_len++; - } - } - - if (sepr_flex_len == 0) { - return; - } - - rcti rect; - ui_but_to_pixelrect(&rect, region, block, block->buttons.last); - const float buttons_width = (float)rect.xmax + UI_HEADER_OFFSET; - const float region_width = (float)region->sizex * U.dpi_fac; - - if (region_width <= buttons_width) { - return; - } - - /* We could get rid of this loop if we agree on a max number of spacer */ - int *spacers_pos = alloca(sizeof(*spacers_pos) * (size_t)sepr_flex_len); - int i = 0; - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->type == UI_BTYPE_SEPR_SPACER) { - ui_but_to_pixelrect(&rect, region, block, but); - spacers_pos[i] = rect.xmax + UI_HEADER_OFFSET; - i++; - } - } - - const float view_scale_x = UI_view2d_scale_get_x(®ion->v2d); - const float segment_width = region_width / (float)sepr_flex_len; - float offset = 0, remaining_space = region_width - buttons_width; - i = 0; - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - BLI_rctf_translate(&but->rect, offset / view_scale_x, 0); - if (but->type == UI_BTYPE_SEPR_SPACER) { - /* How much the next block overlap with the current segment */ - int overlap = ((i == sepr_flex_len - 1) ? buttons_width - spacers_pos[i] : - (spacers_pos[i + 1] - spacers_pos[i]) / 2); - const int segment_end = segment_width * (i + 1); - const int spacer_end = segment_end - overlap; - const int spacer_sta = spacers_pos[i] + offset; - if (spacer_end > spacer_sta) { - const float step = min_ff(remaining_space, spacer_end - spacer_sta); - remaining_space -= step; - offset += step; - } - i++; - } - } - ui_block_bounds_calc(block); -} - -static void ui_update_window_matrix(const wmWindow *window, const ARegion *region, uiBlock *block) -{ - /* window matrix and aspect */ - if (region && region->visible) { - /* Get projection matrix which includes View2D translation and zoom. */ - GPU_matrix_projection_get(block->winmat); - block->aspect = 2.0f / fabsf(region->winx * block->winmat[0][0]); - } - else { - /* No subwindow created yet, for menus for example, so we use the main - * window instead, since buttons are created there anyway. */ - const int width = WM_window_pixels_x(window); - const int height = WM_window_pixels_y(window); - const rcti winrct = {0, width - 1, 0, height - 1}; - - wmGetProjectionMatrix(block->winmat, &winrct); - block->aspect = 2.0f / fabsf(width * block->winmat[0][0]); - } -} - -/** - * Popups will add a margin to #ARegion.winrct for shadow, - * for interactivity (point-inside tests for eg), we want the winrct without the margin added. - */ -void ui_region_winrct_get_no_margin(const struct ARegion *region, struct rcti *r_rect) -{ - uiBlock *block = region->uiblocks.first; - if (block && (block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_RADIAL) == 0) { - BLI_rcti_rctf_copy_floor(r_rect, &block->rect); - BLI_rcti_translate(r_rect, region->winrct.xmin, region->winrct.ymin); - } - else { - *r_rect = region->winrct; - } -} - -/* ******************* block calc ************************* */ - -void UI_block_translate(uiBlock *block, int x, int y) -{ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - BLI_rctf_translate(&but->rect, x, y); - } - - BLI_rctf_translate(&block->rect, x, y); -} - -static bool ui_but_is_row_alignment_group(const uiBut *left, const uiBut *right) -{ - const bool is_same_align_group = (left->alignnr && (left->alignnr == right->alignnr)); - return is_same_align_group && (left->rect.xmin < right->rect.xmin); -} - -static void ui_block_bounds_calc_text(uiBlock *block, float offset) -{ - const uiStyle *style = UI_style_get(); - uiBut *col_bt; - int i = 0, j, x1addval = offset; - - UI_fontstyle_set(&style->widget); - - uiBut *init_col_bt = block->buttons.first; - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (!ELEM(bt->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) { - j = BLF_width(style->widget.uifont_id, bt->drawstr, sizeof(bt->drawstr)); - - if (j > i) { - i = j; - } - } - - /* Skip all buttons that are in a horizontal alignment group. - * We don't want to split them apart (but still check the row's width and apply current - * offsets). */ - if (bt->next && ui_but_is_row_alignment_group(bt, bt->next)) { - int width = 0; - const int alignnr = bt->alignnr; - for (col_bt = bt; col_bt && col_bt->alignnr == alignnr; col_bt = col_bt->next) { - width += BLI_rctf_size_x(&col_bt->rect); - col_bt->rect.xmin += x1addval; - col_bt->rect.xmax += x1addval; - } - if (width > i) { - i = width; - } - /* Give the following code the last button in the alignment group, there might have to be a - * split immediately after. */ - bt = col_bt ? col_bt->prev : NULL; - } - - if (bt && bt->next && bt->rect.xmin < bt->next->rect.xmin) { - /* End of this column, and it's not the last one. */ - for (col_bt = init_col_bt; col_bt->prev != bt; col_bt = col_bt->next) { - col_bt->rect.xmin = x1addval; - col_bt->rect.xmax = x1addval + i + block->bounds; - - ui_but_update(col_bt); /* clips text again */ - } - - /* And we prepare next column. */ - x1addval += i + block->bounds; - i = 0; - init_col_bt = col_bt; - } - } - - /* Last column. */ - for (col_bt = init_col_bt; col_bt; col_bt = col_bt->next) { - /* Recognize a horizontally arranged alignment group and skip its items. */ - if (col_bt->next && ui_but_is_row_alignment_group(col_bt, col_bt->next)) { - const int alignnr = col_bt->alignnr; - for (; col_bt && col_bt->alignnr == alignnr; col_bt = col_bt->next) { - /* pass */ - } - } - if (!col_bt) { - break; - } - - col_bt->rect.xmin = x1addval; - col_bt->rect.xmax = max_ff(x1addval + i + block->bounds, offset + block->minbounds); - - ui_but_update(col_bt); /* clips text again */ - } -} - -void ui_block_bounds_calc(uiBlock *block) -{ - if (BLI_listbase_is_empty(&block->buttons)) { - if (block->panel) { - block->rect.xmin = 0.0; - block->rect.xmax = block->panel->sizex; - block->rect.ymin = 0.0; - block->rect.ymax = block->panel->sizey; - } - } - else { - - BLI_rctf_init_minmax(&block->rect); - - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - BLI_rctf_union(&block->rect, &bt->rect); - } - - block->rect.xmin -= block->bounds; - block->rect.ymin -= block->bounds; - block->rect.xmax += block->bounds; - block->rect.ymax += block->bounds; - } - - block->rect.xmax = block->rect.xmin + max_ff(BLI_rctf_size_x(&block->rect), block->minbounds); - - /* hardcoded exception... but that one is annoying with larger safety */ - uiBut *bt = block->buttons.first; - const int xof = ((bt && STRPREFIX(bt->str, "ERROR")) ? 10 : 40) * U.dpi_fac; - - block->safety.xmin = block->rect.xmin - xof; - block->safety.ymin = block->rect.ymin - xof; - block->safety.xmax = block->rect.xmax + xof; - block->safety.ymax = block->rect.ymax + xof; -} - -static void ui_block_bounds_calc_centered(wmWindow *window, uiBlock *block) -{ - /* note: this is used for the splash where window bounds event has not been - * updated by ghost, get the window bounds from ghost directly */ - - const int xmax = WM_window_pixels_x(window); - const int ymax = WM_window_pixels_y(window); - - ui_block_bounds_calc(block); - - const int width = BLI_rctf_size_x(&block->rect); - const int height = BLI_rctf_size_y(&block->rect); - - const int startx = (xmax * 0.5f) - (width * 0.5f); - const int starty = (ymax * 0.5f) - (height * 0.5f); - - UI_block_translate(block, startx - block->rect.xmin, starty - block->rect.ymin); - - /* now recompute bounds and safety */ - ui_block_bounds_calc(block); -} - -static void ui_block_bounds_calc_centered_pie(uiBlock *block) -{ - const int xy[2] = { - block->pie_data.pie_center_spawned[0], - block->pie_data.pie_center_spawned[1], - }; - - UI_block_translate(block, xy[0], xy[1]); - - /* now recompute bounds and safety */ - ui_block_bounds_calc(block); -} - -static void ui_block_bounds_calc_popup( - wmWindow *window, uiBlock *block, eBlockBoundsCalc bounds_calc, const int xy[2], int r_xy[2]) -{ - const int oldbounds = block->bounds; - - /* compute mouse position with user defined offset */ - ui_block_bounds_calc(block); - - const int xmax = WM_window_pixels_x(window); - const int ymax = WM_window_pixels_y(window); - - int oldwidth = BLI_rctf_size_x(&block->rect); - int oldheight = BLI_rctf_size_y(&block->rect); - - /* first we ensure wide enough text bounds */ - if (bounds_calc == UI_BLOCK_BOUNDS_POPUP_MENU) { - if (block->flag & UI_BLOCK_LOOP) { - block->bounds = 2.5f * UI_UNIT_X; - ui_block_bounds_calc_text(block, block->rect.xmin); - } - } - - /* next we recompute bounds */ - block->bounds = oldbounds; - ui_block_bounds_calc(block); - - /* and we adjust the position to fit within window */ - const int width = BLI_rctf_size_x(&block->rect); - const int height = BLI_rctf_size_y(&block->rect); - - /* avoid divide by zero below, caused by calling with no UI, but better not crash */ - oldwidth = oldwidth > 0 ? oldwidth : MAX2(1, width); - oldheight = oldheight > 0 ? oldheight : MAX2(1, height); - - /* offset block based on mouse position, user offset is scaled - * along in case we resized the block in ui_block_bounds_calc_text */ - rcti rect; - const int raw_x = rect.xmin = xy[0] + block->rect.xmin + - (block->bounds_offset[0] * width) / oldwidth; - int raw_y = rect.ymin = xy[1] + block->rect.ymin + - (block->bounds_offset[1] * height) / oldheight; - rect.xmax = rect.xmin + width; - rect.ymax = rect.ymin + height; - - rcti rect_bounds; - const int margin = UI_SCREEN_MARGIN; - rect_bounds.xmin = margin; - rect_bounds.ymin = margin; - rect_bounds.xmax = xmax - margin; - rect_bounds.ymax = ymax - UI_POPUP_MENU_TOP; - - int ofs_dummy[2]; - BLI_rcti_clamp(&rect, &rect_bounds, ofs_dummy); - UI_block_translate(block, rect.xmin - block->rect.xmin, rect.ymin - block->rect.ymin); - - /* now recompute bounds and safety */ - ui_block_bounds_calc(block); - - /* If given, adjust input coordinates such that they would generate real final popup position. - * Needed to handle correctly floating panels once they have been dragged around, - * see T52999. */ - if (r_xy) { - r_xy[0] = xy[0] + block->rect.xmin - raw_x; - r_xy[1] = xy[1] + block->rect.ymin - raw_y; - } -} - -/* used for various cases */ -void UI_block_bounds_set_normal(uiBlock *block, int addval) -{ - if (block == NULL) { - return; - } - - block->bounds = addval; - block->bounds_type = UI_BLOCK_BOUNDS; -} - -/* used for pulldowns */ -void UI_block_bounds_set_text(uiBlock *block, int addval) -{ - block->bounds = addval; - block->bounds_type = UI_BLOCK_BOUNDS_TEXT; -} - -/* used for block popups */ -void UI_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2]) -{ - block->bounds = addval; - block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MOUSE; - if (bounds_offset != NULL) { - block->bounds_offset[0] = bounds_offset[0]; - block->bounds_offset[1] = bounds_offset[1]; - } - else { - block->bounds_offset[0] = 0; - block->bounds_offset[1] = 0; - } -} - -/* used for menu popups */ -void UI_block_bounds_set_menu(uiBlock *block, int addval, const int bounds_offset[2]) -{ - block->bounds = addval; - block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MENU; - if (bounds_offset != NULL) { - copy_v2_v2_int(block->bounds_offset, bounds_offset); - } - else { - zero_v2_int(block->bounds_offset); - } -} - -/* used for centered popups, i.e. splash */ -void UI_block_bounds_set_centered(uiBlock *block, int addval) -{ - block->bounds = addval; - block->bounds_type = UI_BLOCK_BOUNDS_POPUP_CENTER; -} - -void UI_block_bounds_set_explicit(uiBlock *block, int minx, int miny, int maxx, int maxy) -{ - block->rect.xmin = minx; - block->rect.ymin = miny; - block->rect.xmax = maxx; - block->rect.ymax = maxy; - block->bounds_type = UI_BLOCK_BOUNDS_NONE; -} - -static float ui_but_get_float_precision(uiBut *but) -{ - if (but->type == UI_BTYPE_NUM) { - return ((uiButNumber *)but)->precision; - } - - return but->a2; -} - -static int ui_but_calc_float_precision(uiBut *but, double value) -{ - int prec = (int)ui_but_get_float_precision(but); - - /* first check for various special cases: - * * If button is radians, we want additional precision (see T39861). - * * If prec is not set, we fallback to a simple default */ - if (ui_but_is_unit_radians(but) && prec < 5) { - prec = 5; - } - else if (prec == -1) { - prec = (but->hardmax < 10.001f) ? 3 : 2; - } - else { - CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); - } - - return UI_calc_float_precision(prec, value); -} - -/* ************** BLOCK ENDING FUNCTION ************* */ - -bool ui_but_rna_equals(const uiBut *a, const uiBut *b) -{ - return ui_but_rna_equals_ex(a, &b->rnapoin, b->rnaprop, b->rnaindex); -} - -bool ui_but_rna_equals_ex(const uiBut *but, - const PointerRNA *ptr, - const PropertyRNA *prop, - int index) -{ - if (but->rnapoin.data != ptr->data) { - return false; - } - if (but->rnaprop != prop || but->rnaindex != index) { - return false; - } - - return true; -} - -/* NOTE: if but->poin is allocated memory for every defbut, things fail... */ -static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) -{ - /* various properties are being compared here, hopefully sufficient - * to catch all cases, but it is simple to add more checks later */ - if (but->retval != oldbut->retval) { - return false; - } - if (!ui_but_rna_equals(but, oldbut)) { - return false; - } - if (but->func != oldbut->func) { - return false; - } - if (but->funcN != oldbut->funcN) { - return false; - } - if (!ELEM(oldbut->func_arg1, oldbut, but->func_arg1)) { - return false; - } - if (!ELEM(oldbut->func_arg2, oldbut, but->func_arg2)) { - return false; - } - if (!but->funcN && ((but->poin != oldbut->poin && (uiBut *)oldbut->poin != oldbut) || - (but->pointype != oldbut->pointype))) { - return false; - } - if (but->optype != oldbut->optype) { - return false; - } - - return true; -} - -uiBut *ui_but_find_old(uiBlock *block_old, const uiBut *but_new) -{ - LISTBASE_FOREACH (uiBut *, but, &block_old->buttons) { - if (ui_but_equals_old(but_new, but)) { - return but; - } - } - return NULL; -} - -uiBut *ui_but_find_new(uiBlock *block_new, const uiBut *but_old) -{ - LISTBASE_FOREACH (uiBut *, but, &block_new->buttons) { - if (ui_but_equals_old(but, but_old)) { - return but; - } - } - return NULL; -} - -static bool ui_but_extra_icons_equals_old(const uiButExtraOpIcon *new_extra_icon, - const uiButExtraOpIcon *old_extra_icon) -{ - return (new_extra_icon->optype_params->optype == old_extra_icon->optype_params->optype) && - (new_extra_icon->icon == old_extra_icon->icon); -} - -static uiButExtraOpIcon *ui_but_extra_icon_find_old(const uiButExtraOpIcon *new_extra_icon, - const uiBut *old_but) -{ - LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &old_but->extra_op_icons) { - if (ui_but_extra_icons_equals_old(new_extra_icon, op_icon)) { - return op_icon; - } - } - return NULL; -} - -static void ui_but_extra_icons_update_from_old_but(const uiBut *new_but, const uiBut *old_but) -{ - /* Specifically for keeping some state info for the active button. */ - BLI_assert(old_but->active); - - LISTBASE_FOREACH (uiButExtraOpIcon *, new_extra_icon, &new_but->extra_op_icons) { - uiButExtraOpIcon *old_extra_icon = ui_but_extra_icon_find_old(new_extra_icon, old_but); - /* Keep the highlighting state, and let handling update it later. */ - if (old_extra_icon) { - new_extra_icon->highlighted = old_extra_icon->highlighted; - } - } -} - -/** - * Update pointers and other information in the old active button based on new information in the - * corresponding new button from the current layout pass. - * - * \param oldbut: The button from the last layout pass that will be moved to the new block. - * \param but: The newly added button with much of the up to date information, to be feed later. - * - * \note #uiBut has ownership of many of its pointers. When the button is freed all these - * pointers are freed as well, so ownership has to be moved out of \a but in order to free it. - */ -static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) -{ - BLI_assert(oldbut->active); - - /* flags from the buttons we want to refresh, may want to add more here... */ - const int flag_copy = UI_BUT_REDALERT | UI_HAS_ICON | UI_SELECT_DRAW; - const int drawflag_copy = 0; /* None currently. */ - - /* still stuff needs to be copied */ - oldbut->rect = but->rect; - oldbut->context = but->context; /* set by Layout */ - - /* drawing */ - oldbut->icon = but->icon; - oldbut->iconadd = but->iconadd; - oldbut->alignnr = but->alignnr; - - /* typically the same pointers, but not on undo/redo */ - /* XXX some menu buttons store button itself in but->poin. Ugly */ - if (oldbut->poin != (char *)oldbut) { - SWAP(char *, oldbut->poin, but->poin); - SWAP(void *, oldbut->func_argN, but->func_argN); - } - - /* Move tooltip from new to old. */ - SWAP(uiButToolTipFunc, oldbut->tip_func, but->tip_func); - SWAP(void *, oldbut->tip_argN, but->tip_argN); - - oldbut->flag = (oldbut->flag & ~flag_copy) | (but->flag & flag_copy); - oldbut->drawflag = (oldbut->drawflag & ~drawflag_copy) | (but->drawflag & drawflag_copy); - - ui_but_extra_icons_update_from_old_but(but, oldbut); - SWAP(ListBase, but->extra_op_icons, oldbut->extra_op_icons); - - if (oldbut->type == UI_BTYPE_SEARCH_MENU) { - uiButSearch *search_oldbut = (uiButSearch *)oldbut, *search_but = (uiButSearch *)but; - - SWAP(uiButSearchArgFreeFn, search_oldbut->arg_free_fn, search_but->arg_free_fn); - SWAP(void *, search_oldbut->arg, search_but->arg); - } - - /* copy hardmin for list rows to prevent 'sticking' highlight to mouse position - * when scrolling without moving mouse (see T28432) */ - if (ELEM(oldbut->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { - oldbut->hardmax = but->hardmax; - } - - if (oldbut->type == UI_BTYPE_PROGRESS_BAR) { - uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; - uiButProgressbar *progress_but = (uiButProgressbar *)but; - progress_oldbut->progress = progress_but->progress; - } - - /* move/copy string from the new button to the old */ - /* needed for alt+mouse wheel over enums */ - if (but->str != but->strdata) { - if (oldbut->str != oldbut->strdata) { - SWAP(char *, but->str, oldbut->str); - } - else { - oldbut->str = but->str; - but->str = but->strdata; - } - } - else { - if (oldbut->str != oldbut->strdata) { - MEM_freeN(oldbut->str); - oldbut->str = oldbut->strdata; - } - BLI_strncpy(oldbut->strdata, but->strdata, sizeof(oldbut->strdata)); - } - - if (but->dragpoin && (but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - SWAP(void *, but->dragpoin, oldbut->dragpoin); - } - - /* note: if layout hasn't been applied yet, it uses old button pointers... */ -} - -/** - * \return true when \a but_p is set (only done for active buttons). - */ -static bool ui_but_update_from_old_block(const bContext *C, - uiBlock *block, - uiBut **but_p, - uiBut **but_old_p) -{ - uiBlock *oldblock = block->oldblock; - uiBut *but = *but_p; - -#if 0 - /* Simple method - search every time. Keep this for easy testing of the "fast path." */ - uiBut *oldbut = ui_but_find_old(oldblock, but); - UNUSED_VARS(but_old_p); -#else - BLI_assert(*but_old_p == NULL || BLI_findindex(&oldblock->buttons, *but_old_p) != -1); - - /* As long as old and new buttons are aligned, avoid loop-in-loop (calling #ui_but_find_old). */ - uiBut *oldbut; - if (LIKELY(*but_old_p && ui_but_equals_old(but, *but_old_p))) { - oldbut = *but_old_p; - } - else { - /* Fallback to block search. */ - oldbut = ui_but_find_old(oldblock, but); - } - (*but_old_p) = oldbut ? oldbut->next : NULL; -#endif - - bool found_active = false; - - if (!oldbut) { - return false; - } - - if (oldbut->active) { - /* Move button over from oldblock to new block. */ - BLI_remlink(&oldblock->buttons, oldbut); - BLI_insertlinkafter(&block->buttons, but, oldbut); - /* Add the old button to the button groups in the new block. */ - ui_button_group_replace_but_ptr(block, but, oldbut); - oldbut->block = block; - *but_p = oldbut; - - ui_but_update_old_active_from_new(oldbut, but); - - if (!BLI_listbase_is_empty(&block->butstore)) { - UI_butstore_register_update(block, oldbut, but); - } - - BLI_remlink(&block->buttons, but); - ui_but_free(C, but); - - found_active = true; - } - else { - const int flag_copy = UI_BUT_DRAG_MULTI; - - but->flag = (but->flag & ~flag_copy) | (oldbut->flag & flag_copy); - - /* ensures one button can get activated, and in case the buttons - * draw are the same this gives O(1) lookup for each button */ - BLI_remlink(&oldblock->buttons, oldbut); - ui_but_free(C, oldbut); - } - - return found_active; -} - -/** - * Needed for temporarily rename buttons, such as in outliner or file-select, - * they should keep calling #uiDefBut to keep them alive. - * \return false when button removed. - */ -bool UI_but_active_only_ex( - const bContext *C, ARegion *region, uiBlock *block, uiBut *but, const bool remove_on_failure) -{ - bool activate = false, found = false, isactive = false; - - uiBlock *oldblock = block->oldblock; - if (!oldblock) { - activate = true; - } - else { - uiBut *oldbut = ui_but_find_old(oldblock, but); - if (oldbut) { - found = true; - - if (oldbut->active) { - isactive = true; - } - } - } - if ((activate == true) || (found == false)) { - /* There might still be another active button. */ - uiBut *old_active = ui_region_find_active_but(region); - if (old_active) { - ui_but_active_free(C, old_active); - } - - ui_but_activate_event((bContext *)C, region, but); - } - else if ((found == true) && (isactive == false)) { - if (remove_on_failure) { - BLI_remlink(&block->buttons, but); - ui_but_free(C, but); - } - return false; - } - - return true; -} - -bool UI_but_active_only(const bContext *C, ARegion *region, uiBlock *block, uiBut *but) -{ - return UI_but_active_only_ex(C, region, block, but, true); -} - -/** - * \warning This must run after other handlers have been added, - * otherwise the handler wont be removed, see: T71112. - */ -bool UI_block_active_only_flagged_buttons(const bContext *C, ARegion *region, uiBlock *block) -{ - /* Running this command before end-block has run, means buttons that open menus - * wont have those menus correctly positioned, see T83539. */ - BLI_assert(block->endblock); - - bool done = false; - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->flag & UI_BUT_ACTIVATE_ON_INIT) { - but->flag &= ~UI_BUT_ACTIVATE_ON_INIT; - if (ui_but_is_editable(but)) { - if (UI_but_active_only_ex(C, region, block, but, false)) { - done = true; - break; - } - } - } - } - - if (done) { - /* Run this in a second pass since it's possible activating the button - * removes the buttons being looped over. */ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->flag &= ~UI_BUT_ACTIVATE_ON_INIT; - } - } - - return done; -} - -/* simulate button click */ -void UI_but_execute(const bContext *C, ARegion *region, uiBut *but) -{ - void *active_back; - ui_but_execute_begin((bContext *)C, region, but, &active_back); - /* Value is applied in begin. No further action required. */ - ui_but_execute_end((bContext *)C, region, but, active_back); -} - -/* use to check if we need to disable undo, but don't make any changes - * returns false if undo needs to be disabled. */ -static bool ui_but_is_rna_undo(const uiBut *but) -{ - if (but->rnapoin.owner_id) { - /* avoid undo push for buttons who's ID are screen or wm level - * we could disable undo for buttons with no ID too but may have - * unforeseen consequences, so best check for ID's we _know_ are not - * handled by undo - campbell */ - ID *id = but->rnapoin.owner_id; - if (ID_CHECK_UNDO(id) == false) { - return false; - } - } - if (but->rnapoin.type && !RNA_struct_undo_check(but->rnapoin.type)) { - return false; - } - - return true; -} - -/* assigns automatic keybindings to menu items for fast access - * (underline key in menu) */ -static void ui_menu_block_set_keyaccels(uiBlock *block) -{ - uint menu_key_mask = 0; - int tot_missing = 0; - - /* only do it before bounding */ - if (block->rect.xmin != block->rect.xmax) { - return; - } - - for (int pass = 0; pass < 2; pass++) { - /* 2 Passes: One for first letter only, second for any letter if the first pass fails. - * Run first pass on all buttons so first word chars always get first priority. */ - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (!ELEM(but->type, - UI_BTYPE_BUT, - UI_BTYPE_BUT_MENU, - UI_BTYPE_MENU, - UI_BTYPE_BLOCK, - UI_BTYPE_PULLDOWN, - /* For PIE-menus. */ - UI_BTYPE_ROW) || - (but->flag & UI_HIDDEN)) { - continue; - } - - if (but->menu_key != '\0') { - continue; - } - - if (but->str == NULL || but->str[0] == '\0') { - continue; - } - - const char *str_pt = but->str; - uchar menu_key; - do { - menu_key = tolower(*str_pt); - if ((menu_key >= 'a' && menu_key <= 'z') && !(menu_key_mask & 1 << (menu_key - 'a'))) { - menu_key_mask |= 1 << (menu_key - 'a'); - break; - } - - if (pass == 0) { - /* Skip to next delimiter on first pass (be picky) */ - while (isalpha(*str_pt)) { - str_pt++; - } - - if (*str_pt) { - str_pt++; - } - } - else { - /* just step over every char second pass and find first usable key */ - str_pt++; - } - } while (*str_pt); - - if (*str_pt) { - but->menu_key = menu_key; - } - else { - /* run second pass */ - tot_missing++; - } - - /* if all keys have been used just exit, unlikely */ - if (menu_key_mask == (1 << 26) - 1) { - return; - } - } - - /* check if second pass is needed */ - if (!tot_missing) { - break; - } - } -} - -/* XXX, this code will shorten any allocated string to 'UI_MAX_NAME_STR' - * since this is really long its unlikely to be an issue, - * but this could be supported */ -void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_strip) -{ - if (do_strip && (but->flag & UI_BUT_HAS_SEP_CHAR)) { - char *cpoin = strrchr(but->str, UI_SEP_CHAR); - if (cpoin) { - *cpoin = '\0'; - } - but->flag &= ~UI_BUT_HAS_SEP_CHAR; - } - - /* without this, just allow stripping of the shortcut */ - if (shortcut_str == NULL) { - return; - } - - char *butstr_orig; - if (but->str != but->strdata) { - butstr_orig = but->str; /* free after using as source buffer */ - } - else { - butstr_orig = BLI_strdup(but->str); - } - BLI_snprintf( - but->strdata, sizeof(but->strdata), "%s" UI_SEP_CHAR_S "%s", butstr_orig, shortcut_str); - MEM_freeN(butstr_orig); - but->str = but->strdata; - but->flag |= UI_BUT_HAS_SEP_CHAR; - but->drawflag |= UI_BUT_HAS_SHORTCUT; - ui_but_update(but); -} - -/* -------------------------------------------------------------------- */ -/** \name Find Key Shortcut for Button - * - * - #ui_but_event_operator_string (and helpers) - * - #ui_but_event_property_operator_string - * \{ */ - -static bool ui_but_event_operator_string_from_operator(const bContext *C, - uiBut *but, - char *buf, - const size_t buf_len) -{ - BLI_assert(but->optype != NULL); - bool found = false; - IDProperty *prop = (but->opptr) ? but->opptr->data : NULL; - - if (WM_key_event_operator_string( - C, but->optype->idname, but->opcontext, prop, true, buf, buf_len)) { - found = true; - } - return found; -} - -static bool ui_but_event_operator_string_from_menu(const bContext *C, - uiBut *but, - char *buf, - const size_t buf_len) -{ - MenuType *mt = UI_but_menutype_get(but); - BLI_assert(mt != NULL); - - bool found = false; - - /* annoying, create a property */ - const IDPropertyTemplate val = {0}; - IDProperty *prop_menu = IDP_New(IDP_GROUP, &val, __func__); /* dummy, name is unimportant */ - IDP_AddToGroup(prop_menu, IDP_NewString(mt->idname, "name", sizeof(mt->idname))); - - if (WM_key_event_operator_string( - C, "WM_OT_call_menu", WM_OP_INVOKE_REGION_WIN, prop_menu, true, buf, buf_len)) { - found = true; - } - - IDP_FreeProperty(prop_menu); - return found; -} - -static bool ui_but_event_operator_string_from_panel(const bContext *C, - uiBut *but, - char *buf, - const size_t buf_len) -{ - /** Nearly exact copy of #ui_but_event_operator_string_from_menu */ - PanelType *pt = UI_but_paneltype_get(but); - BLI_assert(pt != NULL); - - bool found = false; - - /* annoying, create a property */ - const IDPropertyTemplate val = {0}; - IDProperty *prop_panel = IDP_New(IDP_GROUP, &val, __func__); /* dummy, name is unimportant */ - IDP_AddToGroup(prop_panel, IDP_NewString(pt->idname, "name", sizeof(pt->idname))); - IDP_AddToGroup(prop_panel, - IDP_New(IDP_INT, - &(IDPropertyTemplate){ - .i = pt->space_type, - }, - "space_type")); - IDP_AddToGroup(prop_panel, - IDP_New(IDP_INT, - &(IDPropertyTemplate){ - .i = pt->region_type, - }, - "region_type")); - - for (int i = 0; i < 2; i++) { - /* FIXME(campbell): We can't reasonably search all configurations - long term. */ - IDP_ReplaceInGroup(prop_panel, - IDP_New(IDP_INT, - &(IDPropertyTemplate){ - .i = i, - }, - "keep_open")); - if (WM_key_event_operator_string( - C, "WM_OT_call_panel", WM_OP_INVOKE_REGION_WIN, prop_panel, true, buf, buf_len)) { - found = true; - break; - } - } - - IDP_FreeProperty(prop_panel); - return found; -} - -static bool ui_but_event_operator_string(const bContext *C, - uiBut *but, - char *buf, - const size_t buf_len) -{ - bool found = false; - - if (but->optype != NULL) { - found = ui_but_event_operator_string_from_operator(C, but, buf, buf_len); - } - else if (UI_but_menutype_get(but) != NULL) { - found = ui_but_event_operator_string_from_menu(C, but, buf, buf_len); - } - else if (UI_but_paneltype_get(but) != NULL) { - found = ui_but_event_operator_string_from_panel(C, but, buf, buf_len); - } - - return found; -} - -static bool ui_but_event_property_operator_string(const bContext *C, - uiBut *but, - char *buf, - const size_t buf_len) -{ - /* Context toggle operator names to check. */ - - /* This function could use a refactor to generalize button type to operator relationship - * as well as which operators use properties. - Campbell */ - const char *ctx_toggle_opnames[] = { - "WM_OT_context_toggle", - "WM_OT_context_toggle_enum", - "WM_OT_context_cycle_int", - "WM_OT_context_cycle_enum", - "WM_OT_context_cycle_array", - "WM_OT_context_menu_enum", - NULL, - }; - - const char *ctx_enum_opnames[] = { - "WM_OT_context_set_enum", - NULL, - }; - - const char *ctx_enum_opnames_for_Area_ui_type[] = { - "SCREEN_OT_space_type_set_or_cycle", - NULL, - }; - - const char **opnames = ctx_toggle_opnames; - int opnames_len = ARRAY_SIZE(ctx_toggle_opnames); - - int prop_enum_value = -1; - bool prop_enum_value_ok = false; - bool prop_enum_value_is_int = false; - const char *prop_enum_value_id = "value"; - PointerRNA *ptr = &but->rnapoin; - PropertyRNA *prop = but->rnaprop; - if ((but->type == UI_BTYPE_BUT_MENU) && (but->block->handle != NULL)) { - uiBut *but_parent = but->block->handle->popup_create_vars.but; - if ((but->type == UI_BTYPE_BUT_MENU) && (but_parent && but_parent->rnaprop) && - (RNA_property_type(but_parent->rnaprop) == PROP_ENUM) && - ELEM(but_parent->menu_create_func, - ui_def_but_rna__menu, - ui_def_but_rna__panel_type, - ui_def_but_rna__menu_type)) { - prop_enum_value = (int)but->hardmin; - ptr = &but_parent->rnapoin; - prop = but_parent->rnaprop; - prop_enum_value_ok = true; - - opnames = ctx_enum_opnames; - opnames_len = ARRAY_SIZE(ctx_enum_opnames); - } - } - /* Don't use the button again. */ - but = NULL; - - if (prop == NULL) { - return false; - } - - /* this version is only for finding hotkeys for properties - * (which get set via context using operators) */ - /* to avoid massive slowdowns on property panels, for now, we only check the - * hotkeys for Editor / Scene settings... - * - * TODO: userpref settings? - */ - char *data_path = NULL; - - if (ptr->owner_id) { - ID *id = ptr->owner_id; - - if (GS(id->name) == ID_SCR) { - /* screen/editor property - * NOTE: in most cases, there is actually no info for backwards tracing - * how to get back to ID from the editor data we may be dealing with - */ - if (RNA_struct_is_a(ptr->type, &RNA_Space)) { - /* data should be directly on here... */ - data_path = BLI_sprintfN("space_data.%s", RNA_property_identifier(prop)); - } - else if (RNA_struct_is_a(ptr->type, &RNA_Area)) { - /* data should be directly on here... */ - const char *prop_id = RNA_property_identifier(prop); - /* Hack since keys access 'type', UI shows 'ui_type'. */ - if (STREQ(prop_id, "ui_type")) { - prop_id = "type"; - prop_enum_value >>= 16; - prop = RNA_struct_find_property(ptr, prop_id); - - opnames = ctx_enum_opnames_for_Area_ui_type; - opnames_len = ARRAY_SIZE(ctx_enum_opnames_for_Area_ui_type); - prop_enum_value_id = "space_type"; - prop_enum_value_is_int = true; - } - else { - data_path = BLI_sprintfN("area.%s", prop_id); - } - } - else { - /* special exceptions for common nested data in editors... */ - if (RNA_struct_is_a(ptr->type, &RNA_DopeSheet)) { - /* dopesheet filtering options... */ - data_path = BLI_sprintfN("space_data.dopesheet.%s", RNA_property_identifier(prop)); - } - else if (RNA_struct_is_a(ptr->type, &RNA_FileSelectParams)) { - /* Filebrowser options... */ - data_path = BLI_sprintfN("space_data.params.%s", RNA_property_identifier(prop)); - } - } - } - else if (GS(id->name) == ID_SCE) { - if (RNA_struct_is_a(ptr->type, &RNA_ToolSettings)) { - /* Tool-settings property: - * NOTE: tool-settings is usually accessed directly (i.e. not through scene). */ - data_path = RNA_path_from_ID_to_property(ptr, prop); - } - else { - /* scene property */ - char *path = RNA_path_from_ID_to_property(ptr, prop); - - if (path) { - data_path = BLI_sprintfN("scene.%s", path); - MEM_freeN(path); - } -#if 0 - else { - printf("ERROR in %s(): Couldn't get path for scene property - %s\n", - __func__, - RNA_property_identifier(prop)); - } -#endif - } - } - else { - // puts("other id"); - } - - // printf("prop shortcut: '%s' (%s)\n", RNA_property_identifier(prop), data_path); - } - - /* We have a data-path! */ - bool found = false; - if (data_path || (prop_enum_value_ok && prop_enum_value_id)) { - /* Create a property to host the "data_path" property we're sending to the operators. */ - IDProperty *prop_path; - - const IDPropertyTemplate val = {0}; - prop_path = IDP_New(IDP_GROUP, &val, __func__); - if (data_path) { - IDP_AddToGroup(prop_path, IDP_NewString(data_path, "data_path", strlen(data_path) + 1)); - } - if (prop_enum_value_ok) { - const EnumPropertyItem *item; - bool free; - RNA_property_enum_items((bContext *)C, ptr, prop, &item, NULL, &free); - const int index = RNA_enum_from_value(item, prop_enum_value); - if (index != -1) { - IDProperty *prop_value; - if (prop_enum_value_is_int) { - const int value = item[index].value; - prop_value = IDP_New(IDP_INT, - &(IDPropertyTemplate){ - .i = value, - }, - prop_enum_value_id); - } - else { - const char *id = item[index].identifier; - prop_value = IDP_NewString(id, prop_enum_value_id, strlen(id) + 1); - } - IDP_AddToGroup(prop_path, prop_value); - } - else { - opnames_len = 0; /* Do nothing. */ - } - if (free) { - MEM_freeN((void *)item); - } - } - - /* check each until one works... */ - - for (int i = 0; (i < opnames_len) && (opnames[i]); i++) { - if (WM_key_event_operator_string( - C, opnames[i], WM_OP_INVOKE_REGION_WIN, prop_path, false, buf, buf_len)) { - found = true; - break; - } - } - - /* cleanup */ - IDP_FreeProperty(prop_path); - if (data_path) { - MEM_freeN(data_path); - } - } - - return found; -} - -/** \} */ - -/** - * This goes in a seemingly weird pattern: - * - *
- *     4
- *  5     6
- * 1       2
- *  7     8
- *     3
- * 
- * - * but it's actually quite logical. It's designed to be 'upwards compatible' - * for muscle memory so that the menu item locations are fixed and don't move - * as new items are added to the menu later on. It also optimizes efficiency - - * a radial menu is best kept symmetrical, with as large an angle between - * items as possible, so that the gestural mouse movements can be fast and inexact. - * - * It starts off with two opposite sides for the first two items - * then joined by the one below for the third (this way, even with three items, - * the menu seems to still be 'in order' reading left to right). Then the fourth is - * added to complete the compass directions. From here, it's just a matter of - * subdividing the rest of the angles for the last 4 items. - * - * --Matt 07/2006 - */ -const char ui_radial_dir_order[8] = { - UI_RADIAL_W, - UI_RADIAL_E, - UI_RADIAL_S, - UI_RADIAL_N, - UI_RADIAL_NW, - UI_RADIAL_NE, - UI_RADIAL_SW, - UI_RADIAL_SE, -}; - -const char ui_radial_dir_to_numpad[8] = {8, 9, 6, 3, 2, 1, 4, 7}; -const short ui_radial_dir_to_angle[8] = {90, 45, 0, 315, 270, 225, 180, 135}; - -static void ui_but_pie_direction_string(uiBut *but, char *buf, int size) -{ - BLI_assert(but->pie_dir < ARRAY_SIZE(ui_radial_dir_to_numpad)); - BLI_snprintf(buf, size, "%d", ui_radial_dir_to_numpad[but->pie_dir]); -} - -static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block) -{ - char buf[128]; - - BLI_assert(block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)); - - /* only do it before bounding */ - if (block->rect.xmin != block->rect.xmax) { - return; - } - if (STREQ(block->name, "splash")) { - return; - } - - if (block->flag & UI_BLOCK_RADIAL) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->pie_dir != UI_RADIAL_NONE) { - ui_but_pie_direction_string(but, buf, sizeof(buf)); - ui_but_add_shortcut(but, buf, false); - } - } - } - else { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (block->flag & UI_BLOCK_SHOW_SHORTCUT_ALWAYS) { - /* Skip icon-only buttons (as used in the toolbar). */ - if (but->drawstr[0] == '\0') { - continue; - } - if (((block->flag & UI_BLOCK_POPOVER) == 0) && UI_but_is_tool(but)) { - /* For non-popovers, shown in shortcut only - * (has special shortcut handling code). */ - continue; - } - } - else if (but->emboss != UI_EMBOSS_PULLDOWN) { - continue; - } - - if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { - ui_but_add_shortcut(but, buf, false); - } - else if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { - ui_but_add_shortcut(but, buf, false); - } - } - } -} - -void ui_but_override_flag(Main *bmain, uiBut *but) -{ - const uint override_status = RNA_property_override_library_status( - bmain, &but->rnapoin, but->rnaprop, but->rnaindex); - - if (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN) { - but->flag |= UI_BUT_OVERRIDDEN; - } - else { - but->flag &= ~UI_BUT_OVERRIDDEN; - } -} - -/* -------------------------------------------------------------------- */ -/** \name Button Extra Operator Icons - * - * Extra icons are shown on the right hand side of buttons. They can be clicked to invoke custom - * operators. - * There are some predefined here, which get added to buttons automatically based on button data - * (type, flags, state, etc). - * \{ */ - -/** - * Predefined types for generic extra operator icons (uiButExtraOpIcon). - */ -typedef enum PredefinedExtraOpIconType { - PREDEFINED_EXTRA_OP_ICON_NONE = 1, - PREDEFINED_EXTRA_OP_ICON_CLEAR, - PREDEFINED_EXTRA_OP_ICON_EYEDROPPER, -} PredefinedExtraOpIconType; - -static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but, - wmOperatorType *optype, - short opcontext, - int icon) -{ - uiButExtraOpIcon *extra_op_icon = MEM_mallocN(sizeof(*extra_op_icon), __func__); - - extra_op_icon->icon = (BIFIconID)icon; - extra_op_icon->optype_params = MEM_callocN(sizeof(*extra_op_icon->optype_params), - "uiButExtraOpIcon.optype_params"); - extra_op_icon->optype_params->optype = optype; - extra_op_icon->optype_params->opptr = MEM_callocN(sizeof(*extra_op_icon->optype_params->opptr), - "uiButExtraOpIcon.optype_params.opptr"); - WM_operator_properties_create_ptr(extra_op_icon->optype_params->opptr, - extra_op_icon->optype_params->optype); - extra_op_icon->optype_params->opcontext = opcontext; - extra_op_icon->highlighted = false; - - BLI_addtail(&but->extra_op_icons, extra_op_icon); - - return extra_op_icon->optype_params->opptr; -} - -static void ui_but_extra_operator_icon_free(uiButExtraOpIcon *extra_icon) -{ - WM_operator_properties_free(extra_icon->optype_params->opptr); - MEM_freeN(extra_icon->optype_params->opptr); - MEM_freeN(extra_icon->optype_params); - MEM_freeN(extra_icon); -} - -void ui_but_extra_operator_icons_free(uiBut *but) -{ - LISTBASE_FOREACH_MUTABLE (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { - ui_but_extra_operator_icon_free(op_icon); - } - BLI_listbase_clear(&but->extra_op_icons); -} - -PointerRNA *UI_but_extra_operator_icon_add(uiBut *but, - const char *opname, - short opcontext, - int icon) -{ - wmOperatorType *optype = WM_operatortype_find(opname, false); - - if (optype) { - return ui_but_extra_operator_icon_add_ptr(but, optype, opcontext, icon); - } - - return NULL; -} - -static bool ui_but_icon_extra_is_visible_text_clear(const uiBut *but) -{ - BLI_assert(but->type == UI_BTYPE_TEXT); - return ((but->flag & UI_BUT_VALUE_CLEAR) && but->drawstr[0]); -} - -static bool ui_but_icon_extra_is_visible_search_unlink(const uiBut *but) -{ - BLI_assert(ELEM(but->type, UI_BTYPE_SEARCH_MENU)); - return ((but->editstr == NULL) && (but->drawstr[0] != '\0') && (but->flag & UI_BUT_VALUE_CLEAR)); -} - -static bool ui_but_icon_extra_is_visible_search_eyedropper(uiBut *but) -{ - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU && (but->flag & UI_BUT_VALUE_CLEAR)); - - if (but->rnaprop == NULL) { - return false; - } - - StructRNA *type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); - const short idcode = RNA_type_to_ID_code(type); - - return ((but->editstr == NULL) && (idcode == ID_OB || OB_DATA_SUPPORT_ID(idcode))); -} - -static PredefinedExtraOpIconType ui_but_icon_extra_get(uiBut *but) -{ - switch (but->type) { - case UI_BTYPE_TEXT: - if (ui_but_icon_extra_is_visible_text_clear(but)) { - return PREDEFINED_EXTRA_OP_ICON_CLEAR; - } - break; - case UI_BTYPE_SEARCH_MENU: - if ((but->flag & UI_BUT_VALUE_CLEAR) == 0) { - /* pass */ - } - else if (ui_but_icon_extra_is_visible_search_unlink(but)) { - return PREDEFINED_EXTRA_OP_ICON_CLEAR; - } - else if (ui_but_icon_extra_is_visible_search_eyedropper(but)) { - return PREDEFINED_EXTRA_OP_ICON_EYEDROPPER; - } - break; - default: - break; - } - - return PREDEFINED_EXTRA_OP_ICON_NONE; -} - -/** - * While some extra operator icons have to be set explicitly upon button creating, this code adds - * some generic ones based on button data. Currently these are mutually exclusive, so there's only - * ever one predefined extra icon. - */ -static void ui_but_predefined_extra_operator_icons_add(uiBut *but) -{ - const PredefinedExtraOpIconType extra_icon = ui_but_icon_extra_get(but); - wmOperatorType *optype = NULL; - BIFIconID icon = ICON_NONE; - - switch (extra_icon) { - case PREDEFINED_EXTRA_OP_ICON_EYEDROPPER: { - static wmOperatorType *id_eyedropper_ot = NULL; - if (!id_eyedropper_ot) { - id_eyedropper_ot = WM_operatortype_find("UI_OT_eyedropper_id", false); - } - BLI_assert(id_eyedropper_ot); - - optype = id_eyedropper_ot; - icon = ICON_EYEDROPPER; - - break; - } - case PREDEFINED_EXTRA_OP_ICON_CLEAR: { - static wmOperatorType *clear_ot = NULL; - if (!clear_ot) { - clear_ot = WM_operatortype_find("UI_OT_button_string_clear", false); - } - BLI_assert(clear_ot); - - optype = clear_ot; - icon = ICON_PANEL_CLOSE; - - break; - } - default: - break; - } - - if (optype) { - LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { - if ((op_icon->optype_params->optype == optype) && (op_icon->icon == icon)) { - /* Don't add the same operator icon twice (happens if button is kept alive while active). - */ - return; - } - } - ui_but_extra_operator_icon_add_ptr(but, optype, WM_OP_INVOKE_DEFAULT, (int)icon); - } -} - -/** \} */ - -void UI_block_update_from_old(const bContext *C, uiBlock *block) -{ - if (!block->oldblock) { - return; - } - - uiBut *but_old = block->oldblock->buttons.first; - - if (BLI_listbase_is_empty(&block->oldblock->butstore) == false) { - UI_butstore_update(block); - } - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (ui_but_update_from_old_block(C, block, &but, &but_old)) { - ui_but_update(but); - - /* redraw dynamic tooltip if we have one open */ - if (but->tip_func) { - UI_but_tooltip_refresh((bContext *)C, but); - } - } - } - - block->auto_open = block->oldblock->auto_open; - block->auto_open_last = block->oldblock->auto_open_last; - block->tooltipdisabled = block->oldblock->tooltipdisabled; - BLI_movelisttolist(&block->color_pickers.list, &block->oldblock->color_pickers.list); - - block->oldblock = NULL; -} - -#ifndef NDEBUG -/** - * Extra sanity checks for invariants (debug builds only). - */ -static void ui_but_validate(const uiBut *but) -{ - /* Number buttons must have a click-step, - * assert instead of correcting the value to ensure the caller knows what they're doing. */ - if (but->type == UI_BTYPE_NUM) { - uiButNumber *number_but = (uiButNumber *)but; - - if (ELEM(but->pointype, UI_BUT_POIN_CHAR, UI_BUT_POIN_SHORT, UI_BUT_POIN_INT)) { - BLI_assert((int)number_but->step_size > 0); - } - } -} -#endif - -/** - * Check if the operator \a ot poll is successful with the context given by \a but (optionally). - * \param but: The button that might store context. Can be NULL for convenience (e.g. if there is - * no button to take context from, but we still want to poll the operator). - */ -bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but) -{ - bool result; - int opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT; - - if (but && but->context) { - CTX_store_set(C, but->context); - } - - result = WM_operator_poll_context(C, ot, opcontext); - - if (but && but->context) { - CTX_store_set(C, NULL); - } - - return result; -} - -void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2]) -{ - wmWindow *window = CTX_wm_window(C); - Scene *scene = CTX_data_scene(C); - ARegion *region = CTX_wm_region(C); - Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - - BLI_assert(block->active); - - /* Extend button data. This needs to be done before the block updating. */ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - ui_but_predefined_extra_operator_icons_add(but); - } - - UI_block_update_from_old(C, block); - - /* inherit flags from 'old' buttons that was drawn here previous, based - * on matching buttons, we need this to make button event handling non - * blocking, while still allowing buttons to be remade each redraw as it - * is expected by blender code */ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - /* temp? Proper check for graying out */ - if (but->optype) { - wmOperatorType *ot = but->optype; - - if (ot == NULL || !ui_but_context_poll_operator((bContext *)C, ot, but)) { - but->flag |= UI_BUT_DISABLED; - } - } - - const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( - depsgraph, (scene) ? scene->r.cfra : 0.0f); - ui_but_anim_flag(but, &anim_eval_context); - ui_but_override_flag(CTX_data_main(C), but); - if (UI_but_is_decorator(but)) { - ui_but_anim_decorate_update_from_flag((uiButDecorator *)but); - } - -#ifndef NDEBUG - ui_but_validate(but); -#endif - } - - /* handle pending stuff */ - if (block->layouts.first) { - UI_block_layout_resolve(block, NULL, NULL); - } - ui_block_align_calc(block, CTX_wm_region(C)); - if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT)) { - ui_menu_block_set_keyaccels(block); /* could use a different flag to check */ - } - - if (block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)) { - ui_menu_block_set_keymaps(C, block); - } - - /* after keymaps! */ - switch (block->bounds_type) { - case UI_BLOCK_BOUNDS_NONE: - break; - case UI_BLOCK_BOUNDS: - ui_block_bounds_calc(block); - break; - case UI_BLOCK_BOUNDS_TEXT: - ui_block_bounds_calc_text(block, 0.0f); - break; - case UI_BLOCK_BOUNDS_POPUP_CENTER: - ui_block_bounds_calc_centered(window, block); - break; - case UI_BLOCK_BOUNDS_PIE_CENTER: - ui_block_bounds_calc_centered_pie(block); - break; - - /* fallback */ - case UI_BLOCK_BOUNDS_POPUP_MOUSE: - case UI_BLOCK_BOUNDS_POPUP_MENU: - ui_block_bounds_calc_popup(window, block, block->bounds_type, xy, r_xy); - break; - } - - if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { - UI_block_bounds_set_normal(block, 0); - } - if (block->flag & UI_BUT_ALIGN) { - UI_block_align_end(block); - } - - ui_update_flexible_spacing(region, block); - - block->endblock = true; -} - -void UI_block_end(const bContext *C, uiBlock *block) -{ - wmWindow *window = CTX_wm_window(C); - - UI_block_end_ex(C, block, &window->eventstate->x, NULL); -} - -/* ************** BLOCK DRAWING FUNCTION ************* */ - -void ui_fontscale(short *points, float aspect) -{ - if (aspect < 0.9f || aspect > 1.1f) { - float pointsf = *points; - - /* for some reason scaling fonts goes too fast compared to widget size */ - /* XXX not true anymore? (ton) */ - // aspect = sqrt(aspect); - pointsf /= aspect; - - if (aspect > 1.0f) { - *points = ceilf(pointsf); - } - else { - *points = floorf(pointsf); - } - } -} - -/* Project button or block (but==NULL) to pixels in region-space. */ -static void ui_but_to_pixelrect(rcti *rect, const ARegion *region, uiBlock *block, uiBut *but) -{ - rctf rectf; - - ui_block_to_window_rctf(region, block, &rectf, (but) ? &but->rect : &block->rect); - BLI_rcti_rctf_copy_round(rect, &rectf); - BLI_rcti_translate(rect, -region->winrct.xmin, -region->winrct.ymin); -} - -/* uses local copy of style, to scale things down, and allow widgets to change stuff */ -void UI_block_draw(const bContext *C, uiBlock *block) -{ - uiStyle style = *UI_style_get_dpi(); /* XXX pass on as arg */ - - /* get menu region or area region */ - ARegion *region = CTX_wm_menu(C); - if (!region) { - region = CTX_wm_region(C); - } - - if (!block->endblock) { - UI_block_end(C, block); - } - - /* we set this only once */ - GPU_blend(GPU_BLEND_ALPHA); - - /* scale fonts */ - ui_fontscale(&style.paneltitle.points, block->aspect); - ui_fontscale(&style.grouplabel.points, block->aspect); - ui_fontscale(&style.widgetlabel.points, block->aspect); - ui_fontscale(&style.widget.points, block->aspect); - - /* scale block min/max to rect */ - rcti rect; - ui_but_to_pixelrect(&rect, region, block, NULL); - - /* pixel space for AA widgets */ - GPU_matrix_push_projection(); - GPU_matrix_push(); - GPU_matrix_identity_set(); - - wmOrtho2_region_pixelspace(region); - - /* back */ - if (block->flag & UI_BLOCK_RADIAL) { - ui_draw_pie_center(block); - } - else if (block->flag & UI_BLOCK_POPOVER) { - ui_draw_popover_back(region, &style, block, &rect); - } - else if (block->flag & UI_BLOCK_LOOP) { - ui_draw_menu_back(&style, block, &rect); - } - else if (block->panel) { - bool show_background = region->alignment != RGN_ALIGN_FLOAT; - if (show_background) { - if (block->panel->type && (block->panel->type->flag & PANEL_TYPE_NO_HEADER)) { - if (region->regiontype == RGN_TYPE_TOOLS) { - /* We never want a background around active tools. */ - show_background = false; - } - else { - /* Without a header there is no background except for region overlap. */ - show_background = region->overlap != 0; - } - } - } - ui_draw_aligned_panel(&style, - block, - &rect, - UI_panel_category_is_visible(region), - show_background, - region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE); - } - - BLF_batch_draw_begin(); - UI_icon_draw_cache_begin(); - UI_widgetbase_draw_cache_begin(); - - /* widgets */ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (!(but->flag & (UI_HIDDEN | UI_SCROLLED))) { - ui_but_to_pixelrect(&rect, region, block, but); - - /* XXX: figure out why invalid coordinates happen when closing render window */ - /* and material preview is redrawn in main window (temp fix for bug T23848) */ - if (rect.xmin < rect.xmax && rect.ymin < rect.ymax) { - ui_draw_but(C, region, &style, but, &rect); - } - } - } - - UI_widgetbase_draw_cache_end(); - UI_icon_draw_cache_end(); - BLF_batch_draw_end(); - - /* restore matrix */ - GPU_matrix_pop_projection(); - GPU_matrix_pop(); -} - -static void ui_block_message_subscribe(ARegion *region, struct wmMsgBus *mbus, uiBlock *block) -{ - uiBut *but_prev = NULL; - /* possibly we should keep the region this block is contained in? */ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->rnapoin.type && but->rnaprop) { - /* quick check to avoid adding buttons representing a vector, multiple times. */ - if ((but_prev && (but_prev->rnaprop == but->rnaprop) && - (but_prev->rnapoin.type == but->rnapoin.type) && - (but_prev->rnapoin.data == but->rnapoin.data) && - (but_prev->rnapoin.owner_id == but->rnapoin.owner_id)) == false) { - /* TODO: could make this into utility function. */ - WM_msg_subscribe_rna(mbus, - &but->rnapoin, - but->rnaprop, - &(const wmMsgSubscribeValue){ - .owner = region, - .user_data = region, - .notify = ED_region_do_msg_notify_tag_redraw, - }, - __func__); - but_prev = but; - } - } - } -} - -void UI_region_message_subscribe(ARegion *region, struct wmMsgBus *mbus) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - ui_block_message_subscribe(region, mbus, block); - } -} - -/* ************* EVENTS ************* */ - -/** - * Check if the button is pushed, this is only meaningful for some button types. - * - * \return (0 == UNSELECT), (1 == SELECT), (-1 == DO-NOTHING) - */ -int ui_but_is_pushed_ex(uiBut *but, double *value) -{ - int is_push = 0; - - if (but->bit) { - const bool state = !ELEM( - but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N); - int lvalue; - UI_GET_BUT_VALUE_INIT(but, *value); - lvalue = (int)*value; - if (UI_BITBUT_TEST(lvalue, (but->bitnr))) { - is_push = state; - } - else { - is_push = !state; - } - } - else { - switch (but->type) { - case UI_BTYPE_BUT: - case UI_BTYPE_HOTKEY_EVENT: - case UI_BTYPE_KEY_EVENT: - case UI_BTYPE_COLOR: - case UI_BTYPE_DECORATOR: - is_push = -1; - break; - case UI_BTYPE_BUT_TOGGLE: - case UI_BTYPE_TOGGLE: - case UI_BTYPE_ICON_TOGGLE: - case UI_BTYPE_CHECKBOX: - UI_GET_BUT_VALUE_INIT(but, *value); - if (*value != (double)but->hardmin) { - is_push = true; - } - break; - case UI_BTYPE_ICON_TOGGLE_N: - case UI_BTYPE_TOGGLE_N: - case UI_BTYPE_CHECKBOX_N: - UI_GET_BUT_VALUE_INIT(but, *value); - if (*value == 0.0) { - is_push = true; - } - break; - case UI_BTYPE_ROW: - case UI_BTYPE_LISTROW: - case UI_BTYPE_TAB: - if ((but->type == UI_BTYPE_TAB) && but->rnaprop && but->custom_data) { - /* uiBut.custom_data points to data this tab represents (e.g. workspace). - * uiBut.rnapoin/prop store an active value (e.g. active workspace). */ - if (RNA_property_type(but->rnaprop) == PROP_POINTER) { - const PointerRNA active_ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); - if (active_ptr.data == but->custom_data) { - is_push = true; - } - } - break; - } - else if (but->optype) { - break; - } - - UI_GET_BUT_VALUE_INIT(but, *value); - /* support for rna enum buts */ - if (but->rnaprop && (RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG)) { - if ((int)*value & (int)but->hardmax) { - is_push = true; - } - } - else { - if (*value == (double)but->hardmax) { - is_push = true; - } - } - break; - default: - is_push = -1; - break; - } - } - - if ((but->drawflag & UI_BUT_CHECKBOX_INVERT) && (is_push != -1)) { - is_push = !((bool)is_push); - } - return is_push; -} -int ui_but_is_pushed(uiBut *but) -{ - double value = UI_BUT_VALUE_UNSET; - return ui_but_is_pushed_ex(but, &value); -} - -static void ui_but_update_select_flag(uiBut *but, double *value) -{ - switch (ui_but_is_pushed_ex(but, value)) { - case true: - but->flag |= UI_SELECT; - break; - case false: - but->flag &= ~UI_SELECT; - break; - } -} - -/* ************************************************ */ - -void UI_block_lock_set(uiBlock *block, bool val, const char *lockstr) -{ - if (val) { - block->lock = val; - block->lockstr = lockstr; - } -} - -void UI_block_lock_clear(uiBlock *block) -{ - block->lock = false; - block->lockstr = NULL; -} - -/* *********************** data get/set *********************** - * this either works with the pointed to data, or can work with - * an edit override pointer while dragging for example */ - -/* for buttons pointing to color for example */ -void ui_but_v3_get(uiBut *but, float vec[3]) -{ - if (but->editvec) { - copy_v3_v3(vec, but->editvec); - } - - if (but->rnaprop) { - PropertyRNA *prop = but->rnaprop; - - zero_v3(vec); - - if (RNA_property_type(prop) == PROP_FLOAT) { - int tot = RNA_property_array_length(&but->rnapoin, prop); - BLI_assert(tot > 0); - if (tot == 3) { - RNA_property_float_get_array(&but->rnapoin, prop, vec); - } - else { - tot = min_ii(tot, 3); - for (int a = 0; a < tot; a++) { - vec[a] = RNA_property_float_get_index(&but->rnapoin, prop, a); - } - } - } - } - else if (but->pointype == UI_BUT_POIN_CHAR) { - const char *cp = (char *)but->poin; - - vec[0] = ((float)cp[0]) / 255.0f; - vec[1] = ((float)cp[1]) / 255.0f; - vec[2] = ((float)cp[2]) / 255.0f; - } - else if (but->pointype == UI_BUT_POIN_FLOAT) { - const float *fp = (float *)but->poin; - copy_v3_v3(vec, fp); - } - else { - if (but->editvec == NULL) { - fprintf(stderr, "%s: can't get color, should never happen\n", __func__); - zero_v3(vec); - } - } - - if (but->type == UI_BTYPE_UNITVEC) { - normalize_v3(vec); - } -} - -/* for buttons pointing to color for example */ -void ui_but_v3_set(uiBut *but, const float vec[3]) -{ - if (but->editvec) { - copy_v3_v3(but->editvec, vec); - } - - if (but->rnaprop) { - PropertyRNA *prop = but->rnaprop; - - if (RNA_property_type(prop) == PROP_FLOAT) { - int tot; - int a; - - tot = RNA_property_array_length(&but->rnapoin, prop); - BLI_assert(tot > 0); - if (tot == 3) { - RNA_property_float_set_array(&but->rnapoin, prop, vec); - } - else { - tot = min_ii(tot, 3); - for (a = 0; a < tot; a++) { - RNA_property_float_set_index(&but->rnapoin, prop, a, vec[a]); - } - } - } - } - else if (but->pointype == UI_BUT_POIN_CHAR) { - char *cp = (char *)but->poin; - cp[0] = (char)(0.5f + vec[0] * 255.0f); - cp[1] = (char)(0.5f + vec[1] * 255.0f); - cp[2] = (char)(0.5f + vec[2] * 255.0f); - } - else if (but->pointype == UI_BUT_POIN_FLOAT) { - float *fp = (float *)but->poin; - copy_v3_v3(fp, vec); - } -} - -bool ui_but_is_float(const uiBut *but) -{ - if (but->pointype == UI_BUT_POIN_FLOAT && but->poin) { - return true; - } - - if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_FLOAT) { - return true; - } - - return false; -} - -bool ui_but_is_bool(const uiBut *but) -{ - if (ELEM(but->type, - UI_BTYPE_TOGGLE, - UI_BTYPE_TOGGLE_N, - UI_BTYPE_ICON_TOGGLE, - UI_BTYPE_ICON_TOGGLE_N, - UI_BTYPE_TAB)) { - return true; - } - - if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_BOOLEAN) { - return true; - } - - if ((but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) && - (but->type == UI_BTYPE_ROW)) { - return true; - } - - return false; -} - -bool ui_but_is_unit(const uiBut *but) -{ - UnitSettings *unit = but->block->unit; - const int unit_type = UI_but_unit_type_get(but); - - if (unit_type == PROP_UNIT_NONE) { - return false; - } - -#if 1 /* removed so angle buttons get correct snapping */ - if (ui_but_is_unit_radians_ex(unit, unit_type)) { - return false; - } -#endif - - /* for now disable time unit conversion */ - if (unit_type == PROP_UNIT_TIME) { - return false; - } - - if (unit->system == USER_UNIT_NONE) { - if (unit_type != PROP_UNIT_ROTATION) { - return false; - } - } - - return true; -} - -/** - * Check if this button is similar enough to be grouped with another. - */ -bool ui_but_is_compatible(const uiBut *but_a, const uiBut *but_b) -{ - if (but_a->type != but_b->type) { - return false; - } - if (but_a->pointype != but_b->pointype) { - return false; - } - - if (but_a->rnaprop) { - /* skip 'rnapoin.data', 'rnapoin.owner_id' - * allow different data to have the same props edited at once */ - if (but_a->rnapoin.type != but_b->rnapoin.type) { - return false; - } - if (RNA_property_type(but_a->rnaprop) != RNA_property_type(but_b->rnaprop)) { - return false; - } - if (RNA_property_subtype(but_a->rnaprop) != RNA_property_subtype(but_b->rnaprop)) { - return false; - } - } - - return true; -} - -bool ui_but_is_rna_valid(uiBut *but) -{ - if (but->rnaprop == NULL || RNA_struct_contains_property(&but->rnapoin, but->rnaprop)) { - return true; - } - printf("property removed %s: %p\n", but->drawstr, but->rnaprop); - return false; -} - -/** - * Checks if the button supports cycling next/previous menu items (ctrl+mouse-wheel). - */ -bool ui_but_supports_cycling(const uiBut *but) -{ - return ((ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_LISTBOX)) || - (but->type == UI_BTYPE_MENU && ui_but_menu_step_poll(but)) || - (but->type == UI_BTYPE_COLOR && ((uiButColor *)but)->is_pallete_color) || - (but->menu_step_func != NULL)); -} - -double ui_but_value_get(uiBut *but) -{ - double value = 0.0; - - if (but->editval) { - return *(but->editval); - } - if (but->poin == NULL && but->rnapoin.data == NULL) { - return 0.0; - } - - if (but->rnaprop) { - PropertyRNA *prop = but->rnaprop; - - BLI_assert(but->rnaindex != -1); - - switch (RNA_property_type(prop)) { - case PROP_BOOLEAN: - if (RNA_property_array_check(prop)) { - value = RNA_property_boolean_get_index(&but->rnapoin, prop, but->rnaindex); - } - else { - value = RNA_property_boolean_get(&but->rnapoin, prop); - } - break; - case PROP_INT: - if (RNA_property_array_check(prop)) { - value = RNA_property_int_get_index(&but->rnapoin, prop, but->rnaindex); - } - else { - value = RNA_property_int_get(&but->rnapoin, prop); - } - break; - case PROP_FLOAT: - if (RNA_property_array_check(prop)) { - value = RNA_property_float_get_index(&but->rnapoin, prop, but->rnaindex); - } - else { - value = RNA_property_float_get(&but->rnapoin, prop); - } - break; - case PROP_ENUM: - value = RNA_property_enum_get(&but->rnapoin, prop); - break; - default: - value = 0.0; - break; - } - } - else if (but->pointype == UI_BUT_POIN_CHAR) { - value = *(char *)but->poin; - } - else if (but->pointype == UI_BUT_POIN_SHORT) { - value = *(short *)but->poin; - } - else if (but->pointype == UI_BUT_POIN_INT) { - value = *(int *)but->poin; - } - else if (but->pointype == UI_BUT_POIN_FLOAT) { - value = *(float *)but->poin; - } - - return value; -} - -void ui_but_value_set(uiBut *but, double value) -{ - /* value is a hsv value: convert to rgb */ - if (but->rnaprop) { - PropertyRNA *prop = but->rnaprop; - - if (RNA_property_editable(&but->rnapoin, prop)) { - switch (RNA_property_type(prop)) { - case PROP_BOOLEAN: - if (RNA_property_array_check(prop)) { - RNA_property_boolean_set_index(&but->rnapoin, prop, but->rnaindex, value); - } - else { - RNA_property_boolean_set(&but->rnapoin, prop, value); - } - break; - case PROP_INT: - if (RNA_property_array_check(prop)) { - RNA_property_int_set_index(&but->rnapoin, prop, but->rnaindex, (int)value); - } - else { - RNA_property_int_set(&but->rnapoin, prop, (int)value); - } - break; - case PROP_FLOAT: - if (RNA_property_array_check(prop)) { - RNA_property_float_set_index(&but->rnapoin, prop, but->rnaindex, value); - } - else { - RNA_property_float_set(&but->rnapoin, prop, value); - } - break; - case PROP_ENUM: - if (RNA_property_flag(prop) & PROP_ENUM_FLAG) { - int ivalue = (int)value; - /* toggle for enum/flag buttons */ - ivalue ^= RNA_property_enum_get(&but->rnapoin, prop); - RNA_property_enum_set(&but->rnapoin, prop, ivalue); - } - else { - RNA_property_enum_set(&but->rnapoin, prop, value); - } - break; - default: - break; - } - } - - /* we can't be sure what RNA set functions actually do, - * so leave this unset */ - value = UI_BUT_VALUE_UNSET; - } - else if (but->pointype == 0) { - /* pass */ - } - else { - /* first do rounding */ - if (but->pointype == UI_BUT_POIN_CHAR) { - value = round_db_to_uchar_clamp(value); - } - else if (but->pointype == UI_BUT_POIN_SHORT) { - value = round_db_to_short_clamp(value); - } - else if (but->pointype == UI_BUT_POIN_INT) { - value = round_db_to_int_clamp(value); - } - else if (but->pointype == UI_BUT_POIN_FLOAT) { - float fval = (float)value; - if (fval >= -0.00001f && fval <= 0.00001f) { - /* prevent negative zero */ - fval = 0.0f; - } - value = fval; - } - - /* then set value with possible edit override */ - if (but->editval) { - value = *but->editval = value; - } - else if (but->pointype == UI_BUT_POIN_CHAR) { - value = *((char *)but->poin) = (char)value; - } - else if (but->pointype == UI_BUT_POIN_SHORT) { - value = *((short *)but->poin) = (short)value; - } - else if (but->pointype == UI_BUT_POIN_INT) { - value = *((int *)but->poin) = (int)value; - } - else if (but->pointype == UI_BUT_POIN_FLOAT) { - value = *((float *)but->poin) = (float)value; - } - } - - ui_but_update_select_flag(but, &value); -} - -int ui_but_string_get_max_length(uiBut *but) -{ - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - return but->hardmax; - } - return UI_MAX_DRAW_STR; -} - -uiBut *ui_but_drag_multi_edit_get(uiBut *but) -{ - uiBut *return_but = NULL; - - BLI_assert(but->flag & UI_BUT_DRAG_MULTI); - - LISTBASE_FOREACH (uiBut *, but_iter, &but->block->buttons) { - if (but_iter->editstr) { - return_but = but_iter; - break; - } - } - - return return_but; -} - -static double ui_get_but_scale_unit(uiBut *but, double value) -{ - UnitSettings *unit = but->block->unit; - const int unit_type = UI_but_unit_type_get(but); - - /* Time unit is a bit special, not handled by BKE_scene_unit_scale() for now. */ - if (unit_type == PROP_UNIT_TIME) { /* WARNING - using evil_C :| */ - Scene *scene = CTX_data_scene(but->block->evil_C); - return FRA2TIME(value); - } - return BKE_scene_unit_scale(unit, RNA_SUBTYPE_UNIT_VALUE(unit_type), value); -} - -/* str will be overwritten */ -void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t maxlen) -{ - if (!ui_but_is_unit(but)) { - return; - } - - UnitSettings *unit = but->block->unit; - const int unit_type = UI_but_unit_type_get(but); - char *orig_str; - - orig_str = BLI_strdup(str); - - BKE_unit_name_to_alt(str, maxlen, orig_str, unit->system, RNA_SUBTYPE_UNIT_VALUE(unit_type)); - - MEM_freeN(orig_str); -} - -/** - * \param float_precision: Override the button precision. - */ -static void ui_get_but_string_unit( - uiBut *but, char *str, int len_max, double value, bool pad, int float_precision) -{ - UnitSettings *unit = but->block->unit; - const int unit_type = UI_but_unit_type_get(but); - int precision; - - if (unit->scale_length < 0.0001f) { - unit->scale_length = 1.0f; /* XXX do_versions */ - } - - /* Use precision override? */ - if (float_precision == -1) { - /* Sanity checks */ - precision = (int)ui_but_get_float_precision(but); - if (precision > UI_PRECISION_FLOAT_MAX) { - precision = UI_PRECISION_FLOAT_MAX; - } - else if (precision == -1) { - precision = 2; - } - } - else { - precision = float_precision; - } - - BKE_unit_value_as_string(str, - len_max, - ui_get_but_scale_unit(but, value), - precision, - RNA_SUBTYPE_UNIT_VALUE(unit_type), - unit, - pad); -} - -static float ui_get_but_step_unit(uiBut *but, float step_default) -{ - const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); - const double step_orig = step_default * UI_PRECISION_FLOAT_SCALE; - /* Scaling up 'step_origg ' here is a bit arbitrary, - * its just giving better scales from user POV */ - const double scale_step = ui_get_but_scale_unit(but, step_orig * 10); - const double step = BKE_unit_closest_scalar(scale_step, but->block->unit->system, unit_type); - - /* -1 is an error value */ - if (step == -1.0f) { - return step_default; - } - - const double scale_unit = ui_get_but_scale_unit(but, 1.0); - const double step_unit = BKE_unit_closest_scalar( - scale_unit, but->block->unit->system, unit_type); - double step_final; - - BLI_assert(step > 0.0); - - step_final = (step / scale_unit) / (double)UI_PRECISION_FLOAT_SCALE; - - if (step == step_unit) { - /* Logic here is to scale by the original 'step_orig' - * only when the unit step matches the scaled step. - * - * This is needed for units that don't have a wide range of scales (degrees for eg.). - * Without this we can't select between a single degree, or a 10th of a degree. - */ - step_final *= step_orig; - } - - return (float)step_final; -} - -/** - * \param float_precision: For number buttons the precision - * to use or -1 to fallback to the button default. - * \param use_exp_float: Use exponent representation of floats - * when out of reasonable range (outside of 1e3/1e-3). - */ -void ui_but_string_get_ex(uiBut *but, - char *str, - const size_t maxlen, - const int float_precision, - const bool use_exp_float, - bool *r_use_exp_float) -{ - if (r_use_exp_float) { - *r_use_exp_float = false; - } - - if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU, UI_BTYPE_TAB)) { - const PropertyType type = RNA_property_type(but->rnaprop); - - int buf_len; - const char *buf = NULL; - if ((but->type == UI_BTYPE_TAB) && (but->custom_data)) { - StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); - PointerRNA ptr; - - /* uiBut.custom_data points to data this tab represents (e.g. workspace). - * uiBut.rnapoin/prop store an active value (e.g. active workspace). */ - RNA_pointer_create(but->rnapoin.owner_id, ptr_type, but->custom_data, &ptr); - buf = RNA_struct_name_get_alloc(&ptr, str, maxlen, &buf_len); - } - else if (type == PROP_STRING) { - /* RNA string */ - buf = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, str, maxlen, &buf_len); - } - else if (type == PROP_ENUM) { - /* RNA enum */ - const int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); - if (RNA_property_enum_name(but->block->evil_C, &but->rnapoin, but->rnaprop, value, &buf)) { - BLI_strncpy(str, buf, maxlen); - buf = str; - } - } - else if (type == PROP_POINTER) { - /* RNA pointer */ - PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); - buf = RNA_struct_name_get_alloc(&ptr, str, maxlen, &buf_len); - } - else { - BLI_assert(0); - } - - if (buf == NULL) { - str[0] = '\0'; - } - else if (buf != str) { - BLI_assert(maxlen <= buf_len + 1); - /* string was too long, we have to truncate */ - if (UI_but_is_utf8(but)) { - BLI_strncpy_utf8(str, buf, maxlen); - } - else { - BLI_strncpy(str, buf, maxlen); - } - MEM_freeN((void *)buf); - } - } - else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - /* string */ - BLI_strncpy(str, but->poin, maxlen); - return; - } - else if (ui_but_anim_expression_get(but, str, maxlen)) { - /* driver expression */ - } - else { - /* number editing */ - const double value = ui_but_value_get(but); - - PropertySubType subtype = PROP_NONE; - if (but->rnaprop) { - subtype = RNA_property_subtype(but->rnaprop); - } - - if (ui_but_is_float(but)) { - int prec = (float_precision == -1) ? ui_but_calc_float_precision(but, value) : - float_precision; - - if (ui_but_is_unit(but)) { - ui_get_but_string_unit(but, str, maxlen, value, false, prec); - } - else if (subtype == PROP_FACTOR) { - if (U.factor_display_type == USER_FACTOR_AS_FACTOR) { - BLI_snprintf(str, maxlen, "%.*f", prec, value); - } - else { - BLI_snprintf(str, maxlen, "%.*f", MAX2(0, prec - 2), value * 100); - } - } - else { - const int int_digits_num = integer_digits_f(value); - if (use_exp_float) { - if (int_digits_num < -6 || int_digits_num > 12) { - BLI_snprintf(str, maxlen, "%.*g", prec, value); - if (r_use_exp_float) { - *r_use_exp_float = true; - } - } - else { - prec -= int_digits_num; - CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); - BLI_snprintf(str, maxlen, "%.*f", prec, value); - } - } - else { - prec -= int_digits_num; - CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); - BLI_snprintf(str, maxlen, "%.*f", prec, value); - } - } - } - else { - BLI_snprintf(str, maxlen, "%d", (int)value); - } - } -} -void ui_but_string_get(uiBut *but, char *str, const size_t maxlen) -{ - ui_but_string_get_ex(but, str, maxlen, -1, false, NULL); -} - -/** - * A version of #ui_but_string_get_ex for dynamic buffer sizes - * (where #ui_but_string_get_max_length returns 0). - * - * \param r_str_size: size of the returned string (including terminator). - */ -char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size) -{ - char *str = NULL; - *r_str_size = 1; - - if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - const PropertyType type = RNA_property_type(but->rnaprop); - - if (type == PROP_STRING) { - /* RNA string */ - str = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, NULL, 0, r_str_size); - (*r_str_size) += 1; - } - else if (type == PROP_ENUM) { - /* RNA enum */ - const int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); - const char *value_id; - if (!RNA_property_enum_name( - but->block->evil_C, &but->rnapoin, but->rnaprop, value, &value_id)) { - value_id = ""; - } - - *r_str_size = strlen(value_id) + 1; - str = BLI_strdupn(value_id, *r_str_size); - } - else if (type == PROP_POINTER) { - /* RNA pointer */ - PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); - str = RNA_struct_name_get_alloc(&ptr, NULL, 0, r_str_size); - (*r_str_size) += 1; - } - else { - BLI_assert(0); - } - } - else { - BLI_assert(0); - } - - if (UNLIKELY(str == NULL)) { - /* should never happen, paranoid check */ - *r_str_size = 1; - str = BLI_strdup(""); - BLI_assert(0); - } - - return str; -} - -/** - * Report a generic error prefix when evaluating a string with #BPY_run_string_as_number - * as the Python error on its own doesn't provide enough context. - */ -#define UI_NUMBER_EVAL_ERROR_PREFIX IFACE_("Error evaluating number, see Info editor for details") - -static bool ui_number_from_string_units( - bContext *C, const char *str, const int unit_type, const UnitSettings *unit, double *r_value) -{ - char *error = NULL; - const bool ok = user_string_to_number(C, str, unit, unit_type, r_value, true, &error); - if (error) { - ReportList *reports = CTX_wm_reports(C); - BKE_reportf(reports, RPT_ERROR, "%s: %s", UI_NUMBER_EVAL_ERROR_PREFIX, error); - MEM_freeN(error); - } - return ok; -} - -static bool ui_number_from_string_units_with_but(bContext *C, - const char *str, - const uiBut *but, - double *r_value) -{ - const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); - const UnitSettings *unit = but->block->unit; - return ui_number_from_string_units(C, str, unit_type, unit, r_value); -} - -static bool ui_number_from_string(bContext *C, const char *str, double *r_value) -{ - bool ok; -#ifdef WITH_PYTHON - struct BPy_RunErrInfo err_info = { - .reports = CTX_wm_reports(C), - .report_prefix = UI_NUMBER_EVAL_ERROR_PREFIX, - }; - ok = BPY_run_string_as_number(C, NULL, str, &err_info, r_value); -#else - UNUSED_VARS(C); - *r_value = atof(str); - ok = true; -#endif - return ok; -} - -static bool ui_number_from_string_factor(bContext *C, const char *str, double *r_value) -{ - const int len = strlen(str); - if (BLI_strn_endswith(str, "%", len)) { - char *str_new = BLI_strdupn(str, len - 1); - const bool success = ui_number_from_string(C, str_new, r_value); - MEM_freeN(str_new); - *r_value /= 100.0; - return success; - } - if (!ui_number_from_string(C, str, r_value)) { - return false; - } - if (U.factor_display_type == USER_FACTOR_AS_PERCENTAGE) { - *r_value /= 100.0; - } - return true; -} - -static bool ui_number_from_string_percentage(bContext *C, const char *str, double *r_value) -{ - const int len = strlen(str); - if (BLI_strn_endswith(str, "%", len)) { - char *str_new = BLI_strdupn(str, len - 1); - const bool success = ui_number_from_string(C, str_new, r_value); - MEM_freeN(str_new); - return success; - } - return ui_number_from_string(C, str, r_value); -} - -bool ui_but_string_eval_number(bContext *C, const uiBut *but, const char *str, double *r_value) -{ - if (str[0] == '\0') { - *r_value = 0.0; - return true; - } - - PropertySubType subtype = PROP_NONE; - if (but->rnaprop) { - subtype = RNA_property_subtype(but->rnaprop); - } - - if (ui_but_is_float(but)) { - if (ui_but_is_unit(but)) { - return ui_number_from_string_units_with_but(C, str, but, r_value); - } - if (subtype == PROP_FACTOR) { - return ui_number_from_string_factor(C, str, r_value); - } - if (subtype == PROP_PERCENTAGE) { - return ui_number_from_string_percentage(C, str, r_value); - } - return ui_number_from_string(C, str, r_value); - } - return ui_number_from_string(C, str, r_value); -} - -/* just the assignment/free part */ -static void ui_but_string_set_internal(uiBut *but, const char *str, size_t str_len) -{ - BLI_assert(str_len == strlen(str)); - BLI_assert(but->str == NULL); - str_len += 1; - - if (str_len > UI_MAX_NAME_STR) { - but->str = MEM_mallocN(str_len, "ui_def_but str"); - } - else { - but->str = but->strdata; - } - memcpy(but->str, str, str_len); -} - -static void ui_but_string_free_internal(uiBut *but) -{ - if (but->str) { - if (but->str != but->strdata) { - MEM_freeN(but->str); - } - /* must call 'ui_but_string_set_internal' after */ - but->str = NULL; - } -} - -bool ui_but_string_set(bContext *C, uiBut *but, const char *str) -{ - if (but->rnaprop && but->rnapoin.data && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - if (RNA_property_editable(&but->rnapoin, but->rnaprop)) { - const PropertyType type = RNA_property_type(but->rnaprop); - - if (type == PROP_STRING) { - /* RNA string */ - RNA_property_string_set(&but->rnapoin, but->rnaprop, str); - return true; - } - - if (type == PROP_POINTER) { - if (str[0] == '\0') { - RNA_property_pointer_set(&but->rnapoin, but->rnaprop, PointerRNA_NULL, NULL); - return true; - } - - uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : NULL; - /* RNA pointer */ - PointerRNA rptr; - - /* This is kind of hackish, in theory think we could only ever use the second member of - * this if/else, since ui_searchbox_apply() is supposed to always set that pointer when - * we are storing pointers... But keeping str search first for now, - * to try to break as little as possible existing code. All this is band-aids anyway. - * Fact remains, using editstr as main 'reference' over whole search button thingy - * is utterly weak and should be redesigned imho, but that's not a simple task. */ - if (search_but && search_but->rnasearchprop && - RNA_property_collection_lookup_string( - &search_but->rnasearchpoin, search_but->rnasearchprop, str, &rptr)) { - RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr, NULL); - } - else if (search_but->item_active != NULL) { - RNA_pointer_create(NULL, - RNA_property_pointer_type(&but->rnapoin, but->rnaprop), - search_but->item_active, - &rptr); - RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr, NULL); - } - - return true; - } - - if (type == PROP_ENUM) { - int value; - if (RNA_property_enum_value( - but->block->evil_C, &but->rnapoin, but->rnaprop, str, &value)) { - RNA_property_enum_set(&but->rnapoin, but->rnaprop, value); - return true; - } - return false; - } - BLI_assert(0); - } - } - else if (but->type == UI_BTYPE_TAB) { - if (but->rnaprop && but->custom_data) { - StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); - PointerRNA ptr; - PropertyRNA *prop; - - /* uiBut.custom_data points to data this tab represents (e.g. workspace). - * uiBut.rnapoin/prop store an active value (e.g. active workspace). */ - RNA_pointer_create(but->rnapoin.owner_id, ptr_type, but->custom_data, &ptr); - prop = RNA_struct_name_property(ptr_type); - if (RNA_property_editable(&ptr, prop)) { - RNA_property_string_set(&ptr, prop, str); - } - } - } - else if (but->type == UI_BTYPE_TEXT) { - /* string */ - if (!but->poin) { - str = ""; - } - else if (UI_but_is_utf8(but)) { - BLI_strncpy_utf8(but->poin, str, but->hardmax); - } - else { - BLI_strncpy(but->poin, str, but->hardmax); - } - - return true; - } - else if (but->type == UI_BTYPE_SEARCH_MENU) { - /* string */ - BLI_strncpy(but->poin, str, but->hardmax); - return true; - } - else if (ui_but_anim_expression_set(but, str)) { - /* driver expression */ - return true; - } - else if (str[0] == '#') { - /* shortcut to create new driver expression (versus immediate Py-execution) */ - return ui_but_anim_expression_create(but, str + 1); - } - else { - /* number editing */ - double value; - - if (ui_but_string_eval_number(C, but, str, &value) == false) { - WM_report_banner_show(); - return false; - } - - if (!ui_but_is_float(but)) { - value = floor(value + 0.5); - } - - /* not that we use hard limits here */ - if (value < (double)but->hardmin) { - value = but->hardmin; - } - if (value > (double)but->hardmax) { - value = but->hardmax; - } - - ui_but_value_set(but, value); - return true; - } - - return false; -} - -static double soft_range_round_up(double value, double max) -{ - /* round up to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. - * checking for 0.0 prevents floating point exceptions */ - const double newmax = (value != 0.0) ? pow(10.0, ceil(log(value) / M_LN10)) : 0.0; - - if (newmax * 0.2 >= max && newmax * 0.2 >= value) { - return newmax * 0.2; - } - if (newmax * 0.5 >= max && newmax * 0.5 >= value) { - return newmax * 0.5; - } - return newmax; -} - -static double soft_range_round_down(double value, double max) -{ - /* round down to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. - * checking for 0.0 prevents floating point exceptions */ - const double newmax = (value != 0.0) ? pow(10.0, floor(log(value) / M_LN10)) : 0.0; - - if (newmax * 5.0 <= max && newmax * 5.0 <= value) { - return newmax * 5.0; - } - if (newmax * 2.0 <= max && newmax * 2.0 <= value) { - return newmax * 2.0; - } - return newmax; -} - -void ui_but_range_set_hard(uiBut *but) -{ - if (but->rnaprop == NULL) { - return; - } - - const PropertyType type = RNA_property_type(but->rnaprop); - - /* clamp button range to something reasonable in case - * we get -inf/inf from RNA properties */ - if (type == PROP_INT) { - int imin, imax; - RNA_property_int_range(&but->rnapoin, but->rnaprop, &imin, &imax); - but->hardmin = (imin == INT_MIN) ? -1e4 : imin; - but->hardmax = (imin == INT_MAX) ? 1e4 : imax; - } - else if (type == PROP_FLOAT) { - float fmin, fmax; - RNA_property_float_range(&but->rnapoin, but->rnaprop, &fmin, &fmax); - but->hardmin = (fmin == -FLT_MAX) ? (float)-1e4 : fmin; - but->hardmax = (fmax == FLT_MAX) ? (float)1e4 : fmax; - } -} - -/* note: this could be split up into functions which handle arrays and not */ -void ui_but_range_set_soft(uiBut *but) -{ - /* ideally we would not limit this but practically, its more than - * enough worst case is very long vectors wont use a smart soft-range - * which isn't so bad. */ - - if (but->rnaprop) { - const PropertyType type = RNA_property_type(but->rnaprop); - const PropertySubType subtype = RNA_property_subtype(but->rnaprop); - double softmin, softmax /*, step, precision*/; - double value_min; - double value_max; - - /* clamp button range to something reasonable in case - * we get -inf/inf from RNA properties */ - if (type == PROP_INT) { - const bool is_array = RNA_property_array_check(but->rnaprop); - int imin, imax, istep; - - RNA_property_int_ui_range(&but->rnapoin, but->rnaprop, &imin, &imax, &istep); - softmin = (imin == INT_MIN) ? -1e4 : imin; - softmax = (imin == INT_MAX) ? 1e4 : imax; - /*step = istep;*/ /*UNUSED*/ - /*precision = 1;*/ /*UNUSED*/ - - if (is_array) { - int value_range[2]; - RNA_property_int_get_array_range(&but->rnapoin, but->rnaprop, value_range); - value_min = (double)value_range[0]; - value_max = (double)value_range[1]; - } - else { - value_min = value_max = ui_but_value_get(but); - } - } - else if (type == PROP_FLOAT) { - const bool is_array = RNA_property_array_check(but->rnaprop); - float fmin, fmax, fstep, fprecision; - - RNA_property_float_ui_range(&but->rnapoin, but->rnaprop, &fmin, &fmax, &fstep, &fprecision); - softmin = (fmin == -FLT_MAX) ? (float)-1e4 : fmin; - softmax = (fmax == FLT_MAX) ? (float)1e4 : fmax; - /*step = fstep;*/ /*UNUSED*/ - /*precision = fprecision;*/ /*UNUSED*/ - - /* Use shared min/max for array values, except for color alpha. */ - if (is_array && !(subtype == PROP_COLOR && but->rnaindex == 3)) { - float value_range[2]; - RNA_property_float_get_array_range(&but->rnapoin, but->rnaprop, value_range); - value_min = (double)value_range[0]; - value_max = (double)value_range[1]; - } - else { - value_min = value_max = ui_but_value_get(but); - } - } - else { - return; - } - - /* if the value goes out of the soft/max range, adapt the range */ - if (value_min + 1e-10 < softmin) { - if (value_min < 0.0) { - softmin = -soft_range_round_up(-value_min, -softmin); - } - else { - softmin = soft_range_round_down(value_min, softmin); - } - - if (softmin < (double)but->hardmin) { - softmin = (double)but->hardmin; - } - } - if (value_max - 1e-10 > softmax) { - if (value_max < 0.0) { - softmax = -soft_range_round_down(-value_max, -softmax); - } - else { - softmax = soft_range_round_up(value_max, softmax); - } - - if (softmax > (double)but->hardmax) { - softmax = but->hardmax; - } - } - - but->softmin = softmin; - but->softmax = softmax; - } - else if (but->poin && (but->pointype & UI_BUT_POIN_TYPES)) { - float value = ui_but_value_get(but); - if (isfinite(value)) { - CLAMP(value, but->hardmin, but->hardmax); - but->softmin = min_ff(but->softmin, value); - but->softmax = max_ff(but->softmax, value); - } - } -} - -/* ******************* Free ********************/ - -/** - * Free data specific to a certain button type. - * For now just do in a switch-case, we could instead have a callback stored in #uiBut and set that - * in #ui_but_alloc_info(). - */ -static void ui_but_free_type_specific(uiBut *but) -{ - switch (but->type) { - case UI_BTYPE_SEARCH_MENU: { - uiButSearch *search_but = (uiButSearch *)but; - - if (search_but->arg_free_fn) { - search_but->arg_free_fn(search_but->arg); - search_but->arg = NULL; - } - break; - } - default: - break; - } -} - -/* can be called with C==NULL */ -static void ui_but_free(const bContext *C, uiBut *but) -{ - if (but->opptr) { - WM_operator_properties_free(but->opptr); - MEM_freeN(but->opptr); - } - - if (but->func_argN) { - MEM_freeN(but->func_argN); - } - - if (but->tip_argN) { - MEM_freeN(but->tip_argN); - } - - if (but->hold_argN) { - MEM_freeN(but->hold_argN); - } - - ui_but_free_type_specific(but); - - if (but->active) { - /* XXX solve later, buttons should be free-able without context ideally, - * however they may have open tooltips or popup windows, which need to - * be closed using a context pointer */ - if (C) { - ui_but_active_free(C, but); - } - else { - if (but->active) { - MEM_freeN(but->active); - } - } - } - if (but->str && but->str != but->strdata) { - MEM_freeN(but->str); - } - - if ((but->type == UI_BTYPE_IMAGE) && but->poin) { - IMB_freeImBuf((struct ImBuf *)but->poin); - } - - if (but->dragpoin && (but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - } - ui_but_extra_operator_icons_free(but); - - BLI_assert(UI_butstore_is_registered(but->block, but) == false); - - MEM_freeN(but); -} - -/* can be called with C==NULL */ -void UI_block_free(const bContext *C, uiBlock *block) -{ - UI_butstore_clear(block); - - uiBut *but; - while ((but = BLI_pophead(&block->buttons))) { - ui_but_free(C, but); - } - - if (block->unit) { - MEM_freeN(block->unit); - } - - if (block->func_argN) { - MEM_freeN(block->func_argN); - } - - CTX_store_free_list(&block->contexts); - - BLI_freelistN(&block->saferct); - BLI_freelistN(&block->color_pickers.list); - - ui_block_free_button_groups(block); - - MEM_freeN(block); -} - -void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb) -{ - ARegion *region = CTX_wm_region(C); - wmWindow *window = CTX_wm_window(C); - - LISTBASE_FOREACH (uiBlock *, block, lb) { - if (block->active) { - ui_update_window_matrix(window, region, block); - } - } -} - -void UI_blocklist_draw(const bContext *C, const ListBase *lb) -{ - LISTBASE_FOREACH (uiBlock *, block, lb) { - if (block->active) { - UI_block_draw(C, block); - } - } -} - -/* can be called with C==NULL */ -void UI_blocklist_free(const bContext *C, ListBase *lb) -{ - uiBlock *block; - while ((block = BLI_pophead(lb))) { - UI_block_free(C, block); - } -} - -void UI_blocklist_free_inactive(const bContext *C, ListBase *lb) -{ - LISTBASE_FOREACH_MUTABLE (uiBlock *, block, lb) { - if (!block->handle) { - if (block->active) { - block->active = false; - } - else { - BLI_remlink(lb, block); - UI_block_free(C, block); - } - } - } -} - -void UI_block_region_set(uiBlock *block, ARegion *region) -{ - ListBase *lb = ®ion->uiblocks; - uiBlock *oldblock = NULL; - - /* each listbase only has one block with this name, free block - * if is already there so it can be rebuilt from scratch */ - if (lb) { - oldblock = BLI_findstring(lb, block->name, offsetof(uiBlock, name)); - - if (oldblock) { - oldblock->active = false; - oldblock->panel = NULL; - oldblock->handle = NULL; - } - - /* at the beginning of the list! for dynamical menus/blocks */ - BLI_addhead(lb, block); - } - - block->oldblock = oldblock; -} - -uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, eUIEmbossType emboss) -{ - wmWindow *window = CTX_wm_window(C); - Scene *scene = CTX_data_scene(C); - - uiBlock *block = MEM_callocN(sizeof(uiBlock), "uiBlock"); - block->active = true; - block->emboss = emboss; - block->evil_C = (void *)C; /* XXX */ - - BLI_listbase_clear(&block->button_groups); - - if (scene) { - /* store display device name, don't lookup for transformations yet - * block could be used for non-color displays where looking up for transformation - * would slow down redraw, so only lookup for actual transform when it's indeed - * needed - */ - STRNCPY(block->display_device, scene->display_settings.display_device); - - /* copy to avoid crash when scene gets deleted with ui still open */ - block->unit = MEM_mallocN(sizeof(scene->unit), "UI UnitSettings"); - memcpy(block->unit, &scene->unit, sizeof(scene->unit)); - } - else { - STRNCPY(block->display_device, IMB_colormanagement_display_get_default_name()); - } - - BLI_strncpy(block->name, name, sizeof(block->name)); - - if (region) { - UI_block_region_set(block, region); - } - - /* Set window matrix and aspect for region and OpenGL state. */ - ui_update_window_matrix(window, region, block); - - /* Tag as popup menu if not created within a region. */ - if (!(region && region->visible)) { - block->auto_open = true; - block->flag |= UI_BLOCK_LOOP; - } - - return block; -} - -char UI_block_emboss_get(uiBlock *block) -{ - return block->emboss; -} - -void UI_block_emboss_set(uiBlock *block, eUIEmbossType emboss) -{ - block->emboss = emboss; -} - -void UI_block_theme_style_set(uiBlock *block, char theme_style) -{ - block->theme_style = theme_style; -} - -bool UI_block_is_search_only(const uiBlock *block) -{ - return block->flag & UI_BLOCK_SEARCH_ONLY; -} - -/** - * Use when a block must be searched to give accurate results - * for the whole region but shouldn't be displayed. - */ -void UI_block_set_search_only(uiBlock *block, bool search_only) -{ - SET_FLAG_FROM_TEST(block->flag, search_only, UI_BLOCK_SEARCH_ONLY); -} - -static void ui_but_build_drawstr_float(uiBut *but, double value) -{ - size_t slen = 0; - STR_CONCAT(but->drawstr, slen, but->str); - - PropertySubType subtype = PROP_NONE; - if (but->rnaprop) { - subtype = RNA_property_subtype(but->rnaprop); - } - - /* Change negative zero to regular zero, without altering anything else. */ - value += +0.0f; - - if (value == (double)FLT_MAX) { - STR_CONCAT(but->drawstr, slen, "inf"); - } - else if (value == (double)-FLT_MAX) { - STR_CONCAT(but->drawstr, slen, "-inf"); - } - else if (subtype == PROP_PERCENTAGE) { - const int prec = ui_but_calc_float_precision(but, value); - STR_CONCATF(but->drawstr, slen, "%.*f%%", prec, value); - } - else if (subtype == PROP_PIXEL) { - const int prec = ui_but_calc_float_precision(but, value); - STR_CONCATF(but->drawstr, slen, "%.*f px", prec, value); - } - else if (subtype == PROP_FACTOR) { - const int precision = ui_but_calc_float_precision(but, value); - - if (U.factor_display_type == USER_FACTOR_AS_FACTOR) { - STR_CONCATF(but->drawstr, slen, "%.*f", precision, value); - } - else { - STR_CONCATF(but->drawstr, slen, "%.*f%%", MAX2(0, precision - 2), value * 100); - } - } - else if (ui_but_is_unit(but)) { - char new_str[sizeof(but->drawstr)]; - ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1); - STR_CONCAT(but->drawstr, slen, new_str); - } - else { - const int prec = ui_but_calc_float_precision(but, value); - STR_CONCATF(but->drawstr, slen, "%.*f", prec, value); - } -} - -static void ui_but_build_drawstr_int(uiBut *but, int value) -{ - size_t slen = 0; - STR_CONCAT(but->drawstr, slen, but->str); - - PropertySubType subtype = PROP_NONE; - if (but->rnaprop) { - subtype = RNA_property_subtype(but->rnaprop); - } - - STR_CONCATF(but->drawstr, slen, "%d", value); - - if (subtype == PROP_PERCENTAGE) { - STR_CONCAT(but->drawstr, slen, "%"); - } - else if (subtype == PROP_PIXEL) { - STR_CONCAT(but->drawstr, slen, " px"); - } -} - -/** - * \param but: Button to update. - * \param validate: When set, this function may change the button value. - * Otherwise treat the button value as read-only. - */ -static void ui_but_update_ex(uiBut *but, const bool validate) -{ - /* if something changed in the button */ - double value = UI_BUT_VALUE_UNSET; - - ui_but_update_select_flag(but, &value); - - /* only update soft range while not editing */ - if (!ui_but_is_editing(but)) { - if ((but->rnaprop != NULL) || (but->poin && (but->pointype & UI_BUT_POIN_TYPES))) { - ui_but_range_set_soft(but); - } - } - - /* test for min and max, icon sliders, etc */ - switch (but->type) { - case UI_BTYPE_NUM: - case UI_BTYPE_SCROLL: - case UI_BTYPE_NUM_SLIDER: - if (validate) { - UI_GET_BUT_VALUE_INIT(but, value); - if (value < (double)but->hardmin) { - ui_but_value_set(but, but->hardmin); - } - else if (value > (double)but->hardmax) { - ui_but_value_set(but, but->hardmax); - } - - /* max must never be smaller than min! Both being equal is allowed though */ - BLI_assert(but->softmin <= but->softmax && but->hardmin <= but->hardmax); - } - break; - - case UI_BTYPE_ICON_TOGGLE: - case UI_BTYPE_ICON_TOGGLE_N: - if ((but->rnaprop == NULL) || (RNA_property_flag(but->rnaprop) & PROP_ICONS_CONSECUTIVE)) { - if (but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ICONS_REVERSE) { - but->drawflag |= UI_BUT_ICON_REVERSE; - } - - but->iconadd = (but->flag & UI_SELECT) ? 1 : 0; - } - break; - - /* quiet warnings for unhandled types */ - default: - break; - } - - /* safety is 4 to enable small number buttons (like 'users') */ - // okwidth = -4 + (BLI_rcti_size_x(&but->rect)); /* UNUSED */ - - /* name: */ - switch (but->type) { - - case UI_BTYPE_MENU: - if (BLI_rctf_size_x(&but->rect) >= (UI_UNIT_X * 2)) { - /* only needed for menus in popup blocks that don't recreate buttons on redraw */ - if (but->block->flag & UI_BLOCK_LOOP) { - if (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_ENUM)) { - const int value_enum = RNA_property_enum_get(&but->rnapoin, but->rnaprop); - - EnumPropertyItem item; - if (RNA_property_enum_item_from_value_gettexted( - but->block->evil_C, &but->rnapoin, but->rnaprop, value_enum, &item)) { - const size_t slen = strlen(item.name); - ui_but_string_free_internal(but); - ui_but_string_set_internal(but, item.name, slen); - but->icon = item.icon; - } - } - } - BLI_strncpy(but->drawstr, but->str, sizeof(but->drawstr)); - } - break; - - case UI_BTYPE_NUM: - case UI_BTYPE_NUM_SLIDER: - if (but->editstr) { - break; - } - UI_GET_BUT_VALUE_INIT(but, value); - if (ui_but_is_float(but)) { - ui_but_build_drawstr_float(but, value); - } - else { - ui_but_build_drawstr_int(but, (int)value); - } - break; - - case UI_BTYPE_LABEL: - if (ui_but_is_float(but)) { - UI_GET_BUT_VALUE_INIT(but, value); - const int prec = ui_but_calc_float_precision(but, value); - BLI_snprintf(but->drawstr, sizeof(but->drawstr), "%s%.*f", but->str, prec, value); - } - else { - BLI_strncpy(but->drawstr, but->str, UI_MAX_DRAW_STR); - } - - break; - - case UI_BTYPE_TEXT: - case UI_BTYPE_SEARCH_MENU: - if (!but->editstr) { - char str[UI_MAX_DRAW_STR]; - - ui_but_string_get(but, str, UI_MAX_DRAW_STR); - BLI_snprintf(but->drawstr, sizeof(but->drawstr), "%s%s", but->str, str); - } - break; - - case UI_BTYPE_KEY_EVENT: { - const char *str; - if (but->flag & UI_SELECT) { - str = "Press a key"; - } - else { - UI_GET_BUT_VALUE_INIT(but, value); - str = WM_key_event_string((short)value, false); - } - BLI_snprintf(but->drawstr, UI_MAX_DRAW_STR, "%s%s", but->str, str); - break; - } - case UI_BTYPE_HOTKEY_EVENT: - if (but->flag & UI_SELECT) { - - if (but->modifier_key) { - char *str = but->drawstr; - but->drawstr[0] = '\0'; - - if (but->modifier_key & KM_SHIFT) { - str += BLI_strcpy_rlen(str, "Shift "); - } - if (but->modifier_key & KM_CTRL) { - str += BLI_strcpy_rlen(str, "Ctrl "); - } - if (but->modifier_key & KM_ALT) { - str += BLI_strcpy_rlen(str, "Alt "); - } - if (but->modifier_key & KM_OSKEY) { - str += BLI_strcpy_rlen(str, "Cmd "); - } - - (void)str; /* UNUSED */ - } - else { - BLI_strncpy(but->drawstr, "Press a key", UI_MAX_DRAW_STR); - } - } - else { - BLI_strncpy(but->drawstr, but->str, UI_MAX_DRAW_STR); - } - - break; - - case UI_BTYPE_HSVCUBE: - case UI_BTYPE_HSVCIRCLE: - break; - default: - BLI_strncpy(but->drawstr, but->str, UI_MAX_DRAW_STR); - break; - } - - /* if we are doing text editing, this will override the drawstr */ - if (but->editstr) { - but->drawstr[0] = '\0'; - } - - /* text clipping moved to widget drawing code itself */ -} - -void ui_but_update(uiBut *but) -{ - ui_but_update_ex(but, false); -} - -void ui_but_update_edited(uiBut *but) -{ - ui_but_update_ex(but, true); -} - -void UI_block_align_begin(uiBlock *block) -{ - /* if other align was active, end it */ - if (block->flag & UI_BUT_ALIGN) { - UI_block_align_end(block); - } - - block->flag |= UI_BUT_ALIGN_DOWN; - block->alignnr++; - - /* buttons declared after this call will get this align nr */ /* XXX flag? */ -} - -void UI_block_align_end(uiBlock *block) -{ - block->flag &= ~UI_BUT_ALIGN; /* all 4 flags */ -} - -struct ColorManagedDisplay *ui_block_cm_display_get(uiBlock *block) -{ - return IMB_colormanagement_display_get_named(block->display_device); -} - -void ui_block_cm_to_display_space_v3(uiBlock *block, float pixel[3]) -{ - struct ColorManagedDisplay *display = ui_block_cm_display_get(block); - - IMB_colormanagement_scene_linear_to_display_v3(pixel, display); -} - -static void ui_but_alloc_info(const eButType type, - size_t *r_alloc_size, - const char **r_alloc_str, - bool *r_has_custom_type) -{ - size_t alloc_size; - const char *alloc_str; - bool has_custom_type = true; - - switch (type) { - case UI_BTYPE_NUM: - alloc_size = sizeof(uiButNumber); - alloc_str = "uiButNumber"; - break; - case UI_BTYPE_COLOR: - alloc_size = sizeof(uiButColor); - alloc_str = "uiButColor"; - break; - case UI_BTYPE_DECORATOR: - alloc_size = sizeof(uiButDecorator); - alloc_str = "uiButDecorator"; - break; - case UI_BTYPE_TAB: - alloc_size = sizeof(uiButTab); - alloc_str = "uiButTab"; - break; - case UI_BTYPE_SEARCH_MENU: - alloc_size = sizeof(uiButSearch); - alloc_str = "uiButSearch"; - break; - case UI_BTYPE_PROGRESS_BAR: - alloc_size = sizeof(uiButProgressbar); - alloc_str = "uiButProgressbar"; - break; - case UI_BTYPE_HSVCUBE: - alloc_size = sizeof(uiButHSVCube); - alloc_str = "uiButHSVCube"; - break; - case UI_BTYPE_COLORBAND: - alloc_size = sizeof(uiButColorBand); - alloc_str = "uiButColorBand"; - break; - case UI_BTYPE_CURVE: - alloc_size = sizeof(uiButCurveMapping); - alloc_str = "uiButCurveMapping"; - break; - case UI_BTYPE_CURVEPROFILE: - alloc_size = sizeof(uiButCurveProfile); - alloc_str = "uiButCurveProfile"; - break; - default: - alloc_size = sizeof(uiBut); - alloc_str = "uiBut"; - has_custom_type = false; - break; - } - - if (r_alloc_size) { - *r_alloc_size = alloc_size; - } - if (r_alloc_str) { - *r_alloc_str = alloc_str; - } - if (r_has_custom_type) { - *r_has_custom_type = has_custom_type; - } -} - -static uiBut *ui_but_alloc(const eButType type) -{ - size_t alloc_size; - const char *alloc_str; - ui_but_alloc_info(type, &alloc_size, &alloc_str, NULL); - - return MEM_callocN(alloc_size, alloc_str); -} - -/** - * Reallocate the button (new address is returned) for a new button type. - * This should generally be avoided and instead the correct type be created right away. - * - * \note Only the #uiBut data can be kept. If the old button used a derived type (e.g. #uiButTab), - * the data that is not inside #uiBut will be lost. - */ -uiBut *ui_but_change_type(uiBut *but, eButType new_type) -{ - if (but->type == new_type) { - /* Nothing to do. */ - return but; - } - - size_t alloc_size; - const char *alloc_str; - uiBut *insert_after_but = but->prev; - bool new_has_custom_type, old_has_custom_type; - - /* Remove old button address */ - BLI_remlink(&but->block->buttons, but); - - ui_but_alloc_info(but->type, NULL, NULL, &old_has_custom_type); - ui_but_alloc_info(new_type, &alloc_size, &alloc_str, &new_has_custom_type); - - if (new_has_custom_type || old_has_custom_type) { - const void *old_but_ptr = but; - /* Button may have pointer to a member within itself, this will have to be updated. */ - const bool has_str_ptr_to_self = but->str == but->strdata; - const bool has_poin_ptr_to_self = but->poin == (char *)but; - - but = MEM_recallocN_id(but, alloc_size, alloc_str); - but->type = new_type; - if (has_str_ptr_to_self) { - but->str = but->strdata; - } - if (has_poin_ptr_to_self) { - but->poin = (char *)but; - } - - BLI_insertlinkafter(&but->block->buttons, insert_after_but, but); - - if (but->layout) { - const bool found_layout = ui_layout_replace_but_ptr(but->layout, old_but_ptr, but); - BLI_assert(found_layout); - UNUSED_VARS_NDEBUG(found_layout); - ui_button_group_replace_but_ptr(uiLayoutGetBlock(but->layout), old_but_ptr, but); - } - if (UI_editsource_enable_check()) { - UI_editsource_but_replace(old_but_ptr, but); - } - } - - return but; -} - -/** - * \brief ui_def_but is the function that draws many button types - * - * \param x, y: The lower left hand corner of the button (X axis) - * \param width, height: The size of the button. - * - * for float buttons: - * \param a1: Click Step (how much to change the value each click) - * \param a2: Number of decimal point values to display. 0 defaults to 3 (0.000) - * 1,2,3, and a maximum of 4, all greater values will be clamped to 4. - */ -static uiBut *ui_def_but(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - BLI_assert(width >= 0 && height >= 0); - - /* we could do some more error checks here */ - if ((type & BUTTYPE) == UI_BTYPE_LABEL) { - BLI_assert((poin != NULL || min != 0.0f || max != 0.0f || (a1 == 0.0f && a2 != 0.0f) || - (a1 != 0.0f && a1 != 1.0f)) == false); - } - - if (type & UI_BUT_POIN_TYPES) { /* a pointer is required */ - if (poin == NULL) { - BLI_assert(0); - return NULL; - } - } - - uiBut *but = ui_but_alloc(type & BUTTYPE); - - but->type = type & BUTTYPE; - but->pointype = type & UI_BUT_POIN_TYPES; - but->bit = type & UI_BUT_POIN_BIT; - but->bitnr = type & 31; - but->icon = ICON_NONE; - but->iconadd = 0; - - but->retval = retval; - - const int slen = strlen(str); - ui_but_string_set_internal(but, str, slen); - - but->rect.xmin = x; - but->rect.ymin = y; - but->rect.xmax = but->rect.xmin + width; - but->rect.ymax = but->rect.ymin + height; - - but->poin = poin; - but->hardmin = but->softmin = min; - but->hardmax = but->softmax = max; - but->a1 = a1; - but->a2 = a2; - but->tip = tip; - - but->disabled_info = block->lockstr; - but->emboss = block->emboss; - but->pie_dir = UI_RADIAL_NONE; - - but->block = block; /* pointer back, used for front-buffer status, and picker. */ - - if ((block->flag & UI_BUT_ALIGN) && ui_but_can_align(but)) { - but->alignnr = block->alignnr; - } - - but->func = block->func; - but->func_arg1 = block->func_arg1; - but->func_arg2 = block->func_arg2; - - but->funcN = block->funcN; - if (block->func_argN) { - but->func_argN = MEM_dupallocN(block->func_argN); - } - - but->pos = -1; /* cursor invisible */ - - if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* add a space to name */ - /* slen remains unchanged from previous assignment, ensure this stays true */ - if (slen > 0 && slen < UI_MAX_NAME_STR - 2) { - if (but->str[slen - 1] != ' ') { - but->str[slen] = ' '; - but->str[slen + 1] = 0; - } - } - } - - if (block->flag & UI_BLOCK_RADIAL) { - but->drawflag |= UI_BUT_TEXT_LEFT; - if (but->str && but->str[0]) { - but->drawflag |= UI_BUT_ICON_LEFT; - } - } - else if (((block->flag & UI_BLOCK_LOOP) && !ui_block_is_popover(block) && - !(block->flag & UI_BLOCK_QUICK_SETUP)) || - ELEM(but->type, - UI_BTYPE_MENU, - UI_BTYPE_TEXT, - UI_BTYPE_LABEL, - UI_BTYPE_BLOCK, - UI_BTYPE_BUT_MENU, - UI_BTYPE_SEARCH_MENU, - UI_BTYPE_PROGRESS_BAR, - UI_BTYPE_POPOVER)) { - but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); - } -#ifdef USE_NUMBUTS_LR_ALIGN - else if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { - if (slen != 0) { - but->drawflag |= UI_BUT_TEXT_LEFT; - } - } -#endif - - but->drawflag |= (block->flag & UI_BUT_ALIGN); - - if (block->lock == true) { - but->flag |= UI_BUT_DISABLED; - } - - /* keep track of UI_interface.h */ - if (ELEM(but->type, - UI_BTYPE_BLOCK, - UI_BTYPE_BUT, - UI_BTYPE_DECORATOR, - UI_BTYPE_LABEL, - UI_BTYPE_PULLDOWN, - UI_BTYPE_ROUNDBOX, - UI_BTYPE_LISTBOX, - UI_BTYPE_BUT_MENU, - UI_BTYPE_SCROLL, - UI_BTYPE_GRIP, - UI_BTYPE_SEPR, - UI_BTYPE_SEPR_LINE, - UI_BTYPE_SEPR_SPACER) || - (but->type >= UI_BTYPE_SEARCH_MENU)) { - /* pass */ - } - else { - but->flag |= UI_BUT_UNDO; - } - - BLI_addtail(&block->buttons, but); - - if (block->curlayout) { - ui_layout_add_but(block->curlayout, but); - } - -#ifdef WITH_PYTHON - /* if the 'UI_OT_editsource' is running, extract the source info from the button */ - if (UI_editsource_enable_check()) { - UI_editsource_active_but_test(but); - } -#endif - - return but; -} - -void ui_def_but_icon(uiBut *but, const int icon, const int flag) -{ - if (icon) { - ui_icon_ensure_deferred(but->block->evil_C, icon, (flag & UI_BUT_ICON_PREVIEW) != 0); - } - but->icon = (BIFIconID)icon; - but->flag |= flag; - - if (but->str && but->str[0]) { - but->drawflag |= UI_BUT_ICON_LEFT; - } -} - -/** - * Avoid using this where possible since it's better not to ask for an icon in the first place. - */ -void ui_def_but_icon_clear(uiBut *but) -{ - but->icon = ICON_NONE; - but->flag &= ~UI_HAS_ICON; - but->drawflag &= ~UI_BUT_ICON_LEFT; -} - -static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *but_p) -{ - uiBlock *block = uiLayoutGetBlock(layout); - uiPopupBlockHandle *handle = block->handle; - uiBut *but = (uiBut *)but_p; - - /* see comment in ui_item_enum_expand, re: uiname */ - const EnumPropertyItem *item_array; - - UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); - - bool free; - RNA_property_enum_items_gettexted( - block->evil_C, &but->rnapoin, but->rnaprop, &item_array, NULL, &free); - - /* We don't want nested rows, cols in menus. */ - UI_block_layout_set_current(block, layout); - - int totitems = 0; - int categories = 0; - int nbr_entries_nosepr = 0; - for (const EnumPropertyItem *item = item_array; item->identifier; item++, totitems++) { - if (!item->identifier[0]) { - /* inconsistent, but menus with categories do not look good flipped */ - if (item->name) { - block->flag |= UI_BLOCK_NO_FLIP; - categories++; - nbr_entries_nosepr++; - } - /* We do not want simple separators in nbr_entries_nosepr count */ - continue; - } - nbr_entries_nosepr++; - } - - /* Columns and row estimation. Ignore simple separators here. */ - int columns = (nbr_entries_nosepr + 20) / 20; - if (columns < 1) { - columns = 1; - } - if (columns > 8) { - columns = (nbr_entries_nosepr + 25) / 25; - } - - int rows = totitems / columns; - if (rows < 1) { - rows = 1; - } - while (rows * columns < totitems) { - rows++; - } - - const char *title = RNA_property_ui_name(but->rnaprop); - - if (title[0] && (categories == 0) && (block->flag & UI_BLOCK_NO_FLIP)) { - /* Title at the top for menus with categories. */ - uiDefBut( - block, UI_BTYPE_LABEL, 0, title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); - uiItemS(layout); - } - - /* note, item_array[...] is reversed on access */ - - /* create items */ - uiLayout *split = uiLayoutSplit(layout, 0.0f, false); - - bool new_column; - - int column_end = 0; - uiLayout *column = NULL; - for (int a = 0; a < totitems; a++) { - new_column = (a == column_end); - if (new_column) { - /* start new column, and find out where it ends in advance, so we - * can flip the order of items properly per column */ - column_end = totitems; - - for (int b = a + 1; b < totitems; b++) { - const EnumPropertyItem *item = &item_array[b]; - - /* new column on N rows or on separation label */ - if (((b - a) % rows == 0) || (!item->identifier[0] && item->name)) { - column_end = b; - break; - } - } - - column = uiLayoutColumn(split, false); - } - - const EnumPropertyItem *item = &item_array[a]; - - if (new_column && (categories > 0) && item->identifier[0]) { - uiItemL(column, "", ICON_NONE); - uiItemS(column); - } - - if (!item->identifier[0]) { - if (item->name) { - if (item->icon) { - uiItemL(column, item->name, item->icon); - } - else { - /* Do not use uiItemL here, as our root layout is a menu one, - * it will add a fake blank icon! */ - uiDefBut(block, - UI_BTYPE_LABEL, - 0, - item->name, - 0, - 0, - UI_UNIT_X * 5, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - } - } - uiItemS(column); - } - else { - if (item->icon) { - uiDefIconTextButI(block, - UI_BTYPE_BUT_MENU, - B_NOP, - item->icon, - item->name, - 0, - 0, - UI_UNIT_X * 5, - UI_UNIT_Y, - &handle->retvalue, - item->value, - 0.0, - 0, - -1, - item->description); - } - else { - uiDefButI(block, - UI_BTYPE_BUT_MENU, - B_NOP, - item->name, - 0, - 0, - UI_UNIT_X * 5, - UI_UNIT_X, - &handle->retvalue, - item->value, - 0.0, - 0, - -1, - item->description); - } - } - } - - if (title[0] && (categories == 0) && !(block->flag & UI_BLOCK_NO_FLIP)) { - /* Title at the bottom for menus without categories. */ - uiItemS(layout); - uiDefBut( - block, UI_BTYPE_LABEL, 0, title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); - } - - UI_block_layout_set_current(block, layout); - - if (free) { - MEM_freeN((void *)item_array); - } - BLI_assert((block->flag & UI_BLOCK_IS_FLIP) == 0); - block->flag |= UI_BLOCK_IS_FLIP; -} - -static void ui_def_but_rna__panel_type(bContext *C, uiLayout *layout, void *but_p) -{ - uiBut *but = but_p; - const char *panel_type = but->func_argN; - PanelType *pt = WM_paneltype_find(panel_type, true); - if (pt) { - ui_item_paneltype_func(C, layout, pt); - } - else { - char msg[256]; - SNPRINTF(msg, "Missing Panel: %s", panel_type); - uiItemL(layout, msg, ICON_NONE); - } -} - -void ui_but_rna_menu_convert_to_panel_type(uiBut *but, const char *panel_type) -{ - BLI_assert(ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)); - // BLI_assert(but->menu_create_func == ui_def_but_rna__menu); - // BLI_assert((void *)but->poin == but); - but->menu_create_func = ui_def_but_rna__panel_type; - but->func_argN = BLI_strdup(panel_type); -} - -bool ui_but_menu_draw_as_popover(const uiBut *but) -{ - return (but->menu_create_func == ui_def_but_rna__panel_type); -} - -static void ui_def_but_rna__menu_type(bContext *C, uiLayout *layout, void *but_p) -{ - uiBut *but = but_p; - const char *menu_type = but->func_argN; - MenuType *mt = WM_menutype_find(menu_type, true); - if (mt) { - ui_item_menutype_func(C, layout, mt); - } - else { - char msg[256]; - SNPRINTF(msg, "Missing Menu: %s", menu_type); - uiItemL(layout, msg, ICON_NONE); - } -} - -void ui_but_rna_menu_convert_to_menu_type(uiBut *but, const char *menu_type) -{ - BLI_assert(but->type == UI_BTYPE_MENU); - BLI_assert(but->menu_create_func == ui_def_but_rna__menu); - BLI_assert((void *)but->poin == but); - but->menu_create_func = ui_def_but_rna__menu_type; - but->func_argN = BLI_strdup(menu_type); -} - -static void ui_but_submenu_enable(uiBlock *block, uiBut *but) -{ - but->flag |= UI_BUT_ICON_SUBMENU; - block->content_hints |= UI_BLOCK_CONTAINS_SUBMENU_BUT; -} - -/** - * ui_def_but_rna_propname and ui_def_but_rna - * both take the same args except for propname vs prop, this is done so we can - * avoid an extra lookup on 'prop' when its already available. - * - * When this kind of change won't disrupt branches, best look into making more - * of our UI functions take prop rather than propname. - */ -static uiBut *ui_def_but_rna(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - const PropertyType proptype = RNA_property_type(prop); - int icon = 0; - uiMenuCreateFunc func = NULL; - const bool always_set_a1_a2 = ELEM(type, UI_BTYPE_NUM); - - if (ELEM(type, UI_BTYPE_COLOR, UI_BTYPE_HSVCIRCLE, UI_BTYPE_HSVCUBE)) { - BLI_assert(index == -1); - } - - /* use rna values if parameters are not specified */ - if ((proptype == PROP_ENUM) && ELEM(type, UI_BTYPE_MENU, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { - bool free; - const EnumPropertyItem *item; - RNA_property_enum_items(block->evil_C, ptr, prop, &item, NULL, &free); - - int value; - /* UI_BTYPE_MENU is handled a little differently here */ - if (type == UI_BTYPE_MENU) { - value = RNA_property_enum_get(ptr, prop); - } - else { - value = (int)max; - } - - const int i = RNA_enum_from_value(item, value); - if (i != -1) { - - if (!str) { - str = item[i].name; -#ifdef WITH_INTERNATIONAL - str = CTX_IFACE_(RNA_property_translation_context(prop), str); -#endif - } - - icon = item[i].icon; - } - else { - if (!str) { - if (type == UI_BTYPE_MENU) { - str = ""; - } - else { - str = RNA_property_ui_name(prop); - } - } - } - - if (type == UI_BTYPE_MENU) { - func = ui_def_but_rna__menu; - } - - if (free) { - MEM_freeN((void *)item); - } - } - else { - if (!str) { - str = RNA_property_ui_name(prop); - } - icon = RNA_property_ui_icon(prop); - } - - if (!tip && proptype != PROP_ENUM) { - tip = RNA_property_ui_description(prop); - } - - if (min == max || a1 == -1 || a2 == -1 || always_set_a1_a2) { - if (proptype == PROP_INT) { - int hardmin, hardmax, softmin, softmax, step; - - RNA_property_int_range(ptr, prop, &hardmin, &hardmax); - RNA_property_int_ui_range(ptr, prop, &softmin, &softmax, &step); - - if (!ELEM(type, UI_BTYPE_ROW, UI_BTYPE_LISTROW) && min == max) { - min = hardmin; - max = hardmax; - } - if (a1 == -1 || always_set_a1_a2) { - a1 = step; - } - if (a2 == -1 || always_set_a1_a2) { - a2 = 0; - } - } - else if (proptype == PROP_FLOAT) { - float hardmin, hardmax, softmin, softmax, step, precision; - - RNA_property_float_range(ptr, prop, &hardmin, &hardmax); - RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); - - if (!ELEM(type, UI_BTYPE_ROW, UI_BTYPE_LISTROW) && min == max) { - min = hardmin; - max = hardmax; - } - if (a1 == -1 || always_set_a1_a2) { - a1 = step; - } - if (a2 == -1 || always_set_a1_a2) { - a2 = precision; - } - } - else if (proptype == PROP_STRING) { - min = 0; - max = RNA_property_string_maxlength(prop); - /* note, 'max' may be zero (code for dynamically resized array) */ - } - } - - /* now create button */ - uiBut *but = ui_def_but( - block, type, retval, str, x, y, width, height, NULL, min, max, a1, a2, tip); - - if (but->type == UI_BTYPE_NUM) { - /* Set default values, can be overridden later. */ - UI_but_number_step_size_set(but, a1); - UI_but_number_precision_set(but, a2); - } - - but->rnapoin = *ptr; - but->rnaprop = prop; - - if (RNA_property_array_check(but->rnaprop)) { - but->rnaindex = index; - } - else { - but->rnaindex = 0; - } - - if (icon) { - ui_def_but_icon(but, icon, UI_HAS_ICON); - } - - if (type == UI_BTYPE_MENU) { - if (but->emboss == UI_EMBOSS_PULLDOWN) { - ui_but_submenu_enable(block, but); - } - } - else if (type == UI_BTYPE_SEARCH_MENU) { - if (proptype == PROP_POINTER) { - /* Search buttons normally don't get undo, see: T54580. */ - but->flag |= UI_BUT_UNDO; - } - } - - const char *info; - if (but->rnapoin.data && !RNA_property_editable_info(&but->rnapoin, prop, &info)) { - UI_but_disable(but, info); - } - - if (proptype == PROP_POINTER) { - /* If the button shows an ID, automatically set it as focused in context so operators can - * access it.*/ - const PointerRNA pptr = RNA_property_pointer_get(ptr, prop); - if (pptr.data && RNA_struct_is_ID(pptr.type)) { - but->context = CTX_store_add(&block->contexts, "id", &pptr); - } - } - - if (but->flag & UI_BUT_UNDO && (ui_but_is_rna_undo(but) == false)) { - but->flag &= ~UI_BUT_UNDO; - } - - /* If this button uses units, calculate the step from this */ - if ((proptype == PROP_FLOAT) && ui_but_is_unit(but)) { - if (type == UI_BTYPE_NUM) { - uiButNumber *number_but = (uiButNumber *)but; - number_but->step_size = ui_get_but_step_unit(but, number_but->step_size); - } - else { - but->a1 = ui_get_but_step_unit(but, but->a1); - } - } - - if (func) { - but->menu_create_func = func; - but->poin = (char *)but; - } - - return but; -} - -static uiBut *ui_def_but_rna_propname(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - const char *propname, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - uiBut *but; - if (prop) { - but = ui_def_but_rna( - block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); - } - else { - but = ui_def_but( - block, type, retval, propname, x, y, width, height, NULL, min, max, a1, a2, tip); - - UI_but_disable(but, "Unknown Property."); - } - - return but; -} - -static uiBut *ui_def_but_operator_ptr(uiBlock *block, - int type, - wmOperatorType *ot, - int opcontext, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - if (!str) { - if (ot && ot->srna) { - str = WM_operatortype_name(ot, NULL); - } - else { - str = ""; - } - } - - if ((!tip || tip[0] == '\0') && ot && ot->srna && !ot->get_description) { - tip = RNA_struct_ui_description(ot->srna); - } - - uiBut *but = ui_def_but(block, type, -1, str, x, y, width, height, NULL, 0, 0, 0, 0, tip); - but->optype = ot; - but->opcontext = opcontext; - but->flag &= ~UI_BUT_UNDO; /* no need for ui_but_is_rna_undo(), we never need undo here */ - - if (!ot) { - UI_but_disable(but, ""); - } - - return but; -} - -uiBut *uiDefBut(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but( - block, type, retval, str, x, y, width, height, poin, min, max, a1, a2, tip); - - ui_but_update(but); - - return but; -} - -uiBut *uiDefButImage( - uiBlock *block, void *imbuf, int x, int y, short width, short height, const uchar color[4]) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_IMAGE, 0, "", x, y, width, height, imbuf, 0, 0, 0, 0, ""); - if (color) { - copy_v4_v4_uchar(but->col, color); - } - else { - but->col[0] = 255; - but->col[1] = 255; - but->col[2] = 255; - but->col[3] = 255; - } - ui_but_update(but); - return but; -} - -uiBut *uiDefButAlert(uiBlock *block, int icon, int x, int y, short width, short height) -{ - struct ImBuf *ibuf = UI_icon_alert_imbuf_get(icon); - bTheme *btheme = UI_GetTheme(); - return uiDefButImage(block, ibuf, x, y, width, height, btheme->tui.wcol_menu_back.text); -} - -/** - * if \a _x_ is a power of two (only one bit) return the power, - * otherwise return -1. - * - * for powers of two: - * \code{.c} - * ((1 << findBitIndex(x)) == x); - * \endcode - */ -static int findBitIndex(uint x) -{ - if (!x || !is_power_of_2_i(x)) { /* is_power_of_2_i(x) strips lowest bit */ - return -1; - } - int idx = 0; - - if (x & 0xFFFF0000) { - idx += 16; - x >>= 16; - } - if (x & 0xFF00) { - idx += 8; - x >>= 8; - } - if (x & 0xF0) { - idx += 4; - x >>= 4; - } - if (x & 0xC) { - idx += 2; - x >>= 2; - } - if (x & 0x2) { - idx += 1; - } - - return idx; -} - -/* Auto-complete helper functions. */ -struct AutoComplete { - size_t maxlen; - int matches; - char *truncate; - const char *startname; -}; - -AutoComplete *UI_autocomplete_begin(const char *startname, size_t maxlen) -{ - AutoComplete *autocpl; - - autocpl = MEM_callocN(sizeof(AutoComplete), "AutoComplete"); - autocpl->maxlen = maxlen; - autocpl->matches = 0; - autocpl->truncate = MEM_callocN(sizeof(char) * maxlen, "AutoCompleteTruncate"); - autocpl->startname = startname; - - return autocpl; -} - -void UI_autocomplete_update_name(AutoComplete *autocpl, const char *name) -{ - char *truncate = autocpl->truncate; - const char *startname = autocpl->startname; - int match_index = 0; - for (int a = 0; a < autocpl->maxlen - 1; a++) { - if (startname[a] == 0 || startname[a] != name[a]) { - match_index = a; - break; - } - } - - /* found a match */ - if (startname[match_index] == 0) { - autocpl->matches++; - /* first match */ - if (truncate[0] == 0) { - BLI_strncpy(truncate, name, autocpl->maxlen); - } - else { - /* remove from truncate what is not in bone->name */ - for (int a = 0; a < autocpl->maxlen - 1; a++) { - if (name[a] == 0) { - truncate[a] = 0; - break; - } - if (truncate[a] != name[a]) { - truncate[a] = 0; - } - } - } - } -} - -int UI_autocomplete_end(AutoComplete *autocpl, char *autoname) -{ - int match = AUTOCOMPLETE_NO_MATCH; - if (autocpl->truncate[0]) { - if (autocpl->matches == 1) { - match = AUTOCOMPLETE_FULL_MATCH; - } - else { - match = AUTOCOMPLETE_PARTIAL_MATCH; - } - BLI_strncpy(autoname, autocpl->truncate, autocpl->maxlen); - } - else { - if (autoname != autocpl->startname) { /* don't copy a string over its self */ - BLI_strncpy(autoname, autocpl->startname, autocpl->maxlen); - } - } - - MEM_freeN(autocpl->truncate); - MEM_freeN(autocpl); - return match; -} - -static void ui_but_update_and_icon_set(uiBut *but, int icon) -{ - if (icon) { - ui_def_but_icon(but, icon, UI_HAS_ICON); - } - - ui_but_update(but); -} - -static uiBut *uiDefButBit(uiBlock *block, - int type, - int bit, - int retval, - const char *str, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - const int bitIdx = findBitIndex(bit); - if (bitIdx == -1) { - return NULL; - } - return uiDefBut(block, - type | UI_BUT_POIN_BIT | bitIdx, - retval, - str, - x, - y, - width, - height, - poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButF(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - float *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefBut(block, - type | UI_BUT_POIN_FLOAT, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButBitF(uiBlock *block, - int type, - int bit, - int retval, - const char *str, - int x, - int y, - short width, - short height, - float *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefButBit(block, - type | UI_BUT_POIN_FLOAT, - bit, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButI(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - int *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefBut(block, - type | UI_BUT_POIN_INT, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButBitI(uiBlock *block, - int type, - int bit, - int retval, - const char *str, - int x, - int y, - short width, - short height, - int *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefButBit(block, - type | UI_BUT_POIN_INT, - bit, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButS(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - short *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefBut(block, - type | UI_BUT_POIN_SHORT, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButBitS(uiBlock *block, - int type, - int bit, - int retval, - const char *str, - int x, - int y, - short width, - short height, - short *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefButBit(block, - type | UI_BUT_POIN_SHORT, - bit, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButC(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - char *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefBut(block, - type | UI_BUT_POIN_CHAR, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButBitC(uiBlock *block, - int type, - int bit, - int retval, - const char *str, - int x, - int y, - short width, - short height, - char *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefButBit(block, - type | UI_BUT_POIN_CHAR, - bit, - retval, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefButR(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - const char *propname, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but_rna_propname( - block, type, retval, str, x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); - ui_but_update(but); - return but; -} -uiBut *uiDefButR_prop(uiBlock *block, - int type, - int retval, - const char *str, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but_rna( - block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); - ui_but_update(but); - return but; -} - -uiBut *uiDefButO_ptr(uiBlock *block, - int type, - wmOperatorType *ot, - int opcontext, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); - ui_but_update(but); - return but; -} -uiBut *uiDefButO(uiBlock *block, - int type, - const char *opname, - int opcontext, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); - if (str == NULL && ot == NULL) { - str = opname; - } - return uiDefButO_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); -} - -/* if a1==1.0 then a2 is an extra icon blending factor (alpha 0.0 - 1.0) */ -uiBut *uiDefIconBut(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but( - block, type, retval, "", x, y, width, height, poin, min, max, a1, a2, tip); - ui_but_update_and_icon_set(but, icon); - return but; -} -static uiBut *uiDefIconButBit(uiBlock *block, - int type, - int bit, - int retval, - int icon, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - const int bitIdx = findBitIndex(bit); - if (bitIdx == -1) { - return NULL; - } - return uiDefIconBut(block, - type | UI_BUT_POIN_BIT | bitIdx, - retval, - icon, - x, - y, - width, - height, - poin, - min, - max, - a1, - a2, - tip); -} - -uiBut *uiDefIconButF(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - float *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconBut(block, - type | UI_BUT_POIN_FLOAT, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButBitF(uiBlock *block, - int type, - int bit, - int retval, - int icon, - int x, - int y, - short width, - short height, - float *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconButBit(block, - type | UI_BUT_POIN_FLOAT, - bit, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButI(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - int *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconBut(block, - type | UI_BUT_POIN_INT, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButBitI(uiBlock *block, - int type, - int bit, - int retval, - int icon, - int x, - int y, - short width, - short height, - int *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconButBit(block, - type | UI_BUT_POIN_INT, - bit, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButS(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - short *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconBut(block, - type | UI_BUT_POIN_SHORT, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButBitS(uiBlock *block, - int type, - int bit, - int retval, - int icon, - int x, - int y, - short width, - short height, - short *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconButBit(block, - type | UI_BUT_POIN_SHORT, - bit, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButC(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - char *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconBut(block, - type | UI_BUT_POIN_CHAR, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButBitC(uiBlock *block, - int type, - int bit, - int retval, - int icon, - int x, - int y, - short width, - short height, - char *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconButBit(block, - type | UI_BUT_POIN_CHAR, - bit, - retval, - icon, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconButR(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - const char *propname, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but_rna_propname( - block, type, retval, "", x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); - ui_but_update_and_icon_set(but, icon); - return but; -} -uiBut *uiDefIconButR_prop(uiBlock *block, - int type, - int retval, - int icon, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but_rna( - block, type, retval, "", x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); - ui_but_update_and_icon_set(but, icon); - return but; -} - -uiBut *uiDefIconButO_ptr(uiBlock *block, - int type, - wmOperatorType *ot, - int opcontext, - int icon, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, "", x, y, width, height, tip); - ui_but_update_and_icon_set(but, icon); - return but; -} -uiBut *uiDefIconButO(uiBlock *block, - int type, - const char *opname, - int opcontext, - int icon, - int x, - int y, - short width, - short height, - const char *tip) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); - return uiDefIconButO_ptr(block, type, ot, opcontext, icon, x, y, width, height, tip); -} - -/* Button containing both string label and icon */ -uiBut *uiDefIconTextBut(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but( - block, type, retval, str, x, y, width, height, poin, min, max, a1, a2, tip); - ui_but_update_and_icon_set(but, icon); - but->drawflag |= UI_BUT_ICON_LEFT; - return but; -} -static uiBut *uiDefIconTextButBit(uiBlock *block, - int type, - int bit, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - void *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - const int bitIdx = findBitIndex(bit); - if (bitIdx == -1) { - return NULL; - } - return uiDefIconTextBut(block, - type | UI_BUT_POIN_BIT | bitIdx, - retval, - icon, - str, - x, - y, - width, - height, - poin, - min, - max, - a1, - a2, - tip); -} - -uiBut *uiDefIconTextButF(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - float *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextBut(block, - type | UI_BUT_POIN_FLOAT, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButBitF(uiBlock *block, - int type, - int bit, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - float *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextButBit(block, - type | UI_BUT_POIN_FLOAT, - bit, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButI(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - int *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextBut(block, - type | UI_BUT_POIN_INT, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButBitI(uiBlock *block, - int type, - int bit, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - int *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextButBit(block, - type | UI_BUT_POIN_INT, - bit, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButS(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - short *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextBut(block, - type | UI_BUT_POIN_SHORT, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButBitS(uiBlock *block, - int type, - int bit, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - short *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextButBit(block, - type | UI_BUT_POIN_SHORT, - bit, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButC(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - char *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextBut(block, - type | UI_BUT_POIN_CHAR, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButBitC(uiBlock *block, - int type, - int bit, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - char *poin, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - return uiDefIconTextButBit(block, - type | UI_BUT_POIN_CHAR, - bit, - retval, - icon, - str, - x, - y, - width, - height, - (void *)poin, - min, - max, - a1, - a2, - tip); -} -uiBut *uiDefIconTextButR(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - const char *propname, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but_rna_propname( - block, type, retval, str, x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); - ui_but_update_and_icon_set(but, icon); - but->drawflag |= UI_BUT_ICON_LEFT; - return but; -} -uiBut *uiDefIconTextButR_prop(uiBlock *block, - int type, - int retval, - int icon, - const char *str, - int x, - int y, - short width, - short height, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - float min, - float max, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but_rna( - block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); - ui_but_update_and_icon_set(but, icon); - but->drawflag |= UI_BUT_ICON_LEFT; - return but; -} -uiBut *uiDefIconTextButO_ptr(uiBlock *block, - int type, - wmOperatorType *ot, - int opcontext, - int icon, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); - ui_but_update_and_icon_set(but, icon); - but->drawflag |= UI_BUT_ICON_LEFT; - return but; -} -uiBut *uiDefIconTextButO(uiBlock *block, - int type, - const char *opname, - int opcontext, - int icon, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); - if (str && str[0] == '\0') { - return uiDefIconButO_ptr(block, type, ot, opcontext, icon, x, y, width, height, tip); - } - return uiDefIconTextButO_ptr(block, type, ot, opcontext, icon, str, x, y, width, height, tip); -} - -/* END Button containing both string label and icon */ - -/* cruft to make uiBlock and uiBut private */ - -int UI_blocklist_min_y_get(ListBase *lb) -{ - int min = 0; - - LISTBASE_FOREACH (uiBlock *, block, lb) { - if (block == lb->first || block->rect.ymin < min) { - min = block->rect.ymin; - } - } - - return min; -} - -void UI_block_direction_set(uiBlock *block, char direction) -{ - block->direction = direction; -} - -/* this call escapes if there's alignment flags */ -void UI_block_order_flip(uiBlock *block) -{ - float centy, miny = 10000, maxy = -10000; - - if (U.uiflag & USER_MENUFIXEDORDER) { - return; - } - if (block->flag & UI_BLOCK_NO_FLIP) { - return; - } - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->drawflag & UI_BUT_ALIGN) { - return; - } - if (but->rect.ymin < miny) { - miny = but->rect.ymin; - } - if (but->rect.ymax > maxy) { - maxy = but->rect.ymax; - } - } - /* mirror trick */ - centy = (miny + maxy) / 2.0f; - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->rect.ymin = centy - (but->rect.ymin - centy); - but->rect.ymax = centy - (but->rect.ymax - centy); - SWAP(float, but->rect.ymin, but->rect.ymax); - } - - block->flag ^= UI_BLOCK_IS_FLIP; -} - -void UI_block_flag_enable(uiBlock *block, int flag) -{ - block->flag |= flag; -} - -void UI_block_flag_disable(uiBlock *block, int flag) -{ - block->flag &= ~flag; -} - -void UI_but_flag_enable(uiBut *but, int flag) -{ - but->flag |= flag; -} - -void UI_but_flag_disable(uiBut *but, int flag) -{ - but->flag &= ~flag; -} - -bool UI_but_flag_is_set(uiBut *but, int flag) -{ - return (but->flag & flag) != 0; -} - -void UI_but_drawflag_enable(uiBut *but, int flag) -{ - but->drawflag |= flag; -} - -void UI_but_drawflag_disable(uiBut *but, int flag) -{ - but->drawflag &= ~flag; -} - -void UI_but_disable(uiBut *but, const char *disabled_hint) -{ - UI_but_flag_enable(but, UI_BUT_DISABLED); - - /* Only one disabled hint at a time currently. Don't override the previous one here. */ - if (but->disabled_info && but->disabled_info[0]) { - return; - } - - but->disabled_info = disabled_hint; -} - -void UI_but_type_set_menu_from_pulldown(uiBut *but) -{ - BLI_assert(but->type == UI_BTYPE_PULLDOWN); - but->type = UI_BTYPE_MENU; - UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT); - UI_but_drawflag_enable(but, UI_BUT_TEXT_LEFT); -} - -int UI_but_return_value_get(uiBut *but) -{ - return but->retval; -} - -void UI_but_drag_set_id(uiBut *but, ID *id) -{ - but->dragtype = WM_DRAG_ID; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; - } - but->dragpoin = (void *)id; -} - -void UI_but_drag_set_asset(uiBut *but, - const char *name, - const char *path, - int id_type, - int icon, - struct ImBuf *imb, - float scale) -{ - wmDragAsset *asset_drag = MEM_mallocN(sizeof(*asset_drag), "wmDragAsset"); - - BLI_strncpy(asset_drag->name, name, sizeof(asset_drag->name)); - asset_drag->path = path; - asset_drag->id_type = id_type; - - but->dragtype = WM_DRAG_ASSET; - ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - } - but->dragpoin = asset_drag; - but->dragflag |= UI_BUT_DRAGPOIN_FREE; - but->imb = imb; - but->imb_scale = scale; -} - -void UI_but_drag_set_rna(uiBut *but, PointerRNA *ptr) -{ - but->dragtype = WM_DRAG_RNA; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; - } - but->dragpoin = (void *)ptr; -} - -void UI_but_drag_set_path(uiBut *but, const char *path, const bool use_free) -{ - but->dragtype = WM_DRAG_PATH; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; - } - but->dragpoin = (void *)path; - if (use_free) { - but->dragflag |= UI_BUT_DRAGPOIN_FREE; - } -} - -void UI_but_drag_set_name(uiBut *but, const char *name) -{ - but->dragtype = WM_DRAG_NAME; - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; - } - but->dragpoin = (void *)name; -} - -/* value from button itself */ -void UI_but_drag_set_value(uiBut *but) -{ - but->dragtype = WM_DRAG_VALUE; -} - -void UI_but_drag_set_image( - uiBut *but, const char *path, int icon, struct ImBuf *imb, float scale, const bool use_free) -{ - but->dragtype = WM_DRAG_PATH; - ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ - if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { - WM_drag_data_free(but->dragtype, but->dragpoin); - but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; - } - but->dragpoin = (void *)path; - if (use_free) { - but->dragflag |= UI_BUT_DRAGPOIN_FREE; - } - but->imb = imb; - but->imb_scale = scale; -} - -PointerRNA *UI_but_operator_ptr_get(uiBut *but) -{ - if (but->optype && !but->opptr) { - but->opptr = MEM_callocN(sizeof(PointerRNA), "uiButOpPtr"); - WM_operator_properties_create_ptr(but->opptr, but->optype); - } - - return but->opptr; -} - -void UI_but_unit_type_set(uiBut *but, const int unit_type) -{ - but->unit_type = (uchar)(RNA_SUBTYPE_UNIT_VALUE(unit_type)); -} - -int UI_but_unit_type_get(const uiBut *but) -{ - const int ownUnit = (int)but->unit_type; - - /* own unit define always takes precedence over RNA provided, allowing for overriding - * default value provided in RNA in a few special cases (i.e. Active Keyframe in Graph Edit) - */ - /* XXX: this doesn't allow clearing unit completely, though the same could be said for icons */ - if ((ownUnit != 0) || (but->rnaprop == NULL)) { - return ownUnit << 16; - } - return RNA_SUBTYPE_UNIT(RNA_property_subtype(but->rnaprop)); -} - -void UI_block_func_handle_set(uiBlock *block, uiBlockHandleFunc func, void *arg) -{ - block->handle_func = func; - block->handle_func_arg = arg; -} - -void UI_block_func_butmenu_set(uiBlock *block, uiMenuHandleFunc func, void *arg) -{ - block->butm_func = func; - block->butm_func_arg = arg; -} - -void UI_block_func_set(uiBlock *block, uiButHandleFunc func, void *arg1, void *arg2) -{ - block->func = func; - block->func_arg1 = arg1; - block->func_arg2 = arg2; -} - -void UI_block_funcN_set(uiBlock *block, uiButHandleNFunc funcN, void *argN, void *arg2) -{ - if (block->func_argN) { - MEM_freeN(block->func_argN); - } - - block->funcN = funcN; - block->func_argN = argN; - block->func_arg2 = arg2; -} - -void UI_but_func_rename_set(uiBut *but, uiButHandleRenameFunc func, void *arg1) -{ - but->rename_func = func; - but->rename_arg1 = arg1; -} - -void UI_but_func_drawextra_set( - uiBlock *block, - void (*func)(const bContext *C, void *idv, void *arg1, void *arg2, rcti *rect), - void *arg1, - void *arg2) -{ - block->drawextra = func; - block->drawextra_arg1 = arg1; - block->drawextra_arg2 = arg2; -} - -void UI_but_func_set(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2) -{ - but->func = func; - but->func_arg1 = arg1; - but->func_arg2 = arg2; -} - -void UI_but_funcN_set(uiBut *but, uiButHandleNFunc funcN, void *argN, void *arg2) -{ - if (but->func_argN) { - MEM_freeN(but->func_argN); - } - - but->funcN = funcN; - but->func_argN = argN; - but->func_arg2 = arg2; -} - -void UI_but_func_complete_set(uiBut *but, uiButCompleteFunc func, void *arg) -{ - but->autocomplete_func = func; - but->autofunc_arg = arg; -} - -void UI_but_func_menu_step_set(uiBut *but, uiMenuStepFunc func) -{ - but->menu_step_func = func; -} - -void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *argN) -{ - but->tip_func = func; - if (but->tip_argN) { - MEM_freeN(but->tip_argN); - } - but->tip_argN = argN; -} - -void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, void *arg) -{ - but->pushed_state_func = func; - but->pushed_state_arg = arg; -} - -uiBut *uiDefBlockBut(uiBlock *block, - uiBlockCreateFunc func, - void *arg, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - but->block_create_func = func; - ui_but_update(but); - return but; -} - -uiBut *uiDefBlockButN(uiBlock *block, - uiBlockCreateFunc func, - void *argN, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, NULL, 0.0, 0.0, 0.0, 0.0, tip); - but->block_create_func = func; - if (but->func_argN) { - MEM_freeN(but->func_argN); - } - but->func_argN = argN; - ui_but_update(but); - return but; -} - -uiBut *uiDefPulldownBut(uiBlock *block, - uiBlockCreateFunc func, - void *arg, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - but->block_create_func = func; - ui_but_update(but); - return but; -} - -uiBut *uiDefMenuBut(uiBlock *block, - uiMenuCreateFunc func, - void *arg, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - but->menu_create_func = func; - ui_but_update(but); - return but; -} - -uiBut *uiDefIconTextMenuBut(uiBlock *block, - uiMenuCreateFunc func, - void *arg, - int icon, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - - ui_def_but_icon(but, icon, UI_HAS_ICON); - - but->drawflag |= UI_BUT_ICON_LEFT; - ui_but_submenu_enable(block, but); - - but->menu_create_func = func; - ui_but_update(but); - - return but; -} - -uiBut *uiDefIconMenuBut(uiBlock *block, - uiMenuCreateFunc func, - void *arg, - int icon, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_PULLDOWN, 0, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - - ui_def_but_icon(but, icon, UI_HAS_ICON); - but->drawflag &= ~UI_BUT_ICON_LEFT; - - but->menu_create_func = func; - ui_but_update(but); - - return but; -} - -/* Block button containing both string label and icon */ -uiBut *uiDefIconTextBlockBut(uiBlock *block, - uiBlockCreateFunc func, - void *arg, - int icon, - const char *str, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - - /* XXX temp, old menu calls pass on icon arrow, which is now UI_BUT_ICON_SUBMENU flag */ - if (icon != ICON_RIGHTARROW_THIN) { - ui_def_but_icon(but, icon, 0); - but->drawflag |= UI_BUT_ICON_LEFT; - } - but->flag |= UI_HAS_ICON; - ui_but_submenu_enable(block, but); - - but->block_create_func = func; - ui_but_update(but); - - return but; -} - -/* Block button containing icon */ -uiBut *uiDefIconBlockBut(uiBlock *block, - uiBlockCreateFunc func, - void *arg, - int retval, - int icon, - int x, - int y, - short width, - short height, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_BLOCK, retval, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); - - ui_def_but_icon(but, icon, UI_HAS_ICON); - - but->drawflag |= UI_BUT_ICON_LEFT; - - but->block_create_func = func; - ui_but_update(but); - - return but; -} - -uiBut *uiDefKeyevtButS(uiBlock *block, - int retval, - const char *str, - int x, - int y, - short width, - short height, - short *spoin, - const char *tip) -{ - uiBut *but = ui_def_but(block, - UI_BTYPE_KEY_EVENT | UI_BUT_POIN_SHORT, - retval, - str, - x, - y, - width, - height, - spoin, - 0.0, - 0.0, - 0.0, - 0.0, - tip); - ui_but_update(but); - return but; -} - -/* short pointers hardcoded */ -/* modkeypoin will be set to KM_SHIFT, KM_ALT, KM_CTRL, KM_OSKEY bits */ -uiBut *uiDefHotKeyevtButS(uiBlock *block, - int retval, - const char *str, - int x, - int y, - short width, - short height, - short *keypoin, - const short *modkeypoin, - const char *tip) -{ - uiBut *but = ui_def_but(block, - UI_BTYPE_HOTKEY_EVENT | UI_BUT_POIN_SHORT, - retval, - str, - x, - y, - width, - height, - keypoin, - 0.0, - 0.0, - 0.0, - 0.0, - tip); - but->modifier_key = *modkeypoin; - ui_but_update(but); - return but; -} - -/* arg is pointer to string/name, use UI_but_func_search_set() below to make this work */ -/* here a1 and a2, if set, control thumbnail preview rows/cols */ -uiBut *uiDefSearchBut(uiBlock *block, - void *arg, - int retval, - int icon, - int maxlen, - int x, - int y, - short width, - short height, - float a1, - float a2, - const char *tip) -{ - uiBut *but = ui_def_but( - block, UI_BTYPE_SEARCH_MENU, retval, "", x, y, width, height, arg, 0.0, maxlen, a1, a2, tip); - - ui_def_but_icon(but, icon, UI_HAS_ICON); - - but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; - - ui_but_update(but); - - return but; -} - -/** - * \note The item-pointer (referred to below) is a per search item user pointer - * passed to #UI_search_item_add (stored in #uiSearchItems.pointers). - * - * \param search_create_fn: Function to create the menu. - * \param search_update_fn: Function to refresh search content after the search text has changed. - * \param arg: user value. - * \param free_arg: Set to true if the argument is newly allocated memory for every redraw and - * should be freed when the button is destroyed. - * \param search_arg_free_fn: When non-null, use this function to free \a arg. - * \param search_exec_fn: Function that executes the action, gets \a arg as the first argument. - * The second argument as the active item-pointer - * \param active: When non-null, this item-pointer item will be visible and selected, - * otherwise the first item will be selected. - */ -void UI_but_func_search_set(uiBut *but, - uiButSearchCreateFn search_create_fn, - uiButSearchUpdateFn search_update_fn, - void *arg, - const bool free_arg, - uiButSearchArgFreeFn search_arg_free_fn, - uiButHandleFunc search_exec_fn, - void *active) -{ - uiButSearch *search_but = (uiButSearch *)but; - - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - /* needed since callers don't have access to internal functions - * (as an alternative we could expose it) */ - if (search_create_fn == NULL) { - search_create_fn = ui_searchbox_create_generic; - } - - if (search_but->arg_free_fn != NULL) { - search_but->arg_free_fn(search_but->arg); - search_but->arg = NULL; - } - - search_but->popup_create_fn = search_create_fn; - search_but->items_update_fn = search_update_fn; - search_but->item_active = active; - - search_but->arg = arg; - search_but->arg_free_fn = search_arg_free_fn; - - if (search_exec_fn) { -#ifdef DEBUG - if (search_but->but.func) { - /* watch this, can be cause of much confusion, see: T47691 */ - printf("%s: warning, overwriting button callback with search function callback!\n", - __func__); - } -#endif - /* Handling will pass the active item as arg2 later, so keep it NULL here. */ - if (free_arg) { - UI_but_funcN_set(but, search_exec_fn, search_but->arg, NULL); - } - else { - UI_but_func_set(but, search_exec_fn, search_but->arg, NULL); - } - } - - /* search buttons show red-alert if item doesn't exist, not for menus. Don't do this for - * buttons where any result is valid anyway, since any string will be valid anyway. */ - if (0 == (but->block->flag & UI_BLOCK_LOOP) && !search_but->results_are_suggestions) { - /* skip empty buttons, not all buttons need input, we only show invalid */ - if (but->drawstr[0]) { - ui_but_search_refresh(search_but); - } - } -} - -void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn) -{ - uiButSearch *but_search = (uiButSearch *)but; - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - but_search->item_context_menu_fn = context_menu_fn; -} - -/** - * \param search_sep_string: when not NULL, this string is used as a separator, - * showing the icon and highlighted text after the last instance of this string. - */ -void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string) -{ - uiButSearch *but_search = (uiButSearch *)but; - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - but_search->item_sep_string = search_sep_string; -} - -void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn) -{ - uiButSearch *but_search = (uiButSearch *)but; - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - but_search->item_tooltip_fn = tooltip_fn; -} - -void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value) -{ - uiButSearch *but_search = (uiButSearch *)but; - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - but_search->results_are_suggestions = value; -} - -/* Callbacks for operator search button. */ -static void operator_enum_search_update_fn(const struct bContext *C, - void *but, - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - wmOperatorType *ot = ((uiBut *)but)->optype; - PropertyRNA *prop = ot->prop; - - if (prop == NULL) { - printf("%s: %s has no enum property set\n", __func__, ot->idname); - } - else if (RNA_property_type(prop) != PROP_ENUM) { - printf("%s: %s \"%s\" is not an enum property\n", - __func__, - ot->idname, - RNA_property_identifier(prop)); - } - else { - PointerRNA *ptr = UI_but_operator_ptr_get(but); /* Will create it if needed! */ - - bool do_free; - const EnumPropertyItem *all_items; - RNA_property_enum_items_gettexted((bContext *)C, ptr, prop, &all_items, NULL, &do_free); - - StringSearch *search = BLI_string_search_new(); - for (const EnumPropertyItem *item = all_items; item->identifier; item++) { - BLI_string_search_add(search, item->name, (void *)item); - } - - const EnumPropertyItem **filtered_items; - const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items); - - for (int i = 0; i < filtered_amount; i++) { - const EnumPropertyItem *item = filtered_items[i]; - /* note: need to give the index rather than the - * identifier because the enum can be freed */ - if (!UI_search_item_add( - items, item->name, POINTER_FROM_INT(item->value), item->icon, 0, 0)) { - break; - } - } - - MEM_freeN((void *)filtered_items); - BLI_string_search_free(search); - - if (do_free) { - MEM_freeN((void *)all_items); - } - } -} - -static void operator_enum_search_exec_fn(struct bContext *UNUSED(C), void *but, void *arg2) -{ - wmOperatorType *ot = ((uiBut *)but)->optype; - PointerRNA *opptr = UI_but_operator_ptr_get(but); /* Will create it if needed! */ - - if (ot) { - if (ot->prop) { - RNA_property_enum_set(opptr, ot->prop, POINTER_AS_INT(arg2)); - /* We do not call op from here, will be called by button code. - * ui_apply_but_funcs_after() (in interface_handlers.c) - * called this func before checking operators, - * because one of its parameters is the button itself! */ - } - else { - printf("%s: op->prop for '%s' is NULL\n", __func__, ot->idname); - } - } -} - -/** - * Same parameters as for uiDefSearchBut, with additional operator type and properties, - * used by callback to call again the right op with the right options (properties values). - */ -uiBut *uiDefSearchButO_ptr(uiBlock *block, - wmOperatorType *ot, - IDProperty *properties, - void *arg, - int retval, - int icon, - int maxlen, - int x, - int y, - short width, - short height, - float a1, - float a2, - const char *tip) -{ - uiBut *but = uiDefSearchBut(block, arg, retval, icon, maxlen, x, y, width, height, a1, a2, tip); - UI_but_func_search_set(but, - ui_searchbox_create_generic, - operator_enum_search_update_fn, - but, - false, - NULL, - operator_enum_search_exec_fn, - NULL); - - but->optype = ot; - but->opcontext = WM_OP_EXEC_DEFAULT; - - if (properties) { - PointerRNA *ptr = UI_but_operator_ptr_get(but); - /* Copy idproperties. */ - ptr->data = IDP_CopyProperty(properties); - } - - return but; -} - -void UI_but_node_link_set(uiBut *but, bNodeSocket *socket, const float draw_color[4]) -{ - but->flag |= UI_BUT_NODE_LINK; - but->custom_data = socket; - rgba_float_to_uchar(but->col, draw_color); -} - -void UI_but_number_step_size_set(uiBut *but, float step_size) -{ - uiButNumber *but_number = (uiButNumber *)but; - BLI_assert(but->type == UI_BTYPE_NUM); - - but_number->step_size = step_size; - BLI_assert(step_size > 0); -} - -void UI_but_number_precision_set(uiBut *but, float precision) -{ - uiButNumber *but_number = (uiButNumber *)but; - BLI_assert(but->type == UI_BTYPE_NUM); - - but_number->precision = precision; - /* -1 is a valid value, UI code figures out an appropriate precision then. */ - BLI_assert(precision > -2); -} - -/** - * push a new event onto event queue to activate the given button - * (usually a text-field) upon entering a popup - */ -void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but) -{ - wmEvent event; - wm_event_init_from_window(win, &event); - - event.type = EVT_BUT_OPEN; - event.val = KM_PRESS; - event.is_repeat = false; - event.customdata = but; - event.customdatafree = false; - - wm_event_add(win, &event); -} - -void UI_but_func_hold_set(uiBut *but, uiButHandleHoldFunc func, void *argN) -{ - but->hold_func = func; - but->hold_argN = argN; -} - -void UI_but_string_info_get(bContext *C, uiBut *but, ...) -{ - va_list args; - uiStringInfo *si; - - const EnumPropertyItem *items = NULL, *item = NULL; - int totitems; - bool free_items = false; - - va_start(args, but); - while ((si = (uiStringInfo *)va_arg(args, void *))) { - uiStringInfoType type = si->type; - char *tmp = NULL; - - if (type == BUT_GET_LABEL) { - if (but->str && but->str[0]) { - const char *str_sep; - size_t str_len; - - if ((but->flag & UI_BUT_HAS_SEP_CHAR) && (str_sep = strrchr(but->str, UI_SEP_CHAR))) { - str_len = (str_sep - but->str); - } - else { - str_len = strlen(but->str); - } - - tmp = BLI_strdupn(but->str, str_len); - } - else { - type = BUT_GET_RNA_LABEL; /* Fail-safe solution... */ - } - } - else if (type == BUT_GET_TIP) { - if (but->tip_func) { - tmp = but->tip_func(C, but->tip_argN, but->tip); - } - else if (but->tip && but->tip[0]) { - tmp = BLI_strdup(but->tip); - } - else { - type = BUT_GET_RNA_TIP; /* Fail-safe solution... */ - } - } - - if (type == BUT_GET_RNAPROP_IDENTIFIER) { - if (but->rnaprop) { - tmp = BLI_strdup(RNA_property_identifier(but->rnaprop)); - } - } - else if (type == BUT_GET_RNASTRUCT_IDENTIFIER) { - if (but->rnaprop && but->rnapoin.data) { - tmp = BLI_strdup(RNA_struct_identifier(but->rnapoin.type)); - } - else if (but->optype) { - tmp = BLI_strdup(but->optype->idname); - } - else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) { - MenuType *mt = UI_but_menutype_get(but); - if (mt) { - tmp = BLI_strdup(mt->idname); - } - } - else if (but->type == UI_BTYPE_POPOVER) { - PanelType *pt = UI_but_paneltype_get(but); - if (pt) { - tmp = BLI_strdup(pt->idname); - } - } - } - else if (ELEM(type, BUT_GET_RNA_LABEL, BUT_GET_RNA_TIP)) { - if (but->rnaprop) { - if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(RNA_property_ui_name(but->rnaprop)); - } - else { - const char *t = RNA_property_ui_description(but->rnaprop); - if (t && t[0]) { - tmp = BLI_strdup(t); - } - } - } - else if (but->optype) { - if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(WM_operatortype_name(but->optype, but->opptr)); - } - else { - tmp = WM_operatortype_description(C, but->optype, but->opptr); - } - } - else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER)) { - { - MenuType *mt = UI_but_menutype_get(but); - if (mt) { - if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(mt->label); - } - else { - /* Not all menus are from Python. */ - if (mt->rna_ext.srna) { - const char *t = RNA_struct_ui_description(mt->rna_ext.srna); - if (t && t[0]) { - tmp = BLI_strdup(t); - } - } - } - } - } - - if (tmp == NULL) { - wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, NULL); - if (ot) { - if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(WM_operatortype_name(ot, NULL)); - } - else { - tmp = WM_operatortype_description(C, ot, NULL); - } - } - } - - if (tmp == NULL) { - PanelType *pt = UI_but_paneltype_get(but); - if (pt) { - if (type == BUT_GET_RNA_LABEL) { - tmp = BLI_strdup(pt->label); - } - else { - /* Not all panels are from Python. */ - if (pt->rna_ext.srna) { - /* Panels don't yet have descriptions, this may be added. */ - } - } - } - } - } - } - else if (type == BUT_GET_RNA_LABEL_CONTEXT) { - const char *_tmp = BLT_I18NCONTEXT_DEFAULT; - if (but->rnaprop) { - _tmp = RNA_property_translation_context(but->rnaprop); - } - else if (but->optype) { - _tmp = RNA_struct_translation_context(but->optype->srna); - } - else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) { - MenuType *mt = UI_but_menutype_get(but); - if (mt) { - _tmp = RNA_struct_translation_context(mt->rna_ext.srna); - } - } - if (BLT_is_default_context(_tmp)) { - _tmp = BLT_I18NCONTEXT_DEFAULT_BPYRNA; - } - tmp = BLI_strdup(_tmp); - } - else if (ELEM(type, BUT_GET_RNAENUM_IDENTIFIER, BUT_GET_RNAENUM_LABEL, BUT_GET_RNAENUM_TIP)) { - PointerRNA *ptr = NULL; - PropertyRNA *prop = NULL; - int value = 0; - - /* get the enum property... */ - if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) { - /* enum property */ - ptr = &but->rnapoin; - prop = but->rnaprop; - value = (ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_TAB)) ? (int)but->hardmax : - (int)ui_but_value_get(but); - } - else if (but->optype) { - PointerRNA *opptr = UI_but_operator_ptr_get(but); - wmOperatorType *ot = but->optype; - - /* so the context is passed to itemf functions */ - WM_operator_properties_sanitize(opptr, false); - - /* if the default property of the operator is enum and it is set, - * fetch the tooltip of the selected value so that "Snap" and "Mirror" - * operator menus in the Anim Editors will show tooltips for the different - * operations instead of the meaningless generic operator tooltip - */ - if (ot->prop && RNA_property_type(ot->prop) == PROP_ENUM) { - if (RNA_struct_contains_property(opptr, ot->prop)) { - ptr = opptr; - prop = ot->prop; - value = RNA_property_enum_get(opptr, ot->prop); - } - } - } - - /* get strings from matching enum item */ - if (ptr && prop) { - if (!item) { - int i; - - RNA_property_enum_items_gettexted(C, ptr, prop, &items, &totitems, &free_items); - for (i = 0, item = items; i < totitems; i++, item++) { - if (item->identifier[0] && item->value == value) { - break; - } - } - } - if (item && item->identifier) { - if (type == BUT_GET_RNAENUM_IDENTIFIER) { - tmp = BLI_strdup(item->identifier); - } - else if (type == BUT_GET_RNAENUM_LABEL) { - tmp = BLI_strdup(item->name); - } - else if (item->description && item->description[0]) { - tmp = BLI_strdup(item->description); - } - } - } - } - else if (type == BUT_GET_OP_KEYMAP) { - if (!ui_block_is_menu(but->block)) { - char buf[128]; - if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { - tmp = BLI_strdup(buf); - } - } - } - else if (type == BUT_GET_PROP_KEYMAP) { - /* for properties that are bound to one of the context cycle, etc. keys... */ - char buf[128]; - if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { - tmp = BLI_strdup(buf); - } - } - - si->strinfo = tmp; - } - va_end(args); - - if (free_items && items) { - MEM_freeN((void *)items); - } -} - -/* Program Init/Exit */ - -void UI_init(void) -{ - ui_resources_init(); -} - -/* after reading userdef file */ -void UI_init_userdef(void) -{ - /* Initialize UI variables from values set in the preferences. */ - uiStyleInit(); -} - -void UI_reinit_font(void) -{ - uiStyleInit(); -} - -void UI_exit(void) -{ - ui_resources_free(); - ui_but_clipboard_free(); -} - -void UI_interface_tag_script_reload(void) -{ - ui_interface_tag_script_reload_queries(); -} diff --git a/source/blender/editors/interface/interface.cc b/source/blender/editors/interface/interface.cc new file mode 100644 index 00000000000..4e06e1def27 --- /dev/null +++ b/source/blender/editors/interface/interface.cc @@ -0,0 +1,7132 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include +#include /* offsetof() */ +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" +#include "DNA_workspace_types.h" + +#include "BLI_array.hh" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_string_search.h" +#include "BLI_string_utf8.h" + +#include "BLI_utildefines.h" + +#include "BLO_readfile.h" + +#include "BKE_animsys.h" +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_main.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_unit.h" + +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "BLF_api.h" +#include "BLT_translation.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_view2d.h" + +#include "IMB_imbuf.h" + +#include "WM_api.h" +#include "WM_message.h" +#include "WM_types.h" + +#include "RNA_access.h" + +#ifdef WITH_PYTHON +# include "BPY_extern_run.h" +#endif + +#include "ED_numinput.h" +#include "ED_screen.h" + +#include "IMB_colormanagement.h" + +#include "DEG_depsgraph_query.h" + +#include "interface_intern.h" + +using blender::Array; + +/* prototypes. */ +static void ui_but_to_pixelrect(struct rcti *rect, + const struct ARegion *region, + struct uiBlock *block, + struct uiBut *but); +static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *but_p); +static void ui_def_but_rna__panel_type(bContext *UNUSED(C), uiLayout *layout, void *but_p); +static void ui_def_but_rna__menu_type(bContext *UNUSED(C), uiLayout *layout, void *but_p); + +/* avoid unneeded calls to ui_but_value_get */ +#define UI_BUT_VALUE_UNSET DBL_MAX +#define UI_GET_BUT_VALUE_INIT(_but, _value) \ + if (_value == DBL_MAX) { \ + (_value) = ui_but_value_get(_but); \ + } \ + ((void)0) + +#define B_NOP -1 + +/** + * a full doc with API notes can be found in 'blender/doc/guides/interface_API.txt' + * + * `uiBlahBlah()` external function. + * `ui_blah_blah()` internal function. + */ + +static void ui_but_free(const bContext *C, uiBut *but); + +static bool ui_but_is_unit_radians_ex(UnitSettings *unit, const int unit_type) +{ + return (unit->system_rotation == USER_UNIT_ROT_RADIANS && unit_type == PROP_UNIT_ROTATION); +} + +static bool ui_but_is_unit_radians(const uiBut *but) +{ + UnitSettings *unit = but->block->unit; + const int unit_type = UI_but_unit_type_get(but); + + return ui_but_is_unit_radians_ex(unit, unit_type); +} + +/* ************* window matrix ************** */ + +void ui_block_to_window_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y) +{ + const int getsizex = BLI_rcti_size_x(®ion->winrct) + 1; + const int getsizey = BLI_rcti_size_y(®ion->winrct) + 1; + const int sx = region->winrct.xmin; + const int sy = region->winrct.ymin; + + float gx = *r_x; + float gy = *r_y; + + if (block->panel) { + gx += block->panel->ofsx; + gy += block->panel->ofsy; + } + + *r_x = ((float)sx) + + ((float)getsizex) * (0.5f + 0.5f * (gx * block->winmat[0][0] + gy * block->winmat[1][0] + + block->winmat[3][0])); + *r_y = ((float)sy) + + ((float)getsizey) * (0.5f + 0.5f * (gx * block->winmat[0][1] + gy * block->winmat[1][1] + + block->winmat[3][1])); +} + +void ui_block_to_window(const ARegion *region, uiBlock *block, int *r_x, int *r_y) +{ + float fx = *r_x; + float fy = *r_y; + + ui_block_to_window_fl(region, block, &fx, &fy); + + *r_x = (int)(fx + 0.5f); + *r_y = (int)(fy + 0.5f); +} + +void ui_block_to_window_rctf(const ARegion *region, + uiBlock *block, + rctf *rct_dst, + const rctf *rct_src) +{ + *rct_dst = *rct_src; + ui_block_to_window_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); + ui_block_to_window_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); +} + +float ui_block_to_window_scale(const ARegion *region, uiBlock *block) +{ + /* We could have function for this to avoid dummy arg. */ + float min_y = 0, max_y = 1; + float dummy_x = 0.0f; + ui_block_to_window_fl(region, block, &dummy_x, &min_y); + dummy_x = 0.0f; + ui_block_to_window_fl(region, block, &dummy_x, &max_y); + return max_y - min_y; +} + +/* for mouse cursor */ +void ui_window_to_block_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y) +{ + const int getsizex = BLI_rcti_size_x(®ion->winrct) + 1; + const int getsizey = BLI_rcti_size_y(®ion->winrct) + 1; + const int sx = region->winrct.xmin; + const int sy = region->winrct.ymin; + + const float a = 0.5f * ((float)getsizex) * block->winmat[0][0]; + const float b = 0.5f * ((float)getsizex) * block->winmat[1][0]; + const float c = 0.5f * ((float)getsizex) * (1.0f + block->winmat[3][0]); + + const float d = 0.5f * ((float)getsizey) * block->winmat[0][1]; + const float e = 0.5f * ((float)getsizey) * block->winmat[1][1]; + const float f = 0.5f * ((float)getsizey) * (1.0f + block->winmat[3][1]); + + const float px = *r_x - sx; + const float py = *r_y - sy; + + *r_y = (a * (py - f) + d * (c - px)) / (a * e - d * b); + *r_x = (px - b * (*r_y) - c) / a; + + if (block->panel) { + *r_x -= block->panel->ofsx; + *r_y -= block->panel->ofsy; + } +} + +void ui_window_to_block_rctf(const struct ARegion *region, + uiBlock *block, + rctf *rct_dst, + const rctf *rct_src) +{ + *rct_dst = *rct_src; + ui_window_to_block_fl(region, block, &rct_dst->xmin, &rct_dst->ymin); + ui_window_to_block_fl(region, block, &rct_dst->xmax, &rct_dst->ymax); +} + +void ui_window_to_block(const ARegion *region, uiBlock *block, int *r_x, int *r_y) +{ + float fx = *r_x; + float fy = *r_y; + + ui_window_to_block_fl(region, block, &fx, &fy); + + *r_x = (int)(fx + 0.5f); + *r_y = (int)(fy + 0.5f); +} + +void ui_window_to_region(const ARegion *region, int *r_x, int *r_y) +{ + *r_x -= region->winrct.xmin; + *r_y -= region->winrct.ymin; +} + +void ui_window_to_region_rcti(const ARegion *region, rcti *rect_dst, const rcti *rct_src) +{ + rect_dst->xmin = rct_src->xmin - region->winrct.xmin; + rect_dst->xmax = rct_src->xmax - region->winrct.xmin; + rect_dst->ymin = rct_src->ymin - region->winrct.ymin; + rect_dst->ymax = rct_src->ymax - region->winrct.ymin; +} + +void ui_region_to_window(const ARegion *region, int *r_x, int *r_y) +{ + *r_x += region->winrct.xmin; + *r_y += region->winrct.ymin; +} + +static void ui_update_flexible_spacing(const ARegion *region, uiBlock *block) +{ + int sepr_flex_len = 0; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->type == UI_BTYPE_SEPR_SPACER) { + sepr_flex_len++; + } + } + + if (sepr_flex_len == 0) { + return; + } + + rcti rect; + ui_but_to_pixelrect(&rect, region, block, (uiBut *)block->buttons.last); + const float buttons_width = (float)rect.xmax + UI_HEADER_OFFSET; + const float region_width = (float)region->sizex * U.dpi_fac; + + if (region_width <= buttons_width) { + return; + } + + /* We could get rid of this loop if we agree on a max number of spacer */ + Array spacers_pos(sepr_flex_len); + int i = 0; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->type == UI_BTYPE_SEPR_SPACER) { + ui_but_to_pixelrect(&rect, region, block, but); + spacers_pos[i] = rect.xmax + UI_HEADER_OFFSET; + i++; + } + } + + const float view_scale_x = UI_view2d_scale_get_x(®ion->v2d); + const float segment_width = region_width / (float)sepr_flex_len; + float offset = 0, remaining_space = region_width - buttons_width; + i = 0; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + BLI_rctf_translate(&but->rect, offset / view_scale_x, 0); + if (but->type == UI_BTYPE_SEPR_SPACER) { + /* How much the next block overlap with the current segment */ + int overlap = ((i == sepr_flex_len - 1) ? buttons_width - spacers_pos[i] : + (spacers_pos[i + 1] - spacers_pos[i]) / 2); + const int segment_end = segment_width * (i + 1); + const int spacer_end = segment_end - overlap; + const int spacer_sta = spacers_pos[i] + offset; + if (spacer_end > spacer_sta) { + const float step = min_ff(remaining_space, spacer_end - spacer_sta); + remaining_space -= step; + offset += step; + } + i++; + } + } + ui_block_bounds_calc(block); +} + +static void ui_update_window_matrix(const wmWindow *window, const ARegion *region, uiBlock *block) +{ + /* window matrix and aspect */ + if (region && region->visible) { + /* Get projection matrix which includes View2D translation and zoom. */ + GPU_matrix_projection_get(block->winmat); + block->aspect = 2.0f / fabsf(region->winx * block->winmat[0][0]); + } + else { + /* No subwindow created yet, for menus for example, so we use the main + * window instead, since buttons are created there anyway. */ + const int width = WM_window_pixels_x(window); + const int height = WM_window_pixels_y(window); + const rcti winrct = {0, width - 1, 0, height - 1}; + + wmGetProjectionMatrix(block->winmat, &winrct); + block->aspect = 2.0f / fabsf(width * block->winmat[0][0]); + } +} + +/** + * Popups will add a margin to #ARegion.winrct for shadow, + * for interactivity (point-inside tests for eg), we want the winrct without the margin added. + */ +void ui_region_winrct_get_no_margin(const struct ARegion *region, struct rcti *r_rect) +{ + uiBlock *block = (uiBlock *)region->uiblocks.first; + if (block && (block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_RADIAL) == 0) { + BLI_rcti_rctf_copy_floor(r_rect, &block->rect); + BLI_rcti_translate(r_rect, region->winrct.xmin, region->winrct.ymin); + } + else { + *r_rect = region->winrct; + } +} + +/* ******************* block calc ************************* */ + +void UI_block_translate(uiBlock *block, int x, int y) +{ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + BLI_rctf_translate(&but->rect, x, y); + } + + BLI_rctf_translate(&block->rect, x, y); +} + +static bool ui_but_is_row_alignment_group(const uiBut *left, const uiBut *right) +{ + const bool is_same_align_group = (left->alignnr && (left->alignnr == right->alignnr)); + return is_same_align_group && (left->rect.xmin < right->rect.xmin); +} + +static void ui_block_bounds_calc_text(uiBlock *block, float offset) +{ + const uiStyle *style = UI_style_get(); + uiBut *col_bt; + int i = 0, j, x1addval = offset; + + UI_fontstyle_set(&style->widget); + + uiBut *init_col_bt = (uiBut *)block->buttons.first; + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (!ELEM(bt->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) { + j = BLF_width(style->widget.uifont_id, bt->drawstr, sizeof(bt->drawstr)); + + if (j > i) { + i = j; + } + } + + /* Skip all buttons that are in a horizontal alignment group. + * We don't want to split them apart (but still check the row's width and apply current + * offsets). */ + if (bt->next && ui_but_is_row_alignment_group(bt, bt->next)) { + int width = 0; + const int alignnr = bt->alignnr; + for (col_bt = bt; col_bt && col_bt->alignnr == alignnr; col_bt = col_bt->next) { + width += BLI_rctf_size_x(&col_bt->rect); + col_bt->rect.xmin += x1addval; + col_bt->rect.xmax += x1addval; + } + if (width > i) { + i = width; + } + /* Give the following code the last button in the alignment group, there might have to be a + * split immediately after. */ + bt = col_bt ? col_bt->prev : NULL; + } + + if (bt && bt->next && bt->rect.xmin < bt->next->rect.xmin) { + /* End of this column, and it's not the last one. */ + for (col_bt = init_col_bt; col_bt->prev != bt; col_bt = col_bt->next) { + col_bt->rect.xmin = x1addval; + col_bt->rect.xmax = x1addval + i + block->bounds; + + ui_but_update(col_bt); /* clips text again */ + } + + /* And we prepare next column. */ + x1addval += i + block->bounds; + i = 0; + init_col_bt = col_bt; + } + } + + /* Last column. */ + for (col_bt = init_col_bt; col_bt; col_bt = col_bt->next) { + /* Recognize a horizontally arranged alignment group and skip its items. */ + if (col_bt->next && ui_but_is_row_alignment_group(col_bt, col_bt->next)) { + const int alignnr = col_bt->alignnr; + for (; col_bt && col_bt->alignnr == alignnr; col_bt = col_bt->next) { + /* pass */ + } + } + if (!col_bt) { + break; + } + + col_bt->rect.xmin = x1addval; + col_bt->rect.xmax = max_ff(x1addval + i + block->bounds, offset + block->minbounds); + + ui_but_update(col_bt); /* clips text again */ + } +} + +void ui_block_bounds_calc(uiBlock *block) +{ + if (BLI_listbase_is_empty(&block->buttons)) { + if (block->panel) { + block->rect.xmin = 0.0; + block->rect.xmax = block->panel->sizex; + block->rect.ymin = 0.0; + block->rect.ymax = block->panel->sizey; + } + } + else { + + BLI_rctf_init_minmax(&block->rect); + + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + BLI_rctf_union(&block->rect, &bt->rect); + } + + block->rect.xmin -= block->bounds; + block->rect.ymin -= block->bounds; + block->rect.xmax += block->bounds; + block->rect.ymax += block->bounds; + } + + block->rect.xmax = block->rect.xmin + max_ff(BLI_rctf_size_x(&block->rect), block->minbounds); + + /* hardcoded exception... but that one is annoying with larger safety */ + uiBut *bt = (uiBut *)block->buttons.first; + const int xof = ((bt && STRPREFIX(bt->str, "ERROR")) ? 10 : 40) * U.dpi_fac; + + block->safety.xmin = block->rect.xmin - xof; + block->safety.ymin = block->rect.ymin - xof; + block->safety.xmax = block->rect.xmax + xof; + block->safety.ymax = block->rect.ymax + xof; +} + +static void ui_block_bounds_calc_centered(wmWindow *window, uiBlock *block) +{ + /* note: this is used for the splash where window bounds event has not been + * updated by ghost, get the window bounds from ghost directly */ + + const int xmax = WM_window_pixels_x(window); + const int ymax = WM_window_pixels_y(window); + + ui_block_bounds_calc(block); + + const int width = BLI_rctf_size_x(&block->rect); + const int height = BLI_rctf_size_y(&block->rect); + + const int startx = (xmax * 0.5f) - (width * 0.5f); + const int starty = (ymax * 0.5f) - (height * 0.5f); + + UI_block_translate(block, startx - block->rect.xmin, starty - block->rect.ymin); + + /* now recompute bounds and safety */ + ui_block_bounds_calc(block); +} + +static void ui_block_bounds_calc_centered_pie(uiBlock *block) +{ + const int xy[2] = { + (int)block->pie_data.pie_center_spawned[0], + (int)block->pie_data.pie_center_spawned[1], + }; + + UI_block_translate(block, xy[0], xy[1]); + + /* now recompute bounds and safety */ + ui_block_bounds_calc(block); +} + +static void ui_block_bounds_calc_popup( + wmWindow *window, uiBlock *block, eBlockBoundsCalc bounds_calc, const int xy[2], int r_xy[2]) +{ + const int oldbounds = block->bounds; + + /* compute mouse position with user defined offset */ + ui_block_bounds_calc(block); + + const int xmax = WM_window_pixels_x(window); + const int ymax = WM_window_pixels_y(window); + + int oldwidth = BLI_rctf_size_x(&block->rect); + int oldheight = BLI_rctf_size_y(&block->rect); + + /* first we ensure wide enough text bounds */ + if (bounds_calc == UI_BLOCK_BOUNDS_POPUP_MENU) { + if (block->flag & UI_BLOCK_LOOP) { + block->bounds = 2.5f * UI_UNIT_X; + ui_block_bounds_calc_text(block, block->rect.xmin); + } + } + + /* next we recompute bounds */ + block->bounds = oldbounds; + ui_block_bounds_calc(block); + + /* and we adjust the position to fit within window */ + const int width = BLI_rctf_size_x(&block->rect); + const int height = BLI_rctf_size_y(&block->rect); + + /* avoid divide by zero below, caused by calling with no UI, but better not crash */ + oldwidth = oldwidth > 0 ? oldwidth : MAX2(1, width); + oldheight = oldheight > 0 ? oldheight : MAX2(1, height); + + /* offset block based on mouse position, user offset is scaled + * along in case we resized the block in ui_block_bounds_calc_text */ + rcti rect; + const int raw_x = rect.xmin = xy[0] + block->rect.xmin + + (block->bounds_offset[0] * width) / oldwidth; + int raw_y = rect.ymin = xy[1] + block->rect.ymin + + (block->bounds_offset[1] * height) / oldheight; + rect.xmax = rect.xmin + width; + rect.ymax = rect.ymin + height; + + rcti rect_bounds; + const int margin = UI_SCREEN_MARGIN; + rect_bounds.xmin = margin; + rect_bounds.ymin = margin; + rect_bounds.xmax = xmax - margin; + rect_bounds.ymax = ymax - UI_POPUP_MENU_TOP; + + int ofs_dummy[2]; + BLI_rcti_clamp(&rect, &rect_bounds, ofs_dummy); + UI_block_translate(block, rect.xmin - block->rect.xmin, rect.ymin - block->rect.ymin); + + /* now recompute bounds and safety */ + ui_block_bounds_calc(block); + + /* If given, adjust input coordinates such that they would generate real final popup position. + * Needed to handle correctly floating panels once they have been dragged around, + * see T52999. */ + if (r_xy) { + r_xy[0] = xy[0] + block->rect.xmin - raw_x; + r_xy[1] = xy[1] + block->rect.ymin - raw_y; + } +} + +/* used for various cases */ +void UI_block_bounds_set_normal(uiBlock *block, int addval) +{ + if (block == NULL) { + return; + } + + block->bounds = addval; + block->bounds_type = UI_BLOCK_BOUNDS; +} + +/* used for pulldowns */ +void UI_block_bounds_set_text(uiBlock *block, int addval) +{ + block->bounds = addval; + block->bounds_type = UI_BLOCK_BOUNDS_TEXT; +} + +/* used for block popups */ +void UI_block_bounds_set_popup(uiBlock *block, int addval, const int bounds_offset[2]) +{ + block->bounds = addval; + block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MOUSE; + if (bounds_offset != NULL) { + block->bounds_offset[0] = bounds_offset[0]; + block->bounds_offset[1] = bounds_offset[1]; + } + else { + block->bounds_offset[0] = 0; + block->bounds_offset[1] = 0; + } +} + +/* used for menu popups */ +void UI_block_bounds_set_menu(uiBlock *block, int addval, const int bounds_offset[2]) +{ + block->bounds = addval; + block->bounds_type = UI_BLOCK_BOUNDS_POPUP_MENU; + if (bounds_offset != NULL) { + copy_v2_v2_int(block->bounds_offset, bounds_offset); + } + else { + zero_v2_int(block->bounds_offset); + } +} + +/* used for centered popups, i.e. splash */ +void UI_block_bounds_set_centered(uiBlock *block, int addval) +{ + block->bounds = addval; + block->bounds_type = UI_BLOCK_BOUNDS_POPUP_CENTER; +} + +void UI_block_bounds_set_explicit(uiBlock *block, int minx, int miny, int maxx, int maxy) +{ + block->rect.xmin = minx; + block->rect.ymin = miny; + block->rect.xmax = maxx; + block->rect.ymax = maxy; + block->bounds_type = UI_BLOCK_BOUNDS_NONE; +} + +static float ui_but_get_float_precision(uiBut *but) +{ + if (but->type == UI_BTYPE_NUM) { + return ((uiButNumber *)but)->precision; + } + + return but->a2; +} + +static int ui_but_calc_float_precision(uiBut *but, double value) +{ + int prec = (int)ui_but_get_float_precision(but); + + /* first check for various special cases: + * * If button is radians, we want additional precision (see T39861). + * * If prec is not set, we fallback to a simple default */ + if (ui_but_is_unit_radians(but) && prec < 5) { + prec = 5; + } + else if (prec == -1) { + prec = (but->hardmax < 10.001f) ? 3 : 2; + } + else { + CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); + } + + return UI_calc_float_precision(prec, value); +} + +/* ************** BLOCK ENDING FUNCTION ************* */ + +bool ui_but_rna_equals(const uiBut *a, const uiBut *b) +{ + return ui_but_rna_equals_ex(a, &b->rnapoin, b->rnaprop, b->rnaindex); +} + +bool ui_but_rna_equals_ex(const uiBut *but, + const PointerRNA *ptr, + const PropertyRNA *prop, + int index) +{ + if (but->rnapoin.data != ptr->data) { + return false; + } + if (but->rnaprop != prop || but->rnaindex != index) { + return false; + } + + return true; +} + +/* NOTE: if but->poin is allocated memory for every defbut, things fail... */ +static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut) +{ + /* various properties are being compared here, hopefully sufficient + * to catch all cases, but it is simple to add more checks later */ + if (but->retval != oldbut->retval) { + return false; + } + if (!ui_but_rna_equals(but, oldbut)) { + return false; + } + if (but->func != oldbut->func) { + return false; + } + if (but->funcN != oldbut->funcN) { + return false; + } + if (!ELEM(oldbut->func_arg1, oldbut, but->func_arg1)) { + return false; + } + if (!ELEM(oldbut->func_arg2, oldbut, but->func_arg2)) { + return false; + } + if (!but->funcN && ((but->poin != oldbut->poin && (uiBut *)oldbut->poin != oldbut) || + (but->pointype != oldbut->pointype))) { + return false; + } + if (but->optype != oldbut->optype) { + return false; + } + + return true; +} + +uiBut *ui_but_find_old(uiBlock *block_old, const uiBut *but_new) +{ + LISTBASE_FOREACH (uiBut *, but, &block_old->buttons) { + if (ui_but_equals_old(but_new, but)) { + return but; + } + } + return NULL; +} + +uiBut *ui_but_find_new(uiBlock *block_new, const uiBut *but_old) +{ + LISTBASE_FOREACH (uiBut *, but, &block_new->buttons) { + if (ui_but_equals_old(but, but_old)) { + return but; + } + } + return NULL; +} + +static bool ui_but_extra_icons_equals_old(const uiButExtraOpIcon *new_extra_icon, + const uiButExtraOpIcon *old_extra_icon) +{ + return (new_extra_icon->optype_params->optype == old_extra_icon->optype_params->optype) && + (new_extra_icon->icon == old_extra_icon->icon); +} + +static uiButExtraOpIcon *ui_but_extra_icon_find_old(const uiButExtraOpIcon *new_extra_icon, + const uiBut *old_but) +{ + LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &old_but->extra_op_icons) { + if (ui_but_extra_icons_equals_old(new_extra_icon, op_icon)) { + return op_icon; + } + } + return NULL; +} + +static void ui_but_extra_icons_update_from_old_but(const uiBut *new_but, const uiBut *old_but) +{ + /* Specifically for keeping some state info for the active button. */ + BLI_assert(old_but->active); + + LISTBASE_FOREACH (uiButExtraOpIcon *, new_extra_icon, &new_but->extra_op_icons) { + uiButExtraOpIcon *old_extra_icon = ui_but_extra_icon_find_old(new_extra_icon, old_but); + /* Keep the highlighting state, and let handling update it later. */ + if (old_extra_icon) { + new_extra_icon->highlighted = old_extra_icon->highlighted; + } + } +} + +/** + * Update pointers and other information in the old active button based on new information in the + * corresponding new button from the current layout pass. + * + * \param oldbut: The button from the last layout pass that will be moved to the new block. + * \param but: The newly added button with much of the up to date information, to be feed later. + * + * \note #uiBut has ownership of many of its pointers. When the button is freed all these + * pointers are freed as well, so ownership has to be moved out of \a but in order to free it. + */ +static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but) +{ + BLI_assert(oldbut->active); + + /* flags from the buttons we want to refresh, may want to add more here... */ + const int flag_copy = UI_BUT_REDALERT | UI_HAS_ICON | UI_SELECT_DRAW; + const int drawflag_copy = 0; /* None currently. */ + + /* still stuff needs to be copied */ + oldbut->rect = but->rect; + oldbut->context = but->context; /* set by Layout */ + + /* drawing */ + oldbut->icon = but->icon; + oldbut->iconadd = but->iconadd; + oldbut->alignnr = but->alignnr; + + /* typically the same pointers, but not on undo/redo */ + /* XXX some menu buttons store button itself in but->poin. Ugly */ + if (oldbut->poin != (char *)oldbut) { + SWAP(char *, oldbut->poin, but->poin); + SWAP(void *, oldbut->func_argN, but->func_argN); + } + + /* Move tooltip from new to old. */ + SWAP(uiButToolTipFunc, oldbut->tip_func, but->tip_func); + SWAP(void *, oldbut->tip_argN, but->tip_argN); + + oldbut->flag = (oldbut->flag & ~flag_copy) | (but->flag & flag_copy); + oldbut->drawflag = (oldbut->drawflag & ~drawflag_copy) | (but->drawflag & drawflag_copy); + + ui_but_extra_icons_update_from_old_but(but, oldbut); + SWAP(ListBase, but->extra_op_icons, oldbut->extra_op_icons); + + if (oldbut->type == UI_BTYPE_SEARCH_MENU) { + uiButSearch *search_oldbut = (uiButSearch *)oldbut, *search_but = (uiButSearch *)but; + + SWAP(uiButSearchArgFreeFn, search_oldbut->arg_free_fn, search_but->arg_free_fn); + SWAP(void *, search_oldbut->arg, search_but->arg); + } + + /* copy hardmin for list rows to prevent 'sticking' highlight to mouse position + * when scrolling without moving mouse (see T28432) */ + if (ELEM(oldbut->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { + oldbut->hardmax = but->hardmax; + } + + if (oldbut->type == UI_BTYPE_PROGRESS_BAR) { + uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut; + uiButProgressbar *progress_but = (uiButProgressbar *)but; + progress_oldbut->progress = progress_but->progress; + } + + /* move/copy string from the new button to the old */ + /* needed for alt+mouse wheel over enums */ + if (but->str != but->strdata) { + if (oldbut->str != oldbut->strdata) { + SWAP(char *, but->str, oldbut->str); + } + else { + oldbut->str = but->str; + but->str = but->strdata; + } + } + else { + if (oldbut->str != oldbut->strdata) { + MEM_freeN(oldbut->str); + oldbut->str = oldbut->strdata; + } + BLI_strncpy(oldbut->strdata, but->strdata, sizeof(oldbut->strdata)); + } + + if (but->dragpoin && (but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + SWAP(void *, but->dragpoin, oldbut->dragpoin); + } + + /* note: if layout hasn't been applied yet, it uses old button pointers... */ +} + +/** + * \return true when \a but_p is set (only done for active buttons). + */ +static bool ui_but_update_from_old_block(const bContext *C, + uiBlock *block, + uiBut **but_p, + uiBut **but_old_p) +{ + uiBlock *oldblock = block->oldblock; + uiBut *but = *but_p; + +#if 0 + /* Simple method - search every time. Keep this for easy testing of the "fast path." */ + uiBut *oldbut = ui_but_find_old(oldblock, but); + UNUSED_VARS(but_old_p); +#else + BLI_assert(*but_old_p == NULL || BLI_findindex(&oldblock->buttons, *but_old_p) != -1); + + /* As long as old and new buttons are aligned, avoid loop-in-loop (calling #ui_but_find_old). */ + uiBut *oldbut; + if (LIKELY(*but_old_p && ui_but_equals_old(but, *but_old_p))) { + oldbut = *but_old_p; + } + else { + /* Fallback to block search. */ + oldbut = ui_but_find_old(oldblock, but); + } + (*but_old_p) = oldbut ? oldbut->next : NULL; +#endif + + bool found_active = false; + + if (!oldbut) { + return false; + } + + if (oldbut->active) { + /* Move button over from oldblock to new block. */ + BLI_remlink(&oldblock->buttons, oldbut); + BLI_insertlinkafter(&block->buttons, but, oldbut); + /* Add the old button to the button groups in the new block. */ + ui_button_group_replace_but_ptr(block, but, oldbut); + oldbut->block = block; + *but_p = oldbut; + + ui_but_update_old_active_from_new(oldbut, but); + + if (!BLI_listbase_is_empty(&block->butstore)) { + UI_butstore_register_update(block, oldbut, but); + } + + BLI_remlink(&block->buttons, but); + ui_but_free(C, but); + + found_active = true; + } + else { + const int flag_copy = UI_BUT_DRAG_MULTI; + + but->flag = (but->flag & ~flag_copy) | (oldbut->flag & flag_copy); + + /* ensures one button can get activated, and in case the buttons + * draw are the same this gives O(1) lookup for each button */ + BLI_remlink(&oldblock->buttons, oldbut); + ui_but_free(C, oldbut); + } + + return found_active; +} + +/** + * Needed for temporarily rename buttons, such as in outliner or file-select, + * they should keep calling #uiDefBut to keep them alive. + * \return false when button removed. + */ +bool UI_but_active_only_ex( + const bContext *C, ARegion *region, uiBlock *block, uiBut *but, const bool remove_on_failure) +{ + bool activate = false, found = false, isactive = false; + + uiBlock *oldblock = block->oldblock; + if (!oldblock) { + activate = true; + } + else { + uiBut *oldbut = ui_but_find_old(oldblock, but); + if (oldbut) { + found = true; + + if (oldbut->active) { + isactive = true; + } + } + } + if ((activate == true) || (found == false)) { + /* There might still be another active button. */ + uiBut *old_active = ui_region_find_active_but(region); + if (old_active) { + ui_but_active_free(C, old_active); + } + + ui_but_activate_event((bContext *)C, region, but); + } + else if ((found == true) && (isactive == false)) { + if (remove_on_failure) { + BLI_remlink(&block->buttons, but); + ui_but_free(C, but); + } + return false; + } + + return true; +} + +bool UI_but_active_only(const bContext *C, ARegion *region, uiBlock *block, uiBut *but) +{ + return UI_but_active_only_ex(C, region, block, but, true); +} + +/** + * \warning This must run after other handlers have been added, + * otherwise the handler wont be removed, see: T71112. + */ +bool UI_block_active_only_flagged_buttons(const bContext *C, ARegion *region, uiBlock *block) +{ + /* Running this command before end-block has run, means buttons that open menus + * wont have those menus correctly positioned, see T83539. */ + BLI_assert(block->endblock); + + bool done = false; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->flag & UI_BUT_ACTIVATE_ON_INIT) { + but->flag &= ~UI_BUT_ACTIVATE_ON_INIT; + if (ui_but_is_editable(but)) { + if (UI_but_active_only_ex(C, region, block, but, false)) { + done = true; + break; + } + } + } + } + + if (done) { + /* Run this in a second pass since it's possible activating the button + * removes the buttons being looped over. */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->flag &= ~UI_BUT_ACTIVATE_ON_INIT; + } + } + + return done; +} + +/* simulate button click */ +void UI_but_execute(const bContext *C, ARegion *region, uiBut *but) +{ + void *active_back; + ui_but_execute_begin((bContext *)C, region, but, &active_back); + /* Value is applied in begin. No further action required. */ + ui_but_execute_end((bContext *)C, region, but, active_back); +} + +/* use to check if we need to disable undo, but don't make any changes + * returns false if undo needs to be disabled. */ +static bool ui_but_is_rna_undo(const uiBut *but) +{ + if (but->rnapoin.owner_id) { + /* avoid undo push for buttons who's ID are screen or wm level + * we could disable undo for buttons with no ID too but may have + * unforeseen consequences, so best check for ID's we _know_ are not + * handled by undo - campbell */ + ID *id = but->rnapoin.owner_id; + if (ID_CHECK_UNDO(id) == false) { + return false; + } + } + if (but->rnapoin.type && !RNA_struct_undo_check(but->rnapoin.type)) { + return false; + } + + return true; +} + +/* assigns automatic keybindings to menu items for fast access + * (underline key in menu) */ +static void ui_menu_block_set_keyaccels(uiBlock *block) +{ + uint menu_key_mask = 0; + int tot_missing = 0; + + /* only do it before bounding */ + if (block->rect.xmin != block->rect.xmax) { + return; + } + + for (int pass = 0; pass < 2; pass++) { + /* 2 Passes: One for first letter only, second for any letter if the first pass fails. + * Run first pass on all buttons so first word chars always get first priority. */ + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (!ELEM(but->type, + UI_BTYPE_BUT, + UI_BTYPE_BUT_MENU, + UI_BTYPE_MENU, + UI_BTYPE_BLOCK, + UI_BTYPE_PULLDOWN, + /* For PIE-menus. */ + UI_BTYPE_ROW) || + (but->flag & UI_HIDDEN)) { + continue; + } + + if (but->menu_key != '\0') { + continue; + } + + if (but->str == NULL || but->str[0] == '\0') { + continue; + } + + const char *str_pt = but->str; + uchar menu_key; + do { + menu_key = tolower(*str_pt); + if ((menu_key >= 'a' && menu_key <= 'z') && !(menu_key_mask & 1 << (menu_key - 'a'))) { + menu_key_mask |= 1 << (menu_key - 'a'); + break; + } + + if (pass == 0) { + /* Skip to next delimiter on first pass (be picky) */ + while (isalpha(*str_pt)) { + str_pt++; + } + + if (*str_pt) { + str_pt++; + } + } + else { + /* just step over every char second pass and find first usable key */ + str_pt++; + } + } while (*str_pt); + + if (*str_pt) { + but->menu_key = menu_key; + } + else { + /* run second pass */ + tot_missing++; + } + + /* if all keys have been used just exit, unlikely */ + if (menu_key_mask == (1 << 26) - 1) { + return; + } + } + + /* check if second pass is needed */ + if (!tot_missing) { + break; + } + } +} + +/* XXX, this code will shorten any allocated string to 'UI_MAX_NAME_STR' + * since this is really long its unlikely to be an issue, + * but this could be supported */ +void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_strip) +{ + if (do_strip && (but->flag & UI_BUT_HAS_SEP_CHAR)) { + char *cpoin = strrchr(but->str, UI_SEP_CHAR); + if (cpoin) { + *cpoin = '\0'; + } + but->flag &= ~UI_BUT_HAS_SEP_CHAR; + } + + /* without this, just allow stripping of the shortcut */ + if (shortcut_str == NULL) { + return; + } + + char *butstr_orig; + if (but->str != but->strdata) { + butstr_orig = but->str; /* free after using as source buffer */ + } + else { + butstr_orig = BLI_strdup(but->str); + } + BLI_snprintf( + but->strdata, sizeof(but->strdata), "%s" UI_SEP_CHAR_S "%s", butstr_orig, shortcut_str); + MEM_freeN(butstr_orig); + but->str = but->strdata; + but->flag |= UI_BUT_HAS_SEP_CHAR; + but->drawflag |= UI_BUT_HAS_SHORTCUT; + ui_but_update(but); +} + +/* -------------------------------------------------------------------- */ +/** \name Find Key Shortcut for Button + * + * - #ui_but_event_operator_string (and helpers) + * - #ui_but_event_property_operator_string + * \{ */ + +static bool ui_but_event_operator_string_from_operator(const bContext *C, + uiBut *but, + char *buf, + const size_t buf_len) +{ + BLI_assert(but->optype != NULL); + bool found = false; + IDProperty *prop = (but->opptr) ? (IDProperty *)but->opptr->data : NULL; + + if (WM_key_event_operator_string( + C, but->optype->idname, but->opcontext, prop, true, buf, buf_len)) { + found = true; + } + return found; +} + +static bool ui_but_event_operator_string_from_menu(const bContext *C, + uiBut *but, + char *buf, + const size_t buf_len) +{ + MenuType *mt = UI_but_menutype_get(but); + BLI_assert(mt != NULL); + + bool found = false; + + /* annoying, create a property */ + const IDPropertyTemplate val = {0}; + IDProperty *prop_menu = IDP_New(IDP_GROUP, &val, __func__); /* dummy, name is unimportant */ + IDP_AddToGroup(prop_menu, IDP_NewString(mt->idname, "name", sizeof(mt->idname))); + + if (WM_key_event_operator_string( + C, "WM_OT_call_menu", WM_OP_INVOKE_REGION_WIN, prop_menu, true, buf, buf_len)) { + found = true; + } + + IDP_FreeProperty(prop_menu); + return found; +} + +static bool ui_but_event_operator_string_from_panel(const bContext *C, + uiBut *but, + char *buf, + const size_t buf_len) +{ + /** Nearly exact copy of #ui_but_event_operator_string_from_menu */ + PanelType *pt = UI_but_paneltype_get(but); + BLI_assert(pt != NULL); + + bool found = false; + + /* annoying, create a property */ + const IDPropertyTemplate val = {0}; + IDProperty *prop_panel = IDP_New(IDP_GROUP, &val, __func__); /* dummy, name is unimportant */ + IDP_AddToGroup(prop_panel, IDP_NewString(pt->idname, "name", sizeof(pt->idname))); + + IDPropertyTemplate space_type{0}; + space_type.i = pt->space_type; + IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, &space_type, "space_type")); + + IDPropertyTemplate region_type{0}; + space_type.i = pt->region_type; + IDP_AddToGroup(prop_panel, IDP_New(IDP_INT, ®ion_type, "region_type")); + + for (int i = 0; i < 2; i++) { + /* FIXME(campbell): We can't reasonably search all configurations - long term. */ + IDPropertyTemplate keep_open{0}; + keep_open.i = i; + IDP_ReplaceInGroup(prop_panel, IDP_New(IDP_INT, &keep_open, "keep_open")); + if (WM_key_event_operator_string( + C, "WM_OT_call_panel", WM_OP_INVOKE_REGION_WIN, prop_panel, true, buf, buf_len)) { + found = true; + break; + } + } + + IDP_FreeProperty(prop_panel); + return found; +} + +static bool ui_but_event_operator_string(const bContext *C, + uiBut *but, + char *buf, + const size_t buf_len) +{ + bool found = false; + + if (but->optype != NULL) { + found = ui_but_event_operator_string_from_operator(C, but, buf, buf_len); + } + else if (UI_but_menutype_get(but) != NULL) { + found = ui_but_event_operator_string_from_menu(C, but, buf, buf_len); + } + else if (UI_but_paneltype_get(but) != NULL) { + found = ui_but_event_operator_string_from_panel(C, but, buf, buf_len); + } + + return found; +} + +static bool ui_but_event_property_operator_string(const bContext *C, + uiBut *but, + char *buf, + const size_t buf_len) +{ + /* Context toggle operator names to check. */ + + /* This function could use a refactor to generalize button type to operator relationship + * as well as which operators use properties. - Campbell */ + const char *ctx_toggle_opnames[] = { + "WM_OT_context_toggle", + "WM_OT_context_toggle_enum", + "WM_OT_context_cycle_int", + "WM_OT_context_cycle_enum", + "WM_OT_context_cycle_array", + "WM_OT_context_menu_enum", + NULL, + }; + + const char *ctx_enum_opnames[] = { + "WM_OT_context_set_enum", + NULL, + }; + + const char *ctx_enum_opnames_for_Area_ui_type[] = { + "SCREEN_OT_space_type_set_or_cycle", + NULL, + }; + + const char **opnames = ctx_toggle_opnames; + int opnames_len = ARRAY_SIZE(ctx_toggle_opnames); + + int prop_enum_value = -1; + bool prop_enum_value_ok = false; + bool prop_enum_value_is_int = false; + const char *prop_enum_value_id = "value"; + PointerRNA *ptr = &but->rnapoin; + PropertyRNA *prop = but->rnaprop; + if ((but->type == UI_BTYPE_BUT_MENU) && (but->block->handle != NULL)) { + uiBut *but_parent = but->block->handle->popup_create_vars.but; + if ((but->type == UI_BTYPE_BUT_MENU) && (but_parent && but_parent->rnaprop) && + (RNA_property_type(but_parent->rnaprop) == PROP_ENUM) && + ELEM(but_parent->menu_create_func, + ui_def_but_rna__menu, + ui_def_but_rna__panel_type, + ui_def_but_rna__menu_type)) { + prop_enum_value = (int)but->hardmin; + ptr = &but_parent->rnapoin; + prop = but_parent->rnaprop; + prop_enum_value_ok = true; + + opnames = ctx_enum_opnames; + opnames_len = ARRAY_SIZE(ctx_enum_opnames); + } + } + /* Don't use the button again. */ + but = NULL; + + if (prop == NULL) { + return false; + } + + /* this version is only for finding hotkeys for properties + * (which get set via context using operators) */ + /* to avoid massive slowdowns on property panels, for now, we only check the + * hotkeys for Editor / Scene settings... + * + * TODO: userpref settings? + */ + char *data_path = NULL; + + if (ptr->owner_id) { + ID *id = ptr->owner_id; + + if (GS(id->name) == ID_SCR) { + /* screen/editor property + * NOTE: in most cases, there is actually no info for backwards tracing + * how to get back to ID from the editor data we may be dealing with + */ + if (RNA_struct_is_a(ptr->type, &RNA_Space)) { + /* data should be directly on here... */ + data_path = BLI_sprintfN("space_data.%s", RNA_property_identifier(prop)); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Area)) { + /* data should be directly on here... */ + const char *prop_id = RNA_property_identifier(prop); + /* Hack since keys access 'type', UI shows 'ui_type'. */ + if (STREQ(prop_id, "ui_type")) { + prop_id = "type"; + prop_enum_value >>= 16; + prop = RNA_struct_find_property(ptr, prop_id); + + opnames = ctx_enum_opnames_for_Area_ui_type; + opnames_len = ARRAY_SIZE(ctx_enum_opnames_for_Area_ui_type); + prop_enum_value_id = "space_type"; + prop_enum_value_is_int = true; + } + else { + data_path = BLI_sprintfN("area.%s", prop_id); + } + } + else { + /* special exceptions for common nested data in editors... */ + if (RNA_struct_is_a(ptr->type, &RNA_DopeSheet)) { + /* dopesheet filtering options... */ + data_path = BLI_sprintfN("space_data.dopesheet.%s", RNA_property_identifier(prop)); + } + else if (RNA_struct_is_a(ptr->type, &RNA_FileSelectParams)) { + /* Filebrowser options... */ + data_path = BLI_sprintfN("space_data.params.%s", RNA_property_identifier(prop)); + } + } + } + else if (GS(id->name) == ID_SCE) { + if (RNA_struct_is_a(ptr->type, &RNA_ToolSettings)) { + /* Tool-settings property: + * NOTE: tool-settings is usually accessed directly (i.e. not through scene). */ + data_path = RNA_path_from_ID_to_property(ptr, prop); + } + else { + /* scene property */ + char *path = RNA_path_from_ID_to_property(ptr, prop); + + if (path) { + data_path = BLI_sprintfN("scene.%s", path); + MEM_freeN(path); + } +#if 0 + else { + printf("ERROR in %s(): Couldn't get path for scene property - %s\n", + __func__, + RNA_property_identifier(prop)); + } +#endif + } + } + else { + // puts("other id"); + } + + // printf("prop shortcut: '%s' (%s)\n", RNA_property_identifier(prop), data_path); + } + + /* We have a data-path! */ + bool found = false; + if (data_path || (prop_enum_value_ok && prop_enum_value_id)) { + /* Create a property to host the "data_path" property we're sending to the operators. */ + IDProperty *prop_path; + + const IDPropertyTemplate val = {0}; + prop_path = IDP_New(IDP_GROUP, &val, __func__); + if (data_path) { + IDP_AddToGroup(prop_path, IDP_NewString(data_path, "data_path", strlen(data_path) + 1)); + } + if (prop_enum_value_ok) { + const EnumPropertyItem *item; + bool free; + RNA_property_enum_items((bContext *)C, ptr, prop, &item, NULL, &free); + const int index = RNA_enum_from_value(item, prop_enum_value); + if (index != -1) { + IDProperty *prop_value; + if (prop_enum_value_is_int) { + const int value = item[index].value; + IDPropertyTemplate idp_template{0}; + idp_template.i = value; + prop_value = IDP_New(IDP_INT, &idp_template, prop_enum_value_id); + } + else { + const char *id = item[index].identifier; + prop_value = IDP_NewString(id, prop_enum_value_id, strlen(id) + 1); + } + IDP_AddToGroup(prop_path, prop_value); + } + else { + opnames_len = 0; /* Do nothing. */ + } + if (free) { + MEM_freeN((void *)item); + } + } + + /* check each until one works... */ + + for (int i = 0; (i < opnames_len) && (opnames[i]); i++) { + if (WM_key_event_operator_string( + C, opnames[i], WM_OP_INVOKE_REGION_WIN, prop_path, false, buf, buf_len)) { + found = true; + break; + } + } + + /* cleanup */ + IDP_FreeProperty(prop_path); + if (data_path) { + MEM_freeN(data_path); + } + } + + return found; +} + +/** \} */ + +/** + * This goes in a seemingly weird pattern: + * + *
+ *     4
+ *  5     6
+ * 1       2
+ *  7     8
+ *     3
+ * 
+ * + * but it's actually quite logical. It's designed to be 'upwards compatible' + * for muscle memory so that the menu item locations are fixed and don't move + * as new items are added to the menu later on. It also optimizes efficiency - + * a radial menu is best kept symmetrical, with as large an angle between + * items as possible, so that the gestural mouse movements can be fast and inexact. + * + * It starts off with two opposite sides for the first two items + * then joined by the one below for the third (this way, even with three items, + * the menu seems to still be 'in order' reading left to right). Then the fourth is + * added to complete the compass directions. From here, it's just a matter of + * subdividing the rest of the angles for the last 4 items. + * + * --Matt 07/2006 + */ +static const RadialDirection ui_radial_dir_order[8] = { + UI_RADIAL_W, + UI_RADIAL_E, + UI_RADIAL_S, + UI_RADIAL_N, + UI_RADIAL_NW, + UI_RADIAL_NE, + UI_RADIAL_SW, + UI_RADIAL_SE, +}; + +const char ui_radial_dir_to_numpad[8] = {8, 9, 6, 3, 2, 1, 4, 7}; +const short ui_radial_dir_to_angle[8] = {90, 45, 0, 315, 270, 225, 180, 135}; + +static void ui_but_pie_direction_string(uiBut *but, char *buf, int size) +{ + BLI_assert(but->pie_dir < ARRAY_SIZE(ui_radial_dir_to_numpad)); + BLI_snprintf(buf, size, "%d", ui_radial_dir_to_numpad[but->pie_dir]); +} + +static void ui_menu_block_set_keymaps(const bContext *C, uiBlock *block) +{ + char buf[128]; + + BLI_assert(block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)); + + /* only do it before bounding */ + if (block->rect.xmin != block->rect.xmax) { + return; + } + if (STREQ(block->name, "splash")) { + return; + } + + if (block->flag & UI_BLOCK_RADIAL) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->pie_dir != UI_RADIAL_NONE) { + ui_but_pie_direction_string(but, buf, sizeof(buf)); + ui_but_add_shortcut(but, buf, false); + } + } + } + else { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (block->flag & UI_BLOCK_SHOW_SHORTCUT_ALWAYS) { + /* Skip icon-only buttons (as used in the toolbar). */ + if (but->drawstr[0] == '\0') { + continue; + } + if (((block->flag & UI_BLOCK_POPOVER) == 0) && UI_but_is_tool(but)) { + /* For non-popovers, shown in shortcut only + * (has special shortcut handling code). */ + continue; + } + } + else if (but->emboss != UI_EMBOSS_PULLDOWN) { + continue; + } + + if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { + ui_but_add_shortcut(but, buf, false); + } + else if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { + ui_but_add_shortcut(but, buf, false); + } + } + } +} + +void ui_but_override_flag(Main *bmain, uiBut *but) +{ + const uint override_status = RNA_property_override_library_status( + bmain, &but->rnapoin, but->rnaprop, but->rnaindex); + + if (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN) { + but->flag |= UI_BUT_OVERRIDDEN; + } + else { + but->flag &= ~UI_BUT_OVERRIDDEN; + } +} + +/* -------------------------------------------------------------------- */ +/** \name Button Extra Operator Icons + * + * Extra icons are shown on the right hand side of buttons. They can be clicked to invoke custom + * operators. + * There are some predefined here, which get added to buttons automatically based on button data + * (type, flags, state, etc). + * \{ */ + +/** + * Predefined types for generic extra operator icons (uiButExtraOpIcon). + */ +typedef enum PredefinedExtraOpIconType { + PREDEFINED_EXTRA_OP_ICON_NONE = 1, + PREDEFINED_EXTRA_OP_ICON_CLEAR, + PREDEFINED_EXTRA_OP_ICON_EYEDROPPER, +} PredefinedExtraOpIconType; + +static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but, + wmOperatorType *optype, + short opcontext, + int icon) +{ + uiButExtraOpIcon *extra_op_icon = (uiButExtraOpIcon *)MEM_mallocN(sizeof(*extra_op_icon), + __func__); + + extra_op_icon->icon = (BIFIconID)icon; + extra_op_icon->optype_params = (wmOperatorCallParams *)MEM_callocN( + sizeof(*extra_op_icon->optype_params), "uiButExtraOpIcon.optype_params"); + extra_op_icon->optype_params->optype = optype; + extra_op_icon->optype_params->opptr = (PointerRNA *)MEM_callocN( + sizeof(*extra_op_icon->optype_params->opptr), "uiButExtraOpIcon.optype_params.opptr"); + WM_operator_properties_create_ptr(extra_op_icon->optype_params->opptr, + extra_op_icon->optype_params->optype); + extra_op_icon->optype_params->opcontext = opcontext; + extra_op_icon->highlighted = false; + + BLI_addtail(&but->extra_op_icons, extra_op_icon); + + return extra_op_icon->optype_params->opptr; +} + +static void ui_but_extra_operator_icon_free(uiButExtraOpIcon *extra_icon) +{ + WM_operator_properties_free(extra_icon->optype_params->opptr); + MEM_freeN(extra_icon->optype_params->opptr); + MEM_freeN(extra_icon->optype_params); + MEM_freeN(extra_icon); +} + +void ui_but_extra_operator_icons_free(uiBut *but) +{ + LISTBASE_FOREACH_MUTABLE (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { + ui_but_extra_operator_icon_free(op_icon); + } + BLI_listbase_clear(&but->extra_op_icons); +} + +PointerRNA *UI_but_extra_operator_icon_add(uiBut *but, + const char *opname, + short opcontext, + int icon) +{ + wmOperatorType *optype = WM_operatortype_find(opname, false); + + if (optype) { + return ui_but_extra_operator_icon_add_ptr(but, optype, opcontext, icon); + } + + return NULL; +} + +static bool ui_but_icon_extra_is_visible_text_clear(const uiBut *but) +{ + BLI_assert(but->type == UI_BTYPE_TEXT); + return ((but->flag & UI_BUT_VALUE_CLEAR) && but->drawstr[0]); +} + +static bool ui_but_icon_extra_is_visible_search_unlink(const uiBut *but) +{ + BLI_assert(ELEM(but->type, UI_BTYPE_SEARCH_MENU)); + return ((but->editstr == NULL) && (but->drawstr[0] != '\0') && (but->flag & UI_BUT_VALUE_CLEAR)); +} + +static bool ui_but_icon_extra_is_visible_search_eyedropper(uiBut *but) +{ + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU && (but->flag & UI_BUT_VALUE_CLEAR)); + + if (but->rnaprop == NULL) { + return false; + } + + StructRNA *type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); + const short idcode = RNA_type_to_ID_code(type); + + return ((but->editstr == NULL) && (idcode == ID_OB || OB_DATA_SUPPORT_ID(idcode))); +} + +static PredefinedExtraOpIconType ui_but_icon_extra_get(uiBut *but) +{ + switch (but->type) { + case UI_BTYPE_TEXT: + if (ui_but_icon_extra_is_visible_text_clear(but)) { + return PREDEFINED_EXTRA_OP_ICON_CLEAR; + } + break; + case UI_BTYPE_SEARCH_MENU: + if ((but->flag & UI_BUT_VALUE_CLEAR) == 0) { + /* pass */ + } + else if (ui_but_icon_extra_is_visible_search_unlink(but)) { + return PREDEFINED_EXTRA_OP_ICON_CLEAR; + } + else if (ui_but_icon_extra_is_visible_search_eyedropper(but)) { + return PREDEFINED_EXTRA_OP_ICON_EYEDROPPER; + } + break; + default: + break; + } + + return PREDEFINED_EXTRA_OP_ICON_NONE; +} + +/** + * While some extra operator icons have to be set explicitly upon button creating, this code adds + * some generic ones based on button data. Currently these are mutually exclusive, so there's only + * ever one predefined extra icon. + */ +static void ui_but_predefined_extra_operator_icons_add(uiBut *but) +{ + const PredefinedExtraOpIconType extra_icon = ui_but_icon_extra_get(but); + wmOperatorType *optype = NULL; + BIFIconID icon = ICON_NONE; + + switch (extra_icon) { + case PREDEFINED_EXTRA_OP_ICON_EYEDROPPER: { + static wmOperatorType *id_eyedropper_ot = NULL; + if (!id_eyedropper_ot) { + id_eyedropper_ot = WM_operatortype_find("UI_OT_eyedropper_id", false); + } + BLI_assert(id_eyedropper_ot); + + optype = id_eyedropper_ot; + icon = ICON_EYEDROPPER; + + break; + } + case PREDEFINED_EXTRA_OP_ICON_CLEAR: { + static wmOperatorType *clear_ot = NULL; + if (!clear_ot) { + clear_ot = WM_operatortype_find("UI_OT_button_string_clear", false); + } + BLI_assert(clear_ot); + + optype = clear_ot; + icon = ICON_PANEL_CLOSE; + + break; + } + default: + break; + } + + if (optype) { + LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { + if ((op_icon->optype_params->optype == optype) && (op_icon->icon == icon)) { + /* Don't add the same operator icon twice (happens if button is kept alive while active). + */ + return; + } + } + ui_but_extra_operator_icon_add_ptr(but, optype, WM_OP_INVOKE_DEFAULT, (int)icon); + } +} + +/** \} */ + +void UI_block_update_from_old(const bContext *C, uiBlock *block) +{ + if (!block->oldblock) { + return; + } + + uiBut *but_old = (uiBut *)block->oldblock->buttons.first; + + if (BLI_listbase_is_empty(&block->oldblock->butstore) == false) { + UI_butstore_update(block); + } + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (ui_but_update_from_old_block(C, block, &but, &but_old)) { + ui_but_update(but); + + /* redraw dynamic tooltip if we have one open */ + if (but->tip_func) { + UI_but_tooltip_refresh((bContext *)C, but); + } + } + } + + block->auto_open = block->oldblock->auto_open; + block->auto_open_last = block->oldblock->auto_open_last; + block->tooltipdisabled = block->oldblock->tooltipdisabled; + BLI_movelisttolist(&block->color_pickers.list, &block->oldblock->color_pickers.list); + + block->oldblock = NULL; +} + +#ifndef NDEBUG +/** + * Extra sanity checks for invariants (debug builds only). + */ +static void ui_but_validate(const uiBut *but) +{ + /* Number buttons must have a click-step, + * assert instead of correcting the value to ensure the caller knows what they're doing. */ + if (but->type == UI_BTYPE_NUM) { + uiButNumber *number_but = (uiButNumber *)but; + + if (ELEM(but->pointype, UI_BUT_POIN_CHAR, UI_BUT_POIN_SHORT, UI_BUT_POIN_INT)) { + BLI_assert((int)number_but->step_size > 0); + } + } +} +#endif + +/** + * Check if the operator \a ot poll is successful with the context given by \a but (optionally). + * \param but: The button that might store context. Can be NULL for convenience (e.g. if there is + * no button to take context from, but we still want to poll the operator). + */ +bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but) +{ + bool result; + int opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT; + + if (but && but->context) { + CTX_store_set(C, but->context); + } + + result = WM_operator_poll_context(C, ot, opcontext); + + if (but && but->context) { + CTX_store_set(C, NULL); + } + + return result; +} + +void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2]) +{ + wmWindow *window = CTX_wm_window(C); + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + + BLI_assert(block->active); + + /* Extend button data. This needs to be done before the block updating. */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + ui_but_predefined_extra_operator_icons_add(but); + } + + UI_block_update_from_old(C, block); + + /* inherit flags from 'old' buttons that was drawn here previous, based + * on matching buttons, we need this to make button event handling non + * blocking, while still allowing buttons to be remade each redraw as it + * is expected by blender code */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + /* temp? Proper check for graying out */ + if (but->optype) { + wmOperatorType *ot = but->optype; + + if (ot == NULL || !ui_but_context_poll_operator((bContext *)C, ot, but)) { + but->flag |= UI_BUT_DISABLED; + } + } + + const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( + depsgraph, (scene) ? scene->r.cfra : 0.0f); + ui_but_anim_flag(but, &anim_eval_context); + ui_but_override_flag(CTX_data_main(C), but); + if (UI_but_is_decorator(but)) { + ui_but_anim_decorate_update_from_flag((uiButDecorator *)but); + } + +#ifndef NDEBUG + ui_but_validate(but); +#endif + } + + /* handle pending stuff */ + if (block->layouts.first) { + UI_block_layout_resolve(block, NULL, NULL); + } + ui_block_align_calc(block, CTX_wm_region(C)); + if ((block->flag & UI_BLOCK_LOOP) && (block->flag & UI_BLOCK_NUMSELECT)) { + ui_menu_block_set_keyaccels(block); /* could use a different flag to check */ + } + + if (block->flag & (UI_BLOCK_LOOP | UI_BLOCK_SHOW_SHORTCUT_ALWAYS)) { + ui_menu_block_set_keymaps(C, block); + } + + /* after keymaps! */ + switch (block->bounds_type) { + case UI_BLOCK_BOUNDS_NONE: + break; + case UI_BLOCK_BOUNDS: + ui_block_bounds_calc(block); + break; + case UI_BLOCK_BOUNDS_TEXT: + ui_block_bounds_calc_text(block, 0.0f); + break; + case UI_BLOCK_BOUNDS_POPUP_CENTER: + ui_block_bounds_calc_centered(window, block); + break; + case UI_BLOCK_BOUNDS_PIE_CENTER: + ui_block_bounds_calc_centered_pie(block); + break; + + /* fallback */ + case UI_BLOCK_BOUNDS_POPUP_MOUSE: + case UI_BLOCK_BOUNDS_POPUP_MENU: + ui_block_bounds_calc_popup(window, block, block->bounds_type, xy, r_xy); + break; + } + + if (block->rect.xmin == 0.0f && block->rect.xmax == 0.0f) { + UI_block_bounds_set_normal(block, 0); + } + if (block->flag & UI_BUT_ALIGN) { + UI_block_align_end(block); + } + + ui_update_flexible_spacing(region, block); + + block->endblock = true; +} + +void UI_block_end(const bContext *C, uiBlock *block) +{ + wmWindow *window = CTX_wm_window(C); + + UI_block_end_ex(C, block, &window->eventstate->x, NULL); +} + +/* ************** BLOCK DRAWING FUNCTION ************* */ + +void ui_fontscale(short *points, float aspect) +{ + if (aspect < 0.9f || aspect > 1.1f) { + float pointsf = *points; + + /* for some reason scaling fonts goes too fast compared to widget size */ + /* XXX not true anymore? (ton) */ + // aspect = sqrt(aspect); + pointsf /= aspect; + + if (aspect > 1.0f) { + *points = ceilf(pointsf); + } + else { + *points = floorf(pointsf); + } + } +} + +/* Project button or block (but==NULL) to pixels in region-space. */ +static void ui_but_to_pixelrect(rcti *rect, const ARegion *region, uiBlock *block, uiBut *but) +{ + rctf rectf; + + ui_block_to_window_rctf(region, block, &rectf, (but) ? &but->rect : &block->rect); + BLI_rcti_rctf_copy_round(rect, &rectf); + BLI_rcti_translate(rect, -region->winrct.xmin, -region->winrct.ymin); +} + +/* uses local copy of style, to scale things down, and allow widgets to change stuff */ +void UI_block_draw(const bContext *C, uiBlock *block) +{ + uiStyle style = *UI_style_get_dpi(); /* XXX pass on as arg */ + + /* get menu region or area region */ + ARegion *region = CTX_wm_menu(C); + if (!region) { + region = CTX_wm_region(C); + } + + if (!block->endblock) { + UI_block_end(C, block); + } + + /* we set this only once */ + GPU_blend(GPU_BLEND_ALPHA); + + /* scale fonts */ + ui_fontscale(&style.paneltitle.points, block->aspect); + ui_fontscale(&style.grouplabel.points, block->aspect); + ui_fontscale(&style.widgetlabel.points, block->aspect); + ui_fontscale(&style.widget.points, block->aspect); + + /* scale block min/max to rect */ + rcti rect; + ui_but_to_pixelrect(&rect, region, block, NULL); + + /* pixel space for AA widgets */ + GPU_matrix_push_projection(); + GPU_matrix_push(); + GPU_matrix_identity_set(); + + wmOrtho2_region_pixelspace(region); + + /* back */ + if (block->flag & UI_BLOCK_RADIAL) { + ui_draw_pie_center(block); + } + else if (block->flag & UI_BLOCK_POPOVER) { + ui_draw_popover_back(region, &style, block, &rect); + } + else if (block->flag & UI_BLOCK_LOOP) { + ui_draw_menu_back(&style, block, &rect); + } + else if (block->panel) { + bool show_background = region->alignment != RGN_ALIGN_FLOAT; + if (show_background) { + if (block->panel->type && (block->panel->type->flag & PANEL_TYPE_NO_HEADER)) { + if (region->regiontype == RGN_TYPE_TOOLS) { + /* We never want a background around active tools. */ + show_background = false; + } + else { + /* Without a header there is no background except for region overlap. */ + show_background = region->overlap != 0; + } + } + } + ui_draw_aligned_panel(&style, + block, + &rect, + UI_panel_category_is_visible(region), + show_background, + region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE); + } + + BLF_batch_draw_begin(); + UI_icon_draw_cache_begin(); + UI_widgetbase_draw_cache_begin(); + + /* widgets */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (!(but->flag & (UI_HIDDEN | UI_SCROLLED))) { + ui_but_to_pixelrect(&rect, region, block, but); + + /* XXX: figure out why invalid coordinates happen when closing render window */ + /* and material preview is redrawn in main window (temp fix for bug T23848) */ + if (rect.xmin < rect.xmax && rect.ymin < rect.ymax) { + ui_draw_but(C, region, &style, but, &rect); + } + } + } + + UI_widgetbase_draw_cache_end(); + UI_icon_draw_cache_end(); + BLF_batch_draw_end(); + + /* restore matrix */ + GPU_matrix_pop_projection(); + GPU_matrix_pop(); +} + +static void ui_block_message_subscribe(ARegion *region, struct wmMsgBus *mbus, uiBlock *block) +{ + uiBut *but_prev = NULL; + /* possibly we should keep the region this block is contained in? */ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->rnapoin.type && but->rnaprop) { + /* quick check to avoid adding buttons representing a vector, multiple times. */ + if ((but_prev && (but_prev->rnaprop == but->rnaprop) && + (but_prev->rnapoin.type == but->rnapoin.type) && + (but_prev->rnapoin.data == but->rnapoin.data) && + (but_prev->rnapoin.owner_id == but->rnapoin.owner_id)) == false) { + /* TODO: could make this into utility function. */ + wmMsgSubscribeValue subscribe_value = {0}; + subscribe_value.owner = region; + subscribe_value.owner_data = region; + subscribe_value.notify = ED_region_do_msg_notify_tag_redraw; + WM_msg_subscribe_rna(mbus, &but->rnapoin, but->rnaprop, &subscribe_value, __func__); + but_prev = but; + } + } + } +} + +void UI_region_message_subscribe(ARegion *region, struct wmMsgBus *mbus) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + ui_block_message_subscribe(region, mbus, block); + } +} + +/* ************* EVENTS ************* */ + +/** + * Check if the button is pushed, this is only meaningful for some button types. + * + * \return (0 == UNSELECT), (1 == SELECT), (-1 == DO-NOTHING) + */ +int ui_but_is_pushed_ex(uiBut *but, double *value) +{ + int is_push = 0; + + if (but->bit) { + const bool state = !ELEM( + but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N); + int lvalue; + UI_GET_BUT_VALUE_INIT(but, *value); + lvalue = (int)*value; + if (UI_BITBUT_TEST(lvalue, (but->bitnr))) { + is_push = state; + } + else { + is_push = !state; + } + } + else { + switch (but->type) { + case UI_BTYPE_BUT: + case UI_BTYPE_HOTKEY_EVENT: + case UI_BTYPE_KEY_EVENT: + case UI_BTYPE_COLOR: + case UI_BTYPE_DECORATOR: + is_push = -1; + break; + case UI_BTYPE_BUT_TOGGLE: + case UI_BTYPE_TOGGLE: + case UI_BTYPE_ICON_TOGGLE: + case UI_BTYPE_CHECKBOX: + UI_GET_BUT_VALUE_INIT(but, *value); + if (*value != (double)but->hardmin) { + is_push = true; + } + break; + case UI_BTYPE_ICON_TOGGLE_N: + case UI_BTYPE_TOGGLE_N: + case UI_BTYPE_CHECKBOX_N: + UI_GET_BUT_VALUE_INIT(but, *value); + if (*value == 0.0) { + is_push = true; + } + break; + case UI_BTYPE_ROW: + case UI_BTYPE_LISTROW: + case UI_BTYPE_TAB: + if ((but->type == UI_BTYPE_TAB) && but->rnaprop && but->custom_data) { + /* uiBut.custom_data points to data this tab represents (e.g. workspace). + * uiBut.rnapoin/prop store an active value (e.g. active workspace). */ + if (RNA_property_type(but->rnaprop) == PROP_POINTER) { + const PointerRNA active_ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); + if (active_ptr.data == but->custom_data) { + is_push = true; + } + } + break; + } + else if (but->optype) { + break; + } + + UI_GET_BUT_VALUE_INIT(but, *value); + /* support for rna enum buts */ + if (but->rnaprop && (RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG)) { + if ((int)*value & (int)but->hardmax) { + is_push = true; + } + } + else { + if (*value == (double)but->hardmax) { + is_push = true; + } + } + break; + default: + is_push = -1; + break; + } + } + + if ((but->drawflag & UI_BUT_CHECKBOX_INVERT) && (is_push != -1)) { + is_push = !((bool)is_push); + } + return is_push; +} +int ui_but_is_pushed(uiBut *but) +{ + double value = UI_BUT_VALUE_UNSET; + return ui_but_is_pushed_ex(but, &value); +} + +static void ui_but_update_select_flag(uiBut *but, double *value) +{ + switch (ui_but_is_pushed_ex(but, value)) { + case true: + but->flag |= UI_SELECT; + break; + case false: + but->flag &= ~UI_SELECT; + break; + } +} + +/* ************************************************ */ + +void UI_block_lock_set(uiBlock *block, bool val, const char *lockstr) +{ + if (val) { + block->lock = val; + block->lockstr = lockstr; + } +} + +void UI_block_lock_clear(uiBlock *block) +{ + block->lock = false; + block->lockstr = NULL; +} + +/* *********************** data get/set *********************** + * this either works with the pointed to data, or can work with + * an edit override pointer while dragging for example */ + +/* for buttons pointing to color for example */ +void ui_but_v3_get(uiBut *but, float vec[3]) +{ + if (but->editvec) { + copy_v3_v3(vec, but->editvec); + } + + if (but->rnaprop) { + PropertyRNA *prop = but->rnaprop; + + zero_v3(vec); + + if (RNA_property_type(prop) == PROP_FLOAT) { + int tot = RNA_property_array_length(&but->rnapoin, prop); + BLI_assert(tot > 0); + if (tot == 3) { + RNA_property_float_get_array(&but->rnapoin, prop, vec); + } + else { + tot = min_ii(tot, 3); + for (int a = 0; a < tot; a++) { + vec[a] = RNA_property_float_get_index(&but->rnapoin, prop, a); + } + } + } + } + else if (but->pointype == UI_BUT_POIN_CHAR) { + const char *cp = (char *)but->poin; + + vec[0] = ((float)cp[0]) / 255.0f; + vec[1] = ((float)cp[1]) / 255.0f; + vec[2] = ((float)cp[2]) / 255.0f; + } + else if (but->pointype == UI_BUT_POIN_FLOAT) { + const float *fp = (float *)but->poin; + copy_v3_v3(vec, fp); + } + else { + if (but->editvec == NULL) { + fprintf(stderr, "%s: can't get color, should never happen\n", __func__); + zero_v3(vec); + } + } + + if (but->type == UI_BTYPE_UNITVEC) { + normalize_v3(vec); + } +} + +/* for buttons pointing to color for example */ +void ui_but_v3_set(uiBut *but, const float vec[3]) +{ + if (but->editvec) { + copy_v3_v3(but->editvec, vec); + } + + if (but->rnaprop) { + PropertyRNA *prop = but->rnaprop; + + if (RNA_property_type(prop) == PROP_FLOAT) { + int tot; + int a; + + tot = RNA_property_array_length(&but->rnapoin, prop); + BLI_assert(tot > 0); + if (tot == 3) { + RNA_property_float_set_array(&but->rnapoin, prop, vec); + } + else { + tot = min_ii(tot, 3); + for (a = 0; a < tot; a++) { + RNA_property_float_set_index(&but->rnapoin, prop, a, vec[a]); + } + } + } + } + else if (but->pointype == UI_BUT_POIN_CHAR) { + char *cp = (char *)but->poin; + cp[0] = (char)(0.5f + vec[0] * 255.0f); + cp[1] = (char)(0.5f + vec[1] * 255.0f); + cp[2] = (char)(0.5f + vec[2] * 255.0f); + } + else if (but->pointype == UI_BUT_POIN_FLOAT) { + float *fp = (float *)but->poin; + copy_v3_v3(fp, vec); + } +} + +bool ui_but_is_float(const uiBut *but) +{ + if (but->pointype == UI_BUT_POIN_FLOAT && but->poin) { + return true; + } + + if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_FLOAT) { + return true; + } + + return false; +} + +bool ui_but_is_bool(const uiBut *but) +{ + if (ELEM(but->type, + UI_BTYPE_TOGGLE, + UI_BTYPE_TOGGLE_N, + UI_BTYPE_ICON_TOGGLE, + UI_BTYPE_ICON_TOGGLE_N, + UI_BTYPE_TAB)) { + return true; + } + + if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_BOOLEAN) { + return true; + } + + if ((but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) && + (but->type == UI_BTYPE_ROW)) { + return true; + } + + return false; +} + +bool ui_but_is_unit(const uiBut *but) +{ + UnitSettings *unit = but->block->unit; + const int unit_type = UI_but_unit_type_get(but); + + if (unit_type == PROP_UNIT_NONE) { + return false; + } + +#if 1 /* removed so angle buttons get correct snapping */ + if (ui_but_is_unit_radians_ex(unit, unit_type)) { + return false; + } +#endif + + /* for now disable time unit conversion */ + if (unit_type == PROP_UNIT_TIME) { + return false; + } + + if (unit->system == USER_UNIT_NONE) { + if (unit_type != PROP_UNIT_ROTATION) { + return false; + } + } + + return true; +} + +/** + * Check if this button is similar enough to be grouped with another. + */ +bool ui_but_is_compatible(const uiBut *but_a, const uiBut *but_b) +{ + if (but_a->type != but_b->type) { + return false; + } + if (but_a->pointype != but_b->pointype) { + return false; + } + + if (but_a->rnaprop) { + /* skip 'rnapoin.data', 'rnapoin.owner_id' + * allow different data to have the same props edited at once */ + if (but_a->rnapoin.type != but_b->rnapoin.type) { + return false; + } + if (RNA_property_type(but_a->rnaprop) != RNA_property_type(but_b->rnaprop)) { + return false; + } + if (RNA_property_subtype(but_a->rnaprop) != RNA_property_subtype(but_b->rnaprop)) { + return false; + } + } + + return true; +} + +bool ui_but_is_rna_valid(uiBut *but) +{ + if (but->rnaprop == NULL || RNA_struct_contains_property(&but->rnapoin, but->rnaprop)) { + return true; + } + printf("property removed %s: %p\n", but->drawstr, but->rnaprop); + return false; +} + +/** + * Checks if the button supports cycling next/previous menu items (ctrl+mouse-wheel). + */ +bool ui_but_supports_cycling(const uiBut *but) +{ + return ((ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER, UI_BTYPE_LISTBOX)) || + (but->type == UI_BTYPE_MENU && ui_but_menu_step_poll(but)) || + (but->type == UI_BTYPE_COLOR && ((uiButColor *)but)->is_pallete_color) || + (but->menu_step_func != NULL)); +} + +double ui_but_value_get(uiBut *but) +{ + double value = 0.0; + + if (but->editval) { + return *(but->editval); + } + if (but->poin == NULL && but->rnapoin.data == NULL) { + return 0.0; + } + + if (but->rnaprop) { + PropertyRNA *prop = but->rnaprop; + + BLI_assert(but->rnaindex != -1); + + switch (RNA_property_type(prop)) { + case PROP_BOOLEAN: + if (RNA_property_array_check(prop)) { + value = RNA_property_boolean_get_index(&but->rnapoin, prop, but->rnaindex); + } + else { + value = RNA_property_boolean_get(&but->rnapoin, prop); + } + break; + case PROP_INT: + if (RNA_property_array_check(prop)) { + value = RNA_property_int_get_index(&but->rnapoin, prop, but->rnaindex); + } + else { + value = RNA_property_int_get(&but->rnapoin, prop); + } + break; + case PROP_FLOAT: + if (RNA_property_array_check(prop)) { + value = RNA_property_float_get_index(&but->rnapoin, prop, but->rnaindex); + } + else { + value = RNA_property_float_get(&but->rnapoin, prop); + } + break; + case PROP_ENUM: + value = RNA_property_enum_get(&but->rnapoin, prop); + break; + default: + value = 0.0; + break; + } + } + else if (but->pointype == UI_BUT_POIN_CHAR) { + value = *(char *)but->poin; + } + else if (but->pointype == UI_BUT_POIN_SHORT) { + value = *(short *)but->poin; + } + else if (but->pointype == UI_BUT_POIN_INT) { + value = *(int *)but->poin; + } + else if (but->pointype == UI_BUT_POIN_FLOAT) { + value = *(float *)but->poin; + } + + return value; +} + +void ui_but_value_set(uiBut *but, double value) +{ + /* value is a hsv value: convert to rgb */ + if (but->rnaprop) { + PropertyRNA *prop = but->rnaprop; + + if (RNA_property_editable(&but->rnapoin, prop)) { + switch (RNA_property_type(prop)) { + case PROP_BOOLEAN: + if (RNA_property_array_check(prop)) { + RNA_property_boolean_set_index(&but->rnapoin, prop, but->rnaindex, value); + } + else { + RNA_property_boolean_set(&but->rnapoin, prop, value); + } + break; + case PROP_INT: + if (RNA_property_array_check(prop)) { + RNA_property_int_set_index(&but->rnapoin, prop, but->rnaindex, (int)value); + } + else { + RNA_property_int_set(&but->rnapoin, prop, (int)value); + } + break; + case PROP_FLOAT: + if (RNA_property_array_check(prop)) { + RNA_property_float_set_index(&but->rnapoin, prop, but->rnaindex, value); + } + else { + RNA_property_float_set(&but->rnapoin, prop, value); + } + break; + case PROP_ENUM: + if (RNA_property_flag(prop) & PROP_ENUM_FLAG) { + int ivalue = (int)value; + /* toggle for enum/flag buttons */ + ivalue ^= RNA_property_enum_get(&but->rnapoin, prop); + RNA_property_enum_set(&but->rnapoin, prop, ivalue); + } + else { + RNA_property_enum_set(&but->rnapoin, prop, value); + } + break; + default: + break; + } + } + + /* we can't be sure what RNA set functions actually do, + * so leave this unset */ + value = UI_BUT_VALUE_UNSET; + } + else if (but->pointype == 0) { + /* pass */ + } + else { + /* first do rounding */ + if (but->pointype == UI_BUT_POIN_CHAR) { + value = round_db_to_uchar_clamp(value); + } + else if (but->pointype == UI_BUT_POIN_SHORT) { + value = round_db_to_short_clamp(value); + } + else if (but->pointype == UI_BUT_POIN_INT) { + value = round_db_to_int_clamp(value); + } + else if (but->pointype == UI_BUT_POIN_FLOAT) { + float fval = (float)value; + if (fval >= -0.00001f && fval <= 0.00001f) { + /* prevent negative zero */ + fval = 0.0f; + } + value = fval; + } + + /* then set value with possible edit override */ + if (but->editval) { + value = *but->editval = value; + } + else if (but->pointype == UI_BUT_POIN_CHAR) { + value = *((char *)but->poin) = (char)value; + } + else if (but->pointype == UI_BUT_POIN_SHORT) { + value = *((short *)but->poin) = (short)value; + } + else if (but->pointype == UI_BUT_POIN_INT) { + value = *((int *)but->poin) = (int)value; + } + else if (but->pointype == UI_BUT_POIN_FLOAT) { + value = *((float *)but->poin) = (float)value; + } + } + + ui_but_update_select_flag(but, &value); +} + +int ui_but_string_get_max_length(uiBut *but) +{ + if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + return but->hardmax; + } + return UI_MAX_DRAW_STR; +} + +uiBut *ui_but_drag_multi_edit_get(uiBut *but) +{ + uiBut *return_but = NULL; + + BLI_assert(but->flag & UI_BUT_DRAG_MULTI); + + LISTBASE_FOREACH (uiBut *, but_iter, &but->block->buttons) { + if (but_iter->editstr) { + return_but = but_iter; + break; + } + } + + return return_but; +} + +static double ui_get_but_scale_unit(uiBut *but, double value) +{ + UnitSettings *unit = but->block->unit; + const int unit_type = UI_but_unit_type_get(but); + + /* Time unit is a bit special, not handled by BKE_scene_unit_scale() for now. */ + if (unit_type == PROP_UNIT_TIME) { /* WARNING - using evil_C :| */ + Scene *scene = CTX_data_scene((bContext *)but->block->evil_C); + return FRA2TIME(value); + } + return BKE_scene_unit_scale(unit, RNA_SUBTYPE_UNIT_VALUE(unit_type), value); +} + +/* str will be overwritten */ +void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t maxlen) +{ + if (!ui_but_is_unit(but)) { + return; + } + + UnitSettings *unit = but->block->unit; + const int unit_type = UI_but_unit_type_get(but); + char *orig_str; + + orig_str = BLI_strdup(str); + + BKE_unit_name_to_alt(str, maxlen, orig_str, unit->system, RNA_SUBTYPE_UNIT_VALUE(unit_type)); + + MEM_freeN(orig_str); +} + +/** + * \param float_precision: Override the button precision. + */ +static void ui_get_but_string_unit( + uiBut *but, char *str, int len_max, double value, bool pad, int float_precision) +{ + UnitSettings *unit = but->block->unit; + const int unit_type = UI_but_unit_type_get(but); + int precision; + + if (unit->scale_length < 0.0001f) { + unit->scale_length = 1.0f; /* XXX do_versions */ + } + + /* Use precision override? */ + if (float_precision == -1) { + /* Sanity checks */ + precision = (int)ui_but_get_float_precision(but); + if (precision > UI_PRECISION_FLOAT_MAX) { + precision = UI_PRECISION_FLOAT_MAX; + } + else if (precision == -1) { + precision = 2; + } + } + else { + precision = float_precision; + } + + BKE_unit_value_as_string(str, + len_max, + ui_get_but_scale_unit(but, value), + precision, + RNA_SUBTYPE_UNIT_VALUE(unit_type), + unit, + pad); +} + +static float ui_get_but_step_unit(uiBut *but, float step_default) +{ + const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); + const double step_orig = step_default * UI_PRECISION_FLOAT_SCALE; + /* Scaling up 'step_origg ' here is a bit arbitrary, + * its just giving better scales from user POV */ + const double scale_step = ui_get_but_scale_unit(but, step_orig * 10); + const double step = BKE_unit_closest_scalar(scale_step, but->block->unit->system, unit_type); + + /* -1 is an error value */ + if (step == -1.0f) { + return step_default; + } + + const double scale_unit = ui_get_but_scale_unit(but, 1.0); + const double step_unit = BKE_unit_closest_scalar( + scale_unit, but->block->unit->system, unit_type); + double step_final; + + BLI_assert(step > 0.0); + + step_final = (step / scale_unit) / (double)UI_PRECISION_FLOAT_SCALE; + + if (step == step_unit) { + /* Logic here is to scale by the original 'step_orig' + * only when the unit step matches the scaled step. + * + * This is needed for units that don't have a wide range of scales (degrees for eg.). + * Without this we can't select between a single degree, or a 10th of a degree. + */ + step_final *= step_orig; + } + + return (float)step_final; +} + +/** + * \param float_precision: For number buttons the precision + * to use or -1 to fallback to the button default. + * \param use_exp_float: Use exponent representation of floats + * when out of reasonable range (outside of 1e3/1e-3). + */ +void ui_but_string_get_ex(uiBut *but, + char *str, + const size_t maxlen, + const int float_precision, + const bool use_exp_float, + bool *r_use_exp_float) +{ + if (r_use_exp_float) { + *r_use_exp_float = false; + } + + if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU, UI_BTYPE_TAB)) { + const PropertyType type = RNA_property_type(but->rnaprop); + + int buf_len; + const char *buf = NULL; + if ((but->type == UI_BTYPE_TAB) && (but->custom_data)) { + StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); + PointerRNA ptr; + + /* uiBut.custom_data points to data this tab represents (e.g. workspace). + * uiBut.rnapoin/prop store an active value (e.g. active workspace). */ + RNA_pointer_create(but->rnapoin.owner_id, ptr_type, but->custom_data, &ptr); + buf = RNA_struct_name_get_alloc(&ptr, str, maxlen, &buf_len); + } + else if (type == PROP_STRING) { + /* RNA string */ + buf = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, str, maxlen, &buf_len); + } + else if (type == PROP_ENUM) { + /* RNA enum */ + const int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); + if (RNA_property_enum_name( + (bContext *)but->block->evil_C, &but->rnapoin, but->rnaprop, value, &buf)) { + BLI_strncpy(str, buf, maxlen); + buf = str; + } + } + else if (type == PROP_POINTER) { + /* RNA pointer */ + PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); + buf = RNA_struct_name_get_alloc(&ptr, str, maxlen, &buf_len); + } + else { + BLI_assert(0); + } + + if (buf == NULL) { + str[0] = '\0'; + } + else if (buf != str) { + BLI_assert(maxlen <= buf_len + 1); + /* string was too long, we have to truncate */ + if (UI_but_is_utf8(but)) { + BLI_strncpy_utf8(str, buf, maxlen); + } + else { + BLI_strncpy(str, buf, maxlen); + } + MEM_freeN((void *)buf); + } + } + else if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + /* string */ + BLI_strncpy(str, but->poin, maxlen); + return; + } + else if (ui_but_anim_expression_get(but, str, maxlen)) { + /* driver expression */ + } + else { + /* number editing */ + const double value = ui_but_value_get(but); + + PropertySubType subtype = PROP_NONE; + if (but->rnaprop) { + subtype = RNA_property_subtype(but->rnaprop); + } + + if (ui_but_is_float(but)) { + int prec = (float_precision == -1) ? ui_but_calc_float_precision(but, value) : + float_precision; + + if (ui_but_is_unit(but)) { + ui_get_but_string_unit(but, str, maxlen, value, false, prec); + } + else if (subtype == PROP_FACTOR) { + if (U.factor_display_type == USER_FACTOR_AS_FACTOR) { + BLI_snprintf(str, maxlen, "%.*f", prec, value); + } + else { + BLI_snprintf(str, maxlen, "%.*f", MAX2(0, prec - 2), value * 100); + } + } + else { + const int int_digits_num = integer_digits_f(value); + if (use_exp_float) { + if (int_digits_num < -6 || int_digits_num > 12) { + BLI_snprintf(str, maxlen, "%.*g", prec, value); + if (r_use_exp_float) { + *r_use_exp_float = true; + } + } + else { + prec -= int_digits_num; + CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); + BLI_snprintf(str, maxlen, "%.*f", prec, value); + } + } + else { + prec -= int_digits_num; + CLAMP(prec, 0, UI_PRECISION_FLOAT_MAX); + BLI_snprintf(str, maxlen, "%.*f", prec, value); + } + } + } + else { + BLI_snprintf(str, maxlen, "%d", (int)value); + } + } +} +void ui_but_string_get(uiBut *but, char *str, const size_t maxlen) +{ + ui_but_string_get_ex(but, str, maxlen, -1, false, NULL); +} + +/** + * A version of #ui_but_string_get_ex for dynamic buffer sizes + * (where #ui_but_string_get_max_length returns 0). + * + * \param r_str_size: size of the returned string (including terminator). + */ +char *ui_but_string_get_dynamic(uiBut *but, int *r_str_size) +{ + char *str = NULL; + *r_str_size = 1; + + if (but->rnaprop && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + const PropertyType type = RNA_property_type(but->rnaprop); + + if (type == PROP_STRING) { + /* RNA string */ + str = RNA_property_string_get_alloc(&but->rnapoin, but->rnaprop, NULL, 0, r_str_size); + (*r_str_size) += 1; + } + else if (type == PROP_ENUM) { + /* RNA enum */ + const int value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); + const char *value_id; + if (!RNA_property_enum_name( + (bContext *)but->block->evil_C, &but->rnapoin, but->rnaprop, value, &value_id)) { + value_id = ""; + } + + *r_str_size = strlen(value_id) + 1; + str = BLI_strdupn(value_id, *r_str_size); + } + else if (type == PROP_POINTER) { + /* RNA pointer */ + PointerRNA ptr = RNA_property_pointer_get(&but->rnapoin, but->rnaprop); + str = RNA_struct_name_get_alloc(&ptr, NULL, 0, r_str_size); + (*r_str_size) += 1; + } + else { + BLI_assert(0); + } + } + else { + BLI_assert(0); + } + + if (UNLIKELY(str == NULL)) { + /* should never happen, paranoid check */ + *r_str_size = 1; + str = BLI_strdup(""); + BLI_assert(0); + } + + return str; +} + +/** + * Report a generic error prefix when evaluating a string with #BPY_run_string_as_number + * as the Python error on its own doesn't provide enough context. + */ +#define UI_NUMBER_EVAL_ERROR_PREFIX IFACE_("Error evaluating number, see Info editor for details") + +static bool ui_number_from_string_units( + bContext *C, const char *str, const int unit_type, const UnitSettings *unit, double *r_value) +{ + char *error = NULL; + const bool ok = user_string_to_number(C, str, unit, unit_type, r_value, true, &error); + if (error) { + ReportList *reports = CTX_wm_reports(C); + BKE_reportf(reports, RPT_ERROR, "%s: %s", UI_NUMBER_EVAL_ERROR_PREFIX, error); + MEM_freeN(error); + } + return ok; +} + +static bool ui_number_from_string_units_with_but(bContext *C, + const char *str, + const uiBut *but, + double *r_value) +{ + const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); + const UnitSettings *unit = but->block->unit; + return ui_number_from_string_units(C, str, unit_type, unit, r_value); +} + +static bool ui_number_from_string(bContext *C, const char *str, double *r_value) +{ + bool ok; +#ifdef WITH_PYTHON + struct BPy_RunErrInfo err_info = { + .reports = CTX_wm_reports(C), + .report_prefix = UI_NUMBER_EVAL_ERROR_PREFIX, + }; + ok = BPY_run_string_as_number(C, NULL, str, &err_info, r_value); +#else + UNUSED_VARS(C); + *r_value = atof(str); + ok = true; +#endif + return ok; +} + +static bool ui_number_from_string_factor(bContext *C, const char *str, double *r_value) +{ + const int len = strlen(str); + if (BLI_strn_endswith(str, "%", len)) { + char *str_new = BLI_strdupn(str, len - 1); + const bool success = ui_number_from_string(C, str_new, r_value); + MEM_freeN(str_new); + *r_value /= 100.0; + return success; + } + if (!ui_number_from_string(C, str, r_value)) { + return false; + } + if (U.factor_display_type == USER_FACTOR_AS_PERCENTAGE) { + *r_value /= 100.0; + } + return true; +} + +static bool ui_number_from_string_percentage(bContext *C, const char *str, double *r_value) +{ + const int len = strlen(str); + if (BLI_strn_endswith(str, "%", len)) { + char *str_new = BLI_strdupn(str, len - 1); + const bool success = ui_number_from_string(C, str_new, r_value); + MEM_freeN(str_new); + return success; + } + return ui_number_from_string(C, str, r_value); +} + +bool ui_but_string_eval_number(bContext *C, const uiBut *but, const char *str, double *r_value) +{ + if (str[0] == '\0') { + *r_value = 0.0; + return true; + } + + PropertySubType subtype = PROP_NONE; + if (but->rnaprop) { + subtype = RNA_property_subtype(but->rnaprop); + } + + if (ui_but_is_float(but)) { + if (ui_but_is_unit(but)) { + return ui_number_from_string_units_with_but(C, str, but, r_value); + } + if (subtype == PROP_FACTOR) { + return ui_number_from_string_factor(C, str, r_value); + } + if (subtype == PROP_PERCENTAGE) { + return ui_number_from_string_percentage(C, str, r_value); + } + return ui_number_from_string(C, str, r_value); + } + return ui_number_from_string(C, str, r_value); +} + +/* just the assignment/free part */ +static void ui_but_string_set_internal(uiBut *but, const char *str, size_t str_len) +{ + BLI_assert(str_len == strlen(str)); + BLI_assert(but->str == NULL); + str_len += 1; + + if (str_len > UI_MAX_NAME_STR) { + but->str = (char *)MEM_mallocN(str_len, "ui_def_but str"); + } + else { + but->str = but->strdata; + } + memcpy(but->str, str, str_len); +} + +static void ui_but_string_free_internal(uiBut *but) +{ + if (but->str) { + if (but->str != but->strdata) { + MEM_freeN(but->str); + } + /* must call 'ui_but_string_set_internal' after */ + but->str = NULL; + } +} + +bool ui_but_string_set(bContext *C, uiBut *but, const char *str) +{ + if (but->rnaprop && but->rnapoin.data && ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + if (RNA_property_editable(&but->rnapoin, but->rnaprop)) { + const PropertyType type = RNA_property_type(but->rnaprop); + + if (type == PROP_STRING) { + /* RNA string */ + RNA_property_string_set(&but->rnapoin, but->rnaprop, str); + return true; + } + + if (type == PROP_POINTER) { + if (str[0] == '\0') { + RNA_property_pointer_set(&but->rnapoin, but->rnaprop, PointerRNA_NULL, NULL); + return true; + } + + uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : NULL; + /* RNA pointer */ + PointerRNA rptr; + + /* This is kind of hackish, in theory think we could only ever use the second member of + * this if/else, since ui_searchbox_apply() is supposed to always set that pointer when + * we are storing pointers... But keeping str search first for now, + * to try to break as little as possible existing code. All this is band-aids anyway. + * Fact remains, using editstr as main 'reference' over whole search button thingy + * is utterly weak and should be redesigned imho, but that's not a simple task. */ + if (search_but && search_but->rnasearchprop && + RNA_property_collection_lookup_string( + &search_but->rnasearchpoin, search_but->rnasearchprop, str, &rptr)) { + RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr, NULL); + } + else if (search_but->item_active != NULL) { + RNA_pointer_create(NULL, + RNA_property_pointer_type(&but->rnapoin, but->rnaprop), + search_but->item_active, + &rptr); + RNA_property_pointer_set(&but->rnapoin, but->rnaprop, rptr, NULL); + } + + return true; + } + + if (type == PROP_ENUM) { + int value; + if (RNA_property_enum_value( + (bContext *)but->block->evil_C, &but->rnapoin, but->rnaprop, str, &value)) { + RNA_property_enum_set(&but->rnapoin, but->rnaprop, value); + return true; + } + return false; + } + BLI_assert(0); + } + } + else if (but->type == UI_BTYPE_TAB) { + if (but->rnaprop && but->custom_data) { + StructRNA *ptr_type = RNA_property_pointer_type(&but->rnapoin, but->rnaprop); + PointerRNA ptr; + PropertyRNA *prop; + + /* uiBut.custom_data points to data this tab represents (e.g. workspace). + * uiBut.rnapoin/prop store an active value (e.g. active workspace). */ + RNA_pointer_create(but->rnapoin.owner_id, ptr_type, but->custom_data, &ptr); + prop = RNA_struct_name_property(ptr_type); + if (RNA_property_editable(&ptr, prop)) { + RNA_property_string_set(&ptr, prop, str); + } + } + } + else if (but->type == UI_BTYPE_TEXT) { + /* string */ + if (!but->poin) { + str = ""; + } + else if (UI_but_is_utf8(but)) { + BLI_strncpy_utf8(but->poin, str, but->hardmax); + } + else { + BLI_strncpy(but->poin, str, but->hardmax); + } + + return true; + } + else if (but->type == UI_BTYPE_SEARCH_MENU) { + /* string */ + BLI_strncpy(but->poin, str, but->hardmax); + return true; + } + else if (ui_but_anim_expression_set(but, str)) { + /* driver expression */ + return true; + } + else if (str[0] == '#') { + /* shortcut to create new driver expression (versus immediate Py-execution) */ + return ui_but_anim_expression_create(but, str + 1); + } + else { + /* number editing */ + double value; + + if (ui_but_string_eval_number(C, but, str, &value) == false) { + WM_report_banner_show(); + return false; + } + + if (!ui_but_is_float(but)) { + value = floor(value + 0.5); + } + + /* not that we use hard limits here */ + if (value < (double)but->hardmin) { + value = but->hardmin; + } + if (value > (double)but->hardmax) { + value = but->hardmax; + } + + ui_but_value_set(but, value); + return true; + } + + return false; +} + +static double soft_range_round_up(double value, double max) +{ + /* round up to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. + * checking for 0.0 prevents floating point exceptions */ + const double newmax = (value != 0.0) ? pow(10.0, ceil(log(value) / M_LN10)) : 0.0; + + if (newmax * 0.2 >= max && newmax * 0.2 >= value) { + return newmax * 0.2; + } + if (newmax * 0.5 >= max && newmax * 0.5 >= value) { + return newmax * 0.5; + } + return newmax; +} + +static double soft_range_round_down(double value, double max) +{ + /* round down to .., 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, .. + * checking for 0.0 prevents floating point exceptions */ + const double newmax = (value != 0.0) ? pow(10.0, floor(log(value) / M_LN10)) : 0.0; + + if (newmax * 5.0 <= max && newmax * 5.0 <= value) { + return newmax * 5.0; + } + if (newmax * 2.0 <= max && newmax * 2.0 <= value) { + return newmax * 2.0; + } + return newmax; +} + +void ui_but_range_set_hard(uiBut *but) +{ + if (but->rnaprop == NULL) { + return; + } + + const PropertyType type = RNA_property_type(but->rnaprop); + + /* clamp button range to something reasonable in case + * we get -inf/inf from RNA properties */ + if (type == PROP_INT) { + int imin, imax; + RNA_property_int_range(&but->rnapoin, but->rnaprop, &imin, &imax); + but->hardmin = (imin == INT_MIN) ? -1e4 : imin; + but->hardmax = (imin == INT_MAX) ? 1e4 : imax; + } + else if (type == PROP_FLOAT) { + float fmin, fmax; + RNA_property_float_range(&but->rnapoin, but->rnaprop, &fmin, &fmax); + but->hardmin = (fmin == -FLT_MAX) ? (float)-1e4 : fmin; + but->hardmax = (fmax == FLT_MAX) ? (float)1e4 : fmax; + } +} + +/* note: this could be split up into functions which handle arrays and not */ +void ui_but_range_set_soft(uiBut *but) +{ + /* ideally we would not limit this but practically, its more than + * enough worst case is very long vectors wont use a smart soft-range + * which isn't so bad. */ + + if (but->rnaprop) { + const PropertyType type = RNA_property_type(but->rnaprop); + const PropertySubType subtype = RNA_property_subtype(but->rnaprop); + double softmin, softmax /*, step, precision*/; + double value_min; + double value_max; + + /* clamp button range to something reasonable in case + * we get -inf/inf from RNA properties */ + if (type == PROP_INT) { + const bool is_array = RNA_property_array_check(but->rnaprop); + int imin, imax, istep; + + RNA_property_int_ui_range(&but->rnapoin, but->rnaprop, &imin, &imax, &istep); + softmin = (imin == INT_MIN) ? -1e4 : imin; + softmax = (imin == INT_MAX) ? 1e4 : imax; + /*step = istep;*/ /*UNUSED*/ + /*precision = 1;*/ /*UNUSED*/ + + if (is_array) { + int value_range[2]; + RNA_property_int_get_array_range(&but->rnapoin, but->rnaprop, value_range); + value_min = (double)value_range[0]; + value_max = (double)value_range[1]; + } + else { + value_min = value_max = ui_but_value_get(but); + } + } + else if (type == PROP_FLOAT) { + const bool is_array = RNA_property_array_check(but->rnaprop); + float fmin, fmax, fstep, fprecision; + + RNA_property_float_ui_range(&but->rnapoin, but->rnaprop, &fmin, &fmax, &fstep, &fprecision); + softmin = (fmin == -FLT_MAX) ? (float)-1e4 : fmin; + softmax = (fmax == FLT_MAX) ? (float)1e4 : fmax; + /*step = fstep;*/ /*UNUSED*/ + /*precision = fprecision;*/ /*UNUSED*/ + + /* Use shared min/max for array values, except for color alpha. */ + if (is_array && !(subtype == PROP_COLOR && but->rnaindex == 3)) { + float value_range[2]; + RNA_property_float_get_array_range(&but->rnapoin, but->rnaprop, value_range); + value_min = (double)value_range[0]; + value_max = (double)value_range[1]; + } + else { + value_min = value_max = ui_but_value_get(but); + } + } + else { + return; + } + + /* if the value goes out of the soft/max range, adapt the range */ + if (value_min + 1e-10 < softmin) { + if (value_min < 0.0) { + softmin = -soft_range_round_up(-value_min, -softmin); + } + else { + softmin = soft_range_round_down(value_min, softmin); + } + + if (softmin < (double)but->hardmin) { + softmin = (double)but->hardmin; + } + } + if (value_max - 1e-10 > softmax) { + if (value_max < 0.0) { + softmax = -soft_range_round_down(-value_max, -softmax); + } + else { + softmax = soft_range_round_up(value_max, softmax); + } + + if (softmax > (double)but->hardmax) { + softmax = but->hardmax; + } + } + + but->softmin = softmin; + but->softmax = softmax; + } + else if (but->poin && (but->pointype & UI_BUT_POIN_TYPES)) { + float value = ui_but_value_get(but); + if (isfinite(value)) { + CLAMP(value, but->hardmin, but->hardmax); + but->softmin = min_ff(but->softmin, value); + but->softmax = max_ff(but->softmax, value); + } + } +} + +/* ******************* Free ********************/ + +/** + * Free data specific to a certain button type. + * For now just do in a switch-case, we could instead have a callback stored in #uiBut and set that + * in #ui_but_alloc_info(). + */ +static void ui_but_free_type_specific(uiBut *but) +{ + switch (but->type) { + case UI_BTYPE_SEARCH_MENU: { + uiButSearch *search_but = (uiButSearch *)but; + + if (search_but->arg_free_fn) { + search_but->arg_free_fn(search_but->arg); + search_but->arg = NULL; + } + break; + } + default: + break; + } +} + +/* can be called with C==NULL */ +static void ui_but_free(const bContext *C, uiBut *but) +{ + if (but->opptr) { + WM_operator_properties_free(but->opptr); + MEM_freeN(but->opptr); + } + + if (but->func_argN) { + MEM_freeN(but->func_argN); + } + + if (but->tip_argN) { + MEM_freeN(but->tip_argN); + } + + if (but->hold_argN) { + MEM_freeN(but->hold_argN); + } + + ui_but_free_type_specific(but); + + if (but->active) { + /* XXX solve later, buttons should be free-able without context ideally, + * however they may have open tooltips or popup windows, which need to + * be closed using a context pointer */ + if (C) { + ui_but_active_free(C, but); + } + else { + if (but->active) { + MEM_freeN(but->active); + } + } + } + if (but->str && but->str != but->strdata) { + MEM_freeN(but->str); + } + + if ((but->type == UI_BTYPE_IMAGE) && but->poin) { + IMB_freeImBuf((struct ImBuf *)but->poin); + } + + if (but->dragpoin && (but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + } + ui_but_extra_operator_icons_free(but); + + BLI_assert(UI_butstore_is_registered(but->block, but) == false); + + MEM_freeN(but); +} + +/* can be called with C==NULL */ +void UI_block_free(const bContext *C, uiBlock *block) +{ + UI_butstore_clear(block); + + uiBut *but; + while ((but = (uiBut *)BLI_pophead(&block->buttons))) { + ui_but_free(C, but); + } + + if (block->unit) { + MEM_freeN(block->unit); + } + + if (block->func_argN) { + MEM_freeN(block->func_argN); + } + + CTX_store_free_list(&block->contexts); + + BLI_freelistN(&block->saferct); + BLI_freelistN(&block->color_pickers.list); + + ui_block_free_button_groups(block); + + MEM_freeN(block); +} + +void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb) +{ + ARegion *region = CTX_wm_region(C); + wmWindow *window = CTX_wm_window(C); + + LISTBASE_FOREACH (uiBlock *, block, lb) { + if (block->active) { + ui_update_window_matrix(window, region, block); + } + } +} + +void UI_blocklist_draw(const bContext *C, const ListBase *lb) +{ + LISTBASE_FOREACH (uiBlock *, block, lb) { + if (block->active) { + UI_block_draw(C, block); + } + } +} + +/* can be called with C==NULL */ +void UI_blocklist_free(const bContext *C, ListBase *lb) +{ + uiBlock *block; + while ((block = (uiBlock *)BLI_pophead(lb))) { + UI_block_free(C, block); + } +} + +void UI_blocklist_free_inactive(const bContext *C, ListBase *lb) +{ + LISTBASE_FOREACH_MUTABLE (uiBlock *, block, lb) { + if (!block->handle) { + if (block->active) { + block->active = false; + } + else { + BLI_remlink(lb, block); + UI_block_free(C, block); + } + } + } +} + +void UI_block_region_set(uiBlock *block, ARegion *region) +{ + ListBase *lb = ®ion->uiblocks; + uiBlock *oldblock = NULL; + + /* each listbase only has one block with this name, free block + * if is already there so it can be rebuilt from scratch */ + if (lb) { + oldblock = (uiBlock *)BLI_findstring(lb, block->name, offsetof(uiBlock, name)); + + if (oldblock) { + oldblock->active = false; + oldblock->panel = NULL; + oldblock->handle = NULL; + } + + /* at the beginning of the list! for dynamical menus/blocks */ + BLI_addhead(lb, block); + } + + block->oldblock = oldblock; +} + +uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, eUIEmbossType emboss) +{ + wmWindow *window = CTX_wm_window(C); + Scene *scene = CTX_data_scene(C); + + uiBlock *block = (uiBlock *)MEM_callocN(sizeof(uiBlock), "uiBlock"); + block->active = true; + block->emboss = emboss; + block->evil_C = (void *)C; /* XXX */ + + BLI_listbase_clear(&block->button_groups); + + if (scene) { + /* store display device name, don't lookup for transformations yet + * block could be used for non-color displays where looking up for transformation + * would slow down redraw, so only lookup for actual transform when it's indeed + * needed + */ + STRNCPY(block->display_device, scene->display_settings.display_device); + + /* copy to avoid crash when scene gets deleted with ui still open */ + block->unit = (UnitSettings *)MEM_mallocN(sizeof(scene->unit), "UI UnitSettings"); + memcpy(block->unit, &scene->unit, sizeof(scene->unit)); + } + else { + STRNCPY(block->display_device, IMB_colormanagement_display_get_default_name()); + } + + BLI_strncpy(block->name, name, sizeof(block->name)); + + if (region) { + UI_block_region_set(block, region); + } + + /* Set window matrix and aspect for region and OpenGL state. */ + ui_update_window_matrix(window, region, block); + + /* Tag as popup menu if not created within a region. */ + if (!(region && region->visible)) { + block->auto_open = true; + block->flag |= UI_BLOCK_LOOP; + } + + return block; +} + +char UI_block_emboss_get(uiBlock *block) +{ + return block->emboss; +} + +void UI_block_emboss_set(uiBlock *block, eUIEmbossType emboss) +{ + block->emboss = emboss; +} + +void UI_block_theme_style_set(uiBlock *block, char theme_style) +{ + block->theme_style = theme_style; +} + +bool UI_block_is_search_only(const uiBlock *block) +{ + return block->flag & UI_BLOCK_SEARCH_ONLY; +} + +/** + * Use when a block must be searched to give accurate results + * for the whole region but shouldn't be displayed. + */ +void UI_block_set_search_only(uiBlock *block, bool search_only) +{ + SET_FLAG_FROM_TEST(block->flag, search_only, UI_BLOCK_SEARCH_ONLY); +} + +static void ui_but_build_drawstr_float(uiBut *but, double value) +{ + size_t slen = 0; + STR_CONCAT(but->drawstr, slen, but->str); + + PropertySubType subtype = PROP_NONE; + if (but->rnaprop) { + subtype = RNA_property_subtype(but->rnaprop); + } + + /* Change negative zero to regular zero, without altering anything else. */ + value += +0.0f; + + if (value == (double)FLT_MAX) { + STR_CONCAT(but->drawstr, slen, "inf"); + } + else if (value == (double)-FLT_MAX) { + STR_CONCAT(but->drawstr, slen, "-inf"); + } + else if (subtype == PROP_PERCENTAGE) { + const int prec = ui_but_calc_float_precision(but, value); + STR_CONCATF(but->drawstr, slen, "%.*f%%", prec, value); + } + else if (subtype == PROP_PIXEL) { + const int prec = ui_but_calc_float_precision(but, value); + STR_CONCATF(but->drawstr, slen, "%.*f px", prec, value); + } + else if (subtype == PROP_FACTOR) { + const int precision = ui_but_calc_float_precision(but, value); + + if (U.factor_display_type == USER_FACTOR_AS_FACTOR) { + STR_CONCATF(but->drawstr, slen, "%.*f", precision, value); + } + else { + STR_CONCATF(but->drawstr, slen, "%.*f%%", MAX2(0, precision - 2), value * 100); + } + } + else if (ui_but_is_unit(but)) { + char new_str[sizeof(but->drawstr)]; + ui_get_but_string_unit(but, new_str, sizeof(new_str), value, true, -1); + STR_CONCAT(but->drawstr, slen, new_str); + } + else { + const int prec = ui_but_calc_float_precision(but, value); + STR_CONCATF(but->drawstr, slen, "%.*f", prec, value); + } +} + +static void ui_but_build_drawstr_int(uiBut *but, int value) +{ + size_t slen = 0; + STR_CONCAT(but->drawstr, slen, but->str); + + PropertySubType subtype = PROP_NONE; + if (but->rnaprop) { + subtype = RNA_property_subtype(but->rnaprop); + } + + STR_CONCATF(but->drawstr, slen, "%d", value); + + if (subtype == PROP_PERCENTAGE) { + STR_CONCAT(but->drawstr, slen, "%"); + } + else if (subtype == PROP_PIXEL) { + STR_CONCAT(but->drawstr, slen, " px"); + } +} + +/** + * \param but: Button to update. + * \param validate: When set, this function may change the button value. + * Otherwise treat the button value as read-only. + */ +static void ui_but_update_ex(uiBut *but, const bool validate) +{ + /* if something changed in the button */ + double value = UI_BUT_VALUE_UNSET; + + ui_but_update_select_flag(but, &value); + + /* only update soft range while not editing */ + if (!ui_but_is_editing(but)) { + if ((but->rnaprop != NULL) || (but->poin && (but->pointype & UI_BUT_POIN_TYPES))) { + ui_but_range_set_soft(but); + } + } + + /* test for min and max, icon sliders, etc */ + switch (but->type) { + case UI_BTYPE_NUM: + case UI_BTYPE_SCROLL: + case UI_BTYPE_NUM_SLIDER: + if (validate) { + UI_GET_BUT_VALUE_INIT(but, value); + if (value < (double)but->hardmin) { + ui_but_value_set(but, but->hardmin); + } + else if (value > (double)but->hardmax) { + ui_but_value_set(but, but->hardmax); + } + + /* max must never be smaller than min! Both being equal is allowed though */ + BLI_assert(but->softmin <= but->softmax && but->hardmin <= but->hardmax); + } + break; + + case UI_BTYPE_ICON_TOGGLE: + case UI_BTYPE_ICON_TOGGLE_N: + if ((but->rnaprop == NULL) || (RNA_property_flag(but->rnaprop) & PROP_ICONS_CONSECUTIVE)) { + if (but->rnaprop && RNA_property_flag(but->rnaprop) & PROP_ICONS_REVERSE) { + but->drawflag |= UI_BUT_ICON_REVERSE; + } + + but->iconadd = (but->flag & UI_SELECT) ? 1 : 0; + } + break; + + /* quiet warnings for unhandled types */ + default: + break; + } + + /* safety is 4 to enable small number buttons (like 'users') */ + // okwidth = -4 + (BLI_rcti_size_x(&but->rect)); /* UNUSED */ + + /* name: */ + switch (but->type) { + + case UI_BTYPE_MENU: + if (BLI_rctf_size_x(&but->rect) >= (UI_UNIT_X * 2)) { + /* only needed for menus in popup blocks that don't recreate buttons on redraw */ + if (but->block->flag & UI_BLOCK_LOOP) { + if (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_ENUM)) { + const int value_enum = RNA_property_enum_get(&but->rnapoin, but->rnaprop); + + EnumPropertyItem item; + if (RNA_property_enum_item_from_value_gettexted((bContext *)but->block->evil_C, + &but->rnapoin, + but->rnaprop, + value_enum, + &item)) { + const size_t slen = strlen(item.name); + ui_but_string_free_internal(but); + ui_but_string_set_internal(but, item.name, slen); + but->icon = (BIFIconID)item.icon; + } + } + } + BLI_strncpy(but->drawstr, but->str, sizeof(but->drawstr)); + } + break; + + case UI_BTYPE_NUM: + case UI_BTYPE_NUM_SLIDER: + if (but->editstr) { + break; + } + UI_GET_BUT_VALUE_INIT(but, value); + if (ui_but_is_float(but)) { + ui_but_build_drawstr_float(but, value); + } + else { + ui_but_build_drawstr_int(but, (int)value); + } + break; + + case UI_BTYPE_LABEL: + if (ui_but_is_float(but)) { + UI_GET_BUT_VALUE_INIT(but, value); + const int prec = ui_but_calc_float_precision(but, value); + BLI_snprintf(but->drawstr, sizeof(but->drawstr), "%s%.*f", but->str, prec, value); + } + else { + BLI_strncpy(but->drawstr, but->str, UI_MAX_DRAW_STR); + } + + break; + + case UI_BTYPE_TEXT: + case UI_BTYPE_SEARCH_MENU: + if (!but->editstr) { + char str[UI_MAX_DRAW_STR]; + + ui_but_string_get(but, str, UI_MAX_DRAW_STR); + BLI_snprintf(but->drawstr, sizeof(but->drawstr), "%s%s", but->str, str); + } + break; + + case UI_BTYPE_KEY_EVENT: { + const char *str; + if (but->flag & UI_SELECT) { + str = "Press a key"; + } + else { + UI_GET_BUT_VALUE_INIT(but, value); + str = WM_key_event_string((short)value, false); + } + BLI_snprintf(but->drawstr, UI_MAX_DRAW_STR, "%s%s", but->str, str); + break; + } + case UI_BTYPE_HOTKEY_EVENT: + if (but->flag & UI_SELECT) { + + if (but->modifier_key) { + char *str = but->drawstr; + but->drawstr[0] = '\0'; + + if (but->modifier_key & KM_SHIFT) { + str += BLI_strcpy_rlen(str, "Shift "); + } + if (but->modifier_key & KM_CTRL) { + str += BLI_strcpy_rlen(str, "Ctrl "); + } + if (but->modifier_key & KM_ALT) { + str += BLI_strcpy_rlen(str, "Alt "); + } + if (but->modifier_key & KM_OSKEY) { + str += BLI_strcpy_rlen(str, "Cmd "); + } + + (void)str; /* UNUSED */ + } + else { + BLI_strncpy(but->drawstr, "Press a key", UI_MAX_DRAW_STR); + } + } + else { + BLI_strncpy(but->drawstr, but->str, UI_MAX_DRAW_STR); + } + + break; + + case UI_BTYPE_HSVCUBE: + case UI_BTYPE_HSVCIRCLE: + break; + default: + BLI_strncpy(but->drawstr, but->str, UI_MAX_DRAW_STR); + break; + } + + /* if we are doing text editing, this will override the drawstr */ + if (but->editstr) { + but->drawstr[0] = '\0'; + } + + /* text clipping moved to widget drawing code itself */ +} + +void ui_but_update(uiBut *but) +{ + ui_but_update_ex(but, false); +} + +void ui_but_update_edited(uiBut *but) +{ + ui_but_update_ex(but, true); +} + +void UI_block_align_begin(uiBlock *block) +{ + /* if other align was active, end it */ + if (block->flag & UI_BUT_ALIGN) { + UI_block_align_end(block); + } + + block->flag |= UI_BUT_ALIGN_DOWN; + block->alignnr++; + + /* buttons declared after this call will get this align nr */ /* XXX flag? */ +} + +void UI_block_align_end(uiBlock *block) +{ + block->flag &= ~UI_BUT_ALIGN; /* all 4 flags */ +} + +struct ColorManagedDisplay *ui_block_cm_display_get(uiBlock *block) +{ + return IMB_colormanagement_display_get_named(block->display_device); +} + +void ui_block_cm_to_display_space_v3(uiBlock *block, float pixel[3]) +{ + struct ColorManagedDisplay *display = ui_block_cm_display_get(block); + + IMB_colormanagement_scene_linear_to_display_v3(pixel, display); +} + +static void ui_but_alloc_info(const eButType type, + size_t *r_alloc_size, + const char **r_alloc_str, + bool *r_has_custom_type) +{ + size_t alloc_size; + const char *alloc_str; + bool has_custom_type = true; + + switch (type) { + case UI_BTYPE_NUM: + alloc_size = sizeof(uiButNumber); + alloc_str = "uiButNumber"; + break; + case UI_BTYPE_COLOR: + alloc_size = sizeof(uiButColor); + alloc_str = "uiButColor"; + break; + case UI_BTYPE_DECORATOR: + alloc_size = sizeof(uiButDecorator); + alloc_str = "uiButDecorator"; + break; + case UI_BTYPE_TAB: + alloc_size = sizeof(uiButTab); + alloc_str = "uiButTab"; + break; + case UI_BTYPE_SEARCH_MENU: + alloc_size = sizeof(uiButSearch); + alloc_str = "uiButSearch"; + break; + case UI_BTYPE_PROGRESS_BAR: + alloc_size = sizeof(uiButProgressbar); + alloc_str = "uiButProgressbar"; + break; + case UI_BTYPE_HSVCUBE: + alloc_size = sizeof(uiButHSVCube); + alloc_str = "uiButHSVCube"; + break; + case UI_BTYPE_COLORBAND: + alloc_size = sizeof(uiButColorBand); + alloc_str = "uiButColorBand"; + break; + case UI_BTYPE_CURVE: + alloc_size = sizeof(uiButCurveMapping); + alloc_str = "uiButCurveMapping"; + break; + case UI_BTYPE_CURVEPROFILE: + alloc_size = sizeof(uiButCurveProfile); + alloc_str = "uiButCurveProfile"; + break; + default: + alloc_size = sizeof(uiBut); + alloc_str = "uiBut"; + has_custom_type = false; + break; + } + + if (r_alloc_size) { + *r_alloc_size = alloc_size; + } + if (r_alloc_str) { + *r_alloc_str = alloc_str; + } + if (r_has_custom_type) { + *r_has_custom_type = has_custom_type; + } +} + +static uiBut *ui_but_alloc(const eButType type) +{ + size_t alloc_size; + const char *alloc_str; + ui_but_alloc_info(type, &alloc_size, &alloc_str, NULL); + + return (uiBut *)MEM_callocN(alloc_size, alloc_str); +} + +/** + * Reallocate the button (new address is returned) for a new button type. + * This should generally be avoided and instead the correct type be created right away. + * + * \note Only the #uiBut data can be kept. If the old button used a derived type (e.g. #uiButTab), + * the data that is not inside #uiBut will be lost. + */ +uiBut *ui_but_change_type(uiBut *but, eButType new_type) +{ + if (but->type == new_type) { + /* Nothing to do. */ + return but; + } + + size_t alloc_size; + const char *alloc_str; + uiBut *insert_after_but = but->prev; + bool new_has_custom_type, old_has_custom_type; + + /* Remove old button address */ + BLI_remlink(&but->block->buttons, but); + + ui_but_alloc_info(but->type, NULL, NULL, &old_has_custom_type); + ui_but_alloc_info(new_type, &alloc_size, &alloc_str, &new_has_custom_type); + + if (new_has_custom_type || old_has_custom_type) { + const void *old_but_ptr = but; + /* Button may have pointer to a member within itself, this will have to be updated. */ + const bool has_str_ptr_to_self = but->str == but->strdata; + const bool has_poin_ptr_to_self = but->poin == (char *)but; + + but = (uiBut *)MEM_recallocN_id(but, alloc_size, alloc_str); + but->type = new_type; + if (has_str_ptr_to_self) { + but->str = but->strdata; + } + if (has_poin_ptr_to_self) { + but->poin = (char *)but; + } + + BLI_insertlinkafter(&but->block->buttons, insert_after_but, but); + + if (but->layout) { + const bool found_layout = ui_layout_replace_but_ptr(but->layout, old_but_ptr, but); + BLI_assert(found_layout); + UNUSED_VARS_NDEBUG(found_layout); + ui_button_group_replace_but_ptr(uiLayoutGetBlock(but->layout), old_but_ptr, but); + } + if (UI_editsource_enable_check()) { + UI_editsource_but_replace((const uiBut *)old_but_ptr, but); + } + } + + return but; +} + +/** + * \brief ui_def_but is the function that draws many button types + * + * \param x, y: The lower left hand corner of the button (X axis) + * \param width, height: The size of the button. + * + * for float buttons: + * \param a1: Click Step (how much to change the value each click) + * \param a2: Number of decimal point values to display. 0 defaults to 3 (0.000) + * 1,2,3, and a maximum of 4, all greater values will be clamped to 4. + */ +static uiBut *ui_def_but(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + BLI_assert(width >= 0 && height >= 0); + + /* we could do some more error checks here */ + if ((type & BUTTYPE) == UI_BTYPE_LABEL) { + BLI_assert((poin != NULL || min != 0.0f || max != 0.0f || (a1 == 0.0f && a2 != 0.0f) || + (a1 != 0.0f && a1 != 1.0f)) == false); + } + + if (type & UI_BUT_POIN_TYPES) { /* a pointer is required */ + if (poin == NULL) { + BLI_assert(0); + return NULL; + } + } + + uiBut *but = ui_but_alloc((eButType)(type & BUTTYPE)); + + but->type = (eButType)(type & BUTTYPE); + but->pointype = (eButPointerType)(type & UI_BUT_POIN_TYPES); + but->bit = type & UI_BUT_POIN_BIT; + but->bitnr = type & 31; + but->icon = ICON_NONE; + but->iconadd = 0; + + but->retval = retval; + + const int slen = strlen(str); + ui_but_string_set_internal(but, str, slen); + + but->rect.xmin = x; + but->rect.ymin = y; + but->rect.xmax = but->rect.xmin + width; + but->rect.ymax = but->rect.ymin + height; + + but->poin = (char *)poin; + but->hardmin = but->softmin = min; + but->hardmax = but->softmax = max; + but->a1 = a1; + but->a2 = a2; + but->tip = tip; + + but->disabled_info = block->lockstr; + but->emboss = block->emboss; + but->pie_dir = UI_RADIAL_NONE; + + but->block = block; /* pointer back, used for front-buffer status, and picker. */ + + if ((block->flag & UI_BUT_ALIGN) && ui_but_can_align(but)) { + but->alignnr = block->alignnr; + } + + but->func = block->func; + but->func_arg1 = block->func_arg1; + but->func_arg2 = block->func_arg2; + + but->funcN = block->funcN; + if (block->func_argN) { + but->func_argN = MEM_dupallocN(block->func_argN); + } + + but->pos = -1; /* cursor invisible */ + + if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* add a space to name */ + /* slen remains unchanged from previous assignment, ensure this stays true */ + if (slen > 0 && slen < UI_MAX_NAME_STR - 2) { + if (but->str[slen - 1] != ' ') { + but->str[slen] = ' '; + but->str[slen + 1] = 0; + } + } + } + + if (block->flag & UI_BLOCK_RADIAL) { + but->drawflag |= UI_BUT_TEXT_LEFT; + if (but->str && but->str[0]) { + but->drawflag |= UI_BUT_ICON_LEFT; + } + } + else if (((block->flag & UI_BLOCK_LOOP) && !ui_block_is_popover(block) && + !(block->flag & UI_BLOCK_QUICK_SETUP)) || + ELEM(but->type, + UI_BTYPE_MENU, + UI_BTYPE_TEXT, + UI_BTYPE_LABEL, + UI_BTYPE_BLOCK, + UI_BTYPE_BUT_MENU, + UI_BTYPE_SEARCH_MENU, + UI_BTYPE_PROGRESS_BAR, + UI_BTYPE_POPOVER)) { + but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT); + } +#ifdef USE_NUMBUTS_LR_ALIGN + else if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { + if (slen != 0) { + but->drawflag |= UI_BUT_TEXT_LEFT; + } + } +#endif + + but->drawflag |= (block->flag & UI_BUT_ALIGN); + + if (block->lock == true) { + but->flag |= UI_BUT_DISABLED; + } + + /* keep track of UI_interface.h */ + if (ELEM(but->type, + UI_BTYPE_BLOCK, + UI_BTYPE_BUT, + UI_BTYPE_DECORATOR, + UI_BTYPE_LABEL, + UI_BTYPE_PULLDOWN, + UI_BTYPE_ROUNDBOX, + UI_BTYPE_LISTBOX, + UI_BTYPE_BUT_MENU, + UI_BTYPE_SCROLL, + UI_BTYPE_GRIP, + UI_BTYPE_SEPR, + UI_BTYPE_SEPR_LINE, + UI_BTYPE_SEPR_SPACER) || + (but->type >= UI_BTYPE_SEARCH_MENU)) { + /* pass */ + } + else { + but->flag |= UI_BUT_UNDO; + } + + BLI_addtail(&block->buttons, but); + + if (block->curlayout) { + ui_layout_add_but(block->curlayout, but); + } + +#ifdef WITH_PYTHON + /* if the 'UI_OT_editsource' is running, extract the source info from the button */ + if (UI_editsource_enable_check()) { + UI_editsource_active_but_test(but); + } +#endif + + return but; +} + +void ui_def_but_icon(uiBut *but, const int icon, const int flag) +{ + if (icon) { + ui_icon_ensure_deferred( + (bContext *)but->block->evil_C, icon, (flag & UI_BUT_ICON_PREVIEW) != 0); + } + but->icon = (BIFIconID)icon; + but->flag |= flag; + + if (but->str && but->str[0]) { + but->drawflag |= UI_BUT_ICON_LEFT; + } +} + +/** + * Avoid using this where possible since it's better not to ask for an icon in the first place. + */ +void ui_def_but_icon_clear(uiBut *but) +{ + but->icon = ICON_NONE; + but->flag &= ~UI_HAS_ICON; + but->drawflag &= ~UI_BUT_ICON_LEFT; +} + +static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *but_p) +{ + uiBlock *block = uiLayoutGetBlock(layout); + uiPopupBlockHandle *handle = block->handle; + uiBut *but = (uiBut *)but_p; + + /* see comment in ui_item_enum_expand, re: uiname */ + const EnumPropertyItem *item_array; + + UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); + + bool free; + RNA_property_enum_items_gettexted( + (bContext *)block->evil_C, &but->rnapoin, but->rnaprop, &item_array, NULL, &free); + + /* We don't want nested rows, cols in menus. */ + UI_block_layout_set_current(block, layout); + + int totitems = 0; + int categories = 0; + int nbr_entries_nosepr = 0; + for (const EnumPropertyItem *item = item_array; item->identifier; item++, totitems++) { + if (!item->identifier[0]) { + /* inconsistent, but menus with categories do not look good flipped */ + if (item->name) { + block->flag |= UI_BLOCK_NO_FLIP; + categories++; + nbr_entries_nosepr++; + } + /* We do not want simple separators in nbr_entries_nosepr count */ + continue; + } + nbr_entries_nosepr++; + } + + /* Columns and row estimation. Ignore simple separators here. */ + int columns = (nbr_entries_nosepr + 20) / 20; + if (columns < 1) { + columns = 1; + } + if (columns > 8) { + columns = (nbr_entries_nosepr + 25) / 25; + } + + int rows = totitems / columns; + if (rows < 1) { + rows = 1; + } + while (rows * columns < totitems) { + rows++; + } + + const char *title = RNA_property_ui_name(but->rnaprop); + + if (title[0] && (categories == 0) && (block->flag & UI_BLOCK_NO_FLIP)) { + /* Title at the top for menus with categories. */ + uiDefBut( + block, UI_BTYPE_LABEL, 0, title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + uiItemS(layout); + } + + /* note, item_array[...] is reversed on access */ + + /* create items */ + uiLayout *split = uiLayoutSplit(layout, 0.0f, false); + + bool new_column; + + int column_end = 0; + uiLayout *column = NULL; + for (int a = 0; a < totitems; a++) { + new_column = (a == column_end); + if (new_column) { + /* start new column, and find out where it ends in advance, so we + * can flip the order of items properly per column */ + column_end = totitems; + + for (int b = a + 1; b < totitems; b++) { + const EnumPropertyItem *item = &item_array[b]; + + /* new column on N rows or on separation label */ + if (((b - a) % rows == 0) || (!item->identifier[0] && item->name)) { + column_end = b; + break; + } + } + + column = uiLayoutColumn(split, false); + } + + const EnumPropertyItem *item = &item_array[a]; + + if (new_column && (categories > 0) && item->identifier[0]) { + uiItemL(column, "", ICON_NONE); + uiItemS(column); + } + + if (!item->identifier[0]) { + if (item->name) { + if (item->icon) { + uiItemL(column, item->name, item->icon); + } + else { + /* Do not use uiItemL here, as our root layout is a menu one, + * it will add a fake blank icon! */ + uiDefBut(block, + UI_BTYPE_LABEL, + 0, + item->name, + 0, + 0, + UI_UNIT_X * 5, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + 0, + ""); + } + } + uiItemS(column); + } + else { + if (item->icon) { + uiDefIconTextButI(block, + UI_BTYPE_BUT_MENU, + B_NOP, + item->icon, + item->name, + 0, + 0, + UI_UNIT_X * 5, + UI_UNIT_Y, + &handle->retvalue, + item->value, + 0.0, + 0, + -1, + item->description); + } + else { + uiDefButI(block, + UI_BTYPE_BUT_MENU, + B_NOP, + item->name, + 0, + 0, + UI_UNIT_X * 5, + UI_UNIT_X, + &handle->retvalue, + item->value, + 0.0, + 0, + -1, + item->description); + } + } + } + + if (title[0] && (categories == 0) && !(block->flag & UI_BLOCK_NO_FLIP)) { + /* Title at the bottom for menus without categories. */ + uiItemS(layout); + uiDefBut( + block, UI_BTYPE_LABEL, 0, title, 0, 0, UI_UNIT_X * 5, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + } + + UI_block_layout_set_current(block, layout); + + if (free) { + MEM_freeN((void *)item_array); + } + BLI_assert((block->flag & UI_BLOCK_IS_FLIP) == 0); + block->flag |= UI_BLOCK_IS_FLIP; +} + +static void ui_def_but_rna__panel_type(bContext *C, uiLayout *layout, void *but_p) +{ + uiBut *but = (uiBut *)but_p; + const char *panel_type = (const char *)but->func_argN; + PanelType *pt = WM_paneltype_find(panel_type, true); + if (pt) { + ui_item_paneltype_func(C, layout, pt); + } + else { + char msg[256]; + SNPRINTF(msg, "Missing Panel: %s", panel_type); + uiItemL(layout, msg, ICON_NONE); + } +} + +void ui_but_rna_menu_convert_to_panel_type(uiBut *but, const char *panel_type) +{ + BLI_assert(ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)); + // BLI_assert(but->menu_create_func == ui_def_but_rna__menu); + // BLI_assert((void *)but->poin == but); + but->menu_create_func = ui_def_but_rna__panel_type; + but->func_argN = BLI_strdup(panel_type); +} + +bool ui_but_menu_draw_as_popover(const uiBut *but) +{ + return (but->menu_create_func == ui_def_but_rna__panel_type); +} + +static void ui_def_but_rna__menu_type(bContext *C, uiLayout *layout, void *but_p) +{ + uiBut *but = (uiBut *)but_p; + const char *menu_type = (const char *)but->func_argN; + MenuType *mt = WM_menutype_find(menu_type, true); + if (mt) { + ui_item_menutype_func(C, layout, mt); + } + else { + char msg[256]; + SNPRINTF(msg, "Missing Menu: %s", menu_type); + uiItemL(layout, msg, ICON_NONE); + } +} + +void ui_but_rna_menu_convert_to_menu_type(uiBut *but, const char *menu_type) +{ + BLI_assert(but->type == UI_BTYPE_MENU); + BLI_assert(but->menu_create_func == ui_def_but_rna__menu); + BLI_assert((void *)but->poin == but); + but->menu_create_func = ui_def_but_rna__menu_type; + but->func_argN = BLI_strdup(menu_type); +} + +static void ui_but_submenu_enable(uiBlock *block, uiBut *but) +{ + but->flag |= UI_BUT_ICON_SUBMENU; + block->content_hints |= UI_BLOCK_CONTAINS_SUBMENU_BUT; +} + +/** + * ui_def_but_rna_propname and ui_def_but_rna + * both take the same args except for propname vs prop, this is done so we can + * avoid an extra lookup on 'prop' when its already available. + * + * When this kind of change won't disrupt branches, best look into making more + * of our UI functions take prop rather than propname. + */ +static uiBut *ui_def_but_rna(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + const PropertyType proptype = RNA_property_type(prop); + int icon = 0; + uiMenuCreateFunc func = NULL; + const bool always_set_a1_a2 = ELEM(type, UI_BTYPE_NUM); + + if (ELEM(type, UI_BTYPE_COLOR, UI_BTYPE_HSVCIRCLE, UI_BTYPE_HSVCUBE)) { + BLI_assert(index == -1); + } + + /* use rna values if parameters are not specified */ + if ((proptype == PROP_ENUM) && ELEM(type, UI_BTYPE_MENU, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { + bool free; + const EnumPropertyItem *item; + RNA_property_enum_items((bContext *)block->evil_C, ptr, prop, &item, NULL, &free); + + int value; + /* UI_BTYPE_MENU is handled a little differently here */ + if (type == UI_BTYPE_MENU) { + value = RNA_property_enum_get(ptr, prop); + } + else { + value = (int)max; + } + + const int i = RNA_enum_from_value(item, value); + if (i != -1) { + + if (!str) { + str = item[i].name; +#ifdef WITH_INTERNATIONAL + str = CTX_IFACE_(RNA_property_translation_context(prop), str); +#endif + } + + icon = item[i].icon; + } + else { + if (!str) { + if (type == UI_BTYPE_MENU) { + str = ""; + } + else { + str = RNA_property_ui_name(prop); + } + } + } + + if (type == UI_BTYPE_MENU) { + func = ui_def_but_rna__menu; + } + + if (free) { + MEM_freeN((void *)item); + } + } + else { + if (!str) { + str = RNA_property_ui_name(prop); + } + icon = RNA_property_ui_icon(prop); + } + + if (!tip && proptype != PROP_ENUM) { + tip = RNA_property_ui_description(prop); + } + + if (min == max || a1 == -1 || a2 == -1 || always_set_a1_a2) { + if (proptype == PROP_INT) { + int hardmin, hardmax, softmin, softmax, step; + + RNA_property_int_range(ptr, prop, &hardmin, &hardmax); + RNA_property_int_ui_range(ptr, prop, &softmin, &softmax, &step); + + if (!ELEM(type, UI_BTYPE_ROW, UI_BTYPE_LISTROW) && min == max) { + min = hardmin; + max = hardmax; + } + if (a1 == -1 || always_set_a1_a2) { + a1 = step; + } + if (a2 == -1 || always_set_a1_a2) { + a2 = 0; + } + } + else if (proptype == PROP_FLOAT) { + float hardmin, hardmax, softmin, softmax, step, precision; + + RNA_property_float_range(ptr, prop, &hardmin, &hardmax); + RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); + + if (!ELEM(type, UI_BTYPE_ROW, UI_BTYPE_LISTROW) && min == max) { + min = hardmin; + max = hardmax; + } + if (a1 == -1 || always_set_a1_a2) { + a1 = step; + } + if (a2 == -1 || always_set_a1_a2) { + a2 = precision; + } + } + else if (proptype == PROP_STRING) { + min = 0; + max = RNA_property_string_maxlength(prop); + /* note, 'max' may be zero (code for dynamically resized array) */ + } + } + + /* now create button */ + uiBut *but = ui_def_but( + block, type, retval, str, x, y, width, height, NULL, min, max, a1, a2, tip); + + if (but->type == UI_BTYPE_NUM) { + /* Set default values, can be overridden later. */ + UI_but_number_step_size_set(but, a1); + UI_but_number_precision_set(but, a2); + } + + but->rnapoin = *ptr; + but->rnaprop = prop; + + if (RNA_property_array_check(but->rnaprop)) { + but->rnaindex = index; + } + else { + but->rnaindex = 0; + } + + if (icon) { + ui_def_but_icon(but, icon, UI_HAS_ICON); + } + + if (type == UI_BTYPE_MENU) { + if (but->emboss == UI_EMBOSS_PULLDOWN) { + ui_but_submenu_enable(block, but); + } + } + else if (type == UI_BTYPE_SEARCH_MENU) { + if (proptype == PROP_POINTER) { + /* Search buttons normally don't get undo, see: T54580. */ + but->flag |= UI_BUT_UNDO; + } + } + + const char *info; + if (but->rnapoin.data && !RNA_property_editable_info(&but->rnapoin, prop, &info)) { + UI_but_disable(but, info); + } + + if (proptype == PROP_POINTER) { + /* If the button shows an ID, automatically set it as focused in context so operators can + * access it.*/ + const PointerRNA pptr = RNA_property_pointer_get(ptr, prop); + if (pptr.data && RNA_struct_is_ID(pptr.type)) { + but->context = CTX_store_add(&block->contexts, "id", &pptr); + } + } + + if (but->flag & UI_BUT_UNDO && (ui_but_is_rna_undo(but) == false)) { + but->flag &= ~UI_BUT_UNDO; + } + + /* If this button uses units, calculate the step from this */ + if ((proptype == PROP_FLOAT) && ui_but_is_unit(but)) { + if (type == UI_BTYPE_NUM) { + uiButNumber *number_but = (uiButNumber *)but; + number_but->step_size = ui_get_but_step_unit(but, number_but->step_size); + } + else { + but->a1 = ui_get_but_step_unit(but, but->a1); + } + } + + if (func) { + but->menu_create_func = func; + but->poin = (char *)but; + } + + return but; +} + +static uiBut *ui_def_but_rna_propname(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + const char *propname, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + uiBut *but; + if (prop) { + but = ui_def_but_rna( + block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); + } + else { + but = ui_def_but( + block, type, retval, propname, x, y, width, height, NULL, min, max, a1, a2, tip); + + UI_but_disable(but, "Unknown Property."); + } + + return but; +} + +static uiBut *ui_def_but_operator_ptr(uiBlock *block, + int type, + wmOperatorType *ot, + int opcontext, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + if (!str) { + if (ot && ot->srna) { + str = WM_operatortype_name(ot, NULL); + } + else { + str = ""; + } + } + + if ((!tip || tip[0] == '\0') && ot && ot->srna && !ot->get_description) { + tip = RNA_struct_ui_description(ot->srna); + } + + uiBut *but = ui_def_but(block, type, -1, str, x, y, width, height, NULL, 0, 0, 0, 0, tip); + but->optype = ot; + but->opcontext = opcontext; + but->flag &= ~UI_BUT_UNDO; /* no need for ui_but_is_rna_undo(), we never need undo here */ + + if (!ot) { + UI_but_disable(but, ""); + } + + return but; +} + +uiBut *uiDefBut(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but( + block, type, retval, str, x, y, width, height, poin, min, max, a1, a2, tip); + + ui_but_update(but); + + return but; +} + +uiBut *uiDefButImage( + uiBlock *block, void *imbuf, int x, int y, short width, short height, const uchar color[4]) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_IMAGE, 0, "", x, y, width, height, imbuf, 0, 0, 0, 0, ""); + if (color) { + copy_v4_v4_uchar(but->col, color); + } + else { + but->col[0] = 255; + but->col[1] = 255; + but->col[2] = 255; + but->col[3] = 255; + } + ui_but_update(but); + return but; +} + +uiBut *uiDefButAlert(uiBlock *block, int icon, int x, int y, short width, short height) +{ + struct ImBuf *ibuf = UI_icon_alert_imbuf_get((eAlertIcon)icon); + bTheme *btheme = UI_GetTheme(); + return uiDefButImage(block, ibuf, x, y, width, height, btheme->tui.wcol_menu_back.text); +} + +/** + * if \a _x_ is a power of two (only one bit) return the power, + * otherwise return -1. + * + * for powers of two: + * \code{.c} + * ((1 << findBitIndex(x)) == x); + * \endcode + */ +static int findBitIndex(uint x) +{ + if (!x || !is_power_of_2_i(x)) { /* is_power_of_2_i(x) strips lowest bit */ + return -1; + } + int idx = 0; + + if (x & 0xFFFF0000) { + idx += 16; + x >>= 16; + } + if (x & 0xFF00) { + idx += 8; + x >>= 8; + } + if (x & 0xF0) { + idx += 4; + x >>= 4; + } + if (x & 0xC) { + idx += 2; + x >>= 2; + } + if (x & 0x2) { + idx += 1; + } + + return idx; +} + +/* Auto-complete helper functions. */ +struct AutoComplete { + size_t maxlen; + int matches; + char *truncate; + const char *startname; +}; + +AutoComplete *UI_autocomplete_begin(const char *startname, size_t maxlen) +{ + AutoComplete *autocpl; + + autocpl = (AutoComplete *)MEM_callocN(sizeof(AutoComplete), "AutoComplete"); + autocpl->maxlen = maxlen; + autocpl->matches = 0; + autocpl->truncate = (char *)MEM_callocN(sizeof(char) * maxlen, "AutoCompleteTruncate"); + autocpl->startname = startname; + + return autocpl; +} + +void UI_autocomplete_update_name(AutoComplete *autocpl, const char *name) +{ + char *truncate = autocpl->truncate; + const char *startname = autocpl->startname; + int match_index = 0; + for (int a = 0; a < autocpl->maxlen - 1; a++) { + if (startname[a] == 0 || startname[a] != name[a]) { + match_index = a; + break; + } + } + + /* found a match */ + if (startname[match_index] == 0) { + autocpl->matches++; + /* first match */ + if (truncate[0] == 0) { + BLI_strncpy(truncate, name, autocpl->maxlen); + } + else { + /* remove from truncate what is not in bone->name */ + for (int a = 0; a < autocpl->maxlen - 1; a++) { + if (name[a] == 0) { + truncate[a] = 0; + break; + } + if (truncate[a] != name[a]) { + truncate[a] = 0; + } + } + } + } +} + +int UI_autocomplete_end(AutoComplete *autocpl, char *autoname) +{ + int match = AUTOCOMPLETE_NO_MATCH; + if (autocpl->truncate[0]) { + if (autocpl->matches == 1) { + match = AUTOCOMPLETE_FULL_MATCH; + } + else { + match = AUTOCOMPLETE_PARTIAL_MATCH; + } + BLI_strncpy(autoname, autocpl->truncate, autocpl->maxlen); + } + else { + if (autoname != autocpl->startname) { /* don't copy a string over its self */ + BLI_strncpy(autoname, autocpl->startname, autocpl->maxlen); + } + } + + MEM_freeN(autocpl->truncate); + MEM_freeN(autocpl); + return match; +} + +static void ui_but_update_and_icon_set(uiBut *but, int icon) +{ + if (icon) { + ui_def_but_icon(but, icon, UI_HAS_ICON); + } + + ui_but_update(but); +} + +static uiBut *uiDefButBit(uiBlock *block, + int type, + int bit, + int retval, + const char *str, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + const int bitIdx = findBitIndex(bit); + if (bitIdx == -1) { + return NULL; + } + return uiDefBut(block, + type | UI_BUT_POIN_BIT | bitIdx, + retval, + str, + x, + y, + width, + height, + poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButF(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + float *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefBut(block, + type | UI_BUT_POIN_FLOAT, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButBitF(uiBlock *block, + int type, + int bit, + int retval, + const char *str, + int x, + int y, + short width, + short height, + float *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefButBit(block, + type | UI_BUT_POIN_FLOAT, + bit, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButI(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + int *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefBut(block, + type | UI_BUT_POIN_INT, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButBitI(uiBlock *block, + int type, + int bit, + int retval, + const char *str, + int x, + int y, + short width, + short height, + int *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefButBit(block, + type | UI_BUT_POIN_INT, + bit, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButS(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + short *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefBut(block, + type | UI_BUT_POIN_SHORT, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButBitS(uiBlock *block, + int type, + int bit, + int retval, + const char *str, + int x, + int y, + short width, + short height, + short *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefButBit(block, + type | UI_BUT_POIN_SHORT, + bit, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButC(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + char *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefBut(block, + type | UI_BUT_POIN_CHAR, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButBitC(uiBlock *block, + int type, + int bit, + int retval, + const char *str, + int x, + int y, + short width, + short height, + char *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefButBit(block, + type | UI_BUT_POIN_CHAR, + bit, + retval, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefButR(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + const char *propname, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but_rna_propname( + block, type, retval, str, x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); + ui_but_update(but); + return but; +} +uiBut *uiDefButR_prop(uiBlock *block, + int type, + int retval, + const char *str, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but_rna( + block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); + ui_but_update(but); + return but; +} + +uiBut *uiDefButO_ptr(uiBlock *block, + int type, + wmOperatorType *ot, + int opcontext, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); + ui_but_update(but); + return but; +} +uiBut *uiDefButO(uiBlock *block, + int type, + const char *opname, + int opcontext, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); + if (str == NULL && ot == NULL) { + str = opname; + } + return uiDefButO_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); +} + +/* if a1==1.0 then a2 is an extra icon blending factor (alpha 0.0 - 1.0) */ +uiBut *uiDefIconBut(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but( + block, type, retval, "", x, y, width, height, poin, min, max, a1, a2, tip); + ui_but_update_and_icon_set(but, icon); + return but; +} +static uiBut *uiDefIconButBit(uiBlock *block, + int type, + int bit, + int retval, + int icon, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + const int bitIdx = findBitIndex(bit); + if (bitIdx == -1) { + return NULL; + } + return uiDefIconBut(block, + type | UI_BUT_POIN_BIT | bitIdx, + retval, + icon, + x, + y, + width, + height, + poin, + min, + max, + a1, + a2, + tip); +} + +uiBut *uiDefIconButF(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + float *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconBut(block, + type | UI_BUT_POIN_FLOAT, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButBitF(uiBlock *block, + int type, + int bit, + int retval, + int icon, + int x, + int y, + short width, + short height, + float *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconButBit(block, + type | UI_BUT_POIN_FLOAT, + bit, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButI(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + int *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconBut(block, + type | UI_BUT_POIN_INT, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButBitI(uiBlock *block, + int type, + int bit, + int retval, + int icon, + int x, + int y, + short width, + short height, + int *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconButBit(block, + type | UI_BUT_POIN_INT, + bit, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButS(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + short *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconBut(block, + type | UI_BUT_POIN_SHORT, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButBitS(uiBlock *block, + int type, + int bit, + int retval, + int icon, + int x, + int y, + short width, + short height, + short *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconButBit(block, + type | UI_BUT_POIN_SHORT, + bit, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButC(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + char *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconBut(block, + type | UI_BUT_POIN_CHAR, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButBitC(uiBlock *block, + int type, + int bit, + int retval, + int icon, + int x, + int y, + short width, + short height, + char *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconButBit(block, + type | UI_BUT_POIN_CHAR, + bit, + retval, + icon, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconButR(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + const char *propname, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but_rna_propname( + block, type, retval, "", x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); + ui_but_update_and_icon_set(but, icon); + return but; +} +uiBut *uiDefIconButR_prop(uiBlock *block, + int type, + int retval, + int icon, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but_rna( + block, type, retval, "", x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); + ui_but_update_and_icon_set(but, icon); + return but; +} + +uiBut *uiDefIconButO_ptr(uiBlock *block, + int type, + wmOperatorType *ot, + int opcontext, + int icon, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, "", x, y, width, height, tip); + ui_but_update_and_icon_set(but, icon); + return but; +} +uiBut *uiDefIconButO(uiBlock *block, + int type, + const char *opname, + int opcontext, + int icon, + int x, + int y, + short width, + short height, + const char *tip) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); + return uiDefIconButO_ptr(block, type, ot, opcontext, icon, x, y, width, height, tip); +} + +/* Button containing both string label and icon */ +uiBut *uiDefIconTextBut(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but( + block, type, retval, str, x, y, width, height, poin, min, max, a1, a2, tip); + ui_but_update_and_icon_set(but, icon); + but->drawflag |= UI_BUT_ICON_LEFT; + return but; +} +static uiBut *uiDefIconTextButBit(uiBlock *block, + int type, + int bit, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + void *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + const int bitIdx = findBitIndex(bit); + if (bitIdx == -1) { + return NULL; + } + return uiDefIconTextBut(block, + type | UI_BUT_POIN_BIT | bitIdx, + retval, + icon, + str, + x, + y, + width, + height, + poin, + min, + max, + a1, + a2, + tip); +} + +uiBut *uiDefIconTextButF(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + float *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextBut(block, + type | UI_BUT_POIN_FLOAT, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButBitF(uiBlock *block, + int type, + int bit, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + float *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextButBit(block, + type | UI_BUT_POIN_FLOAT, + bit, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButI(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + int *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextBut(block, + type | UI_BUT_POIN_INT, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButBitI(uiBlock *block, + int type, + int bit, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + int *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextButBit(block, + type | UI_BUT_POIN_INT, + bit, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButS(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + short *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextBut(block, + type | UI_BUT_POIN_SHORT, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButBitS(uiBlock *block, + int type, + int bit, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + short *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextButBit(block, + type | UI_BUT_POIN_SHORT, + bit, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButC(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + char *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextBut(block, + type | UI_BUT_POIN_CHAR, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButBitC(uiBlock *block, + int type, + int bit, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + char *poin, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + return uiDefIconTextButBit(block, + type | UI_BUT_POIN_CHAR, + bit, + retval, + icon, + str, + x, + y, + width, + height, + (void *)poin, + min, + max, + a1, + a2, + tip); +} +uiBut *uiDefIconTextButR(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + const char *propname, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but_rna_propname( + block, type, retval, str, x, y, width, height, ptr, propname, index, min, max, a1, a2, tip); + ui_but_update_and_icon_set(but, icon); + but->drawflag |= UI_BUT_ICON_LEFT; + return but; +} +uiBut *uiDefIconTextButR_prop(uiBlock *block, + int type, + int retval, + int icon, + const char *str, + int x, + int y, + short width, + short height, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + float min, + float max, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but_rna( + block, type, retval, str, x, y, width, height, ptr, prop, index, min, max, a1, a2, tip); + ui_but_update_and_icon_set(but, icon); + but->drawflag |= UI_BUT_ICON_LEFT; + return but; +} +uiBut *uiDefIconTextButO_ptr(uiBlock *block, + int type, + wmOperatorType *ot, + int opcontext, + int icon, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but_operator_ptr(block, type, ot, opcontext, str, x, y, width, height, tip); + ui_but_update_and_icon_set(but, icon); + but->drawflag |= UI_BUT_ICON_LEFT; + return but; +} +uiBut *uiDefIconTextButO(uiBlock *block, + int type, + const char *opname, + int opcontext, + int icon, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); + if (str && str[0] == '\0') { + return uiDefIconButO_ptr(block, type, ot, opcontext, icon, x, y, width, height, tip); + } + return uiDefIconTextButO_ptr(block, type, ot, opcontext, icon, str, x, y, width, height, tip); +} + +/* END Button containing both string label and icon */ + +/* cruft to make uiBlock and uiBut private */ + +int UI_blocklist_min_y_get(ListBase *lb) +{ + int min = 0; + + LISTBASE_FOREACH (uiBlock *, block, lb) { + if (block == lb->first || block->rect.ymin < min) { + min = block->rect.ymin; + } + } + + return min; +} + +void UI_block_direction_set(uiBlock *block, char direction) +{ + block->direction = direction; +} + +/* this call escapes if there's alignment flags */ +void UI_block_order_flip(uiBlock *block) +{ + float centy, miny = 10000, maxy = -10000; + + if (U.uiflag & USER_MENUFIXEDORDER) { + return; + } + if (block->flag & UI_BLOCK_NO_FLIP) { + return; + } + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->drawflag & UI_BUT_ALIGN) { + return; + } + if (but->rect.ymin < miny) { + miny = but->rect.ymin; + } + if (but->rect.ymax > maxy) { + maxy = but->rect.ymax; + } + } + /* mirror trick */ + centy = (miny + maxy) / 2.0f; + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->rect.ymin = centy - (but->rect.ymin - centy); + but->rect.ymax = centy - (but->rect.ymax - centy); + SWAP(float, but->rect.ymin, but->rect.ymax); + } + + block->flag ^= UI_BLOCK_IS_FLIP; +} + +void UI_block_flag_enable(uiBlock *block, int flag) +{ + block->flag |= flag; +} + +void UI_block_flag_disable(uiBlock *block, int flag) +{ + block->flag &= ~flag; +} + +void UI_but_flag_enable(uiBut *but, int flag) +{ + but->flag |= flag; +} + +void UI_but_flag_disable(uiBut *but, int flag) +{ + but->flag &= ~flag; +} + +bool UI_but_flag_is_set(uiBut *but, int flag) +{ + return (but->flag & flag) != 0; +} + +void UI_but_drawflag_enable(uiBut *but, int flag) +{ + but->drawflag |= flag; +} + +void UI_but_drawflag_disable(uiBut *but, int flag) +{ + but->drawflag &= ~flag; +} + +void UI_but_disable(uiBut *but, const char *disabled_hint) +{ + UI_but_flag_enable(but, UI_BUT_DISABLED); + + /* Only one disabled hint at a time currently. Don't override the previous one here. */ + if (but->disabled_info && but->disabled_info[0]) { + return; + } + + but->disabled_info = disabled_hint; +} + +void UI_but_type_set_menu_from_pulldown(uiBut *but) +{ + BLI_assert(but->type == UI_BTYPE_PULLDOWN); + but->type = UI_BTYPE_MENU; + UI_but_drawflag_disable(but, UI_BUT_TEXT_RIGHT); + UI_but_drawflag_enable(but, UI_BUT_TEXT_LEFT); +} + +int UI_but_return_value_get(uiBut *but) +{ + return but->retval; +} + +void UI_but_drag_set_id(uiBut *but, ID *id) +{ + but->dragtype = WM_DRAG_ID; + if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; + } + but->dragpoin = (void *)id; +} + +void UI_but_drag_set_asset(uiBut *but, + const char *name, + const char *path, + int id_type, + int icon, + struct ImBuf *imb, + float scale) +{ + wmDragAsset *asset_drag = (wmDragAsset *)MEM_mallocN(sizeof(*asset_drag), "wmDragAsset"); + + BLI_strncpy(asset_drag->name, name, sizeof(asset_drag->name)); + asset_drag->path = path; + asset_drag->id_type = id_type; + + but->dragtype = WM_DRAG_ASSET; + ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ + if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + } + but->dragpoin = asset_drag; + but->dragflag |= UI_BUT_DRAGPOIN_FREE; + but->imb = imb; + but->imb_scale = scale; +} + +void UI_but_drag_set_rna(uiBut *but, PointerRNA *ptr) +{ + but->dragtype = WM_DRAG_RNA; + if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; + } + but->dragpoin = (void *)ptr; +} + +void UI_but_drag_set_path(uiBut *but, const char *path, const bool use_free) +{ + but->dragtype = WM_DRAG_PATH; + if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; + } + but->dragpoin = (void *)path; + if (use_free) { + but->dragflag |= UI_BUT_DRAGPOIN_FREE; + } +} + +void UI_but_drag_set_name(uiBut *but, const char *name) +{ + but->dragtype = WM_DRAG_NAME; + if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; + } + but->dragpoin = (void *)name; +} + +/* value from button itself */ +void UI_but_drag_set_value(uiBut *but) +{ + but->dragtype = WM_DRAG_VALUE; +} + +void UI_but_drag_set_image( + uiBut *but, const char *path, int icon, struct ImBuf *imb, float scale, const bool use_free) +{ + but->dragtype = WM_DRAG_PATH; + ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */ + if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) { + WM_drag_data_free(but->dragtype, but->dragpoin); + but->dragflag &= ~UI_BUT_DRAGPOIN_FREE; + } + but->dragpoin = (void *)path; + if (use_free) { + but->dragflag |= UI_BUT_DRAGPOIN_FREE; + } + but->imb = imb; + but->imb_scale = scale; +} + +PointerRNA *UI_but_operator_ptr_get(uiBut *but) +{ + if (but->optype && !but->opptr) { + but->opptr = (PointerRNA *)MEM_callocN(sizeof(PointerRNA), "uiButOpPtr"); + WM_operator_properties_create_ptr(but->opptr, but->optype); + } + + return but->opptr; +} + +void UI_but_unit_type_set(uiBut *but, const int unit_type) +{ + but->unit_type = (uchar)(RNA_SUBTYPE_UNIT_VALUE(unit_type)); +} + +int UI_but_unit_type_get(const uiBut *but) +{ + const int ownUnit = (int)but->unit_type; + + /* own unit define always takes precedence over RNA provided, allowing for overriding + * default value provided in RNA in a few special cases (i.e. Active Keyframe in Graph Edit) + */ + /* XXX: this doesn't allow clearing unit completely, though the same could be said for icons */ + if ((ownUnit != 0) || (but->rnaprop == NULL)) { + return ownUnit << 16; + } + return RNA_SUBTYPE_UNIT(RNA_property_subtype(but->rnaprop)); +} + +void UI_block_func_handle_set(uiBlock *block, uiBlockHandleFunc func, void *arg) +{ + block->handle_func = func; + block->handle_func_arg = arg; +} + +void UI_block_func_butmenu_set(uiBlock *block, uiMenuHandleFunc func, void *arg) +{ + block->butm_func = func; + block->butm_func_arg = arg; +} + +void UI_block_func_set(uiBlock *block, uiButHandleFunc func, void *arg1, void *arg2) +{ + block->func = func; + block->func_arg1 = arg1; + block->func_arg2 = arg2; +} + +void UI_block_funcN_set(uiBlock *block, uiButHandleNFunc funcN, void *argN, void *arg2) +{ + if (block->func_argN) { + MEM_freeN(block->func_argN); + } + + block->funcN = funcN; + block->func_argN = argN; + block->func_arg2 = arg2; +} + +void UI_but_func_rename_set(uiBut *but, uiButHandleRenameFunc func, void *arg1) +{ + but->rename_func = func; + but->rename_arg1 = arg1; +} + +void UI_but_func_drawextra_set( + uiBlock *block, + void (*func)(const bContext *C, void *idv, void *arg1, void *arg2, rcti *rect), + void *arg1, + void *arg2) +{ + block->drawextra = func; + block->drawextra_arg1 = arg1; + block->drawextra_arg2 = arg2; +} + +void UI_but_func_set(uiBut *but, uiButHandleFunc func, void *arg1, void *arg2) +{ + but->func = func; + but->func_arg1 = arg1; + but->func_arg2 = arg2; +} + +void UI_but_funcN_set(uiBut *but, uiButHandleNFunc funcN, void *argN, void *arg2) +{ + if (but->func_argN) { + MEM_freeN(but->func_argN); + } + + but->funcN = funcN; + but->func_argN = argN; + but->func_arg2 = arg2; +} + +void UI_but_func_complete_set(uiBut *but, uiButCompleteFunc func, void *arg) +{ + but->autocomplete_func = func; + but->autofunc_arg = arg; +} + +void UI_but_func_menu_step_set(uiBut *but, uiMenuStepFunc func) +{ + but->menu_step_func = func; +} + +void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *argN) +{ + but->tip_func = func; + if (but->tip_argN) { + MEM_freeN(but->tip_argN); + } + but->tip_argN = argN; +} + +void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, void *arg) +{ + but->pushed_state_func = func; + but->pushed_state_arg = arg; +} + +uiBut *uiDefBlockBut(uiBlock *block, + uiBlockCreateFunc func, + void *arg, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + but->block_create_func = func; + ui_but_update(but); + return but; +} + +uiBut *uiDefBlockButN(uiBlock *block, + uiBlockCreateFunc func, + void *argN, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, NULL, 0.0, 0.0, 0.0, 0.0, tip); + but->block_create_func = func; + if (but->func_argN) { + MEM_freeN(but->func_argN); + } + but->func_argN = argN; + ui_but_update(but); + return but; +} + +uiBut *uiDefPulldownBut(uiBlock *block, + uiBlockCreateFunc func, + void *arg, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + but->block_create_func = func; + ui_but_update(but); + return but; +} + +uiBut *uiDefMenuBut(uiBlock *block, + uiMenuCreateFunc func, + void *arg, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + but->menu_create_func = func; + ui_but_update(but); + return but; +} + +uiBut *uiDefIconTextMenuBut(uiBlock *block, + uiMenuCreateFunc func, + void *arg, + int icon, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_PULLDOWN, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + + ui_def_but_icon(but, icon, UI_HAS_ICON); + + but->drawflag |= UI_BUT_ICON_LEFT; + ui_but_submenu_enable(block, but); + + but->menu_create_func = func; + ui_but_update(but); + + return but; +} + +uiBut *uiDefIconMenuBut(uiBlock *block, + uiMenuCreateFunc func, + void *arg, + int icon, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_PULLDOWN, 0, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + + ui_def_but_icon(but, icon, UI_HAS_ICON); + but->drawflag &= ~UI_BUT_ICON_LEFT; + + but->menu_create_func = func; + ui_but_update(but); + + return but; +} + +/* Block button containing both string label and icon */ +uiBut *uiDefIconTextBlockBut(uiBlock *block, + uiBlockCreateFunc func, + void *arg, + int icon, + const char *str, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_BLOCK, 0, str, x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + + /* XXX temp, old menu calls pass on icon arrow, which is now UI_BUT_ICON_SUBMENU flag */ + if (icon != ICON_RIGHTARROW_THIN) { + ui_def_but_icon(but, icon, 0); + but->drawflag |= UI_BUT_ICON_LEFT; + } + but->flag |= UI_HAS_ICON; + ui_but_submenu_enable(block, but); + + but->block_create_func = func; + ui_but_update(but); + + return but; +} + +/* Block button containing icon */ +uiBut *uiDefIconBlockBut(uiBlock *block, + uiBlockCreateFunc func, + void *arg, + int retval, + int icon, + int x, + int y, + short width, + short height, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_BLOCK, retval, "", x, y, width, height, arg, 0.0, 0.0, 0.0, 0.0, tip); + + ui_def_but_icon(but, icon, UI_HAS_ICON); + + but->drawflag |= UI_BUT_ICON_LEFT; + + but->block_create_func = func; + ui_but_update(but); + + return but; +} + +uiBut *uiDefKeyevtButS(uiBlock *block, + int retval, + const char *str, + int x, + int y, + short width, + short height, + short *spoin, + const char *tip) +{ + uiBut *but = ui_def_but(block, + UI_BTYPE_KEY_EVENT | UI_BUT_POIN_SHORT, + retval, + str, + x, + y, + width, + height, + spoin, + 0.0, + 0.0, + 0.0, + 0.0, + tip); + ui_but_update(but); + return but; +} + +/* short pointers hardcoded */ +/* modkeypoin will be set to KM_SHIFT, KM_ALT, KM_CTRL, KM_OSKEY bits */ +uiBut *uiDefHotKeyevtButS(uiBlock *block, + int retval, + const char *str, + int x, + int y, + short width, + short height, + short *keypoin, + const short *modkeypoin, + const char *tip) +{ + uiBut *but = ui_def_but(block, + UI_BTYPE_HOTKEY_EVENT | UI_BUT_POIN_SHORT, + retval, + str, + x, + y, + width, + height, + keypoin, + 0.0, + 0.0, + 0.0, + 0.0, + tip); + but->modifier_key = *modkeypoin; + ui_but_update(but); + return but; +} + +/* arg is pointer to string/name, use UI_but_func_search_set() below to make this work */ +/* here a1 and a2, if set, control thumbnail preview rows/cols */ +uiBut *uiDefSearchBut(uiBlock *block, + void *arg, + int retval, + int icon, + int maxlen, + int x, + int y, + short width, + short height, + float a1, + float a2, + const char *tip) +{ + uiBut *but = ui_def_but( + block, UI_BTYPE_SEARCH_MENU, retval, "", x, y, width, height, arg, 0.0, maxlen, a1, a2, tip); + + ui_def_but_icon(but, icon, UI_HAS_ICON); + + but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; + + ui_but_update(but); + + return but; +} + +/** + * \note The item-pointer (referred to below) is a per search item user pointer + * passed to #UI_search_item_add (stored in #uiSearchItems.pointers). + * + * \param search_create_fn: Function to create the menu. + * \param search_update_fn: Function to refresh search content after the search text has changed. + * \param arg: user value. + * \param free_arg: Set to true if the argument is newly allocated memory for every redraw and + * should be freed when the button is destroyed. + * \param search_arg_free_fn: When non-null, use this function to free \a arg. + * \param search_exec_fn: Function that executes the action, gets \a arg as the first argument. + * The second argument as the active item-pointer + * \param active: When non-null, this item-pointer item will be visible and selected, + * otherwise the first item will be selected. + */ +void UI_but_func_search_set(uiBut *but, + uiButSearchCreateFn search_create_fn, + uiButSearchUpdateFn search_update_fn, + void *arg, + const bool free_arg, + uiButSearchArgFreeFn search_arg_free_fn, + uiButHandleFunc search_exec_fn, + void *active) +{ + uiButSearch *search_but = (uiButSearch *)but; + + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + /* needed since callers don't have access to internal functions + * (as an alternative we could expose it) */ + if (search_create_fn == NULL) { + search_create_fn = ui_searchbox_create_generic; + } + + if (search_but->arg_free_fn != NULL) { + search_but->arg_free_fn(search_but->arg); + search_but->arg = NULL; + } + + search_but->popup_create_fn = search_create_fn; + search_but->items_update_fn = search_update_fn; + search_but->item_active = active; + + search_but->arg = arg; + search_but->arg_free_fn = search_arg_free_fn; + + if (search_exec_fn) { +#ifdef DEBUG + if (search_but->but.func) { + /* watch this, can be cause of much confusion, see: T47691 */ + printf("%s: warning, overwriting button callback with search function callback!\n", + __func__); + } +#endif + /* Handling will pass the active item as arg2 later, so keep it NULL here. */ + if (free_arg) { + UI_but_funcN_set(but, search_exec_fn, search_but->arg, NULL); + } + else { + UI_but_func_set(but, search_exec_fn, search_but->arg, NULL); + } + } + + /* search buttons show red-alert if item doesn't exist, not for menus. Don't do this for + * buttons where any result is valid anyway, since any string will be valid anyway. */ + if (0 == (but->block->flag & UI_BLOCK_LOOP) && !search_but->results_are_suggestions) { + /* skip empty buttons, not all buttons need input, we only show invalid */ + if (but->drawstr[0]) { + ui_but_search_refresh(search_but); + } + } +} + +void UI_but_func_search_set_context_menu(uiBut *but, uiButSearchContextMenuFn context_menu_fn) +{ + uiButSearch *but_search = (uiButSearch *)but; + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + but_search->item_context_menu_fn = context_menu_fn; +} + +/** + * \param search_sep_string: when not NULL, this string is used as a separator, + * showing the icon and highlighted text after the last instance of this string. + */ +void UI_but_func_search_set_sep_string(uiBut *but, const char *search_sep_string) +{ + uiButSearch *but_search = (uiButSearch *)but; + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + but_search->item_sep_string = search_sep_string; +} + +void UI_but_func_search_set_tooltip(uiBut *but, uiButSearchTooltipFn tooltip_fn) +{ + uiButSearch *but_search = (uiButSearch *)but; + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + but_search->item_tooltip_fn = tooltip_fn; +} + +void UI_but_func_search_set_results_are_suggestions(uiBut *but, const bool value) +{ + uiButSearch *but_search = (uiButSearch *)but; + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + but_search->results_are_suggestions = value; +} + +/* Callbacks for operator search button. */ +static void operator_enum_search_update_fn(const struct bContext *C, + void *but, + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + wmOperatorType *ot = ((uiBut *)but)->optype; + PropertyRNA *prop = ot->prop; + + if (prop == NULL) { + printf("%s: %s has no enum property set\n", __func__, ot->idname); + } + else if (RNA_property_type(prop) != PROP_ENUM) { + printf("%s: %s \"%s\" is not an enum property\n", + __func__, + ot->idname, + RNA_property_identifier(prop)); + } + else { + PointerRNA *ptr = UI_but_operator_ptr_get((uiBut *)but); /* Will create it if needed! */ + + bool do_free; + const EnumPropertyItem *all_items; + RNA_property_enum_items_gettexted((bContext *)C, ptr, prop, &all_items, NULL, &do_free); + + StringSearch *search = BLI_string_search_new(); + for (const EnumPropertyItem *item = all_items; item->identifier; item++) { + BLI_string_search_add(search, item->name, (void *)item); + } + + const EnumPropertyItem **filtered_items; + const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items); + + for (int i = 0; i < filtered_amount; i++) { + const EnumPropertyItem *item = filtered_items[i]; + /* note: need to give the index rather than the + * identifier because the enum can be freed */ + if (!UI_search_item_add( + items, item->name, POINTER_FROM_INT(item->value), item->icon, 0, 0)) { + break; + } + } + + MEM_freeN((void *)filtered_items); + BLI_string_search_free(search); + + if (do_free) { + MEM_freeN((void *)all_items); + } + } +} + +static void operator_enum_search_exec_fn(struct bContext *UNUSED(C), void *but, void *arg2) +{ + wmOperatorType *ot = ((uiBut *)but)->optype; + PointerRNA *opptr = UI_but_operator_ptr_get((uiBut *)but); /* Will create it if needed! */ + + if (ot) { + if (ot->prop) { + RNA_property_enum_set(opptr, ot->prop, POINTER_AS_INT(arg2)); + /* We do not call op from here, will be called by button code. + * ui_apply_but_funcs_after() (in interface_handlers.c) + * called this func before checking operators, + * because one of its parameters is the button itself! */ + } + else { + printf("%s: op->prop for '%s' is NULL\n", __func__, ot->idname); + } + } +} + +/** + * Same parameters as for uiDefSearchBut, with additional operator type and properties, + * used by callback to call again the right op with the right options (properties values). + */ +uiBut *uiDefSearchButO_ptr(uiBlock *block, + wmOperatorType *ot, + IDProperty *properties, + void *arg, + int retval, + int icon, + int maxlen, + int x, + int y, + short width, + short height, + float a1, + float a2, + const char *tip) +{ + uiBut *but = uiDefSearchBut(block, arg, retval, icon, maxlen, x, y, width, height, a1, a2, tip); + UI_but_func_search_set(but, + ui_searchbox_create_generic, + operator_enum_search_update_fn, + but, + false, + NULL, + operator_enum_search_exec_fn, + NULL); + + but->optype = ot; + but->opcontext = WM_OP_EXEC_DEFAULT; + + if (properties) { + PointerRNA *ptr = UI_but_operator_ptr_get(but); + /* Copy idproperties. */ + ptr->data = IDP_CopyProperty(properties); + } + + return but; +} + +void UI_but_node_link_set(uiBut *but, bNodeSocket *socket, const float draw_color[4]) +{ + but->flag |= UI_BUT_NODE_LINK; + but->custom_data = socket; + rgba_float_to_uchar(but->col, draw_color); +} + +void UI_but_number_step_size_set(uiBut *but, float step_size) +{ + uiButNumber *but_number = (uiButNumber *)but; + BLI_assert(but->type == UI_BTYPE_NUM); + + but_number->step_size = step_size; + BLI_assert(step_size > 0); +} + +void UI_but_number_precision_set(uiBut *but, float precision) +{ + uiButNumber *but_number = (uiButNumber *)but; + BLI_assert(but->type == UI_BTYPE_NUM); + + but_number->precision = precision; + /* -1 is a valid value, UI code figures out an appropriate precision then. */ + BLI_assert(precision > -2); +} + +/** + * push a new event onto event queue to activate the given button + * (usually a text-field) upon entering a popup + */ +void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but) +{ + wmEvent event; + wm_event_init_from_window(win, &event); + + event.type = EVT_BUT_OPEN; + event.val = KM_PRESS; + event.is_repeat = false; + event.customdata = but; + event.customdatafree = false; + + wm_event_add(win, &event); +} + +void UI_but_func_hold_set(uiBut *but, uiButHandleHoldFunc func, void *argN) +{ + but->hold_func = func; + but->hold_argN = argN; +} + +void UI_but_string_info_get(bContext *C, uiBut *but, ...) +{ + va_list args; + uiStringInfo *si; + + const EnumPropertyItem *items = NULL, *item = NULL; + int totitems; + bool free_items = false; + + va_start(args, but); + while ((si = (uiStringInfo *)va_arg(args, void *))) { + uiStringInfoType type = si->type; + char *tmp = NULL; + + if (type == BUT_GET_LABEL) { + if (but->str && but->str[0]) { + const char *str_sep; + size_t str_len; + + if ((but->flag & UI_BUT_HAS_SEP_CHAR) && (str_sep = strrchr(but->str, UI_SEP_CHAR))) { + str_len = (str_sep - but->str); + } + else { + str_len = strlen(but->str); + } + + tmp = BLI_strdupn(but->str, str_len); + } + else { + type = BUT_GET_RNA_LABEL; /* Fail-safe solution... */ + } + } + else if (type == BUT_GET_TIP) { + if (but->tip_func) { + tmp = but->tip_func(C, but->tip_argN, but->tip); + } + else if (but->tip && but->tip[0]) { + tmp = BLI_strdup(but->tip); + } + else { + type = BUT_GET_RNA_TIP; /* Fail-safe solution... */ + } + } + + if (type == BUT_GET_RNAPROP_IDENTIFIER) { + if (but->rnaprop) { + tmp = BLI_strdup(RNA_property_identifier(but->rnaprop)); + } + } + else if (type == BUT_GET_RNASTRUCT_IDENTIFIER) { + if (but->rnaprop && but->rnapoin.data) { + tmp = BLI_strdup(RNA_struct_identifier(but->rnapoin.type)); + } + else if (but->optype) { + tmp = BLI_strdup(but->optype->idname); + } + else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) { + MenuType *mt = UI_but_menutype_get(but); + if (mt) { + tmp = BLI_strdup(mt->idname); + } + } + else if (but->type == UI_BTYPE_POPOVER) { + PanelType *pt = UI_but_paneltype_get(but); + if (pt) { + tmp = BLI_strdup(pt->idname); + } + } + } + else if (ELEM(type, BUT_GET_RNA_LABEL, BUT_GET_RNA_TIP)) { + if (but->rnaprop) { + if (type == BUT_GET_RNA_LABEL) { + tmp = BLI_strdup(RNA_property_ui_name(but->rnaprop)); + } + else { + const char *t = RNA_property_ui_description(but->rnaprop); + if (t && t[0]) { + tmp = BLI_strdup(t); + } + } + } + else if (but->optype) { + if (type == BUT_GET_RNA_LABEL) { + tmp = BLI_strdup(WM_operatortype_name(but->optype, but->opptr)); + } + else { + tmp = WM_operatortype_description(C, but->optype, but->opptr); + } + } + else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER)) { + { + MenuType *mt = UI_but_menutype_get(but); + if (mt) { + if (type == BUT_GET_RNA_LABEL) { + tmp = BLI_strdup(mt->label); + } + else { + /* Not all menus are from Python. */ + if (mt->rna_ext.srna) { + const char *t = RNA_struct_ui_description(mt->rna_ext.srna); + if (t && t[0]) { + tmp = BLI_strdup(t); + } + } + } + } + } + + if (tmp == NULL) { + wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, NULL); + if (ot) { + if (type == BUT_GET_RNA_LABEL) { + tmp = BLI_strdup(WM_operatortype_name(ot, NULL)); + } + else { + tmp = WM_operatortype_description(C, ot, NULL); + } + } + } + + if (tmp == NULL) { + PanelType *pt = UI_but_paneltype_get(but); + if (pt) { + if (type == BUT_GET_RNA_LABEL) { + tmp = BLI_strdup(pt->label); + } + else { + /* Not all panels are from Python. */ + if (pt->rna_ext.srna) { + /* Panels don't yet have descriptions, this may be added. */ + } + } + } + } + } + } + else if (type == BUT_GET_RNA_LABEL_CONTEXT) { + const char *_tmp = BLT_I18NCONTEXT_DEFAULT; + if (but->rnaprop) { + _tmp = RNA_property_translation_context(but->rnaprop); + } + else if (but->optype) { + _tmp = RNA_struct_translation_context(but->optype->srna); + } + else if (ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_PULLDOWN)) { + MenuType *mt = UI_but_menutype_get(but); + if (mt) { + _tmp = RNA_struct_translation_context(mt->rna_ext.srna); + } + } + if (BLT_is_default_context(_tmp)) { + _tmp = BLT_I18NCONTEXT_DEFAULT_BPYRNA; + } + tmp = BLI_strdup(_tmp); + } + else if (ELEM(type, BUT_GET_RNAENUM_IDENTIFIER, BUT_GET_RNAENUM_LABEL, BUT_GET_RNAENUM_TIP)) { + PointerRNA *ptr = NULL; + PropertyRNA *prop = NULL; + int value = 0; + + /* get the enum property... */ + if (but->rnaprop && RNA_property_type(but->rnaprop) == PROP_ENUM) { + /* enum property */ + ptr = &but->rnapoin; + prop = but->rnaprop; + value = (ELEM(but->type, UI_BTYPE_ROW, UI_BTYPE_TAB)) ? (int)but->hardmax : + (int)ui_but_value_get(but); + } + else if (but->optype) { + PointerRNA *opptr = UI_but_operator_ptr_get(but); + wmOperatorType *ot = but->optype; + + /* so the context is passed to itemf functions */ + WM_operator_properties_sanitize(opptr, false); + + /* if the default property of the operator is enum and it is set, + * fetch the tooltip of the selected value so that "Snap" and "Mirror" + * operator menus in the Anim Editors will show tooltips for the different + * operations instead of the meaningless generic operator tooltip + */ + if (ot->prop && RNA_property_type(ot->prop) == PROP_ENUM) { + if (RNA_struct_contains_property(opptr, ot->prop)) { + ptr = opptr; + prop = ot->prop; + value = RNA_property_enum_get(opptr, ot->prop); + } + } + } + + /* get strings from matching enum item */ + if (ptr && prop) { + if (!item) { + int i; + + RNA_property_enum_items_gettexted(C, ptr, prop, &items, &totitems, &free_items); + for (i = 0, item = items; i < totitems; i++, item++) { + if (item->identifier[0] && item->value == value) { + break; + } + } + } + if (item && item->identifier) { + if (type == BUT_GET_RNAENUM_IDENTIFIER) { + tmp = BLI_strdup(item->identifier); + } + else if (type == BUT_GET_RNAENUM_LABEL) { + tmp = BLI_strdup(item->name); + } + else if (item->description && item->description[0]) { + tmp = BLI_strdup(item->description); + } + } + } + } + else if (type == BUT_GET_OP_KEYMAP) { + if (!ui_block_is_menu(but->block)) { + char buf[128]; + if (ui_but_event_operator_string(C, but, buf, sizeof(buf))) { + tmp = BLI_strdup(buf); + } + } + } + else if (type == BUT_GET_PROP_KEYMAP) { + /* for properties that are bound to one of the context cycle, etc. keys... */ + char buf[128]; + if (ui_but_event_property_operator_string(C, but, buf, sizeof(buf))) { + tmp = BLI_strdup(buf); + } + } + + si->strinfo = tmp; + } + va_end(args); + + if (free_items && items) { + MEM_freeN((void *)items); + } +} + +/* Program Init/Exit */ + +void UI_init(void) +{ + ui_resources_init(); +} + +/* after reading userdef file */ +void UI_init_userdef(void) +{ + /* Initialize UI variables from values set in the preferences. */ + uiStyleInit(); +} + +void UI_reinit_font(void) +{ + uiStyleInit(); +} + +void UI_exit(void) +{ + ui_resources_free(); + ui_but_clipboard_free(); +} + +void UI_interface_tag_script_reload(void) +{ + ui_interface_tag_script_reload_queries(); +} diff --git a/source/blender/editors/interface/interface_button_group.c b/source/blender/editors/interface/interface_button_group.c deleted file mode 100644 index 4e7da4ada33..00000000000 --- a/source/blender/editors/interface/interface_button_group.c +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - */ - -#include "BLI_listbase.h" - -#include "MEM_guardedalloc.h" - -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Button Groups - * \{ */ - -/** - * Every function that adds a set of buttons must create another group, - * then #ui_def_but adds buttons to the current group (the last). - */ -void ui_block_new_button_group(uiBlock *block, uiButtonGroupFlag flag) -{ - /* Don't create a new group if there is a "lock" on new groups. */ - if (!BLI_listbase_is_empty(&block->button_groups)) { - uiButtonGroup *last_button_group = block->button_groups.last; - if (last_button_group->flag & UI_BUTTON_GROUP_LOCK) { - return; - } - } - - uiButtonGroup *new_group = MEM_mallocN(sizeof(uiButtonGroup), __func__); - BLI_listbase_clear(&new_group->buttons); - new_group->flag = flag; - BLI_addtail(&block->button_groups, new_group); -} - -void ui_button_group_add_but(uiBlock *block, uiBut *but) -{ - if (BLI_listbase_is_empty(&block->button_groups)) { - ui_block_new_button_group(block, 0); - } - - uiButtonGroup *current_button_group = block->button_groups.last; - - /* We can't use the button directly because adding it to - * this list would mess with its prev and next pointers. */ - LinkData *button_link = BLI_genericNodeN(but); - BLI_addtail(¤t_button_group->buttons, button_link); -} - -static void button_group_free(uiButtonGroup *button_group) -{ - BLI_freelistN(&button_group->buttons); - MEM_freeN(button_group); -} - -void ui_block_free_button_groups(uiBlock *block) -{ - LISTBASE_FOREACH_MUTABLE (uiButtonGroup *, button_group, &block->button_groups) { - button_group_free(button_group); - } -} - -void ui_button_group_replace_but_ptr(uiBlock *block, const void *old_but_ptr, uiBut *new_but) -{ - LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { - LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { - if (link->data == old_but_ptr) { - link->data = new_but; - return; - } - } - } -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_button_group.cc b/source/blender/editors/interface/interface_button_group.cc new file mode 100644 index 00000000000..e84ea59c786 --- /dev/null +++ b/source/blender/editors/interface/interface_button_group.cc @@ -0,0 +1,90 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include "BLI_listbase.h" + +#include "MEM_guardedalloc.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Button Groups + * \{ */ + +/** + * Every function that adds a set of buttons must create another group, + * then #ui_def_but adds buttons to the current group (the last). + */ +uiButtonGroup *ui_block_new_button_group(uiBlock *block) +{ + /* Don't create a new group if there is a "lock" on new groups. */ + if (!BLI_listbase_is_empty(&block->button_groups)) { + uiButtonGroup *last_button_group = (uiButtonGroup *)block->button_groups.last; + if (last_button_group->flag & UI_BUTTON_GROUP_LOCK) { + return last_button_group; + } + } + + uiButtonGroup *new_group = (uiButtonGroup *)MEM_mallocN(sizeof(uiButtonGroup), __func__); + BLI_listbase_clear(&new_group->buttons); + BLI_addtail(&block->button_groups, new_group); + return new_group; +} + +void ui_button_group_add_but(uiBlock *block, uiBut *but) +{ + if (BLI_listbase_is_empty(&block->button_groups)) { + ui_block_new_button_group(block); + } + + uiButtonGroup *current_button_group = (uiButtonGroup *)block->button_groups.last; + + /* We can't use the button directly because adding it to + * this list would mess with its prev and next pointers. */ + LinkData *button_link = BLI_genericNodeN(but); + BLI_addtail(¤t_button_group->buttons, button_link); +} + +static void button_group_free(uiButtonGroup *button_group) +{ + BLI_freelistN(&button_group->buttons); + MEM_freeN(button_group); +} + +void ui_block_free_button_groups(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (uiButtonGroup *, button_group, &block->button_groups) { + button_group_free(button_group); + } +} + +void ui_button_group_replace_but_ptr(uiBlock *block, const void *old_but_ptr, uiBut *new_but) +{ + LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + if (link->data == old_but_ptr) { + link->data = new_but; + return; + } + } + } +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c deleted file mode 100644 index b142e383df0..00000000000 --- a/source/blender/editors/interface/interface_context_menu.c +++ /dev/null @@ -1,1289 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - * - * Generic context popup menus. - */ - -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_scene_types.h" -#include "DNA_screen_types.h" - -#include "BLI_path_util.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "BKE_addon.h" -#include "BKE_context.h" -#include "BKE_idprop.h" -#include "BKE_screen.h" - -#include "ED_asset.h" -#include "ED_keyframing.h" -#include "ED_screen.h" - -#include "UI_interface.h" - -#include "interface_intern.h" - -#include "RNA_access.h" - -#ifdef WITH_PYTHON -# include "BPY_extern.h" -# include "BPY_extern_run.h" -#endif - -#include "WM_api.h" -#include "WM_types.h" - -/* This hack is needed because we don't have a good way to - * re-reference keymap items once added: T42944 */ -#define USE_KEYMAP_ADD_HACK - -/* -------------------------------------------------------------------- */ -/** \name Button Context Menu - * \{ */ - -static IDProperty *shortcut_property_from_rna(bContext *C, uiBut *but) -{ - /* Compute data path from context to property. */ - - /* If this returns null, we won't be able to bind shortcuts to these RNA properties. - * Support can be added at #wm_context_member_from_ptr. */ - const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); - if (member_id == NULL) { - return NULL; - } - - const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); - const char *member_id_data_path = member_id; - - if (data_path) { - member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); - MEM_freeN((void *)data_path); - } - - const char *prop_id = RNA_property_identifier(but->rnaprop); - const char *final_data_path = BLI_sprintfN("%s.%s", member_id_data_path, prop_id); - - if (member_id != member_id_data_path) { - MEM_freeN((void *)member_id_data_path); - } - - /* Create ID property of data path, to pass to the operator. */ - const IDPropertyTemplate val = {0}; - IDProperty *prop = IDP_New(IDP_GROUP, &val, __func__); - IDP_AddToGroup(prop, IDP_NewString(final_data_path, "data_path", strlen(final_data_path) + 1)); - - MEM_freeN((void *)final_data_path); - - return prop; -} - -static const char *shortcut_get_operator_property(bContext *C, uiBut *but, IDProperty **r_prop) -{ - if (but->optype) { - /* Operator */ - *r_prop = (but->opptr && but->opptr->data) ? IDP_CopyProperty(but->opptr->data) : NULL; - return but->optype->idname; - } - - if (but->rnaprop) { - const PropertyType rnaprop_type = RNA_property_type(but->rnaprop); - - if (rnaprop_type == PROP_BOOLEAN) { - /* Boolean */ - *r_prop = shortcut_property_from_rna(C, but); - if (*r_prop == NULL) { - return NULL; - } - return "WM_OT_context_toggle"; - } - if (rnaprop_type == PROP_ENUM) { - /* Enum */ - *r_prop = shortcut_property_from_rna(C, but); - if (*r_prop == NULL) { - return NULL; - } - return "WM_OT_context_menu_enum"; - } - } - - *r_prop = NULL; - return NULL; -} - -static void shortcut_free_operator_property(IDProperty *prop) -{ - if (prop) { - IDP_FreeProperty(prop); - } -} - -static void but_shortcut_name_func(bContext *C, void *arg1, int UNUSED(event)) -{ - uiBut *but = (uiBut *)arg1; - char shortcut_str[128]; - - IDProperty *prop; - const char *idname = shortcut_get_operator_property(C, but, &prop); - if (idname == NULL) { - return; - } - - /* complex code to change name of button */ - if (WM_key_event_operator_string( - C, idname, but->opcontext, prop, true, shortcut_str, sizeof(shortcut_str))) { - ui_but_add_shortcut(but, shortcut_str, true); - } - else { - /* simply strip the shortcut */ - ui_but_add_shortcut(but, NULL, true); - } - - shortcut_free_operator_property(prop); -} - -static uiBlock *menu_change_shortcut(bContext *C, ARegion *region, void *arg) -{ - wmWindowManager *wm = CTX_wm_manager(C); - uiBut *but = (uiBut *)arg; - PointerRNA ptr; - const uiStyle *style = UI_style_get_dpi(); - IDProperty *prop; - const char *idname = shortcut_get_operator_property(C, but, &prop); - - wmKeyMap *km; - wmKeyMapItem *kmi = WM_key_event_operator(C, - idname, - but->opcontext, - prop, - EVT_TYPE_MASK_HOTKEY_INCLUDE, - EVT_TYPE_MASK_HOTKEY_EXCLUDE, - &km); - U.runtime.is_dirty = true; - - BLI_assert(kmi != NULL); - - RNA_pointer_create(&wm->id, &RNA_KeyMapItem, kmi, &ptr); - - uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); - UI_block_func_handle_set(block, but_shortcut_name_func, but); - UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); - UI_block_direction_set(block, UI_DIR_CENTER_Y); - - uiLayout *layout = UI_block_layout(block, - UI_LAYOUT_VERTICAL, - UI_LAYOUT_PANEL, - 0, - 0, - U.widget_unit * 10, - U.widget_unit * 2, - 0, - style); - - uiItemL(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"), ICON_HAND); - uiItemR(layout, &ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE); - - UI_block_bounds_set_popup( - block, 6 * U.dpi_fac, (const int[2]){-100 * U.dpi_fac, 36 * U.dpi_fac}); - - shortcut_free_operator_property(prop); - - return block; -} - -#ifdef USE_KEYMAP_ADD_HACK -static int g_kmi_id_hack; -#endif - -static uiBlock *menu_add_shortcut(bContext *C, ARegion *region, void *arg) -{ - wmWindowManager *wm = CTX_wm_manager(C); - uiBut *but = (uiBut *)arg; - PointerRNA ptr; - const uiStyle *style = UI_style_get_dpi(); - IDProperty *prop; - const char *idname = shortcut_get_operator_property(C, but, &prop); - - /* XXX this guess_opname can potentially return a different keymap - * than being found on adding later... */ - wmKeyMap *km = WM_keymap_guess_opname(C, idname); - wmKeyMapItem *kmi = WM_keymap_add_item(km, idname, EVT_AKEY, KM_PRESS, 0, 0); - const int kmi_id = kmi->id; - - /* This takes ownership of prop, or prop can be NULL for reset. */ - WM_keymap_item_properties_reset(kmi, prop); - - /* update and get pointers again */ - WM_keyconfig_update(wm); - U.runtime.is_dirty = true; - - km = WM_keymap_guess_opname(C, idname); - kmi = WM_keymap_item_find_id(km, kmi_id); - - RNA_pointer_create(&wm->id, &RNA_KeyMapItem, kmi, &ptr); - - uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); - UI_block_func_handle_set(block, but_shortcut_name_func, but); - UI_block_direction_set(block, UI_DIR_CENTER_Y); - - uiLayout *layout = UI_block_layout(block, - UI_LAYOUT_VERTICAL, - UI_LAYOUT_PANEL, - 0, - 0, - U.widget_unit * 10, - U.widget_unit * 2, - 0, - style); - - uiItemL(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"), ICON_HAND); - uiItemR(layout, &ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE); - - UI_block_bounds_set_popup( - block, 6 * U.dpi_fac, (const int[2]){-100 * U.dpi_fac, 36 * U.dpi_fac}); - -#ifdef USE_KEYMAP_ADD_HACK - g_kmi_id_hack = kmi_id; -#endif - - return block; -} - -static void menu_add_shortcut_cancel(struct bContext *C, void *arg1) -{ - uiBut *but = (uiBut *)arg1; - - IDProperty *prop; - const char *idname = shortcut_get_operator_property(C, but, &prop); - -#ifdef USE_KEYMAP_ADD_HACK - wmKeyMap *km = WM_keymap_guess_opname(C, idname); - const int kmi_id = g_kmi_id_hack; - UNUSED_VARS(but); -#else - int kmi_id = WM_key_event_operator_id(C, idname, but->opcontext, prop, true, &km); -#endif - - shortcut_free_operator_property(prop); - - wmKeyMapItem *kmi = WM_keymap_item_find_id(km, kmi_id); - WM_keymap_remove_item(km, kmi); -} - -static void popup_change_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) -{ - uiBut *but = (uiBut *)arg1; - UI_popup_block_invoke(C, menu_change_shortcut, but, NULL); -} - -static void remove_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) -{ - uiBut *but = (uiBut *)arg1; - IDProperty *prop; - const char *idname = shortcut_get_operator_property(C, but, &prop); - - wmKeyMap *km; - wmKeyMapItem *kmi = WM_key_event_operator(C, - idname, - but->opcontext, - prop, - EVT_TYPE_MASK_HOTKEY_INCLUDE, - EVT_TYPE_MASK_HOTKEY_EXCLUDE, - &km); - BLI_assert(kmi != NULL); - - WM_keymap_remove_item(km, kmi); - U.runtime.is_dirty = true; - - shortcut_free_operator_property(prop); - but_shortcut_name_func(C, but, 0); -} - -static void popup_add_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) -{ - uiBut *but = (uiBut *)arg1; - UI_popup_block_ex(C, menu_add_shortcut, NULL, menu_add_shortcut_cancel, but, NULL); -} - -static bool ui_but_is_user_menu_compatible(bContext *C, uiBut *but) -{ - return (but->optype || - (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) && - (WM_context_member_from_ptr(C, &but->rnapoin) != NULL)) || - UI_but_menutype_get(but)); -} - -static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *um) -{ - if (but->optype) { - IDProperty *prop = (but->opptr) ? but->opptr->data : NULL; - return (bUserMenuItem *)ED_screen_user_menu_item_find_operator( - &um->items, but->optype, prop, but->opcontext); - } - if (but->rnaprop) { - const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); - const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); - const char *member_id_data_path = member_id; - if (data_path) { - member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); - } - const char *prop_id = RNA_property_identifier(but->rnaprop); - bUserMenuItem *umi = (bUserMenuItem *)ED_screen_user_menu_item_find_prop( - &um->items, member_id_data_path, prop_id, but->rnaindex); - if (data_path) { - MEM_freeN((void *)data_path); - } - if (member_id != member_id_data_path) { - MEM_freeN((void *)member_id_data_path); - } - return umi; - } - - MenuType *mt = UI_but_menutype_get(but); - if (mt != NULL) { - return (bUserMenuItem *)ED_screen_user_menu_item_find_menu(&um->items, mt); - } - return NULL; -} - -static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) -{ - BLI_assert(ui_but_is_user_menu_compatible(C, but)); - - char drawstr[sizeof(but->drawstr)]; - STRNCPY(drawstr, but->drawstr); - if (but->flag & UI_BUT_HAS_SEP_CHAR) { - char *sep = strrchr(drawstr, UI_SEP_CHAR); - if (sep) { - *sep = '\0'; - } - } - - MenuType *mt = NULL; - if (but->optype) { - if (drawstr[0] == '\0') { - /* Hard code overrides for generic operators. */ - if (UI_but_is_tool(but)) { - char idname[64]; - RNA_string_get(but->opptr, "name", idname); -#ifdef WITH_PYTHON - { - const char *expr_imports[] = {"bpy", "bl_ui", NULL}; - char expr[256]; - SNPRINTF(expr, - "bl_ui.space_toolsystem_common.item_from_id(" - "bpy.context, " - "bpy.context.space_data.type, " - "'%s').label", - idname); - char *expr_result = NULL; - if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) { - STRNCPY(drawstr, expr_result); - MEM_freeN(expr_result); - } - else { - BLI_assert(0); - STRNCPY(drawstr, idname); - } - } -#else - STRNCPY(drawstr, idname); -#endif - } - } - ED_screen_user_menu_item_add_operator( - &um->items, drawstr, but->optype, but->opptr ? but->opptr->data : NULL, but->opcontext); - } - else if (but->rnaprop) { - /* Note: 'member_id' may be a path. */ - const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); - const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); - const char *member_id_data_path = member_id; - if (data_path) { - member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); - } - const char *prop_id = RNA_property_identifier(but->rnaprop); - /* Note, ignore 'drawstr', use property idname always. */ - ED_screen_user_menu_item_add_prop(&um->items, "", member_id_data_path, prop_id, but->rnaindex); - if (data_path) { - MEM_freeN((void *)data_path); - } - if (member_id != member_id_data_path) { - MEM_freeN((void *)member_id_data_path); - } - } - else if ((mt = UI_but_menutype_get(but))) { - ED_screen_user_menu_item_add_menu(&um->items, drawstr, mt); - } -} - -static void popup_user_menu_add_or_replace_func(bContext *C, void *arg1, void *UNUSED(arg2)) -{ - uiBut *but = arg1; - bUserMenu *um = ED_screen_user_menu_ensure(C); - U.runtime.is_dirty = true; - ui_but_user_menu_add(C, but, um); -} - -static void popup_user_menu_remove_func(bContext *UNUSED(C), void *arg1, void *arg2) -{ - bUserMenu *um = arg1; - bUserMenuItem *umi = arg2; - U.runtime.is_dirty = true; - ED_screen_user_menu_item_remove(&um->items, umi); -} - -static void ui_but_menu_add_path_operators(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop) -{ - const PropertySubType subtype = RNA_property_subtype(prop); - wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true); - char filepath[FILE_MAX]; - char dir[FILE_MAXDIR]; - char file[FILE_MAXFILE]; - PointerRNA props_ptr; - - BLI_assert(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)); - UNUSED_VARS_NDEBUG(subtype); - - RNA_property_string_get(ptr, prop, filepath); - BLI_split_dirfile(filepath, dir, file, sizeof(dir), sizeof(file)); - - if (file[0]) { - BLI_assert(subtype == PROP_FILEPATH); - uiItemFullO_ptr(layout, - ot, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open File Externally"), - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &props_ptr); - RNA_string_set(&props_ptr, "filepath", filepath); - } - - uiItemFullO_ptr(layout, - ot, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Location Externally"), - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &props_ptr); - RNA_string_set(&props_ptr, "filepath", dir); -} - -bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) -{ - /* ui_but_is_interactive() may let some buttons through that should not get a context menu - it - * doesn't make sense for them. */ - if (ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_IMAGE)) { - return false; - } - - uiPopupMenu *pup; - uiLayout *layout; - bContextStore *previous_ctx = CTX_store_get(C); - { - uiStringInfo label = {BUT_GET_LABEL, NULL}; - - /* highly unlikely getting the label ever fails */ - UI_but_string_info_get(C, but, &label, NULL); - - pup = UI_popup_menu_begin(C, label.strinfo ? label.strinfo : "", ICON_NONE); - layout = UI_popup_menu_layout(pup); - if (label.strinfo) { - MEM_freeN(label.strinfo); - } - - if (but->context) { - uiLayoutContextCopy(layout, but->context); - CTX_store_set(C, uiLayoutGetContextStore(layout)); - } - uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); - } - - const bool is_disabled = but->flag & UI_BUT_DISABLED; - - if (is_disabled) { - /* Suppress editing commands. */ - } - else if (but->type == UI_BTYPE_TAB) { - uiButTab *tab = (uiButTab *)but; - if (tab->menu) { - UI_menutype_draw(C, tab->menu, layout); - uiItemS(layout); - } - } - else if (but->rnapoin.data && but->rnaprop) { - PointerRNA *ptr = &but->rnapoin; - PropertyRNA *prop = but->rnaprop; - const PropertyType type = RNA_property_type(prop); - const PropertySubType subtype = RNA_property_subtype(prop); - bool is_anim = RNA_property_animateable(ptr, prop); - const bool is_idprop = RNA_property_is_idprop(prop); - - /* second slower test, - * saved people finding keyframe items in menus when its not possible */ - if (is_anim) { - is_anim = RNA_property_path_from_ID_check(&but->rnapoin, but->rnaprop); - } - - /* determine if we can key a single component of an array */ - const bool is_array = RNA_property_array_length(&but->rnapoin, but->rnaprop) != 0; - const bool is_array_component = (is_array && but->rnaindex != -1); - const bool is_whole_array = (is_array && but->rnaindex == -1); - - const uint override_status = RNA_property_override_library_status( - CTX_data_main(C), ptr, prop, -1); - const bool is_overridable = (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE) != 0; - - /* Set the (button_pointer, button_prop) - * and pointer data for Python access to the hovered ui element. */ - uiLayoutSetContextFromBut(layout, but); - - /* Keyframes */ - if (but->flag & UI_BUT_ANIMATED_KEY) { - /* Replace/delete keyframes. */ - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframes"), - ICON_KEY_HLT, - "ANIM_OT_keyframe_insert_button", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Single Keyframe"), - ICON_NONE, - "ANIM_OT_keyframe_insert_button", - "all", - 0); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframes"), - ICON_NONE, - "ANIM_OT_keyframe_delete_button", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Keyframe"), - ICON_NONE, - "ANIM_OT_keyframe_delete_button", - "all", - 0); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframe"), - ICON_KEY_HLT, - "ANIM_OT_keyframe_insert_button", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframe"), - ICON_NONE, - "ANIM_OT_keyframe_delete_button", - "all", - 1); - } - - /* keyframe settings */ - uiItemS(layout); - } - else if (but->flag & UI_BUT_DRIVEN) { - /* pass */ - } - else if (is_anim) { - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframes"), - ICON_KEY_HLT, - "ANIM_OT_keyframe_insert_button", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Single Keyframe"), - ICON_NONE, - "ANIM_OT_keyframe_insert_button", - "all", - 0); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframe"), - ICON_KEY_HLT, - "ANIM_OT_keyframe_insert_button", - "all", - 1); - } - } - - if ((but->flag & UI_BUT_ANIMATED) && (but->rnapoin.type != &RNA_NlaStrip)) { - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"), - ICON_KEY_DEHLT, - "ANIM_OT_keyframe_clear_button", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Single Keyframes"), - ICON_NONE, - "ANIM_OT_keyframe_clear_button", - "all", - 0); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"), - ICON_KEY_DEHLT, - "ANIM_OT_keyframe_clear_button", - "all", - 1); - } - } - - /* Drivers */ - if (but->flag & UI_BUT_DRIVEN) { - uiItemS(layout); - - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Drivers"), - ICON_X, - "ANIM_OT_driver_button_remove", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Driver"), - ICON_NONE, - "ANIM_OT_driver_button_remove", - "all", - 0); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Driver"), - ICON_X, - "ANIM_OT_driver_button_remove", - "all", - 1); - } - - if (!is_whole_array) { - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Driver"), - ICON_NONE, - "ANIM_OT_copy_driver_button"); - if (ANIM_driver_can_paste()) { - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"), - ICON_NONE, - "ANIM_OT_paste_driver_button"); - } - - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Edit Driver"), - ICON_DRIVER, - "ANIM_OT_driver_button_edit"); - } - - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"), - ICON_NONE, - "SCREEN_OT_drivers_editor_show"); - } - else if (but->flag & (UI_BUT_ANIMATED_KEY | UI_BUT_ANIMATED)) { - /* pass */ - } - else if (is_anim) { - uiItemS(layout); - - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Driver"), - ICON_DRIVER, - "ANIM_OT_driver_button_add"); - - if (!is_whole_array) { - if (ANIM_driver_can_paste()) { - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"), - ICON_NONE, - "ANIM_OT_paste_driver_button"); - } - } - - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"), - ICON_NONE, - "SCREEN_OT_drivers_editor_show"); - } - - /* Keying Sets */ - /* TODO: check on modifyability of Keying Set when doing this */ - if (is_anim) { - uiItemS(layout); - - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add All to Keying Set"), - ICON_KEYINGSET, - "ANIM_OT_keyingset_button_add", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Single to Keying Set"), - ICON_NONE, - "ANIM_OT_keyingset_button_add", - "all", - 0); - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"), - ICON_NONE, - "ANIM_OT_keyingset_button_remove"); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Keying Set"), - ICON_KEYINGSET, - "ANIM_OT_keyingset_button_add", - "all", - 1); - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"), - ICON_NONE, - "ANIM_OT_keyingset_button_remove"); - } - } - - if (is_overridable) { - wmOperatorType *ot; - PointerRNA op_ptr; - /* Override Operators */ - uiItemS(layout); - - if (but->flag & UI_BUT_OVERRIDDEN) { - if (is_array_component) { -#if 0 /* Disabled for now. */ - ot = WM_operatortype_find("UI_OT_override_type_set_button", false); - uiItemFullO_ptr( - layout, ot, "Overrides Type", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); - RNA_boolean_set(&op_ptr, "all", true); - uiItemFullO_ptr(layout, - ot, - "Single Override Type", - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &op_ptr); - RNA_boolean_set(&op_ptr, "all", false); -#endif - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Overrides"), - ICON_X, - "UI_OT_override_remove_button", - "all", - true); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Single Override"), - ICON_X, - "UI_OT_override_remove_button", - "all", - false); - } - else { -#if 0 /* Disabled for now. */ - uiItemFullO(layout, - "UI_OT_override_type_set_button", - "Override Type", - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &op_ptr); - RNA_boolean_set(&op_ptr, "all", false); -#endif - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Override"), - ICON_X, - "UI_OT_override_remove_button", - "all", - true); - } - } - else { - if (is_array_component) { - ot = WM_operatortype_find("UI_OT_override_type_set_button", false); - uiItemFullO_ptr( - layout, ot, "Define Overrides", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); - RNA_boolean_set(&op_ptr, "all", true); - uiItemFullO_ptr(layout, - ot, - "Define Single Override", - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &op_ptr); - RNA_boolean_set(&op_ptr, "all", false); - } - else { - uiItemFullO(layout, - "UI_OT_override_type_set_button", - "Define Override", - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &op_ptr); - RNA_boolean_set(&op_ptr, "all", false); - } - } - } - - uiItemS(layout); - - /* Property Operators */ - - /* Copy Property Value - * Paste Property Value */ - - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset All to Default Values"), - ICON_LOOP_BACK, - "UI_OT_reset_default_button", - "all", - 1); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset Single to Default Value"), - ICON_NONE, - "UI_OT_reset_default_button", - "all", - 0); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset to Default Value"), - ICON_LOOP_BACK, - "UI_OT_reset_default_button", - "all", - 1); - } - - if (is_idprop && !is_array && ELEM(type, PROP_INT, PROP_FLOAT)) { - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Value as Default"), - ICON_NONE, - "UI_OT_assign_default_button"); - - uiItemS(layout); - } - - if (is_array_component) { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy All to Selected"), - ICON_NONE, - "UI_OT_copy_to_selected_button", - "all", - true); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Single to Selected"), - ICON_NONE, - "UI_OT_copy_to_selected_button", - "all", - false); - } - else { - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"), - ICON_NONE, - "UI_OT_copy_to_selected_button", - "all", - true); - } - - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Data Path"), - ICON_NONE, - "UI_OT_copy_data_path_button"); - uiItemBooleanO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Full Data Path"), - ICON_NONE, - "UI_OT_copy_data_path_button", - "full_path", - true); - - if (ptr->owner_id && !is_whole_array && - ELEM(type, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) { - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy as New Driver"), - ICON_NONE, - "UI_OT_copy_as_driver_button"); - } - - uiItemS(layout); - - if (type == PROP_STRING && ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) { - ui_but_menu_add_path_operators(layout, ptr, prop); - uiItemS(layout); - } - } - - /* If the button represents an id, it can set the "id" context pointer. */ - if (U.experimental.use_asset_browser && ED_asset_can_make_single_from_context(C)) { - ID *id = CTX_data_pointer_get_type(C, "id", &RNA_ID).data; - - /* Gray out items depending on if data-block is an asset. Preferably this could be done via - * operator poll, but that doesn't work since the operator also works with "selected_ids", - * which isn't cheap to check. */ - uiLayout *sub = uiLayoutColumn(layout, true); - uiLayoutSetEnabled(sub, !id->asset_data); - uiItemO(sub, NULL, ICON_NONE, "ASSET_OT_mark"); - sub = uiLayoutColumn(layout, true); - uiLayoutSetEnabled(sub, id->asset_data); - uiItemO(sub, NULL, ICON_NONE, "ASSET_OT_clear"); - uiItemS(layout); - } - - /* Pointer properties and string properties with - * prop_search support jumping to target object/bone. */ - if (but->rnapoin.data && but->rnaprop) { - const PropertyType prop_type = RNA_property_type(but->rnaprop); - if (((prop_type == PROP_POINTER) || - (prop_type == PROP_STRING && but->type == UI_BTYPE_SEARCH_MENU && - ((uiButSearch *)but)->items_update_fn == ui_rna_collection_search_update_fn)) && - ui_jump_to_target_button_poll(C)) { - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Jump to Target"), - ICON_NONE, - "UI_OT_jump_to_target_button"); - uiItemS(layout); - } - } - - /* Favorites Menu */ - if (ui_but_is_user_menu_compatible(C, but)) { - uiBlock *block = uiLayoutGetBlock(layout); - const int w = uiLayoutGetWidth(layout); - bool item_found = false; - - uint um_array_len; - bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len); - for (int um_index = 0; um_index < um_array_len; um_index++) { - bUserMenu *um = um_array[um_index]; - if (um == NULL) { - continue; - } - bUserMenuItem *umi = ui_but_user_menu_find(C, but, um); - if (umi != NULL) { - uiBut *but2 = uiDefIconTextBut( - block, - UI_BTYPE_BUT, - 0, - ICON_MENU_PANEL, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Quick Favorites"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - ""); - UI_but_func_set(but2, popup_user_menu_remove_func, um, umi); - item_found = true; - } - } - if (um_array) { - MEM_freeN(um_array); - } - - if (!item_found) { - uiBut *but2 = uiDefIconTextBut( - block, - UI_BTYPE_BUT, - 0, - ICON_MENU_PANEL, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Quick Favorites"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - "Add to a user defined context menu (stored in the user preferences)"); - UI_but_func_set(but2, popup_user_menu_add_or_replace_func, but, NULL); - } - - uiItemS(layout); - } - - /* Shortcut menu */ - IDProperty *prop; - const char *idname = shortcut_get_operator_property(C, but, &prop); - if (idname != NULL) { - uiBlock *block = uiLayoutGetBlock(layout); - const int w = uiLayoutGetWidth(layout); - - /* We want to know if this op has a shortcut, be it hotkey or not. */ - wmKeyMap *km; - wmKeyMapItem *kmi = WM_key_event_operator( - C, idname, but->opcontext, prop, EVT_TYPE_MASK_ALL, 0, &km); - - /* We do have a shortcut, but only keyboard ones are editable that way... */ - if (kmi) { - if (ISKEYBOARD(kmi->type)) { -#if 0 /* would rather use a block but, but gets weirdly positioned... */ - uiDefBlockBut(block, - menu_change_shortcut, - but, - "Change Shortcut", - 0, - 0, - uiLayoutGetWidth(layout), - UI_UNIT_Y, - ""); -#endif - - uiBut *but2 = uiDefIconTextBut( - block, - UI_BTYPE_BUT, - 0, - ICON_HAND, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - ""); - UI_but_func_set(but2, popup_change_shortcut_func, but, NULL); - } - else { - uiBut *but2 = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_HAND, - IFACE_("Non-Keyboard Shortcut"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Only keyboard shortcuts can be edited that way, " - "please use User Preferences otherwise")); - UI_but_flag_enable(but2, UI_BUT_DISABLED); - } - - uiBut *but2 = uiDefIconTextBut( - block, - UI_BTYPE_BUT, - 0, - ICON_BLANK1, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Shortcut"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - ""); - UI_but_func_set(but2, remove_shortcut_func, but, NULL); - } - /* only show 'assign' if there's a suitable key map for it to go in */ - else if (WM_keymap_guess_opname(C, idname)) { - uiBut *but2 = uiDefIconTextBut( - block, - UI_BTYPE_BUT, - 0, - ICON_HAND, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - ""); - UI_but_func_set(but2, popup_add_shortcut_func, but, NULL); - } - - shortcut_free_operator_property(prop); - - /* Set the operator pointer for python access */ - uiLayoutSetContextFromBut(layout, but); - - uiItemS(layout); - } - - { /* Docs */ - char buf[512]; - - if (UI_but_online_manual_id(but, buf, sizeof(buf))) { - PointerRNA ptr_props; - uiItemO(layout, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Manual"), - ICON_URL, - "WM_OT_doc_view_manual_ui_context"); - - if (U.flag & USER_DEVELOPER_UI) { - uiItemFullO(layout, - "WM_OT_doc_view", - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Python Reference"), - ICON_NONE, - NULL, - WM_OP_EXEC_DEFAULT, - 0, - &ptr_props); - RNA_string_set(&ptr_props, "doc_id", buf); - } - } - } - - if (but->optype && U.flag & USER_DEVELOPER_UI) { - uiItemO(layout, NULL, ICON_NONE, "UI_OT_copy_python_command_button"); - } - - /* perhaps we should move this into (G.debug & G_DEBUG) - campbell */ - if (U.flag & USER_DEVELOPER_UI) { - if (ui_block_is_menu(but->block) == false) { - uiItemFullO( - layout, "UI_OT_editsource", NULL, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, NULL); - } - } - - if (BKE_addon_find(&U.addons, "ui_translate")) { - uiItemFullO(layout, - "UI_OT_edittranslation_init", - NULL, - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - NULL); - } - - /* Show header tools for header buttons. */ - if (ui_block_is_popup_any(but->block) == false) { - const ARegion *region = CTX_wm_region(C); - - if (!region) { - /* skip */ - } - else if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) { - uiItemMenuF(layout, IFACE_("Header"), ICON_NONE, ED_screens_header_tools_menu_create, NULL); - } - else if (region->regiontype == RGN_TYPE_NAV_BAR) { - uiItemMenuF(layout, - IFACE_("Navigation Bar"), - ICON_NONE, - ED_screens_navigation_bar_tools_menu_create, - NULL); - } - else if (region->regiontype == RGN_TYPE_FOOTER) { - uiItemMenuF(layout, IFACE_("Footer"), ICON_NONE, ED_screens_footer_tools_menu_create, NULL); - } - } - - MenuType *mt = WM_menutype_find("WM_MT_button_context", true); - if (mt) { - UI_menutype_draw(C, mt, uiLayoutColumn(layout, false)); - } - - if (but->context) { - CTX_store_set(C, previous_ctx); - } - - return UI_popup_menu_end_or_cancel(C, pup); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Context Menu - * \{ */ - -/** - * menu to show when right clicking on the panel header - */ -void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel) -{ - bScreen *screen = CTX_wm_screen(C); - const bool has_panel_category = UI_panel_category_is_visible(region); - const bool any_item_visible = has_panel_category; - - if (!any_item_visible) { - return; - } - if (panel->type->parent != NULL) { - return; - } - - PointerRNA ptr; - RNA_pointer_create(&screen->id, &RNA_Panel, panel, &ptr); - - uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Panel"), ICON_NONE); - uiLayout *layout = UI_popup_menu_layout(pup); - - if (has_panel_category) { - char tmpstr[80]; - BLI_snprintf(tmpstr, - sizeof(tmpstr), - "%s" UI_SEP_CHAR_S "%s", - IFACE_("Pin"), - IFACE_("Shift Left Mouse")); - uiItemR(layout, &ptr, "use_pin", 0, tmpstr, ICON_NONE); - - /* evil, force shortcut flag */ - { - uiBlock *block = uiLayoutGetBlock(layout); - uiBut *but = block->buttons.last; - but->flag |= UI_BUT_HAS_SEP_CHAR; - but->drawflag |= UI_BUT_HAS_SHORTCUT; - } - } - UI_popup_menu_end(C, pup); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_context_menu.cc b/source/blender/editors/interface/interface_context_menu.cc new file mode 100644 index 00000000000..ff514aa68bd --- /dev/null +++ b/source/blender/editors/interface/interface_context_menu.cc @@ -0,0 +1,1294 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * Generic context popup menus. + */ + +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BLI_path_util.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_addon.h" +#include "BKE_context.h" +#include "BKE_idprop.h" +#include "BKE_screen.h" + +#include "ED_asset.h" +#include "ED_keyframing.h" +#include "ED_screen.h" + +#include "UI_interface.h" + +#include "interface_intern.h" + +#include "RNA_access.h" + +#ifdef WITH_PYTHON +# include "BPY_extern.h" +# include "BPY_extern_run.h" +#endif + +#include "WM_api.h" +#include "WM_types.h" + +/* This hack is needed because we don't have a good way to + * re-reference keymap items once added: T42944 */ +#define USE_KEYMAP_ADD_HACK + +/* -------------------------------------------------------------------- */ +/** \name Button Context Menu + * \{ */ + +static IDProperty *shortcut_property_from_rna(bContext *C, uiBut *but) +{ + /* Compute data path from context to property. */ + + /* If this returns null, we won't be able to bind shortcuts to these RNA properties. + * Support can be added at #wm_context_member_from_ptr. */ + const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); + if (member_id == NULL) { + return NULL; + } + + const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); + const char *member_id_data_path = member_id; + + if (data_path) { + member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); + MEM_freeN((void *)data_path); + } + + const char *prop_id = RNA_property_identifier(but->rnaprop); + const char *final_data_path = BLI_sprintfN("%s.%s", member_id_data_path, prop_id); + + if (member_id != member_id_data_path) { + MEM_freeN((void *)member_id_data_path); + } + + /* Create ID property of data path, to pass to the operator. */ + const IDPropertyTemplate val = {0}; + IDProperty *prop = IDP_New(IDP_GROUP, &val, __func__); + IDP_AddToGroup(prop, IDP_NewString(final_data_path, "data_path", strlen(final_data_path) + 1)); + + MEM_freeN((void *)final_data_path); + + return prop; +} + +static const char *shortcut_get_operator_property(bContext *C, uiBut *but, IDProperty **r_prop) +{ + if (but->optype) { + /* Operator */ + *r_prop = (but->opptr && but->opptr->data) ? + IDP_CopyProperty((const IDProperty *)but->opptr->data) : + NULL; + return but->optype->idname; + } + + if (but->rnaprop) { + const PropertyType rnaprop_type = RNA_property_type(but->rnaprop); + + if (rnaprop_type == PROP_BOOLEAN) { + /* Boolean */ + *r_prop = shortcut_property_from_rna(C, but); + if (*r_prop == NULL) { + return NULL; + } + return "WM_OT_context_toggle"; + } + if (rnaprop_type == PROP_ENUM) { + /* Enum */ + *r_prop = shortcut_property_from_rna(C, but); + if (*r_prop == NULL) { + return NULL; + } + return "WM_OT_context_menu_enum"; + } + } + + *r_prop = NULL; + return NULL; +} + +static void shortcut_free_operator_property(IDProperty *prop) +{ + if (prop) { + IDP_FreeProperty(prop); + } +} + +static void but_shortcut_name_func(bContext *C, void *arg1, int UNUSED(event)) +{ + uiBut *but = (uiBut *)arg1; + char shortcut_str[128]; + + IDProperty *prop; + const char *idname = shortcut_get_operator_property(C, but, &prop); + if (idname == NULL) { + return; + } + + /* complex code to change name of button */ + if (WM_key_event_operator_string( + C, idname, but->opcontext, prop, true, shortcut_str, sizeof(shortcut_str))) { + ui_but_add_shortcut(but, shortcut_str, true); + } + else { + /* simply strip the shortcut */ + ui_but_add_shortcut(but, NULL, true); + } + + shortcut_free_operator_property(prop); +} + +static uiBlock *menu_change_shortcut(bContext *C, ARegion *region, void *arg) +{ + wmWindowManager *wm = CTX_wm_manager(C); + uiBut *but = (uiBut *)arg; + PointerRNA ptr; + const uiStyle *style = UI_style_get_dpi(); + IDProperty *prop; + const char *idname = shortcut_get_operator_property(C, but, &prop); + + wmKeyMap *km; + wmKeyMapItem *kmi = WM_key_event_operator(C, + idname, + but->opcontext, + prop, + EVT_TYPE_MASK_HOTKEY_INCLUDE, + EVT_TYPE_MASK_HOTKEY_EXCLUDE, + &km); + U.runtime.is_dirty = true; + + BLI_assert(kmi != NULL); + + RNA_pointer_create(&wm->id, &RNA_KeyMapItem, kmi, &ptr); + + uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); + UI_block_func_handle_set(block, but_shortcut_name_func, but); + UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT); + UI_block_direction_set(block, UI_DIR_CENTER_Y); + + uiLayout *layout = UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_PANEL, + 0, + 0, + U.widget_unit * 10, + U.widget_unit * 2, + 0, + style); + + uiItemL(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"), ICON_HAND); + uiItemR(layout, &ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE); + + const int bounds_offset[2] = {int(-100 * U.dpi_fac), int(36 * U.dpi_fac)}; + UI_block_bounds_set_popup(block, 6 * U.dpi_fac, bounds_offset); + + shortcut_free_operator_property(prop); + + return block; +} + +#ifdef USE_KEYMAP_ADD_HACK +static int g_kmi_id_hack; +#endif + +static uiBlock *menu_add_shortcut(bContext *C, ARegion *region, void *arg) +{ + wmWindowManager *wm = CTX_wm_manager(C); + uiBut *but = (uiBut *)arg; + PointerRNA ptr; + const uiStyle *style = UI_style_get_dpi(); + IDProperty *prop; + const char *idname = shortcut_get_operator_property(C, but, &prop); + + /* XXX this guess_opname can potentially return a different keymap + * than being found on adding later... */ + wmKeyMap *km = WM_keymap_guess_opname(C, idname); + wmKeyMapItem *kmi = WM_keymap_add_item(km, idname, EVT_AKEY, KM_PRESS, 0, 0); + const int kmi_id = kmi->id; + + /* This takes ownership of prop, or prop can be NULL for reset. */ + WM_keymap_item_properties_reset(kmi, prop); + + /* update and get pointers again */ + WM_keyconfig_update(wm); + U.runtime.is_dirty = true; + + km = WM_keymap_guess_opname(C, idname); + kmi = WM_keymap_item_find_id(km, kmi_id); + + RNA_pointer_create(&wm->id, &RNA_KeyMapItem, kmi, &ptr); + + uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); + UI_block_func_handle_set(block, but_shortcut_name_func, but); + UI_block_direction_set(block, UI_DIR_CENTER_Y); + + uiLayout *layout = UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_PANEL, + 0, + 0, + U.widget_unit * 10, + U.widget_unit * 2, + 0, + style); + + uiItemL(layout, CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"), ICON_HAND); + uiItemR(layout, &ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE); + + UI_block_bounds_set_popup( + block, 6 * U.dpi_fac, (const int[2]){-100 * U.dpi_fac, 36 * U.dpi_fac}); + +#ifdef USE_KEYMAP_ADD_HACK + g_kmi_id_hack = kmi_id; +#endif + + return block; +} + +static void menu_add_shortcut_cancel(struct bContext *C, void *arg1) +{ + uiBut *but = (uiBut *)arg1; + + IDProperty *prop; + const char *idname = shortcut_get_operator_property(C, but, &prop); + +#ifdef USE_KEYMAP_ADD_HACK + wmKeyMap *km = WM_keymap_guess_opname(C, idname); + const int kmi_id = g_kmi_id_hack; + UNUSED_VARS(but); +#else + int kmi_id = WM_key_event_operator_id(C, idname, but->opcontext, prop, true, &km); +#endif + + shortcut_free_operator_property(prop); + + wmKeyMapItem *kmi = WM_keymap_item_find_id(km, kmi_id); + WM_keymap_remove_item(km, kmi); +} + +static void popup_change_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) +{ + uiBut *but = (uiBut *)arg1; + UI_popup_block_invoke(C, menu_change_shortcut, but, NULL); +} + +static void remove_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) +{ + uiBut *but = (uiBut *)arg1; + IDProperty *prop; + const char *idname = shortcut_get_operator_property(C, but, &prop); + + wmKeyMap *km; + wmKeyMapItem *kmi = WM_key_event_operator(C, + idname, + but->opcontext, + prop, + EVT_TYPE_MASK_HOTKEY_INCLUDE, + EVT_TYPE_MASK_HOTKEY_EXCLUDE, + &km); + BLI_assert(kmi != NULL); + + WM_keymap_remove_item(km, kmi); + U.runtime.is_dirty = true; + + shortcut_free_operator_property(prop); + but_shortcut_name_func(C, but, 0); +} + +static void popup_add_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2)) +{ + uiBut *but = (uiBut *)arg1; + UI_popup_block_ex(C, menu_add_shortcut, NULL, menu_add_shortcut_cancel, but, NULL); +} + +static bool ui_but_is_user_menu_compatible(bContext *C, uiBut *but) +{ + return (but->optype || + (but->rnaprop && (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) && + (WM_context_member_from_ptr(C, &but->rnapoin) != NULL)) || + UI_but_menutype_get(but)); +} + +static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *um) +{ + if (but->optype) { + IDProperty *prop = (but->opptr) ? (IDProperty *)but->opptr->data : NULL; + return (bUserMenuItem *)ED_screen_user_menu_item_find_operator( + &um->items, but->optype, prop, but->opcontext); + } + if (but->rnaprop) { + const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); + const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); + const char *member_id_data_path = member_id; + if (data_path) { + member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); + } + const char *prop_id = RNA_property_identifier(but->rnaprop); + bUserMenuItem *umi = (bUserMenuItem *)ED_screen_user_menu_item_find_prop( + &um->items, member_id_data_path, prop_id, but->rnaindex); + if (data_path) { + MEM_freeN((void *)data_path); + } + if (member_id != member_id_data_path) { + MEM_freeN((void *)member_id_data_path); + } + return umi; + } + + MenuType *mt = UI_but_menutype_get(but); + if (mt != NULL) { + return (bUserMenuItem *)ED_screen_user_menu_item_find_menu(&um->items, mt); + } + return NULL; +} + +static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um) +{ + BLI_assert(ui_but_is_user_menu_compatible(C, but)); + + char drawstr[sizeof(but->drawstr)]; + STRNCPY(drawstr, but->drawstr); + if (but->flag & UI_BUT_HAS_SEP_CHAR) { + char *sep = strrchr(drawstr, UI_SEP_CHAR); + if (sep) { + *sep = '\0'; + } + } + + MenuType *mt = NULL; + if (but->optype) { + if (drawstr[0] == '\0') { + /* Hard code overrides for generic operators. */ + if (UI_but_is_tool(but)) { + char idname[64]; + RNA_string_get(but->opptr, "name", idname); +#ifdef WITH_PYTHON + { + const char *expr_imports[] = {"bpy", "bl_ui", NULL}; + char expr[256]; + SNPRINTF(expr, + "bl_ui.space_toolsystem_common.item_from_id(" + "bpy.context, " + "bpy.context.space_data.type, " + "'%s').label", + idname); + char *expr_result = NULL; + if (BPY_run_string_as_string(C, expr_imports, expr, NULL, &expr_result)) { + STRNCPY(drawstr, expr_result); + MEM_freeN(expr_result); + } + else { + BLI_assert(0); + STRNCPY(drawstr, idname); + } + } +#else + STRNCPY(drawstr, idname); +#endif + } + } + ED_screen_user_menu_item_add_operator(&um->items, + drawstr, + but->optype, + but->opptr ? (const IDProperty *)but->opptr->data : NULL, + but->opcontext); + } + else if (but->rnaprop) { + /* Note: 'member_id' may be a path. */ + const char *member_id = WM_context_member_from_ptr(C, &but->rnapoin); + const char *data_path = RNA_path_from_ID_to_struct(&but->rnapoin); + const char *member_id_data_path = member_id; + if (data_path) { + member_id_data_path = BLI_sprintfN("%s.%s", member_id, data_path); + } + const char *prop_id = RNA_property_identifier(but->rnaprop); + /* Note, ignore 'drawstr', use property idname always. */ + ED_screen_user_menu_item_add_prop(&um->items, "", member_id_data_path, prop_id, but->rnaindex); + if (data_path) { + MEM_freeN((void *)data_path); + } + if (member_id != member_id_data_path) { + MEM_freeN((void *)member_id_data_path); + } + } + else if ((mt = UI_but_menutype_get(but))) { + ED_screen_user_menu_item_add_menu(&um->items, drawstr, mt); + } +} + +static void popup_user_menu_add_or_replace_func(bContext *C, void *arg1, void *UNUSED(arg2)) +{ + uiBut *but = (uiBut *)arg1; + bUserMenu *um = ED_screen_user_menu_ensure(C); + U.runtime.is_dirty = true; + ui_but_user_menu_add(C, but, um); +} + +static void popup_user_menu_remove_func(bContext *UNUSED(C), void *arg1, void *arg2) +{ + bUserMenu *um = (bUserMenu *)arg1; + bUserMenuItem *umi = (bUserMenuItem *)arg2; + U.runtime.is_dirty = true; + ED_screen_user_menu_item_remove(&um->items, umi); +} + +static void ui_but_menu_add_path_operators(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop) +{ + const PropertySubType subtype = RNA_property_subtype(prop); + wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true); + char filepath[FILE_MAX]; + char dir[FILE_MAXDIR]; + char file[FILE_MAXFILE]; + PointerRNA props_ptr; + + BLI_assert(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)); + UNUSED_VARS_NDEBUG(subtype); + + RNA_property_string_get(ptr, prop, filepath); + BLI_split_dirfile(filepath, dir, file, sizeof(dir), sizeof(file)); + + if (file[0]) { + BLI_assert(subtype == PROP_FILEPATH); + uiItemFullO_ptr(layout, + ot, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open File Externally"), + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &props_ptr); + RNA_string_set(&props_ptr, "filepath", filepath); + } + + uiItemFullO_ptr(layout, + ot, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Location Externally"), + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &props_ptr); + RNA_string_set(&props_ptr, "filepath", dir); +} + +bool ui_popup_context_menu_for_button(bContext *C, uiBut *but) +{ + /* ui_but_is_interactive() may let some buttons through that should not get a context menu - it + * doesn't make sense for them. */ + if (ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_IMAGE)) { + return false; + } + + uiPopupMenu *pup; + uiLayout *layout; + bContextStore *previous_ctx = CTX_store_get(C); + { + uiStringInfo label = {BUT_GET_LABEL, NULL}; + + /* highly unlikely getting the label ever fails */ + UI_but_string_info_get(C, but, &label, NULL); + + pup = UI_popup_menu_begin(C, label.strinfo ? label.strinfo : "", ICON_NONE); + layout = UI_popup_menu_layout(pup); + if (label.strinfo) { + MEM_freeN(label.strinfo); + } + + if (but->context) { + uiLayoutContextCopy(layout, but->context); + CTX_store_set(C, uiLayoutGetContextStore(layout)); + } + uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); + } + + const bool is_disabled = but->flag & UI_BUT_DISABLED; + + if (is_disabled) { + /* Suppress editing commands. */ + } + else if (but->type == UI_BTYPE_TAB) { + uiButTab *tab = (uiButTab *)but; + if (tab->menu) { + UI_menutype_draw(C, tab->menu, layout); + uiItemS(layout); + } + } + else if (but->rnapoin.data && but->rnaprop) { + PointerRNA *ptr = &but->rnapoin; + PropertyRNA *prop = but->rnaprop; + const PropertyType type = RNA_property_type(prop); + const PropertySubType subtype = RNA_property_subtype(prop); + bool is_anim = RNA_property_animateable(ptr, prop); + const bool is_idprop = RNA_property_is_idprop(prop); + + /* second slower test, + * saved people finding keyframe items in menus when its not possible */ + if (is_anim) { + is_anim = RNA_property_path_from_ID_check(&but->rnapoin, but->rnaprop); + } + + /* determine if we can key a single component of an array */ + const bool is_array = RNA_property_array_length(&but->rnapoin, but->rnaprop) != 0; + const bool is_array_component = (is_array && but->rnaindex != -1); + const bool is_whole_array = (is_array && but->rnaindex == -1); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), ptr, prop, -1); + const bool is_overridable = (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE) != 0; + + /* Set the (button_pointer, button_prop) + * and pointer data for Python access to the hovered ui element. */ + uiLayoutSetContextFromBut(layout, but); + + /* Keyframes */ + if (but->flag & UI_BUT_ANIMATED_KEY) { + /* Replace/delete keyframes. */ + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframes"), + ICON_KEY_HLT, + "ANIM_OT_keyframe_insert_button", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Single Keyframe"), + ICON_NONE, + "ANIM_OT_keyframe_insert_button", + "all", + 0); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframes"), + ICON_NONE, + "ANIM_OT_keyframe_delete_button", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Keyframe"), + ICON_NONE, + "ANIM_OT_keyframe_delete_button", + "all", + 0); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframe"), + ICON_KEY_HLT, + "ANIM_OT_keyframe_insert_button", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframe"), + ICON_NONE, + "ANIM_OT_keyframe_delete_button", + "all", + 1); + } + + /* keyframe settings */ + uiItemS(layout); + } + else if (but->flag & UI_BUT_DRIVEN) { + /* pass */ + } + else if (is_anim) { + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframes"), + ICON_KEY_HLT, + "ANIM_OT_keyframe_insert_button", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Single Keyframe"), + ICON_NONE, + "ANIM_OT_keyframe_insert_button", + "all", + 0); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframe"), + ICON_KEY_HLT, + "ANIM_OT_keyframe_insert_button", + "all", + 1); + } + } + + if ((but->flag & UI_BUT_ANIMATED) && (but->rnapoin.type != &RNA_NlaStrip)) { + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"), + ICON_KEY_DEHLT, + "ANIM_OT_keyframe_clear_button", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Single Keyframes"), + ICON_NONE, + "ANIM_OT_keyframe_clear_button", + "all", + 0); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"), + ICON_KEY_DEHLT, + "ANIM_OT_keyframe_clear_button", + "all", + 1); + } + } + + /* Drivers */ + if (but->flag & UI_BUT_DRIVEN) { + uiItemS(layout); + + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Drivers"), + ICON_X, + "ANIM_OT_driver_button_remove", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Driver"), + ICON_NONE, + "ANIM_OT_driver_button_remove", + "all", + 0); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Driver"), + ICON_X, + "ANIM_OT_driver_button_remove", + "all", + 1); + } + + if (!is_whole_array) { + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Driver"), + ICON_NONE, + "ANIM_OT_copy_driver_button"); + if (ANIM_driver_can_paste()) { + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"), + ICON_NONE, + "ANIM_OT_paste_driver_button"); + } + + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Edit Driver"), + ICON_DRIVER, + "ANIM_OT_driver_button_edit"); + } + + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"), + ICON_NONE, + "SCREEN_OT_drivers_editor_show"); + } + else if (but->flag & (UI_BUT_ANIMATED_KEY | UI_BUT_ANIMATED)) { + /* pass */ + } + else if (is_anim) { + uiItemS(layout); + + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Driver"), + ICON_DRIVER, + "ANIM_OT_driver_button_add"); + + if (!is_whole_array) { + if (ANIM_driver_can_paste()) { + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"), + ICON_NONE, + "ANIM_OT_paste_driver_button"); + } + } + + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"), + ICON_NONE, + "SCREEN_OT_drivers_editor_show"); + } + + /* Keying Sets */ + /* TODO: check on modifyability of Keying Set when doing this */ + if (is_anim) { + uiItemS(layout); + + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add All to Keying Set"), + ICON_KEYINGSET, + "ANIM_OT_keyingset_button_add", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Single to Keying Set"), + ICON_NONE, + "ANIM_OT_keyingset_button_add", + "all", + 0); + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"), + ICON_NONE, + "ANIM_OT_keyingset_button_remove"); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Keying Set"), + ICON_KEYINGSET, + "ANIM_OT_keyingset_button_add", + "all", + 1); + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"), + ICON_NONE, + "ANIM_OT_keyingset_button_remove"); + } + } + + if (is_overridable) { + wmOperatorType *ot; + PointerRNA op_ptr; + /* Override Operators */ + uiItemS(layout); + + if (but->flag & UI_BUT_OVERRIDDEN) { + if (is_array_component) { +#if 0 /* Disabled for now. */ + ot = WM_operatortype_find("UI_OT_override_type_set_button", false); + uiItemFullO_ptr( + layout, ot, "Overrides Type", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); + RNA_boolean_set(&op_ptr, "all", true); + uiItemFullO_ptr(layout, + ot, + "Single Override Type", + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &op_ptr); + RNA_boolean_set(&op_ptr, "all", false); +#endif + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Overrides"), + ICON_X, + "UI_OT_override_remove_button", + "all", + true); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Single Override"), + ICON_X, + "UI_OT_override_remove_button", + "all", + false); + } + else { +#if 0 /* Disabled for now. */ + uiItemFullO(layout, + "UI_OT_override_type_set_button", + "Override Type", + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &op_ptr); + RNA_boolean_set(&op_ptr, "all", false); +#endif + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Override"), + ICON_X, + "UI_OT_override_remove_button", + "all", + true); + } + } + else { + if (is_array_component) { + ot = WM_operatortype_find("UI_OT_override_type_set_button", false); + uiItemFullO_ptr( + layout, ot, "Define Overrides", ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); + RNA_boolean_set(&op_ptr, "all", true); + uiItemFullO_ptr(layout, + ot, + "Define Single Override", + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &op_ptr); + RNA_boolean_set(&op_ptr, "all", false); + } + else { + uiItemFullO(layout, + "UI_OT_override_type_set_button", + "Define Override", + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &op_ptr); + RNA_boolean_set(&op_ptr, "all", false); + } + } + } + + uiItemS(layout); + + /* Property Operators */ + + /* Copy Property Value + * Paste Property Value */ + + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset All to Default Values"), + ICON_LOOP_BACK, + "UI_OT_reset_default_button", + "all", + 1); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset Single to Default Value"), + ICON_NONE, + "UI_OT_reset_default_button", + "all", + 0); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset to Default Value"), + ICON_LOOP_BACK, + "UI_OT_reset_default_button", + "all", + 1); + } + + if (is_idprop && !is_array && ELEM(type, PROP_INT, PROP_FLOAT)) { + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Value as Default"), + ICON_NONE, + "UI_OT_assign_default_button"); + + uiItemS(layout); + } + + if (is_array_component) { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy All to Selected"), + ICON_NONE, + "UI_OT_copy_to_selected_button", + "all", + true); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Single to Selected"), + ICON_NONE, + "UI_OT_copy_to_selected_button", + "all", + false); + } + else { + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"), + ICON_NONE, + "UI_OT_copy_to_selected_button", + "all", + true); + } + + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Data Path"), + ICON_NONE, + "UI_OT_copy_data_path_button"); + uiItemBooleanO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Full Data Path"), + ICON_NONE, + "UI_OT_copy_data_path_button", + "full_path", + true); + + if (ptr->owner_id && !is_whole_array && + ELEM(type, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM)) { + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy as New Driver"), + ICON_NONE, + "UI_OT_copy_as_driver_button"); + } + + uiItemS(layout); + + if (type == PROP_STRING && ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) { + ui_but_menu_add_path_operators(layout, ptr, prop); + uiItemS(layout); + } + } + + /* If the button represents an id, it can set the "id" context pointer. */ + if (U.experimental.use_asset_browser && ED_asset_can_make_single_from_context(C)) { + ID *id = (ID *)CTX_data_pointer_get_type(C, "id", &RNA_ID).data; + + /* Gray out items depending on if data-block is an asset. Preferably this could be done via + * operator poll, but that doesn't work since the operator also works with "selected_ids", + * which isn't cheap to check. */ + uiLayout *sub = uiLayoutColumn(layout, true); + uiLayoutSetEnabled(sub, !id->asset_data); + uiItemO(sub, NULL, ICON_NONE, "ASSET_OT_mark"); + sub = uiLayoutColumn(layout, true); + uiLayoutSetEnabled(sub, id->asset_data); + uiItemO(sub, NULL, ICON_NONE, "ASSET_OT_clear"); + uiItemS(layout); + } + + /* Pointer properties and string properties with + * prop_search support jumping to target object/bone. */ + if (but->rnapoin.data && but->rnaprop) { + const PropertyType prop_type = RNA_property_type(but->rnaprop); + if (((prop_type == PROP_POINTER) || + (prop_type == PROP_STRING && but->type == UI_BTYPE_SEARCH_MENU && + ((uiButSearch *)but)->items_update_fn == ui_rna_collection_search_update_fn)) && + ui_jump_to_target_button_poll(C)) { + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Jump to Target"), + ICON_NONE, + "UI_OT_jump_to_target_button"); + uiItemS(layout); + } + } + + /* Favorites Menu */ + if (ui_but_is_user_menu_compatible(C, but)) { + uiBlock *block = uiLayoutGetBlock(layout); + const int w = uiLayoutGetWidth(layout); + bool item_found = false; + + uint um_array_len; + bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len); + for (int um_index = 0; um_index < um_array_len; um_index++) { + bUserMenu *um = um_array[um_index]; + if (um == NULL) { + continue; + } + bUserMenuItem *umi = ui_but_user_menu_find(C, but, um); + if (umi != NULL) { + uiBut *but2 = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_MENU_PANEL, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Quick Favorites"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but2, popup_user_menu_remove_func, um, umi); + item_found = true; + } + } + if (um_array) { + MEM_freeN(um_array); + } + + if (!item_found) { + uiBut *but2 = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_MENU_PANEL, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Quick Favorites"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + "Add to a user defined context menu (stored in the user preferences)"); + UI_but_func_set(but2, popup_user_menu_add_or_replace_func, but, NULL); + } + + uiItemS(layout); + } + + /* Shortcut menu */ + IDProperty *prop; + const char *idname = shortcut_get_operator_property(C, but, &prop); + if (idname != NULL) { + uiBlock *block = uiLayoutGetBlock(layout); + const int w = uiLayoutGetWidth(layout); + + /* We want to know if this op has a shortcut, be it hotkey or not. */ + wmKeyMap *km; + wmKeyMapItem *kmi = WM_key_event_operator( + C, idname, but->opcontext, prop, EVT_TYPE_MASK_ALL, 0, &km); + + /* We do have a shortcut, but only keyboard ones are editable that way... */ + if (kmi) { + if (ISKEYBOARD(kmi->type)) { +#if 0 /* would rather use a block but, but gets weirdly positioned... */ + uiDefBlockBut(block, + menu_change_shortcut, + but, + "Change Shortcut", + 0, + 0, + uiLayoutGetWidth(layout), + UI_UNIT_Y, + ""); +#endif + + uiBut *but2 = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_HAND, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but2, popup_change_shortcut_func, but, NULL); + } + else { + uiBut *but2 = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_HAND, + IFACE_("Non-Keyboard Shortcut"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Only keyboard shortcuts can be edited that way, " + "please use User Preferences otherwise")); + UI_but_flag_enable(but2, UI_BUT_DISABLED); + } + + uiBut *but2 = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_BLANK1, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Shortcut"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but2, remove_shortcut_func, but, NULL); + } + /* only show 'assign' if there's a suitable key map for it to go in */ + else if (WM_keymap_guess_opname(C, idname)) { + uiBut *but2 = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_HAND, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but2, popup_add_shortcut_func, but, NULL); + } + + shortcut_free_operator_property(prop); + + /* Set the operator pointer for python access */ + uiLayoutSetContextFromBut(layout, but); + + uiItemS(layout); + } + + { /* Docs */ + char buf[512]; + + if (UI_but_online_manual_id(but, buf, sizeof(buf))) { + PointerRNA ptr_props; + uiItemO(layout, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Manual"), + ICON_URL, + "WM_OT_doc_view_manual_ui_context"); + + if (U.flag & USER_DEVELOPER_UI) { + uiItemFullO(layout, + "WM_OT_doc_view", + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Python Reference"), + ICON_NONE, + NULL, + WM_OP_EXEC_DEFAULT, + 0, + &ptr_props); + RNA_string_set(&ptr_props, "doc_id", buf); + } + } + } + + if (but->optype && U.flag & USER_DEVELOPER_UI) { + uiItemO(layout, NULL, ICON_NONE, "UI_OT_copy_python_command_button"); + } + + /* perhaps we should move this into (G.debug & G_DEBUG) - campbell */ + if (U.flag & USER_DEVELOPER_UI) { + if (ui_block_is_menu(but->block) == false) { + uiItemFullO( + layout, "UI_OT_editsource", NULL, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, NULL); + } + } + + if (BKE_addon_find(&U.addons, "ui_translate")) { + uiItemFullO(layout, + "UI_OT_edittranslation_init", + NULL, + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + NULL); + } + + /* Show header tools for header buttons. */ + if (ui_block_is_popup_any(but->block) == false) { + const ARegion *region = CTX_wm_region(C); + + if (!region) { + /* skip */ + } + else if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) { + uiItemMenuF(layout, IFACE_("Header"), ICON_NONE, ED_screens_header_tools_menu_create, NULL); + } + else if (region->regiontype == RGN_TYPE_NAV_BAR) { + uiItemMenuF(layout, + IFACE_("Navigation Bar"), + ICON_NONE, + ED_screens_navigation_bar_tools_menu_create, + NULL); + } + else if (region->regiontype == RGN_TYPE_FOOTER) { + uiItemMenuF(layout, IFACE_("Footer"), ICON_NONE, ED_screens_footer_tools_menu_create, NULL); + } + } + + MenuType *mt = WM_menutype_find("WM_MT_button_context", true); + if (mt) { + UI_menutype_draw(C, mt, uiLayoutColumn(layout, false)); + } + + if (but->context) { + CTX_store_set(C, previous_ctx); + } + + return UI_popup_menu_end_or_cancel(C, pup); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Context Menu + * \{ */ + +/** + * menu to show when right clicking on the panel header + */ +void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel) +{ + bScreen *screen = CTX_wm_screen(C); + const bool has_panel_category = UI_panel_category_is_visible(region); + const bool any_item_visible = has_panel_category; + + if (!any_item_visible) { + return; + } + if (panel->type->parent != NULL) { + return; + } + + PointerRNA ptr; + RNA_pointer_create(&screen->id, &RNA_Panel, panel, &ptr); + + uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Panel"), ICON_NONE); + uiLayout *layout = UI_popup_menu_layout(pup); + + if (has_panel_category) { + char tmpstr[80]; + BLI_snprintf(tmpstr, + sizeof(tmpstr), + "%s" UI_SEP_CHAR_S "%s", + IFACE_("Pin"), + IFACE_("Shift Left Mouse")); + uiItemR(layout, &ptr, "use_pin", 0, tmpstr, ICON_NONE); + + /* evil, force shortcut flag */ + { + uiBlock *block = uiLayoutGetBlock(layout); + uiBut *but = (uiBut *)block->buttons.last; + but->flag |= UI_BUT_HAS_SEP_CHAR; + but->drawflag |= UI_BUT_HAS_SHORTCUT; + } + } + UI_popup_menu_end(C, pup); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c deleted file mode 100644 index 40cfcaea883..00000000000 --- a/source/blender/editors/interface/interface_draw.c +++ /dev/null @@ -1,2397 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include - -#include "DNA_color_types.h" -#include "DNA_curve_types.h" -#include "DNA_curveprofile_types.h" -#include "DNA_movieclip_types.h" -#include "DNA_screen_types.h" - -#include "BLI_math.h" -#include "BLI_polyfill_2d.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "MEM_guardedalloc.h" - -#include "BKE_colorband.h" -#include "BKE_colortools.h" -#include "BKE_curveprofile.h" -#include "BKE_node.h" -#include "BKE_tracking.h" - -#include "IMB_colormanagement.h" -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" - -#include "BIF_glutil.h" - -#include "BLF_api.h" - -#include "GPU_batch.h" -#include "GPU_batch_presets.h" -#include "GPU_immediate.h" -#include "GPU_immediate_util.h" -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "UI_interface.h" - -/* own include */ -#include "interface_intern.h" - -static int roundboxtype = UI_CNR_ALL; - -void UI_draw_roundbox_corner_set(int type) -{ - /* Not sure the roundbox function is the best place to change this - * if this is undone, it's not that big a deal, only makes curves edges - * square for the */ - roundboxtype = type; -} - -#if 0 /* unused */ -int UI_draw_roundbox_corner_get(void) -{ - return roundboxtype; -} -#endif - -void UI_draw_roundbox_4fv_ex(const rctf *rect, - const float inner1[4], - const float inner2[4], - float shade_dir, - const float outline[4], - float outline_width, - float rad) -{ - /* WATCH: This is assuming the ModelViewProjectionMatrix is area pixel space. - * If it has been scaled, then it's no longer valid. */ - uiWidgetBaseParameters widget_params = { - .recti.xmin = rect->xmin + outline_width, - .recti.ymin = rect->ymin + outline_width, - .recti.xmax = rect->xmax - outline_width, - .recti.ymax = rect->ymax - outline_width, - .rect = *rect, - .radi = rad, - .rad = rad, - .round_corners[0] = (roundboxtype & UI_CNR_BOTTOM_LEFT) ? 1.0f : 0.0f, - .round_corners[1] = (roundboxtype & UI_CNR_BOTTOM_RIGHT) ? 1.0f : 0.0f, - .round_corners[2] = (roundboxtype & UI_CNR_TOP_RIGHT) ? 1.0f : 0.0f, - .round_corners[3] = (roundboxtype & UI_CNR_TOP_LEFT) ? 1.0f : 0.0f, - .color_inner1[0] = inner1 ? inner1[0] : 0.0f, - .color_inner1[1] = inner1 ? inner1[1] : 0.0f, - .color_inner1[2] = inner1 ? inner1[2] : 0.0f, - .color_inner1[3] = inner1 ? inner1[3] : 0.0f, - .color_inner2[0] = inner2 ? inner2[0] : inner1 ? inner1[0] : 0.0f, - .color_inner2[1] = inner2 ? inner2[1] : inner1 ? inner1[1] : 0.0f, - .color_inner2[2] = inner2 ? inner2[2] : inner1 ? inner1[2] : 0.0f, - .color_inner2[3] = inner2 ? inner2[3] : inner1 ? inner1[3] : 0.0f, - .color_outline[0] = outline ? outline[0] : inner1 ? inner1[0] : 0.0f, - .color_outline[1] = outline ? outline[1] : inner1 ? inner1[1] : 0.0f, - .color_outline[2] = outline ? outline[2] : inner1 ? inner1[2] : 0.0f, - .color_outline[3] = outline ? outline[3] : inner1 ? inner1[3] : 0.0f, - .shade_dir = shade_dir, - .alpha_discard = 1.0f, - }; - GPUBatch *batch = ui_batch_roundbox_widget_get(); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_WIDGET_BASE); - GPU_batch_uniform_4fv_array(batch, "parameters", 11, (const float(*)[4]) & widget_params); - GPU_blend(GPU_BLEND_ALPHA); - GPU_batch_draw(batch); - GPU_blend(GPU_BLEND_NONE); -} - -void UI_draw_roundbox_3ub_alpha( - const rctf *rect, bool filled, float rad, const uchar col[3], uchar alpha) -{ - float colv[4] = { - ((float)col[0]) / 255, - ((float)col[1]) / 255, - ((float)col[2]) / 255, - ((float)alpha) / 255, - }; - UI_draw_roundbox_4fv_ex(rect, (filled) ? colv : NULL, NULL, 1.0f, colv, U.pixelsize, rad); -} - -void UI_draw_roundbox_3fv_alpha( - const rctf *rect, bool filled, float rad, const float col[3], float alpha) -{ - float colv[4] = {col[0], col[1], col[2], alpha}; - UI_draw_roundbox_4fv_ex(rect, (filled) ? colv : NULL, NULL, 1.0f, colv, U.pixelsize, rad); -} - -void UI_draw_roundbox_aa(const rctf *rect, bool filled, float rad, const float color[4]) -{ - /* XXX this is to emulate previous behavior of semitransparent fills but that's was a side effect - * of the previous AA method. Better fix the callers. */ - float colv[4] = {color[0], color[1], color[2], color[3]}; - if (filled) { - colv[3] *= 0.65f; - } - - UI_draw_roundbox_4fv_ex(rect, (filled) ? colv : NULL, NULL, 1.0f, colv, U.pixelsize, rad); -} - -void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float col[4]) -{ - /* Exactly the same as UI_draw_roundbox_aa but does not do the legacy transparency. */ - UI_draw_roundbox_4fv_ex(rect, (filled) ? col : NULL, NULL, 1.0f, col, U.pixelsize, rad); -} - -/* linear horizontal shade within button or in outline */ -/* view2d scrollers use it */ -void UI_draw_roundbox_shade_x( - const rctf *rect, bool filled, float rad, float shadetop, float shadedown, const float col[4]) -{ - float inner1[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - float inner2[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - float outline[4]; - - if (filled) { - inner1[0] = min_ff(1.0f, col[0] + shadetop); - inner1[1] = min_ff(1.0f, col[1] + shadetop); - inner1[2] = min_ff(1.0f, col[2] + shadetop); - inner1[3] = 1.0f; - inner2[0] = max_ff(0.0f, col[0] + shadedown); - inner2[1] = max_ff(0.0f, col[1] + shadedown); - inner2[2] = max_ff(0.0f, col[2] + shadedown); - inner2[3] = 1.0f; - } - - /* TODO: non-filled box don't have gradients. Just use middle color. */ - outline[0] = clamp_f(col[0] + shadetop + shadedown, 0.0f, 1.0f); - outline[1] = clamp_f(col[1] + shadetop + shadedown, 0.0f, 1.0f); - outline[2] = clamp_f(col[2] + shadetop + shadedown, 0.0f, 1.0f); - outline[3] = clamp_f(col[3] + shadetop + shadedown, 0.0f, 1.0f); - - UI_draw_roundbox_4fv_ex(rect, inner1, inner2, 1.0f, outline, U.pixelsize, rad); -} - -void UI_draw_text_underline(int pos_x, int pos_y, int len, int height, const float color[4]) -{ - const int ofs_y = 4 * U.pixelsize; - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor4fv(color); - - immRecti(pos, pos_x, pos_y - ofs_y, pos_x + len, pos_y - ofs_y + (height * U.pixelsize)); - immUnbindProgram(); -} - -/* ************** SPECIAL BUTTON DRAWING FUNCTIONS ************* */ - -/* based on UI_draw_roundbox_gl_mode, - * check on making a version which allows us to skip some sides */ -void ui_draw_but_TAB_outline(const rcti *rect, - float rad, - uchar highlight[3], - uchar highlight_fade[3]) -{ - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint col = GPU_vertformat_attr_add( - format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT); - /* add a 1px offset, looks nicer */ - const int minx = rect->xmin + U.pixelsize, maxx = rect->xmax - U.pixelsize; - const int miny = rect->ymin + U.pixelsize, maxy = rect->ymax - U.pixelsize; - int a; - float vec[4][2] = { - {0.195, 0.02}, - {0.55, 0.169}, - {0.831, 0.45}, - {0.98, 0.805}, - }; - - /* mult */ - for (a = 0; a < 4; a++) { - mul_v2_fl(vec[a], rad); - } - - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); - immBeginAtMost(GPU_PRIM_LINE_STRIP, 25); - - immAttr3ubv(col, highlight); - - /* start with corner left-top */ - if (roundboxtype & UI_CNR_TOP_LEFT) { - immVertex2f(pos, minx, maxy - rad); - for (a = 0; a < 4; a++) { - immVertex2f(pos, minx + vec[a][1], maxy - rad + vec[a][0]); - } - immVertex2f(pos, minx + rad, maxy); - } - else { - immVertex2f(pos, minx, maxy); - } - - /* corner right-top */ - if (roundboxtype & UI_CNR_TOP_RIGHT) { - immVertex2f(pos, maxx - rad, maxy); - for (a = 0; a < 4; a++) { - immVertex2f(pos, maxx - rad + vec[a][0], maxy - vec[a][1]); - } - immVertex2f(pos, maxx, maxy - rad); - } - else { - immVertex2f(pos, maxx, maxy); - } - - immAttr3ubv(col, highlight_fade); - - /* corner right-bottom */ - if (roundboxtype & UI_CNR_BOTTOM_RIGHT) { - immVertex2f(pos, maxx, miny + rad); - for (a = 0; a < 4; a++) { - immVertex2f(pos, maxx - vec[a][1], miny + rad - vec[a][0]); - } - immVertex2f(pos, maxx - rad, miny); - } - else { - immVertex2f(pos, maxx, miny); - } - - /* corner left-bottom */ - if (roundboxtype & UI_CNR_BOTTOM_LEFT) { - immVertex2f(pos, minx + rad, miny); - for (a = 0; a < 4; a++) { - immVertex2f(pos, minx + rad - vec[a][0], miny + vec[a][1]); - } - immVertex2f(pos, minx, miny + rad); - } - else { - immVertex2f(pos, minx, miny); - } - - immAttr3ubv(col, highlight); - - /* back to corner left-top */ - immVertex2f(pos, minx, (roundboxtype & UI_CNR_TOP_LEFT) ? (maxy - rad) : maxy); - - immEnd(); - immUnbindProgram(); -} - -void ui_draw_but_IMAGE(ARegion *UNUSED(region), - uiBut *but, - const uiWidgetColors *UNUSED(wcol), - const rcti *rect) -{ -#ifdef WITH_HEADLESS - (void)rect; - (void)but; -#else - ImBuf *ibuf = (ImBuf *)but->poin; - - if (!ibuf) { - return; - } - - const int w = BLI_rcti_size_x(rect); - const int h = BLI_rcti_size_y(rect); - - /* scissor doesn't seem to be doing the right thing...? */ -# if 0 - /* prevent drawing outside widget area */ - int scissor[4]; - GPU_scissor_get(scissor); - GPU_scissor(rect->xmin, rect->ymin, w, h); -# endif - - /* Combine with premultiplied alpha. */ - GPU_blend(GPU_BLEND_ALPHA_PREMULT); - - if (w != ibuf->x || h != ibuf->y) { - /* We scale the bitmap, rather than have OGL do a worse job. */ - IMB_scaleImBuf(ibuf, w, h); - } - - float col[4] = {1.0f, 1.0f, 1.0f, 1.0f}; - if (but->col[3] != 0) { - /* Optionally use uiBut's col to recolor the image. */ - rgba_uchar_to_float(col, but->col); - } - - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); - immDrawPixelsTex(&state, - (float)rect->xmin, - (float)rect->ymin, - ibuf->x, - ibuf->y, - GPU_RGBA8, - false, - ibuf->rect, - 1.0f, - 1.0f, - col); - - GPU_blend(GPU_BLEND_NONE); - -# if 0 - /* Restore scissor-test. */ - GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); -# endif - -#endif -} - -/** - * Draw title and text safe areas. - * - * \note This function is to be used with the 2D dashed shader enabled. - * - * \param pos: is a #PRIM_FLOAT, 2, #GPU_FETCH_FLOAT vertex attribute. - * \param x1, x2, y1, y2: The offsets for the view, not the zones. - */ -void UI_draw_safe_areas(uint pos, - const rctf *rect, - const float title_aspect[2], - const float action_aspect[2]) -{ - const float size_x_half = (rect->xmax - rect->xmin) * 0.5f; - const float size_y_half = (rect->ymax - rect->ymin) * 0.5f; - - const float *safe_areas[] = {title_aspect, action_aspect}; - const int safe_len = ARRAY_SIZE(safe_areas); - - for (int i = 0; i < safe_len; i++) { - if (safe_areas[i][0] || safe_areas[i][1]) { - const float margin_x = safe_areas[i][0] * size_x_half; - const float margin_y = safe_areas[i][1] * size_y_half; - - const float minx = rect->xmin + margin_x; - const float miny = rect->ymin + margin_y; - const float maxx = rect->xmax - margin_x; - const float maxy = rect->ymax - margin_y; - - imm_draw_box_wire_2d(pos, minx, miny, maxx, maxy); - } - } -} - -static void draw_scope_end(const rctf *rect) -{ - GPU_blend(GPU_BLEND_ALPHA); - - /* outline */ - UI_draw_roundbox_corner_set(UI_CNR_ALL); - const float color[4] = {0.0f, 0.0f, 0.0f, 0.5f}; - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin - 1, - .xmax = rect->xmax + 1, - .ymin = rect->ymin, - .ymax = rect->ymax + 1, - }, - false, - 3.0f, - color); -} - -static void histogram_draw_one(float r, - float g, - float b, - float alpha, - float x, - float y, - float w, - float h, - const float *data, - int res, - const bool is_line, - uint pos_attr) -{ - const float color[4] = {r, g, b, alpha}; - - /* that can happen */ - if (res == 0) { - return; - } - - GPU_line_smooth(true); - GPU_blend(GPU_BLEND_ADDITIVE); - - immUniformColor4fv(color); - - if (is_line) { - /* curve outline */ - GPU_line_width(1.5); - - immBegin(GPU_PRIM_LINE_STRIP, res); - for (int i = 0; i < res; i++) { - const float x2 = x + i * (w / (float)res); - immVertex2f(pos_attr, x2, y + (data[i] * h)); - } - immEnd(); - } - else { - /* under the curve */ - immBegin(GPU_PRIM_TRI_STRIP, res * 2); - immVertex2f(pos_attr, x, y); - immVertex2f(pos_attr, x, y + (data[0] * h)); - for (int i = 1; i < res; i++) { - const float x2 = x + i * (w / (float)res); - immVertex2f(pos_attr, x2, y + (data[i] * h)); - immVertex2f(pos_attr, x2, y); - } - immEnd(); - - /* curve outline */ - immUniformColor4f(0.0f, 0.0f, 0.0f, 0.25f); - - GPU_blend(GPU_BLEND_ALPHA); - immBegin(GPU_PRIM_LINE_STRIP, res); - for (int i = 0; i < res; i++) { - const float x2 = x + i * (w / (float)res); - immVertex2f(pos_attr, x2, y + (data[i] * h)); - } - immEnd(); - } - - GPU_line_smooth(false); -} - -#define HISTOGRAM_TOT_GRID_LINES 4 - -void ui_draw_but_HISTOGRAM(ARegion *UNUSED(region), - uiBut *but, - const uiWidgetColors *UNUSED(wcol), - const rcti *recti) -{ - Histogram *hist = (Histogram *)but->poin; - const int res = hist->x_resolution; - const bool is_line = (hist->flag & HISTO_FLAG_LINE) != 0; - - rctf rect = { - .xmin = (float)recti->xmin + 1, - .xmax = (float)recti->xmax - 1, - .ymin = (float)recti->ymin + 1, - .ymax = (float)recti->ymax - 1, - }; - - const float w = BLI_rctf_size_x(&rect); - const float h = BLI_rctf_size_y(&rect) * hist->ymax; - - GPU_blend(GPU_BLEND_ALPHA); - - float color[4]; - UI_GetThemeColor4fv(TH_PREVIEW_BACK, color); - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect.xmin - 1, - .xmax = rect.xmax + 1, - .ymin = rect.ymin - 1, - .ymax = rect.ymax + 1, - }, - true, - 3.0f, - color); - - /* need scissor test, histogram can draw outside of boundary */ - int scissor[4]; - GPU_scissor_get(scissor); - GPU_scissor((rect.xmin - 1), - (rect.ymin - 1), - (rect.xmax + 1) - (rect.xmin - 1), - (rect.ymax + 1) - (rect.ymin - 1)); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); - /* draw grid lines here */ - for (int i = 1; i <= HISTOGRAM_TOT_GRID_LINES; i++) { - const float fac = (float)i / (float)HISTOGRAM_TOT_GRID_LINES; - - /* so we can tell the 1.0 color point */ - if (i == HISTOGRAM_TOT_GRID_LINES) { - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.5f); - } - - immBegin(GPU_PRIM_LINES, 4); - - immVertex2f(pos, rect.xmin, rect.ymin + fac * h); - immVertex2f(pos, rect.xmax, rect.ymin + fac * h); - - immVertex2f(pos, rect.xmin + fac * w, rect.ymin); - immVertex2f(pos, rect.xmin + fac * w, rect.ymax); - - immEnd(); - } - - if (hist->mode == HISTO_MODE_LUMA) { - histogram_draw_one( - 1.0, 1.0, 1.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_luma, res, is_line, pos); - } - else if (hist->mode == HISTO_MODE_ALPHA) { - histogram_draw_one( - 1.0, 1.0, 1.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_a, res, is_line, pos); - } - else { - if (ELEM(hist->mode, HISTO_MODE_RGB, HISTO_MODE_R)) { - histogram_draw_one( - 1.0, 0.0, 0.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_r, res, is_line, pos); - } - if (ELEM(hist->mode, HISTO_MODE_RGB, HISTO_MODE_G)) { - histogram_draw_one( - 0.0, 1.0, 0.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_g, res, is_line, pos); - } - if (ELEM(hist->mode, HISTO_MODE_RGB, HISTO_MODE_B)) { - histogram_draw_one( - 0.0, 0.0, 1.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_b, res, is_line, pos); - } - } - - immUnbindProgram(); - - /* Restore scissor test. */ - GPU_scissor(UNPACK4(scissor)); - - /* outline */ - draw_scope_end(&rect); -} - -#undef HISTOGRAM_TOT_GRID_LINES - -static void waveform_draw_one(float *waveform, int nbr, const float col[3]) -{ - GPUVertFormat format = {0}; - const uint pos_id = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); - GPU_vertbuf_data_alloc(vbo, nbr); - - GPU_vertbuf_attr_fill(vbo, pos_id, waveform); - - /* TODO store the GPUBatch inside the scope */ - GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); - GPU_batch_uniform_4f(batch, "color", col[0], col[1], col[2], 1.0f); - GPU_batch_draw(batch); - - GPU_batch_discard(batch); -} - -void ui_draw_but_WAVEFORM(ARegion *UNUSED(region), - uiBut *but, - const uiWidgetColors *UNUSED(wcol), - const rcti *recti) -{ - Scopes *scopes = (Scopes *)but->poin; - int scissor[4]; - float colors[3][3]; - const float colorsycc[3][3] = {{1, 0, 1}, {1, 1, 0}, {0, 1, 1}}; - /* colors pre multiplied by alpha for speed up */ - float colors_alpha[3][3], colorsycc_alpha[3][3]; - float min, max; - - if (scopes == NULL) { - return; - } - - rctf rect = { - .xmin = (float)recti->xmin + 1, - .xmax = (float)recti->xmax - 1, - .ymin = (float)recti->ymin + 1, - .ymax = (float)recti->ymax - 1, - }; - - if (scopes->wavefrm_yfac < 0.5f) { - scopes->wavefrm_yfac = 0.98f; - } - const float w = BLI_rctf_size_x(&rect) - 7; - const float h = BLI_rctf_size_y(&rect) * scopes->wavefrm_yfac; - const float yofs = rect.ymin + (BLI_rctf_size_y(&rect) - h) * 0.5f; - const float w3 = w / 3.0f; - - /* log scale for alpha */ - const float alpha = scopes->wavefrm_alpha * scopes->wavefrm_alpha; - - unit_m3(colors); - - for (int c = 0; c < 3; c++) { - for (int i = 0; i < 3; i++) { - colors_alpha[c][i] = colors[c][i] * alpha; - colorsycc_alpha[c][i] = colorsycc[c][i] * alpha; - } - } - - /* Flush text cache before changing scissors. */ - BLF_batch_draw_flush(); - - GPU_blend(GPU_BLEND_ALPHA); - - float color[4]; - UI_GetThemeColor4fv(TH_PREVIEW_BACK, color); - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect.xmin - 1, - .xmax = rect.xmax + 1, - .ymin = rect.ymin - 1, - .ymax = rect.ymax + 1, - }, - true, - 3.0f, - color); - - /* need scissor test, waveform can draw outside of boundary */ - GPU_scissor_get(scissor); - GPU_scissor((rect.xmin - 1), - (rect.ymin - 1), - (rect.xmax + 1) - (rect.xmin - 1), - (rect.ymax + 1) - (rect.ymin - 1)); - - /* draw scale numbers first before binding any shader */ - for (int i = 0; i < 6; i++) { - char str[4]; - BLI_snprintf(str, sizeof(str), "%-3d", i * 20); - str[3] = '\0'; - BLF_color4f(BLF_default(), 1.0f, 1.0f, 1.0f, 0.08f); - BLF_draw_default(rect.xmin + 1, yofs - 5 + (i * 0.2f) * h, 0, str, sizeof(str) - 1); - } - - /* Flush text cache before drawing things on top. */ - BLF_batch_draw_flush(); - - GPU_blend(GPU_BLEND_ALPHA); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); - - /* draw grid lines here */ - immBegin(GPU_PRIM_LINES, 12); - - for (int i = 0; i < 6; i++) { - immVertex2f(pos, rect.xmin + 22, yofs + (i * 0.2f) * h); - immVertex2f(pos, rect.xmax + 1, yofs + (i * 0.2f) * h); - } - - immEnd(); - - /* 3 vertical separation */ - if (scopes->wavefrm_mode != SCOPES_WAVEFRM_LUMA) { - immBegin(GPU_PRIM_LINES, 4); - - for (int i = 1; i < 3; i++) { - immVertex2f(pos, rect.xmin + i * w3, rect.ymin); - immVertex2f(pos, rect.xmin + i * w3, rect.ymax); - } - - immEnd(); - } - - /* separate min max zone on the right */ - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, rect.xmin + w, rect.ymin); - immVertex2f(pos, rect.xmin + w, rect.ymax); - immEnd(); - - /* 16-235-240 level in case of ITU-R BT601/709 */ - immUniformColor4f(1.0f, 0.4f, 0.0f, 0.2f); - if (ELEM(scopes->wavefrm_mode, SCOPES_WAVEFRM_YCC_601, SCOPES_WAVEFRM_YCC_709)) { - immBegin(GPU_PRIM_LINES, 8); - - immVertex2f(pos, rect.xmin + 22, yofs + h * 16.0f / 255.0f); - immVertex2f(pos, rect.xmax + 1, yofs + h * 16.0f / 255.0f); - - immVertex2f(pos, rect.xmin + 22, yofs + h * 235.0f / 255.0f); - immVertex2f(pos, rect.xmin + w3, yofs + h * 235.0f / 255.0f); - - immVertex2f(pos, rect.xmin + 3 * w3, yofs + h * 235.0f / 255.0f); - immVertex2f(pos, rect.xmax + 1, yofs + h * 235.0f / 255.0f); - - immVertex2f(pos, rect.xmin + w3, yofs + h * 240.0f / 255.0f); - immVertex2f(pos, rect.xmax + 1, yofs + h * 240.0f / 255.0f); - - immEnd(); - } - /* 7.5 IRE black point level for NTSC */ - if (scopes->wavefrm_mode == SCOPES_WAVEFRM_LUMA) { - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, rect.xmin, yofs + h * 0.075f); - immVertex2f(pos, rect.xmax + 1, yofs + h * 0.075f); - immEnd(); - } - - if (scopes->ok && scopes->waveform_1 != NULL) { - GPU_blend(GPU_BLEND_ADDITIVE); - GPU_point_size(1.0); - - /* LUMA (1 channel) */ - if (scopes->wavefrm_mode == SCOPES_WAVEFRM_LUMA) { - const float col[3] = {alpha, alpha, alpha}; - - GPU_matrix_push(); - GPU_matrix_translate_2f(rect.xmin, yofs); - GPU_matrix_scale_2f(w, h); - - waveform_draw_one(scopes->waveform_1, scopes->waveform_tot, col); - - GPU_matrix_pop(); - - /* min max */ - immUniformColor3f(0.5f, 0.5f, 0.5f); - min = yofs + scopes->minmax[0][0] * h; - max = yofs + scopes->minmax[0][1] * h; - CLAMP(min, rect.ymin, rect.ymax); - CLAMP(max, rect.ymin, rect.ymax); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, rect.xmax - 3, min); - immVertex2f(pos, rect.xmax - 3, max); - immEnd(); - } - /* RGB (3 channel) */ - else if (scopes->wavefrm_mode == SCOPES_WAVEFRM_RGB) { - GPU_matrix_push(); - GPU_matrix_translate_2f(rect.xmin, yofs); - GPU_matrix_scale_2f(w, h); - - waveform_draw_one(scopes->waveform_1, scopes->waveform_tot, colors_alpha[0]); - waveform_draw_one(scopes->waveform_2, scopes->waveform_tot, colors_alpha[1]); - waveform_draw_one(scopes->waveform_3, scopes->waveform_tot, colors_alpha[2]); - - GPU_matrix_pop(); - } - /* PARADE / YCC (3 channels) */ - else if (ELEM(scopes->wavefrm_mode, - SCOPES_WAVEFRM_RGB_PARADE, - SCOPES_WAVEFRM_YCC_601, - SCOPES_WAVEFRM_YCC_709, - SCOPES_WAVEFRM_YCC_JPEG)) { - const int rgb = (scopes->wavefrm_mode == SCOPES_WAVEFRM_RGB_PARADE); - - GPU_matrix_push(); - GPU_matrix_translate_2f(rect.xmin, yofs); - GPU_matrix_scale_2f(w3, h); - - waveform_draw_one( - scopes->waveform_1, scopes->waveform_tot, (rgb) ? colors_alpha[0] : colorsycc_alpha[0]); - - GPU_matrix_translate_2f(1.0f, 0.0f); - waveform_draw_one( - scopes->waveform_2, scopes->waveform_tot, (rgb) ? colors_alpha[1] : colorsycc_alpha[1]); - - GPU_matrix_translate_2f(1.0f, 0.0f); - waveform_draw_one( - scopes->waveform_3, scopes->waveform_tot, (rgb) ? colors_alpha[2] : colorsycc_alpha[2]); - - GPU_matrix_pop(); - } - - /* min max */ - if (scopes->wavefrm_mode != SCOPES_WAVEFRM_LUMA) { - for (int c = 0; c < 3; c++) { - if (ELEM(scopes->wavefrm_mode, SCOPES_WAVEFRM_RGB_PARADE, SCOPES_WAVEFRM_RGB)) { - immUniformColor3f(colors[c][0] * 0.75f, colors[c][1] * 0.75f, colors[c][2] * 0.75f); - } - else { - immUniformColor3f( - colorsycc[c][0] * 0.75f, colorsycc[c][1] * 0.75f, colorsycc[c][2] * 0.75f); - } - min = yofs + scopes->minmax[c][0] * h; - max = yofs + scopes->minmax[c][1] * h; - CLAMP(min, rect.ymin, rect.ymax); - CLAMP(max, rect.ymin, rect.ymax); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, rect.xmin + w + 2 + c * 2, min); - immVertex2f(pos, rect.xmin + w + 2 + c * 2, max); - immEnd(); - } - } - } - - immUnbindProgram(); - - /* Restore scissor test. */ - GPU_scissor(UNPACK4(scissor)); - - /* outline */ - draw_scope_end(&rect); - - GPU_blend(GPU_BLEND_NONE); -} - -static float polar_to_x(float center, float diam, float ampli, float angle) -{ - return center + diam * ampli * cosf(angle); -} - -static float polar_to_y(float center, float diam, float ampli, float angle) -{ - return center + diam * ampli * sinf(angle); -} - -static void vectorscope_draw_target( - uint pos, float centerx, float centery, float diam, const float colf[3]) -{ - float y, u, v; - float tangle = 0.0f, tampli; - float dangle, dampli, dangle2, dampli2; - - rgb_to_yuv(colf[0], colf[1], colf[2], &y, &u, &v, BLI_YUV_ITU_BT709); - - if (u > 0 && v >= 0) { - tangle = atanf(v / u); - } - else if (u > 0 && v < 0) { - tangle = atanf(v / u) + 2.0f * (float)M_PI; - } - else if (u < 0) { - tangle = atanf(v / u) + (float)M_PI; - } - else if (u == 0 && v > 0.0f) { - tangle = M_PI_2; - } - else if (u == 0 && v < 0.0f) { - tangle = -M_PI_2; - } - tampli = sqrtf(u * u + v * v); - - /* small target vary by 2.5 degree and 2.5 IRE unit */ - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.12f); - dangle = DEG2RADF(2.5f); - dampli = 2.5f / 200.0f; - immBegin(GPU_PRIM_LINE_LOOP, 4); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli, tangle + dangle), - polar_to_y(centery, diam, tampli + dampli, tangle + dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli, tangle + dangle), - polar_to_y(centery, diam, tampli - dampli, tangle + dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli, tangle - dangle), - polar_to_y(centery, diam, tampli - dampli, tangle - dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli, tangle - dangle), - polar_to_y(centery, diam, tampli + dampli, tangle - dangle)); - immEnd(); - /* big target vary by 10 degree and 20% amplitude */ - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.12f); - dangle = DEG2RADF(10.0f); - dampli = 0.2f * tampli; - dangle2 = DEG2RADF(5.0f); - dampli2 = 0.5f * dampli; - immBegin(GPU_PRIM_LINE_STRIP, 3); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli - dampli2, tangle + dangle), - polar_to_y(centery, diam, tampli + dampli - dampli2, tangle + dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli, tangle + dangle), - polar_to_y(centery, diam, tampli + dampli, tangle + dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli, tangle + dangle - dangle2), - polar_to_y(centery, diam, tampli + dampli, tangle + dangle - dangle2)); - immEnd(); - immBegin(GPU_PRIM_LINE_STRIP, 3); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli + dampli2, tangle + dangle), - polar_to_y(centery, diam, tampli - dampli + dampli2, tangle + dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli, tangle + dangle), - polar_to_y(centery, diam, tampli - dampli, tangle + dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli, tangle + dangle - dangle2), - polar_to_y(centery, diam, tampli - dampli, tangle + dangle - dangle2)); - immEnd(); - immBegin(GPU_PRIM_LINE_STRIP, 3); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli + dampli2, tangle - dangle), - polar_to_y(centery, diam, tampli - dampli + dampli2, tangle - dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli, tangle - dangle), - polar_to_y(centery, diam, tampli - dampli, tangle - dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli - dampli, tangle - dangle + dangle2), - polar_to_y(centery, diam, tampli - dampli, tangle - dangle + dangle2)); - immEnd(); - immBegin(GPU_PRIM_LINE_STRIP, 3); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli - dampli2, tangle - dangle), - polar_to_y(centery, diam, tampli + dampli - dampli2, tangle - dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli, tangle - dangle), - polar_to_y(centery, diam, tampli + dampli, tangle - dangle)); - immVertex2f(pos, - polar_to_x(centerx, diam, tampli + dampli, tangle - dangle + dangle2), - polar_to_y(centery, diam, tampli + dampli, tangle - dangle + dangle2)); - immEnd(); -} - -void ui_draw_but_VECTORSCOPE(ARegion *UNUSED(region), - uiBut *but, - const uiWidgetColors *UNUSED(wcol), - const rcti *recti) -{ - const float skin_rad = DEG2RADF(123.0f); /* angle in radians of the skin tone line */ - Scopes *scopes = (Scopes *)but->poin; - - const float colors[6][3] = { - {0.75, 0.0, 0.0}, - {0.75, 0.75, 0.0}, - {0.0, 0.75, 0.0}, - {0.0, 0.75, 0.75}, - {0.0, 0.0, 0.75}, - {0.75, 0.0, 0.75}, - }; - - rctf rect = { - .xmin = (float)recti->xmin + 1, - .xmax = (float)recti->xmax - 1, - .ymin = (float)recti->ymin + 1, - .ymax = (float)recti->ymax - 1, - }; - - const float w = BLI_rctf_size_x(&rect); - const float h = BLI_rctf_size_y(&rect); - const float centerx = rect.xmin + w * 0.5f; - const float centery = rect.ymin + h * 0.5f; - const float diam = (w < h) ? w : h; - - const float alpha = scopes->vecscope_alpha * scopes->vecscope_alpha * scopes->vecscope_alpha; - - GPU_blend(GPU_BLEND_ALPHA); - - float color[4]; - UI_GetThemeColor4fv(TH_PREVIEW_BACK, color); - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect.xmin - 1, - .xmax = rect.xmax + 1, - .ymin = rect.ymin - 1, - .ymax = rect.ymax + 1, - }, - true, - 3.0f, - color); - - /* need scissor test, hvectorscope can draw outside of boundary */ - int scissor[4]; - GPU_scissor_get(scissor); - GPU_scissor((rect.xmin - 1), - (rect.ymin - 1), - (rect.xmax + 1) - (rect.xmin - 1), - (rect.ymax + 1) - (rect.ymin - 1)); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); - /* draw grid elements */ - /* cross */ - immBegin(GPU_PRIM_LINES, 4); - - immVertex2f(pos, centerx - (diam * 0.5f) - 5, centery); - immVertex2f(pos, centerx + (diam * 0.5f) + 5, centery); - - immVertex2f(pos, centerx, centery - (diam * 0.5f) - 5); - immVertex2f(pos, centerx, centery + (diam * 0.5f) + 5); - - immEnd(); - - /* circles */ - for (int j = 0; j < 5; j++) { - const int increment = 15; - immBegin(GPU_PRIM_LINE_LOOP, (int)(360 / increment)); - for (int i = 0; i <= 360 - increment; i += increment) { - const float a = DEG2RADF((float)i); - const float r = (j + 1) * 0.1f; - immVertex2f(pos, polar_to_x(centerx, diam, r, a), polar_to_y(centery, diam, r, a)); - } - immEnd(); - } - /* skin tone line */ - immUniformColor4f(1.0f, 0.4f, 0.0f, 0.2f); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2f( - pos, polar_to_x(centerx, diam, 0.5f, skin_rad), polar_to_y(centery, diam, 0.5f, skin_rad)); - immVertex2f( - pos, polar_to_x(centerx, diam, 0.1f, skin_rad), polar_to_y(centery, diam, 0.1f, skin_rad)); - immEnd(); - - /* saturation points */ - for (int i = 0; i < 6; i++) { - vectorscope_draw_target(pos, centerx, centery, diam, colors[i]); - } - - if (scopes->ok && scopes->vecscope != NULL) { - /* pixel point cloud */ - const float col[3] = {alpha, alpha, alpha}; - - GPU_blend(GPU_BLEND_ADDITIVE); - GPU_point_size(1.0); - - GPU_matrix_push(); - GPU_matrix_translate_2f(centerx, centery); - GPU_matrix_scale_1f(diam); - - waveform_draw_one(scopes->vecscope, scopes->waveform_tot, col); - - GPU_matrix_pop(); - } - - immUnbindProgram(); - - /* Restore scissor test. */ - GPU_scissor(UNPACK4(scissor)); - /* outline */ - draw_scope_end(&rect); - - GPU_blend(GPU_BLEND_NONE); -} - -static void ui_draw_colorband_handle_tri_hlight( - uint pos, float x1, float y1, float halfwidth, float height) -{ - GPU_line_smooth(true); - - immBegin(GPU_PRIM_LINE_STRIP, 3); - immVertex2f(pos, x1 + halfwidth, y1); - immVertex2f(pos, x1, y1 + height); - immVertex2f(pos, x1 - halfwidth, y1); - immEnd(); - - GPU_line_smooth(false); -} - -static void ui_draw_colorband_handle_tri( - uint pos, float x1, float y1, float halfwidth, float height, bool fill) -{ - if (fill) { - GPU_polygon_smooth(true); - } - else { - GPU_line_smooth(true); - } - - immBegin(fill ? GPU_PRIM_TRIS : GPU_PRIM_LINE_LOOP, 3); - immVertex2f(pos, x1 + halfwidth, y1); - immVertex2f(pos, x1, y1 + height); - immVertex2f(pos, x1 - halfwidth, y1); - immEnd(); - - if (fill) { - GPU_polygon_smooth(false); - } - else { - GPU_line_smooth(false); - } -} - -static void ui_draw_colorband_handle_box( - uint pos, float x1, float y1, float x2, float y2, bool fill) -{ - immBegin(fill ? GPU_PRIM_TRI_FAN : GPU_PRIM_LINE_LOOP, 4); - immVertex2f(pos, x1, y1); - immVertex2f(pos, x1, y2); - immVertex2f(pos, x2, y2); - immVertex2f(pos, x2, y1); - immEnd(); -} - -static void ui_draw_colorband_handle(uint shdr_pos, - const rcti *rect, - float x, - const float rgb[3], - struct ColorManagedDisplay *display, - bool active) -{ - const float sizey = BLI_rcti_size_y(rect); - const float min_width = 3.0f; - float colf[3] = {UNPACK3(rgb)}; - - const float half_width = floorf(sizey / 3.5f); - const float height = half_width * 1.4f; - - float y1 = rect->ymin + (sizey * 0.16f); - const float y2 = rect->ymax; - - /* align to pixels */ - x = floorf(x + 0.5f); - y1 = floorf(y1 + 0.5f); - - if (active || half_width < min_width) { - immUnbindProgram(); - - immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); - - float viewport_size[4]; - GPU_viewport_size_get_f(viewport_size); - immUniform2f("viewport_size", viewport_size[2] / UI_DPI_FAC, viewport_size[3] / UI_DPI_FAC); - - immUniform1i("colors_len", 2); /* "advanced" mode */ - immUniformArray4fv( - "colors", (float *)(float[][4]){{0.8f, 0.8f, 0.8f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, 2); - immUniform1f("dash_width", active ? 4.0f : 2.0f); - immUniform1f("dash_factor", 0.5f); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(shdr_pos, x, y1); - immVertex2f(shdr_pos, x, y2); - immEnd(); - - immUnbindProgram(); - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* hide handles when zoomed out too far */ - if (half_width < min_width) { - return; - } - } - - /* shift handle down */ - y1 -= half_width; - - immUniformColor3ub(0, 0, 0); - ui_draw_colorband_handle_box( - shdr_pos, x - half_width, y1 - 1, x + half_width, y1 + height, false); - - /* draw all triangles blended */ - GPU_blend(GPU_BLEND_ALPHA); - - ui_draw_colorband_handle_tri(shdr_pos, x, y1 + height, half_width, half_width, true); - - if (active) { - immUniformColor3ub(196, 196, 196); - } - else { - immUniformColor3ub(96, 96, 96); - } - ui_draw_colorband_handle_tri(shdr_pos, x, y1 + height, half_width, half_width, true); - - if (active) { - immUniformColor3ub(255, 255, 255); - } - else { - immUniformColor3ub(128, 128, 128); - } - ui_draw_colorband_handle_tri_hlight( - shdr_pos, x, y1 + height - 1, (half_width - 1), (half_width - 1)); - - immUniformColor3ub(0, 0, 0); - ui_draw_colorband_handle_tri_hlight(shdr_pos, x, y1 + height, half_width, half_width); - - GPU_blend(GPU_BLEND_NONE); - - immUniformColor3ub(128, 128, 128); - ui_draw_colorband_handle_box( - shdr_pos, x - (half_width - 1), y1, x + (half_width - 1), y1 + height, true); - - if (display) { - IMB_colormanagement_scene_linear_to_display_v3(colf, display); - } - - immUniformColor3fv(colf); - ui_draw_colorband_handle_box( - shdr_pos, x - (half_width - 2), y1 + 1, x + (half_width - 2), y1 + height - 2, true); -} - -void ui_draw_but_COLORBAND(uiBut *but, const uiWidgetColors *UNUSED(wcol), const rcti *rect) -{ - struct ColorManagedDisplay *display = ui_block_cm_display_get(but->block); - uint pos_id, col_id; - - uiButColorBand *but_coba = (uiButColorBand *)but; - ColorBand *coba = (but_coba->edit_coba == NULL) ? (ColorBand *)but->poin : but_coba->edit_coba; - - if (coba == NULL) { - return; - } - - const float x1 = rect->xmin; - const float sizex = rect->xmax - x1; - const float sizey = BLI_rcti_size_y(rect); - const float sizey_solid = sizey * 0.25f; - const float y1 = rect->ymin; - - /* exit early if too narrow */ - if (sizex <= 0) { - return; - } - - GPUVertFormat *format = immVertexFormat(); - pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_CHECKER); - - /* Drawing the checkerboard. */ - const float checker_dark = UI_ALPHA_CHECKER_DARK / 255.0f; - const float checker_light = UI_ALPHA_CHECKER_LIGHT / 255.0f; - immUniform4f("color1", checker_dark, checker_dark, checker_dark, 1.0f); - immUniform4f("color2", checker_light, checker_light, checker_light, 1.0f); - immUniform1i("size", 8); - immRectf(pos_id, x1, y1, x1 + sizex, rect->ymax); - immUnbindProgram(); - - /* New format */ - format = immVertexFormat(); - pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - col_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); - - /* layer: color ramp */ - GPU_blend(GPU_BLEND_ALPHA); - - CBData *cbd = coba->data; - - float v1[2], v2[2]; - float colf[4] = {0, 0, 0, 0}; /* initialize in case the colorband isn't valid */ - - v1[1] = y1 + sizey_solid; - v2[1] = rect->ymax; - - immBegin(GPU_PRIM_TRI_STRIP, (sizex + 1) * 2); - for (int a = 0; a <= sizex; a++) { - const float pos = ((float)a) / sizex; - BKE_colorband_evaluate(coba, pos, colf); - if (display) { - IMB_colormanagement_scene_linear_to_display_v3(colf, display); - } - - v1[0] = v2[0] = x1 + a; - - immAttr4fv(col_id, colf); - immVertex2fv(pos_id, v1); - immVertex2fv(pos_id, v2); - } - immEnd(); - - /* layer: color ramp without alpha for reference when manipulating ramp properties */ - v1[1] = y1; - v2[1] = y1 + sizey_solid; - - immBegin(GPU_PRIM_TRI_STRIP, (sizex + 1) * 2); - for (int a = 0; a <= sizex; a++) { - const float pos = ((float)a) / sizex; - BKE_colorband_evaluate(coba, pos, colf); - if (display) { - IMB_colormanagement_scene_linear_to_display_v3(colf, display); - } - - v1[0] = v2[0] = x1 + a; - - immAttr4f(col_id, colf[0], colf[1], colf[2], 1.0f); - immVertex2fv(pos_id, v1); - immVertex2fv(pos_id, v2); - } - immEnd(); - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); - - /* New format */ - format = immVertexFormat(); - pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* layer: box outline */ - immUniformColor4f(0.0f, 0.0f, 0.0f, 1.0f); - imm_draw_box_wire_2d(pos_id, x1, y1, x1 + sizex, rect->ymax); - - /* layer: box outline */ - GPU_blend(GPU_BLEND_ALPHA); - immUniformColor4f(0.0f, 0.0f, 0.0f, 0.5f); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos_id, x1, y1); - immVertex2f(pos_id, x1 + sizex, y1); - immEnd(); - - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.25f); - - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos_id, x1, y1 - 1); - immVertex2f(pos_id, x1 + sizex, y1 - 1); - immEnd(); - - GPU_blend(GPU_BLEND_NONE); - - /* layer: draw handles */ - for (int a = 0; a < coba->tot; a++, cbd++) { - if (a != coba->cur) { - const float pos = x1 + cbd->pos * (sizex - 1) + 1; - ui_draw_colorband_handle(pos_id, rect, pos, &cbd->r, display, false); - } - } - - /* layer: active handle */ - if (coba->tot != 0) { - cbd = &coba->data[coba->cur]; - const float pos = x1 + cbd->pos * (sizex - 1) + 1; - ui_draw_colorband_handle(pos_id, rect, pos, &cbd->r, display, true); - } - - immUnbindProgram(); -} - -void ui_draw_but_UNITVEC(uiBut *but, const uiWidgetColors *wcol, const rcti *rect) -{ - /* sphere color */ - const float diffuse[3] = {1.0f, 1.0f, 1.0f}; - float light[3]; - const float size = 0.5f * min_ff(BLI_rcti_size_x(rect), BLI_rcti_size_y(rect)); - - /* backdrop */ - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_3ub_alpha( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = rect->ymin, - .ymax = rect->ymax, - }, - true, - 5.0f, - wcol->inner, - 255); - - GPU_face_culling(GPU_CULL_BACK); - - /* setup lights */ - ui_but_v3_get(but, light); - - /* transform to button */ - GPU_matrix_push(); - - const bool use_project_matrix = (size >= -GPU_MATRIX_ORTHO_CLIP_NEAR_DEFAULT); - if (use_project_matrix) { - GPU_matrix_push_projection(); - GPU_matrix_ortho_set_z(-size, size); - } - - GPU_matrix_translate_2f(rect->xmin + 0.5f * BLI_rcti_size_x(rect), - rect->ymin + 0.5f * BLI_rcti_size_y(rect)); - GPU_matrix_scale_1f(size); - - GPUBatch *sphere = GPU_batch_preset_sphere(2); - GPU_batch_program_set_builtin(sphere, GPU_SHADER_SIMPLE_LIGHTING); - GPU_batch_uniform_4f(sphere, "color", diffuse[0], diffuse[1], diffuse[2], 1.0f); - GPU_batch_uniform_3fv(sphere, "light", light); - GPU_batch_draw(sphere); - - /* Restore. */ - GPU_face_culling(GPU_CULL_NONE); - - /* AA circle */ - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor3ubv(wcol->inner); - - GPU_blend(GPU_BLEND_ALPHA); - GPU_line_smooth(true); - imm_draw_circle_wire_2d(pos, 0.0f, 0.0f, 1.0f, 32); - GPU_blend(GPU_BLEND_NONE); - GPU_line_smooth(false); - - if (use_project_matrix) { - GPU_matrix_pop_projection(); - } - - /* matrix after circle */ - GPU_matrix_pop(); - - immUnbindProgram(); -} - -static void ui_draw_but_curve_grid(const uint pos, - const rcti *rect, - const float zoom_x, - const float zoom_y, - const float offset_x, - const float offset_y, - const float step) -{ - const float start_x = (ceilf(offset_x / step) * step - offset_x) * zoom_x + rect->xmin; - const float start_y = (ceilf(offset_y / step) * step - offset_y) * zoom_y + rect->ymin; - - const int line_count_x = ceilf((rect->xmax - start_x) / (step * zoom_x)); - const int line_count_y = ceilf((rect->ymax - start_y) / (step * zoom_y)); - - if (line_count_x + line_count_y == 0) { - return; - } - - immBegin(GPU_PRIM_LINES, (line_count_x + line_count_y) * 2); - for (int i = 0; i < line_count_x; i++) { - const float x = start_x + i * step * zoom_x; - immVertex2f(pos, x, rect->ymin); - immVertex2f(pos, x, rect->ymax); - } - for (int i = 0; i < line_count_y; i++) { - const float y = start_y + i * step * zoom_y; - immVertex2f(pos, rect->xmin, y); - immVertex2f(pos, rect->xmax, y); - } - immEnd(); -} - -static void gl_shaded_color_get(const uchar color[3], int shade, uchar r_color[3]) -{ - r_color[0] = color[0] - shade > 0 ? color[0] - shade : 0; - r_color[1] = color[1] - shade > 0 ? color[1] - shade : 0; - r_color[2] = color[2] - shade > 0 ? color[2] - shade : 0; -} - -static void gl_shaded_color_get_fl(const uchar *color, int shade, float r_color[3]) -{ - uchar color_shaded[3]; - gl_shaded_color_get(color, shade, color_shaded); - rgb_uchar_to_float(r_color, color_shaded); -} - -static void gl_shaded_color(const uchar *color, int shade) -{ - uchar color_shaded[3]; - gl_shaded_color_get(color, shade, color_shaded); - immUniformColor3ubv(color_shaded); -} - -void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol, const rcti *rect) -{ - uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; - CurveMapping *cumap = (but_cumap->edit_cumap == NULL) ? (CurveMapping *)but->poin : - but_cumap->edit_cumap; - - const float clip_size_x = BLI_rctf_size_x(&cumap->curr); - const float clip_size_y = BLI_rctf_size_y(&cumap->curr); - - /* zero-sized curve */ - if (clip_size_x == 0.0f || clip_size_y == 0.0f) { - return; - } - - /* calculate offset and zoom */ - const float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / clip_size_x; - const float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / clip_size_y; - const float offsx = cumap->curr.xmin - (1.0f / zoomx); - const float offsy = cumap->curr.ymin - (1.0f / zoomy); - - /* exit early if too narrow */ - if (zoomx == 0.0f) { - return; - } - - CurveMap *cuma = &cumap->cm[cumap->cur]; - - /* need scissor test, curve can draw outside of boundary */ - int scissor[4]; - GPU_scissor_get(scissor); - rcti scissor_new = { - .xmin = rect->xmin, - .ymin = rect->ymin, - .xmax = rect->xmax, - .ymax = rect->ymax, - }; - const rcti scissor_region = {0, region->winx, 0, region->winy}; - BLI_rcti_isect(&scissor_new, &scissor_region, &scissor_new); - GPU_scissor(scissor_new.xmin, - scissor_new.ymin, - BLI_rcti_size_x(&scissor_new), - BLI_rcti_size_y(&scissor_new)); - - /* Do this first to not mess imm context */ - if (but_cumap->gradient_type == UI_GRAD_H) { - /* magic trigger for curve backgrounds */ - const float col[3] = {0.0f, 0.0f, 0.0f}; /* dummy arg */ - - rcti grid = { - .xmin = rect->xmin + zoomx * (-offsx), - .xmax = grid.xmin + zoomx, - .ymin = rect->ymin + zoomy * (-offsy), - .ymax = grid.ymin + zoomy, - }; - - ui_draw_gradient(&grid, col, UI_GRAD_H, 1.0f); - } - - GPU_line_width(1.0f); - - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* backdrop */ - float color_backdrop[4] = {0, 0, 0, 1}; - - if (but_cumap->gradient_type == UI_GRAD_H) { - /* grid, hsv uses different grid */ - GPU_blend(GPU_BLEND_ALPHA); - ARRAY_SET_ITEMS(color_backdrop, 0, 0, 0, 48.0 / 255.0); - immUniformColor4fv(color_backdrop); - ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.1666666f); - GPU_blend(GPU_BLEND_NONE); - } - else { - if (cumap->flag & CUMA_DO_CLIP) { - gl_shaded_color_get_fl(wcol->inner, -20, color_backdrop); - immUniformColor3fv(color_backdrop); - immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - immUniformColor3ubv(wcol->inner); - immRectf(pos, - rect->xmin + zoomx * (cumap->clipr.xmin - offsx), - rect->ymin + zoomy * (cumap->clipr.ymin - offsy), - rect->xmin + zoomx * (cumap->clipr.xmax - offsx), - rect->ymin + zoomy * (cumap->clipr.ymax - offsy)); - } - else { - rgb_uchar_to_float(color_backdrop, wcol->inner); - immUniformColor3fv(color_backdrop); - immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - } - - /* grid, every 0.25 step */ - gl_shaded_color(wcol->inner, -16); - ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f); - /* grid, every 1.0 step */ - gl_shaded_color(wcol->inner, -24); - ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f); - /* axes */ - gl_shaded_color(wcol->inner, -50); - immBegin(GPU_PRIM_LINES, 4); - immVertex2f(pos, rect->xmin, rect->ymin + zoomy * (-offsy)); - immVertex2f(pos, rect->xmax, rect->ymin + zoomy * (-offsy)); - immVertex2f(pos, rect->xmin + zoomx * (-offsx), rect->ymin); - immVertex2f(pos, rect->xmin + zoomx * (-offsx), rect->ymax); - immEnd(); - } - - /* cfra option */ - /* XXX 2.48 */ -#if 0 - if (cumap->flag & CUMA_DRAW_CFRA) { - immUniformColor3ub(0x60, 0xc0, 0x40); - immBegin(GPU_PRIM_LINES, 2); - immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[0] - offsx), rect->ymin); - immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[0] - offsx), rect->ymax); - immEnd(); - } -#endif - /* sample option */ - - if (cumap->flag & CUMA_DRAW_SAMPLE) { - immBegin(GPU_PRIM_LINES, 2); /* will draw one of the following 3 lines */ - if (but_cumap->gradient_type == UI_GRAD_H) { - float tsample[3]; - float hsv[3]; - linearrgb_to_srgb_v3_v3(tsample, cumap->sample); - rgb_to_hsv_v(tsample, hsv); - immUniformColor3ub(240, 240, 240); - - immVertex2f(pos, rect->xmin + zoomx * (hsv[0] - offsx), rect->ymin); - immVertex2f(pos, rect->xmin + zoomx * (hsv[0] - offsx), rect->ymax); - } - else if (cumap->cur == 3) { - const float lum = IMB_colormanagement_get_luminance(cumap->sample); - immUniformColor3ub(240, 240, 240); - - immVertex2f(pos, rect->xmin + zoomx * (lum - offsx), rect->ymin); - immVertex2f(pos, rect->xmin + zoomx * (lum - offsx), rect->ymax); - } - else { - if (cumap->cur == 0) { - immUniformColor3ub(240, 100, 100); - } - else if (cumap->cur == 1) { - immUniformColor3ub(100, 240, 100); - } - else { - immUniformColor3ub(100, 100, 240); - } - - immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[cumap->cur] - offsx), rect->ymin); - immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[cumap->cur] - offsx), rect->ymax); - } - immEnd(); - } - immUnbindProgram(); - - if (cuma->table == NULL) { - BKE_curvemapping_changed(cumap, false); - } - - CurveMapPoint *cmp = cuma->table; - rctf line_range; - - /* First curve point. */ - if ((cumap->flag & CUMA_EXTEND_EXTRAPOLATE) == 0) { - line_range.xmin = rect->xmin; - line_range.ymin = rect->ymin + zoomy * (cmp[0].y - offsy); - } - else { - line_range.xmin = rect->xmin + zoomx * (cmp[0].x - offsx + cuma->ext_in[0]); - line_range.ymin = rect->ymin + zoomy * (cmp[0].y - offsy + cuma->ext_in[1]); - } - /* Last curve point. */ - if ((cumap->flag & CUMA_EXTEND_EXTRAPOLATE) == 0) { - line_range.xmax = rect->xmax; - line_range.ymax = rect->ymin + zoomy * (cmp[CM_TABLE].y - offsy); - } - else { - line_range.xmax = rect->xmin + zoomx * (cmp[CM_TABLE].x - offsx - cuma->ext_out[0]); - line_range.ymax = rect->ymin + zoomy * (cmp[CM_TABLE].y - offsy - cuma->ext_out[1]); - } - - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Curve filled. */ - immUniformColor3ubvAlpha(wcol->item, 128); - immBegin(GPU_PRIM_TRI_STRIP, (CM_TABLE * 2 + 2) + 4); - immVertex2f(pos, line_range.xmin, rect->ymin); - immVertex2f(pos, line_range.xmin, line_range.ymin); - for (int a = 0; a <= CM_TABLE; a++) { - const float fx = rect->xmin + zoomx * (cmp[a].x - offsx); - const float fy = rect->ymin + zoomy * (cmp[a].y - offsy); - immVertex2f(pos, fx, rect->ymin); - immVertex2f(pos, fx, fy); - } - immVertex2f(pos, line_range.xmax, rect->ymin); - immVertex2f(pos, line_range.xmax, line_range.ymax); - immEnd(); - - /* Curve line. */ - GPU_line_width(1.0f); - immUniformColor3ubvAlpha(wcol->item, 255); - GPU_line_smooth(true); - immBegin(GPU_PRIM_LINE_STRIP, (CM_TABLE + 1) + 2); - immVertex2f(pos, line_range.xmin, line_range.ymin); - for (int a = 0; a <= CM_TABLE; a++) { - const float fx = rect->xmin + zoomx * (cmp[a].x - offsx); - const float fy = rect->ymin + zoomy * (cmp[a].y - offsy); - immVertex2f(pos, fx, fy); - } - immVertex2f(pos, line_range.xmax, line_range.ymax); - immEnd(); - - /* Reset state for fill & line. */ - GPU_line_smooth(false); - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); - - /* The points, use aspect to make them visible on edges. */ - format = immVertexFormat(); - pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - - /* Calculate vertex colors based on text theme. */ - float color_vert[4], color_vert_select[4]; - UI_GetThemeColor4fv(TH_TEXT_HI, color_vert); - UI_GetThemeColor4fv(TH_TEXT, color_vert_select); - if (len_squared_v3v3(color_vert, color_vert_select) < 0.1f) { - interp_v3_v3v3(color_vert, color_vert_select, color_backdrop, 0.75f); - } - if (len_squared_v3(color_vert) > len_squared_v3(color_vert_select)) { - /* Ensure brightest text color is used for selection. */ - swap_v3_v3(color_vert, color_vert_select); - } - - cmp = cuma->curve; - GPU_point_size(max_ff(1.0f, min_ff(UI_DPI_FAC / but->block->aspect * 4.0f, 4.0f))); - immBegin(GPU_PRIM_POINTS, cuma->totpoint); - for (int a = 0; a < cuma->totpoint; a++) { - const float fx = rect->xmin + zoomx * (cmp[a].x - offsx); - const float fy = rect->ymin + zoomy * (cmp[a].y - offsy); - immAttr4fv(col, (cmp[a].flag & CUMA_SELECT) ? color_vert_select : color_vert); - immVertex2f(pos, fx, fy); - } - immEnd(); - immUnbindProgram(); - - /* Restore scissor-test. */ - GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); - - /* outline */ - format = immVertexFormat(); - pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor3ubv(wcol->outline); - imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - - immUnbindProgram(); -} - -/** - * Helper for #ui_draw_but_CURVEPROFILE. Used to tell whether to draw a control point's handles. - */ -static bool point_draw_handles(CurveProfilePoint *point) -{ - return (point->flag & PROF_SELECT && - (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) || - ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT); -} - -/** - * Draws the curve profile widget. Somewhat similar to ui_draw_but_CURVE. - */ -void ui_draw_but_CURVEPROFILE(ARegion *region, - uiBut *but, - const uiWidgetColors *wcol, - const rcti *rect) -{ - float fx, fy; - - uiButCurveProfile *but_profile = (uiButCurveProfile *)but; - CurveProfile *profile = (but_profile->edit_profile == NULL) ? (CurveProfile *)but->poin : - but_profile->edit_profile; - - /* Calculate offset and zoom. */ - const float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / BLI_rctf_size_x(&profile->view_rect); - const float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / BLI_rctf_size_y(&profile->view_rect); - const float offsx = profile->view_rect.xmin - (1.0f / zoomx); - const float offsy = profile->view_rect.ymin - (1.0f / zoomy); - - /* Exit early if too narrow. */ - if (zoomx == 0.0f) { - return; - } - - /* Test needed because path can draw outside of boundary. */ - int scissor[4]; - GPU_scissor_get(scissor); - rcti scissor_new = { - .xmin = rect->xmin, - .ymin = rect->ymin, - .xmax = rect->xmax, - .ymax = rect->ymax, - }; - const rcti scissor_region = {0, region->winx, 0, region->winy}; - BLI_rcti_isect(&scissor_new, &scissor_region, &scissor_new); - GPU_scissor(scissor_new.xmin, - scissor_new.ymin, - BLI_rcti_size_x(&scissor_new), - BLI_rcti_size_y(&scissor_new)); - - GPU_line_width(1.0f); - - GPUVertFormat *format = immVertexFormat(); - uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* Draw the backdrop. */ - float color_backdrop[4] = {0, 0, 0, 1}; - if (profile->flag & PROF_USE_CLIP) { - gl_shaded_color_get_fl((uchar *)wcol->inner, -20, color_backdrop); - immUniformColor3fv(color_backdrop); - immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - immUniformColor3ubv((uchar *)wcol->inner); - immRectf(pos, - rect->xmin + zoomx * (profile->clip_rect.xmin - offsx), - rect->ymin + zoomy * (profile->clip_rect.ymin - offsy), - rect->xmin + zoomx * (profile->clip_rect.xmax - offsx), - rect->ymin + zoomy * (profile->clip_rect.ymax - offsy)); - } - else { - rgb_uchar_to_float(color_backdrop, (uchar *)wcol->inner); - immUniformColor3fv(color_backdrop); - immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - } - - /* 0.25 step grid. */ - gl_shaded_color((uchar *)wcol->inner, -16); - ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f); - /* 1.0 step grid. */ - gl_shaded_color((uchar *)wcol->inner, -24); - ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f); - - /* Draw the path's fill. */ - if (profile->table == NULL) { - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - } - CurveProfilePoint *pts = profile->table; - /* Also add the last points on the right and bottom edges to close off the fill polygon. */ - const bool add_left_tri = profile->view_rect.xmin < 0.0f; - const bool add_bottom_tri = profile->view_rect.ymin < 0.0f; - uint tot_points = (uint)PROF_TABLE_LEN(profile->path_len) + 1 + add_left_tri + add_bottom_tri; - const uint tot_triangles = tot_points - 2; - - /* Create array of the positions of the table's points. */ - float(*table_coords)[2] = MEM_mallocN(sizeof(*table_coords) * tot_points, "table x coords"); - for (uint i = 0; i < (uint)PROF_TABLE_LEN(profile->path_len); - i++) { /* Only add the points from the table here. */ - table_coords[i][0] = pts[i].x; - table_coords[i][1] = pts[i].y; - } - /* Using some extra margin (-1.0f) for the coordinates used to complete the polygon - * avoids the profile line crossing itself in some common situations, which can lead to - * incorrect triangulation. See T841183. */ - if (add_left_tri && add_bottom_tri) { - /* Add left side, bottom left corner, and bottom side points. */ - table_coords[tot_points - 3][0] = profile->view_rect.xmin - 1.0f; - table_coords[tot_points - 3][1] = 1.0f; - table_coords[tot_points - 2][0] = profile->view_rect.xmin - 1.0f; - table_coords[tot_points - 2][1] = profile->view_rect.ymin - 1.0f; - table_coords[tot_points - 1][0] = 1.0f; - table_coords[tot_points - 1][1] = profile->view_rect.ymin - 1.0f; - } - else if (add_left_tri) { - /* Add the left side and bottom left corner points. */ - table_coords[tot_points - 2][0] = profile->view_rect.xmin - 1.0f; - table_coords[tot_points - 2][1] = 1.0f; - table_coords[tot_points - 1][0] = profile->view_rect.xmin - 1.0f; - table_coords[tot_points - 1][1] = -1.0f; - } - else if (add_bottom_tri) { - /* Add the bottom side and bottom left corner points. */ - table_coords[tot_points - 2][0] = -1.0f; - table_coords[tot_points - 2][1] = profile->view_rect.ymin - 1.0f; - table_coords[tot_points - 1][0] = 1.0f; - table_coords[tot_points - 1][1] = profile->view_rect.ymin - 1.0f; - } - else { - /* Just add the bottom corner point. Side points would be redundant anyway. */ - table_coords[tot_points - 1][0] = -1.0f; - table_coords[tot_points - 1][1] = -1.0f; - } - - /* Calculate the table point indices of the triangles for the profile's fill. */ - uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, "return tri indices"); - BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices); - - /* Draw the triangles for the profile fill. */ - immUniformColor3ubvAlpha((const uchar *)wcol->item, 128); - GPU_blend(GPU_BLEND_ALPHA); - GPU_polygon_smooth(false); - immBegin(GPU_PRIM_TRIS, 3 * tot_triangles); - for (uint i = 0; i < tot_triangles; i++) { - for (uint j = 0; j < 3; j++) { - uint *tri = tri_indices[i]; - fx = rect->xmin + zoomx * (table_coords[tri[j]][0] - offsx); - fy = rect->ymin + zoomy * (table_coords[tri[j]][1] - offsy); - immVertex2f(pos, fx, fy); - } - } - immEnd(); - MEM_freeN(tri_indices); - - /* Draw the profile's path so the edge stands out a bit. */ - tot_points -= (add_left_tri + add_left_tri); - GPU_line_width(1.0f); - immUniformColor3ubvAlpha((const uchar *)wcol->item, 255); - GPU_line_smooth(true); - immBegin(GPU_PRIM_LINE_STRIP, tot_points - 1); - for (uint i = 0; i < tot_points - 1; i++) { - fx = rect->xmin + zoomx * (table_coords[i][0] - offsx); - fy = rect->ymin + zoomy * (table_coords[i][1] - offsy); - immVertex2f(pos, fx, fy); - } - immEnd(); - MEM_freeN(table_coords); - - /* Draw the handles for the selected control points. */ - pts = profile->path; - tot_points = (uint)profile->path_len; - int selected_free_points = 0; - for (uint i = 0; i < tot_points; i++) { - if (point_draw_handles(&pts[i])) { - selected_free_points++; - } - } - /* Draw the lines to the handles from the points. */ - if (selected_free_points > 0) { - GPU_line_width(1.0f); - gl_shaded_color((uchar *)wcol->inner, -24); - GPU_line_smooth(true); - immBegin(GPU_PRIM_LINES, selected_free_points * 4); - float ptx, pty; - for (uint i = 0; i < tot_points; i++) { - if (point_draw_handles(&pts[i])) { - ptx = rect->xmin + zoomx * (pts[i].x - offsx); - pty = rect->ymin + zoomy * (pts[i].y - offsy); - - fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx); - fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy); - immVertex2f(pos, ptx, pty); - immVertex2f(pos, fx, fy); - - fx = rect->xmin + zoomx * (pts[i].h2_loc[0] - offsx); - fy = rect->ymin + zoomy * (pts[i].h2_loc[1] - offsy); - immVertex2f(pos, ptx, pty); - immVertex2f(pos, fx, fy); - } - } - immEnd(); - } - immUnbindProgram(); - - /* New GPU instructions for control points and sampled points. */ - format = immVertexFormat(); - pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - - /* Calculate vertex colors based on text theme. */ - float color_vert[4], color_vert_select[4], color_sample[4]; - UI_GetThemeColor4fv(TH_TEXT_HI, color_vert); - UI_GetThemeColor4fv(TH_TEXT, color_vert_select); - color_sample[0] = (float)wcol->item[0] / 255.0f; - color_sample[1] = (float)wcol->item[1] / 255.0f; - color_sample[2] = (float)wcol->item[2] / 255.0f; - color_sample[3] = (float)wcol->item[3] / 255.0f; - if (len_squared_v3v3(color_vert, color_vert_select) < 0.1f) { - interp_v3_v3v3(color_vert, color_vert_select, color_backdrop, 0.75f); - } - if (len_squared_v3(color_vert) > len_squared_v3(color_vert_select)) { - /* Ensure brightest text color is used for selection. */ - swap_v3_v3(color_vert, color_vert_select); - } - - /* Draw the control points. */ - GPU_line_smooth(false); - GPU_blend(GPU_BLEND_NONE); - GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f))); - immBegin(GPU_PRIM_POINTS, tot_points); - for (uint i = 0; i < tot_points; i++) { - fx = rect->xmin + zoomx * (pts[i].x - offsx); - fy = rect->ymin + zoomy * (pts[i].y - offsy); - immAttr4fv(col, (pts[i].flag & PROF_SELECT) ? color_vert_select : color_vert); - immVertex2f(pos, fx, fy); - } - immEnd(); - - /* Draw the handle points. */ - if (selected_free_points > 0) { - GPU_line_smooth(false); - GPU_blend(GPU_BLEND_NONE); - GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 4.0f, 4.0f))); - immBegin(GPU_PRIM_POINTS, selected_free_points * 2); - for (uint i = 0; i < tot_points; i++) { - if (point_draw_handles(&pts[i])) { - fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx); - fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy); - immAttr4fv(col, (pts[i].flag & PROF_H1_SELECT) ? color_vert_select : color_vert); - immVertex2f(pos, fx, fy); - - fx = rect->xmin + zoomx * (pts[i].h2_loc[0] - offsx); - fy = rect->ymin + zoomy * (pts[i].h2_loc[1] - offsy); - immAttr4fv(col, (pts[i].flag & PROF_H2_SELECT) ? color_vert_select : color_vert); - immVertex2f(pos, fx, fy); - } - } - immEnd(); - } - - /* Draw the sampled points in addition to the control points if they have been created */ - pts = profile->segments; - tot_points = (uint)profile->segments_len; - if (tot_points > 0 && pts) { - GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 3.0f, 3.0f))); - immBegin(GPU_PRIM_POINTS, tot_points); - for (uint i = 0; i < tot_points; i++) { - fx = rect->xmin + zoomx * (pts[i].x - offsx); - fy = rect->ymin + zoomy * (pts[i].y - offsy); - immAttr4fv(col, color_sample); - immVertex2f(pos, fx, fy); - } - immEnd(); - } - immUnbindProgram(); - - /* Restore scissor-test. */ - GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); - - /* Outline */ - format = immVertexFormat(); - pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor3ubv((const uchar *)wcol->outline); - imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - immUnbindProgram(); -} - -void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(region), - uiBut *but, - const uiWidgetColors *UNUSED(wcol), - const rcti *recti) -{ - bool ok = false; - MovieClipScopes *scopes = (MovieClipScopes *)but->poin; - - rctf rect = { - .xmin = (float)recti->xmin + 1, - .xmax = (float)recti->xmax - 1, - .ymin = (float)recti->ymin + 1, - .ymax = (float)recti->ymax - 1, - }; - - const int width = BLI_rctf_size_x(&rect) + 1; - const int height = BLI_rctf_size_y(&rect); - - GPU_blend(GPU_BLEND_ALPHA); - - /* need scissor test, preview image can draw outside of boundary */ - int scissor[4]; - GPU_scissor_get(scissor); - GPU_scissor((rect.xmin - 1), - (rect.ymin - 1), - (rect.xmax + 1) - (rect.xmin - 1), - (rect.ymax + 1) - (rect.ymin - 1)); - - if (scopes->track_disabled) { - const float color[4] = {0.7f, 0.3f, 0.3f, 0.3f}; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect.xmin - 1, - .xmax = rect.xmax + 1, - .ymin = rect.ymin, - .ymax = rect.ymax + 1, - }, - true, - 3.0f, - color); - - ok = true; - } - else if ((scopes->track_search) && - ((!scopes->track_preview) || - (scopes->track_preview->x != width || scopes->track_preview->y != height))) { - if (scopes->track_preview) { - IMB_freeImBuf(scopes->track_preview); - } - - ImBuf *tmpibuf = BKE_tracking_sample_pattern(scopes->frame_width, - scopes->frame_height, - scopes->track_search, - scopes->track, - &scopes->undist_marker, - true, - scopes->use_track_mask, - width, - height, - scopes->track_pos); - if (tmpibuf) { - if (tmpibuf->rect_float) { - IMB_rect_from_float(tmpibuf); - } - - if (tmpibuf->rect) { - scopes->track_preview = tmpibuf; - } - else { - IMB_freeImBuf(tmpibuf); - } - } - } - - if (!ok && scopes->track_preview) { - GPU_matrix_push(); - - /* draw content of pattern area */ - GPU_scissor(rect.xmin, rect.ymin, scissor[2], scissor[3]); - - if (width > 0 && height > 0) { - ImBuf *drawibuf = scopes->track_preview; - float col_sel[4], col_outline[4]; - - if (scopes->use_track_mask) { - const float color[4] = {0.0f, 0.0f, 0.0f, 0.3f}; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect.xmin - 1, - .xmax = rect.xmax + 1, - .ymin = rect.ymin, - .ymax = rect.ymax + 1, - }, - true, - 3.0f, - color); - } - - IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); - immDrawPixelsTex(&state, - rect.xmin, - rect.ymin + 1, - drawibuf->x, - drawibuf->y, - GPU_RGBA8, - true, - drawibuf->rect, - 1.0f, - 1.0f, - NULL); - - /* draw cross for pixel position */ - GPU_matrix_translate_2f(rect.xmin + scopes->track_pos[0], rect.ymin + scopes->track_pos[1]); - GPU_scissor(rect.xmin, rect.ymin, BLI_rctf_size_x(&rect), BLI_rctf_size_y(&rect)); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); - - UI_GetThemeColor4fv(TH_SEL_MARKER, col_sel); - UI_GetThemeColor4fv(TH_MARKER_OUTLINE, col_outline); - - /* Do stipple cross with geometry */ - immBegin(GPU_PRIM_LINES, 7 * 2 * 2); - const float pos_sel[8] = {-10.0f, -7.0f, -4.0f, -1.0f, 2.0f, 5.0f, 8.0f, 11.0f}; - for (int axe = 0; axe < 2; axe++) { - for (int i = 0; i < 7; i++) { - const float x1 = pos_sel[i] * (1 - axe); - const float y1 = pos_sel[i] * axe; - const float x2 = pos_sel[i + 1] * (1 - axe); - const float y2 = pos_sel[i + 1] * axe; - - if (i % 2 == 1) { - immAttr4fv(col, col_sel); - } - else { - immAttr4fv(col, col_outline); - } - - immVertex2f(pos, x1, y1); - immVertex2f(pos, x2, y2); - } - } - immEnd(); - - immUnbindProgram(); - } - - GPU_matrix_pop(); - - ok = true; - } - - if (!ok) { - const float color[4] = {0.0f, 0.0f, 0.0f, 0.3f}; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect.xmin - 1, - .xmax = rect.xmax + 1, - .ymin = rect.ymin, - .ymax = rect.ymax + 1, - }, - true, - 3.0f, - color); - } - - /* Restore scissor test. */ - GPU_scissor(UNPACK4(scissor)); - /* outline */ - draw_scope_end(&rect); - - GPU_blend(GPU_BLEND_NONE); -} - -/* ****************************************************** */ - -/* TODO: high quality UI drop shadows using GLSL shader and single draw call - * would replace / modify the following 3 functions - merwin - */ - -static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, uchar alpha) -{ - /** - *
-   *          v1-_
-   *          |   -_v2
-   *          |     |
-   *          |     |
-   *          |     |
-   * v7_______v3____v4
-   * \        |     /
-   *  \       |   _v5
-   *  v8______v6_-
-   * 
- */ - const float v1[2] = {rect->xmax, rect->ymax - 0.3f * shadsize}; - const float v2[2] = {rect->xmax + shadsize, rect->ymax - 0.75f * shadsize}; - const float v3[2] = {rect->xmax, rect->ymin}; - const float v4[2] = {rect->xmax + shadsize, rect->ymin}; - - const float v5[2] = {rect->xmax + 0.7f * shadsize, rect->ymin - 0.7f * shadsize}; - - const float v6[2] = {rect->xmax, rect->ymin - shadsize}; - const float v7[2] = {rect->xmin + 0.3f * shadsize, rect->ymin}; - const float v8[2] = {rect->xmin + 0.5f * shadsize, rect->ymin - shadsize}; - - /* right quad */ - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v3); - immVertex2fv(pos, v1); - immAttr4ub(color, 0, 0, 0, 0); - immVertex2fv(pos, v2); - - immVertex2fv(pos, v2); - immVertex2fv(pos, v4); - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v3); - - /* corner shape */ - /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */ - immVertex2fv(pos, v3); - immAttr4ub(color, 0, 0, 0, 0); - immVertex2fv(pos, v4); - immVertex2fv(pos, v5); - - immVertex2fv(pos, v5); - immVertex2fv(pos, v6); - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v3); - - /* bottom quad */ - /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */ - immVertex2fv(pos, v3); - immAttr4ub(color, 0, 0, 0, 0); - immVertex2fv(pos, v6); - immVertex2fv(pos, v8); - - immVertex2fv(pos, v8); - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v7); - immVertex2fv(pos, v3); -} - -void UI_draw_box_shadow(const rctf *rect, uchar alpha) -{ - GPU_blend(GPU_BLEND_ALPHA); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint color = GPU_vertformat_attr_add( - format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - - immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); - - immBegin(GPU_PRIM_TRIS, 54); - - /* accumulated outline boxes to make shade not linear, is more pleasant */ - ui_shadowbox(rect, pos, color, 11.0, (20 * alpha) >> 8); - ui_shadowbox(rect, pos, color, 7.0, (40 * alpha) >> 8); - ui_shadowbox(rect, pos, color, 5.0, (80 * alpha) >> 8); - - immEnd(); - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); -} - -void ui_draw_dropshadow( - const rctf *rct, float radius, float aspect, float alpha, int UNUSED(select)) -{ - float rad; - - if (radius > (BLI_rctf_size_y(rct) - 10.0f) * 0.5f) { - rad = (BLI_rctf_size_y(rct) - 10.0f) * 0.5f; - } - else { - rad = radius; - } - - int a, i = 12; -#if 0 - if (select) { - a = i * aspect; /* same as below */ - } - else -#endif - { - a = i * aspect; - } - - GPU_blend(GPU_BLEND_ALPHA); - const float dalpha = alpha * 2.0f / 255.0f; - float calpha = dalpha; - float visibility = 1.0f; - for (; i--;) { - /* alpha ranges from 2 to 20 or so */ -#if 0 /* Old Method (pre 2.8) */ - float color[4] = {0.0f, 0.0f, 0.0f, calpha}; - UI_draw_roundbox_4fv( - true, rct->xmin - a, rct->ymin - a, rct->xmax + a, rct->ymax - 10.0f + a, rad + a, color); -#endif - /* Compute final visibility to match old method result. */ - /* TODO we could just find a better fit function inside the shader instead of this. */ - visibility = visibility * (1.0f - calpha); - calpha += dalpha; - } - - uiWidgetBaseParameters widget_params = { - .recti.xmin = rct->xmin, - .recti.ymin = rct->ymin, - .recti.xmax = rct->xmax, - .recti.ymax = rct->ymax - 10.0f, - .rect.xmin = rct->xmin - a, - .rect.ymin = rct->ymin - a, - .rect.xmax = rct->xmax + a, - .rect.ymax = rct->ymax - 10.0f + a, - .radi = rad, - .rad = rad + a, - .round_corners[0] = (roundboxtype & UI_CNR_BOTTOM_LEFT) ? 1.0f : 0.0f, - .round_corners[1] = (roundboxtype & UI_CNR_BOTTOM_RIGHT) ? 1.0f : 0.0f, - .round_corners[2] = (roundboxtype & UI_CNR_TOP_RIGHT) ? 1.0f : 0.0f, - .round_corners[3] = (roundboxtype & UI_CNR_TOP_LEFT) ? 1.0f : 0.0f, - .alpha_discard = 1.0f, - }; - - GPUBatch *batch = ui_batch_roundbox_shadow_get(); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_WIDGET_SHADOW); - GPU_batch_uniform_4fv_array(batch, "parameters", 4, (const float(*)[4]) & widget_params); - GPU_batch_uniform_1f(batch, "alpha", 1.0f - visibility); - GPU_batch_draw(batch); - - /* outline emphasis */ - const float color[4] = {0.0f, 0.0f, 0.0f, 0.4f}; - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin - 0.5f, - .xmax = rct->xmax + 0.5f, - .ymin = rct->ymin - 0.5f, - .ymax = rct->ymax + 0.5f, - }, - false, - radius + 0.5f, - color); - - GPU_blend(GPU_BLEND_NONE); -} diff --git a/source/blender/editors/interface/interface_draw.cc b/source/blender/editors/interface/interface_draw.cc new file mode 100644 index 00000000000..5dde7fa5921 --- /dev/null +++ b/source/blender/editors/interface/interface_draw.cc @@ -0,0 +1,2382 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include + +#include "DNA_color_types.h" +#include "DNA_curve_types.h" +#include "DNA_curveprofile_types.h" +#include "DNA_movieclip_types.h" +#include "DNA_screen_types.h" + +#include "BLI_math.h" +#include "BLI_polyfill_2d.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "MEM_guardedalloc.h" + +#include "BKE_colorband.h" +#include "BKE_colortools.h" +#include "BKE_curveprofile.h" +#include "BKE_node.h" +#include "BKE_tracking.h" + +#include "IMB_colormanagement.h" +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +#include "BIF_glutil.h" + +#include "BLF_api.h" + +#include "GPU_batch.h" +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_immediate_util.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "UI_interface.h" + +/* own include */ +#include "interface_intern.h" + +static int roundboxtype = UI_CNR_ALL; + +void UI_draw_roundbox_corner_set(int type) +{ + /* Not sure the roundbox function is the best place to change this + * if this is undone, it's not that big a deal, only makes curves edges + * square for the */ + roundboxtype = type; +} + +#if 0 /* unused */ +int UI_draw_roundbox_corner_get(void) +{ + return roundboxtype; +} +#endif + +void UI_draw_roundbox_4fv_ex(const rctf *rect, + const float inner1[4], + const float inner2[4], + float shade_dir, + const float outline[4], + float outline_width, + float rad) +{ + /* WATCH: This is assuming the ModelViewProjectionMatrix is area pixel space. + * If it has been scaled, then it's no longer valid. */ + uiWidgetBaseParameters params; + params.recti.xmin = rect->xmin + outline_width; + params.recti.ymin = rect->ymin + outline_width; + params.recti.xmax = rect->xmax - outline_width; + params.recti.ymax = rect->ymax - outline_width; + params.rect = *rect; + params.radi = rad; + params.rad = rad; + params.round_corners[0] = (roundboxtype & UI_CNR_BOTTOM_LEFT) ? 1.0f : 0.0f; + params.round_corners[1] = (roundboxtype & UI_CNR_BOTTOM_RIGHT) ? 1.0f : 0.0f; + params.round_corners[2] = (roundboxtype & UI_CNR_TOP_RIGHT) ? 1.0f : 0.0f; + params.round_corners[3] = (roundboxtype & UI_CNR_TOP_LEFT) ? 1.0f : 0.0f; + params.color_inner1[0] = inner1 ? inner1[0] : 0.0f; + params.color_inner1[1] = inner1 ? inner1[1] : 0.0f; + params.color_inner1[2] = inner1 ? inner1[2] : 0.0f; + params.color_inner1[3] = inner1 ? inner1[3] : 0.0f; + params.color_inner2[0] = inner2 ? inner2[0] : inner1 ? inner1[0] : 0.0f; + params.color_inner2[1] = inner2 ? inner2[1] : inner1 ? inner1[1] : 0.0f; + params.color_inner2[2] = inner2 ? inner2[2] : inner1 ? inner1[2] : 0.0f; + params.color_inner2[3] = inner2 ? inner2[3] : inner1 ? inner1[3] : 0.0f; + params.color_outline[0] = outline ? outline[0] : inner1 ? inner1[0] : 0.0f; + params.color_outline[1] = outline ? outline[1] : inner1 ? inner1[1] : 0.0f; + params.color_outline[2] = outline ? outline[2] : inner1 ? inner1[2] : 0.0f; + params.color_outline[3] = outline ? outline[3] : inner1 ? inner1[3] : 0.0f; + params.shade_dir = shade_dir; + params.alpha_discard = 1.0f; + + GPUBatch *batch = ui_batch_roundbox_widget_get(); + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_WIDGET_BASE); + GPU_batch_uniform_4fv_array(batch, "parameters", 11, (const float(*)[4]) & params); + GPU_blend(GPU_BLEND_ALPHA); + GPU_batch_draw(batch); + GPU_blend(GPU_BLEND_NONE); +} + +void UI_draw_roundbox_3ub_alpha( + const rctf *rect, bool filled, float rad, const uchar col[3], uchar alpha) +{ + float colv[4] = { + ((float)col[0]) / 255, + ((float)col[1]) / 255, + ((float)col[2]) / 255, + ((float)alpha) / 255, + }; + UI_draw_roundbox_4fv_ex(rect, (filled) ? colv : NULL, NULL, 1.0f, colv, U.pixelsize, rad); +} + +void UI_draw_roundbox_3fv_alpha( + const rctf *rect, bool filled, float rad, const float col[3], float alpha) +{ + float colv[4] = {col[0], col[1], col[2], alpha}; + UI_draw_roundbox_4fv_ex(rect, (filled) ? colv : NULL, NULL, 1.0f, colv, U.pixelsize, rad); +} + +void UI_draw_roundbox_aa(const rctf *rect, bool filled, float rad, const float color[4]) +{ + /* XXX this is to emulate previous behavior of semitransparent fills but that's was a side effect + * of the previous AA method. Better fix the callers. */ + float colv[4] = {color[0], color[1], color[2], color[3]}; + if (filled) { + colv[3] *= 0.65f; + } + + UI_draw_roundbox_4fv_ex(rect, (filled) ? colv : NULL, NULL, 1.0f, colv, U.pixelsize, rad); +} + +void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float col[4]) +{ + /* Exactly the same as UI_draw_roundbox_aa but does not do the legacy transparency. */ + UI_draw_roundbox_4fv_ex(rect, (filled) ? col : NULL, NULL, 1.0f, col, U.pixelsize, rad); +} + +/* linear horizontal shade within button or in outline */ +/* view2d scrollers use it */ +void UI_draw_roundbox_shade_x( + const rctf *rect, bool filled, float rad, float shadetop, float shadedown, const float col[4]) +{ + float inner1[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float inner2[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float outline[4]; + + if (filled) { + inner1[0] = min_ff(1.0f, col[0] + shadetop); + inner1[1] = min_ff(1.0f, col[1] + shadetop); + inner1[2] = min_ff(1.0f, col[2] + shadetop); + inner1[3] = 1.0f; + inner2[0] = max_ff(0.0f, col[0] + shadedown); + inner2[1] = max_ff(0.0f, col[1] + shadedown); + inner2[2] = max_ff(0.0f, col[2] + shadedown); + inner2[3] = 1.0f; + } + + /* TODO: non-filled box don't have gradients. Just use middle color. */ + outline[0] = clamp_f(col[0] + shadetop + shadedown, 0.0f, 1.0f); + outline[1] = clamp_f(col[1] + shadetop + shadedown, 0.0f, 1.0f); + outline[2] = clamp_f(col[2] + shadetop + shadedown, 0.0f, 1.0f); + outline[3] = clamp_f(col[3] + shadetop + shadedown, 0.0f, 1.0f); + + UI_draw_roundbox_4fv_ex(rect, inner1, inner2, 1.0f, outline, U.pixelsize, rad); +} + +void UI_draw_text_underline(int pos_x, int pos_y, int len, int height, const float color[4]) +{ + const int ofs_y = 4 * U.pixelsize; + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformColor4fv(color); + + immRecti(pos, pos_x, pos_y - ofs_y, pos_x + len, pos_y - ofs_y + (height * U.pixelsize)); + immUnbindProgram(); +} + +/* ************** SPECIAL BUTTON DRAWING FUNCTIONS ************* */ + +/* based on UI_draw_roundbox_gl_mode, + * check on making a version which allows us to skip some sides */ +void ui_draw_but_TAB_outline(const rcti *rect, + float rad, + uchar highlight[3], + uchar highlight_fade[3]) +{ + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const uint col = GPU_vertformat_attr_add( + format, "color", GPU_COMP_U8, 3, GPU_FETCH_INT_TO_FLOAT_UNIT); + /* add a 1px offset, looks nicer */ + const int minx = rect->xmin + U.pixelsize, maxx = rect->xmax - U.pixelsize; + const int miny = rect->ymin + U.pixelsize, maxy = rect->ymax - U.pixelsize; + int a; + float vec[4][2] = { + {0.195, 0.02}, + {0.55, 0.169}, + {0.831, 0.45}, + {0.98, 0.805}, + }; + + /* mult */ + for (a = 0; a < 4; a++) { + mul_v2_fl(vec[a], rad); + } + + immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + immBeginAtMost(GPU_PRIM_LINE_STRIP, 25); + + immAttr3ubv(col, highlight); + + /* start with corner left-top */ + if (roundboxtype & UI_CNR_TOP_LEFT) { + immVertex2f(pos, minx, maxy - rad); + for (a = 0; a < 4; a++) { + immVertex2f(pos, minx + vec[a][1], maxy - rad + vec[a][0]); + } + immVertex2f(pos, minx + rad, maxy); + } + else { + immVertex2f(pos, minx, maxy); + } + + /* corner right-top */ + if (roundboxtype & UI_CNR_TOP_RIGHT) { + immVertex2f(pos, maxx - rad, maxy); + for (a = 0; a < 4; a++) { + immVertex2f(pos, maxx - rad + vec[a][0], maxy - vec[a][1]); + } + immVertex2f(pos, maxx, maxy - rad); + } + else { + immVertex2f(pos, maxx, maxy); + } + + immAttr3ubv(col, highlight_fade); + + /* corner right-bottom */ + if (roundboxtype & UI_CNR_BOTTOM_RIGHT) { + immVertex2f(pos, maxx, miny + rad); + for (a = 0; a < 4; a++) { + immVertex2f(pos, maxx - vec[a][1], miny + rad - vec[a][0]); + } + immVertex2f(pos, maxx - rad, miny); + } + else { + immVertex2f(pos, maxx, miny); + } + + /* corner left-bottom */ + if (roundboxtype & UI_CNR_BOTTOM_LEFT) { + immVertex2f(pos, minx + rad, miny); + for (a = 0; a < 4; a++) { + immVertex2f(pos, minx + rad - vec[a][0], miny + vec[a][1]); + } + immVertex2f(pos, minx, miny + rad); + } + else { + immVertex2f(pos, minx, miny); + } + + immAttr3ubv(col, highlight); + + /* back to corner left-top */ + immVertex2f(pos, minx, (roundboxtype & UI_CNR_TOP_LEFT) ? (maxy - rad) : maxy); + + immEnd(); + immUnbindProgram(); +} + +void ui_draw_but_IMAGE(ARegion *UNUSED(region), + uiBut *but, + const uiWidgetColors *UNUSED(wcol), + const rcti *rect) +{ +#ifdef WITH_HEADLESS + (void)rect; + (void)but; +#else + ImBuf *ibuf = (ImBuf *)but->poin; + + if (!ibuf) { + return; + } + + const int w = BLI_rcti_size_x(rect); + const int h = BLI_rcti_size_y(rect); + + /* scissor doesn't seem to be doing the right thing...? */ +# if 0 + /* prevent drawing outside widget area */ + int scissor[4]; + GPU_scissor_get(scissor); + GPU_scissor(rect->xmin, rect->ymin, w, h); +# endif + + /* Combine with premultiplied alpha. */ + GPU_blend(GPU_BLEND_ALPHA_PREMULT); + + if (w != ibuf->x || h != ibuf->y) { + /* We scale the bitmap, rather than have OGL do a worse job. */ + IMB_scaleImBuf(ibuf, w, h); + } + + float col[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + if (but->col[3] != 0) { + /* Optionally use uiBut's col to recolor the image. */ + rgba_uchar_to_float(col, but->col); + } + + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + immDrawPixelsTex(&state, + (float)rect->xmin, + (float)rect->ymin, + ibuf->x, + ibuf->y, + GPU_RGBA8, + false, + ibuf->rect, + 1.0f, + 1.0f, + col); + + GPU_blend(GPU_BLEND_NONE); + +# if 0 + /* Restore scissor-test. */ + GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); +# endif + +#endif +} + +/** + * Draw title and text safe areas. + * + * \note This function is to be used with the 2D dashed shader enabled. + * + * \param pos: is a #PRIM_FLOAT, 2, #GPU_FETCH_FLOAT vertex attribute. + * \param x1, x2, y1, y2: The offsets for the view, not the zones. + */ +void UI_draw_safe_areas(uint pos, + const rctf *rect, + const float title_aspect[2], + const float action_aspect[2]) +{ + const float size_x_half = (rect->xmax - rect->xmin) * 0.5f; + const float size_y_half = (rect->ymax - rect->ymin) * 0.5f; + + const float *safe_areas[] = {title_aspect, action_aspect}; + const int safe_len = ARRAY_SIZE(safe_areas); + + for (int i = 0; i < safe_len; i++) { + if (safe_areas[i][0] || safe_areas[i][1]) { + const float margin_x = safe_areas[i][0] * size_x_half; + const float margin_y = safe_areas[i][1] * size_y_half; + + const float minx = rect->xmin + margin_x; + const float miny = rect->ymin + margin_y; + const float maxx = rect->xmax - margin_x; + const float maxy = rect->ymax - margin_y; + + imm_draw_box_wire_2d(pos, minx, miny, maxx, maxy); + } + } +} + +static void draw_scope_end(const rctf *rect) +{ + GPU_blend(GPU_BLEND_ALPHA); + + /* outline */ + UI_draw_roundbox_corner_set(UI_CNR_ALL); + const float color[4] = {0.0f, 0.0f, 0.0f, 0.5f}; + const rctf box_rect{ + rect->xmin - 1, + rect->xmax + 1, + rect->ymin, + rect->ymax + 1, + }; + UI_draw_roundbox_4fv(&box_rect, false, 3.0f, color); +} + +static void histogram_draw_one(float r, + float g, + float b, + float alpha, + float x, + float y, + float w, + float h, + const float *data, + int res, + const bool is_line, + uint pos_attr) +{ + const float color[4] = {r, g, b, alpha}; + + /* that can happen */ + if (res == 0) { + return; + } + + GPU_line_smooth(true); + GPU_blend(GPU_BLEND_ADDITIVE); + + immUniformColor4fv(color); + + if (is_line) { + /* curve outline */ + GPU_line_width(1.5); + + immBegin(GPU_PRIM_LINE_STRIP, res); + for (int i = 0; i < res; i++) { + const float x2 = x + i * (w / (float)res); + immVertex2f(pos_attr, x2, y + (data[i] * h)); + } + immEnd(); + } + else { + /* under the curve */ + immBegin(GPU_PRIM_TRI_STRIP, res * 2); + immVertex2f(pos_attr, x, y); + immVertex2f(pos_attr, x, y + (data[0] * h)); + for (int i = 1; i < res; i++) { + const float x2 = x + i * (w / (float)res); + immVertex2f(pos_attr, x2, y + (data[i] * h)); + immVertex2f(pos_attr, x2, y); + } + immEnd(); + + /* curve outline */ + immUniformColor4f(0.0f, 0.0f, 0.0f, 0.25f); + + GPU_blend(GPU_BLEND_ALPHA); + immBegin(GPU_PRIM_LINE_STRIP, res); + for (int i = 0; i < res; i++) { + const float x2 = x + i * (w / (float)res); + immVertex2f(pos_attr, x2, y + (data[i] * h)); + } + immEnd(); + } + + GPU_line_smooth(false); +} + +#define HISTOGRAM_TOT_GRID_LINES 4 + +void ui_draw_but_HISTOGRAM(ARegion *UNUSED(region), + uiBut *but, + const uiWidgetColors *UNUSED(wcol), + const rcti *recti) +{ + Histogram *hist = (Histogram *)but->poin; + const int res = hist->x_resolution; + const bool is_line = (hist->flag & HISTO_FLAG_LINE) != 0; + + rctf rect = { + (float)recti->xmin + 1, + (float)recti->xmax - 1, + (float)recti->ymin + 1, + (float)recti->ymax - 1, + }; + + const float w = BLI_rctf_size_x(&rect); + const float h = BLI_rctf_size_y(&rect) * hist->ymax; + + GPU_blend(GPU_BLEND_ALPHA); + + float color[4]; + UI_GetThemeColor4fv(TH_PREVIEW_BACK, color); + UI_draw_roundbox_corner_set(UI_CNR_ALL); + { + const rctf box_rect{ + rect.xmin - 1, + rect.xmax + 1, + rect.ymin - 1, + rect.ymax + 1, + }; + UI_draw_roundbox_4fv(&box_rect, true, 3.0f, color); + } + /* need scissor test, histogram can draw outside of boundary */ + int scissor[4]; + GPU_scissor_get(scissor); + GPU_scissor((rect.xmin - 1), + (rect.ymin - 1), + (rect.xmax + 1) - (rect.xmin - 1), + (rect.ymax + 1) - (rect.ymin - 1)); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); + /* draw grid lines here */ + for (int i = 1; i <= HISTOGRAM_TOT_GRID_LINES; i++) { + const float fac = (float)i / (float)HISTOGRAM_TOT_GRID_LINES; + + /* so we can tell the 1.0 color point */ + if (i == HISTOGRAM_TOT_GRID_LINES) { + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.5f); + } + + immBegin(GPU_PRIM_LINES, 4); + + immVertex2f(pos, rect.xmin, rect.ymin + fac * h); + immVertex2f(pos, rect.xmax, rect.ymin + fac * h); + + immVertex2f(pos, rect.xmin + fac * w, rect.ymin); + immVertex2f(pos, rect.xmin + fac * w, rect.ymax); + + immEnd(); + } + + if (hist->mode == HISTO_MODE_LUMA) { + histogram_draw_one( + 1.0, 1.0, 1.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_luma, res, is_line, pos); + } + else if (hist->mode == HISTO_MODE_ALPHA) { + histogram_draw_one( + 1.0, 1.0, 1.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_a, res, is_line, pos); + } + else { + if (ELEM(hist->mode, HISTO_MODE_RGB, HISTO_MODE_R)) { + histogram_draw_one( + 1.0, 0.0, 0.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_r, res, is_line, pos); + } + if (ELEM(hist->mode, HISTO_MODE_RGB, HISTO_MODE_G)) { + histogram_draw_one( + 0.0, 1.0, 0.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_g, res, is_line, pos); + } + if (ELEM(hist->mode, HISTO_MODE_RGB, HISTO_MODE_B)) { + histogram_draw_one( + 0.0, 0.0, 1.0, 0.75, rect.xmin, rect.ymin, w, h, hist->data_b, res, is_line, pos); + } + } + + immUnbindProgram(); + + /* Restore scissor test. */ + GPU_scissor(UNPACK4(scissor)); + + /* outline */ + draw_scope_end(&rect); +} + +#undef HISTOGRAM_TOT_GRID_LINES + +static void waveform_draw_one(float *waveform, int nbr, const float col[3]) +{ + GPUVertFormat format = {0}; + const uint pos_id = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format); + GPU_vertbuf_data_alloc(vbo, nbr); + + GPU_vertbuf_attr_fill(vbo, pos_id, waveform); + + /* TODO store the GPUBatch inside the scope */ + GPUBatch *batch = GPU_batch_create_ex(GPU_PRIM_POINTS, vbo, NULL, GPU_BATCH_OWNS_VBO); + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_UNIFORM_COLOR); + GPU_batch_uniform_4f(batch, "color", col[0], col[1], col[2], 1.0f); + GPU_batch_draw(batch); + + GPU_batch_discard(batch); +} + +void ui_draw_but_WAVEFORM(ARegion *UNUSED(region), + uiBut *but, + const uiWidgetColors *UNUSED(wcol), + const rcti *recti) +{ + Scopes *scopes = (Scopes *)but->poin; + int scissor[4]; + float colors[3][3]; + const float colorsycc[3][3] = {{1, 0, 1}, {1, 1, 0}, {0, 1, 1}}; + /* colors pre multiplied by alpha for speed up */ + float colors_alpha[3][3], colorsycc_alpha[3][3]; + float min, max; + + if (scopes == NULL) { + return; + } + + rctf rect = { + (float)recti->xmin + 1, + (float)recti->xmax - 1, + (float)recti->ymin + 1, + (float)recti->ymax - 1, + }; + + if (scopes->wavefrm_yfac < 0.5f) { + scopes->wavefrm_yfac = 0.98f; + } + const float w = BLI_rctf_size_x(&rect) - 7; + const float h = BLI_rctf_size_y(&rect) * scopes->wavefrm_yfac; + const float yofs = rect.ymin + (BLI_rctf_size_y(&rect) - h) * 0.5f; + const float w3 = w / 3.0f; + + /* log scale for alpha */ + const float alpha = scopes->wavefrm_alpha * scopes->wavefrm_alpha; + + unit_m3(colors); + + for (int c = 0; c < 3; c++) { + for (int i = 0; i < 3; i++) { + colors_alpha[c][i] = colors[c][i] * alpha; + colorsycc_alpha[c][i] = colorsycc[c][i] * alpha; + } + } + + /* Flush text cache before changing scissors. */ + BLF_batch_draw_flush(); + + GPU_blend(GPU_BLEND_ALPHA); + + float color[4]; + UI_GetThemeColor4fv(TH_PREVIEW_BACK, color); + UI_draw_roundbox_corner_set(UI_CNR_ALL); + { + const rctf box_rect{ + rect.xmin - 1, + rect.xmax + 1, + rect.ymin - 1, + rect.ymax + 1, + }; + UI_draw_roundbox_4fv(&box_rect, true, 3.0f, color); + } + /* need scissor test, waveform can draw outside of boundary */ + GPU_scissor_get(scissor); + GPU_scissor((rect.xmin - 1), + (rect.ymin - 1), + (rect.xmax + 1) - (rect.xmin - 1), + (rect.ymax + 1) - (rect.ymin - 1)); + + /* draw scale numbers first before binding any shader */ + for (int i = 0; i < 6; i++) { + char str[4]; + BLI_snprintf(str, sizeof(str), "%-3d", i * 20); + str[3] = '\0'; + BLF_color4f(BLF_default(), 1.0f, 1.0f, 1.0f, 0.08f); + BLF_draw_default(rect.xmin + 1, yofs - 5 + (i * 0.2f) * h, 0, str, sizeof(str) - 1); + } + + /* Flush text cache before drawing things on top. */ + BLF_batch_draw_flush(); + + GPU_blend(GPU_BLEND_ALPHA); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); + + /* draw grid lines here */ + immBegin(GPU_PRIM_LINES, 12); + + for (int i = 0; i < 6; i++) { + immVertex2f(pos, rect.xmin + 22, yofs + (i * 0.2f) * h); + immVertex2f(pos, rect.xmax + 1, yofs + (i * 0.2f) * h); + } + + immEnd(); + + /* 3 vertical separation */ + if (scopes->wavefrm_mode != SCOPES_WAVEFRM_LUMA) { + immBegin(GPU_PRIM_LINES, 4); + + for (int i = 1; i < 3; i++) { + immVertex2f(pos, rect.xmin + i * w3, rect.ymin); + immVertex2f(pos, rect.xmin + i * w3, rect.ymax); + } + + immEnd(); + } + + /* separate min max zone on the right */ + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, rect.xmin + w, rect.ymin); + immVertex2f(pos, rect.xmin + w, rect.ymax); + immEnd(); + + /* 16-235-240 level in case of ITU-R BT601/709 */ + immUniformColor4f(1.0f, 0.4f, 0.0f, 0.2f); + if (ELEM(scopes->wavefrm_mode, SCOPES_WAVEFRM_YCC_601, SCOPES_WAVEFRM_YCC_709)) { + immBegin(GPU_PRIM_LINES, 8); + + immVertex2f(pos, rect.xmin + 22, yofs + h * 16.0f / 255.0f); + immVertex2f(pos, rect.xmax + 1, yofs + h * 16.0f / 255.0f); + + immVertex2f(pos, rect.xmin + 22, yofs + h * 235.0f / 255.0f); + immVertex2f(pos, rect.xmin + w3, yofs + h * 235.0f / 255.0f); + + immVertex2f(pos, rect.xmin + 3 * w3, yofs + h * 235.0f / 255.0f); + immVertex2f(pos, rect.xmax + 1, yofs + h * 235.0f / 255.0f); + + immVertex2f(pos, rect.xmin + w3, yofs + h * 240.0f / 255.0f); + immVertex2f(pos, rect.xmax + 1, yofs + h * 240.0f / 255.0f); + + immEnd(); + } + /* 7.5 IRE black point level for NTSC */ + if (scopes->wavefrm_mode == SCOPES_WAVEFRM_LUMA) { + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, rect.xmin, yofs + h * 0.075f); + immVertex2f(pos, rect.xmax + 1, yofs + h * 0.075f); + immEnd(); + } + + if (scopes->ok && scopes->waveform_1 != NULL) { + GPU_blend(GPU_BLEND_ADDITIVE); + GPU_point_size(1.0); + + /* LUMA (1 channel) */ + if (scopes->wavefrm_mode == SCOPES_WAVEFRM_LUMA) { + const float col[3] = {alpha, alpha, alpha}; + + GPU_matrix_push(); + GPU_matrix_translate_2f(rect.xmin, yofs); + GPU_matrix_scale_2f(w, h); + + waveform_draw_one(scopes->waveform_1, scopes->waveform_tot, col); + + GPU_matrix_pop(); + + /* min max */ + immUniformColor3f(0.5f, 0.5f, 0.5f); + min = yofs + scopes->minmax[0][0] * h; + max = yofs + scopes->minmax[0][1] * h; + CLAMP(min, rect.ymin, rect.ymax); + CLAMP(max, rect.ymin, rect.ymax); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, rect.xmax - 3, min); + immVertex2f(pos, rect.xmax - 3, max); + immEnd(); + } + /* RGB (3 channel) */ + else if (scopes->wavefrm_mode == SCOPES_WAVEFRM_RGB) { + GPU_matrix_push(); + GPU_matrix_translate_2f(rect.xmin, yofs); + GPU_matrix_scale_2f(w, h); + + waveform_draw_one(scopes->waveform_1, scopes->waveform_tot, colors_alpha[0]); + waveform_draw_one(scopes->waveform_2, scopes->waveform_tot, colors_alpha[1]); + waveform_draw_one(scopes->waveform_3, scopes->waveform_tot, colors_alpha[2]); + + GPU_matrix_pop(); + } + /* PARADE / YCC (3 channels) */ + else if (ELEM(scopes->wavefrm_mode, + SCOPES_WAVEFRM_RGB_PARADE, + SCOPES_WAVEFRM_YCC_601, + SCOPES_WAVEFRM_YCC_709, + SCOPES_WAVEFRM_YCC_JPEG)) { + const int rgb = (scopes->wavefrm_mode == SCOPES_WAVEFRM_RGB_PARADE); + + GPU_matrix_push(); + GPU_matrix_translate_2f(rect.xmin, yofs); + GPU_matrix_scale_2f(w3, h); + + waveform_draw_one( + scopes->waveform_1, scopes->waveform_tot, (rgb) ? colors_alpha[0] : colorsycc_alpha[0]); + + GPU_matrix_translate_2f(1.0f, 0.0f); + waveform_draw_one( + scopes->waveform_2, scopes->waveform_tot, (rgb) ? colors_alpha[1] : colorsycc_alpha[1]); + + GPU_matrix_translate_2f(1.0f, 0.0f); + waveform_draw_one( + scopes->waveform_3, scopes->waveform_tot, (rgb) ? colors_alpha[2] : colorsycc_alpha[2]); + + GPU_matrix_pop(); + } + + /* min max */ + if (scopes->wavefrm_mode != SCOPES_WAVEFRM_LUMA) { + for (int c = 0; c < 3; c++) { + if (ELEM(scopes->wavefrm_mode, SCOPES_WAVEFRM_RGB_PARADE, SCOPES_WAVEFRM_RGB)) { + immUniformColor3f(colors[c][0] * 0.75f, colors[c][1] * 0.75f, colors[c][2] * 0.75f); + } + else { + immUniformColor3f( + colorsycc[c][0] * 0.75f, colorsycc[c][1] * 0.75f, colorsycc[c][2] * 0.75f); + } + min = yofs + scopes->minmax[c][0] * h; + max = yofs + scopes->minmax[c][1] * h; + CLAMP(min, rect.ymin, rect.ymax); + CLAMP(max, rect.ymin, rect.ymax); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, rect.xmin + w + 2 + c * 2, min); + immVertex2f(pos, rect.xmin + w + 2 + c * 2, max); + immEnd(); + } + } + } + + immUnbindProgram(); + + /* Restore scissor test. */ + GPU_scissor(UNPACK4(scissor)); + + /* outline */ + draw_scope_end(&rect); + + GPU_blend(GPU_BLEND_NONE); +} + +static float polar_to_x(float center, float diam, float ampli, float angle) +{ + return center + diam * ampli * cosf(angle); +} + +static float polar_to_y(float center, float diam, float ampli, float angle) +{ + return center + diam * ampli * sinf(angle); +} + +static void vectorscope_draw_target( + uint pos, float centerx, float centery, float diam, const float colf[3]) +{ + float y, u, v; + float tangle = 0.0f, tampli; + float dangle, dampli, dangle2, dampli2; + + rgb_to_yuv(colf[0], colf[1], colf[2], &y, &u, &v, BLI_YUV_ITU_BT709); + + if (u > 0 && v >= 0) { + tangle = atanf(v / u); + } + else if (u > 0 && v < 0) { + tangle = atanf(v / u) + 2.0f * (float)M_PI; + } + else if (u < 0) { + tangle = atanf(v / u) + (float)M_PI; + } + else if (u == 0 && v > 0.0f) { + tangle = M_PI_2; + } + else if (u == 0 && v < 0.0f) { + tangle = -M_PI_2; + } + tampli = sqrtf(u * u + v * v); + + /* small target vary by 2.5 degree and 2.5 IRE unit */ + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.12f); + dangle = DEG2RADF(2.5f); + dampli = 2.5f / 200.0f; + immBegin(GPU_PRIM_LINE_LOOP, 4); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli, tangle + dangle), + polar_to_y(centery, diam, tampli + dampli, tangle + dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli, tangle + dangle), + polar_to_y(centery, diam, tampli - dampli, tangle + dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli, tangle - dangle), + polar_to_y(centery, diam, tampli - dampli, tangle - dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli, tangle - dangle), + polar_to_y(centery, diam, tampli + dampli, tangle - dangle)); + immEnd(); + /* big target vary by 10 degree and 20% amplitude */ + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.12f); + dangle = DEG2RADF(10.0f); + dampli = 0.2f * tampli; + dangle2 = DEG2RADF(5.0f); + dampli2 = 0.5f * dampli; + immBegin(GPU_PRIM_LINE_STRIP, 3); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli - dampli2, tangle + dangle), + polar_to_y(centery, diam, tampli + dampli - dampli2, tangle + dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli, tangle + dangle), + polar_to_y(centery, diam, tampli + dampli, tangle + dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli, tangle + dangle - dangle2), + polar_to_y(centery, diam, tampli + dampli, tangle + dangle - dangle2)); + immEnd(); + immBegin(GPU_PRIM_LINE_STRIP, 3); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli + dampli2, tangle + dangle), + polar_to_y(centery, diam, tampli - dampli + dampli2, tangle + dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli, tangle + dangle), + polar_to_y(centery, diam, tampli - dampli, tangle + dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli, tangle + dangle - dangle2), + polar_to_y(centery, diam, tampli - dampli, tangle + dangle - dangle2)); + immEnd(); + immBegin(GPU_PRIM_LINE_STRIP, 3); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli + dampli2, tangle - dangle), + polar_to_y(centery, diam, tampli - dampli + dampli2, tangle - dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli, tangle - dangle), + polar_to_y(centery, diam, tampli - dampli, tangle - dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli - dampli, tangle - dangle + dangle2), + polar_to_y(centery, diam, tampli - dampli, tangle - dangle + dangle2)); + immEnd(); + immBegin(GPU_PRIM_LINE_STRIP, 3); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli - dampli2, tangle - dangle), + polar_to_y(centery, diam, tampli + dampli - dampli2, tangle - dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli, tangle - dangle), + polar_to_y(centery, diam, tampli + dampli, tangle - dangle)); + immVertex2f(pos, + polar_to_x(centerx, diam, tampli + dampli, tangle - dangle + dangle2), + polar_to_y(centery, diam, tampli + dampli, tangle - dangle + dangle2)); + immEnd(); +} + +void ui_draw_but_VECTORSCOPE(ARegion *UNUSED(region), + uiBut *but, + const uiWidgetColors *UNUSED(wcol), + const rcti *recti) +{ + const float skin_rad = DEG2RADF(123.0f); /* angle in radians of the skin tone line */ + Scopes *scopes = (Scopes *)but->poin; + + const float colors[6][3] = { + {0.75, 0.0, 0.0}, + {0.75, 0.75, 0.0}, + {0.0, 0.75, 0.0}, + {0.0, 0.75, 0.75}, + {0.0, 0.0, 0.75}, + {0.75, 0.0, 0.75}, + }; + + rctf rect = { + (float)recti->xmin + 1, + (float)recti->xmax - 1, + (float)recti->ymin + 1, + (float)recti->ymax - 1, + }; + + const float w = BLI_rctf_size_x(&rect); + const float h = BLI_rctf_size_y(&rect); + const float centerx = rect.xmin + w * 0.5f; + const float centery = rect.ymin + h * 0.5f; + const float diam = (w < h) ? w : h; + + const float alpha = scopes->vecscope_alpha * scopes->vecscope_alpha * scopes->vecscope_alpha; + + GPU_blend(GPU_BLEND_ALPHA); + + float color[4]; + UI_GetThemeColor4fv(TH_PREVIEW_BACK, color); + UI_draw_roundbox_corner_set(UI_CNR_ALL); + + { + const rctf box_rect{ + rect.xmin - 1, + rect.xmax + 1, + rect.ymin - 1, + rect.ymax + 1, + }; + UI_draw_roundbox_4fv(&box_rect, true, 3.0f, color); + } + /* need scissor test, hvectorscope can draw outside of boundary */ + int scissor[4]; + GPU_scissor_get(scissor); + GPU_scissor((rect.xmin - 1), + (rect.ymin - 1), + (rect.xmax + 1) - (rect.xmin - 1), + (rect.ymax + 1) - (rect.ymin - 1)); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.08f); + /* draw grid elements */ + /* cross */ + immBegin(GPU_PRIM_LINES, 4); + + immVertex2f(pos, centerx - (diam * 0.5f) - 5, centery); + immVertex2f(pos, centerx + (diam * 0.5f) + 5, centery); + + immVertex2f(pos, centerx, centery - (diam * 0.5f) - 5); + immVertex2f(pos, centerx, centery + (diam * 0.5f) + 5); + + immEnd(); + + /* circles */ + for (int j = 0; j < 5; j++) { + const int increment = 15; + immBegin(GPU_PRIM_LINE_LOOP, (int)(360 / increment)); + for (int i = 0; i <= 360 - increment; i += increment) { + const float a = DEG2RADF((float)i); + const float r = (j + 1) * 0.1f; + immVertex2f(pos, polar_to_x(centerx, diam, r, a), polar_to_y(centery, diam, r, a)); + } + immEnd(); + } + /* skin tone line */ + immUniformColor4f(1.0f, 0.4f, 0.0f, 0.2f); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f( + pos, polar_to_x(centerx, diam, 0.5f, skin_rad), polar_to_y(centery, diam, 0.5f, skin_rad)); + immVertex2f( + pos, polar_to_x(centerx, diam, 0.1f, skin_rad), polar_to_y(centery, diam, 0.1f, skin_rad)); + immEnd(); + + /* saturation points */ + for (int i = 0; i < 6; i++) { + vectorscope_draw_target(pos, centerx, centery, diam, colors[i]); + } + + if (scopes->ok && scopes->vecscope != NULL) { + /* pixel point cloud */ + const float col[3] = {alpha, alpha, alpha}; + + GPU_blend(GPU_BLEND_ADDITIVE); + GPU_point_size(1.0); + + GPU_matrix_push(); + GPU_matrix_translate_2f(centerx, centery); + GPU_matrix_scale_1f(diam); + + waveform_draw_one(scopes->vecscope, scopes->waveform_tot, col); + + GPU_matrix_pop(); + } + + immUnbindProgram(); + + /* Restore scissor test. */ + GPU_scissor(UNPACK4(scissor)); + /* outline */ + draw_scope_end(&rect); + + GPU_blend(GPU_BLEND_NONE); +} + +static void ui_draw_colorband_handle_tri_hlight( + uint pos, float x1, float y1, float halfwidth, float height) +{ + GPU_line_smooth(true); + + immBegin(GPU_PRIM_LINE_STRIP, 3); + immVertex2f(pos, x1 + halfwidth, y1); + immVertex2f(pos, x1, y1 + height); + immVertex2f(pos, x1 - halfwidth, y1); + immEnd(); + + GPU_line_smooth(false); +} + +static void ui_draw_colorband_handle_tri( + uint pos, float x1, float y1, float halfwidth, float height, bool fill) +{ + if (fill) { + GPU_polygon_smooth(true); + } + else { + GPU_line_smooth(true); + } + + immBegin(fill ? GPU_PRIM_TRIS : GPU_PRIM_LINE_LOOP, 3); + immVertex2f(pos, x1 + halfwidth, y1); + immVertex2f(pos, x1, y1 + height); + immVertex2f(pos, x1 - halfwidth, y1); + immEnd(); + + if (fill) { + GPU_polygon_smooth(false); + } + else { + GPU_line_smooth(false); + } +} + +static void ui_draw_colorband_handle_box( + uint pos, float x1, float y1, float x2, float y2, bool fill) +{ + immBegin(fill ? GPU_PRIM_TRI_FAN : GPU_PRIM_LINE_LOOP, 4); + immVertex2f(pos, x1, y1); + immVertex2f(pos, x1, y2); + immVertex2f(pos, x2, y2); + immVertex2f(pos, x2, y1); + immEnd(); +} + +static void ui_draw_colorband_handle(uint shdr_pos, + const rcti *rect, + float x, + const float rgb[3], + struct ColorManagedDisplay *display, + bool active) +{ + const float sizey = BLI_rcti_size_y(rect); + const float min_width = 3.0f; + float colf[3] = {UNPACK3(rgb)}; + + const float half_width = floorf(sizey / 3.5f); + const float height = half_width * 1.4f; + + float y1 = rect->ymin + (sizey * 0.16f); + const float y2 = rect->ymax; + + /* align to pixels */ + x = floorf(x + 0.5f); + y1 = floorf(y1 + 0.5f); + + if (active || half_width < min_width) { + immUnbindProgram(); + + immBindBuiltinProgram(GPU_SHADER_2D_LINE_DASHED_UNIFORM_COLOR); + + float viewport_size[4]; + GPU_viewport_size_get_f(viewport_size); + immUniform2f("viewport_size", viewport_size[2] / UI_DPI_FAC, viewport_size[3] / UI_DPI_FAC); + + immUniform1i("colors_len", 2); /* "advanced" mode */ + immUniformArray4fv( + "colors", (float *)(float[][4]){{0.8f, 0.8f, 0.8f, 1.0f}, {0.0f, 0.0f, 0.0f, 1.0f}}, 2); + immUniform1f("dash_width", active ? 4.0f : 2.0f); + immUniform1f("dash_factor", 0.5f); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(shdr_pos, x, y1); + immVertex2f(shdr_pos, x, y2); + immEnd(); + + immUnbindProgram(); + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* hide handles when zoomed out too far */ + if (half_width < min_width) { + return; + } + } + + /* shift handle down */ + y1 -= half_width; + + immUniformColor3ub(0, 0, 0); + ui_draw_colorband_handle_box( + shdr_pos, x - half_width, y1 - 1, x + half_width, y1 + height, false); + + /* draw all triangles blended */ + GPU_blend(GPU_BLEND_ALPHA); + + ui_draw_colorband_handle_tri(shdr_pos, x, y1 + height, half_width, half_width, true); + + if (active) { + immUniformColor3ub(196, 196, 196); + } + else { + immUniformColor3ub(96, 96, 96); + } + ui_draw_colorband_handle_tri(shdr_pos, x, y1 + height, half_width, half_width, true); + + if (active) { + immUniformColor3ub(255, 255, 255); + } + else { + immUniformColor3ub(128, 128, 128); + } + ui_draw_colorband_handle_tri_hlight( + shdr_pos, x, y1 + height - 1, (half_width - 1), (half_width - 1)); + + immUniformColor3ub(0, 0, 0); + ui_draw_colorband_handle_tri_hlight(shdr_pos, x, y1 + height, half_width, half_width); + + GPU_blend(GPU_BLEND_NONE); + + immUniformColor3ub(128, 128, 128); + ui_draw_colorband_handle_box( + shdr_pos, x - (half_width - 1), y1, x + (half_width - 1), y1 + height, true); + + if (display) { + IMB_colormanagement_scene_linear_to_display_v3(colf, display); + } + + immUniformColor3fv(colf); + ui_draw_colorband_handle_box( + shdr_pos, x - (half_width - 2), y1 + 1, x + (half_width - 2), y1 + height - 2, true); +} + +void ui_draw_but_COLORBAND(uiBut *but, const uiWidgetColors *UNUSED(wcol), const rcti *rect) +{ + struct ColorManagedDisplay *display = ui_block_cm_display_get(but->block); + uint pos_id, col_id; + + uiButColorBand *but_coba = (uiButColorBand *)but; + ColorBand *coba = (but_coba->edit_coba == NULL) ? (ColorBand *)but->poin : but_coba->edit_coba; + + if (coba == NULL) { + return; + } + + const float x1 = rect->xmin; + const float sizex = rect->xmax - x1; + const float sizey = BLI_rcti_size_y(rect); + const float sizey_solid = sizey * 0.25f; + const float y1 = rect->ymin; + + /* exit early if too narrow */ + if (sizex <= 0) { + return; + } + + GPUVertFormat *format = immVertexFormat(); + pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_CHECKER); + + /* Drawing the checkerboard. */ + const float checker_dark = UI_ALPHA_CHECKER_DARK / 255.0f; + const float checker_light = UI_ALPHA_CHECKER_LIGHT / 255.0f; + immUniform4f("color1", checker_dark, checker_dark, checker_dark, 1.0f); + immUniform4f("color2", checker_light, checker_light, checker_light, 1.0f); + immUniform1i("size", 8); + immRectf(pos_id, x1, y1, x1 + sizex, rect->ymax); + immUnbindProgram(); + + /* New format */ + format = immVertexFormat(); + pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + col_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + + /* layer: color ramp */ + GPU_blend(GPU_BLEND_ALPHA); + + CBData *cbd = coba->data; + + float v1[2], v2[2]; + float colf[4] = {0, 0, 0, 0}; /* initialize in case the colorband isn't valid */ + + v1[1] = y1 + sizey_solid; + v2[1] = rect->ymax; + + immBegin(GPU_PRIM_TRI_STRIP, (sizex + 1) * 2); + for (int a = 0; a <= sizex; a++) { + const float pos = ((float)a) / sizex; + BKE_colorband_evaluate(coba, pos, colf); + if (display) { + IMB_colormanagement_scene_linear_to_display_v3(colf, display); + } + + v1[0] = v2[0] = x1 + a; + + immAttr4fv(col_id, colf); + immVertex2fv(pos_id, v1); + immVertex2fv(pos_id, v2); + } + immEnd(); + + /* layer: color ramp without alpha for reference when manipulating ramp properties */ + v1[1] = y1; + v2[1] = y1 + sizey_solid; + + immBegin(GPU_PRIM_TRI_STRIP, (sizex + 1) * 2); + for (int a = 0; a <= sizex; a++) { + const float pos = ((float)a) / sizex; + BKE_colorband_evaluate(coba, pos, colf); + if (display) { + IMB_colormanagement_scene_linear_to_display_v3(colf, display); + } + + v1[0] = v2[0] = x1 + a; + + immAttr4f(col_id, colf[0], colf[1], colf[2], 1.0f); + immVertex2fv(pos_id, v1); + immVertex2fv(pos_id, v2); + } + immEnd(); + + immUnbindProgram(); + + GPU_blend(GPU_BLEND_NONE); + + /* New format */ + format = immVertexFormat(); + pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* layer: box outline */ + immUniformColor4f(0.0f, 0.0f, 0.0f, 1.0f); + imm_draw_box_wire_2d(pos_id, x1, y1, x1 + sizex, rect->ymax); + + /* layer: box outline */ + GPU_blend(GPU_BLEND_ALPHA); + immUniformColor4f(0.0f, 0.0f, 0.0f, 0.5f); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos_id, x1, y1); + immVertex2f(pos_id, x1 + sizex, y1); + immEnd(); + + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.25f); + + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos_id, x1, y1 - 1); + immVertex2f(pos_id, x1 + sizex, y1 - 1); + immEnd(); + + GPU_blend(GPU_BLEND_NONE); + + /* layer: draw handles */ + for (int a = 0; a < coba->tot; a++, cbd++) { + if (a != coba->cur) { + const float pos = x1 + cbd->pos * (sizex - 1) + 1; + ui_draw_colorband_handle(pos_id, rect, pos, &cbd->r, display, false); + } + } + + /* layer: active handle */ + if (coba->tot != 0) { + cbd = &coba->data[coba->cur]; + const float pos = x1 + cbd->pos * (sizex - 1) + 1; + ui_draw_colorband_handle(pos_id, rect, pos, &cbd->r, display, true); + } + + immUnbindProgram(); +} + +void ui_draw_but_UNITVEC(uiBut *but, const uiWidgetColors *wcol, const rcti *rect) +{ + /* sphere color */ + const float diffuse[3] = {1.0f, 1.0f, 1.0f}; + float light[3]; + const float size = 0.5f * min_ff(BLI_rcti_size_x(rect), BLI_rcti_size_y(rect)); + + /* backdrop */ + UI_draw_roundbox_corner_set(UI_CNR_ALL); + + { + const rctf box_rect{ + rect->xmin, + rect->xmax, + rect->ymin, + rect->ymax, + }; + UI_draw_roundbox_3ub_alpha(&box_rect, true, 5.0f, wcol->inner, 255); + } + GPU_face_culling(GPU_CULL_BACK); + + /* setup lights */ + ui_but_v3_get(but, light); + + /* transform to button */ + GPU_matrix_push(); + + const bool use_project_matrix = (size >= -GPU_MATRIX_ORTHO_CLIP_NEAR_DEFAULT); + if (use_project_matrix) { + GPU_matrix_push_projection(); + GPU_matrix_ortho_set_z(-size, size); + } + + GPU_matrix_translate_2f(rect->xmin + 0.5f * BLI_rcti_size_x(rect), + rect->ymin + 0.5f * BLI_rcti_size_y(rect)); + GPU_matrix_scale_1f(size); + + GPUBatch *sphere = GPU_batch_preset_sphere(2); + GPU_batch_program_set_builtin(sphere, GPU_SHADER_SIMPLE_LIGHTING); + GPU_batch_uniform_4f(sphere, "color", diffuse[0], diffuse[1], diffuse[2], 1.0f); + GPU_batch_uniform_3fv(sphere, "light", light); + GPU_batch_draw(sphere); + + /* Restore. */ + GPU_face_culling(GPU_CULL_NONE); + + /* AA circle */ + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformColor3ubv(wcol->inner); + + GPU_blend(GPU_BLEND_ALPHA); + GPU_line_smooth(true); + imm_draw_circle_wire_2d(pos, 0.0f, 0.0f, 1.0f, 32); + GPU_blend(GPU_BLEND_NONE); + GPU_line_smooth(false); + + if (use_project_matrix) { + GPU_matrix_pop_projection(); + } + + /* matrix after circle */ + GPU_matrix_pop(); + + immUnbindProgram(); +} + +static void ui_draw_but_curve_grid(const uint pos, + const rcti *rect, + const float zoom_x, + const float zoom_y, + const float offset_x, + const float offset_y, + const float step) +{ + const float start_x = (ceilf(offset_x / step) * step - offset_x) * zoom_x + rect->xmin; + const float start_y = (ceilf(offset_y / step) * step - offset_y) * zoom_y + rect->ymin; + + const int line_count_x = ceilf((rect->xmax - start_x) / (step * zoom_x)); + const int line_count_y = ceilf((rect->ymax - start_y) / (step * zoom_y)); + + if (line_count_x + line_count_y == 0) { + return; + } + + immBegin(GPU_PRIM_LINES, (line_count_x + line_count_y) * 2); + for (int i = 0; i < line_count_x; i++) { + const float x = start_x + i * step * zoom_x; + immVertex2f(pos, x, rect->ymin); + immVertex2f(pos, x, rect->ymax); + } + for (int i = 0; i < line_count_y; i++) { + const float y = start_y + i * step * zoom_y; + immVertex2f(pos, rect->xmin, y); + immVertex2f(pos, rect->xmax, y); + } + immEnd(); +} + +static void gl_shaded_color_get(const uchar color[3], int shade, uchar r_color[3]) +{ + r_color[0] = color[0] - shade > 0 ? color[0] - shade : 0; + r_color[1] = color[1] - shade > 0 ? color[1] - shade : 0; + r_color[2] = color[2] - shade > 0 ? color[2] - shade : 0; +} + +static void gl_shaded_color_get_fl(const uchar *color, int shade, float r_color[3]) +{ + uchar color_shaded[3]; + gl_shaded_color_get(color, shade, color_shaded); + rgb_uchar_to_float(r_color, color_shaded); +} + +static void gl_shaded_color(const uchar *color, int shade) +{ + uchar color_shaded[3]; + gl_shaded_color_get(color, shade, color_shaded); + immUniformColor3ubv(color_shaded); +} + +void ui_draw_but_CURVE(ARegion *region, uiBut *but, const uiWidgetColors *wcol, const rcti *rect) +{ + uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; + CurveMapping *cumap = (but_cumap->edit_cumap == NULL) ? (CurveMapping *)but->poin : + but_cumap->edit_cumap; + + const float clip_size_x = BLI_rctf_size_x(&cumap->curr); + const float clip_size_y = BLI_rctf_size_y(&cumap->curr); + + /* zero-sized curve */ + if (clip_size_x == 0.0f || clip_size_y == 0.0f) { + return; + } + + /* calculate offset and zoom */ + const float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / clip_size_x; + const float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / clip_size_y; + const float offsx = cumap->curr.xmin - (1.0f / zoomx); + const float offsy = cumap->curr.ymin - (1.0f / zoomy); + + /* exit early if too narrow */ + if (zoomx == 0.0f) { + return; + } + + CurveMap *cuma = &cumap->cm[cumap->cur]; + + /* need scissor test, curve can draw outside of boundary */ + int scissor[4]; + GPU_scissor_get(scissor); + rcti scissor_new = { + rect->xmin, + rect->xmax, + rect->ymin, + rect->ymax, + }; + const rcti scissor_region = {0, region->winx, 0, region->winy}; + BLI_rcti_isect(&scissor_new, &scissor_region, &scissor_new); + GPU_scissor(scissor_new.xmin, + scissor_new.ymin, + BLI_rcti_size_x(&scissor_new), + BLI_rcti_size_y(&scissor_new)); + + /* Do this first to not mess imm context */ + if (but_cumap->gradient_type == UI_GRAD_H) { + /* magic trigger for curve backgrounds */ + const float col[3] = {0.0f, 0.0f, 0.0f}; /* dummy arg */ + + rcti grid = { + rect->xmin + zoomx * (-offsx), + grid.xmin + zoomx, + rect->ymin + zoomy * (-offsy), + grid.ymin + zoomy, + }; + + ui_draw_gradient(&grid, col, UI_GRAD_H, 1.0f); + } + + GPU_line_width(1.0f); + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* backdrop */ + float color_backdrop[4] = {0, 0, 0, 1}; + + if (but_cumap->gradient_type == UI_GRAD_H) { + /* grid, hsv uses different grid */ + GPU_blend(GPU_BLEND_ALPHA); + ARRAY_SET_ITEMS(color_backdrop, 0, 0, 0, 48.0 / 255.0); + immUniformColor4fv(color_backdrop); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.1666666f); + GPU_blend(GPU_BLEND_NONE); + } + else { + if (cumap->flag & CUMA_DO_CLIP) { + gl_shaded_color_get_fl(wcol->inner, -20, color_backdrop); + immUniformColor3fv(color_backdrop); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + immUniformColor3ubv(wcol->inner); + immRectf(pos, + rect->xmin + zoomx * (cumap->clipr.xmin - offsx), + rect->ymin + zoomy * (cumap->clipr.ymin - offsy), + rect->xmin + zoomx * (cumap->clipr.xmax - offsx), + rect->ymin + zoomy * (cumap->clipr.ymax - offsy)); + } + else { + rgb_uchar_to_float(color_backdrop, wcol->inner); + immUniformColor3fv(color_backdrop); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + } + + /* grid, every 0.25 step */ + gl_shaded_color(wcol->inner, -16); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f); + /* grid, every 1.0 step */ + gl_shaded_color(wcol->inner, -24); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f); + /* axes */ + gl_shaded_color(wcol->inner, -50); + immBegin(GPU_PRIM_LINES, 4); + immVertex2f(pos, rect->xmin, rect->ymin + zoomy * (-offsy)); + immVertex2f(pos, rect->xmax, rect->ymin + zoomy * (-offsy)); + immVertex2f(pos, rect->xmin + zoomx * (-offsx), rect->ymin); + immVertex2f(pos, rect->xmin + zoomx * (-offsx), rect->ymax); + immEnd(); + } + + /* cfra option */ + /* XXX 2.48 */ +#if 0 + if (cumap->flag & CUMA_DRAW_CFRA) { + immUniformColor3ub(0x60, 0xc0, 0x40); + immBegin(GPU_PRIM_LINES, 2); + immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[0] - offsx), rect->ymin); + immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[0] - offsx), rect->ymax); + immEnd(); + } +#endif + /* sample option */ + + if (cumap->flag & CUMA_DRAW_SAMPLE) { + immBegin(GPU_PRIM_LINES, 2); /* will draw one of the following 3 lines */ + if (but_cumap->gradient_type == UI_GRAD_H) { + float tsample[3]; + float hsv[3]; + linearrgb_to_srgb_v3_v3(tsample, cumap->sample); + rgb_to_hsv_v(tsample, hsv); + immUniformColor3ub(240, 240, 240); + + immVertex2f(pos, rect->xmin + zoomx * (hsv[0] - offsx), rect->ymin); + immVertex2f(pos, rect->xmin + zoomx * (hsv[0] - offsx), rect->ymax); + } + else if (cumap->cur == 3) { + const float lum = IMB_colormanagement_get_luminance(cumap->sample); + immUniformColor3ub(240, 240, 240); + + immVertex2f(pos, rect->xmin + zoomx * (lum - offsx), rect->ymin); + immVertex2f(pos, rect->xmin + zoomx * (lum - offsx), rect->ymax); + } + else { + if (cumap->cur == 0) { + immUniformColor3ub(240, 100, 100); + } + else if (cumap->cur == 1) { + immUniformColor3ub(100, 240, 100); + } + else { + immUniformColor3ub(100, 100, 240); + } + + immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[cumap->cur] - offsx), rect->ymin); + immVertex2f(pos, rect->xmin + zoomx * (cumap->sample[cumap->cur] - offsx), rect->ymax); + } + immEnd(); + } + immUnbindProgram(); + + if (cuma->table == NULL) { + BKE_curvemapping_changed(cumap, false); + } + + CurveMapPoint *cmp = cuma->table; + rctf line_range; + + /* First curve point. */ + if ((cumap->flag & CUMA_EXTEND_EXTRAPOLATE) == 0) { + line_range.xmin = rect->xmin; + line_range.ymin = rect->ymin + zoomy * (cmp[0].y - offsy); + } + else { + line_range.xmin = rect->xmin + zoomx * (cmp[0].x - offsx + cuma->ext_in[0]); + line_range.ymin = rect->ymin + zoomy * (cmp[0].y - offsy + cuma->ext_in[1]); + } + /* Last curve point. */ + if ((cumap->flag & CUMA_EXTEND_EXTRAPOLATE) == 0) { + line_range.xmax = rect->xmax; + line_range.ymax = rect->ymin + zoomy * (cmp[CM_TABLE].y - offsy); + } + else { + line_range.xmax = rect->xmin + zoomx * (cmp[CM_TABLE].x - offsx - cuma->ext_out[0]); + line_range.ymax = rect->ymin + zoomy * (cmp[CM_TABLE].y - offsy - cuma->ext_out[1]); + } + + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + + /* Curve filled. */ + immUniformColor3ubvAlpha(wcol->item, 128); + immBegin(GPU_PRIM_TRI_STRIP, (CM_TABLE * 2 + 2) + 4); + immVertex2f(pos, line_range.xmin, rect->ymin); + immVertex2f(pos, line_range.xmin, line_range.ymin); + for (int a = 0; a <= CM_TABLE; a++) { + const float fx = rect->xmin + zoomx * (cmp[a].x - offsx); + const float fy = rect->ymin + zoomy * (cmp[a].y - offsy); + immVertex2f(pos, fx, rect->ymin); + immVertex2f(pos, fx, fy); + } + immVertex2f(pos, line_range.xmax, rect->ymin); + immVertex2f(pos, line_range.xmax, line_range.ymax); + immEnd(); + + /* Curve line. */ + GPU_line_width(1.0f); + immUniformColor3ubvAlpha(wcol->item, 255); + GPU_line_smooth(true); + immBegin(GPU_PRIM_LINE_STRIP, (CM_TABLE + 1) + 2); + immVertex2f(pos, line_range.xmin, line_range.ymin); + for (int a = 0; a <= CM_TABLE; a++) { + const float fx = rect->xmin + zoomx * (cmp[a].x - offsx); + const float fy = rect->ymin + zoomy * (cmp[a].y - offsy); + immVertex2f(pos, fx, fy); + } + immVertex2f(pos, line_range.xmax, line_range.ymax); + immEnd(); + + /* Reset state for fill & line. */ + GPU_line_smooth(false); + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); + + /* The points, use aspect to make them visible on edges. */ + format = immVertexFormat(); + pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + + /* Calculate vertex colors based on text theme. */ + float color_vert[4], color_vert_select[4]; + UI_GetThemeColor4fv(TH_TEXT_HI, color_vert); + UI_GetThemeColor4fv(TH_TEXT, color_vert_select); + if (len_squared_v3v3(color_vert, color_vert_select) < 0.1f) { + interp_v3_v3v3(color_vert, color_vert_select, color_backdrop, 0.75f); + } + if (len_squared_v3(color_vert) > len_squared_v3(color_vert_select)) { + /* Ensure brightest text color is used for selection. */ + swap_v3_v3(color_vert, color_vert_select); + } + + cmp = cuma->curve; + GPU_point_size(max_ff(1.0f, min_ff(UI_DPI_FAC / but->block->aspect * 4.0f, 4.0f))); + immBegin(GPU_PRIM_POINTS, cuma->totpoint); + for (int a = 0; a < cuma->totpoint; a++) { + const float fx = rect->xmin + zoomx * (cmp[a].x - offsx); + const float fy = rect->ymin + zoomy * (cmp[a].y - offsy); + immAttr4fv(col, (cmp[a].flag & CUMA_SELECT) ? color_vert_select : color_vert); + immVertex2f(pos, fx, fy); + } + immEnd(); + immUnbindProgram(); + + /* Restore scissor-test. */ + GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + + /* outline */ + format = immVertexFormat(); + pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor3ubv(wcol->outline); + imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + + immUnbindProgram(); +} + +/** + * Helper for #ui_draw_but_CURVEPROFILE. Used to tell whether to draw a control point's handles. + */ +static bool point_draw_handles(CurveProfilePoint *point) +{ + return (point->flag & PROF_SELECT && + (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) || + ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT); +} + +/** + * Draws the curve profile widget. Somewhat similar to ui_draw_but_CURVE. + */ +void ui_draw_but_CURVEPROFILE(ARegion *region, + uiBut *but, + const uiWidgetColors *wcol, + const rcti *rect) +{ + float fx, fy; + + uiButCurveProfile *but_profile = (uiButCurveProfile *)but; + CurveProfile *profile = (but_profile->edit_profile == NULL) ? (CurveProfile *)but->poin : + but_profile->edit_profile; + + /* Calculate offset and zoom. */ + const float zoomx = (BLI_rcti_size_x(rect) - 2.0f) / BLI_rctf_size_x(&profile->view_rect); + const float zoomy = (BLI_rcti_size_y(rect) - 2.0f) / BLI_rctf_size_y(&profile->view_rect); + const float offsx = profile->view_rect.xmin - (1.0f / zoomx); + const float offsy = profile->view_rect.ymin - (1.0f / zoomy); + + /* Exit early if too narrow. */ + if (zoomx == 0.0f) { + return; + } + + /* Test needed because path can draw outside of boundary. */ + int scissor[4]; + GPU_scissor_get(scissor); + rcti scissor_new = { + rect->xmin, + rect->ymin, + rect->xmax, + rect->ymax, + }; + const rcti scissor_region = {0, region->winx, 0, region->winy}; + BLI_rcti_isect(&scissor_new, &scissor_region, &scissor_new); + GPU_scissor(scissor_new.xmin, + scissor_new.ymin, + BLI_rcti_size_x(&scissor_new), + BLI_rcti_size_y(&scissor_new)); + + GPU_line_width(1.0f); + + GPUVertFormat *format = immVertexFormat(); + uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* Draw the backdrop. */ + float color_backdrop[4] = {0, 0, 0, 1}; + if (profile->flag & PROF_USE_CLIP) { + gl_shaded_color_get_fl((uchar *)wcol->inner, -20, color_backdrop); + immUniformColor3fv(color_backdrop); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + immUniformColor3ubv((uchar *)wcol->inner); + immRectf(pos, + rect->xmin + zoomx * (profile->clip_rect.xmin - offsx), + rect->ymin + zoomy * (profile->clip_rect.ymin - offsy), + rect->xmin + zoomx * (profile->clip_rect.xmax - offsx), + rect->ymin + zoomy * (profile->clip_rect.ymax - offsy)); + } + else { + rgb_uchar_to_float(color_backdrop, (uchar *)wcol->inner); + immUniformColor3fv(color_backdrop); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + } + + /* 0.25 step grid. */ + gl_shaded_color((uchar *)wcol->inner, -16); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 0.25f); + /* 1.0 step grid. */ + gl_shaded_color((uchar *)wcol->inner, -24); + ui_draw_but_curve_grid(pos, rect, zoomx, zoomy, offsx, offsy, 1.0f); + + /* Draw the path's fill. */ + if (profile->table == NULL) { + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + } + CurveProfilePoint *pts = profile->table; + /* Also add the last points on the right and bottom edges to close off the fill polygon. */ + const bool add_left_tri = profile->view_rect.xmin < 0.0f; + const bool add_bottom_tri = profile->view_rect.ymin < 0.0f; + uint tot_points = (uint)PROF_TABLE_LEN(profile->path_len) + 1 + add_left_tri + add_bottom_tri; + const uint tot_triangles = tot_points - 2; + + /* Create array of the positions of the table's points. */ + float(*table_coords)[2] = (float(*)[2])MEM_mallocN(sizeof(*table_coords) * tot_points, + "table x coords"); + for (uint i = 0; i < (uint)PROF_TABLE_LEN(profile->path_len); + i++) { /* Only add the points from the table here. */ + table_coords[i][0] = pts[i].x; + table_coords[i][1] = pts[i].y; + } + /* Using some extra margin (-1.0f) for the coordinates used to complete the polygon + * avoids the profile line crossing itself in some common situations, which can lead to + * incorrect triangulation. See T841183. */ + if (add_left_tri && add_bottom_tri) { + /* Add left side, bottom left corner, and bottom side points. */ + table_coords[tot_points - 3][0] = profile->view_rect.xmin - 1.0f; + table_coords[tot_points - 3][1] = 1.0f; + table_coords[tot_points - 2][0] = profile->view_rect.xmin - 1.0f; + table_coords[tot_points - 2][1] = profile->view_rect.ymin - 1.0f; + table_coords[tot_points - 1][0] = 1.0f; + table_coords[tot_points - 1][1] = profile->view_rect.ymin - 1.0f; + } + else if (add_left_tri) { + /* Add the left side and bottom left corner points. */ + table_coords[tot_points - 2][0] = profile->view_rect.xmin - 1.0f; + table_coords[tot_points - 2][1] = 1.0f; + table_coords[tot_points - 1][0] = profile->view_rect.xmin - 1.0f; + table_coords[tot_points - 1][1] = -1.0f; + } + else if (add_bottom_tri) { + /* Add the bottom side and bottom left corner points. */ + table_coords[tot_points - 2][0] = -1.0f; + table_coords[tot_points - 2][1] = profile->view_rect.ymin - 1.0f; + table_coords[tot_points - 1][0] = 1.0f; + table_coords[tot_points - 1][1] = profile->view_rect.ymin - 1.0f; + } + else { + /* Just add the bottom corner point. Side points would be redundant anyway. */ + table_coords[tot_points - 1][0] = -1.0f; + table_coords[tot_points - 1][1] = -1.0f; + } + + /* Calculate the table point indices of the triangles for the profile's fill. */ + uint(*tri_indices)[3] = (uint(*)[3])MEM_mallocN(sizeof(*tri_indices) * tot_triangles, + "return tri indices"); + BLI_polyfill_calc(table_coords, tot_points, -1, tri_indices); + + /* Draw the triangles for the profile fill. */ + immUniformColor3ubvAlpha((const uchar *)wcol->item, 128); + GPU_blend(GPU_BLEND_ALPHA); + GPU_polygon_smooth(false); + immBegin(GPU_PRIM_TRIS, 3 * tot_triangles); + for (uint i = 0; i < tot_triangles; i++) { + for (uint j = 0; j < 3; j++) { + uint *tri = tri_indices[i]; + fx = rect->xmin + zoomx * (table_coords[tri[j]][0] - offsx); + fy = rect->ymin + zoomy * (table_coords[tri[j]][1] - offsy); + immVertex2f(pos, fx, fy); + } + } + immEnd(); + MEM_freeN(tri_indices); + + /* Draw the profile's path so the edge stands out a bit. */ + tot_points -= (add_left_tri + add_left_tri); + GPU_line_width(1.0f); + immUniformColor3ubvAlpha((const uchar *)wcol->item, 255); + GPU_line_smooth(true); + immBegin(GPU_PRIM_LINE_STRIP, tot_points - 1); + for (uint i = 0; i < tot_points - 1; i++) { + fx = rect->xmin + zoomx * (table_coords[i][0] - offsx); + fy = rect->ymin + zoomy * (table_coords[i][1] - offsy); + immVertex2f(pos, fx, fy); + } + immEnd(); + MEM_freeN(table_coords); + + /* Draw the handles for the selected control points. */ + pts = profile->path; + tot_points = (uint)profile->path_len; + int selected_free_points = 0; + for (uint i = 0; i < tot_points; i++) { + if (point_draw_handles(&pts[i])) { + selected_free_points++; + } + } + /* Draw the lines to the handles from the points. */ + if (selected_free_points > 0) { + GPU_line_width(1.0f); + gl_shaded_color((uchar *)wcol->inner, -24); + GPU_line_smooth(true); + immBegin(GPU_PRIM_LINES, selected_free_points * 4); + float ptx, pty; + for (uint i = 0; i < tot_points; i++) { + if (point_draw_handles(&pts[i])) { + ptx = rect->xmin + zoomx * (pts[i].x - offsx); + pty = rect->ymin + zoomy * (pts[i].y - offsy); + + fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx); + fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy); + immVertex2f(pos, ptx, pty); + immVertex2f(pos, fx, fy); + + fx = rect->xmin + zoomx * (pts[i].h2_loc[0] - offsx); + fy = rect->ymin + zoomy * (pts[i].h2_loc[1] - offsy); + immVertex2f(pos, ptx, pty); + immVertex2f(pos, fx, fy); + } + } + immEnd(); + } + immUnbindProgram(); + + /* New GPU instructions for control points and sampled points. */ + format = immVertexFormat(); + pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + + /* Calculate vertex colors based on text theme. */ + float color_vert[4], color_vert_select[4], color_sample[4]; + UI_GetThemeColor4fv(TH_TEXT_HI, color_vert); + UI_GetThemeColor4fv(TH_TEXT, color_vert_select); + color_sample[0] = (float)wcol->item[0] / 255.0f; + color_sample[1] = (float)wcol->item[1] / 255.0f; + color_sample[2] = (float)wcol->item[2] / 255.0f; + color_sample[3] = (float)wcol->item[3] / 255.0f; + if (len_squared_v3v3(color_vert, color_vert_select) < 0.1f) { + interp_v3_v3v3(color_vert, color_vert_select, color_backdrop, 0.75f); + } + if (len_squared_v3(color_vert) > len_squared_v3(color_vert_select)) { + /* Ensure brightest text color is used for selection. */ + swap_v3_v3(color_vert, color_vert_select); + } + + /* Draw the control points. */ + GPU_line_smooth(false); + GPU_blend(GPU_BLEND_NONE); + GPU_point_size(max_ff(3.0f, min_ff(UI_DPI_FAC / but->block->aspect * 5.0f, 5.0f))); + immBegin(GPU_PRIM_POINTS, tot_points); + for (uint i = 0; i < tot_points; i++) { + fx = rect->xmin + zoomx * (pts[i].x - offsx); + fy = rect->ymin + zoomy * (pts[i].y - offsy); + immAttr4fv(col, (pts[i].flag & PROF_SELECT) ? color_vert_select : color_vert); + immVertex2f(pos, fx, fy); + } + immEnd(); + + /* Draw the handle points. */ + if (selected_free_points > 0) { + GPU_line_smooth(false); + GPU_blend(GPU_BLEND_NONE); + GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 4.0f, 4.0f))); + immBegin(GPU_PRIM_POINTS, selected_free_points * 2); + for (uint i = 0; i < tot_points; i++) { + if (point_draw_handles(&pts[i])) { + fx = rect->xmin + zoomx * (pts[i].h1_loc[0] - offsx); + fy = rect->ymin + zoomy * (pts[i].h1_loc[1] - offsy); + immAttr4fv(col, (pts[i].flag & PROF_H1_SELECT) ? color_vert_select : color_vert); + immVertex2f(pos, fx, fy); + + fx = rect->xmin + zoomx * (pts[i].h2_loc[0] - offsx); + fy = rect->ymin + zoomy * (pts[i].h2_loc[1] - offsy); + immAttr4fv(col, (pts[i].flag & PROF_H2_SELECT) ? color_vert_select : color_vert); + immVertex2f(pos, fx, fy); + } + } + immEnd(); + } + + /* Draw the sampled points in addition to the control points if they have been created */ + pts = profile->segments; + tot_points = (uint)profile->segments_len; + if (tot_points > 0 && pts) { + GPU_point_size(max_ff(2.0f, min_ff(UI_DPI_FAC / but->block->aspect * 3.0f, 3.0f))); + immBegin(GPU_PRIM_POINTS, tot_points); + for (uint i = 0; i < tot_points; i++) { + fx = rect->xmin + zoomx * (pts[i].x - offsx); + fy = rect->ymin + zoomy * (pts[i].y - offsy); + immAttr4fv(col, color_sample); + immVertex2f(pos, fx, fy); + } + immEnd(); + } + immUnbindProgram(); + + /* Restore scissor-test. */ + GPU_scissor(scissor[0], scissor[1], scissor[2], scissor[3]); + + /* Outline */ + format = immVertexFormat(); + pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor3ubv((const uchar *)wcol->outline); + imm_draw_box_wire_2d(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + immUnbindProgram(); +} + +void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(region), + uiBut *but, + const uiWidgetColors *UNUSED(wcol), + const rcti *recti) +{ + bool ok = false; + MovieClipScopes *scopes = (MovieClipScopes *)but->poin; + + rctf rect = { + (float)recti->xmin + 1, + (float)recti->xmax - 1, + (float)recti->ymin + 1, + (float)recti->ymax - 1, + }; + + const int width = BLI_rctf_size_x(&rect) + 1; + const int height = BLI_rctf_size_y(&rect); + + GPU_blend(GPU_BLEND_ALPHA); + + /* need scissor test, preview image can draw outside of boundary */ + int scissor[4]; + GPU_scissor_get(scissor); + GPU_scissor((rect.xmin - 1), + (rect.ymin - 1), + (rect.xmax + 1) - (rect.xmin - 1), + (rect.ymax + 1) - (rect.ymin - 1)); + + if (scopes->track_disabled) { + const float color[4] = {0.7f, 0.3f, 0.3f, 0.3f}; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + const rctf box_rect{ + rect.xmin - 1, + rect.xmax + 1, + rect.ymin, + rect.ymax + 1, + }; + UI_draw_roundbox_4fv(&box_rect, true, 3.0f, color); + + ok = true; + } + else if ((scopes->track_search) && + ((!scopes->track_preview) || + (scopes->track_preview->x != width || scopes->track_preview->y != height))) { + if (scopes->track_preview) { + IMB_freeImBuf(scopes->track_preview); + } + + ImBuf *tmpibuf = BKE_tracking_sample_pattern(scopes->frame_width, + scopes->frame_height, + scopes->track_search, + scopes->track, + &scopes->undist_marker, + true, + scopes->use_track_mask, + width, + height, + scopes->track_pos); + if (tmpibuf) { + if (tmpibuf->rect_float) { + IMB_rect_from_float(tmpibuf); + } + + if (tmpibuf->rect) { + scopes->track_preview = tmpibuf; + } + else { + IMB_freeImBuf(tmpibuf); + } + } + } + + if (!ok && scopes->track_preview) { + GPU_matrix_push(); + + /* draw content of pattern area */ + GPU_scissor(rect.xmin, rect.ymin, scissor[2], scissor[3]); + + if (width > 0 && height > 0) { + ImBuf *drawibuf = scopes->track_preview; + float col_sel[4], col_outline[4]; + + if (scopes->use_track_mask) { + const float color[4] = {0.0f, 0.0f, 0.0f, 0.3f}; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_4fv( + &(const rctf){ + rect.xmin - 1, + rect.xmax + 1, + rect.ymin, + rect.ymax + 1, + }, + true, + 3.0f, + color); + } + + IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR); + immDrawPixelsTex(&state, + rect.xmin, + rect.ymin + 1, + drawibuf->x, + drawibuf->y, + GPU_RGBA8, + true, + drawibuf->rect, + 1.0f, + 1.0f, + NULL); + + /* draw cross for pixel position */ + GPU_matrix_translate_2f(rect.xmin + scopes->track_pos[0], rect.ymin + scopes->track_pos[1]); + GPU_scissor(rect.xmin, rect.ymin, BLI_rctf_size_x(&rect), BLI_rctf_size_y(&rect)); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + const uint col = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR); + + UI_GetThemeColor4fv(TH_SEL_MARKER, col_sel); + UI_GetThemeColor4fv(TH_MARKER_OUTLINE, col_outline); + + /* Do stipple cross with geometry */ + immBegin(GPU_PRIM_LINES, 7 * 2 * 2); + const float pos_sel[8] = {-10.0f, -7.0f, -4.0f, -1.0f, 2.0f, 5.0f, 8.0f, 11.0f}; + for (int axe = 0; axe < 2; axe++) { + for (int i = 0; i < 7; i++) { + const float x1 = pos_sel[i] * (1 - axe); + const float y1 = pos_sel[i] * axe; + const float x2 = pos_sel[i + 1] * (1 - axe); + const float y2 = pos_sel[i + 1] * axe; + + if (i % 2 == 1) { + immAttr4fv(col, col_sel); + } + else { + immAttr4fv(col, col_outline); + } + + immVertex2f(pos, x1, y1); + immVertex2f(pos, x2, y2); + } + } + immEnd(); + + immUnbindProgram(); + } + + GPU_matrix_pop(); + + ok = true; + } + + if (!ok) { + const float color[4] = {0.0f, 0.0f, 0.0f, 0.3f}; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + const rctf box_rect{ + rect.xmin - 1, + rect.xmax + 1, + rect.ymin, + rect.ymax + 1, + }; + UI_draw_roundbox_4fv(&box_rect, true, 3.0f, color); + } + + /* Restore scissor test. */ + GPU_scissor(UNPACK4(scissor)); + /* outline */ + draw_scope_end(&rect); + + GPU_blend(GPU_BLEND_NONE); +} + +/* ****************************************************** */ + +/* TODO: high quality UI drop shadows using GLSL shader and single draw call + * would replace / modify the following 3 functions - merwin + */ + +static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, uchar alpha) +{ + /** + *
+   *          v1-_
+   *          |   -_v2
+   *          |     |
+   *          |     |
+   *          |     |
+   * v7_______v3____v4
+   * \        |     /
+   *  \       |   _v5
+   *  v8______v6_-
+   * 
+ */ + const float v1[2] = {rect->xmax, rect->ymax - 0.3f * shadsize}; + const float v2[2] = {rect->xmax + shadsize, rect->ymax - 0.75f * shadsize}; + const float v3[2] = {rect->xmax, rect->ymin}; + const float v4[2] = {rect->xmax + shadsize, rect->ymin}; + + const float v5[2] = {rect->xmax + 0.7f * shadsize, rect->ymin - 0.7f * shadsize}; + + const float v6[2] = {rect->xmax, rect->ymin - shadsize}; + const float v7[2] = {rect->xmin + 0.3f * shadsize, rect->ymin}; + const float v8[2] = {rect->xmin + 0.5f * shadsize, rect->ymin - shadsize}; + + /* right quad */ + immAttr4ub(color, 0, 0, 0, alpha); + immVertex2fv(pos, v3); + immVertex2fv(pos, v1); + immAttr4ub(color, 0, 0, 0, 0); + immVertex2fv(pos, v2); + + immVertex2fv(pos, v2); + immVertex2fv(pos, v4); + immAttr4ub(color, 0, 0, 0, alpha); + immVertex2fv(pos, v3); + + /* corner shape */ + /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */ + immVertex2fv(pos, v3); + immAttr4ub(color, 0, 0, 0, 0); + immVertex2fv(pos, v4); + immVertex2fv(pos, v5); + + immVertex2fv(pos, v5); + immVertex2fv(pos, v6); + immAttr4ub(color, 0, 0, 0, alpha); + immVertex2fv(pos, v3); + + /* bottom quad */ + /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */ + immVertex2fv(pos, v3); + immAttr4ub(color, 0, 0, 0, 0); + immVertex2fv(pos, v6); + immVertex2fv(pos, v8); + + immVertex2fv(pos, v8); + immAttr4ub(color, 0, 0, 0, alpha); + immVertex2fv(pos, v7); + immVertex2fv(pos, v3); +} + +void UI_draw_box_shadow(const rctf *rect, uchar alpha) +{ + GPU_blend(GPU_BLEND_ALPHA); + + GPUVertFormat *format = immVertexFormat(); + const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + uint color = GPU_vertformat_attr_add( + format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); + + immBindBuiltinProgram(GPU_SHADER_2D_SMOOTH_COLOR); + + immBegin(GPU_PRIM_TRIS, 54); + + /* accumulated outline boxes to make shade not linear, is more pleasant */ + ui_shadowbox(rect, pos, color, 11.0, (20 * alpha) >> 8); + ui_shadowbox(rect, pos, color, 7.0, (40 * alpha) >> 8); + ui_shadowbox(rect, pos, color, 5.0, (80 * alpha) >> 8); + + immEnd(); + + immUnbindProgram(); + + GPU_blend(GPU_BLEND_NONE); +} + +void ui_draw_dropshadow( + const rctf *rct, float radius, float aspect, float alpha, int UNUSED(select)) +{ + float rad; + + if (radius > (BLI_rctf_size_y(rct) - 10.0f) * 0.5f) { + rad = (BLI_rctf_size_y(rct) - 10.0f) * 0.5f; + } + else { + rad = radius; + } + + int a, i = 12; +#if 0 + if (select) { + a = i * aspect; /* same as below */ + } + else +#endif + { + a = i * aspect; + } + + GPU_blend(GPU_BLEND_ALPHA); + const float dalpha = alpha * 2.0f / 255.0f; + float calpha = dalpha; + float visibility = 1.0f; + for (; i--;) { + /* alpha ranges from 2 to 20 or so */ +#if 0 /* Old Method (pre 2.8) */ + float color[4] = {0.0f, 0.0f, 0.0f, calpha}; + UI_draw_roundbox_4fv( + true, rct->xmin - a, rct->ymin - a, rct->xmax + a, rct->ymax - 10.0f + a, rad + a, color); +#endif + /* Compute final visibility to match old method result. */ + /* TODO we could just find a better fit function inside the shader instead of this. */ + visibility = visibility * (1.0f - calpha); + calpha += dalpha; + } + + uiWidgetBaseParameters widget_params; + widget_params.recti.xmin = rct->xmin; + widget_params.recti.ymin = rct->ymin; + widget_params.recti.xmax = rct->xmax; + widget_params.recti.ymax = rct->ymax - 10.0f; + widget_params.rect.xmin = rct->xmin - a; + widget_params.rect.ymin = rct->ymin - a; + widget_params.rect.xmax = rct->xmax + a; + widget_params.rect.ymax = rct->ymax - 10.0f + a; + widget_params.radi = rad; + widget_params.rad = rad + a; + widget_params.round_corners[0] = (roundboxtype & UI_CNR_BOTTOM_LEFT) ? 1.0f : 0.0f; + widget_params.round_corners[1] = (roundboxtype & UI_CNR_BOTTOM_RIGHT) ? 1.0f : 0.0f; + widget_params.round_corners[2] = (roundboxtype & UI_CNR_TOP_RIGHT) ? 1.0f : 0.0f; + widget_params.round_corners[3] = (roundboxtype & UI_CNR_TOP_LEFT) ? 1.0f : 0.0f; + widget_params.alpha_discard = 1.0f; + + GPUBatch *batch = ui_batch_roundbox_shadow_get(); + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_WIDGET_SHADOW); + GPU_batch_uniform_4fv_array(batch, "parameters", 4, (const float(*)[4]) & widget_params); + GPU_batch_uniform_1f(batch, "alpha", 1.0f - visibility); + GPU_batch_draw(batch); + + /* outline emphasis */ + const float color[4] = {0.0f, 0.0f, 0.0f, 0.4f}; + UI_draw_roundbox_4fv( + &(const rctf){ + rct->xmin - 0.5f, + rct->xmax + 0.5f, + rct->ymin - 0.5f, + rct->ymax + 0.5f, + }, + false, + radius + 0.5f, + color); + + GPU_blend(GPU_BLEND_NONE); +} diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c deleted file mode 100644 index 5a254db0eec..00000000000 --- a/source/blender/editors/interface/interface_handlers.c +++ /dev/null @@ -1,11154 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2008 Blender Foundation. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_brush_types.h" -#include "DNA_curveprofile_types.h" -#include "DNA_scene_types.h" -#include "DNA_screen_types.h" - -#include "BLI_linklist.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_string_cursor_utf8.h" -#include "BLI_string_utf8.h" -#include "BLI_utildefines.h" - -#include "PIL_time.h" - -#include "BKE_animsys.h" -#include "BKE_blender_undo.h" -#include "BKE_brush.h" -#include "BKE_colorband.h" -#include "BKE_colortools.h" -#include "BKE_context.h" -#include "BKE_curveprofile.h" -#include "BKE_movieclip.h" -#include "BKE_paint.h" -#include "BKE_report.h" -#include "BKE_screen.h" -#include "BKE_tracking.h" -#include "BKE_unit.h" - -#include "IMB_colormanagement.h" - -#include "ED_screen.h" -#include "ED_undo.h" - -#include "UI_interface.h" -#include "UI_view2d.h" - -#include "BLF_api.h" - -#include "interface_intern.h" - -#include "RNA_access.h" - -#include "WM_api.h" -#include "WM_types.h" -#include "wm_event_system.h" - -#ifdef WITH_INPUT_IME -# include "BLT_lang.h" -# include "BLT_translation.h" -# include "wm_window.h" -#endif - -/* -------------------------------------------------------------------- */ -/** \name Feature Defines - * - * These defines allow developers to locally toggle functionality which - * may be useful for testing (especially conflicts in dragging). - * Ideally the code would be refactored to support this functionality in a less fragile way. - * Until then keep these defines. - * \{ */ - -/** Place the mouse at the scaled down location when un-grabbing. */ -#define USE_CONT_MOUSE_CORRECT -/** Support dragging toggle buttons. */ -#define USE_DRAG_TOGGLE - -/** Support dragging multiple number buttons at once. */ -#define USE_DRAG_MULTINUM - -/** Allow dragging/editing all other selected items at once. */ -#define USE_ALLSELECT - -/** - * Check to avoid very small mouse-moves from jumping away from keyboard navigation, - * while larger mouse motion will override keyboard input, see: T34936. - */ -#define USE_KEYNAV_LIMIT - -/** Support dragging popups by their header. */ -#define USE_DRAG_POPUP - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Local Defines - * \{ */ - -/** - * The buffer side used for password strings, where the password is stored internally, - * but not displayed. - */ -#define UI_MAX_PASSWORD_STR 128 - -/** - * When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used, - * Use this as a maximum soft range for mapping cursor motion to the value. - * Otherwise min/max of #FLT_MAX, #INT_MAX cause small adjustments to jump to large numbers. - * - * This is needed for values such as location & dimensions which don't have a meaningful min/max, - * Instead of mapping cursor motion to the min/max, map the motion to the click-step. - * - * This value is multiplied by the click step to calculate a range to clamp the soft-range by. - * See: T68130 - */ -#define UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX 1000 - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Local Prototypes - * \{ */ - -static int ui_do_but_EXIT(bContext *C, - uiBut *but, - struct uiHandleButtonData *data, - const wmEvent *event); -static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b); -static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str); -static void button_tooltip_timer_reset(bContext *C, uiBut *but); - -#ifdef USE_KEYNAV_LIMIT -static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event); -static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event); -#endif - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Structs & Defines - * \{ */ - -#define BUTTON_FLASH_DELAY 0.020 -#define MENU_SCROLL_INTERVAL 0.1 -#define PIE_MENU_INTERVAL 0.01 -#define BUTTON_AUTO_OPEN_THRESH 0.2 -#define BUTTON_MOUSE_TOWARDS_THRESH 1.0 -/** Pixels to move the cursor to get out of keyboard navigation. */ -#define BUTTON_KEYNAV_PX_LIMIT 8 - -/** Margin around the menu, use to check if we're moving towards this rectangle (in pixels). */ -#define MENU_TOWARDS_MARGIN 20 -/** Tolerance for closing menus (in pixels). */ -#define MENU_TOWARDS_WIGGLE_ROOM 64 -/** Drag-lock distance threshold (in pixels). */ -#define BUTTON_DRAGLOCK_THRESH 3 - -typedef enum uiButtonActivateType { - BUTTON_ACTIVATE_OVER, - BUTTON_ACTIVATE, - BUTTON_ACTIVATE_APPLY, - BUTTON_ACTIVATE_TEXT_EDITING, - BUTTON_ACTIVATE_OPEN, -} uiButtonActivateType; - -typedef enum uiHandleButtonState { - BUTTON_STATE_INIT, - BUTTON_STATE_HIGHLIGHT, - BUTTON_STATE_WAIT_FLASH, - BUTTON_STATE_WAIT_RELEASE, - BUTTON_STATE_WAIT_KEY_EVENT, - BUTTON_STATE_NUM_EDITING, - BUTTON_STATE_TEXT_EDITING, - BUTTON_STATE_TEXT_SELECTING, - BUTTON_STATE_MENU_OPEN, - BUTTON_STATE_WAIT_DRAG, - BUTTON_STATE_EXIT, -} uiHandleButtonState; - -typedef enum uiMenuScrollType { - MENU_SCROLL_UP, - MENU_SCROLL_DOWN, - MENU_SCROLL_TOP, - MENU_SCROLL_BOTTOM, -} uiMenuScrollType; - -#ifdef USE_ALLSELECT - -/* Unfortunately there's no good way handle more generally: - * (propagate single clicks on layer buttons to other objects) */ -# define USE_ALLSELECT_LAYER_HACK - -typedef struct uiSelectContextElem { - PointerRNA ptr; - union { - bool val_b; - int val_i; - float val_f; - }; -} uiSelectContextElem; - -typedef struct uiSelectContextStore { - uiSelectContextElem *elems; - int elems_len; - bool do_free; - bool is_enabled; - /* When set, simply copy values (don't apply difference). - * Rules are: - * - dragging numbers uses delta. - * - typing in values will assign to all. */ - bool is_copy; -} uiSelectContextStore; - -static bool ui_selectcontext_begin(bContext *C, - uiBut *but, - struct uiSelectContextStore *selctx_data); -static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data); -static void ui_selectcontext_apply(bContext *C, - uiBut *but, - struct uiSelectContextStore *selctx_data, - const double value, - const double value_orig); - -# define IS_ALLSELECT_EVENT(event) ((event)->alt != 0) - -/** just show a tinted color so users know its activated */ -# define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE - -#endif /* USE_ALLSELECT */ - -#ifdef USE_DRAG_MULTINUM - -/** - * how far to drag before we check for gesture direction (in pixels), - * note: half the height of a button is about right... */ -# define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4) - -/** - * How far to drag horizontally - * before we stop checking which buttons the gesture spans (in pixels), - * locking down the buttons so we can drag freely without worrying about vertical movement. - */ -# define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4) - -/** - * How strict to be when detecting a vertical gesture: - * [0.5 == sloppy], [0.9 == strict], (unsigned dot-product). - * - * \note We should be quite strict here, - * since doing a vertical gesture by accident should be avoided, - * however with some care a user should be able to do a vertical movement without _missing_. - */ -# define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f) - -/* a simple version of uiHandleButtonData when accessing multiple buttons */ -typedef struct uiButMultiState { - double origvalue; - uiBut *but; - -# ifdef USE_ALLSELECT - uiSelectContextStore select_others; -# endif -} uiButMultiState; - -typedef struct uiHandleButtonMulti { - enum { - /** gesture direction unknown, wait until mouse has moved enough... */ - BUTTON_MULTI_INIT_UNSET = 0, - /** vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */ - BUTTON_MULTI_INIT_SETUP, - /** flag buttons finished, apply horizontal motion to active and flagged */ - BUTTON_MULTI_INIT_ENABLE, - /** vertical gesture _not_ detected, take no further action */ - BUTTON_MULTI_INIT_DISABLE, - } init; - - bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */ - LinkNode *mbuts; - uiButStore *bs_mbuts; - - bool is_proportional; - - /* In some cases we directly apply the changes to multiple buttons, - * so we don't want to do it twice. */ - bool skip; - - /* before activating, we need to check gesture direction accumulate signed cursor movement - * here so we can tell if this is a vertical motion or not. */ - float drag_dir[2]; - - /* values copied direct from event->x,y - * used to detect buttons between the current and initial mouse position */ - int drag_start[2]; - - /* store x location once BUTTON_MULTI_INIT_SETUP is set, - * moving outside this sets BUTTON_MULTI_INIT_ENABLE */ - int drag_lock_x; - -} uiHandleButtonMulti; - -#endif /* USE_DRAG_MULTINUM */ - -typedef struct uiHandleButtonData { - wmWindowManager *wm; - wmWindow *window; - ScrArea *area; - ARegion *region; - - bool interactive; - - /* overall state */ - uiHandleButtonState state; - int retval; - /* booleans (could be made into flags) */ - bool cancel, escapecancel; - bool applied, applied_interactive; - bool changed_cursor; - wmTimer *flashtimer; - - /* edited value */ - /* use 'ui_textedit_string_set' to assign new strings */ - char *str; - char *origstr; - double value, origvalue, startvalue; - float vec[3], origvec[3]; -#if 0 /* UNUSED */ - int togdual, togonly; -#endif - ColorBand *coba; - - /* Tool-tip. */ - uint tooltip_force : 1; - - /* auto open */ - bool used_mouse; - wmTimer *autoopentimer; - - /* auto open (hold) */ - wmTimer *hold_action_timer; - - /* text selection/editing */ - /* size of 'str' (including terminator) */ - int maxlen; - /* Button text selection: - * extension direction, selextend, inside ui_do_but_TEX */ - int sel_pos_init; - /* Allow reallocating str/editstr and using 'maxlen' to track alloc size (maxlen + 1) */ - bool is_str_dynamic; - - /* number editing / dragging */ - /* coords are Window/uiBlock relative (depends on the button) */ - int draglastx, draglasty; - int dragstartx, dragstarty; - int draglastvalue; - int dragstartvalue; - bool dragchange, draglock; - int dragsel; - float dragf, dragfstart; - CBData *dragcbd; - - /** Soft min/max with #UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX applied. */ - float drag_map_soft_min; - float drag_map_soft_max; - -#ifdef USE_CONT_MOUSE_CORRECT - /* when ungrabbing buttons which are #ui_but_is_cursor_warp(), - * we may want to position them. - * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() - * to get this into a usable space. */ - float ungrab_mval[2]; -#endif - - /* menu open (watch UI_screen_free_active_but) */ - uiPopupBlockHandle *menu; - int menuretval; - - /* search box (watch UI_screen_free_active_but) */ - ARegion *searchbox; -#ifdef USE_KEYNAV_LIMIT - struct uiKeyNavLock searchbox_keynav_state; -#endif - -#ifdef USE_DRAG_MULTINUM - /* Multi-buttons will be updated in unison with the active button. */ - uiHandleButtonMulti multi_data; -#endif - -#ifdef USE_ALLSELECT - uiSelectContextStore select_others; -#endif - - /* Text field undo. */ - struct uiUndoStack_Text *undo_stack_text; - - /* post activate */ - uiButtonActivateType posttype; - uiBut *postbut; -} uiHandleButtonData; - -typedef struct uiAfterFunc { - struct uiAfterFunc *next, *prev; - - uiButHandleFunc func; - void *func_arg1; - void *func_arg2; - - uiButHandleNFunc funcN; - void *func_argN; - - uiButHandleRenameFunc rename_func; - void *rename_arg1; - void *rename_orig; - - uiBlockHandleFunc handle_func; - void *handle_func_arg; - int retval; - - uiMenuHandleFunc butm_func; - void *butm_func_arg; - int a2; - - wmOperator *popup_op; - wmOperatorType *optype; - int opcontext; - PointerRNA *opptr; - - PointerRNA rnapoin; - PropertyRNA *rnaprop; - - void *search_arg; - uiButSearchArgFreeFn search_arg_free_fn; - - bContextStore *context; - - char undostr[BKE_UNDO_STR_MAX]; -} uiAfterFunc; - -static void button_activate_init(bContext *C, - ARegion *region, - uiBut *but, - uiButtonActivateType type); -static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state); -static void button_activate_exit( - bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree); -static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata); -static void ui_handle_button_activate(bContext *C, - ARegion *region, - uiBut *but, - uiButtonActivateType type); -static bool ui_do_but_extra_operator_icon(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event); -static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, - uiHandleButtonData *data, - const wmEvent *event); - -#ifdef USE_DRAG_MULTINUM -static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block); -static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but); -#endif - -/* buttons clipboard */ -static ColorBand but_copypaste_coba = {0}; -static CurveMapping but_copypaste_curve = {0}; -static bool but_copypaste_curve_alive = false; -static CurveProfile but_copypaste_profile = {0}; -static bool but_copypaste_profile_alive = false; - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI Queries - * \{ */ - -bool ui_but_is_editing(const uiBut *but) -{ - uiHandleButtonData *data = but->active; - return (data && ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)); -} - -/* assumes event type is MOUSEPAN */ -void ui_pan_to_scroll(const wmEvent *event, int *type, int *val) -{ - static int lastdy = 0; - const int dy = WM_event_absolute_delta_y(event); - - /* This event should be originally from event->type, - * converting wrong event into wheel is bad, see T33803. */ - BLI_assert(*type == MOUSEPAN); - - /* sign differs, reset */ - if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) { - lastdy = dy; - } - else { - lastdy += dy; - - if (abs(lastdy) > (int)UI_UNIT_Y) { - *val = KM_PRESS; - - if (dy > 0) { - *type = WHEELUPMOUSE; - } - else { - *type = WHEELDOWNMOUSE; - } - - lastdy = 0; - } - } -} - -static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b) -{ - return ((but_a->type == but_b->type) && (but_a->alignnr == but_b->alignnr) && - (but_a->poin == but_b->poin) && (but_a->rnapoin.type == but_b->rnapoin.type) && - (but_a->rnaprop == but_b->rnaprop)); -} - -/** - * Finds the pressed button in an aligned row (typically an expanded enum). - * - * \param direction: Use when there may be multiple buttons pressed. - */ -uiBut *ui_but_find_select_in_enum(uiBut *but, int direction) -{ - uiBut *but_iter = but; - uiBut *but_found = NULL; - BLI_assert(ELEM(direction, -1, 1)); - - while ((but_iter->prev) && ui_but_find_select_in_enum__cmp(but_iter->prev, but)) { - but_iter = but_iter->prev; - } - - while (but_iter && ui_but_find_select_in_enum__cmp(but_iter, but)) { - if (but_iter->flag & UI_SELECT) { - but_found = but_iter; - if (direction == 1) { - break; - } - } - but_iter = but_iter->next; - } - - return but_found; -} - -static float ui_mouse_scale_warp_factor(const bool shift) -{ - return shift ? 0.05f : 1.0f; -} - -static void ui_mouse_scale_warp(uiHandleButtonData *data, - const float mx, - const float my, - float *r_mx, - float *r_my, - const bool shift) -{ - const float fac = ui_mouse_scale_warp_factor(shift); - - /* slow down the mouse, this is fairly picky */ - *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac); - *r_my = (data->dragstarty * (1.0f - fac) + my * fac); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI Utilities - * \{ */ - -/** - * Ignore mouse movements within some horizontal pixel threshold before starting to drag - */ -static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx) -{ - if (mx == data->draglastx) { - return false; - } - - if (data->draglock) { - if (abs(mx - data->dragstartx) <= BUTTON_DRAGLOCK_THRESH) { - return false; - } -#ifdef USE_DRAG_MULTINUM - if (ELEM(data->multi_data.init, BUTTON_MULTI_INIT_UNSET, BUTTON_MULTI_INIT_SETUP)) { - return false; - } -#endif - data->draglock = false; - data->dragstartx = mx; /* ignore mouse movement within drag-lock */ - } - - return true; -} - -static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop) -{ - /* Not very elegant, but ensures preference changes force re-save. */ - bool tag = false; - if (prop && !(RNA_property_flag(prop) & PROP_NO_DEG_UPDATE)) { - StructRNA *base = RNA_struct_base(ptr->type); - if (base == NULL) { - base = ptr->type; - } - if (ELEM(base, - &RNA_AddonPreferences, - &RNA_KeyConfigPreferences, - &RNA_KeyMapItem, - &RNA_UserAssetLibrary)) { - tag = true; - } - } - return tag; -} - -bool UI_but_is_userdef(const uiBut *but) -{ - /* This is read-only, RNA API isn't using const when it could. */ - return ui_rna_is_userdef((PointerRNA *)&but->rnapoin, but->rnaprop); -} - -static void ui_rna_update_preferences_dirty(PointerRNA *ptr, PropertyRNA *prop) -{ - if (ui_rna_is_userdef(ptr, prop)) { - U.runtime.is_dirty = true; - WM_main_add_notifier(NC_WINDOW, NULL); - } -} - -static void ui_but_update_preferences_dirty(uiBut *but) -{ - ui_rna_update_preferences_dirty(&but->rnapoin, but->rnaprop); -} - -static void ui_afterfunc_update_preferences_dirty(uiAfterFunc *after) -{ - ui_rna_update_preferences_dirty(&after->rnapoin, after->rnaprop); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Snap Values - * \{ */ - -enum eSnapType { - SNAP_OFF = 0, - SNAP_ON, - SNAP_ON_SMALL, -}; - -static enum eSnapType ui_event_to_snap(const wmEvent *event) -{ - return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF; -} - -static bool ui_event_is_snap(const wmEvent *event) -{ - return (ELEM(event->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) || - ELEM(event->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY)); -} - -static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue) -{ - const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12; - BLI_assert(snap != SNAP_OFF); - *r_hue = roundf((*r_hue) * snap_increment) / snap_increment; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Apply/Revert - * \{ */ - -static ListBase UIAfterFuncs = {NULL, NULL}; - -static uiAfterFunc *ui_afterfunc_new(void) -{ - uiAfterFunc *after = MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc"); - - BLI_addtail(&UIAfterFuncs, after); - - return after; -} - -/** - * For executing operators after the button is pressed. - * (some non operator buttons need to trigger operators), see: T37795. - * - * \note Can only call while handling buttons. - */ -PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props) -{ - PointerRNA *ptr = NULL; - uiAfterFunc *after = ui_afterfunc_new(); - - after->optype = ot; - after->opcontext = opcontext; - - if (create_props) { - ptr = MEM_callocN(sizeof(PointerRNA), __func__); - WM_operator_properties_create_ptr(ptr, ot); - after->opptr = ptr; - } - - return ptr; -} - -static void popup_check(bContext *C, wmOperator *op) -{ - if (op && op->type->check) { - op->type->check(C, op); - } -} - -/** - * Check if a #uiAfterFunc is needed for this button. - */ -static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but) -{ - return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop || - block->handle_func || (but->type == UI_BTYPE_BUT_MENU && block->butm_func) || - (block->handle && block->handle->popup_op)); -} - -static void ui_apply_but_func(bContext *C, uiBut *but) -{ - uiBlock *block = but->block; - - /* these functions are postponed and only executed after all other - * handling is done, i.e. menus are closed, in order to avoid conflicts - * with these functions removing the buttons we are working with */ - - if (ui_afterfunc_check(block, but)) { - uiAfterFunc *after = ui_afterfunc_new(); - - if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) { - /* exception, this will crash due to removed button otherwise */ - but->func(C, but->func_arg1, but->func_arg2); - } - else { - after->func = but->func; - } - - after->func_arg1 = but->func_arg1; - after->func_arg2 = but->func_arg2; - - after->funcN = but->funcN; - after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL; - - after->rename_func = but->rename_func; - after->rename_arg1 = but->rename_arg1; - after->rename_orig = but->rename_orig; /* needs free! */ - - after->handle_func = block->handle_func; - after->handle_func_arg = block->handle_func_arg; - after->retval = but->retval; - - if (but->type == UI_BTYPE_BUT_MENU) { - after->butm_func = block->butm_func; - after->butm_func_arg = block->butm_func_arg; - after->a2 = but->a2; - } - - if (block->handle) { - after->popup_op = block->handle->popup_op; - } - - after->optype = but->optype; - after->opcontext = but->opcontext; - after->opptr = but->opptr; - - after->rnapoin = but->rnapoin; - after->rnaprop = but->rnaprop; - - if (but->type == UI_BTYPE_SEARCH_MENU) { - uiButSearch *search_but = (uiButSearch *)but; - after->search_arg_free_fn = search_but->arg_free_fn; - after->search_arg = search_but->arg; - search_but->arg_free_fn = NULL; - search_but->arg = NULL; - } - - if (but->context) { - after->context = CTX_store_copy(but->context); - } - - but->optype = NULL; - but->opcontext = 0; - but->opptr = NULL; - } -} - -/* typically call ui_apply_but_undo(), ui_apply_but_autokey() */ -static void ui_apply_but_undo(uiBut *but) -{ - if (but->flag & UI_BUT_UNDO) { - const char *str = NULL; - size_t str_len_clip = SIZE_MAX - 1; - bool skip_undo = false; - - /* define which string to use for undo */ - if (but->type == UI_BTYPE_MENU) { - str = but->drawstr; - str_len_clip = ui_but_drawstr_len_without_sep_char(but); - } - else if (but->drawstr[0]) { - str = but->drawstr; - str_len_clip = ui_but_drawstr_len_without_sep_char(but); - } - else { - str = but->tip; - str_len_clip = ui_but_tip_len_only_first_line(but); - } - - /* fallback, else we don't get an undo! */ - if (str == NULL || str[0] == '\0' || str_len_clip == 0) { - str = "Unknown Action"; - str_len_clip = strlen(str); - } - - /* Optionally override undo when undo system doesn't support storing properties. */ - if (but->rnapoin.owner_id) { - /* Exception for renaming ID data, we always need undo pushes in this case, - * because undo systems track data by their ID, see: T67002. */ - extern PropertyRNA rna_ID_name; - /* Exception for active shape-key, since changing this in edit-mode updates - * the shape key from object mode data. */ - extern PropertyRNA rna_Object_active_shape_key_index; - if (ELEM(but->rnaprop, &rna_ID_name, &rna_Object_active_shape_key_index)) { - /* pass */ - } - else { - ID *id = but->rnapoin.owner_id; - if (!ED_undo_is_legacy_compatible_for_property(but->block->evil_C, id)) { - skip_undo = true; - } - } - } - - if (skip_undo == false) { - /* XXX: disable all undo pushes from UI changes from sculpt mode as they cause memfile undo - * steps to be written which cause lag: T71434. */ - if (BKE_paintmode_get_active_from_context(but->block->evil_C) == PAINT_MODE_SCULPT) { - skip_undo = true; - } - } - - if (skip_undo) { - str = ""; - } - - /* delayed, after all other funcs run, popups are closed, etc */ - uiAfterFunc *after = ui_afterfunc_new(); - BLI_strncpy(after->undostr, str, min_zz(str_len_clip + 1, sizeof(after->undostr))); - } -} - -static void ui_apply_but_autokey(bContext *C, uiBut *but) -{ - Scene *scene = CTX_data_scene(C); - - /* try autokey */ - ui_but_anim_autokey(C, but, scene, scene->r.cfra); - - /* make a little report about what we've done! */ - if (but->rnaprop) { - char *buf; - - if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) { - return; - } - - buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex); - if (buf) { - BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf); - MEM_freeN(buf); - - WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL); - } - } -} - -static void ui_apply_but_funcs_after(bContext *C) -{ - /* copy to avoid recursive calls */ - ListBase funcs = UIAfterFuncs; - BLI_listbase_clear(&UIAfterFuncs); - - LISTBASE_FOREACH_MUTABLE (uiAfterFunc *, afterf, &funcs) { - uiAfterFunc after = *afterf; /* copy to avoid memleak on exit() */ - BLI_freelinkN(&funcs, afterf); - - if (after.context) { - CTX_store_set(C, after.context); - } - - if (after.popup_op) { - popup_check(C, after.popup_op); - } - - PointerRNA opptr; - if (after.opptr) { - /* free in advance to avoid leak on exit */ - opptr = *after.opptr; - MEM_freeN(after.opptr); - } - - if (after.optype) { - WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL); - } - - if (after.opptr) { - WM_operator_properties_free(&opptr); - } - - if (after.rnapoin.data) { - RNA_property_update(C, &after.rnapoin, after.rnaprop); - } - - if (after.context) { - CTX_store_set(C, NULL); - CTX_store_free(after.context); - } - - if (after.func) { - after.func(C, after.func_arg1, after.func_arg2); - } - if (after.funcN) { - after.funcN(C, after.func_argN, after.func_arg2); - } - if (after.func_argN) { - MEM_freeN(after.func_argN); - } - - if (after.handle_func) { - after.handle_func(C, after.handle_func_arg, after.retval); - } - if (after.butm_func) { - after.butm_func(C, after.butm_func_arg, after.a2); - } - - if (after.rename_func) { - after.rename_func(C, after.rename_arg1, after.rename_orig); - } - if (after.rename_orig) { - MEM_freeN(after.rename_orig); - } - - if (after.search_arg_free_fn) { - after.search_arg_free_fn(after.search_arg); - } - - ui_afterfunc_update_preferences_dirty(&after); - - if (after.undostr[0]) { - ED_undo_push(C, after.undostr); - } - } -} - -static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_but_value_set(but, but->hardmin); - ui_apply_but_func(C, but); - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - if (but->type == UI_BTYPE_MENU) { - ui_but_value_set(but, data->value); - } - - ui_but_update_edited(but); - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - const double value = ui_but_value_get(but); - int value_toggle; - if (but->bit) { - value_toggle = UI_BITBUT_VALUE_TOGGLED((int)value, but->bitnr); - } - else { - value_toggle = (value == 0.0); - if (ELEM(but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N)) { - value_toggle = !value_toggle; - } - } - - ui_but_value_set(but, (double)value_toggle); - if (ELEM(but->type, UI_BTYPE_ICON_TOGGLE, UI_BTYPE_ICON_TOGGLE_N)) { - ui_but_update_edited(but); - } - - ui_apply_but_func(C, but); - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) -{ - ui_but_value_set(but, but->hardmax); - - ui_apply_but_func(C, but); - - /* states of other row buttons */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (bt != but && bt->poin == but->poin && ELEM(bt->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { - ui_but_update_edited(bt); - } - } - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - if (!data->str) { - return; - } - - ui_but_string_set(C, but, data->str); - ui_but_update_edited(but); - - /* give butfunc a copy of the original text too. - * feature used for bone renaming, channels, etc. - * afterfunc frees rename_orig */ - if (data->origstr && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) { - /* In this case, we need to keep origstr available, - * to restore real org string in case we cancel after having typed something already. */ - but->rename_orig = BLI_strdup(data->origstr); - } - /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */ - else if (ui_afterfunc_check(but->block, but)) { - but->rename_orig = data->origstr; - data->origstr = NULL; - } - - void *orig_arg2 = but->func_arg2; - - /* If arg2 isn't in use already, pass the active search item through it. */ - if ((but->func_arg2 == NULL) && (but->type == UI_BTYPE_SEARCH_MENU)) { - uiButSearch *search_but = (uiButSearch *)but; - but->func_arg2 = search_but->item_active; - } - - ui_apply_but_func(C, but); - - but->func_arg2 = orig_arg2; - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - if (data->str) { - ui_but_string_set(C, but, data->str); - ui_but_update_edited(but); - } - else { - ui_but_value_set(but, but->hardmax); - ui_apply_but_func(C, but); - } - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - if (data->str) { - double value; - /* Check if the string value is a number and cancel if it's equal to the startvalue. */ - if (ui_but_string_eval_number(C, but, data->str, &value) && (value == data->startvalue)) { - data->cancel = true; - return; - } - - if (ui_but_string_set(C, but, data->str)) { - data->value = ui_but_value_get(but); - } - else { - data->cancel = true; - return; - } - } - else { - ui_but_value_set(but, data->value); - } - - ui_but_update_edited(but); - ui_apply_but_func(C, but); - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_but_v3_set(but, data->vec); - ui_but_update_edited(but); - ui_apply_but_func(C, but); - - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Drag Multi-Number - * \{ */ - -#ifdef USE_DRAG_MULTINUM - -/* small multi-but api */ -static void ui_multibut_add(uiHandleButtonData *data, uiBut *but) -{ - BLI_assert(but->flag & UI_BUT_DRAG_MULTI); - BLI_assert(data->multi_data.has_mbuts); - - uiButMultiState *mbut_state = MEM_callocN(sizeof(*mbut_state), __func__); - mbut_state->but = but; - mbut_state->origvalue = ui_but_value_get(but); -# ifdef USE_ALLSELECT - mbut_state->select_others.is_copy = data->select_others.is_copy; -# endif - - BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state); - - UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but); -} - -static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but) -{ - for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) { - uiButMultiState *mbut_state = l->link; - - if (mbut_state->but == but) { - return mbut_state; - } - } - - return NULL; -} - -static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block) -{ - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->flag & UI_BUT_DRAG_MULTI) { - uiButMultiState *mbut_state = ui_multibut_lookup(data, but); - if (mbut_state) { - ui_but_value_set(but, mbut_state->origvalue); - -# ifdef USE_ALLSELECT - if (mbut_state->select_others.elems_len > 0) { - ui_selectcontext_apply( - C, but, &mbut_state->select_others, mbut_state->origvalue, mbut_state->origvalue); - } -# else - UNUSED_VARS(C); -# endif - } - } - } -} - -static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block) -{ -# ifdef USE_ALLSELECT - if (data->multi_data.mbuts) { - LinkNode *list = data->multi_data.mbuts; - while (list) { - LinkNode *next = list->next; - uiButMultiState *mbut_state = list->link; - - if (mbut_state->select_others.elems) { - MEM_freeN(mbut_state->select_others.elems); - } - - MEM_freeN(list->link); - MEM_freeN(list); - list = next; - } - } -# else - BLI_linklist_freeN(data->multi_data.mbuts); -# endif - - data->multi_data.mbuts = NULL; - - if (data->multi_data.bs_mbuts) { - UI_butstore_free(block, data->multi_data.bs_mbuts); - data->multi_data.bs_mbuts = NULL; - } -} - -static bool ui_multibut_states_tag(uiBut *but_active, - uiHandleButtonData *data, - const wmEvent *event) -{ - float seg[2][2]; - bool changed = false; - - seg[0][0] = data->multi_data.drag_start[0]; - seg[0][1] = data->multi_data.drag_start[1]; - - seg[1][0] = event->x; - seg[1][1] = event->y; - - BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP); - - ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]); - ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]); - - data->multi_data.has_mbuts = false; - - /* follow ui_but_find_mouse_over_ex logic */ - LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) { - bool drag_prev = false; - bool drag_curr = false; - - /* re-set each time */ - if (but->flag & UI_BUT_DRAG_MULTI) { - but->flag &= ~UI_BUT_DRAG_MULTI; - drag_prev = true; - } - - if (ui_but_is_interactive(but, false)) { - - /* drag checks */ - if (but_active != but) { - if (ui_but_is_compatible(but_active, but)) { - - BLI_assert(but->active == NULL); - - /* finally check for overlap */ - if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) { - - but->flag |= UI_BUT_DRAG_MULTI; - data->multi_data.has_mbuts = true; - drag_curr = true; - } - } - } - } - - changed |= (drag_prev != drag_curr); - } - - return changed; -} - -static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data) -{ - BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP); - BLI_assert(data->multi_data.has_mbuts); - - data->multi_data.bs_mbuts = UI_butstore_create(but_active->block); - - LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) { - if (but->flag & UI_BUT_DRAG_MULTI) { - ui_multibut_add(data, but); - } - } - - /* edit buttons proportionally to eachother - * note: if we mix buttons which are proportional and others which are not, - * this may work a bit strangely */ - if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) || - ELEM(but_active->unit_type, RNA_SUBTYPE_UNIT_VALUE(PROP_UNIT_LENGTH))) { - if (data->origvalue != 0.0) { - data->multi_data.is_proportional = true; - } - } -} - -static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block) -{ - ARegion *region = data->region; - const double value_delta = data->value - data->origvalue; - const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) : - 0.0; - - BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_ENABLE); - BLI_assert(data->multi_data.skip == false); - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (!(but->flag & UI_BUT_DRAG_MULTI)) { - continue; - } - - uiButMultiState *mbut_state = ui_multibut_lookup(data, but); - - if (mbut_state == NULL) { - /* Highly unlikely. */ - printf("%s: Can't find button\n", __func__); - /* While this avoids crashing, multi-button dragging will fail, - * which is still a bug from the user perspective. See T83651. */ - continue; - } - - void *active_back; - ui_but_execute_begin(C, region, but, &active_back); - -# ifdef USE_ALLSELECT - if (data->select_others.is_enabled) { - /* init once! */ - if (mbut_state->select_others.elems_len == 0) { - ui_selectcontext_begin(C, but, &mbut_state->select_others); - } - if (mbut_state->select_others.elems_len == 0) { - mbut_state->select_others.elems_len = -1; - } - } - - /* Needed so we apply the right deltas. */ - but->active->origvalue = mbut_state->origvalue; - but->active->select_others = mbut_state->select_others; - but->active->select_others.do_free = false; -# endif - - BLI_assert(active_back == NULL); - /* No need to check 'data->state' here. */ - if (data->str) { - /* Entering text (set all). */ - but->active->value = data->value; - ui_but_string_set(C, but, data->str); - } - else { - /* Dragging (use delta). */ - if (data->multi_data.is_proportional) { - but->active->value = mbut_state->origvalue * value_scale; - } - else { - but->active->value = mbut_state->origvalue + value_delta; - } - - /* Clamp based on soft limits, see T40154. */ - CLAMP(but->active->value, (double)but->softmin, (double)but->softmax); - } - - ui_but_execute_end(C, region, but, active_back); - } -} - -#endif /* USE_DRAG_MULTINUM */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Drag Toggle - * \{ */ - -#ifdef USE_DRAG_TOGGLE - -/* Helpers that wrap boolean functions, to support different kinds of buttons. */ - -static bool ui_drag_toggle_but_is_supported(const uiBut *but) -{ - if (but->flag & UI_BUT_DISABLED) { - return false; - } - if (ui_but_is_bool(but)) { - return true; - } - if (UI_but_is_decorator(but)) { - return ELEM(but->icon, - ICON_DECORATE, - ICON_DECORATE_KEYFRAME, - ICON_DECORATE_ANIMATE, - ICON_DECORATE_OVERRIDE); - } - return false; -} - -/* Button pushed state to compare if other buttons match. Can be more - * then just true or false for toggle buttons with more than 2 states. */ -static int ui_drag_toggle_but_pushed_state(bContext *C, uiBut *but) -{ - if (but->rnapoin.data == NULL && but->poin == NULL && but->icon) { - if (but->pushed_state_func) { - return but->pushed_state_func(C, but->pushed_state_arg); - } - /* Assume icon identifies a unique state, for buttons that - * work through functions callbacks and don't have an boolean - * value that indicates the state. */ - return but->icon + but->iconadd; - } - if (ui_but_is_bool(but)) { - return ui_but_is_pushed(but); - } - return 0; -} - -typedef struct uiDragToggleHandle { - /* init */ - int pushed_state; - float but_cent_start[2]; - - bool is_xy_lock_init; - bool xy_lock[2]; - - int xy_init[2]; - int xy_last[2]; -} uiDragToggleHandle; - -static bool ui_drag_toggle_set_xy_xy( - bContext *C, ARegion *region, const int pushed_state, const int xy_src[2], const int xy_dst[2]) -{ - /* popups such as layers won't re-evaluate on redraw */ - const bool do_check = (region->regiontype == RGN_TYPE_TEMPORARY); - bool changed = false; - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float xy_a_block[2] = {UNPACK2(xy_src)}; - float xy_b_block[2] = {UNPACK2(xy_dst)}; - - ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); - ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - /* Note: ctrl is always true here because (at least for now) - * we always want to consider text control in this case, even when not embossed. */ - if (ui_but_is_interactive(but, true)) { - if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) { - - /* execute the button */ - if (ui_drag_toggle_but_is_supported(but)) { - /* is it pressed? */ - const int pushed_state_but = ui_drag_toggle_but_pushed_state(C, but); - if (pushed_state_but != pushed_state) { - UI_but_execute(C, region, but); - if (do_check) { - ui_but_update_edited(but); - } - if (U.runtime.is_dirty == false) { - ui_but_update_preferences_dirty(but); - } - changed = true; - } - } - /* done */ - } - } - } - } - if (changed) { - /* apply now, not on release (or if handlers are canceled for whatever reason) */ - ui_apply_but_funcs_after(C); - } - - return changed; -} - -static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2]) -{ - ARegion *region = CTX_wm_region(C); - bool do_draw = false; - - /** - * Initialize Locking: - * - * Check if we need to initialize the lock axis by finding if the first - * button we mouse over is X or Y aligned, then lock the mouse to that axis after. - */ - if (drag_info->is_xy_lock_init == false) { - /* first store the buttons original coords */ - uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true); - - if (but) { - if (but->flag & UI_BUT_DRAG_LOCK) { - const float but_cent_new[2] = { - BLI_rctf_cent_x(&but->rect), - BLI_rctf_cent_y(&but->rect), - }; - - /* check if this is a different button, - * chances are high the button wont move about :) */ - if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) { - if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) < - fabsf(drag_info->but_cent_start[1] - but_cent_new[1])) { - drag_info->xy_lock[0] = true; - } - else { - drag_info->xy_lock[1] = true; - } - drag_info->is_xy_lock_init = true; - } - } - else { - drag_info->is_xy_lock_init = true; - } - } - } - /* done with axis locking */ - - int xy[2]; - xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0]; - xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1]; - - /* touch all buttons between last mouse coord and this one */ - do_draw = ui_drag_toggle_set_xy_xy(C, region, drag_info->pushed_state, drag_info->xy_last, xy); - - if (do_draw) { - ED_region_tag_redraw(region); - } - - copy_v2_v2_int(drag_info->xy_last, xy); -} - -static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata) -{ - uiDragToggleHandle *drag_info = userdata; - MEM_freeN(drag_info); -} - -static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata) -{ - uiDragToggleHandle *drag_info = userdata; - bool done = false; - - switch (event->type) { - case LEFTMOUSE: { - if (event->val == KM_RELEASE) { - done = true; - } - break; - } - case MOUSEMOVE: { - ui_drag_toggle_set(C, drag_info, &event->x); - break; - } - } - - if (done) { - wmWindow *win = CTX_wm_window(C); - const ARegion *region = CTX_wm_region(C); - uiBut *but = ui_but_find_mouse_over_ex( - region, drag_info->xy_init[0], drag_info->xy_init[1], true); - - if (but) { - ui_apply_but_undo(but); - } - - WM_event_remove_ui_handler(&win->modalhandlers, - ui_handler_region_drag_toggle, - ui_handler_region_drag_toggle_remove, - drag_info, - false); - ui_handler_region_drag_toggle_remove(C, drag_info); - - WM_event_add_mousemove(win); - return WM_UI_HANDLER_BREAK; - } - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_but_is_drag_toggle(const uiBut *but) -{ - return ((ui_drag_toggle_but_is_supported(but) == true) && - /* Menu check is important so the button dragged over isn't removed instantly. */ - (ui_block_is_menu(but->block) == false)); -} - -#endif /* USE_DRAG_TOGGLE */ - -#ifdef USE_ALLSELECT - -static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore *selctx_data) -{ - PointerRNA lptr, idptr; - PropertyRNA *lprop; - bool success = false; - - char *path = NULL; - ListBase lb = {NULL}; - - PointerRNA ptr = but->rnapoin; - PropertyRNA *prop = but->rnaprop; - const int index = but->rnaindex; - - /* for now don't support whole colors */ - if (index == -1) { - return false; - } - - /* if there is a valid property that is editable... */ - if (ptr.data && prop) { - bool use_path_from_id; - - /* some facts we want to know */ - const bool is_array = RNA_property_array_check(prop); - const int rna_type = RNA_property_type(prop); - - if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) && - !BLI_listbase_is_empty(&lb)) { - selctx_data->elems_len = BLI_listbase_count(&lb); - selctx_data->elems = MEM_mallocN(sizeof(uiSelectContextElem) * selctx_data->elems_len, - __func__); - int i; - LISTBASE_FOREACH_INDEX (CollectionPointerLink *, link, &lb, i) { - if (i >= selctx_data->elems_len) { - break; - } - uiSelectContextElem *other = &selctx_data->elems[i]; - /* TODO,. de-duplicate copy_to_selected_button */ - if (link->ptr.data != ptr.data) { - if (use_path_from_id) { - /* Path relative to ID. */ - lprop = NULL; - RNA_id_pointer_create(link->ptr.owner_id, &idptr); - RNA_path_resolve_property(&idptr, path, &lptr, &lprop); - } - else if (path) { - /* Path relative to elements from list. */ - lprop = NULL; - RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop); - } - else { - lptr = link->ptr; - lprop = prop; - } - - /* lptr might not be the same as link->ptr! */ - if ((lptr.data != ptr.data) && (lprop == prop) && RNA_property_editable(&lptr, lprop)) { - other->ptr = lptr; - if (is_array) { - if (rna_type == PROP_FLOAT) { - other->val_f = RNA_property_float_get_index(&lptr, lprop, index); - } - else if (rna_type == PROP_INT) { - other->val_i = RNA_property_int_get_index(&lptr, lprop, index); - } - /* ignored for now */ -# if 0 - else if (rna_type == PROP_BOOLEAN) { - other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index); - } -# endif - } - else { - if (rna_type == PROP_FLOAT) { - other->val_f = RNA_property_float_get(&lptr, lprop); - } - else if (rna_type == PROP_INT) { - other->val_i = RNA_property_int_get(&lptr, lprop); - } - /* ignored for now */ -# if 0 - else if (rna_type == PROP_BOOLEAN) { - other->val_b = RNA_property_boolean_get(&lptr, lprop); - } - else if (rna_type == PROP_ENUM) { - other->val_i = RNA_property_enum_get(&lptr, lprop); - } -# endif - } - - continue; - } - } - - selctx_data->elems_len -= 1; - i -= 1; - } - - success = (selctx_data->elems_len != 0); - } - } - - if (selctx_data->elems_len == 0) { - MEM_SAFE_FREE(selctx_data->elems); - } - - MEM_SAFE_FREE(path); - BLI_freelistN(&lb); - - /* caller can clear */ - selctx_data->do_free = true; - - if (success) { - but->flag |= UI_BUT_IS_SELECT_CONTEXT; - } - - return success; -} - -static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data) -{ - if (selctx_data->do_free) { - if (selctx_data->elems) { - MEM_freeN(selctx_data->elems); - } - } - - but->flag &= ~UI_BUT_IS_SELECT_CONTEXT; -} - -static void ui_selectcontext_apply(bContext *C, - uiBut *but, - uiSelectContextStore *selctx_data, - const double value, - const double value_orig) -{ - if (selctx_data->elems) { - PropertyRNA *prop = but->rnaprop; - PropertyRNA *lprop = but->rnaprop; - const int index = but->rnaindex; - const bool use_delta = (selctx_data->is_copy == false); - - union { - bool b; - int i; - float f; - PointerRNA p; - } delta, min, max; - - const bool is_array = RNA_property_array_check(prop); - const int rna_type = RNA_property_type(prop); - - if (rna_type == PROP_FLOAT) { - delta.f = use_delta ? (value - value_orig) : value; - RNA_property_float_range(&but->rnapoin, prop, &min.f, &max.f); - } - else if (rna_type == PROP_INT) { - delta.i = use_delta ? ((int)value - (int)value_orig) : (int)value; - RNA_property_int_range(&but->rnapoin, prop, &min.i, &max.i); - } - else if (rna_type == PROP_ENUM) { - /* Not a delta in fact. */ - delta.i = RNA_property_enum_get(&but->rnapoin, prop); - } - else if (rna_type == PROP_BOOLEAN) { - if (is_array) { - /* Not a delta in fact. */ - delta.b = RNA_property_boolean_get_index(&but->rnapoin, prop, index); - } - else { - /* Not a delta in fact. */ - delta.b = RNA_property_boolean_get(&but->rnapoin, prop); - } - } - else if (rna_type == PROP_POINTER) { - /* Not a delta in fact. */ - delta.p = RNA_property_pointer_get(&but->rnapoin, prop); - } - -# ifdef USE_ALLSELECT_LAYER_HACK - /* make up for not having 'handle_layer_buttons' */ - { - const PropertySubType subtype = RNA_property_subtype(prop); - - if ((rna_type == PROP_BOOLEAN) && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) && is_array && - /* could check for 'handle_layer_buttons' */ - but->func) { - wmWindow *win = CTX_wm_window(C); - if (!win->eventstate->shift) { - const int len = RNA_property_array_length(&but->rnapoin, prop); - bool *tmparray = MEM_callocN(sizeof(bool) * len, __func__); - - tmparray[index] = true; - - for (int i = 0; i < selctx_data->elems_len; i++) { - uiSelectContextElem *other = &selctx_data->elems[i]; - PointerRNA lptr = other->ptr; - RNA_property_boolean_set_array(&lptr, lprop, tmparray); - RNA_property_update(C, &lptr, lprop); - } - - MEM_freeN(tmparray); - - return; - } - } - } -# endif - - for (int i = 0; i < selctx_data->elems_len; i++) { - uiSelectContextElem *other = &selctx_data->elems[i]; - PointerRNA lptr = other->ptr; - - if (rna_type == PROP_FLOAT) { - float other_value = use_delta ? (other->val_f + delta.f) : delta.f; - CLAMP(other_value, min.f, max.f); - if (is_array) { - RNA_property_float_set_index(&lptr, lprop, index, other_value); - } - else { - RNA_property_float_set(&lptr, lprop, other_value); - } - } - else if (rna_type == PROP_INT) { - int other_value = use_delta ? (other->val_i + delta.i) : delta.i; - CLAMP(other_value, min.i, max.i); - if (is_array) { - RNA_property_int_set_index(&lptr, lprop, index, other_value); - } - else { - RNA_property_int_set(&lptr, lprop, other_value); - } - } - else if (rna_type == PROP_BOOLEAN) { - const bool other_value = delta.b; - if (is_array) { - RNA_property_boolean_set_index(&lptr, lprop, index, other_value); - } - else { - RNA_property_boolean_set(&lptr, lprop, delta.b); - } - } - else if (rna_type == PROP_ENUM) { - const int other_value = delta.i; - BLI_assert(!is_array); - RNA_property_enum_set(&lptr, lprop, other_value); - } - else if (rna_type == PROP_POINTER) { - const PointerRNA other_value = delta.p; - RNA_property_pointer_set(&lptr, lprop, other_value, NULL); - } - - RNA_property_update(C, &lptr, prop); - } - } -} - -#endif /* USE_ALLSELECT */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Drag - * \{ */ - -static bool ui_but_drag_init(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - /* prevent other WM gestures to start while we try to drag */ - WM_gestures_remove(CTX_wm_window(C)); - - /* Clamp the maximum to half the UI unit size so a high user preference - * doesn't require the user to drag more than half the default button height. */ - const int drag_threshold = min_ii( - WM_event_drag_threshold(event), - (int)((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block))); - - if (abs(data->dragstartx - event->x) + abs(data->dragstarty - event->y) > drag_threshold) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - data->cancel = true; -#ifdef USE_DRAG_TOGGLE - if (ui_drag_toggle_but_is_supported(but)) { - uiDragToggleHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__); - ARegion *region_prev; - - /* call here because regular mouse-up event wont run, - * typically 'button_activate_exit()' handles this */ - ui_apply_but_autokey(C, but); - - drag_info->pushed_state = ui_drag_toggle_but_pushed_state(C, but); - drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect); - drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect); - copy_v2_v2_int(drag_info->xy_init, &event->x); - copy_v2_v2_int(drag_info->xy_last, &event->x); - - /* needed for toggle drag on popups */ - region_prev = CTX_wm_region(C); - CTX_wm_region_set(C, data->region); - - WM_event_add_ui_handler(C, - &data->window->modalhandlers, - ui_handler_region_drag_toggle, - ui_handler_region_drag_toggle_remove, - drag_info, - WM_HANDLER_BLOCKING); - - CTX_wm_region_set(C, region_prev); - - /* Initialize alignment for single row/column regions, - * otherwise we use the relative position of the first other button dragged over. */ - if (ELEM(data->region->regiontype, - RGN_TYPE_NAV_BAR, - RGN_TYPE_HEADER, - RGN_TYPE_TOOL_HEADER, - RGN_TYPE_FOOTER)) { - const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment); - int lock_axis = -1; - - if (ELEM(region_alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) { - lock_axis = 0; - } - else if (ELEM(region_alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) { - lock_axis = 1; - } - if (lock_axis != -1) { - drag_info->xy_lock[lock_axis] = true; - drag_info->is_xy_lock_init = true; - } - } - } - else -#endif - if (but->type == UI_BTYPE_COLOR) { - bool valid = false; - uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__); - - /* TODO support more button pointer types */ - if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - ui_but_v3_get(but, drag_info->color); - drag_info->gamma_corrected = true; - valid = true; - } - else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { - ui_but_v3_get(but, drag_info->color); - drag_info->gamma_corrected = false; - valid = true; - } - else if (ELEM(but->pointype, UI_BUT_POIN_FLOAT, UI_BUT_POIN_CHAR)) { - ui_but_v3_get(but, drag_info->color); - copy_v3_v3(drag_info->color, (float *)but->poin); - valid = true; - } - - if (valid) { - WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA); - } - else { - MEM_freeN(drag_info); - return false; - } - } - else { - wmDrag *drag = WM_event_start_drag( - C, - but->icon, - but->dragtype, - but->dragpoin, - ui_but_value_get(but), - (but->dragflag & UI_BUT_DRAGPOIN_FREE) ? WM_DRAG_FREE_DATA : WM_DRAG_NOP); - /* wmDrag has ownership over dragpoin now, stop messing with it. */ - but->dragpoin = NULL; - - if (but->imb) { - WM_event_drag_image(drag, - but->imb, - but->imb_scale, - BLI_rctf_size_x(&but->rect), - BLI_rctf_size_y(&but->rect)); - } - } - return true; - } - - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Apply - * \{ */ - -static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - ui_apply_but_func(C, but); - data->retval = but->retval; - data->applied = true; -} - -static void ui_apply_but( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive) -{ - const eButType but_type = but->type; /* Store as const to quiet maybe uninitialized warning. */ - - data->retval = 0; - - /* if we cancel and have not applied yet, there is nothing to do, - * otherwise we have to restore the original value again */ - if (data->cancel) { - if (!data->applied) { - return; - } - - if (data->str) { - MEM_freeN(data->str); - } - data->str = data->origstr; - data->origstr = NULL; - data->value = data->origvalue; - copy_v3_v3(data->vec, data->origvec); - /* postpone clearing origdata */ - } - else { - /* We avoid applying interactive edits a second time - * at the end with the #uiHandleButtonData.applied_interactive flag. */ - if (interactive) { - data->applied_interactive = true; - } - else if (data->applied_interactive) { - return; - } - -#ifdef USE_ALLSELECT -# ifdef USE_DRAG_MULTINUM - if (but->flag & UI_BUT_DRAG_MULTI) { - /* pass */ - } - else -# endif - if (data->select_others.elems_len == 0) { - wmWindow *win = CTX_wm_window(C); - /* may have been enabled before activating */ - if (data->select_others.is_enabled || IS_ALLSELECT_EVENT(win->eventstate)) { - ui_selectcontext_begin(C, but, &data->select_others); - data->select_others.is_enabled = true; - } - } - if (data->select_others.elems_len == 0) { - /* Don't check again. */ - data->select_others.elems_len = -1; - } -#endif - } - - /* ensures we are writing actual values */ - char *editstr = but->editstr; - double *editval = but->editval; - float *editvec = but->editvec; - ColorBand *editcoba; - CurveMapping *editcumap; - CurveProfile *editprofile; - if (but_type == UI_BTYPE_COLORBAND) { - uiButColorBand *but_coba = (uiButColorBand *)but; - editcoba = but_coba->edit_coba; - } - else if (but_type == UI_BTYPE_CURVE) { - uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; - editcumap = but_cumap->edit_cumap; - } - else if (but_type == UI_BTYPE_CURVEPROFILE) { - uiButCurveProfile *but_profile = (uiButCurveProfile *)but; - editprofile = but_profile->edit_profile; - } - but->editstr = NULL; - but->editval = NULL; - but->editvec = NULL; - if (but_type == UI_BTYPE_COLORBAND) { - uiButColorBand *but_coba = (uiButColorBand *)but; - but_coba->edit_coba = NULL; - } - else if (but_type == UI_BTYPE_CURVE) { - uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; - but_cumap->edit_cumap = NULL; - } - else if (but_type == UI_BTYPE_CURVEPROFILE) { - uiButCurveProfile *but_profile = (uiButCurveProfile *)but; - but_profile->edit_profile = NULL; - } - - /* handle different types */ - switch (but_type) { - case UI_BTYPE_BUT: - case UI_BTYPE_DECORATOR: - ui_apply_but_BUT(C, but, data); - break; - case UI_BTYPE_TEXT: - case UI_BTYPE_SEARCH_MENU: - ui_apply_but_TEX(C, but, data); - break; - case UI_BTYPE_BUT_TOGGLE: - case UI_BTYPE_TOGGLE: - case UI_BTYPE_TOGGLE_N: - case UI_BTYPE_ICON_TOGGLE: - case UI_BTYPE_ICON_TOGGLE_N: - case UI_BTYPE_CHECKBOX: - case UI_BTYPE_CHECKBOX_N: - ui_apply_but_TOG(C, but, data); - break; - case UI_BTYPE_ROW: - case UI_BTYPE_LISTROW: - ui_apply_but_ROW(C, block, but, data); - break; - case UI_BTYPE_TAB: - ui_apply_but_TAB(C, but, data); - break; - case UI_BTYPE_SCROLL: - case UI_BTYPE_GRIP: - case UI_BTYPE_NUM: - case UI_BTYPE_NUM_SLIDER: - ui_apply_but_NUM(C, but, data); - break; - case UI_BTYPE_MENU: - case UI_BTYPE_BLOCK: - case UI_BTYPE_PULLDOWN: - ui_apply_but_BLOCK(C, but, data); - break; - case UI_BTYPE_COLOR: - if (data->cancel) { - ui_apply_but_VEC(C, but, data); - } - else { - ui_apply_but_BLOCK(C, but, data); - } - break; - case UI_BTYPE_BUT_MENU: - ui_apply_but_BUTM(C, but, data); - break; - case UI_BTYPE_UNITVEC: - case UI_BTYPE_HSVCUBE: - case UI_BTYPE_HSVCIRCLE: - ui_apply_but_VEC(C, but, data); - break; - case UI_BTYPE_COLORBAND: - ui_apply_but_COLORBAND(C, but, data); - break; - case UI_BTYPE_CURVE: - ui_apply_but_CURVE(C, but, data); - break; - case UI_BTYPE_CURVEPROFILE: - ui_apply_but_CURVEPROFILE(C, but, data); - break; - case UI_BTYPE_KEY_EVENT: - case UI_BTYPE_HOTKEY_EVENT: - ui_apply_but_BUT(C, but, data); - break; - case UI_BTYPE_IMAGE: - ui_apply_but_IMAGE(C, but, data); - break; - case UI_BTYPE_HISTOGRAM: - ui_apply_but_HISTOGRAM(C, but, data); - break; - case UI_BTYPE_WAVEFORM: - ui_apply_but_WAVEFORM(C, but, data); - break; - case UI_BTYPE_TRACK_PREVIEW: - ui_apply_but_TRACKPREVIEW(C, but, data); - break; - default: - break; - } - -#ifdef USE_DRAG_MULTINUM - if (data->multi_data.has_mbuts) { - if ((data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) && (data->multi_data.skip == false)) { - if (data->cancel) { - ui_multibut_restore(C, data, block); - } - else { - ui_multibut_states_apply(C, data, block); - } - } - } -#endif - -#ifdef USE_ALLSELECT - ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue); -#endif - - if (data->cancel) { - data->origvalue = 0.0; - zero_v3(data->origvec); - } - - but->editstr = editstr; - but->editval = editval; - but->editvec = editvec; - if (but_type == UI_BTYPE_COLORBAND) { - uiButColorBand *but_coba = (uiButColorBand *)but; - but_coba->edit_coba = editcoba; - } - else if (but_type == UI_BTYPE_CURVE) { - uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; - but_cumap->edit_cumap = editcumap; - } - else if (but_type == UI_BTYPE_CURVEPROFILE) { - uiButCurveProfile *but_profile = (uiButCurveProfile *)but; - but_profile->edit_profile = editprofile; - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Drop Event - * \{ */ - -/* only call if event type is EVT_DROP */ -static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data) -{ - ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */ - - LISTBASE_FOREACH (wmDrag *, wmd, drags) { - /* TODO asset dropping. */ - if (wmd->type == WM_DRAG_ID) { - /* align these types with UI_but_active_drop_name */ - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - ID *id = WM_drag_get_local_ID(wmd, 0); - - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - - ui_textedit_string_set(but, data, id->name + 2); - - if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) { - but->changed = true; - ui_searchbox_update(C, data->searchbox, but, true); - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Copy & Paste - * \{ */ - -static void ui_but_get_pasted_text_from_clipboard(char **buf_paste, int *buf_len) -{ - /* get only first line even if the clipboard contains multiple lines */ - int length; - char *text = WM_clipboard_text_get_firstline(false, &length); - - if (text) { - *buf_paste = text; - *buf_len = length; - } - else { - *buf_paste = MEM_callocN(sizeof(char), __func__); - *buf_len = 0; - } -} - -static int get_but_property_array_length(uiBut *but) -{ - return RNA_property_array_length(&but->rnapoin, but->rnaprop); -} - -static void ui_but_set_float_array( - bContext *C, uiBut *but, uiHandleButtonData *data, float *values, int array_length) -{ - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - for (int i = 0; i < array_length; i++) { - RNA_property_float_set_index(&but->rnapoin, but->rnaprop, i, values[i]); - } - if (data) { - if (but->type == UI_BTYPE_UNITVEC) { - BLI_assert(array_length == 3); - copy_v3_v3(data->vec, values); - } - else { - data->value = values[but->rnaindex]; - } - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); -} - -static void float_array_to_string(float *values, - int array_length, - char *output, - int output_len_max) -{ - /* to avoid buffer overflow attacks; numbers are quite arbitrary */ - BLI_assert(output_len_max > 15); - output_len_max -= 10; - - int current_index = 0; - output[current_index] = '['; - current_index++; - - for (int i = 0; i < array_length; i++) { - int length = BLI_snprintf( - output + current_index, output_len_max - current_index, "%f", values[i]); - current_index += length; - - if (i < array_length - 1) { - if (current_index < output_len_max) { - output[current_index + 0] = ','; - output[current_index + 1] = ' '; - current_index += 2; - } - } - } - - output[current_index + 0] = ']'; - output[current_index + 1] = '\0'; -} - -static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_len_max) -{ - const int array_length = get_but_property_array_length(but); - float *values = alloca(array_length * sizeof(float)); - RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values); - float_array_to_string(values, array_length, output, output_len_max); -} - -static bool parse_float_array(char *text, float *values, int expected_length) -{ - /* can parse max 4 floats for now */ - BLI_assert(0 <= expected_length && expected_length <= 4); - - float v[5]; - const int actual_length = sscanf( - text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]); - - if (actual_length == expected_length) { - memcpy(values, v, sizeof(float) * expected_length); - return true; - } - return false; -} - -static void ui_but_paste_numeric_array(bContext *C, - uiBut *but, - uiHandleButtonData *data, - char *buf_paste) -{ - const int array_length = get_but_property_array_length(but); - if (array_length > 4) { - /* not supported for now */ - return; - } - - float *values = alloca(sizeof(float) * array_length); - - if (parse_float_array(buf_paste, values, array_length)) { - ui_but_set_float_array(C, but, data, values, array_length); - } - else { - WM_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]"); - } -} - -static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_len_max) -{ - /* Get many decimal places, then strip trailing zeros. - * note: too high values start to give strange results */ - ui_but_string_get_ex(but, output, output_len_max, UI_PRECISION_FLOAT_MAX, false, NULL); - BLI_str_rstrip_float_zero(output, '\0'); -} - -static void ui_but_paste_numeric_value(bContext *C, - uiBut *but, - uiHandleButtonData *data, - char *buf_paste) -{ - double value; - if (ui_but_string_eval_number(C, but, buf_paste, &value)) { - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - data->value = value; - ui_but_string_set(C, but, buf_paste); - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else { - WM_report(RPT_ERROR, "Expected a number"); - } -} - -static void ui_but_paste_normalized_vector(bContext *C, - uiBut *but, - uiHandleButtonData *data, - char *buf_paste) -{ - float xyz[3]; - if (parse_float_array(buf_paste, xyz, 3)) { - if (normalize_v3(xyz) == 0.0f) { - /* better set Z up then have a zero vector */ - xyz[2] = 1.0; - } - ui_but_set_float_array(C, but, data, xyz, 3); - } - else { - WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'"); - } -} - -static void ui_but_copy_color(uiBut *but, char *output, int output_len_max) -{ - float rgba[4]; - - if (but->rnaprop && get_but_property_array_length(but) == 4) { - rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); - } - else { - rgba[3] = 1.0f; - } - - ui_but_v3_get(but, rgba); - - /* convert to linear color to do compatible copy between gamma and non-gamma */ - if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - srgb_to_linearrgb_v3_v3(rgba, rgba); - } - - float_array_to_string(rgba, 4, output, output_len_max); -} - -static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste) -{ - float rgba[4]; - if (parse_float_array(buf_paste, rgba, 4)) { - if (but->rnaprop) { - /* Assume linear colors in buffer. */ - if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - linearrgb_to_srgb_v3_v3(rgba, rgba); - } - - /* Some color properties are RGB, not RGBA. */ - const int array_len = get_but_property_array_length(but); - BLI_assert(ELEM(array_len, 3, 4)); - ui_but_set_float_array(C, but, NULL, rgba, array_len); - } - } - else { - WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'"); - } -} - -static void ui_but_copy_text(uiBut *but, char *output, int output_len_max) -{ - ui_but_string_get(but, output, output_len_max); -} - -static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste) -{ - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - ui_textedit_string_set(but, but->active, buf_paste); - - if (but->type == UI_BTYPE_SEARCH_MENU) { - but->changed = true; - ui_searchbox_update(C, data->searchbox, but, true); - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); -} - -static void ui_but_copy_colorband(uiBut *but) -{ - if (but->poin != NULL) { - memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand)); - } -} - -static void ui_but_paste_colorband(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - if (but_copypaste_coba.tot != 0) { - if (!but->poin) { - but->poin = MEM_callocN(sizeof(ColorBand), "colorband"); - } - - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand)); - button_activate_state(C, but, BUTTON_STATE_EXIT); - } -} - -static void ui_but_copy_curvemapping(uiBut *but) -{ - if (but->poin != NULL) { - but_copypaste_curve_alive = true; - BKE_curvemapping_free_data(&but_copypaste_curve); - BKE_curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *)but->poin); - } -} - -static void ui_but_paste_curvemapping(bContext *C, uiBut *but) -{ - if (but_copypaste_curve_alive) { - if (!but->poin) { - but->poin = MEM_callocN(sizeof(CurveMapping), "curvemapping"); - } - - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - BKE_curvemapping_free_data((CurveMapping *)but->poin); - BKE_curvemapping_copy_data((CurveMapping *)but->poin, &but_copypaste_curve); - button_activate_state(C, but, BUTTON_STATE_EXIT); - } -} - -static void ui_but_copy_CurveProfile(uiBut *but) -{ - if (but->poin != NULL) { - but_copypaste_profile_alive = true; - BKE_curveprofile_free_data(&but_copypaste_profile); - BKE_curveprofile_copy_data(&but_copypaste_profile, (CurveProfile *)but->poin); - } -} - -static void ui_but_paste_CurveProfile(bContext *C, uiBut *but) -{ - if (but_copypaste_profile_alive) { - if (!but->poin) { - but->poin = MEM_callocN(sizeof(CurveProfile), "CurveProfile"); - } - - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - BKE_curveprofile_free_data((CurveProfile *)but->poin); - BKE_curveprofile_copy_data((CurveProfile *)but->poin, &but_copypaste_profile); - button_activate_state(C, but, BUTTON_STATE_EXIT); - } -} - -static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max) -{ - PointerRNA *opptr = UI_but_operator_ptr_get(but); - - char *str; - str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr); - BLI_strncpy(output, str, output_len_max); - MEM_freeN(str); -} - -static bool ui_but_copy_menu(uiBut *but, char *output, int output_len_max) -{ - MenuType *mt = UI_but_menutype_get(but); - if (mt) { - BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname); - return true; - } - return false; -} - -static bool ui_but_copy_popover(uiBut *but, char *output, int output_len_max) -{ - PanelType *pt = UI_but_paneltype_get(but); - if (pt) { - BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_panel(name=\"%s\")", pt->idname); - return true; - } - return false; -} - -static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array) -{ - if (ui_but_contains_password(but)) { - return; - } - - /* Arbitrary large value (allow for paths: 'PATH_MAX') */ - char buf[4096] = {0}; - const int buf_max_len = sizeof(buf); - - /* Left false for copying internal data (color-band for eg). */ - bool is_buf_set = false; - - const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL); - - switch (but->type) { - case UI_BTYPE_NUM: - case UI_BTYPE_NUM_SLIDER: - if (!has_required_data) { - break; - } - if (copy_array && ui_but_has_array_value(but)) { - ui_but_copy_numeric_array(but, buf, buf_max_len); - } - else { - ui_but_copy_numeric_value(but, buf, buf_max_len); - } - is_buf_set = true; - break; - - case UI_BTYPE_UNITVEC: - if (!has_required_data) { - break; - } - ui_but_copy_numeric_array(but, buf, buf_max_len); - is_buf_set = true; - break; - - case UI_BTYPE_COLOR: - if (!has_required_data) { - break; - } - ui_but_copy_color(but, buf, buf_max_len); - is_buf_set = true; - break; - - case UI_BTYPE_TEXT: - case UI_BTYPE_SEARCH_MENU: - if (!has_required_data) { - break; - } - ui_but_copy_text(but, buf, buf_max_len); - is_buf_set = true; - break; - - case UI_BTYPE_COLORBAND: - ui_but_copy_colorband(but); - break; - - case UI_BTYPE_CURVE: - ui_but_copy_curvemapping(but); - break; - - case UI_BTYPE_CURVEPROFILE: - ui_but_copy_CurveProfile(but); - break; - - case UI_BTYPE_BUT: - if (!but->optype) { - break; - } - ui_but_copy_operator(C, but, buf, buf_max_len); - is_buf_set = true; - break; - - case UI_BTYPE_MENU: - case UI_BTYPE_PULLDOWN: - if (ui_but_copy_menu(but, buf, buf_max_len)) { - is_buf_set = true; - } - break; - case UI_BTYPE_POPOVER: - if (ui_but_copy_popover(but, buf, buf_max_len)) { - is_buf_set = true; - } - break; - - default: - break; - } - - if (is_buf_set) { - WM_clipboard_text_set(buf, 0); - } -} - -static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array) -{ - BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */ - - int buf_paste_len = 0; - char *buf_paste; - ui_but_get_pasted_text_from_clipboard(&buf_paste, &buf_paste_len); - - const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL); - - switch (but->type) { - case UI_BTYPE_NUM: - case UI_BTYPE_NUM_SLIDER: - if (!has_required_data) { - break; - } - if (paste_array && ui_but_has_array_value(but)) { - ui_but_paste_numeric_array(C, but, data, buf_paste); - } - else { - ui_but_paste_numeric_value(C, but, data, buf_paste); - } - break; - - case UI_BTYPE_UNITVEC: - if (!has_required_data) { - break; - } - ui_but_paste_normalized_vector(C, but, data, buf_paste); - break; - - case UI_BTYPE_COLOR: - if (!has_required_data) { - break; - } - ui_but_paste_color(C, but, buf_paste); - break; - - case UI_BTYPE_TEXT: - case UI_BTYPE_SEARCH_MENU: - if (!has_required_data) { - break; - } - ui_but_paste_text(C, but, data, buf_paste); - break; - - case UI_BTYPE_COLORBAND: - ui_but_paste_colorband(C, but, data); - break; - - case UI_BTYPE_CURVE: - ui_but_paste_curvemapping(C, but); - break; - - case UI_BTYPE_CURVEPROFILE: - ui_but_paste_CurveProfile(C, but); - break; - - default: - break; - } - - MEM_freeN((void *)buf_paste); -} - -void ui_but_clipboard_free(void) -{ - BKE_curvemapping_free_data(&but_copypaste_curve); - BKE_curveprofile_free_data(&but_copypaste_profile); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Text Password - * - * Functions to convert password strings that should not be displayed - * to asterisk representation (e.g. 'mysecretpasswd' -> '*************') - * - * It converts every UTF-8 character to an asterisk, and also remaps - * the cursor position and selection start/end. - * - * \note remapping is used, because password could contain UTF-8 characters. - * - * \{ */ - -static int ui_text_position_from_hidden(uiBut *but, int pos) -{ - const char *butstr = (but->editstr) ? but->editstr : but->drawstr; - const char *strpos = butstr; - for (int i = 0; i < pos; i++) { - strpos = BLI_str_find_next_char_utf8(strpos, NULL); - } - - return (strpos - butstr); -} - -static int ui_text_position_to_hidden(uiBut *but, int pos) -{ - const char *butstr = (but->editstr) ? but->editstr : but->drawstr; - return BLI_strnlen_utf8(butstr, pos); -} - -void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], - uiBut *but, - const bool restore) -{ - if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) { - return; - } - - char *butstr = (but->editstr) ? but->editstr : but->drawstr; - - if (restore) { - /* restore original string */ - BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR); - - /* remap cursor positions */ - if (but->pos >= 0) { - but->pos = ui_text_position_from_hidden(but, but->pos); - but->selsta = ui_text_position_from_hidden(but, but->selsta); - but->selend = ui_text_position_from_hidden(but, but->selend); - } - } - else { - /* convert text to hidden text using asterisks (e.g. pass -> ****) */ - const size_t len = BLI_strlen_utf8(butstr); - - /* remap cursor positions */ - if (but->pos >= 0) { - but->pos = ui_text_position_to_hidden(but, but->pos); - but->selsta = ui_text_position_to_hidden(but, but->selsta); - but->selend = ui_text_position_to_hidden(but, but->selend); - } - - /* save original string */ - BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR); - memset(butstr, '*', len); - butstr[len] = '\0'; - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Text Selection/Editing - * \{ */ - -void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but) -{ - if (!but->active) { - return; - } - - /* most likely NULL, but let's check, and give it temp zero string */ - if (!but->active->str) { - but->active->str = MEM_callocN(1, "temp str"); - } - but->active->str[0] = 0; - - ui_apply_but_TEX(C, but, but->active); - button_activate_state(C, but, BUTTON_STATE_EXIT); -} - -static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen) -{ - BLI_assert(data->is_str_dynamic); - BLI_assert(data->str == but->editstr); - - if (maxlen > data->maxlen) { - data->str = but->editstr = MEM_reallocN(data->str, sizeof(char) * maxlen); - data->maxlen = maxlen; - } -} - -static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str) -{ - if (data->is_str_dynamic) { - ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1); - } - - if (UI_but_is_utf8(but)) { - BLI_strncpy_utf8(data->str, str, data->maxlen); - } - else { - BLI_strncpy(data->str, str, data->maxlen); - } -} - -static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data) -{ - char *str = data->str; - const int len = strlen(str); - bool changed = false; - if (but->selsta != but->selend && len) { - memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1); - changed = true; - } - - but->pos = but->selend = but->selsta; - return changed; -} - -static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str), - const size_t str_step_ofs, - const rcti *glyph_step_bounds, - const int UNUSED(glyph_advance_x), - const rctf *glyph_bounds, - const int UNUSED(glyph_bearing[2]), - void *user_data) -{ - int *cursor_data = user_data; - const float center = glyph_step_bounds->xmin + (BLI_rctf_size_x(glyph_bounds) / 2.0f); - if (cursor_data[0] < center) { - cursor_data[1] = str_step_ofs; - return false; - } - return true; -} - -/** - * \param x: Screen space cursor location - #wmEvent.x - * - * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too. - */ -static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x) -{ - /* XXX pass on as arg. */ - uiFontStyle fstyle = UI_style_get()->widget; - const float aspect = but->block->aspect; - - float startx = but->rect.xmin; - float starty_dummy = 0.0f; - char password_str[UI_MAX_PASSWORD_STR]; - /* treat 'str_last' as null terminator for str, no need to modify in-place */ - const char *str = but->editstr, *str_last; - - ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy); - - ui_fontscale(&fstyle.points, aspect); - - UI_fontstyle_set(&fstyle); - - if (fstyle.kerning == 1) { - /* for BLF_width */ - BLF_enable(fstyle.uifont_id, BLF_KERNING_DEFAULT); - } - - ui_but_text_password_hide(password_str, but, false); - - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - if (but->flag & UI_HAS_ICON) { - startx += UI_DPI_ICON_SIZE / aspect; - } - } - startx += (UI_TEXT_MARGIN_X * U.widget_unit) / aspect; - - /* mouse dragged outside the widget to the left */ - if (x < startx) { - int i = but->ofs; - - str_last = &str[but->ofs]; - - while (i > 0) { - if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) { - /* 0.25 == scale factor for less sensitivity */ - if (BLF_width(fstyle.uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) { - break; - } - } - else { - break; /* unlikely but possible */ - } - } - but->ofs = i; - but->pos = but->ofs; - } - /* mouse inside the widget, mouse coords mapped in widget space */ - else { - str_last = &str[but->ofs]; - const int str_last_len = strlen(str_last); - const int x_pos = (int)(x - startx); - int glyph_data[2] = { - x_pos, /* horizontal position to test. */ - -1, /* Write the character offset here. */ - }; - BLF_boundbox_foreach_glyph(fstyle.uifont_id, - str + but->ofs, - INT_MAX, - ui_textedit_set_cursor_pos_foreach_glyph, - glyph_data); - /* If value untouched then we are to the right. */ - if (glyph_data[1] == -1) { - glyph_data[1] = str_last_len; - } - but->pos = glyph_data[1] + but->ofs; - } - - if (fstyle.kerning == 1) { - BLF_disable(fstyle.uifont_id, BLF_KERNING_DEFAULT); - } - - ui_but_text_password_hide(password_str, but, true); -} - -static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x) -{ - ui_textedit_set_cursor_pos(but, data, x); - - but->selsta = but->pos; - but->selend = data->sel_pos_init; - if (but->selend < but->selsta) { - SWAP(short, but->selsta, but->selend); - } - - ui_but_update(but); -} - -/** - * This is used for both utf8 and ascii - * - * For unicode buttons, \a buf is treated as unicode. - */ -static bool ui_textedit_insert_buf(uiBut *but, - uiHandleButtonData *data, - const char *buf, - int buf_len) -{ - int len = strlen(data->str); - const int len_new = len - (but->selend - but->selsta) + 1; - bool changed = false; - - if (data->is_str_dynamic) { - ui_textedit_string_ensure_max_length(but, data, len_new + buf_len); - } - - if (len_new <= data->maxlen) { - char *str = data->str; - size_t step = buf_len; - - /* type over the current selection */ - if ((but->selend - but->selsta) > 0) { - changed = ui_textedit_delete_selection(but, data); - len = strlen(str); - } - - if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) { - if (UI_but_is_utf8(but)) { - /* shorten 'step' to a utf8 aligned size that fits */ - BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step); - } - else { - step = data->maxlen - (len + 1); - } - } - - if (step && (len + step < data->maxlen)) { - memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos); - memcpy(&str[but->pos], buf, step * sizeof(char)); - but->pos += step; - changed = true; - } - } - - return changed; -} - -static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii) -{ - const char buf[2] = {ascii, '\0'}; - - if (UI_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) { - printf( - "%s: entering invalid ascii char into an ascii key (%d)\n", __func__, (int)(uchar)ascii); - - return false; - } - - /* in some cases we want to allow invalid utf8 chars */ - return ui_textedit_insert_buf(but, data, buf, 1); -} - -static void ui_textedit_move(uiBut *but, - uiHandleButtonData *data, - eStrCursorJumpDirection direction, - const bool select, - eStrCursorJumpType jump) -{ - const char *str = data->str; - const int len = strlen(str); - const int pos_prev = but->pos; - const bool has_sel = (but->selend - but->selsta) > 0; - - ui_but_update(but); - - /* special case, quit selection and set cursor */ - if (has_sel && !select) { - if (jump == STRCUR_JUMP_ALL) { - but->selsta = but->selend = but->pos = direction ? len : 0; - } - else { - if (direction) { - but->selsta = but->pos = but->selend; - } - else { - but->pos = but->selend = but->selsta; - } - } - data->sel_pos_init = but->pos; - } - else { - int pos_i = but->pos; - BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true); - but->pos = pos_i; - - if (select) { - if (has_sel == false) { - data->sel_pos_init = pos_prev; - } - but->selsta = but->pos; - but->selend = data->sel_pos_init; - } - if (but->selend < but->selsta) { - SWAP(short, but->selsta, but->selend); - } - } -} - -static bool ui_textedit_delete(uiBut *but, - uiHandleButtonData *data, - int direction, - eStrCursorJumpType jump) -{ - char *str = data->str; - const int len = strlen(str); - - bool changed = false; - - if (jump == STRCUR_JUMP_ALL) { - if (len) { - changed = true; - } - str[0] = '\0'; - but->pos = 0; - } - else if (direction) { /* delete */ - if ((but->selend - but->selsta) > 0) { - changed = ui_textedit_delete_selection(but, data); - } - else if (but->pos >= 0 && but->pos < len) { - int pos = but->pos; - int step; - BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true); - step = pos - but->pos; - memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step)); - changed = true; - } - } - else { /* backspace */ - if (len != 0) { - if ((but->selend - but->selsta) > 0) { - changed = ui_textedit_delete_selection(but, data); - } - else if (but->pos > 0) { - int pos = but->pos; - int step; - - BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true); - step = but->pos - pos; - memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos); - but->pos -= step; - changed = true; - } - } - } - - return changed; -} - -static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - char *str = data->str; - - int changed; - if (data->searchbox) { - changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str); - } - else { - changed = but->autocomplete_func(C, str, but->autofunc_arg); - } - - but->pos = strlen(str); - but->selsta = but->selend = but->pos; - - return changed; -} - -/* mode for ui_textedit_copypaste() */ -enum { - UI_TEXTEDIT_PASTE = 1, - UI_TEXTEDIT_COPY, - UI_TEXTEDIT_CUT, -}; - -static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode) -{ - bool changed = false; - - /* paste */ - if (mode == UI_TEXTEDIT_PASTE) { - /* extract the first line from the clipboard */ - int buf_len; - char *pbuf = WM_clipboard_text_get_firstline(false, &buf_len); - - if (pbuf) { - if (UI_but_is_utf8(but)) { - buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len); - } - - ui_textedit_insert_buf(but, data, pbuf, buf_len); - - changed = true; - - MEM_freeN(pbuf); - } - } - /* cut & copy */ - else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) { - /* copy the contents to the copypaste buffer */ - const int sellen = but->selend - but->selsta; - char *buf = MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste"); - - BLI_strncpy(buf, data->str + but->selsta, sellen + 1); - WM_clipboard_text_set(buf, 0); - MEM_freeN(buf); - - /* for cut only, delete the selection afterwards */ - if (mode == UI_TEXTEDIT_CUT) { - if ((but->selend - but->selsta) > 0) { - changed = ui_textedit_delete_selection(but, data); - } - } - } - - return changed; -} - -#ifdef WITH_INPUT_IME -/* enable ime, and set up uibut ime data */ -static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but)) -{ - /* XXX Is this really needed? */ - int x, y; - - BLI_assert(win->ime_data == NULL); - - /* enable IME and position to cursor, it's a trick */ - x = win->eventstate->x; - /* flip y and move down a bit, prevent the IME panel cover the edit button */ - y = win->eventstate->y - 12; - - wm_window_IME_begin(win, x, y, 0, 0, true); -} - -/* disable ime, and clear uibut ime data */ -static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but)) -{ - wm_window_IME_end(win); -} - -void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete) -{ - BLI_assert(but->active); - - ui_region_to_window(but->active->region, &x, &y); - wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete); -} - -wmIMEData *ui_but_ime_data_get(uiBut *but) -{ - if (but->active && but->active->window) { - return but->active->window->ime_data; - } - else { - return NULL; - } -} -#endif /* WITH_INPUT_IME */ - -static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - wmWindow *win = data->window; - const bool is_num_but = ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER); - bool no_zero_strip = false; - - if (data->str) { - MEM_freeN(data->str); - data->str = NULL; - } - -#ifdef USE_DRAG_MULTINUM - /* this can happen from multi-drag */ - if (data->applied_interactive) { - /* remove any small changes so canceling edit doesn't restore invalid value: T40538 */ - data->cancel = true; - ui_apply_but(C, but->block, but, data, true); - data->cancel = false; - - data->applied_interactive = false; - } -#endif - -#ifdef USE_ALLSELECT - if (is_num_but) { - if (IS_ALLSELECT_EVENT(win->eventstate)) { - data->select_others.is_enabled = true; - data->select_others.is_copy = true; - } - } -#endif - - /* retrieve string */ - data->maxlen = ui_but_string_get_max_length(but); - if (data->maxlen != 0) { - data->str = MEM_callocN(sizeof(char) * data->maxlen, "textedit str"); - /* We do not want to truncate precision to default here, it's nice to show value, - * not to edit it - way too much precision is lost then. */ - ui_but_string_get_ex( - but, data->str, data->maxlen, UI_PRECISION_FLOAT_MAX, true, &no_zero_strip); - } - else { - data->is_str_dynamic = true; - data->str = ui_but_string_get_dynamic(but, &data->maxlen); - } - - if (ui_but_is_float(but) && !ui_but_is_unit(but) && !ui_but_anim_expression_get(but, NULL, 0) && - !no_zero_strip) { - BLI_str_rstrip_float_zero(data->str, '\0'); - } - - if (is_num_but) { - BLI_assert(data->is_str_dynamic == false); - ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen); - } - - /* won't change from now on */ - const int len = strlen(data->str); - - data->origstr = BLI_strdupn(data->str, len); - data->sel_pos_init = 0; - - /* set cursor pos to the end of the text */ - but->editstr = data->str; - but->pos = len; - but->selsta = 0; - but->selend = len; - - /* Initialize undo history tracking. */ - data->undo_stack_text = ui_textedit_undo_stack_create(); - ui_textedit_undo_push(data->undo_stack_text, but->editstr, but->pos); - - /* optional searchbox */ - if (but->type == UI_BTYPE_SEARCH_MENU) { - uiButSearch *search_but = (uiButSearch *)but; - - data->searchbox = search_but->popup_create_fn(C, data->region, search_but); - ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */ - } - - /* reset alert flag (avoid confusion, will refresh on exit) */ - but->flag &= ~UI_BUT_REDALERT; - - ui_but_update(but); - - WM_cursor_modal_set(win, WM_CURSOR_TEXT_EDIT); - -#ifdef WITH_INPUT_IME - if (is_num_but == false && BLT_lang_is_ime_supported()) { - ui_textedit_ime_begin(win, but); - } -#endif -} - -static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - wmWindow *win = data->window; - - if (but) { - if (UI_but_is_utf8(but)) { - const int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr)); - /* not a file?, strip non utf-8 chars */ - if (strip) { - /* wont happen often so isn't that annoying to keep it here for a while */ - printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip); - } - } - - if (data->searchbox) { - if (data->cancel == false) { - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - uiButSearch *but_search = (uiButSearch *)but; - - if ((ui_searchbox_apply(but, data->searchbox) == false) && - (ui_searchbox_find_index(data->searchbox, but->editstr) == -1) && - !but_search->results_are_suggestions) { - data->cancel = true; - - /* ensure menu (popup) too is closed! */ - data->escapecancel = true; - - WM_reportf(RPT_ERROR, "Failed to find '%s'", but->editstr); - WM_report_banner_show(); - } - } - - ui_searchbox_free(C, data->searchbox); - data->searchbox = NULL; - } - - but->editstr = NULL; - but->pos = -1; - } - - WM_cursor_modal_restore(win); - - /* Free text undo history text blocks. */ - ui_textedit_undo_stack_destroy(data->undo_stack_text); - data->undo_stack_text = NULL; - -#ifdef WITH_INPUT_IME - if (win->ime_data) { - ui_textedit_ime_end(win, but); - } -#endif -} - -static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data) -{ - /* label and roundbox can overlap real buttons (backdrops...) */ - if (ELEM(actbut->type, - UI_BTYPE_LABEL, - UI_BTYPE_SEPR, - UI_BTYPE_SEPR_LINE, - UI_BTYPE_ROUNDBOX, - UI_BTYPE_LISTBOX)) { - return; - } - - for (uiBut *but = actbut->next; but; but = but->next) { - if (ui_but_is_editable_as_text(but)) { - if (!(but->flag & UI_BUT_DISABLED)) { - data->postbut = but; - data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; - return; - } - } - } - for (uiBut *but = block->buttons.first; but != actbut; but = but->next) { - if (ui_but_is_editable_as_text(but)) { - if (!(but->flag & UI_BUT_DISABLED)) { - data->postbut = but; - data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; - return; - } - } - } -} - -static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data) -{ - /* label and roundbox can overlap real buttons (backdrops...) */ - if (ELEM(actbut->type, - UI_BTYPE_LABEL, - UI_BTYPE_SEPR, - UI_BTYPE_SEPR_LINE, - UI_BTYPE_ROUNDBOX, - UI_BTYPE_LISTBOX)) { - return; - } - - for (uiBut *but = actbut->prev; but; but = but->prev) { - if (ui_but_is_editable_as_text(but)) { - if (!(but->flag & UI_BUT_DISABLED)) { - data->postbut = but; - data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; - return; - } - } - } - for (uiBut *but = block->buttons.last; but != actbut; but = but->prev) { - if (ui_but_is_editable_as_text(but)) { - if (!(but->flag & UI_BUT_DISABLED)) { - data->postbut = but; - data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; - return; - } - } - } -} - -static void ui_do_but_textedit( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int retval = WM_UI_HANDLER_CONTINUE; - bool changed = false, inbox = false, update = false, skip_undo_push = false; - -#ifdef WITH_INPUT_IME - wmWindow *win = CTX_wm_window(C); - wmIMEData *ime_data = win->ime_data; - const bool is_ime_composing = ime_data && ime_data->is_ime_composing; -#else - const bool is_ime_composing = false; -#endif - - switch (event->type) { - case MOUSEMOVE: - case MOUSEPAN: - if (data->searchbox) { -#ifdef USE_KEYNAV_LIMIT - if ((event->type == MOUSEMOVE) && - ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event)) { - /* pass */ - } - else { - ui_searchbox_event(C, data->searchbox, but, data->region, event); - } -#else - ui_searchbox_event(C, data->searchbox, but, data->region, event); -#endif - } - ui_do_but_extra_operator_icons_mousemove(but, data, event); - - break; - case RIGHTMOUSE: - case EVT_ESCKEY: - if (event->val == KM_PRESS) { - /* Support search context menu. */ - if (event->type == RIGHTMOUSE) { - if (data->searchbox) { - if (ui_searchbox_event(C, data->searchbox, but, data->region, event)) { - /* Only break if the event was handled. */ - break; - } - } - } - -#ifdef WITH_INPUT_IME - /* skips button handling since it is not wanted */ - if (is_ime_composing) { - break; - } -#endif - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - } - break; - case LEFTMOUSE: { - /* Allow clicks on extra icons while editing. */ - if (ui_do_but_extra_operator_icon(C, but, data, event)) { - break; - } - - const bool had_selection = but->selsta != but->selend; - - /* exit on LMB only on RELEASE for searchbox, to mimic other popups, - * and allow multiple menu levels */ - if (data->searchbox) { - inbox = ui_searchbox_inside(data->searchbox, event->x, event->y); - } - - /* for double click: we do a press again for when you first click on button - * (selects all text, no cursor pos) */ - if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { - float mx = event->x; - float my = event->y; - ui_window_to_block_fl(data->region, block, &mx, &my); - - if (ui_but_contains_pt(but, mx, my)) { - ui_textedit_set_cursor_pos(but, data, event->x); - but->selsta = but->selend = but->pos; - data->sel_pos_init = but->pos; - - button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING); - retval = WM_UI_HANDLER_BREAK; - } - else if (inbox == false) { - /* if searchbox, click outside will cancel */ - if (data->searchbox) { - data->cancel = data->escapecancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - } - } - - /* only select a word in button if there was no selection before */ - if (event->val == KM_DBL_CLICK && had_selection == false) { - ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM); - ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM); - retval = WM_UI_HANDLER_BREAK; - changed = true; - } - else if (inbox) { - /* if we allow activation on key press, - * it gives problems launching operators T35713. */ - if (event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - } - } - break; - } - } - - if (event->val == KM_PRESS && !is_ime_composing) { - switch (event->type) { - case EVT_VKEY: - case EVT_XKEY: - case EVT_CKEY: - if (IS_EVENT_MOD(event, ctrl, oskey)) { - if (event->type == EVT_VKEY) { - changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE); - } - else if (event->type == EVT_CKEY) { - changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_COPY); - } - else if (event->type == EVT_XKEY) { - changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_CUT); - } - - retval = WM_UI_HANDLER_BREAK; - } - break; - case EVT_RIGHTARROWKEY: - ui_textedit_move(but, - data, - STRCUR_DIR_NEXT, - event->shift != 0, - event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); - retval = WM_UI_HANDLER_BREAK; - break; - case EVT_LEFTARROWKEY: - ui_textedit_move(but, - data, - STRCUR_DIR_PREV, - event->shift != 0, - event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); - retval = WM_UI_HANDLER_BREAK; - break; - case WHEELDOWNMOUSE: - case EVT_DOWNARROWKEY: - if (data->searchbox) { -#ifdef USE_KEYNAV_LIMIT - ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event); -#endif - ui_searchbox_event(C, data->searchbox, but, data->region, event); - break; - } - if (event->type == WHEELDOWNMOUSE) { - break; - } - ATTR_FALLTHROUGH; - case EVT_ENDKEY: - ui_textedit_move(but, data, STRCUR_DIR_NEXT, event->shift != 0, STRCUR_JUMP_ALL); - retval = WM_UI_HANDLER_BREAK; - break; - case WHEELUPMOUSE: - case EVT_UPARROWKEY: - if (data->searchbox) { -#ifdef USE_KEYNAV_LIMIT - ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event); -#endif - ui_searchbox_event(C, data->searchbox, but, data->region, event); - break; - } - if (event->type == WHEELUPMOUSE) { - break; - } - ATTR_FALLTHROUGH; - case EVT_HOMEKEY: - ui_textedit_move(but, data, STRCUR_DIR_PREV, event->shift != 0, STRCUR_JUMP_ALL); - retval = WM_UI_HANDLER_BREAK; - break; - case EVT_PADENTER: - case EVT_RETKEY: - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - break; - case EVT_DELKEY: - changed = ui_textedit_delete( - but, data, 1, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); - retval = WM_UI_HANDLER_BREAK; - break; - - case EVT_BACKSPACEKEY: - changed = ui_textedit_delete( - but, data, 0, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); - retval = WM_UI_HANDLER_BREAK; - break; - - case EVT_AKEY: - - /* Ctrl-A: Select all. */ -#if defined(__APPLE__) - /* OSX uses Command-A system-wide, so add it. */ - if ((event->oskey && !IS_EVENT_MOD(event, shift, alt, ctrl)) || - (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey))) -#else - if (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)) -#endif - { - ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_ALL); - ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_ALL); - retval = WM_UI_HANDLER_BREAK; - } - break; - - case EVT_TABKEY: - /* There is a key conflict here, we can't tab with auto-complete. */ - if (but->autocomplete_func || data->searchbox) { - const int autocomplete = ui_textedit_autocomplete(C, but, data); - changed = autocomplete != AUTOCOMPLETE_NO_MATCH; - - if (autocomplete == AUTOCOMPLETE_FULL_MATCH) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (!IS_EVENT_MOD(event, ctrl, alt, oskey)) { - /* Use standard keys for cycling through buttons Tab, Shift-Tab to reverse. */ - if (event->shift) { - ui_textedit_prev_but(block, but, data); - } - else { - ui_textedit_next_but(block, but, data); - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - retval = WM_UI_HANDLER_BREAK; - break; - case EVT_ZKEY: { - /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */ - - const bool is_redo = (event->shift != 0); - if ( -#if defined(__APPLE__) - (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) || -#endif - (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) { - int undo_pos; - const char *undo_str = ui_textedit_undo( - data->undo_stack_text, is_redo ? 1 : -1, &undo_pos); - if (undo_str != NULL) { - ui_textedit_string_set(but, data, undo_str); - - /* Set the cursor & clear selection. */ - but->pos = undo_pos; - but->selsta = but->pos; - but->selend = but->pos; - changed = true; - } - retval = WM_UI_HANDLER_BREAK; - skip_undo_push = true; - } - break; - } - } - - if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) -#ifdef WITH_INPUT_IME - && !is_ime_composing && (!WM_event_is_ime_switch(event) || !BLT_lang_is_ime_supported()) -#endif - ) { - char ascii = event->ascii; - const char *utf8_buf = event->utf8_buf; - - /* exception that's useful for number buttons, some keyboard - * numpads have a comma instead of a period */ - if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* could use data->min*/ - if (event->type == EVT_PADPERIOD && ascii == ',') { - ascii = '.'; - utf8_buf = NULL; /* force ascii fallback */ - } - } - - if (utf8_buf && utf8_buf[0]) { - const int utf8_buf_len = BLI_str_utf8_size(utf8_buf); - BLI_assert(utf8_buf_len != -1); - changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len); - } - else { - changed = ui_textedit_insert_ascii(but, data, ascii); - } - - retval = WM_UI_HANDLER_BREAK; - } - /* textbutton with this flag: do live update (e.g. for search buttons) */ - if (but->flag & UI_BUT_TEXTEDIT_UPDATE) { - update = true; - } - } - -#ifdef WITH_INPUT_IME - if (event->type == WM_IME_COMPOSITE_START || event->type == WM_IME_COMPOSITE_EVENT) { - changed = true; - - if (event->type == WM_IME_COMPOSITE_START && but->selend > but->selsta) { - ui_textedit_delete_selection(but, data); - } - if (event->type == WM_IME_COMPOSITE_EVENT && ime_data->result_len) { - ui_textedit_insert_buf(but, data, ime_data->str_result, ime_data->result_len); - } - } - else if (event->type == WM_IME_COMPOSITE_END) { - changed = true; - } -#endif - - if (changed) { - /* The undo stack may be NULL if an event exits editing. */ - if ((skip_undo_push == false) && (data->undo_stack_text != NULL)) { - ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos); - } - - /* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */ - if (update && data->interactive) { - ui_apply_but(C, block, but, data, true); - } - else { - ui_but_update_edited(but); - } - but->changed = true; - - if (data->searchbox) { - ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */ - } - } - - if (changed || (retval == WM_UI_HANDLER_BREAK)) { - ED_region_tag_redraw(data->region); - } -} - -static void ui_do_but_textedit_select( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int retval = WM_UI_HANDLER_CONTINUE; - - switch (event->type) { - case MOUSEMOVE: { - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - ui_textedit_set_cursor_select(but, data, event->x); - retval = WM_UI_HANDLER_BREAK; - break; - } - case LEFTMOUSE: - if (event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - } - retval = WM_UI_HANDLER_BREAK; - break; - } - - if (retval == WM_UI_HANDLER_BREAK) { - ui_but_update(but); - ED_region_tag_redraw(data->region); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Number Editing (various types) - * \{ */ - -static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) -{ - if (but->type == UI_BTYPE_CURVE) { - uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; - but_cumap->edit_cumap = (CurveMapping *)but->poin; - } - else if (but->type == UI_BTYPE_CURVEPROFILE) { - uiButCurveProfile *but_profile = (uiButCurveProfile *)but; - but_profile->edit_profile = (CurveProfile *)but->poin; - } - else if (but->type == UI_BTYPE_COLORBAND) { - uiButColorBand *but_coba = (uiButColorBand *)but; - data->coba = (ColorBand *)but->poin; - but_coba->edit_coba = data->coba; - } - else if (ELEM(but->type, - UI_BTYPE_UNITVEC, - UI_BTYPE_HSVCUBE, - UI_BTYPE_HSVCIRCLE, - UI_BTYPE_COLOR)) { - ui_but_v3_get(but, data->origvec); - copy_v3_v3(data->vec, data->origvec); - but->editvec = data->vec; - } - else { - float softrange, softmin, softmax; - - data->startvalue = ui_but_value_get(but); - data->origvalue = data->startvalue; - data->value = data->origvalue; - but->editval = &data->value; - - softmin = but->softmin; - softmax = but->softmax; - softrange = softmax - softmin; - - if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) { - uiButNumber *number_but = (uiButNumber *)but; - /* Use a minimum so we have a predictable range, - * otherwise some float buttons get a large range. */ - const float value_step_float_min = 0.1f; - const bool is_float = ui_but_is_float(but); - const double value_step = is_float ? - (double)(number_but->step_size * UI_PRECISION_FLOAT_SCALE) : - (int)number_but->step_size; - const float drag_map_softrange_max = UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX * UI_DPI_FAC; - const float softrange_max = min_ff( - softrange, - 2 * (is_float ? min_ff(value_step, value_step_float_min) * - (drag_map_softrange_max / value_step_float_min) : - drag_map_softrange_max)); - - if (softrange > softrange_max) { - /* Center around the value, keeping in the real soft min/max range. */ - softmin = data->origvalue - (softrange_max / 2); - softmax = data->origvalue + (softrange_max / 2); - if (!isfinite(softmin)) { - softmin = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX); - } - if (!isfinite(softmax)) { - softmax = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX); - } - - if (softmin < but->softmin) { - softmin = but->softmin; - softmax = softmin + softrange_max; - } - else if (softmax > but->softmax) { - softmax = but->softmax; - softmin = softmax - softrange_max; - } - - /* Can happen at extreme values. */ - if (UNLIKELY(softmin == softmax)) { - if (data->origvalue > 0.0) { - softmin = nextafterf(softmin, -FLT_MAX); - } - else { - softmax = nextafterf(softmax, FLT_MAX); - } - } - - softrange = softmax - softmin; - } - } - - data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange; - data->dragf = data->dragfstart; - - data->drag_map_soft_min = softmin; - data->drag_map_soft_max = softmax; - } - - data->dragchange = false; - data->draglock = true; -} - -static void ui_numedit_end(uiBut *but, uiHandleButtonData *data) -{ - but->editval = NULL; - but->editvec = NULL; - if (but->type == UI_BTYPE_COLORBAND) { - uiButColorBand *but_coba = (uiButColorBand *)but; - but_coba->edit_coba = NULL; - } - else if (but->type == UI_BTYPE_CURVE) { - uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; - but_cumap->edit_cumap = NULL; - } - else if (but->type == UI_BTYPE_CURVEPROFILE) { - uiButCurveProfile *but_profile = (uiButCurveProfile *)but; - but_profile->edit_profile = NULL; - } - data->dragstartx = 0; - data->draglastx = 0; - data->dragchange = false; - data->dragcbd = NULL; - data->dragsel = 0; -} - -static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) -{ - if (data->interactive) { - ui_apply_but(C, block, but, data, true); - } - else { - ui_but_update(but); - } - - ED_region_tag_redraw(data->region); -} - -static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon) -{ - if (but->active->interactive) { - ui_apply_but(C, but->block, but, but->active, true); - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - WM_operator_name_call_ptr(C, - op_icon->optype_params->optype, - op_icon->optype_params->opcontext, - op_icon->optype_params->opptr); - - /* Force recreation of extra operator icons (pseudo update). */ - ui_but_extra_operator_icons_free(but); - - WM_event_add_mousemove(CTX_wm_window(C)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Menu/Popup Begin/End (various popup types) - * \{ */ - -static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - uiBlockCreateFunc func = NULL; - uiBlockHandleCreateFunc handlefunc = NULL; - uiMenuCreateFunc menufunc = NULL; - uiMenuCreateFunc popoverfunc = NULL; - void *arg = NULL; - - switch (but->type) { - case UI_BTYPE_BLOCK: - case UI_BTYPE_PULLDOWN: - if (but->menu_create_func) { - menufunc = but->menu_create_func; - arg = but->poin; - } - else { - func = but->block_create_func; - arg = but->poin ? but->poin : but->func_argN; - } - break; - case UI_BTYPE_MENU: - case UI_BTYPE_POPOVER: - BLI_assert(but->menu_create_func); - if ((but->type == UI_BTYPE_POPOVER) || ui_but_menu_draw_as_popover(but)) { - popoverfunc = but->menu_create_func; - } - else { - menufunc = but->menu_create_func; - } - arg = but->poin; - break; - case UI_BTYPE_COLOR: - ui_but_v3_get(but, data->origvec); - copy_v3_v3(data->vec, data->origvec); - but->editvec = data->vec; - - if (ui_but_menu_draw_as_popover(but)) { - popoverfunc = but->menu_create_func; - } - else { - handlefunc = ui_block_func_COLOR; - } - arg = but; - break; - - /* quiet warnings for unhandled types */ - default: - break; - } - - if (func || handlefunc) { - data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg, NULL); - if (but->block->handle) { - data->menu->popup = but->block->handle->popup; - } - } - else if (menufunc) { - data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg); - if (but->block->handle) { - data->menu->popup = but->block->handle->popup; - } - } - else if (popoverfunc) { - data->menu = ui_popover_panel_create(C, data->region, but, popoverfunc, arg); - if (but->block->handle) { - data->menu->popup = but->block->handle->popup; - } - } - -#ifdef USE_ALLSELECT - { - if (IS_ALLSELECT_EVENT(data->window->eventstate)) { - data->select_others.is_enabled = true; - } - } -#endif - - /* this makes adjacent blocks auto open from now on */ - // if (but->block->auto_open == 0) { - // but->block->auto_open = 1; - //} -} - -static void ui_block_open_end(bContext *C, uiBut *but, uiHandleButtonData *data) -{ - if (but) { - but->editval = NULL; - but->editvec = NULL; - - but->block->auto_open_last = PIL_check_seconds_timer(); - } - - if (data->menu) { - ui_popup_block_free(C, data->menu); - data->menu = NULL; - } -} - -int ui_but_menu_direction(uiBut *but) -{ - uiHandleButtonData *data = but->active; - - if (data && data->menu) { - return data->menu->direction; - } - - return 0; -} - -/** - * Hack for #uiList #UI_BTYPE_LISTROW buttons to "give" events to overlaying #UI_BTYPE_TEXT - * buttons (Ctrl-Click rename feature & co). - */ -static uiBut *ui_but_list_row_text_activate(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event, - uiButtonActivateType activate_type) -{ - ARegion *region = CTX_wm_region(C); - uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true); - - if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) { - /* exit listrow */ - data->cancel = true; - button_activate_exit(C, but, data, false, false); - - /* Activate the text button. */ - button_activate_init(C, region, labelbut, activate_type); - - return labelbut; - } - return NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Events for Various Button Types - * \{ */ - -static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - float xmax = but->rect.xmax; - const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */ - int x = event->x, y = event->y; - - ui_window_to_block(data->region, but->block, &x, &y); - if (!BLI_rctf_isect_pt(&but->rect, x, y)) { - return NULL; - } - - /* Same as in 'widget_draw_extra_icons', icon padding from the right edge. */ - xmax -= 0.2 * icon_size; - - /* Handle the padding space from the right edge as the last button. */ - if (x > xmax) { - return but->extra_op_icons.last; - } - - /* Inverse order, from right to left. */ - LISTBASE_FOREACH_BACKWARD (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { - if ((x > (xmax - icon_size)) && x <= xmax) { - return op_icon; - } - xmax -= icon_size; - } - - return NULL; -} - -static bool ui_do_but_extra_operator_icon(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - uiButExtraOpIcon *op_icon = ui_but_extra_operator_icon_mouse_over_get(but, data, event); - - if (!op_icon) { - return false; - } - - /* Only act on release, avoids some glitches. */ - if (event->val != KM_RELEASE) { - /* Still swallow events on the icon. */ - return true; - } - - ED_region_tag_redraw(data->region); - button_tooltip_timer_reset(C, but); - - ui_but_extra_operator_icon_apply(C, but, op_icon); - /* Note: 'but', 'data' may now be freed, don't access. */ - - return true; -} - -static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - uiButExtraOpIcon *old_highlighted = NULL; - - /* Unset highlighting of all first. */ - LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { - if (op_icon->highlighted) { - old_highlighted = op_icon; - } - op_icon->highlighted = false; - } - - uiButExtraOpIcon *hovered = ui_but_extra_operator_icon_mouse_over_get(but, data, event); - - if (hovered) { - hovered->highlighted = true; - } - - if (old_highlighted != hovered) { - ED_region_tag_redraw_no_rebuild(data->region); - } -} - -#ifdef USE_DRAG_TOGGLE -/* Shared by any button that supports drag-toggle. */ -static bool ui_do_but_ANY_drag_toggle( - bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, int *r_retval) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) { -# if 0 /* UNUSED */ - data->togdual = event->ctrl; - data->togonly = !event->shift; -# endif - ui_apply_but(C, but->block, but, data, true); - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - *r_retval = WM_UI_HANDLER_BREAK; - return true; - } - } - else if (data->state == BUTTON_STATE_WAIT_DRAG) { - /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into - * its own function */ - data->applied = false; - *r_retval = ui_do_but_EXIT(C, but, data, event); - return true; - } - return false; -} -#endif /* USE_DRAG_TOGGLE */ - -static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ -#ifdef USE_DRAG_TOGGLE - { - int retval; - if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) { - return retval; - } - } -#endif - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE); - return WM_UI_HANDLER_BREAK; - } - if (event->type == LEFTMOUSE && event->val == KM_RELEASE && but->block->handle) { - /* regular buttons will be 'UI_SELECT', menu items 'UI_ACTIVE' */ - if (!(but->flag & (UI_SELECT | UI_ACTIVE))) { - data->cancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_WAIT_RELEASE) { - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - if (!(but->flag & UI_SELECT)) { - data->cancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_HOTKEYEVT(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - but->drawstr[0] = 0; - but->modifier_key = 0; - button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - return WM_UI_HANDLER_CONTINUE; - } - if (event->type == EVT_UNKNOWNKEY) { - WM_report(RPT_WARNING, "Unsupported key: Unknown"); - return WM_UI_HANDLER_CONTINUE; - } - if (event->type == EVT_CAPSLOCKKEY) { - WM_report(RPT_WARNING, "Unsupported key: CapsLock"); - return WM_UI_HANDLER_CONTINUE; - } - - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - /* only cancel if click outside the button */ - if (ui_but_contains_point_px(but, but->active->region, event->x, event->y) == false) { - /* data->cancel doesn't work, this button opens immediate */ - if (but->flag & UI_BUT_IMMEDIATE) { - ui_but_value_set(but, 0); - } - else { - data->cancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - } - - /* always set */ - but->modifier_key = 0; - if (event->shift) { - but->modifier_key |= KM_SHIFT; - } - if (event->alt) { - but->modifier_key |= KM_ALT; - } - if (event->ctrl) { - but->modifier_key |= KM_CTRL; - } - if (event->oskey) { - but->modifier_key |= KM_OSKEY; - } - - ui_but_update(but); - ED_region_tag_redraw(data->region); - - if (event->val == KM_PRESS) { - if (ISHOTKEY(event->type) && (event->type != EVT_ESCKEY)) { - if (WM_key_event_string(event->type, false)[0]) { - ui_but_value_set(but, event->type); - } - else { - data->cancel = true; - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - if (event->type == EVT_ESCKEY) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - } - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_KEYEVT(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - return WM_UI_HANDLER_CONTINUE; - } - - if (event->val == KM_PRESS) { - if (WM_key_event_string(event->type, false)[0]) { - ui_but_value_set(but, event->type); - } - else { - data->cancel = true; - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_TAB( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - const bool is_property = (but->rnaprop != NULL); - -#ifdef USE_DRAG_TOGGLE - if (is_property) { - int retval; - if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) { - return retval; - } - } -#endif - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0; - - if (is_property && ELEM(rna_type, PROP_POINTER, PROP_STRING) && (but->custom_data != NULL) && - (event->type == LEFTMOUSE) && ((event->val == KM_DBL_CLICK) || event->ctrl)) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - return WM_UI_HANDLER_BREAK; - } - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY)) { - const int event_val = (is_property) ? KM_PRESS : KM_CLICK; - if (event->val == event_val) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_TEXT_EDITING) { - ui_do_but_textedit(C, block, but, data, event); - return WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_TEXT_SELECTING) { - ui_do_but_textedit_select(C, block, but, data, event); - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_TEX( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) && - event->val == KM_PRESS) { - if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && (!UI_but_is_utf8(but))) { - /* pass - allow filesel, enter to execute */ - } - else if (but->emboss == UI_EMBOSS_NONE && !event->ctrl) { - /* pass */ - } - else { - if (!ui_but_extra_operator_icon_mouse_over_get(but, data, event)) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - } - return WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_TEXT_EDITING) { - ui_do_but_textedit(C, block, but, data, event); - return WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_TEXT_SELECTING) { - ui_do_but_textedit_select(C, block, but, data, event); - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_SEARCH_UNLINK( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - /* unlink icon is on right */ - if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY)) { - /* doing this on KM_PRESS calls eyedropper after clicking unlink icon */ - if ((event->val == KM_RELEASE) && ui_do_but_extra_operator_icon(C, but, data, event)) { - return WM_UI_HANDLER_BREAK; - } - } - return ui_do_but_TEX(C, block, but, data, event); -} - -static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ -#ifdef USE_DRAG_TOGGLE - { - int retval; - if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) { - return retval; - } - } -#endif - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - bool do_activate = false; - if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY)) { - if (event->val == KM_PRESS) { - do_activate = true; - } - } - else if (event->type == LEFTMOUSE) { - if (ui_block_is_menu(but->block)) { - /* Behave like other menu items. */ - do_activate = (event->val == KM_RELEASE); - } - else { - /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */ - do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK); - } - } - - if (do_activate) { -#if 0 /* UNUSED */ - data->togdual = event->ctrl; - data->togonly = !event->shift; -#endif - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) { - /* Support Ctrl-Wheel to cycle values on expanded enum rows. */ - if (but->type == UI_BTYPE_ROW) { - int type = event->type; - int val = event->val; - - /* Convert pan to scroll-wheel. */ - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - - if (type == MOUSEPAN) { - return WM_UI_HANDLER_BREAK; - } - } - - const int direction = (type == WHEELDOWNMOUSE) ? -1 : 1; - uiBut *but_select = ui_but_find_select_in_enum(but, direction); - if (but_select) { - uiBut *but_other = (direction == -1) ? but_select->next : but_select->prev; - if (but_other && ui_but_find_select_in_enum__cmp(but, but_other)) { - ARegion *region = data->region; - - data->cancel = true; - button_activate_exit(C, but, data, false, false); - - /* Activate the text button. */ - button_activate_init(C, region, but_other, BUTTON_ACTIVATE_OVER); - data = but_other->active; - if (data) { - ui_apply_but(C, but->block, but_other, but_other->active, true); - button_activate_exit(C, but_other, data, false, false); - - /* restore active button */ - button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); - } - else { - /* shouldn't happen */ - BLI_assert(0); - } - } - } - return WM_UI_HANDLER_BREAK; - } - } - } - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - - /* first handle click on icondrag type button */ - if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && but->dragpoin) { - if (ui_but_contains_point_px_icon(but, data->region, event)) { - - /* tell the button to wait and keep checking further events to - * see if it should start dragging */ - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - return WM_UI_HANDLER_CONTINUE; - } - } -#ifdef USE_DRAG_TOGGLE - if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_is_drag_toggle(but)) { - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - return WM_UI_HANDLER_CONTINUE; - } -#endif - - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - int ret = WM_UI_HANDLER_BREAK; - /* XXX (a bit ugly) Special case handling for filebrowser drag button */ - if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) { - ret = WM_UI_HANDLER_CONTINUE; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - return ret; - } - } - else if (data->state == BUTTON_STATE_WAIT_DRAG) { - - /* this function also ends state */ - if (ui_but_drag_init(C, but, data, event)) { - return WM_UI_HANDLER_BREAK; - } - - /* If the mouse has been pressed and released, getting to - * this point without triggering a drag, then clear the - * drag state for this button and continue to pass on the event */ - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_CONTINUE; - } - - /* while waiting for a drag to be triggered, always block - * other events from getting handled */ - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -/* var names match ui_numedit_but_NUM */ -static float ui_numedit_apply_snapf( - uiBut *but, float tempf, float softmin, float softmax, const enum eSnapType snap) -{ - if (tempf == softmin || tempf == softmax || snap == SNAP_OFF) { - /* pass */ - } - else { - float softrange = softmax - softmin; - float fac = 1.0f; - - if (ui_but_is_unit(but)) { - UnitSettings *unit = but->block->unit; - const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); - - if (BKE_unit_is_valid(unit->system, unit_type)) { - fac = (float)BKE_unit_base_scalar(unit->system, unit_type); - if (ELEM(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) { - fac /= unit->scale_length; - } - } - } - - if (fac != 1.0f) { - /* snap in unit-space */ - tempf /= fac; - /* softmin /= fac; */ /* UNUSED */ - /* softmax /= fac; */ /* UNUSED */ - softrange /= fac; - } - - /* workaround, too high snapping values */ - /* snapping by 10's for float buttons is quite annoying (location, scale...), - * but allow for rotations */ - if (softrange >= 21.0f) { - UnitSettings *unit = but->block->unit; - const int unit_type = UI_but_unit_type_get(but); - if ((unit_type == PROP_UNIT_ROTATION) && (unit->system_rotation != USER_UNIT_ROT_RADIANS)) { - /* pass (degrees)*/ - } - else { - softrange = 20.0f; - } - } - - if (snap == SNAP_ON) { - if (softrange < 2.10f) { - tempf = roundf(tempf * 10.0f) * 0.1f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf); - } - else { - tempf = roundf(tempf * 0.1f) * 10.0f; - } - } - else if (snap == SNAP_ON_SMALL) { - if (softrange < 2.10f) { - tempf = roundf(tempf * 100.0f) * 0.01f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf * 10.0f) * 0.1f; - } - else { - tempf = roundf(tempf); - } - } - else { - BLI_assert(0); - } - - if (fac != 1.0f) { - tempf *= fac; - } - } - - return tempf; -} - -static float ui_numedit_apply_snap(int temp, - float softmin, - float softmax, - const enum eSnapType snap) -{ - if (ELEM(temp, softmin, softmax)) { - return temp; - } - - switch (snap) { - case SNAP_OFF: - break; - case SNAP_ON: - temp = 10 * (temp / 10); - break; - case SNAP_ON_SMALL: - temp = 100 * (temp / 100); - break; - } - - return temp; -} - -static bool ui_numedit_but_NUM(uiButNumber *number_but, - uiHandleButtonData *data, - int mx, - const bool is_motion, - const enum eSnapType snap, - float fac) -{ - uiBut *but = &number_but->but; - float deler, tempf; - int lvalue, temp; - bool changed = false; - const bool is_float = ui_but_is_float(but); - - /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */ - if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) { - return changed; - } - - if (ui_but_is_cursor_warp(but)) { - const float softmin = but->softmin; - const float softmax = but->softmax; - const float softrange = softmax - softmin; - - /* Mouse location isn't screen clamped to the screen so use a linear mapping - * 2px == 1-int, or 1px == 1-ClickStep */ - if (is_float) { - fac *= 0.01f * number_but->step_size; - tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac); - tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap); - -#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */ - if (tempf < softmin) { - data->dragstartx -= (softmin - tempf) / fac; - tempf = softmin; - } - else if (tempf > softmax) { - data->dragstartx += (tempf - softmax) / fac; - tempf = softmax; - } -#else - CLAMP(tempf, softmin, softmax); -#endif - - if (tempf != (float)data->value) { - data->dragchange = true; - data->value = tempf; - changed = true; - } - } - else { - if (softrange > 256) { - fac = 1.0; - } /* 1px == 1 */ - else if (softrange > 32) { - fac = 1.0 / 2.0; - } /* 2px == 1 */ - else { - fac = 1.0 / 16.0; - } /* 16px == 1? */ - - temp = data->startvalue + (((double)mx - data->dragstartx) * (double)fac); - temp = ui_numedit_apply_snap(temp, softmin, softmax, snap); - -#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */ - if (temp < softmin) { - data->dragstartx -= (softmin - temp) / fac; - temp = softmin; - } - else if (temp > softmax) { - data->dragstartx += (temp - softmax) / fac; - temp = softmax; - } -#else - CLAMP(temp, softmin, softmax); -#endif - - if (temp != data->value) { - data->dragchange = true; - data->value = temp; - changed = true; - } - } - - data->draglastx = mx; - } - else { - /* Use 'but->softmin', 'but->softmax' when clamping values. */ - const float softmin = data->drag_map_soft_min; - const float softmax = data->drag_map_soft_max; - const float softrange = softmax - softmin; - - float non_linear_range_limit; - float non_linear_pixel_map; - float non_linear_scale; - - /* Use a non-linear mapping of the mouse drag especially for large floats - * (normal behavior) */ - deler = 500; - if (is_float) { - /* not needed for smaller float buttons */ - non_linear_range_limit = 11.0f; - non_linear_pixel_map = 500.0f; - } - else { - /* only scale large int buttons */ - non_linear_range_limit = 129.0f; - /* Larger for ints, we don't need to fine tune them. */ - non_linear_pixel_map = 250.0f; - - /* prevent large ranges from getting too out of control */ - if (softrange > 600) { - deler = powf(softrange, 0.75f); - } - else if (softrange < 25) { - deler = 50.0; - } - else if (softrange < 100) { - deler = 100.0; - } - } - deler /= fac; - - if (softrange > non_linear_range_limit) { - non_linear_scale = (float)abs(mx - data->dragstartx) / non_linear_pixel_map; - } - else { - non_linear_scale = 1.0f; - } - - if (is_float == false) { - /* at minimum, moving cursor 2 pixels should change an int button. */ - CLAMP_MIN(non_linear_scale, 0.5f * UI_DPI_FAC); - } - - data->dragf += (((float)(mx - data->draglastx)) / deler) * non_linear_scale; - - if (but->softmin == softmin) { - CLAMP_MIN(data->dragf, 0.0f); - } - if (but->softmax == softmax) { - CLAMP_MAX(data->dragf, 1.0f); - } - - data->draglastx = mx; - tempf = (softmin + data->dragf * softrange); - - if (!is_float) { - temp = round_fl_to_int(tempf); - - temp = ui_numedit_apply_snap(temp, but->softmin, but->softmax, snap); - - CLAMP(temp, but->softmin, but->softmax); - lvalue = (int)data->value; - - if (temp != lvalue) { - data->dragchange = true; - data->value = (double)temp; - changed = true; - } - } - else { - temp = 0; - tempf = ui_numedit_apply_snapf(but, tempf, but->softmin, but->softmax, snap); - - CLAMP(tempf, but->softmin, but->softmax); - - if (tempf != (float)data->value) { - data->dragchange = true; - data->value = tempf; - changed = true; - } - } - } - - return changed; -} - -static void ui_numedit_set_active(uiBut *but) -{ - const int oldflag = but->drawflag; - but->drawflag &= ~(UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT); - - uiHandleButtonData *data = but->active; - if (!data) { - return; - } - - /* Ignore once we start dragging. */ - if (data->dragchange == false) { - const float handle_width = min_ff(BLI_rctf_size_x(&but->rect) / 3, - BLI_rctf_size_y(&but->rect) * 0.7f); - /* we can click on the side arrows to increment/decrement, - * or click inside to edit the value directly */ - int mx = data->window->eventstate->x; - int my = data->window->eventstate->y; - ui_window_to_block(data->region, but->block, &mx, &my); - - if (mx < (but->rect.xmin + handle_width)) { - but->drawflag |= UI_BUT_ACTIVE_LEFT; - } - else if (mx > (but->rect.xmax - handle_width)) { - but->drawflag |= UI_BUT_ACTIVE_RIGHT; - } - } - - /* Don't change the cursor once pressed. */ - if ((but->flag & UI_SELECT) == 0) { - if ((but->drawflag & UI_BUT_ACTIVE_LEFT) || (but->drawflag & UI_BUT_ACTIVE_RIGHT)) { - if (data->changed_cursor) { - WM_cursor_modal_restore(data->window); - data->changed_cursor = false; - } - } - else { - if (data->changed_cursor == false) { - WM_cursor_modal_set(data->window, WM_CURSOR_X_MOVE); - data->changed_cursor = true; - } - } - } - - if (but->drawflag != oldflag) { - ED_region_tag_redraw(data->region); - } -} - -static int ui_do_but_NUM( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - uiButNumber *number_but = (uiButNumber *)but; - int click = 0; - int retval = WM_UI_HANDLER_CONTINUE; - - /* mouse location scaled to fit the UI */ - int mx = event->x; - int my = event->y; - /* mouse location kept at screen pixel coords */ - const int screen_mx = event->x; - - BLI_assert(but->type == UI_BTYPE_NUM); - - ui_window_to_block(data->region, block, &mx, &my); - ui_numedit_set_active(but); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - int type = event->type, val = event->val; - - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - } - - /* XXX hardcoded keymap check.... */ - if (type == MOUSEPAN && event->ctrl) { - /* allow accumulating values, otherwise scrolling gets preference */ - retval = WM_UI_HANDLER_BREAK; - } - else if (type == WHEELDOWNMOUSE && event->ctrl) { - mx = but->rect.xmin; - but->drawflag &= ~UI_BUT_ACTIVE_RIGHT; - but->drawflag |= UI_BUT_ACTIVE_LEFT; - click = 1; - } - else if (type == WHEELUPMOUSE && event->ctrl) { - mx = but->rect.xmax; - but->drawflag &= ~UI_BUT_ACTIVE_LEFT; - but->drawflag |= UI_BUT_ACTIVE_RIGHT; - click = 1; - } - else if (event->val == KM_PRESS) { - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - else if (event->type == LEFTMOUSE) { - data->dragstartx = data->draglastx = ui_but_is_cursor_warp(but) ? screen_mx : mx; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - click = 1; - } - else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) { - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - data->value = -data->value; - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - } - -#ifdef USE_DRAG_MULTINUM - copy_v2_v2_int(data->multi_data.drag_start, &event->x); -#endif - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - if (data->dragchange) { -#ifdef USE_DRAG_MULTINUM - /* If we started multi-button but didn't drag, then edit. */ - if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) { - click = 1; - } - else -#endif - { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else { - click = 1; - } - } - else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { - const bool is_motion = (event->type == MOUSEMOVE); - const enum eSnapType snap = ui_event_to_snap(event); - float fac; - -#ifdef USE_DRAG_MULTINUM - data->multi_data.drag_dir[0] += abs(data->draglastx - mx); - data->multi_data.drag_dir[1] += abs(data->draglasty - my); -#endif - - fac = 1.0f; - if (event->shift) { - fac /= 10.0f; - } - - if (ui_numedit_but_NUM(number_but, - data, - (ui_but_is_cursor_warp(but) ? screen_mx : mx), - is_motion, - snap, - fac)) { - ui_numedit_apply(C, block, but, data); - } -#ifdef USE_DRAG_MULTINUM - else if (data->multi_data.has_mbuts) { - if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) { - ui_multibut_states_apply(C, data, block); - } - } -#endif - } - retval = WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_TEXT_EDITING) { - ui_do_but_textedit(C, block, but, data, event); - retval = WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_TEXT_SELECTING) { - ui_do_but_textedit_select(C, block, but, data, event); - retval = WM_UI_HANDLER_BREAK; - } - - if (click) { - /* we can click on the side arrows to increment/decrement, - * or click inside to edit the value directly */ - - if (!ui_but_is_float(but)) { - /* Integer Value. */ - if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - const int value_step = (int)number_but->step_size; - BLI_assert(value_step > 0); - const int softmin = round_fl_to_int_clamp(but->softmin); - const int softmax = round_fl_to_int_clamp(but->softmax); - const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? - (double)max_ii(softmin, (int)data->value - value_step) : - (double)min_ii(softmax, (int)data->value + value_step); - if (value_test != data->value) { - data->value = (double)value_test; - } - else { - data->cancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - } - } - else { - /* Float Value. */ - if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE; - BLI_assert(value_step > 0.0f); - const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? - (double)max_ff(but->softmin, - (float)(data->value - value_step)) : - (double)min_ff(but->softmax, - (float)(data->value + value_step)); - if (value_test != data->value) { - data->value = value_test; - } - else { - data->cancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - } - } - - retval = WM_UI_HANDLER_BREAK; - } - - data->draglastx = mx; - data->draglasty = my; - - return retval; -} - -static bool ui_numedit_but_SLI(uiBut *but, - uiHandleButtonData *data, - int mx, - const bool is_horizontal, - const bool is_motion, - const bool snap, - const bool shift) -{ - float cursor_x_range, f, tempf, softmin, softmax, softrange; - int temp, lvalue; - bool changed = false; - float mx_fl, my_fl; - - /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */ - if ((but->type != UI_BTYPE_SCROLL) && (is_motion || data->draglock) && - (ui_but_dragedit_update_mval(data, mx) == false)) { - return changed; - } - - softmin = but->softmin; - softmax = but->softmax; - softrange = softmax - softmin; - - /* yes, 'mx' as both x/y is intentional */ - ui_mouse_scale_warp(data, mx, mx, &mx_fl, &my_fl, shift); - - if (but->type == UI_BTYPE_NUM_SLIDER) { - cursor_x_range = BLI_rctf_size_x(&but->rect); - } - else if (but->type == UI_BTYPE_SCROLL) { - const float size = (is_horizontal) ? BLI_rctf_size_x(&but->rect) : - -BLI_rctf_size_y(&but->rect); - cursor_x_range = size * (but->softmax - but->softmin) / - (but->softmax - but->softmin + but->a1); - } - else { - const float ofs = (BLI_rctf_size_y(&but->rect) / 2.0f); - cursor_x_range = (BLI_rctf_size_x(&but->rect) - ofs); - } - - f = (mx_fl - data->dragstartx) / cursor_x_range + data->dragfstart; - CLAMP(f, 0.0f, 1.0f); - - /* deal with mouse correction */ -#ifdef USE_CONT_MOUSE_CORRECT - if (ui_but_is_cursor_warp(but)) { - /* OK but can go outside bounds */ - if (is_horizontal) { - data->ungrab_mval[0] = but->rect.xmin + (f * cursor_x_range); - data->ungrab_mval[1] = BLI_rctf_cent_y(&but->rect); - } - else { - data->ungrab_mval[1] = but->rect.ymin + (f * cursor_x_range); - data->ungrab_mval[0] = BLI_rctf_cent_x(&but->rect); - } - BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); - } -#endif - /* done correcting mouse */ - - tempf = softmin + f * softrange; - temp = round_fl_to_int(tempf); - - if (snap) { - if (ELEM(tempf, softmin, softmax)) { - /* pass */ - } - else if (ui_but_is_float(but)) { - - if (shift) { - if (ELEM(tempf, softmin, softmax)) { - } - else if (softrange < 2.10f) { - tempf = roundf(tempf * 100.0f) * 0.01f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf * 10.0f) * 0.1f; - } - else { - tempf = roundf(tempf); - } - } - else { - if (softrange < 2.10f) { - tempf = roundf(tempf * 10.0f) * 0.1f; - } - else if (softrange < 21.0f) { - tempf = roundf(tempf); - } - else { - tempf = roundf(tempf * 0.1f) * 10.0f; - } - } - } - else { - temp = 10 * (temp / 10); - tempf = temp; - } - } - - if (!ui_but_is_float(but)) { - lvalue = round(data->value); - - CLAMP(temp, softmin, softmax); - - if (temp != lvalue) { - data->value = temp; - data->dragchange = true; - changed = true; - } - } - else { - CLAMP(tempf, softmin, softmax); - - if (tempf != (float)data->value) { - data->value = tempf; - data->dragchange = true; - changed = true; - } - } - - return changed; -} - -static int ui_do_but_SLI( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int click = 0; - int retval = WM_UI_HANDLER_CONTINUE; - - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - int type = event->type, val = event->val; - - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - } - - /* XXX hardcoded keymap check.... */ - if (type == MOUSEPAN && event->ctrl) { - /* allow accumulating values, otherwise scrolling gets preference */ - retval = WM_UI_HANDLER_BREAK; - } - else if (type == WHEELDOWNMOUSE && event->ctrl) { - mx = but->rect.xmin; - click = 2; - } - else if (type == WHEELUPMOUSE && event->ctrl) { - mx = but->rect.xmax; - click = 2; - } - else if (event->val == KM_PRESS) { - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - retval = WM_UI_HANDLER_BREAK; - } -#ifndef USE_ALLSELECT - /* alt-click on sides to get "arrows" like in UI_BTYPE_NUM buttons, - * and match wheel usage above */ - else if (event->type == LEFTMOUSE && event->alt) { - int halfpos = BLI_rctf_cent_x(&but->rect); - click = 2; - if (mx < halfpos) { - mx = but->rect.xmin; - } - else { - mx = but->rect.xmax; - } - } -#endif - else if (event->type == LEFTMOUSE) { - data->dragstartx = mx; - data->draglastx = mx; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - click = 1; - } - else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) { - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - data->value = -data->value; - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - } - } -#ifdef USE_DRAG_MULTINUM - copy_v2_v2_int(data->multi_data.drag_start, &event->x); -#endif - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - if (data->dragchange) { -#ifdef USE_DRAG_MULTINUM - /* If we started multi-button but didn't drag, then edit. */ - if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) { - click = 1; - } - else -#endif - { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else { -#ifdef USE_CONT_MOUSE_CORRECT - /* reset! */ - copy_v2_fl(data->ungrab_mval, FLT_MAX); -#endif - click = 1; - } - } - else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { - const bool is_motion = (event->type == MOUSEMOVE); -#ifdef USE_DRAG_MULTINUM - data->multi_data.drag_dir[0] += abs(data->draglastx - mx); - data->multi_data.drag_dir[1] += abs(data->draglasty - my); -#endif - if (ui_numedit_but_SLI( - but, data, mx, true, is_motion, event->ctrl != 0, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - -#ifdef USE_DRAG_MULTINUM - else if (data->multi_data.has_mbuts) { - if (data->multi_data.init == BUTTON_MULTI_INIT_ENABLE) { - ui_multibut_states_apply(C, data, block); - } - } -#endif - } - retval = WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_TEXT_EDITING) { - ui_do_but_textedit(C, block, but, data, event); - retval = WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_TEXT_SELECTING) { - ui_do_but_textedit_select(C, block, but, data, event); - retval = WM_UI_HANDLER_BREAK; - } - - if (click) { - if (click == 2) { - /* nudge slider to the left or right */ - float f, tempf, softmin, softmax, softrange; - int temp; - - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - softmin = but->softmin; - softmax = but->softmax; - softrange = softmax - softmin; - - tempf = data->value; - temp = (int)data->value; - -#if 0 - if (but->type == SLI) { - /* same as below */ - f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect)); - } - else -#endif - { - f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect)); - } - - f = softmin + f * softrange; - - if (!ui_but_is_float(but)) { - if (f < temp) { - temp--; - } - else { - temp++; - } - - if (temp >= softmin && temp <= softmax) { - data->value = temp; - } - else { - data->cancel = true; - } - } - else { - if (f < tempf) { - tempf -= 0.01f; - } - else { - tempf += 0.01f; - } - - if (tempf >= softmin && tempf <= softmax) { - data->value = tempf; - } - else { - data->cancel = true; - } - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - } - else { - /* edit the value directly */ - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - } - - data->draglastx = mx; - data->draglasty = my; - - return retval; -} - -static int ui_do_but_SCROLL( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int retval = WM_UI_HANDLER_CONTINUE; - const bool horizontal = (BLI_rctf_size_x(&but->rect) > BLI_rctf_size_y(&but->rect)); - - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->val == KM_PRESS) { - if (event->type == LEFTMOUSE) { - if (horizontal) { - data->dragstartx = mx; - data->draglastx = mx; - } - else { - data->dragstartx = my; - data->draglastx = my; - } - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == EVT_ESCKEY) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else if (event->type == MOUSEMOVE) { - const bool is_motion = (event->type == MOUSEMOVE); - if (ui_numedit_but_SLI( - but, data, (horizontal) ? mx : my, horizontal, is_motion, false, false)) { - ui_numedit_apply(C, block, but, data); - } - } - - retval = WM_UI_HANDLER_BREAK; - } - - return retval; -} - -static int ui_do_but_GRIP( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int retval = WM_UI_HANDLER_CONTINUE; - const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect)); - - /* Note: Having to store org point in window space and recompute it to block "space" each time - * is not ideal, but this is a way to hack around behavior of ui_window_to_block(), which - * returns different results when the block is inside a panel or not... - * See T37739. - */ - - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->val == KM_PRESS) { - if (event->type == LEFTMOUSE) { - data->dragstartx = event->x; - data->dragstarty = event->y; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == EVT_ESCKEY) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else if (event->type == MOUSEMOVE) { - int dragstartx = data->dragstartx; - int dragstarty = data->dragstarty; - ui_window_to_block(data->region, block, &dragstartx, &dragstarty); - data->value = data->origvalue + (horizontal ? mx - dragstartx : dragstarty - my); - ui_numedit_apply(C, block, but, data); - } - - retval = WM_UI_HANDLER_BREAK; - } - - return retval; -} - -static int ui_do_but_LISTROW(bContext *C, - uiBut *but, - uiHandleButtonData *data, - const wmEvent *event) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - /* hack to pass on ctrl+click and double click to overlapping text - * editing field for editing list item names - */ - if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS && - event->ctrl) || - (event->type == LEFTMOUSE && event->val == KM_DBL_CLICK)) { - uiBut *labelbut = ui_but_list_row_text_activate( - C, but, data, event, BUTTON_ACTIVATE_TEXT_EDITING); - if (labelbut) { - /* Nothing else to do. */ - return WM_UI_HANDLER_BREAK; - } - } - } - - return ui_do_but_EXIT(C, but, data, event); -} - -static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - - /* first handle click on icondrag type button */ - if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) { - if (ui_but_contains_point_px_icon(but, data->region, event)) { - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - return WM_UI_HANDLER_BREAK; - } - } -#ifdef USE_DRAG_TOGGLE - if (event->type == LEFTMOUSE && event->val == KM_PRESS && (ui_but_is_drag_toggle(but))) { - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - return WM_UI_HANDLER_BREAK; - } -#endif - /* regular open menu */ - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - return WM_UI_HANDLER_BREAK; - } - if (ui_but_supports_cycling(but)) { - if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) { - int type = event->type; - int val = event->val; - - /* Convert pan to scroll-wheel. */ - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - - if (type == MOUSEPAN) { - return WM_UI_HANDLER_BREAK; - } - } - - const int direction = (type == WHEELDOWNMOUSE) ? 1 : -1; - - data->value = ui_but_menu_step(but, direction); - - button_activate_state(C, but, BUTTON_STATE_EXIT); - ui_apply_but(C, but->block, but, data, true); - - /* Button's state need to be changed to EXIT so moving mouse away from this mouse - * wouldn't lead to cancel changes made to this button, but changing state to EXIT also - * makes no button active for a while which leads to triggering operator when doing fast - * scrolling mouse wheel. using post activate stuff from button allows to make button be - * active again after checking for all all that mouse leave and cancel stuff, so quick - * scroll wouldn't be an issue anymore. Same goes for scrolling wheel in another - * direction below (sergey). - */ - data->postbut = but; - data->posttype = BUTTON_ACTIVATE_OVER; - - /* without this, a new interface that draws as result of the menu change - * won't register that the mouse is over it, eg: - * Alt+MouseWheel over the render slots, without this, - * the slot menu fails to switch a second time. - * - * The active state of the button could be maintained some other way - * and remove this mousemove event. - */ - WM_event_add_mousemove(data->window); - - return WM_UI_HANDLER_BREAK; - } - } - } - else if (data->state == BUTTON_STATE_WAIT_DRAG) { - - /* this function also ends state */ - if (ui_but_drag_init(C, but, data, event)) { - return WM_UI_HANDLER_BREAK; - } - - /* outside icon quit, not needed if drag activated */ - if (0 == ui_but_contains_point_px_icon(but, data->region, event)) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - data->cancel = true; - return WM_UI_HANDLER_BREAK; - } - - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - return WM_UI_HANDLER_BREAK; - } - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_UNITVEC( - uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap) -{ - float mrad; - bool changed = true; - - /* button is presumed square */ - /* if mouse moves outside of sphere, it does negative normal */ - - /* note that both data->vec and data->origvec should be normalized - * else we'll get a harmless but annoying jump when first clicking */ - - float *fp = data->origvec; - const float rad = BLI_rctf_size_x(&but->rect); - const float radsq = rad * rad; - - int mdx, mdy; - if (fp[2] > 0.0f) { - mdx = (rad * fp[0]); - mdy = (rad * fp[1]); - } - else if (fp[2] > -1.0f) { - mrad = rad / sqrtf(fp[0] * fp[0] + fp[1] * fp[1]); - - mdx = 2.0f * mrad * fp[0] - (rad * fp[0]); - mdy = 2.0f * mrad * fp[1] - (rad * fp[1]); - } - else { - mdx = mdy = 0; - } - - float dx = (float)(mx + mdx - data->dragstartx); - float dy = (float)(my + mdy - data->dragstarty); - - fp = data->vec; - mrad = dx * dx + dy * dy; - if (mrad < radsq) { /* inner circle */ - fp[0] = dx; - fp[1] = dy; - fp[2] = sqrtf(radsq - dx * dx - dy * dy); - } - else { /* outer circle */ - - mrad = rad / sqrtf(mrad); /* veclen */ - - dx *= (2.0f * mrad - 1.0f); - dy *= (2.0f * mrad - 1.0f); - - mrad = dx * dx + dy * dy; - if (mrad < radsq) { - fp[0] = dx; - fp[1] = dy; - fp[2] = -sqrtf(radsq - dx * dx - dy * dy); - } - } - normalize_v3(fp); - - if (snap != SNAP_OFF) { - const int snap_steps = (snap == SNAP_ON) ? 4 : 12; /* 45 or 15 degree increments */ - const float snap_steps_angle = M_PI / snap_steps; - float angle, angle_snap; - - /* round each axis of 'fp' to the next increment - * do this in "angle" space - this gives increments of same size */ - for (int i = 0; i < 3; i++) { - angle = asinf(fp[i]); - angle_snap = roundf((angle / snap_steps_angle)) * snap_steps_angle; - fp[i] = sinf(angle_snap); - } - normalize_v3(fp); - changed = !compare_v3v3(fp, data->origvec, FLT_EPSILON); - } - - data->draglastx = mx; - data->draglasty = my; - - return changed; -} - -static void ui_palette_set_active(uiButColor *color_but) -{ - if (color_but->is_pallete_color) { - Palette *palette = (Palette *)color_but->but.rnapoin.owner_id; - PaletteColor *color = color_but->but.rnapoin.data; - palette->active_color = BLI_findindex(&palette->colors, color); - } -} - -static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - BLI_assert(but->type == UI_BTYPE_COLOR); - uiButColor *color_but = (uiButColor *)but; - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - /* first handle click on icondrag type button */ - if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) { - ui_palette_set_active(color_but); - if (ui_but_contains_point_px_icon(but, data->region, event)) { - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - return WM_UI_HANDLER_BREAK; - } - } -#ifdef USE_DRAG_TOGGLE - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - ui_palette_set_active(color_but); - button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); - data->dragstartx = event->x; - data->dragstarty = event->y; - return WM_UI_HANDLER_BREAK; - } -#endif - /* regular open menu */ - if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { - ui_palette_set_active(color_but); - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - return WM_UI_HANDLER_BREAK; - } - if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) { - ColorPicker *cpicker = but->custom_data; - float hsv_static[3] = {0.0f}; - float *hsv = cpicker ? cpicker->hsv_perceptual : hsv_static; - float col[3]; - - ui_but_v3_get(but, col); - rgb_to_hsv_compat_v(col, hsv); - - if (event->type == WHEELDOWNMOUSE) { - hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f); - } - else if (event->type == WHEELUPMOUSE) { - hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f); - } - else { - const float fac = 0.005 * (event->y - event->prevy); - hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f); - } - - hsv_to_rgb_v(hsv, data->vec); - ui_but_v3_set(but, data->vec); - - button_activate_state(C, but, BUTTON_STATE_EXIT); - ui_apply_but(C, but->block, but, data, true); - return WM_UI_HANDLER_BREAK; - } - if (color_but->is_pallete_color && (event->type == EVT_DELKEY) && (event->val == KM_PRESS)) { - Palette *palette = (Palette *)but->rnapoin.owner_id; - PaletteColor *color = but->rnapoin.data; - - BKE_palette_color_remove(palette, color); - - button_activate_state(C, but, BUTTON_STATE_EXIT); - - /* this is risky. it works OK for now, - * but if it gives trouble we should delay execution */ - but->rnapoin = PointerRNA_NULL; - but->rnaprop = NULL; - - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_WAIT_DRAG) { - - /* this function also ends state */ - if (ui_but_drag_init(C, but, data, event)) { - return WM_UI_HANDLER_BREAK; - } - - /* outside icon quit, not needed if drag activated */ - if (0 == ui_but_contains_point_px_icon(but, data->region, event)) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - data->cancel = true; - return WM_UI_HANDLER_BREAK; - } - - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - if (color_but->is_pallete_color) { - if (!event->ctrl) { - float color[3]; - Paint *paint = BKE_paint_get_active_from_context(C); - Brush *brush = BKE_paint_brush(paint); - - if (brush->flag & BRUSH_USE_GRADIENT) { - float *target = &brush->gradient->data[brush->gradient->cur].r; - - if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target); - IMB_colormanagement_srgb_to_scene_linear_v3(target); - } - else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { - RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target); - } - } - else { - Scene *scene = CTX_data_scene(C); - bool updated = false; - - if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { - RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color); - BKE_brush_color_set(scene, brush, color); - updated = true; - } - else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { - RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color); - IMB_colormanagement_scene_linear_to_srgb_v3(color); - BKE_brush_color_set(scene, brush, color); - updated = true; - } - - if (updated) { - PointerRNA brush_ptr; - PropertyRNA *brush_color_prop; - - RNA_id_pointer_create(&brush->id, &brush_ptr); - brush_color_prop = RNA_struct_find_property(&brush_ptr, "color"); - RNA_property_update(C, &brush_ptr, brush_color_prop); - } - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else { - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - } - } - else { - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - } - return WM_UI_HANDLER_BREAK; - } - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_but_UNITVEC( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - const enum eSnapType snap = ui_event_to_snap(event); - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - /* also do drag the first time */ - if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) { - ui_numedit_apply(C, block, but, data); - } - - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { - if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) { - const enum eSnapType snap = ui_event_to_snap(event); - if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -/* scales a vector so no axis exceeds max - * (could become BLI_math func) */ -static void clamp_axis_max_v3(float v[3], const float max) -{ - const float v_max = max_fff(v[0], v[1], v[2]); - if (v_max > max) { - mul_v3_fl(v, max / v_max); - if (v[0] > max) { - v[0] = max; - } - if (v[1] > max) { - v[1] = max; - } - if (v[2] > max) { - v[2] = max; - } - } -} - -static void ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube *hsv_but, - const float rgb[3], - float hsv[3]) -{ - if (hsv_but->gradient_type == UI_GRAD_L_ALT) { - rgb_to_hsl_compat_v(rgb, hsv); - } - else { - rgb_to_hsv_compat_v(rgb, hsv); - } -} - -static void ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube *hsv_but, - const float rgb[3], - float hsv[3]) -{ - if (hsv_but->gradient_type == UI_GRAD_L_ALT) { - rgb_to_hsl_v(rgb, hsv); - } - else { - rgb_to_hsv_v(rgb, hsv); - } -} - -static void ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube *hsv_but, - const float hsv[3], - float rgb[3]) -{ - if (hsv_but->gradient_type == UI_GRAD_L_ALT) { - hsl_to_rgb_v(hsv, rgb); - } - else { - hsv_to_rgb_v(hsv, rgb); - } -} - -static bool ui_numedit_but_HSVCUBE(uiBut *but, - uiHandleButtonData *data, - int mx, - int my, - const enum eSnapType snap, - const bool shift) -{ - const uiButHSVCube *hsv_but = (uiButHSVCube *)but; - ColorPicker *cpicker = but->custom_data; - float *hsv = cpicker->hsv_perceptual; - float rgb[3]; - float x, y; - float mx_fl, my_fl; - const bool changed = true; - - ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift); - -#ifdef USE_CONT_MOUSE_CORRECT - if (ui_but_is_cursor_warp(but)) { - /* OK but can go outside bounds */ - data->ungrab_mval[0] = mx_fl; - data->ungrab_mval[1] = my_fl; - BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); - } -#endif - - ui_but_v3_get(but, rgb); - ui_scene_linear_to_perceptual_space(but, rgb); - - ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv); - - /* only apply the delta motion, not absolute */ - if (shift) { - rcti rect_i; - float xpos, ypos, hsvo[3]; - - BLI_rcti_rctf_copy(&rect_i, &but->rect); - - /* calculate original hsv again */ - copy_v3_v3(rgb, data->origvec); - ui_scene_linear_to_perceptual_space(but, rgb); - - copy_v3_v3(hsvo, hsv); - - ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsvo); - - /* and original position */ - ui_hsvcube_pos_from_vals(hsv_but, &rect_i, hsvo, &xpos, &ypos); - - mx_fl = xpos - (data->dragstartx - mx_fl); - my_fl = ypos - (data->dragstarty - my_fl); - } - - /* relative position within box */ - x = ((float)mx_fl - but->rect.xmin) / BLI_rctf_size_x(&but->rect); - y = ((float)my_fl - but->rect.ymin) / BLI_rctf_size_y(&but->rect); - CLAMP(x, 0.0f, 1.0f); - CLAMP(y, 0.0f, 1.0f); - - switch (hsv_but->gradient_type) { - case UI_GRAD_SV: - hsv[1] = x; - hsv[2] = y; - break; - case UI_GRAD_HV: - hsv[0] = x; - hsv[2] = y; - break; - case UI_GRAD_HS: - hsv[0] = x; - hsv[1] = y; - break; - case UI_GRAD_H: - hsv[0] = x; - break; - case UI_GRAD_S: - hsv[1] = x; - break; - case UI_GRAD_V: - hsv[2] = x; - break; - case UI_GRAD_L_ALT: - hsv[2] = y; - break; - case UI_GRAD_V_ALT: { - /* vertical 'value' strip */ - const float min = but->softmin, max = but->softmax; - /* exception only for value strip - use the range set in but->min/max */ - hsv[2] = y * (max - min) + min; - break; - } - default: - BLI_assert(0); - break; - } - - if (snap != SNAP_OFF) { - if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) { - ui_color_snap_hue(snap, &hsv[0]); - } - } - - ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb); - ui_perceptual_to_scene_linear_space(but, rgb); - - /* clamp because with color conversion we can exceed range T34295. */ - if (hsv_but->gradient_type == UI_GRAD_V_ALT) { - clamp_axis_max_v3(rgb, but->softmax); - } - - copy_v3_v3(data->vec, rgb); - - data->draglastx = mx; - data->draglasty = my; - - return changed; -} - -#ifdef WITH_INPUT_NDOF -static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but, - uiHandleButtonData *data, - const wmNDOFMotionData *ndof, - const enum eSnapType snap, - const bool shift) -{ - ColorPicker *cpicker = hsv_but->but.custom_data; - float *hsv = cpicker->hsv_perceptual; - const float hsv_v_max = max_ff(hsv[2], hsv_but->but.softmax); - float rgb[3]; - const float sensitivity = (shift ? 0.15f : 0.3f) * ndof->dt; - - ui_but_v3_get(&hsv_but->but, rgb); - ui_scene_linear_to_perceptual_space(&hsv_but->but, rgb); - ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv); - - switch (hsv_but->gradient_type) { - case UI_GRAD_SV: - hsv[1] += ndof->rvec[2] * sensitivity; - hsv[2] += ndof->rvec[0] * sensitivity; - break; - case UI_GRAD_HV: - hsv[0] += ndof->rvec[2] * sensitivity; - hsv[2] += ndof->rvec[0] * sensitivity; - break; - case UI_GRAD_HS: - hsv[0] += ndof->rvec[2] * sensitivity; - hsv[1] += ndof->rvec[0] * sensitivity; - break; - case UI_GRAD_H: - hsv[0] += ndof->rvec[2] * sensitivity; - break; - case UI_GRAD_S: - hsv[1] += ndof->rvec[2] * sensitivity; - break; - case UI_GRAD_V: - hsv[2] += ndof->rvec[2] * sensitivity; - break; - case UI_GRAD_V_ALT: - case UI_GRAD_L_ALT: - /* vertical 'value' strip */ - - /* exception only for value strip - use the range set in but->min/max */ - hsv[2] += ndof->rvec[0] * sensitivity; - - CLAMP(hsv[2], hsv_but->but.softmin, hsv_but->but.softmax); - break; - default: - BLI_assert(!"invalid hsv type"); - break; - } - - if (snap != SNAP_OFF) { - if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) { - ui_color_snap_hue(snap, &hsv[0]); - } - } - - /* ndof specific: the changes above aren't clamping */ - hsv_clamp_v(hsv, hsv_v_max); - - ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb); - ui_perceptual_to_scene_linear_space(&hsv_but->but, rgb); - - copy_v3_v3(data->vec, rgb); - ui_but_v3_set(&hsv_but->but, data->vec); -} -#endif /* WITH_INPUT_NDOF */ - -static int ui_do_but_HSVCUBE( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - uiButHSVCube *hsv_but = (uiButHSVCube *)but; - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - const enum eSnapType snap = ui_event_to_snap(event); - - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - /* also do drag the first time */ - if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - - return WM_UI_HANDLER_BREAK; - } -#ifdef WITH_INPUT_NDOF - if (event->type == NDOF_MOTION) { - const wmNDOFMotionData *ndof = event->customdata; - const enum eSnapType snap = ui_event_to_snap(event); - - ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->shift != 0); - - button_activate_state(C, but, BUTTON_STATE_EXIT); - ui_apply_but(C, but->block, but, data, true); - - return WM_UI_HANDLER_BREAK; - } -#endif /* WITH_INPUT_NDOF */ - /* XXX hardcoded keymap check.... */ - if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { - if (ELEM(hsv_but->gradient_type, UI_GRAD_V_ALT, UI_GRAD_L_ALT)) { - int len; - - /* reset only value */ - - len = RNA_property_array_length(&but->rnapoin, but->rnaprop); - if (ELEM(len, 3, 4)) { - float rgb[3], def_hsv[3]; - float def[4]; - ColorPicker *cpicker = but->custom_data; - float *hsv = cpicker->hsv_perceptual; - - RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def); - ui_rgb_to_color_picker_HSVCUBE_v(hsv_but, def, def_hsv); - - ui_but_v3_get(but, rgb); - ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv); - - def_hsv[0] = hsv[0]; - def_hsv[1] = hsv[1]; - - ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, def_hsv, rgb); - ui_but_v3_set(but, rgb); - - RNA_property_update(C, &but->rnapoin, but->rnaprop); - return WM_UI_HANDLER_BREAK; - } - } - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { - if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) { - const enum eSnapType snap = ui_event_to_snap(event); - - if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_HSVCIRCLE(uiBut *but, - uiHandleButtonData *data, - float mx, - float my, - const enum eSnapType snap, - const bool shift) -{ - const bool changed = true; - ColorPicker *cpicker = but->custom_data; - float *hsv = cpicker->hsv_perceptual; - - float mx_fl, my_fl; - ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift); - -#ifdef USE_CONT_MOUSE_CORRECT - if (ui_but_is_cursor_warp(but)) { - /* OK but can go outside bounds */ - data->ungrab_mval[0] = mx_fl; - data->ungrab_mval[1] = my_fl; - { /* clamp */ - const float radius = min_ff(BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)) / 2.0f; - const float cent[2] = {BLI_rctf_cent_x(&but->rect), BLI_rctf_cent_y(&but->rect)}; - const float len = len_v2v2(cent, data->ungrab_mval); - if (len > radius) { - dist_ensure_v2_v2fl(data->ungrab_mval, cent, radius); - } - } - } -#endif - - rcti rect; - BLI_rcti_rctf_copy(&rect, &but->rect); - - float rgb[3]; - ui_but_v3_get(but, rgb); - ui_scene_linear_to_perceptual_space(but, rgb); - ui_color_picker_rgb_to_hsv_compat(rgb, hsv); - - /* exception, when using color wheel in 'locked' value state: - * allow choosing a hue for black values, by giving a tiny increment */ - if (cpicker->use_color_lock) { - if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */ - if (hsv[2] == 0.0f) { - hsv[2] = 0.0001f; - } - } - else { - if (hsv[2] == 0.0f) { - hsv[2] = 0.0001f; - } - if (hsv[2] >= 0.9999f) { - hsv[2] = 0.9999f; - } - } - } - - /* only apply the delta motion, not absolute */ - if (shift) { - float xpos, ypos, hsvo[3], rgbo[3]; - - /* calculate original hsv again */ - copy_v3_v3(hsvo, hsv); - copy_v3_v3(rgbo, data->origvec); - ui_scene_linear_to_perceptual_space(but, rgbo); - ui_color_picker_rgb_to_hsv_compat(rgbo, hsvo); - - /* and original position */ - ui_hsvcircle_pos_from_vals(cpicker, &rect, hsvo, &xpos, &ypos); - - mx_fl = xpos - (data->dragstartx - mx_fl); - my_fl = ypos - (data->dragstarty - my_fl); - } - - ui_hsvcircle_vals_from_pos(&rect, mx_fl, my_fl, hsv, hsv + 1); - - if ((cpicker->use_color_cubic) && (U.color_picker_type == USER_CP_CIRCLE_HSV)) { - hsv[1] = 1.0f - sqrt3f(1.0f - hsv[1]); - } - - if (snap != SNAP_OFF) { - ui_color_snap_hue(snap, &hsv[0]); - } - - ui_color_picker_hsv_to_rgb(hsv, rgb); - - if ((cpicker->use_luminosity_lock)) { - if (!is_zero_v3(rgb)) { - normalize_v3_length(rgb, cpicker->luminosity_lock_value); - } - } - - ui_perceptual_to_scene_linear_space(but, rgb); - ui_but_v3_set(but, rgb); - - data->draglastx = mx; - data->draglasty = my; - - return changed; -} - -#ifdef WITH_INPUT_NDOF -static void ui_ndofedit_but_HSVCIRCLE(uiBut *but, - uiHandleButtonData *data, - const wmNDOFMotionData *ndof, - const enum eSnapType snap, - const bool shift) -{ - ColorPicker *cpicker = but->custom_data; - float *hsv = cpicker->hsv_perceptual; - float rgb[3]; - float phi, r /*, sqr */ /* UNUSED */, v[2]; - const float sensitivity = (shift ? 0.06f : 0.3f) * ndof->dt; - - ui_but_v3_get(but, rgb); - ui_scene_linear_to_perceptual_space(but, rgb); - ui_color_picker_rgb_to_hsv_compat(rgb, hsv); - - /* Convert current color on hue/sat disc to circular coordinates phi, r */ - phi = fmodf(hsv[0] + 0.25f, 1.0f) * -2.0f * (float)M_PI; - r = hsv[1]; - /* sqr = r > 0.0f ? sqrtf(r) : 1; */ /* UNUSED */ - - /* Convert to 2d vectors */ - v[0] = r * cosf(phi); - v[1] = r * sinf(phi); - - /* Use ndof device y and x rotation to move the vector in 2d space */ - v[0] += ndof->rvec[2] * sensitivity; - v[1] += ndof->rvec[0] * sensitivity; - - /* convert back to polar coords on circle */ - phi = atan2f(v[0], v[1]) / (2.0f * (float)M_PI) + 0.5f; - - /* use ndof Y rotation to additionally rotate hue */ - phi += ndof->rvec[1] * sensitivity * 0.5f; - r = len_v2(v); - - /* convert back to hsv values, in range [0,1] */ - hsv[0] = phi; - hsv[1] = r; - - /* exception, when using color wheel in 'locked' value state: - * allow choosing a hue for black values, by giving a tiny increment */ - if (cpicker->use_color_lock) { - if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */ - if (hsv[2] == 0.0f) { - hsv[2] = 0.0001f; - } - } - else { - if (hsv[2] == 0.0f) { - hsv[2] = 0.0001f; - } - if (hsv[2] == 1.0f) { - hsv[2] = 0.9999f; - } - } - } - - if (snap != SNAP_OFF) { - ui_color_snap_hue(snap, &hsv[0]); - } - - hsv_clamp_v(hsv, FLT_MAX); - - ui_color_picker_hsv_to_rgb(hsv, data->vec); - - if (cpicker->use_luminosity_lock) { - if (!is_zero_v3(data->vec)) { - normalize_v3_length(data->vec, cpicker->luminosity_lock_value); - } - } - - ui_perceptual_to_scene_linear_space(but, data->vec); - ui_but_v3_set(but, data->vec); -} -#endif /* WITH_INPUT_NDOF */ - -static int ui_do_but_HSVCIRCLE( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - ColorPicker *cpicker = but->custom_data; - float *hsv = cpicker->hsv_perceptual; - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - const enum eSnapType snap = ui_event_to_snap(event); - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - /* also do drag the first time */ - if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - - return WM_UI_HANDLER_BREAK; - } -#ifdef WITH_INPUT_NDOF - if (event->type == NDOF_MOTION) { - const enum eSnapType snap = ui_event_to_snap(event); - const wmNDOFMotionData *ndof = event->customdata; - - ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->shift != 0); - - button_activate_state(C, but, BUTTON_STATE_EXIT); - ui_apply_but(C, but->block, but, data, true); - - return WM_UI_HANDLER_BREAK; - } -#endif /* WITH_INPUT_NDOF */ - /* XXX hardcoded keymap check.... */ - if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { - int len; - - /* reset only saturation */ - - len = RNA_property_array_length(&but->rnapoin, but->rnaprop); - if (len >= 3) { - float rgb[3], def_hsv[3]; - float *def; - def = MEM_callocN(sizeof(float) * len, "reset_defaults - float"); - - RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def); - ui_color_picker_hsv_to_rgb(def, def_hsv); - - ui_but_v3_get(but, rgb); - ui_color_picker_rgb_to_hsv_compat(rgb, hsv); - - def_hsv[0] = hsv[0]; - def_hsv[2] = hsv[2]; - - hsv_to_rgb_v(def_hsv, rgb); - ui_but_v3_set(but, rgb); - - RNA_property_update(C, &but->rnapoin, but->rnaprop); - - MEM_freeN(def); - } - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - /* XXX hardcoded keymap check.... */ - else if (event->type == WHEELDOWNMOUSE) { - hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f); - ui_but_hsv_set(but); /* converts to rgb */ - ui_numedit_apply(C, block, but, data); - } - else if (event->type == WHEELUPMOUSE) { - hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f); - ui_but_hsv_set(but); /* converts to rgb */ - ui_numedit_apply(C, block, but, data); - } - else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { - if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) { - const enum eSnapType snap = ui_event_to_snap(event); - - if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int mx) -{ - bool changed = false; - - if (data->draglastx == mx) { - return changed; - } - - if (data->coba->tot == 0) { - return changed; - } - - const float dx = ((float)(mx - data->draglastx)) / BLI_rctf_size_x(&but->rect); - data->dragcbd->pos += dx; - CLAMP(data->dragcbd->pos, 0.0f, 1.0f); - - BKE_colorband_update_sort(data->coba); - data->dragcbd = data->coba->data + data->coba->cur; /* because qsort */ - - data->draglastx = mx; - changed = true; - - return changed; -} - -static int ui_do_but_COLORBAND( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - ColorBand *coba = (ColorBand *)but->poin; - - if (event->ctrl) { - /* insert new key on mouse location */ - const float pos = ((float)(mx - but->rect.xmin)) / BLI_rctf_size_x(&but->rect); - BKE_colorband_element_add(coba, pos); - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else { - CBData *cbd; - /* ignore zoom-level for mindist */ - int mindist = (50 * UI_DPI_FAC) * block->aspect; - int xco; - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - - /* activate new key when mouse is close */ - int a; - for (a = 0, cbd = coba->data; a < coba->tot; a++, cbd++) { - xco = but->rect.xmin + (cbd->pos * BLI_rctf_size_x(&but->rect)); - xco = abs(xco - mx); - if (a == coba->cur) { - /* Selected one disadvantage. */ - xco += 5; - } - if (xco < mindist) { - coba->cur = a; - mindist = xco; - } - } - - data->dragcbd = coba->data + coba->cur; - data->dragfstart = data->dragcbd->pos; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - } - - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == MOUSEMOVE) { - if (mx != data->draglastx || my != data->draglasty) { - if (ui_numedit_but_COLORBAND(but, data, mx)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { - if (event->val == KM_PRESS) { - data->dragcbd->pos = data->dragfstart; - BKE_colorband_update_sort(data->coba); - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_CURVE(uiBlock *block, - uiBut *but, - uiHandleButtonData *data, - int evtx, - int evty, - bool snap, - const bool shift) -{ - CurveMapping *cumap = (CurveMapping *)but->poin; - CurveMap *cuma = cumap->cm + cumap->cur; - CurveMapPoint *cmp = cuma->curve; - bool changed = false; - - /* evtx evty and drag coords are absolute mousecoords, - * prevents errors when editing when layout changes */ - int mx = evtx; - int my = evty; - ui_window_to_block(data->region, block, &mx, &my); - int dragx = data->draglastx; - int dragy = data->draglasty; - ui_window_to_block(data->region, block, &dragx, &dragy); - - const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&cumap->curr); - const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&cumap->curr); - - if (snap) { - float d[2]; - - d[0] = mx - data->dragstartx; - d[1] = my - data->dragstarty; - - if (len_squared_v2(d) < (3.0f * 3.0f)) { - snap = false; - } - } - - float fx = (mx - dragx) / zoomx; - float fy = (my - dragy) / zoomy; - - if (data->dragsel != -1) { - CurveMapPoint *cmp_last = NULL; - const float mval_factor = ui_mouse_scale_warp_factor(shift); - bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */ - - fx *= mval_factor; - fy *= mval_factor; - - for (int a = 0; a < cuma->totpoint; a++) { - if (cmp[a].flag & CUMA_SELECT) { - const float origx = cmp[a].x, origy = cmp[a].y; - cmp[a].x += fx; - cmp[a].y += fy; - if (snap) { - cmp[a].x = 0.125f * roundf(8.0f * cmp[a].x); - cmp[a].y = 0.125f * roundf(8.0f * cmp[a].y); - } - if (cmp[a].x != origx || cmp[a].y != origy) { - moved_point = true; - } - - cmp_last = &cmp[a]; - } - } - - BKE_curvemapping_changed(cumap, false); - - if (moved_point) { - data->draglastx = evtx; - data->draglasty = evty; - changed = true; - -#ifdef USE_CONT_MOUSE_CORRECT - /* note: using 'cmp_last' is weak since there may be multiple points selected, - * but in practice this isn't really an issue */ - if (ui_but_is_cursor_warp(but)) { - /* OK but can go outside bounds */ - data->ungrab_mval[0] = but->rect.xmin + ((cmp_last->x - cumap->curr.xmin) * zoomx); - data->ungrab_mval[1] = but->rect.ymin + ((cmp_last->y - cumap->curr.ymin) * zoomy); - BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); - } -#endif - } - - data->dragchange = true; /* mark for selection */ - } - else { - /* clamp for clip */ - if (cumap->flag & CUMA_DO_CLIP) { - if (cumap->curr.xmin - fx < cumap->clipr.xmin) { - fx = cumap->curr.xmin - cumap->clipr.xmin; - } - else if (cumap->curr.xmax - fx > cumap->clipr.xmax) { - fx = cumap->curr.xmax - cumap->clipr.xmax; - } - if (cumap->curr.ymin - fy < cumap->clipr.ymin) { - fy = cumap->curr.ymin - cumap->clipr.ymin; - } - else if (cumap->curr.ymax - fy > cumap->clipr.ymax) { - fy = cumap->curr.ymax - cumap->clipr.ymax; - } - } - - cumap->curr.xmin -= fx; - cumap->curr.ymin -= fy; - cumap->curr.xmax -= fx; - cumap->curr.ymax -= fy; - - data->draglastx = evtx; - data->draglasty = evty; - - changed = true; - } - - return changed; -} - -static int ui_do_but_CURVE( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - bool changed = false; - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - CurveMapping *cumap = (CurveMapping *)but->poin; - CurveMap *cuma = cumap->cm + cumap->cur; - const float m_xy[2] = {mx, my}; - float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius */ - int sel = -1; - - if (event->ctrl) { - float f_xy[2]; - BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy); - - BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]); - BKE_curvemapping_changed(cumap, false); - changed = true; - } - - /* check for selecting of a point */ - CurveMapPoint *cmp = cuma->curve; /* ctrl adds point, new malloc */ - for (int a = 0; a < cuma->totpoint; a++) { - float f_xy[2]; - BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[a].x); - const float dist_sq = len_squared_v2v2(m_xy, f_xy); - if (dist_sq < dist_min_sq) { - sel = a; - dist_min_sq = dist_sq; - } - } - - if (sel == -1) { - float f_xy[2], f_xy_prev[2]; - - /* if the click didn't select anything, check if it's clicked on the - * curve itself, and if so, add a point */ - cmp = cuma->table; - - BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[0].x); - - /* with 160px height 8px should translate to the old 0.05 coefficient at no zoom */ - dist_min_sq = square_f(U.dpi_fac * 8.0f); - - /* loop through the curve segment table and find what's near the mouse. */ - for (int i = 1; i <= CM_TABLE; i++) { - copy_v2_v2(f_xy_prev, f_xy); - BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[i].x); - - if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) { - BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy); - - BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]); - BKE_curvemapping_changed(cumap, false); - - changed = true; - - /* reset cmp back to the curve points again, - * rather than drawing segments */ - cmp = cuma->curve; - - /* find newly added point and make it 'sel' */ - for (int a = 0; a < cuma->totpoint; a++) { - if (cmp[a].x == f_xy[0]) { - sel = a; - } - } - break; - } - } - } - - if (sel != -1) { - /* ok, we move a point */ - /* deselect all if this one is deselect. except if we hold shift */ - if (!event->shift) { - for (int a = 0; a < cuma->totpoint; a++) { - cmp[a].flag &= ~CUMA_SELECT; - } - cmp[sel].flag |= CUMA_SELECT; - } - else { - cmp[sel].flag ^= CUMA_SELECT; - } - } - else { - /* move the view */ - data->cancel = true; - } - - data->dragsel = sel; - - data->dragstartx = event->x; - data->dragstarty = event->y; - data->draglastx = event->x; - data->draglasty = event->y; - - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == MOUSEMOVE) { - if (event->x != data->draglastx || event->y != data->draglasty) { - - if (ui_numedit_but_CURVE( - block, but, data, event->x, event->y, event->ctrl != 0, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - if (data->dragsel != -1) { - CurveMapping *cumap = (CurveMapping *)but->poin; - CurveMap *cuma = cumap->cm + cumap->cur; - CurveMapPoint *cmp = cuma->curve; - - if (data->dragchange == false) { - /* deselect all, select one */ - if (!event->shift) { - for (int a = 0; a < cuma->totpoint; a++) { - cmp[a].flag &= ~CUMA_SELECT; - } - cmp[data->dragsel].flag |= CUMA_SELECT; - } - } - else { - BKE_curvemapping_changed(cumap, true); /* remove doubles */ - BKE_paint_invalidate_cursor_overlay(scene, view_layer, cumap); - } - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - - return WM_UI_HANDLER_BREAK; - } - - /* UNUSED but keep for now */ - (void)changed; - - return WM_UI_HANDLER_CONTINUE; -} - -/* Same as ui_numedit_but_CURVE with some smaller changes. */ -static bool ui_numedit_but_CURVEPROFILE(uiBlock *block, - uiBut *but, - uiHandleButtonData *data, - int evtx, - int evty, - bool snap, - const bool shift) -{ - CurveProfile *profile = (CurveProfile *)but->poin; - CurveProfilePoint *pts = profile->path; - bool changed = false; - - /* evtx evty and drag coords are absolute mousecoords, - * prevents errors when editing when layout changes */ - int mx = evtx; - int my = evty; - ui_window_to_block(data->region, block, &mx, &my); - int dragx = data->draglastx; - int dragy = data->draglasty; - ui_window_to_block(data->region, block, &dragx, &dragy); - - const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect); - const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect); - - if (snap) { - float d[2] = {mx - data->dragstartx, data->dragstarty}; - - if (len_squared_v2(d) < (9.0f * U.dpi_fac)) { - snap = false; - } - } - - float fx = (mx - dragx) / zoomx; - float fy = (my - dragy) / zoomy; - - if (data->dragsel != -1) { - float last_x, last_y; - const float mval_factor = ui_mouse_scale_warp_factor(shift); - bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */ - - fx *= mval_factor; - fy *= mval_factor; - - /* Move all selected points. */ - const float delta[2] = {fx, fy}; - for (int a = 0; a < profile->path_len; a++) { - /* Don't move the last and first control points. */ - if (pts[a].flag & PROF_SELECT) { - moved_point |= BKE_curveprofile_move_point(profile, &pts[a], snap, delta); - last_x = pts[a].x; - last_y = pts[a].y; - } - else { - /* Move handles when they're selected but the control point isn't. */ - if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H1_SELECT) { - moved_point |= BKE_curveprofile_move_handle(&pts[a], true, snap, delta); - last_x = pts[a].h1_loc[0]; - last_y = pts[a].h1_loc[1]; - } - if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H2_SELECT) { - moved_point |= BKE_curveprofile_move_handle(&pts[a], false, snap, delta); - last_x = pts[a].h2_loc[0]; - last_y = pts[a].h2_loc[1]; - } - } - } - - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - - if (moved_point) { - data->draglastx = evtx; - data->draglasty = evty; - changed = true; -#ifdef USE_CONT_MOUSE_CORRECT - /* note: using 'cmp_last' is weak since there may be multiple points selected, - * but in practice this isn't really an issue */ - if (ui_but_is_cursor_warp(but)) { - /* OK but can go outside bounds */ - data->ungrab_mval[0] = but->rect.xmin + ((last_x - profile->view_rect.xmin) * zoomx); - data->ungrab_mval[1] = but->rect.ymin + ((last_y - profile->view_rect.ymin) * zoomy); - BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); - } -#endif - } - data->dragchange = true; /* mark for selection */ - } - else { - /* Clamp the view rect when clipping is on. */ - if (profile->flag & PROF_USE_CLIP) { - if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) { - fx = profile->view_rect.xmin - profile->clip_rect.xmin; - } - else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) { - fx = profile->view_rect.xmax - profile->clip_rect.xmax; - } - if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) { - fy = profile->view_rect.ymin - profile->clip_rect.ymin; - } - else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) { - fy = profile->view_rect.ymax - profile->clip_rect.ymax; - } - } - - profile->view_rect.xmin -= fx; - profile->view_rect.ymin -= fy; - profile->view_rect.xmax -= fx; - profile->view_rect.ymax -= fy; - - data->draglastx = evtx; - data->draglasty = evty; - - changed = true; - } - - return changed; -} - -/** - * Helper for #ui_do_but_CURVEPROFILE. Used to tell whether to select a control point's handles. - */ -static bool point_draw_handles(CurveProfilePoint *point) -{ - return (point->flag & PROF_SELECT && - (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) || - ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT); -} - -/** - * Interaction for curve profile widget. - * \note Uses hardcoded keys rather than the keymap. - */ -static int ui_do_but_CURVEPROFILE( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - CurveProfile *profile = (CurveProfile *)but->poin; - int mx = event->x; - int my = event->y; - - ui_window_to_block(data->region, block, &mx, &my); - - /* Move selected control points. */ - if (event->type == EVT_GKEY && event->val == KM_RELEASE) { - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - return WM_UI_HANDLER_BREAK; - } - - /* Delete selected control points. */ - if (event->type == EVT_XKEY && event->val == KM_RELEASE) { - BKE_curveprofile_remove_by_flag(profile, PROF_SELECT); - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - - /* Selecting, adding, and starting point movements. */ - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - const float m_xy[2] = {mx, my}; - - if (event->ctrl) { - float f_xy[2]; - BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy); - - BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]); - BKE_curveprofile_update(profile, PROF_UPDATE_CLIP); - } - - /* Check for selecting of a point by finding closest point in radius. */ - CurveProfilePoint *pts = profile->path; - float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */ - int i_selected = -1; - short selection_type = 0; /* For handle selection. */ - for (int i = 0; i < profile->path_len; i++) { - float f_xy[2]; - BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x); - float dist_sq = len_squared_v2v2(m_xy, f_xy); - if (dist_sq < dist_min_sq) { - i_selected = i; - selection_type = PROF_SELECT; - dist_min_sq = dist_sq; - } - - /* Also select handles if the point is selected and it has the right handle type. */ - if (point_draw_handles(&pts[i])) { - if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) { - BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h1_loc); - dist_sq = len_squared_v2v2(m_xy, f_xy); - if (dist_sq < dist_min_sq) { - i_selected = i; - selection_type = PROF_H1_SELECT; - dist_min_sq = dist_sq; - } - } - if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) { - BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h2_loc); - dist_sq = len_squared_v2v2(m_xy, f_xy); - if (dist_sq < dist_min_sq) { - i_selected = i; - selection_type = PROF_H2_SELECT; - dist_min_sq = dist_sq; - } - } - } - } - - /* Add a point if the click was close to the path but not a control point or handle. */ - if (i_selected == -1) { - float f_xy[2], f_xy_prev[2]; - CurveProfilePoint *table = profile->table; - BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[0].x); - - dist_min_sq = square_f(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */ - - /* Loop through the path's high resolution table and find what's near the click. */ - for (int i = 1; i <= PROF_TABLE_LEN(profile->path_len); i++) { - copy_v2_v2(f_xy_prev, f_xy); - BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x); - - if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) { - BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy); - - CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]); - BKE_curveprofile_update(profile, PROF_UPDATE_CLIP); - - /* Get the index of the newly added point. */ - i_selected = (int)(new_pt - profile->path); - BLI_assert(i_selected >= 0 && i_selected <= profile->path_len); - selection_type = PROF_SELECT; - break; - } - } - } - - /* Change the flag for the point(s) if one was selected or added. */ - if (i_selected != -1) { - /* Deselect all if this one is deselected, except if we hold shift. */ - if (event->shift) { - pts[i_selected].flag ^= selection_type; - } - else { - for (int i = 0; i < profile->path_len; i++) { - // pts[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT); - profile->path[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT); - } - profile->path[i_selected].flag |= selection_type; - } - } - else { - /* Move the view. */ - data->cancel = true; - } - - data->dragsel = i_selected; - - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */ - if (event->type == MOUSEMOVE) { - if (mx != data->draglastx || my != data->draglasty) { - if (ui_numedit_but_CURVEPROFILE( - block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - /* Finish move. */ - if (data->dragsel != -1) { - - if (data->dragchange == false) { - /* Deselect all, select one. */ - } - else { - /* Remove doubles, clip after move. */ - BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP); - } - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my) -{ - Histogram *hist = (Histogram *)but->poin; - const bool changed = true; - const float dy = my - data->draglasty; - - /* scale histogram values (dy / 10 for better control) */ - const float yfac = min_ff(pow2f(hist->ymax), 1.0f) * 0.5f; - hist->ymax += (dy * 0.1f) * yfac; - - /* 0.1 allows us to see HDR colors up to 10 */ - CLAMP(hist->ymax, 0.1f, 100.0f); - - data->draglastx = mx; - data->draglasty = my; - - return changed; -} - -static int ui_do_but_HISTOGRAM( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - /* also do drag the first time */ - if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) { - ui_numedit_apply(C, block, but, data); - } - - return WM_UI_HANDLER_BREAK; - } - /* XXX hardcoded keymap check.... */ - if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { - Histogram *hist = (Histogram *)but->poin; - hist->ymax = 1.0f; - - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == EVT_ESCKEY) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == MOUSEMOVE) { - if (mx != data->draglastx || my != data->draglasty) { - if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx, int my) -{ - Scopes *scopes = (Scopes *)but->poin; - const bool changed = true; - - const float dy = my - data->draglasty; - - /* scale waveform values */ - scopes->wavefrm_yfac += dy / 200.0f; - - CLAMP(scopes->wavefrm_yfac, 0.5f, 2.0f); - - data->draglastx = mx; - data->draglasty = my; - - return changed; -} - -static int ui_do_but_WAVEFORM( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - /* also do drag the first time */ - if (ui_numedit_but_WAVEFORM(but, data, mx, my)) { - ui_numedit_apply(C, block, but, data); - } - - return WM_UI_HANDLER_BREAK; - } - /* XXX hardcoded keymap check.... */ - if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { - Scopes *scopes = (Scopes *)but->poin; - scopes->wavefrm_yfac = 1.0f; - - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == EVT_ESCKEY) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == MOUSEMOVE) { - if (mx != data->draglastx || my != data->draglasty) { - if (ui_numedit_but_WAVEFORM(but, data, mx, my)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static bool ui_numedit_but_TRACKPREVIEW( - bContext *C, uiBut *but, uiHandleButtonData *data, int mx, int my, const bool shift) -{ - MovieClipScopes *scopes = (MovieClipScopes *)but->poin; - const bool changed = true; - - float dx = mx - data->draglastx; - float dy = my - data->draglasty; - - if (shift) { - dx /= 5.0f; - dy /= 5.0f; - } - - if (!scopes->track_locked) { - const MovieClip *clip = CTX_data_edit_movieclip(C); - const int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, scopes->scene_framenr); - if (scopes->marker->framenr != clip_framenr) { - scopes->marker = BKE_tracking_marker_ensure(scopes->track, clip_framenr); - } - - scopes->marker->flag &= ~(MARKER_DISABLED | MARKER_TRACKED); - scopes->marker->pos[0] += -dx * scopes->slide_scale[0] / BLI_rctf_size_x(&but->block->rect); - scopes->marker->pos[1] += -dy * scopes->slide_scale[1] / BLI_rctf_size_y(&but->block->rect); - - WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL); - } - - scopes->ok = 0; - - data->draglastx = mx; - data->draglasty = my; - - return changed; -} - -static int ui_do_but_TRACKPREVIEW( - bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) -{ - int mx = event->x; - int my = event->y; - ui_window_to_block(data->region, block, &mx, &my); - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - if (event->type == LEFTMOUSE && event->val == KM_PRESS) { - data->dragstartx = mx; - data->dragstarty = my; - data->draglastx = mx; - data->draglasty = my; - button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); - - /* also do drag the first time */ - if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - - return WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - if (event->type == EVT_ESCKEY) { - if (event->val == KM_PRESS) { - data->cancel = true; - data->escapecancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - else if (event->type == MOUSEMOVE) { - if (mx != data->draglastx || my != data->draglasty) { - if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) { - ui_numedit_apply(C, block, but, data); - } - } - } - else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *event) -{ - uiHandleButtonData *data = but->active; - int retval = WM_UI_HANDLER_CONTINUE; - - const bool is_disabled = but->flag & UI_BUT_DISABLED; - - /* if but->pointype is set, but->poin should be too */ - BLI_assert(!but->pointype || but->poin); - - /* Only hard-coded stuff here, button interactions with configurable - * keymaps are handled using operators (see #ED_keymap_ui). */ - - if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) { - - /* handle copy and paste */ - bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) && - !event->shift; - const bool do_copy = event->type == EVT_CKEY && is_press_ctrl_but_no_shift; - const bool do_paste = event->type == EVT_VKEY && is_press_ctrl_but_no_shift; - - /* Specific handling for listrows, we try to find their overlapping tex button. */ - if ((do_copy || do_paste) && but->type == UI_BTYPE_LISTROW) { - uiBut *labelbut = ui_but_list_row_text_activate(C, but, data, event, BUTTON_ACTIVATE_OVER); - if (labelbut) { - but = labelbut; - data = but->active; - } - } - - /* do copy first, because it is the only allowed operator when disabled */ - if (do_copy) { - ui_but_copy(C, but, event->alt); - return WM_UI_HANDLER_BREAK; - } - - /* handle menu */ - if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) && - (event->val == KM_PRESS)) { - /* RMB has two options now */ - if (ui_popup_context_menu_for_button(C, but)) { - return WM_UI_HANDLER_BREAK; - } - } - - if (is_disabled) { - return WM_UI_HANDLER_CONTINUE; - } - - if (do_paste) { - ui_but_paste(C, but, data, event->alt); - return WM_UI_HANDLER_BREAK; - } - - /* handle drop */ - if (event->type == EVT_DROP) { - ui_but_drop(C, event, but, data); - } - - if ((data->state == BUTTON_STATE_HIGHLIGHT) && - ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) && - (event->val == KM_RELEASE) && - /* Only returns true if the event was handled. */ - ui_do_but_extra_operator_icon(C, but, data, event)) { - return WM_UI_HANDLER_BREAK; - } - } - - if (but->flag & UI_BUT_DISABLED) { - return WM_UI_HANDLER_BREAK; - } - - switch (but->type) { - case UI_BTYPE_BUT: - case UI_BTYPE_DECORATOR: - retval = ui_do_but_BUT(C, but, data, event); - break; - case UI_BTYPE_KEY_EVENT: - retval = ui_do_but_KEYEVT(C, but, data, event); - break; - case UI_BTYPE_HOTKEY_EVENT: - retval = ui_do_but_HOTKEYEVT(C, but, data, event); - break; - case UI_BTYPE_TAB: - retval = ui_do_but_TAB(C, block, but, data, event); - break; - case UI_BTYPE_BUT_TOGGLE: - case UI_BTYPE_TOGGLE: - case UI_BTYPE_ICON_TOGGLE: - case UI_BTYPE_ICON_TOGGLE_N: - case UI_BTYPE_TOGGLE_N: - case UI_BTYPE_CHECKBOX: - case UI_BTYPE_CHECKBOX_N: - case UI_BTYPE_ROW: - retval = ui_do_but_TOG(C, but, data, event); - break; - case UI_BTYPE_SCROLL: - retval = ui_do_but_SCROLL(C, block, but, data, event); - break; - case UI_BTYPE_GRIP: - retval = ui_do_but_GRIP(C, block, but, data, event); - break; - case UI_BTYPE_NUM: - retval = ui_do_but_NUM(C, block, but, data, event); - break; - case UI_BTYPE_NUM_SLIDER: - retval = ui_do_but_SLI(C, block, but, data, event); - break; - case UI_BTYPE_LISTBOX: - /* Nothing to do! */ - break; - case UI_BTYPE_LISTROW: - retval = ui_do_but_LISTROW(C, but, data, event); - break; - case UI_BTYPE_ROUNDBOX: - case UI_BTYPE_LABEL: - case UI_BTYPE_IMAGE: - case UI_BTYPE_PROGRESS_BAR: - case UI_BTYPE_NODE_SOCKET: - retval = ui_do_but_EXIT(C, but, data, event); - break; - case UI_BTYPE_HISTOGRAM: - retval = ui_do_but_HISTOGRAM(C, block, but, data, event); - break; - case UI_BTYPE_WAVEFORM: - retval = ui_do_but_WAVEFORM(C, block, but, data, event); - break; - case UI_BTYPE_VECTORSCOPE: - /* Nothing to do! */ - break; - case UI_BTYPE_TEXT: - case UI_BTYPE_SEARCH_MENU: - if ((but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) { - retval = ui_do_but_SEARCH_UNLINK(C, block, but, data, event); - if (retval & WM_UI_HANDLER_BREAK) { - break; - } - } - retval = ui_do_but_TEX(C, block, but, data, event); - break; - case UI_BTYPE_MENU: - case UI_BTYPE_POPOVER: - case UI_BTYPE_BLOCK: - case UI_BTYPE_PULLDOWN: - retval = ui_do_but_BLOCK(C, but, data, event); - break; - case UI_BTYPE_BUT_MENU: - retval = ui_do_but_BUT(C, but, data, event); - break; - case UI_BTYPE_COLOR: - retval = ui_do_but_COLOR(C, but, data, event); - break; - case UI_BTYPE_UNITVEC: - retval = ui_do_but_UNITVEC(C, block, but, data, event); - break; - case UI_BTYPE_COLORBAND: - retval = ui_do_but_COLORBAND(C, block, but, data, event); - break; - case UI_BTYPE_CURVE: - retval = ui_do_but_CURVE(C, block, but, data, event); - break; - case UI_BTYPE_CURVEPROFILE: - retval = ui_do_but_CURVEPROFILE(C, block, but, data, event); - break; - case UI_BTYPE_HSVCUBE: - retval = ui_do_but_HSVCUBE(C, block, but, data, event); - break; - case UI_BTYPE_HSVCIRCLE: - retval = ui_do_but_HSVCIRCLE(C, block, but, data, event); - break; - case UI_BTYPE_TRACK_PREVIEW: - retval = ui_do_but_TRACKPREVIEW(C, block, but, data, event); - break; - - /* quiet warnings for unhandled types */ - case UI_BTYPE_SEPR: - case UI_BTYPE_SEPR_LINE: - case UI_BTYPE_SEPR_SPACER: - case UI_BTYPE_EXTRA: - break; - } - -#ifdef USE_DRAG_MULTINUM - data = but->active; - if (data) { - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || - /* if we started dragging, progress on any event */ - (data->multi_data.init == BUTTON_MULTI_INIT_SETUP)) { - if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) && - ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) { - /* initialize! */ - if (data->multi_data.init == BUTTON_MULTI_INIT_UNSET) { - /* --> (BUTTON_MULTI_INIT_SETUP | BUTTON_MULTI_INIT_DISABLE) */ - - const float margin_y = DRAG_MULTINUM_THRESHOLD_DRAG_Y / sqrtf(block->aspect); - - /* check if we have a vertical gesture */ - if (len_squared_v2(data->multi_data.drag_dir) > (margin_y * margin_y)) { - const float dir_nor_y[2] = {0.0, 1.0f}; - float dir_nor_drag[2]; - - normalize_v2_v2(dir_nor_drag, data->multi_data.drag_dir); - - if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) { - data->multi_data.init = BUTTON_MULTI_INIT_SETUP; - data->multi_data.drag_lock_x = event->x; - } - else { - data->multi_data.init = BUTTON_MULTI_INIT_DISABLE; - } - } - } - else if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) { - /* --> (BUTTON_MULTI_INIT_ENABLE) */ - const float margin_x = DRAG_MULTINUM_THRESHOLD_DRAG_X / sqrtf(block->aspect); - /* Check if we're don't setting buttons. */ - if ((data->str && - ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) || - ((abs(data->multi_data.drag_lock_x - event->x) > margin_x) && - /* Just to be sure, check we're dragging more horizontally then vertically. */ - abs(event->prevx - event->x) > abs(event->prevy - event->y))) { - if (data->multi_data.has_mbuts) { - ui_multibut_states_create(but, data); - data->multi_data.init = BUTTON_MULTI_INIT_ENABLE; - } - else { - data->multi_data.init = BUTTON_MULTI_INIT_DISABLE; - } - } - } - - if (data->multi_data.init == BUTTON_MULTI_INIT_SETUP) { - if (ui_multibut_states_tag(but, data, event)) { - ED_region_tag_redraw(data->region); - } - } - } - } - } -#endif /* USE_DRAG_MULTINUM */ - - return retval; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Tool Tip - * \{ */ - -static void ui_blocks_set_tooltips(ARegion *region, const bool enable) -{ - if (!region) { - return; - } - - /* we disabled buttons when when they were already shown, and - * re-enable them on mouse move */ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - block->tooltipdisabled = !enable; - } -} - -/** - * Recreate tool-tip (use to update dynamic tips) - */ -void UI_but_tooltip_refresh(bContext *C, uiBut *but) -{ - uiHandleButtonData *data = but->active; - if (data) { - bScreen *screen = WM_window_get_active_screen(data->window); - if (screen->tool_tip && screen->tool_tip->region) { - WM_tooltip_refresh(C, data->window); - } - } -} - -/** - * Removes tool-tip timer from active but - * (meaning tool-tip is disabled until it's re-enabled again). - */ -void UI_but_tooltip_timer_remove(bContext *C, uiBut *but) -{ - uiHandleButtonData *data = but->active; - if (data) { - if (data->autoopentimer) { - WM_event_remove_timer(data->wm, data->window, data->autoopentimer); - data->autoopentimer = NULL; - } - - if (data->window) { - WM_tooltip_clear(C, data->window); - } - } -} - -static ARegion *ui_but_tooltip_init( - bContext *C, ARegion *region, int *pass, double *r_pass_delay, bool *r_exit_on_event) -{ - bool is_label = false; - if (*pass == 1) { - is_label = true; - (*pass)--; - (*r_pass_delay) = UI_TOOLTIP_DELAY - UI_TOOLTIP_DELAY_LABEL; - } - - uiBut *but = UI_region_active_but_get(region); - *r_exit_on_event = false; - if (but) { - return UI_tooltip_create_from_button(C, region, but, is_label); - } - return NULL; -} - -static void button_tooltip_timer_reset(bContext *C, uiBut *but) -{ - wmWindowManager *wm = CTX_wm_manager(C); - uiHandleButtonData *data = but->active; - - WM_tooltip_timer_clear(C, data->window); - - if ((U.flag & USER_TOOLTIPS) || (data->tooltip_force)) { - if (!but->block->tooltipdisabled) { - if (!wm->drags.first) { - const bool is_label = UI_but_has_tooltip_label(but); - const double delay = is_label ? UI_TOOLTIP_DELAY_LABEL : UI_TOOLTIP_DELAY; - WM_tooltip_timer_init_ex( - C, data->window, data->area, data->region, ui_but_tooltip_init, delay); - if (is_label) { - bScreen *screen = WM_window_get_active_screen(data->window); - if (screen->tool_tip) { - screen->tool_tip->pass = 1; - } - } - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button State Handling - * \{ */ - -static bool button_modal_state(uiHandleButtonState state) -{ - return ELEM(state, - BUTTON_STATE_WAIT_RELEASE, - BUTTON_STATE_WAIT_KEY_EVENT, - BUTTON_STATE_NUM_EDITING, - BUTTON_STATE_TEXT_EDITING, - BUTTON_STATE_TEXT_SELECTING, - BUTTON_STATE_MENU_OPEN); -} - -static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state) -{ - uiHandleButtonData *data = but->active; - if (data->state == state) { - return; - } - - /* Highlight has timers for tool-tips and auto open. */ - if (state == BUTTON_STATE_HIGHLIGHT) { - but->flag &= ~UI_SELECT; - - button_tooltip_timer_reset(C, but); - - /* Automatic open pull-down block timer. */ - if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER) || - /* Menu button types may draw as popovers, check for this case - * ignoring other kinds of menus (mainly enums). (see T66538). */ - ((but->type == UI_BTYPE_MENU) && - (UI_but_paneltype_get(but) || ui_but_menu_draw_as_popover(but)))) { - if (data->used_mouse && !data->autoopentimer) { - int time; - - if (but->block->auto_open == true) { /* test for toolbox */ - time = 1; - } - else if ((but->block->flag & UI_BLOCK_LOOP && but->type != UI_BTYPE_BLOCK) || - (but->block->auto_open == true)) { - time = 5 * U.menuthreshold2; - } - else if (U.uiflag & USER_MENUOPENAUTO) { - time = 5 * U.menuthreshold1; - } - else { - time = -1; /* do nothing */ - } - - if (time >= 0) { - data->autoopentimer = WM_event_add_timer( - data->wm, data->window, TIMER, 0.02 * (double)time); - } - } - } - } - else { - but->flag |= UI_SELECT; - UI_but_tooltip_timer_remove(C, but); - } - - /* text editing */ - if (state == BUTTON_STATE_TEXT_EDITING && data->state != BUTTON_STATE_TEXT_SELECTING) { - ui_textedit_begin(C, but, data); - } - else if (data->state == BUTTON_STATE_TEXT_EDITING && state != BUTTON_STATE_TEXT_SELECTING) { - ui_textedit_end(C, but, data); - } - else if (data->state == BUTTON_STATE_TEXT_SELECTING && state != BUTTON_STATE_TEXT_EDITING) { - ui_textedit_end(C, but, data); - } - - /* number editing */ - if (state == BUTTON_STATE_NUM_EDITING) { - if (ui_but_is_cursor_warp(but)) { - WM_cursor_grab_enable(CTX_wm_window(C), WM_CURSOR_WRAP_XY, true, NULL); - } - ui_numedit_begin(but, data); - } - else if (data->state == BUTTON_STATE_NUM_EDITING) { - ui_numedit_end(but, data); - - if (but->flag & UI_BUT_DRIVEN) { - /* Only warn when editing stepping/dragging the value. - * No warnings should show for editing driver expressions though! - */ - if (state != BUTTON_STATE_TEXT_EDITING) { - WM_report(RPT_INFO, - "Can't edit driven number value, see graph editor for the driver setup."); - } - } - - if (ui_but_is_cursor_warp(but)) { - -#ifdef USE_CONT_MOUSE_CORRECT - /* stereo3d has issues with changing cursor location so rather avoid */ - if (data->ungrab_mval[0] != FLT_MAX && !WM_stereo3d_enabled(data->window, false)) { - int mouse_ungrab_xy[2]; - ui_block_to_window_fl( - data->region, but->block, &data->ungrab_mval[0], &data->ungrab_mval[1]); - mouse_ungrab_xy[0] = data->ungrab_mval[0]; - mouse_ungrab_xy[1] = data->ungrab_mval[1]; - - WM_cursor_grab_disable(data->window, mouse_ungrab_xy); - } - else { - WM_cursor_grab_disable(data->window, NULL); - } -#else - WM_cursor_grab_disable(data->window, NULL); -#endif - } - } - /* menu open */ - if (state == BUTTON_STATE_MENU_OPEN) { - ui_block_open_begin(C, but, data); - } - else if (data->state == BUTTON_STATE_MENU_OPEN) { - ui_block_open_end(C, but, data); - } - - /* add a short delay before exiting, to ensure there is some feedback */ - if (state == BUTTON_STATE_WAIT_FLASH) { - data->flashtimer = WM_event_add_timer(data->wm, data->window, TIMER, BUTTON_FLASH_DELAY); - } - else if (data->flashtimer) { - WM_event_remove_timer(data->wm, data->window, data->flashtimer); - data->flashtimer = NULL; - } - - /* add hold timer if it's used */ - if (state == BUTTON_STATE_WAIT_RELEASE && (but->hold_func != NULL)) { - data->hold_action_timer = WM_event_add_timer( - data->wm, data->window, TIMER, BUTTON_AUTO_OPEN_THRESH); - } - else if (data->hold_action_timer) { - WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); - data->hold_action_timer = NULL; - } - - /* add a blocking ui handler at the window handler for blocking, modal states - * but not for popups, because we already have a window level handler*/ - if (!(but->block->handle && but->block->handle->popup)) { - if (button_modal_state(state)) { - if (!button_modal_state(data->state)) { - WM_event_add_ui_handler( - C, &data->window->modalhandlers, ui_handler_region_menu, NULL, data, 0); - } - } - else { - if (button_modal_state(data->state)) { - /* true = postpone free */ - WM_event_remove_ui_handler( - &data->window->modalhandlers, ui_handler_region_menu, NULL, data, true); - } - } - } - - /* wait for mousemove to enable drag */ - if (state == BUTTON_STATE_WAIT_DRAG) { - but->flag &= ~UI_SELECT; - } - - data->state = state; - - if (state != BUTTON_STATE_EXIT) { - /* When objects for eg. are removed, running ui_but_update() can access - * the removed data - so disable update on exit. Also in case of - * highlight when not in a popup menu, we remove because data used in - * button below popup might have been removed by action of popup. Needs - * a more reliable solution... */ - if (state != BUTTON_STATE_HIGHLIGHT || (but->block->flag & UI_BLOCK_LOOP)) { - ui_but_update(but); - } - } - - /* redraw */ - ED_region_tag_redraw_no_rebuild(data->region); -} - -static void button_activate_init(bContext *C, - ARegion *region, - uiBut *but, - uiButtonActivateType type) -{ - /* Only ever one active button! */ - BLI_assert(ui_region_find_active_but(region) == NULL); - - /* setup struct */ - uiHandleButtonData *data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData"); - data->wm = CTX_wm_manager(C); - data->window = CTX_wm_window(C); - data->area = CTX_wm_area(C); - BLI_assert(region != NULL); - data->region = region; - -#ifdef USE_CONT_MOUSE_CORRECT - copy_v2_fl(data->ungrab_mval, FLT_MAX); -#endif - - if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_CURVEPROFILE, UI_BTYPE_SEARCH_MENU)) { - /* XXX curve is temp */ - } - else { - if ((but->flag & UI_BUT_UPDATE_DELAY) == 0) { - data->interactive = true; - } - } - - data->state = BUTTON_STATE_INIT; - - /* activate button */ - but->flag |= UI_ACTIVE; - - but->active = data; - - /* we disable auto_open in the block after a threshold, because we still - * want to allow auto opening adjacent menus even if no button is activated - * in between going over to the other button, but only for a short while */ - if (type == BUTTON_ACTIVATE_OVER && but->block->auto_open == true) { - if (but->block->auto_open_last + BUTTON_AUTO_OPEN_THRESH < PIL_check_seconds_timer()) { - but->block->auto_open = false; - } - } - - if (type == BUTTON_ACTIVATE_OVER) { - data->used_mouse = true; - } - button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); - - /* activate right away */ - if (but->flag & UI_BUT_IMMEDIATE) { - if (but->type == UI_BTYPE_HOTKEY_EVENT) { - button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); - } - /* .. more to be added here */ - } - - if (type == BUTTON_ACTIVATE_OPEN) { - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - - /* activate first button in submenu */ - if (data->menu && data->menu->region) { - ARegion *subar = data->menu->region; - uiBlock *subblock = subar->uiblocks.first; - uiBut *subbut; - - if (subblock) { - subbut = ui_but_first(subblock); - - if (subbut) { - ui_handle_button_activate(C, subar, subbut, BUTTON_ACTIVATE); - } - } - } - } - else if (type == BUTTON_ACTIVATE_TEXT_EDITING) { - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - } - else if (type == BUTTON_ACTIVATE_APPLY) { - button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH); - } - - if (but->type == UI_BTYPE_GRIP) { - const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect)); - WM_cursor_modal_set(data->window, horizontal ? WM_CURSOR_X_MOVE : WM_CURSOR_Y_MOVE); - } - else if (but->type == UI_BTYPE_NUM) { - ui_numedit_set_active(but); - } - - if (UI_but_has_tooltip_label(but)) { - /* Show a label for this button. */ - bScreen *screen = WM_window_get_active_screen(data->window); - if ((PIL_check_seconds_timer() - WM_tooltip_time_closed()) < 0.1) { - WM_tooltip_immediate_init(C, CTX_wm_window(C), data->area, region, ui_but_tooltip_init); - if (screen->tool_tip) { - screen->tool_tip->pass = 1; - } - } - } -} - -static void button_activate_exit( - bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree) -{ - wmWindow *win = data->window; - uiBlock *block = but->block; - - if (but->type == UI_BTYPE_GRIP) { - WM_cursor_modal_restore(win); - } - - /* ensure we are in the exit state */ - if (data->state != BUTTON_STATE_EXIT) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - - /* apply the button action or value */ - if (!onfree) { - ui_apply_but(C, block, but, data, false); - } - -#ifdef USE_DRAG_MULTINUM - if (data->multi_data.has_mbuts) { - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - if (bt->flag & UI_BUT_DRAG_MULTI) { - bt->flag &= ~UI_BUT_DRAG_MULTI; - - if (!data->cancel) { - ui_apply_but_autokey(C, bt); - } - } - } - - ui_multibut_free(data, block); - } -#endif - - /* if this button is in a menu, this will set the button return - * value to the button value and the menu return value to ok, the - * menu return value will be picked up and the menu will close */ - if (block->handle && !(block->flag & UI_BLOCK_KEEP_OPEN)) { - if (!data->cancel || data->escapecancel) { - uiPopupBlockHandle *menu; - - menu = block->handle; - menu->butretval = data->retval; - menu->menuretval = (data->cancel) ? UI_RETURN_CANCEL : UI_RETURN_OK; - } - } - - if (!onfree && !data->cancel) { - /* autokey & undo push */ - ui_apply_but_undo(but); - ui_apply_but_autokey(C, but); - -#ifdef USE_ALLSELECT - { - /* only RNA from this button is used */ - uiBut but_temp = *but; - uiSelectContextStore *selctx_data = &data->select_others; - for (int i = 0; i < selctx_data->elems_len; i++) { - uiSelectContextElem *other = &selctx_data->elems[i]; - but_temp.rnapoin = other->ptr; - ui_apply_but_autokey(C, &but_temp); - } - } -#endif - - /* popup menu memory */ - if (block->flag & UI_BLOCK_POPUP_MEMORY) { - ui_popup_menu_memory_set(block, but); - } - - if (U.runtime.is_dirty == false) { - ui_but_update_preferences_dirty(but); - } - } - - /* Disable tool-tips until mouse-move + last active flag. */ - LISTBASE_FOREACH (uiBlock *, block_iter, &data->region->uiblocks) { - LISTBASE_FOREACH (uiBut *, bt, &block_iter->buttons) { - bt->flag &= ~UI_BUT_LAST_ACTIVE; - } - - block_iter->tooltipdisabled = true; - } - - ui_blocks_set_tooltips(data->region, false); - - /* clean up */ - if (data->str) { - MEM_freeN(data->str); - } - if (data->origstr) { - MEM_freeN(data->origstr); - } - -#ifdef USE_ALLSELECT - ui_selectcontext_end(but, &data->select_others); -#endif - - if (data->changed_cursor) { - WM_cursor_modal_restore(data->window); - } - - /* redraw and refresh (for popups) */ - ED_region_tag_redraw_no_rebuild(data->region); - ED_region_tag_refresh_ui(data->region); - - /* clean up button */ - if (but->active) { - MEM_freeN(but->active); - but->active = NULL; - } - - but->flag &= ~(UI_ACTIVE | UI_SELECT); - but->flag |= UI_BUT_LAST_ACTIVE; - if (!onfree) { - ui_but_update(but); - } - - /* adds empty mousemove in queue for re-init handler, in case mouse is - * still over a button. We cannot just check for this ourselves because - * at this point the mouse may be over a button in another region */ - if (mousemove) { - WM_event_add_mousemove(CTX_wm_window(C)); - } -} - -void ui_but_active_free(const bContext *C, uiBut *but) -{ - /* this gets called when the button somehow disappears while it is still - * active, this is bad for user interaction, but we need to handle this - * case cleanly anyway in case it happens */ - if (but->active) { - uiHandleButtonData *data = but->active; - data->cancel = true; - button_activate_exit((bContext *)C, but, data, false, true); - } -} - -/* returns the active button with an optional checking function */ -static uiBut *ui_context_button_active(const ARegion *region, bool (*but_check_cb)(const uiBut *)) -{ - uiBut *but_found = NULL; - - while (region) { - uiBut *activebut = NULL; - - /* find active button */ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->active) { - activebut = but; - } - else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) { - activebut = but; - } - } - } - - if (activebut && (but_check_cb == NULL || but_check_cb(activebut))) { - uiHandleButtonData *data = activebut->active; - - but_found = activebut; - - /* Recurse into opened menu, like color-picker case. */ - if (data && data->menu && (region != data->menu->region)) { - region = data->menu->region; - } - else { - return but_found; - } - } - else { - /* no active button */ - return but_found; - } - } - - return but_found; -} - -static bool ui_context_rna_button_active_test(const uiBut *but) -{ - return (but->rnapoin.data != NULL); -} -static uiBut *ui_context_rna_button_active(const bContext *C) -{ - return ui_context_button_active(CTX_wm_region(C), ui_context_rna_button_active_test); -} - -uiBut *UI_context_active_but_get(const bContext *C) -{ - return ui_context_button_active(CTX_wm_region(C), NULL); -} - -/* - * Version of #UI_context_active_get() that uses the result of #CTX_wm_menu() - * if set. Does not traverse into parent menus, which may be wanted in some - * cases. - */ -uiBut *UI_context_active_but_get_respect_menu(const bContext *C) -{ - ARegion *region_menu = CTX_wm_menu(C); - return ui_context_button_active(region_menu ? region_menu : CTX_wm_region(C), NULL); -} - -uiBut *UI_region_active_but_get(const ARegion *region) -{ - return ui_context_button_active(region, NULL); -} - -uiBut *UI_region_but_find_rect_over(const ARegion *region, const rcti *rect_px) -{ - return ui_but_find_rect_over(region, rect_px); -} - -uiBlock *UI_region_block_find_mouse_over(const struct ARegion *region, - const int xy[2], - bool only_clip) -{ - return ui_block_find_mouse_over_ex(region, xy[0], xy[1], only_clip); -} - -/** - * Version of #UI_context_active_but_get that also returns RNA property info. - * Helper function for insert keyframe, reset to default, etc operators. - * - * \return active button, NULL if none found or if it doesn't contain valid RNA data. - */ -uiBut *UI_context_active_but_prop_get(const bContext *C, - struct PointerRNA *r_ptr, - struct PropertyRNA **r_prop, - int *r_index) -{ - uiBut *activebut = ui_context_rna_button_active(C); - - if (activebut && activebut->rnapoin.data) { - *r_ptr = activebut->rnapoin; - *r_prop = activebut->rnaprop; - *r_index = activebut->rnaindex; - } - else { - memset(r_ptr, 0, sizeof(*r_ptr)); - *r_prop = NULL; - *r_index = 0; - } - - return activebut; -} - -void UI_context_active_but_prop_handle(bContext *C) -{ - uiBut *activebut = ui_context_rna_button_active(C); - if (activebut) { - /* TODO, look into a better way to handle the button change - * currently this is mainly so reset defaults works for the - * operator redo panel - campbell */ - uiBlock *block = activebut->block; - if (block->handle_func) { - block->handle_func(C, block->handle_func_arg, activebut->retval); - } - } -} - -void UI_context_active_but_clear(bContext *C, wmWindow *win, ARegion *region) -{ - wm_event_handler_ui_cancel_ex(C, win, region, false); -} - -wmOperator *UI_context_active_operator_get(const struct bContext *C) -{ - ARegion *region_ctx = CTX_wm_region(C); - - /* background mode */ - if (region_ctx == NULL) { - return NULL; - } - - /* scan active regions ui */ - LISTBASE_FOREACH (uiBlock *, block, ®ion_ctx->uiblocks) { - if (block->ui_operator) { - return block->ui_operator; - } - } - - /* scan popups */ - { - bScreen *screen = CTX_wm_screen(C); - - LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { - if (region == region_ctx) { - continue; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (block->ui_operator) { - return block->ui_operator; - } - } - } - } - - return NULL; -} - -/** - * Try to find a search-box region opened from a button in \a button_region. - */ -ARegion *UI_region_searchbox_region_get(const ARegion *button_region) -{ - uiBut *but = UI_region_active_but_get(button_region); - return (but != NULL) ? but->active->searchbox : NULL; -} - -/* helper function for insert keyframe, reset to default, etc operators */ -void UI_context_update_anim_flag(const bContext *C) -{ - Scene *scene = CTX_data_scene(C); - ARegion *region = CTX_wm_region(C); - struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); - const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( - depsgraph, (scene) ? scene->r.cfra : 0.0f); - - while (region) { - /* find active button */ - uiBut *activebut = NULL; - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - ui_but_anim_flag(but, &anim_eval_context); - ui_but_override_flag(CTX_data_main(C), but); - if (UI_but_is_decorator(but)) { - ui_but_anim_decorate_update_from_flag((uiButDecorator *)but); - } - - ED_region_tag_redraw(region); - - if (but->active) { - activebut = but; - } - else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) { - activebut = but; - } - } - } - - if (activebut) { - /* Always recurse into opened menu, so all buttons update (like color-picker). */ - uiHandleButtonData *data = activebut->active; - if (data && data->menu) { - region = data->menu->region; - } - else { - return; - } - } - else { - /* no active button */ - return; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Activation Handling - * \{ */ - -static uiBut *ui_but_find_open_event(ARegion *region, const wmEvent *event) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but == event->customdata) { - return but; - } - } - } - return NULL; -} - -static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *region) -{ - if (event->type == MOUSEMOVE) { - uiBut *but = ui_but_find_mouse_over(region, event); - if (but) { - button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); - - if (event->alt && but->active) { - /* Display tool-tips if holding Alt on mouse-over when tool-tips are disabled in the - * preferences. */ - but->active->tooltip_force = true; - } - } - } - else if (event->type == EVT_BUT_OPEN) { - uiBut *but = ui_but_find_open_event(region, event); - if (but) { - button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); - ui_do_button(C, but->block, but, event); - } - } - - return WM_UI_HANDLER_CONTINUE; -} - -/** - * Exported to interface.c: #UI_but_active_only() - * \note The region is only for the button. - * The context needs to be set by the caller. - */ -void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but) -{ - wmWindow *win = CTX_wm_window(C); - - button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); - - wmEvent event; - wm_event_init_from_window(win, &event); - event.type = EVT_BUT_OPEN; - event.val = KM_PRESS; - event.is_repeat = false; - event.customdata = but; - event.customdatafree = false; - - ui_do_button(C, but->block, but, &event); -} - -/** - * Simulate moving the mouse over a button (or navigating to it with arrow keys). - * - * exported so menus can start with a highlighted button, - * even if the mouse isn't over it - */ -void ui_but_activate_over(bContext *C, ARegion *region, uiBut *but) -{ - button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); -} - -void ui_but_execute_begin(struct bContext *UNUSED(C), - struct ARegion *region, - uiBut *but, - void **active_back) -{ - BLI_assert(region != NULL); - BLI_assert(BLI_findindex(®ion->uiblocks, but->block) != -1); - /* note: ideally we would not have to change 'but->active' however - * some functions we call don't use data (as they should be doing) */ - uiHandleButtonData *data; - *active_back = but->active; - data = MEM_callocN(sizeof(uiHandleButtonData), "uiHandleButtonData_Fake"); - but->active = data; - BLI_assert(region != NULL); - data->region = region; -} - -void ui_but_execute_end(struct bContext *C, - struct ARegion *UNUSED(region), - uiBut *but, - void *active_back) -{ - ui_apply_but(C, but->block, but, but->active, true); - - if ((but->flag & UI_BUT_DRAG_MULTI) == 0) { - ui_apply_but_autokey(C, but); - } - /* use onfree event so undo is handled by caller and apply is already done above */ - button_activate_exit((bContext *)C, but, but->active, false, true); - but->active = active_back; -} - -static void ui_handle_button_activate(bContext *C, - ARegion *region, - uiBut *but, - uiButtonActivateType type) -{ - uiBut *oldbut = ui_region_find_active_but(region); - if (oldbut) { - uiHandleButtonData *data = oldbut->active; - data->cancel = true; - button_activate_exit(C, oldbut, data, false, false); - } - - button_activate_init(C, region, but, type); -} - -/** - * Use for key accelerator or default key to activate the button even if its not active. - */ -static bool ui_handle_button_activate_by_type(bContext *C, ARegion *region, uiBut *but) -{ - if (but->type == UI_BTYPE_BUT_MENU) { - /* mainly for operator buttons */ - ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_APPLY); - } - else if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) { - /* open sub-menus (like right arrow key) */ - ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN); - } - else if (but->type == UI_BTYPE_MENU) { - /* activate menu items */ - ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE); - } - else { -#ifdef DEBUG - printf("%s: error, unhandled type: %u\n", __func__, but->type); -#endif - return false; - } - return true; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Handle Events for Activated Buttons - * \{ */ - -static bool ui_button_value_default(uiBut *but, double *r_value) -{ - if (but->rnaprop != NULL && ui_but_is_rna_valid(but)) { - const int type = RNA_property_type(but->rnaprop); - if (ELEM(type, PROP_FLOAT, PROP_INT)) { - double default_value; - switch (type) { - case PROP_INT: - if (RNA_property_array_check(but->rnaprop)) { - default_value = (double)RNA_property_int_get_default_index( - &but->rnapoin, but->rnaprop, but->rnaindex); - } - else { - default_value = (double)RNA_property_int_get_default(&but->rnapoin, but->rnaprop); - } - break; - case PROP_FLOAT: - if (RNA_property_array_check(but->rnaprop)) { - default_value = (double)RNA_property_float_get_default_index( - &but->rnapoin, but->rnaprop, but->rnaindex); - } - else { - default_value = (double)RNA_property_float_get_default(&but->rnapoin, but->rnaprop); - } - break; - } - *r_value = default_value; - return true; - } - } - return false; -} - -static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) -{ - uiHandleButtonData *data = but->active; - const uiHandleButtonState state_orig = data->state; - - uiBlock *block = but->block; - ARegion *region = data->region; - - int retval = WM_UI_HANDLER_CONTINUE; - - if (data->state == BUTTON_STATE_HIGHLIGHT) { - switch (event->type) { - case WINDEACTIVATE: - case EVT_BUT_CANCEL: - data->cancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - break; -#ifdef USE_UI_POPOVER_ONCE - case LEFTMOUSE: { - if (event->val == KM_RELEASE) { - if (block->flag & UI_BLOCK_POPOVER_ONCE) { - if (!(but->flag & UI_BUT_DISABLED)) { - if (ui_but_is_popover_once_compat(but)) { - data->cancel = false; - button_activate_state(C, but, BUTTON_STATE_EXIT); - retval = WM_UI_HANDLER_BREAK; - /* Cancel because this `but` handles all events and we don't want - * the parent button's update function to do anything. - * - * Causes issues with buttons defined by #uiItemFullR_with_popover. */ - block->handle->menuretval = UI_RETURN_CANCEL; - } - else if (ui_but_is_editable_as_text(but)) { - ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_TEXT_EDITING); - retval = WM_UI_HANDLER_BREAK; - } - } - } - } - break; - } -#endif - case MOUSEMOVE: { - uiBut *but_other = ui_but_find_mouse_over(region, event); - bool exit = false; - - /* always deactivate button for pie menus, - * else moving to blank space will leave activated */ - if ((!ui_block_is_menu(block) || ui_block_is_pie_menu(block)) && - !ui_but_contains_point_px(but, region, event->x, event->y)) { - exit = true; - } - else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) { - exit = true; - } - - if (exit) { - data->cancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else if (event->x != event->prevx || event->y != event->prevy) { - /* Re-enable tool-tip on mouse move. */ - ui_blocks_set_tooltips(region, true); - button_tooltip_timer_reset(C, but); - } - - /* Update extra icons states. */ - ui_do_but_extra_operator_icons_mousemove(but, data, event); - - break; - } - case TIMER: { - /* Handle menu auto open timer. */ - if (event->customdata == data->autoopentimer) { - WM_event_remove_timer(data->wm, data->window, data->autoopentimer); - data->autoopentimer = NULL; - - if (ui_but_contains_point_px(but, region, event->x, event->y) || but->active) { - button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); - } - } - - break; - } - /* XXX hardcoded keymap check... but anyway, - * while view changes, tool-tips should be removed */ - case WHEELUPMOUSE: - case WHEELDOWNMOUSE: - case MIDDLEMOUSE: - case MOUSEPAN: - UI_but_tooltip_timer_remove(C, but); - ATTR_FALLTHROUGH; - default: - break; - } - - /* handle button type specific events */ - retval = ui_do_button(C, block, but, event); - } - else if (data->state == BUTTON_STATE_WAIT_RELEASE) { - switch (event->type) { - case WINDEACTIVATE: - data->cancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - break; - - case TIMER: { - if (event->customdata == data->hold_action_timer) { - if (true) { - data->cancel = true; - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - else { - /* Do this so we can still mouse-up, closing the menu and running the button. - * This is nice to support but there are times when the button gets left pressed. - * Keep disabled for now. */ - WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); - data->hold_action_timer = NULL; - } - retval = WM_UI_HANDLER_CONTINUE; - but->hold_func(C, data->region, but); - } - break; - } - case MOUSEMOVE: { - /* deselect the button when moving the mouse away */ - /* also de-activate for buttons that only show highlights */ - if (ui_but_contains_point_px(but, region, event->x, event->y)) { - - /* Drag on a hold button (used in the toolbar) now opens it immediately. */ - if (data->hold_action_timer) { - if (but->flag & UI_SELECT) { - if (len_manhattan_v2v2_int(&event->x, &event->prevx) <= - WM_EVENT_CURSOR_MOTION_THRESHOLD) { - /* pass */ - } - else { - WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); - data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, 0.0f); - } - } - } - - if (!(but->flag & UI_SELECT)) { - but->flag |= (UI_SELECT | UI_ACTIVE); - data->cancel = false; - ED_region_tag_redraw_no_rebuild(data->region); - } - } - else { - if (but->flag & UI_SELECT) { - but->flag &= ~(UI_SELECT | UI_ACTIVE); - data->cancel = true; - ED_region_tag_redraw_no_rebuild(data->region); - } - } - break; - } - default: - /* otherwise catch mouse release event */ - ui_do_button(C, block, but, event); - break; - } - - retval = WM_UI_HANDLER_BREAK; - } - else if (data->state == BUTTON_STATE_WAIT_FLASH) { - switch (event->type) { - case TIMER: { - if (event->customdata == data->flashtimer) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - break; - } - } - - retval = WM_UI_HANDLER_CONTINUE; - } - else if (data->state == BUTTON_STATE_MENU_OPEN) { - /* check for exit because of mouse-over another button */ - switch (event->type) { - case MOUSEMOVE: { - uiBut *bt; - - if (data->menu && data->menu->region) { - if (ui_region_contains_point_px(data->menu->region, event->x, event->y)) { - break; - } - } - - bt = ui_but_find_mouse_over(region, event); - - if (bt && bt->active != data) { - if (but->type != UI_BTYPE_COLOR) { /* exception */ - data->cancel = true; - } - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - break; - } - case RIGHTMOUSE: { - if (event->val == KM_PRESS) { - uiBut *bt = ui_but_find_mouse_over(region, event); - if (bt && bt->active == data) { - button_activate_state(C, bt, BUTTON_STATE_HIGHLIGHT); - } - } - break; - } - } - - ui_do_button(C, block, but, event); - retval = WM_UI_HANDLER_CONTINUE; - } - else { - retval = ui_do_button(C, block, but, event); - // retval = WM_UI_HANDLER_BREAK; XXX why ? - } - - /* may have been re-allocated above (eyedropper for eg) */ - data = but->active; - if (data && data->state == BUTTON_STATE_EXIT) { - uiBut *post_but = data->postbut; - const uiButtonActivateType post_type = data->posttype; - - /* Reset the button value when empty text is typed. */ - if ((data->cancel == false) && (data->str != NULL) && (data->str[0] == '\0') && - (but->rnaprop && ELEM(RNA_property_type(but->rnaprop), PROP_FLOAT, PROP_INT))) { - MEM_SAFE_FREE(data->str); - ui_button_value_default(but, &data->value); - -#ifdef USE_DRAG_MULTINUM - if (data->multi_data.mbuts) { - for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) { - uiButMultiState *state = l->link; - uiBut *but_iter = state->but; - double default_value; - - if (ui_button_value_default(but_iter, &default_value)) { - ui_but_value_set(but_iter, default_value); - } - } - } - data->multi_data.skip = true; -#endif - } - - button_activate_exit(C, but, data, (post_but == NULL), false); - - /* for jumping to the next button with tab while text editing */ - if (post_but) { - /* The post_but still has previous ranges (without the changes in active button considered), - * needs refreshing the ranges. */ - ui_but_range_set_soft(post_but); - ui_but_range_set_hard(post_but); - - button_activate_init(C, region, post_but, post_type); - } - else if (!((event->type == EVT_BUT_CANCEL) && (event->val == 1))) { - /* XXX issue is because WM_event_add_mousemove(wm) is a bad hack and not reliable, - * if that gets coded better this bypass can go away too. - * - * This is needed to make sure if a button was active, - * it stays active while the mouse is over it. - * This avoids adding mousemoves, see: T33466. */ - if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT, BUTTON_STATE_WAIT_DRAG)) { - if (ui_but_find_mouse_over(region, event) == but) { - button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); - } - } - } - } - - return retval; -} - -static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox) -{ - int retval = WM_UI_HANDLER_CONTINUE; - int type = event->type, val = event->val; - int scroll_dir = 1; - bool redraw = false; - - uiList *ui_list = listbox->custom_data; - if (!ui_list || !ui_list->dyn_data) { - return retval; - } - uiListDyn *dyn_data = ui_list->dyn_data; - - int mx = event->x; - int my = event->y; - ui_window_to_block(region, listbox->block, &mx, &my); - - /* Convert pan to scroll-wheel. */ - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - - /* 'ui_pan_to_scroll' gives the absolute direction. */ - if (event->is_direction_inverted) { - scroll_dir = -1; - } - - /* If type still is mouse-pan, we call it handled, since delta-y accumulate. */ - /* also see wm_event_system.c do_wheel_ui hack */ - if (type == MOUSEPAN) { - retval = WM_UI_HANDLER_BREAK; - } - } - - if (val == KM_PRESS) { - if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) && - !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) || - ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl && - !IS_EVENT_MOD(event, shift, alt, oskey)))) { - const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop); - int value, min, max, inc; - - /* activate up/down the list */ - value = value_orig; - if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) { - inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? 1 : -1; - } - else { - inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? -1 : 1; - } - - if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) { - /* If we have a display order different from - * collection order, we have some work! */ - int *org_order = MEM_mallocN(dyn_data->items_shown * sizeof(int), __func__); - const int *new_order = dyn_data->items_filter_neworder; - int org_idx = -1, len = dyn_data->items_len; - int current_idx = -1; - const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE; - - for (int i = 0; i < len; i++) { - if (!dyn_data->items_filter_flags || - ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) { - org_order[new_order ? new_order[++org_idx] : ++org_idx] = i; - if (i == value) { - current_idx = new_order ? new_order[org_idx] : org_idx; - } - } - else if (i == value && org_idx >= 0) { - current_idx = -(new_order ? new_order[org_idx] : org_idx) - 1; - } - } - /* Now, org_order maps displayed indices to real indices, - * and current_idx either contains the displayed index of active value (positive), - * or its more-nearest one (negated). - */ - if (current_idx < 0) { - current_idx = (current_idx * -1) + (inc < 0 ? inc : inc - 1); - } - else { - current_idx += inc; - } - CLAMP(current_idx, 0, dyn_data->items_shown - 1); - value = org_order[current_idx]; - MEM_freeN(org_order); - } - else { - value += inc; - } - - CLAMP(value, 0, dyn_data->items_len - 1); - - RNA_property_int_range(&listbox->rnapoin, listbox->rnaprop, &min, &max); - CLAMP(value, min, max); - - if (value != value_orig) { - RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value); - RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop); - - ui_apply_but_undo(listbox); - - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; - redraw = true; - } - retval = WM_UI_HANDLER_BREAK; - } - else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->shift) { - /* We now have proper grip, but keep this anyway! */ - if (ui_list->list_grip < (dyn_data->visual_height_min - UI_LIST_AUTO_SIZE_THRESHOLD)) { - ui_list->list_grip = dyn_data->visual_height; - } - ui_list->list_grip += (type == WHEELUPMOUSE) ? -1 : 1; - - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; - - redraw = true; - retval = WM_UI_HANDLER_BREAK; - } - else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { - if (dyn_data->height > dyn_data->visual_height) { - /* list template will clamp */ - ui_list->list_scroll += scroll_dir * ((type == WHEELUPMOUSE) ? -1 : 1); - - redraw = true; - retval = WM_UI_HANDLER_BREAK; - } - } - } - - if (redraw) { - ED_region_tag_redraw(region); - ED_region_tag_refresh_ui(region); - } - - return retval; -} - -static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but) -{ - uiHandleButtonData *data = but->active; - uiPopupBlockHandle *menu = data->menu; - - /* copy over return values from the closing menu */ - if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_UPDATE)) { - if (but->type == UI_BTYPE_COLOR) { - copy_v3_v3(data->vec, menu->retvec); - } - else if (but->type == UI_BTYPE_MENU) { - data->value = menu->retvalue; - } - } - - if (menu->menuretval & UI_RETURN_UPDATE) { - if (data->interactive) { - ui_apply_but(C, but->block, but, data, true); - } - else { - ui_but_update(but); - } - - menu->menuretval = 0; - } - - /* now change button state or exit, which will close the submenu */ - if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_CANCEL)) { - if (menu->menuretval != UI_RETURN_OK) { - data->cancel = true; - } - - button_activate_exit(C, but, data, true, false); - } - else if (menu->menuretval & UI_RETURN_OUT) { - if (event->type == MOUSEMOVE && - ui_but_contains_point_px(but, data->region, event->x, event->y)) { - button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); - } - else { - if (ISKEYBOARD(event->type)) { - /* keyboard menu hierarchy navigation, going back to previous level */ - but->active->used_mouse = false; - button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); - } - else { - data->cancel = true; - button_activate_exit(C, but, data, true, false); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Menu Towards (mouse motion logic) - * \{ */ - -/** - * Function used to prevent losing the open menu when using nested pull-downs, - * when moving mouse towards the pull-down menu over other buttons that could - * steal the highlight from the current button, only checks: - * - * - while mouse moves in triangular area defined old mouse position and - * left/right side of new menu. - * - only for 1 second. - */ - -static void ui_mouse_motion_towards_init_ex(uiPopupBlockHandle *menu, - const int xy[2], - const bool force) -{ - BLI_assert(((uiBlock *)menu->region->uiblocks.first)->flag & - (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)); - - if (!menu->dotowards || force) { - menu->dotowards = true; - menu->towards_xy[0] = xy[0]; - menu->towards_xy[1] = xy[1]; - - if (force) { - menu->towardstime = DBL_MAX; /* unlimited time */ - } - else { - menu->towardstime = PIL_check_seconds_timer(); - } - } -} - -static void ui_mouse_motion_towards_init(uiPopupBlockHandle *menu, const int xy[2]) -{ - ui_mouse_motion_towards_init_ex(menu, xy, false); -} - -static void ui_mouse_motion_towards_reinit(uiPopupBlockHandle *menu, const int xy[2]) -{ - ui_mouse_motion_towards_init_ex(menu, xy, true); -} - -static bool ui_mouse_motion_towards_check(uiBlock *block, - uiPopupBlockHandle *menu, - const int xy[2], - const bool use_wiggle_room) -{ - BLI_assert(block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)); - - /* annoying fix for T36269, this is a bit odd but in fact works quite well - * don't mouse-out of a menu if another menu has been created after it. - * if this causes problems we could remove it and check on a different fix - campbell */ - if (menu->region->next) { - /* am I the last menu (test) */ - ARegion *region = menu->region->next; - do { - uiBlock *block_iter = region->uiblocks.first; - if (block_iter && ui_block_is_menu(block_iter)) { - return true; - } - } while ((region = region->next)); - } - /* annoying fix end! */ - - if (!menu->dotowards) { - return false; - } - - float oldp[2] = {menu->towards_xy[0], menu->towards_xy[1]}; - const float newp[2] = {xy[0], xy[1]}; - if (len_squared_v2v2(oldp, newp) < (4.0f * 4.0f)) { - return menu->dotowards; - } - - /* verify that we are moving towards one of the edges of the - * menu block, in other words, in the triangle formed by the - * initial mouse location and two edge points. */ - rctf rect_px; - ui_block_to_window_rctf(menu->region, block, &rect_px, &block->rect); - - const float margin = MENU_TOWARDS_MARGIN; - - const float p1[2] = {rect_px.xmin - margin, rect_px.ymin - margin}; - const float p2[2] = {rect_px.xmax + margin, rect_px.ymin - margin}; - const float p3[2] = {rect_px.xmax + margin, rect_px.ymax + margin}; - const float p4[2] = {rect_px.xmin - margin, rect_px.ymax + margin}; - - /* allow for some wiggle room, if the user moves a few pixels away, - * don't immediately quit (only for top level menus) */ - if (use_wiggle_room) { - const float cent[2] = {BLI_rctf_cent_x(&rect_px), BLI_rctf_cent_y(&rect_px)}; - float delta[2]; - - sub_v2_v2v2(delta, oldp, cent); - normalize_v2_length(delta, MENU_TOWARDS_WIGGLE_ROOM); - add_v2_v2(oldp, delta); - } - - bool closer = (isect_point_tri_v2(newp, oldp, p1, p2) || - isect_point_tri_v2(newp, oldp, p2, p3) || - isect_point_tri_v2(newp, oldp, p3, p4) || isect_point_tri_v2(newp, oldp, p4, p1)); - - if (!closer) { - menu->dotowards = false; - } - - /* 1 second timer */ - if (PIL_check_seconds_timer() - menu->towardstime > BUTTON_MOUSE_TOWARDS_THRESH) { - menu->dotowards = false; - } - - return menu->dotowards; -} - -#ifdef USE_KEYNAV_LIMIT -static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event) -{ - keynav->is_keynav = true; - copy_v2_v2_int(keynav->event_xy, &event->x); -} -/** - * Return true if key-input isn't blocking mouse-motion, - * or if the mouse-motion is enough to disable key-input. - */ -static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event) -{ - if (keynav->is_keynav && - (len_manhattan_v2v2_int(keynav->event_xy, &event->x) > BUTTON_KEYNAV_PX_LIMIT)) { - keynav->is_keynav = false; - } - - return keynav->is_keynav; -} -#endif /* USE_KEYNAV_LIMIT */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Menu Scroll - * \{ */ - -static char ui_menu_scroll_test(uiBlock *block, int my) -{ - if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) { - if (block->flag & UI_BLOCK_CLIPTOP) { - if (my > block->rect.ymax - UI_MENU_SCROLL_MOUSE) { - return 't'; - } - } - if (block->flag & UI_BLOCK_CLIPBOTTOM) { - if (my < block->rect.ymin + UI_MENU_SCROLL_MOUSE) { - return 'b'; - } - } - } - return 0; -} - -static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float dy) -{ - BLI_assert(dy != 0.0f); - - const int scroll_pad = ui_block_is_menu(block) ? UI_MENU_SCROLL_PAD : UI_UNIT_Y * 0.5f; - - if (dy < 0.0f) { - /* Stop at top item, extra 0.5 UI_UNIT_Y makes it snap nicer. */ - float ymax = -FLT_MAX; - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - ymax = max_ff(ymax, bt->rect.ymax); - } - if (ymax + dy - UI_UNIT_Y * 0.5f < block->rect.ymax - scroll_pad) { - dy = block->rect.ymax - ymax - scroll_pad; - } - } - else { - /* Stop at bottom item, extra 0.5 UI_UNIT_Y makes it snap nicer. */ - float ymin = FLT_MAX; - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - ymin = min_ff(ymin, bt->rect.ymin); - } - if (ymin + dy + UI_UNIT_Y * 0.5f > block->rect.ymin + scroll_pad) { - dy = block->rect.ymin - ymin + scroll_pad; - } - } - - /* remember scroll offset for refreshes */ - block->handle->scrolloffset += dy; - - /* apply scroll offset */ - LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { - bt->rect.ymin += dy; - bt->rect.ymax += dy; - } - - /* set flags again */ - ui_popup_block_scrolltest(block); - - ED_region_tag_redraw(region); -} - -/** Scroll to activated button. */ -static bool ui_menu_scroll_to_but(ARegion *region, uiBlock *block, uiBut *but_target) -{ - float dy = 0.0; - if (block->flag & UI_BLOCK_CLIPTOP) { - if (but_target->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) { - dy = block->rect.ymax - but_target->rect.ymax - UI_MENU_SCROLL_ARROW; - } - } - if (block->flag & UI_BLOCK_CLIPBOTTOM) { - if (but_target->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) { - dy = block->rect.ymin - but_target->rect.ymin + UI_MENU_SCROLL_ARROW; - } - } - if (dy != 0.0f) { - ui_menu_scroll_apply_offset_y(region, block, dy); - return true; - } - return false; -} - -/** Scroll to y location (in block space, see #ui_window_to_block). */ -static bool ui_menu_scroll_to_y(ARegion *region, uiBlock *block, int y) -{ - const char test = ui_menu_scroll_test(block, y); - float dy = 0.0f; - if (test == 't') { - dy = -UI_UNIT_Y; /* scroll to the top */ - } - else if (test == 'b') { - dy = UI_UNIT_Y; /* scroll to the bottom */ - } - if (dy != 0.0f) { - ui_menu_scroll_apply_offset_y(region, block, dy); - return true; - } - return false; -} - -static bool ui_menu_scroll_step(ARegion *region, uiBlock *block, const int scroll_dir) -{ - int my; - if (scroll_dir == 1) { - if ((block->flag & UI_BLOCK_CLIPTOP) == 0) { - return false; - } - my = block->rect.ymax + UI_UNIT_Y; - } - else if (scroll_dir == -1) { - if ((block->flag & UI_BLOCK_CLIPBOTTOM) == 0) { - return false; - } - my = block->rect.ymin - UI_UNIT_Y; - } - else { - BLI_assert(0); - return false; - } - - return ui_menu_scroll_to_y(region, block, my); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Menu Event Handling - * \{ */ - -static void ui_region_auto_open_clear(ARegion *region) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - block->auto_open = false; - } -} - -/** - * Special function to handle nested menus. - * let the parent menu get the event. - * - * This allows a menu to be open, - * but send key events to the parent if there's no active buttons. - * - * Without this keyboard navigation from menu's wont work. - */ -static bool ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle *menu, - const uiBut *but, - const int level, - const int retval) -{ - if ((level != 0) && (but == NULL)) { - menu->menuretval = UI_RETURN_OUT | UI_RETURN_OUT_PARENT; - (void)retval; /* so release builds with strict flags are happy as well */ - BLI_assert(retval == WM_UI_HANDLER_CONTINUE); - return true; - } - return false; -} - -static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu) -{ - ARegion *region = menu->region; - uiBut *but = ui_region_find_active_but(region); - - if (but) { - /* Its possible there is an active menu item NOT under the mouse, - * in this case ignore mouse clicks outside the button (but Enter etc is accepted) */ - if (event->val == KM_RELEASE) { - /* pass, needed so we can exit active menu-items when click-dragging out of them */ - } - else if (but->type == UI_BTYPE_SEARCH_MENU) { - /* Pass, needed so search popup can have RMB context menu. - * This may be useful for other interactions which happen in the search popup - * without being directly over the search button. */ - } - else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) { - /* pass, skip for dialogs */ - } - else if (!ui_region_contains_point_px(but->active->region, event->x, event->y)) { - /* Pass, needed to click-exit outside of non-floating menus. */ - ui_region_auto_open_clear(but->active->region); - } - else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) && - ISMOUSE(event->type)) { - if (!ui_but_contains_point_px(but, but->active->region, event->x, event->y)) { - but = NULL; - } - } - } - - int retval; - if (but) { - ScrArea *ctx_area = CTX_wm_area(C); - ARegion *ctx_region = CTX_wm_region(C); - - if (menu->ctx_area) { - CTX_wm_area_set(C, menu->ctx_area); - } - if (menu->ctx_region) { - CTX_wm_region_set(C, menu->ctx_region); - } - - retval = ui_handle_button_event(C, event, but); - - if (menu->ctx_area) { - CTX_wm_area_set(C, ctx_area); - } - if (menu->ctx_region) { - CTX_wm_region_set(C, ctx_region); - } - } - else { - retval = ui_handle_button_over(C, event, region); - } - - return retval; -} - -float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2]) -{ - float seg1[2]; - - if (block->pie_data.flags & UI_PIE_INITIAL_DIRECTION) { - copy_v2_v2(seg1, block->pie_data.pie_center_init); - } - else { - copy_v2_v2(seg1, block->pie_data.pie_center_spawned); - } - - float seg2[2]; - sub_v2_v2v2(seg2, event_xy, seg1); - - const float len = normalize_v2_v2(block->pie_data.pie_dir, seg2); - - if (len < U.pie_menu_threshold * U.dpi_fac) { - block->pie_data.flags |= UI_PIE_INVALID_DIR; - } - else { - block->pie_data.flags &= ~UI_PIE_INVALID_DIR; - } - - return len; -} - -static int ui_handle_menu_event(bContext *C, - const wmEvent *event, - uiPopupBlockHandle *menu, - int level, - const bool is_parent_inside, - const bool is_parent_menu, - const bool is_floating) -{ - uiBut *but; - ARegion *region = menu->region; - uiBlock *block = region->uiblocks.first; - - int retval = WM_UI_HANDLER_CONTINUE; - - int mx = event->x; - int my = event->y; - ui_window_to_block(region, block, &mx, &my); - - /* check if mouse is inside block */ - const bool inside = BLI_rctf_isect_pt(&block->rect, mx, my); - /* check for title dragging */ - const bool inside_title = inside && ((my + (UI_UNIT_Y * 1.5f)) > block->rect.ymax); - - /* if there's an active modal button, don't check events or outside, except for search menu */ - but = ui_region_find_active_but(region); - -#ifdef USE_DRAG_POPUP - if (menu->is_grab) { - if (event->type == LEFTMOUSE) { - menu->is_grab = false; - retval = WM_UI_HANDLER_BREAK; - } - else { - if (event->type == MOUSEMOVE) { - int mdiff[2]; - - sub_v2_v2v2_int(mdiff, &event->x, menu->grab_xy_prev); - copy_v2_v2_int(menu->grab_xy_prev, &event->x); - - add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff); - - ui_popup_translate(region, mdiff); - } - - return retval; - } - } -#endif - - if (but && button_modal_state(but->active->state)) { - if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { - /* if a button is activated modal, always reset the start mouse - * position of the towards mechanism to avoid losing focus, - * and don't handle events */ - ui_mouse_motion_towards_reinit(menu, &event->x); - } - } - else if (event->type == TIMER) { - if (event->customdata == menu->scrolltimer) { - ui_menu_scroll_to_y(region, block, my); - } - } - else { - /* for ui_mouse_motion_towards_block */ - if (event->type == MOUSEMOVE) { - if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { - ui_mouse_motion_towards_init(menu, &event->x); - } - - /* add menu scroll timer, if needed */ - if (ui_menu_scroll_test(block, my)) { - if (menu->scrolltimer == NULL) { - menu->scrolltimer = WM_event_add_timer( - CTX_wm_manager(C), CTX_wm_window(C), TIMER, MENU_SCROLL_INTERVAL); - } - } - } - - /* first block own event func */ - if (block->block_event_func && block->block_event_func(C, block, event)) { - /* pass */ - } /* events not for active search menu button */ - else { - int act = 0; - - switch (event->type) { - - /* Closing sub-levels of pull-downs. - * - * The actual event is handled by the button under the cursor. - * This is done so we can right click on menu items even when they have sub-menus open. - */ - case RIGHTMOUSE: - if (inside == false) { - if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { - if (block->saferct.first) { - /* Currently right clicking on a top level pull-down (typically in the header) - * just closes the menu and doesn't support immediately handling the RMB event. - * - * To support we would need UI_RETURN_OUT_PARENT to be handled by - * top-level buttons, not just menus. Note that this isn't very important - * since it's easy to manually close these menus by clicking on them. */ - menu->menuretval = (level > 0 && is_parent_inside) ? UI_RETURN_OUT_PARENT : - UI_RETURN_OUT; - } - } - retval = WM_UI_HANDLER_BREAK; - } - break; - - /* Closing sub-levels of pull-downs. */ - case EVT_LEFTARROWKEY: - if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { - if (block->saferct.first) { - menu->menuretval = UI_RETURN_OUT; - } - } - - retval = WM_UI_HANDLER_BREAK; - break; - - /* Opening sub-levels of pull-downs. */ - case EVT_RIGHTARROWKEY: - if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { - - if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { - break; - } - - but = ui_region_find_active_but(region); - - if (!but) { - /* no item active, we make first active */ - if (block->direction & UI_DIR_UP) { - but = ui_but_last(block); - } - else { - but = ui_but_first(block); - } - } - - if (but && ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) { - ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN); - } - } - - retval = WM_UI_HANDLER_BREAK; - break; - - /* Smooth scrolling for popovers. */ - case MOUSEPAN: { - if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { - /* pass */ - } - else if (!ui_block_is_menu(block)) { - if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) { - const float dy = event->y - event->prevy; - if (dy != 0.0f) { - ui_menu_scroll_apply_offset_y(region, block, dy); - - if (but) { - but->active->cancel = true; - button_activate_exit(C, but, but->active, false, false); - } - WM_event_add_mousemove(CTX_wm_window(C)); - } - } - break; - } - ATTR_FALLTHROUGH; - } - case WHEELUPMOUSE: - case WHEELDOWNMOUSE: { - if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { - /* pass */ - } - else if (!ui_block_is_menu(block)) { - const int scroll_dir = (event->type == WHEELUPMOUSE) ? 1 : -1; - if (ui_menu_scroll_step(region, block, scroll_dir)) { - if (but) { - but->active->cancel = true; - button_activate_exit(C, but, but->active, false, false); - } - WM_event_add_mousemove(CTX_wm_window(C)); - } - break; - } - ATTR_FALLTHROUGH; - } - case EVT_UPARROWKEY: - case EVT_DOWNARROWKEY: - case EVT_PAGEUPKEY: - case EVT_PAGEDOWNKEY: - case EVT_HOMEKEY: - case EVT_ENDKEY: - /* Arrow-keys: only handle for block_loop blocks. */ - if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { - /* pass */ - } - else if (inside || (block->flag & UI_BLOCK_LOOP)) { - int type = event->type; - int val = event->val; - - /* Convert pan to scroll-wheel. */ - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - } - - if (val == KM_PRESS) { - /* Determine scroll operation. */ - uiMenuScrollType scrolltype; - const bool ui_block_flipped = (block->flag & UI_BLOCK_IS_FLIP) != 0; - - if (ELEM(type, EVT_PAGEUPKEY, EVT_HOMEKEY)) { - scrolltype = ui_block_flipped ? MENU_SCROLL_TOP : MENU_SCROLL_BOTTOM; - } - else if (ELEM(type, EVT_PAGEDOWNKEY, EVT_ENDKEY)) { - scrolltype = ui_block_flipped ? MENU_SCROLL_BOTTOM : MENU_SCROLL_TOP; - } - else if (ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE)) { - scrolltype = ui_block_flipped ? MENU_SCROLL_UP : MENU_SCROLL_DOWN; - } - else { - scrolltype = ui_block_flipped ? MENU_SCROLL_DOWN : MENU_SCROLL_UP; - } - - if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { - break; - } - -#ifdef USE_KEYNAV_LIMIT - ui_mouse_motion_keynav_init(&menu->keynav_state, event); -#endif - - but = ui_region_find_active_but(region); - if (but) { - /* Apply scroll operation. */ - if (scrolltype == MENU_SCROLL_DOWN) { - but = ui_but_next(but); - } - else if (scrolltype == MENU_SCROLL_UP) { - but = ui_but_prev(but); - } - else if (scrolltype == MENU_SCROLL_TOP) { - but = ui_but_first(block); - } - else if (scrolltype == MENU_SCROLL_BOTTOM) { - but = ui_but_last(block); - } - } - - if (!but) { - /* wrap button or no active button*/ - uiBut *but_wrap = NULL; - if (ELEM(scrolltype, MENU_SCROLL_UP, MENU_SCROLL_BOTTOM)) { - but_wrap = ui_but_last(block); - } - else if (ELEM(scrolltype, MENU_SCROLL_DOWN, MENU_SCROLL_TOP)) { - but_wrap = ui_but_first(block); - } - if (but_wrap) { - but = but_wrap; - } - } - - if (but) { - ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE); - ui_menu_scroll_to_but(region, block, but); - } - } - - retval = WM_UI_HANDLER_BREAK; - } - - break; - - case EVT_ONEKEY: - case EVT_PAD1: - act = 1; - ATTR_FALLTHROUGH; - case EVT_TWOKEY: - case EVT_PAD2: - if (act == 0) { - act = 2; - } - ATTR_FALLTHROUGH; - case EVT_THREEKEY: - case EVT_PAD3: - if (act == 0) { - act = 3; - } - ATTR_FALLTHROUGH; - case EVT_FOURKEY: - case EVT_PAD4: - if (act == 0) { - act = 4; - } - ATTR_FALLTHROUGH; - case EVT_FIVEKEY: - case EVT_PAD5: - if (act == 0) { - act = 5; - } - ATTR_FALLTHROUGH; - case EVT_SIXKEY: - case EVT_PAD6: - if (act == 0) { - act = 6; - } - ATTR_FALLTHROUGH; - case EVT_SEVENKEY: - case EVT_PAD7: - if (act == 0) { - act = 7; - } - ATTR_FALLTHROUGH; - case EVT_EIGHTKEY: - case EVT_PAD8: - if (act == 0) { - act = 8; - } - ATTR_FALLTHROUGH; - case EVT_NINEKEY: - case EVT_PAD9: - if (act == 0) { - act = 9; - } - ATTR_FALLTHROUGH; - case EVT_ZEROKEY: - case EVT_PAD0: - if (act == 0) { - act = 10; - } - - if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) { - int count; - - if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { - break; - } - - /* Only respond to explicit press to avoid the event that opened the menu - * activating an item when the key is held. */ - if (event->is_repeat) { - break; - } - - if (event->alt) { - act += 10; - } - - count = 0; - for (but = block->buttons.first; but; but = but->next) { - bool doit = false; - - if (!ELEM(but->type, - UI_BTYPE_LABEL, - UI_BTYPE_SEPR, - UI_BTYPE_SEPR_LINE, - UI_BTYPE_IMAGE)) { - count++; - } - - /* exception for rna layer buts */ - if (but->rnapoin.data && but->rnaprop && - ELEM(RNA_property_subtype(but->rnaprop), PROP_LAYER, PROP_LAYER_MEMBER)) { - if (but->rnaindex == act - 1) { - doit = true; - } - } - else if (ELEM(but->type, - UI_BTYPE_BUT, - UI_BTYPE_BUT_MENU, - UI_BTYPE_MENU, - UI_BTYPE_BLOCK, - UI_BTYPE_PULLDOWN) && - count == act) { - doit = true; - } - - if (!(but->flag & UI_BUT_DISABLED) && doit) { - /* activate buttons but open menu's */ - uiButtonActivateType activate; - if (but->type == UI_BTYPE_PULLDOWN) { - activate = BUTTON_ACTIVATE_OPEN; - } - else { - activate = BUTTON_ACTIVATE_APPLY; - } - - ui_handle_button_activate(C, region, but, activate); - break; - } - } - - retval = WM_UI_HANDLER_BREAK; - } - break; - - /* Handle keystrokes on menu items */ - case EVT_AKEY: - case EVT_BKEY: - case EVT_CKEY: - case EVT_DKEY: - case EVT_EKEY: - case EVT_FKEY: - case EVT_GKEY: - case EVT_HKEY: - case EVT_IKEY: - case EVT_JKEY: - case EVT_KKEY: - case EVT_LKEY: - case EVT_MKEY: - case EVT_NKEY: - case EVT_OKEY: - case EVT_PKEY: - case EVT_QKEY: - case EVT_RKEY: - case EVT_SKEY: - case EVT_TKEY: - case EVT_UKEY: - case EVT_VKEY: - case EVT_WKEY: - case EVT_XKEY: - case EVT_YKEY: - case EVT_ZKEY: { - if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) && - !IS_EVENT_MOD(event, shift, ctrl, oskey) && - /* Only respond to explicit press to avoid the event that opened the menu - * activating an item when the key is held. */ - !event->is_repeat) { - if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { - break; - } - - for (but = block->buttons.first; but; but = but->next) { - if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) { - if (but->type == UI_BTYPE_BUT) { - UI_but_execute(C, region, but); - } - else { - ui_handle_button_activate_by_type(C, region, but); - } - break; - } - } - - retval = WM_UI_HANDLER_BREAK; - } - break; - } - } - } - - /* here we check return conditions for menus */ - if (block->flag & UI_BLOCK_LOOP) { - /* If we click outside the block, verify if we clicked on the - * button that opened us, otherwise we need to close, - * - * note that there is an exception for root level menus and - * popups which you can click again to close. - * - * Events handled above may have already set the return value, - * don't overwrite them, see: T61015. - */ - if ((inside == false) && (menu->menuretval == 0)) { - uiSafetyRct *saferct = block->saferct.first; - - if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) { - if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { - if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) { - /* for root menus, allow clicking to close */ - if (block->flag & UI_BLOCK_OUT_1) { - menu->menuretval = UI_RETURN_OK; - } - else { - menu->menuretval = UI_RETURN_OUT; - } - } - else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) { - if (block->flag & UI_BLOCK_OUT_1) { - menu->menuretval = UI_RETURN_OK; - } - else { - menu->menuretval = UI_RETURN_OUT; - } - } - } - else if (ELEM(event->val, KM_RELEASE, KM_CLICK)) { - /* For buttons that use a hold function, - * exit when mouse-up outside the menu. */ - if (block->flag & UI_BLOCK_POPUP_HOLD) { - /* Note, we could check the cursor is over the parent button. */ - menu->menuretval = UI_RETURN_CANCEL; - retval = WM_UI_HANDLER_CONTINUE; - } - } - } - } - - if (menu->menuretval) { - /* pass */ - } -#ifdef USE_KEYNAV_LIMIT - else if ((event->type == MOUSEMOVE) && - ui_mouse_motion_keynav_test(&menu->keynav_state, event)) { - /* Don't handle the mouse-move if we're using key-navigation. */ - retval = WM_UI_HANDLER_BREAK; - } -#endif - else if (event->type == EVT_ESCKEY && event->val == KM_PRESS) { - /* Escape cancels this and all preceding menus. */ - menu->menuretval = UI_RETURN_CANCEL; - } - else if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER) && event->val == KM_PRESS) { - uiBut *but_default = ui_region_find_first_but_test_flag( - region, UI_BUT_ACTIVE_DEFAULT, UI_HIDDEN); - if ((but_default != NULL) && (but_default->active == NULL)) { - if (but_default->type == UI_BTYPE_BUT) { - UI_but_execute(C, region, but_default); - } - else { - ui_handle_button_activate_by_type(C, region, but_default); - } - } - else { - uiBut *but_active = ui_region_find_active_but(region); - - /* enter will always close this block, we let the event - * get handled by the button if it is activated, otherwise we cancel */ - if (but_active == NULL) { - menu->menuretval = UI_RETURN_CANCEL | UI_RETURN_POPUP_OK; - } - } - } -#ifdef USE_DRAG_POPUP - else if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && - (inside && is_floating && inside_title)) { - if (!but || !ui_but_contains_point_px(but, region, event->x, event->y)) { - if (but) { - UI_but_tooltip_timer_remove(C, but); - } - - menu->is_grab = true; - copy_v2_v2_int(menu->grab_xy_prev, &event->x); - retval = WM_UI_HANDLER_BREAK; - } - } -#endif - else { - - /* check mouse moving outside of the menu */ - if (inside == false && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) { - uiSafetyRct *saferct; - - ui_mouse_motion_towards_check(block, menu, &event->x, is_parent_inside == false); - - /* Check for all parent rects, enables arrow-keys to be used. */ - for (saferct = block->saferct.first; saferct; saferct = saferct->next) { - /* for mouse move we only check our own rect, for other - * events we check all preceding block rects too to make - * arrow keys navigation work */ - if (event->type != MOUSEMOVE || saferct == block->saferct.first) { - if (BLI_rctf_isect_pt(&saferct->parent, (float)event->x, (float)event->y)) { - break; - } - if (BLI_rctf_isect_pt(&saferct->safety, (float)event->x, (float)event->y)) { - break; - } - } - } - - /* strict check, and include the parent rect */ - if (!menu->dotowards && !saferct) { - if (block->flag & UI_BLOCK_OUT_1) { - menu->menuretval = UI_RETURN_OK; - } - else { - menu->menuretval = UI_RETURN_OUT; - } - } - else if (menu->dotowards && event->type == MOUSEMOVE) { - retval = WM_UI_HANDLER_BREAK; - } - } - } - - /* end switch */ - } - } - - /* if we are didn't handle the event yet, lets pass it on to - * buttons inside this region. disabled inside check .. not sure - * anymore why it was there? but it meant enter didn't work - * for example when mouse was not over submenu */ - if ((event->type == TIMER) || - (/*inside &&*/ (!menu->menuretval || (menu->menuretval & UI_RETURN_UPDATE)) && - retval == WM_UI_HANDLER_CONTINUE)) { - retval = ui_handle_menu_button(C, event, menu); - } - -#ifdef USE_UI_POPOVER_ONCE - if (block->flag & UI_BLOCK_POPOVER_ONCE) { - if ((event->type == LEFTMOUSE) && (event->val == KM_RELEASE)) { - UI_popover_once_clear(menu->popup_create_vars.arg); - block->flag &= ~UI_BLOCK_POPOVER_ONCE; - } - } -#endif - - /* Don't handle double click events, rehandle as regular press/release. */ - if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) { - return retval; - } - - /* if we set a menu return value, ensure we continue passing this on to - * lower menus and buttons, so always set continue then, and if we are - * inside the region otherwise, ensure we swallow the event */ - if (menu->menuretval) { - return WM_UI_HANDLER_CONTINUE; - } - if (inside) { - return WM_UI_HANDLER_BREAK; - } - return retval; -} - -static int ui_handle_menu_return_submenu(bContext *C, - const wmEvent *event, - uiPopupBlockHandle *menu) -{ - ARegion *region = menu->region; - uiBlock *block = region->uiblocks.first; - - uiBut *but = ui_region_find_active_but(region); - - BLI_assert(but); - - uiHandleButtonData *data = but->active; - uiPopupBlockHandle *submenu = data->menu; - - if (submenu->menuretval) { - bool update; - - /* first decide if we want to close our own menu cascading, if - * so pass on the sub menu return value to our own menu handle */ - if ((submenu->menuretval & UI_RETURN_OK) || (submenu->menuretval & UI_RETURN_CANCEL)) { - if (!(block->flag & UI_BLOCK_KEEP_OPEN)) { - menu->menuretval = submenu->menuretval; - menu->butretval = data->retval; - } - } - - update = (submenu->menuretval & UI_RETURN_UPDATE) != 0; - - /* now let activated button in this menu exit, which - * will actually close the submenu too */ - ui_handle_button_return_submenu(C, event, but); - - if (update) { - submenu->menuretval = 0; - } - } - - if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { - /* for cases where close does not cascade, allow the user to - * move the mouse back towards the menu without closing */ - ui_mouse_motion_towards_reinit(menu, &event->x); - } - - if (menu->menuretval) { - return WM_UI_HANDLER_CONTINUE; - } - return WM_UI_HANDLER_BREAK; -} - -static bool ui_but_pie_menu_supported_apply(uiBut *but) -{ - return (!ELEM(but->type, UI_BTYPE_NUM_SLIDER, UI_BTYPE_NUM)); -} - -static int ui_but_pie_menu_apply(bContext *C, - uiPopupBlockHandle *menu, - uiBut *but, - bool force_close) -{ - const int retval = WM_UI_HANDLER_BREAK; - - if (but && ui_but_pie_menu_supported_apply(but)) { - if (but->type == UI_BTYPE_MENU) { - /* forcing the pie menu to close will not handle menus */ - if (!force_close) { - uiBut *active_but = ui_region_find_active_but(menu->region); - - if (active_but) { - button_activate_exit(C, active_but, active_but->active, false, false); - } - - button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OPEN); - return retval; - } - menu->menuretval = UI_RETURN_CANCEL; - } - else { - button_activate_exit((bContext *)C, but, but->active, false, false); - - menu->menuretval = UI_RETURN_OK; - } - } - else { - menu->menuretval = UI_RETURN_CANCEL; - - ED_region_tag_redraw(menu->region); - } - - return retval; -} - -static uiBut *ui_block_pie_dir_activate(uiBlock *block, const wmEvent *event, RadialDirection dir) -{ - if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->pie_dir == dir && !ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) { - return but; - } - } - } - - return NULL; -} - -static int ui_but_pie_button_activate(bContext *C, uiBut *but, uiPopupBlockHandle *menu) -{ - if (but == NULL) { - return WM_UI_HANDLER_BREAK; - } - - uiBut *active_but = ui_region_find_active_but(menu->region); - - if (active_but) { - button_activate_exit(C, active_but, active_but->active, false, false); - } - - button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OVER); - return ui_but_pie_menu_apply(C, menu, but, false); -} - -static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu) -{ - /* we block all events, this is modal interaction, - * except for drop events which is described below */ - int retval = WM_UI_HANDLER_BREAK; - - if (event->type == EVT_DROP) { - /* may want to leave this here for later if we support pie ovens */ - - retval = WM_UI_HANDLER_CONTINUE; - } - - ARegion *region = menu->region; - uiBlock *block = region->uiblocks.first; - - const bool is_click_style = (block->pie_data.flags & UI_PIE_CLICK_STYLE); - - /* if there's an active modal button, don't check events or outside, except for search menu */ - uiBut *but_active = ui_region_find_active_but(region); - - if (menu->scrolltimer == NULL) { - menu->scrolltimer = WM_event_add_timer( - CTX_wm_manager(C), CTX_wm_window(C), TIMER, PIE_MENU_INTERVAL); - menu->scrolltimer->duration = 0.0; - } - - const double duration = menu->scrolltimer->duration; - - float event_xy[2] = {event->x, event->y}; - - ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]); - - /* Distance from initial point. */ - const float dist = ui_block_calc_pie_segment(block, event_xy); - - if (but_active && button_modal_state(but_active->active->state)) { - retval = ui_handle_menu_button(C, event, menu); - } - else { - if (event->type == TIMER) { - if (event->customdata == menu->scrolltimer) { - /* deactivate initial direction after a while */ - if (duration > 0.01 * U.pie_initial_timeout) { - block->pie_data.flags &= ~UI_PIE_INITIAL_DIRECTION; - } - - /* handle animation */ - if (!(block->pie_data.flags & UI_PIE_ANIMATION_FINISHED)) { - const double final_time = 0.01 * U.pie_animation_timeout; - float fac = duration / final_time; - const float pie_radius = U.pie_menu_radius * UI_DPI_FAC; - - if (fac > 1.0f) { - fac = 1.0f; - block->pie_data.flags |= UI_PIE_ANIMATION_FINISHED; - } - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->pie_dir != UI_RADIAL_NONE) { - float vec[2]; - float center[2]; - - ui_but_pie_dir(but->pie_dir, vec); - - center[0] = (vec[0] > 0.01f) ? 0.5f : ((vec[0] < -0.01f) ? -0.5f : 0.0f); - center[1] = (vec[1] > 0.99f) ? 0.5f : ((vec[1] < -0.99f) ? -0.5f : 0.0f); - - center[0] *= BLI_rctf_size_x(&but->rect); - center[1] *= BLI_rctf_size_y(&but->rect); - - mul_v2_fl(vec, pie_radius); - add_v2_v2(vec, center); - mul_v2_fl(vec, fac); - add_v2_v2(vec, block->pie_data.pie_center_spawned); - - BLI_rctf_recenter(&but->rect, vec[0], vec[1]); - } - } - block->pie_data.alphafac = fac; - - ED_region_tag_redraw(region); - } - } - - /* Check pie velocity here if gesture has ended. */ - if (block->pie_data.flags & UI_PIE_GESTURE_END_WAIT) { - float len_sq = 10; - - /* use a time threshold to ensure we leave time to the mouse to move */ - if (duration - block->pie_data.duration_gesture > 0.02) { - len_sq = len_squared_v2v2(event_xy, block->pie_data.last_pos); - copy_v2_v2(block->pie_data.last_pos, event_xy); - block->pie_data.duration_gesture = duration; - } - - if (len_sq < 1.0f) { - uiBut *but = ui_region_find_active_but(menu->region); - - if (but) { - return ui_but_pie_menu_apply(C, menu, but, true); - } - } - } - } - - if (event->type == block->pie_data.event_type && !is_click_style) { - if (event->val != KM_RELEASE) { - ui_handle_menu_button(C, event, menu); - - if (len_squared_v2v2(event_xy, block->pie_data.pie_center_init) > PIE_CLICK_THRESHOLD_SQ) { - block->pie_data.flags |= UI_PIE_DRAG_STYLE; - } - /* why redraw here? It's simple, we are getting many double click events here. - * Those operate like mouse move events almost */ - ED_region_tag_redraw(region); - } - else { - if ((duration < 0.01 * U.pie_tap_timeout) && - !(block->pie_data.flags & UI_PIE_DRAG_STYLE)) { - block->pie_data.flags |= UI_PIE_CLICK_STYLE; - } - else { - uiBut *but = ui_region_find_active_but(menu->region); - - if (but && (U.pie_menu_confirm > 0) && - (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) { - return ui_but_pie_menu_apply(C, menu, but, true); - } - - retval = ui_but_pie_menu_apply(C, menu, but, true); - } - } - } - else { - /* direction from numpad */ - RadialDirection num_dir = UI_RADIAL_NONE; - - switch (event->type) { - case MOUSEMOVE: - if (!is_click_style) { - const float len_sq = len_squared_v2v2(event_xy, block->pie_data.pie_center_init); - - /* here we use the initial position explicitly */ - if (len_sq > PIE_CLICK_THRESHOLD_SQ) { - block->pie_data.flags |= UI_PIE_DRAG_STYLE; - } - - /* here instead, we use the offset location to account for the initial - * direction timeout */ - if ((U.pie_menu_confirm > 0) && - (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) { - block->pie_data.flags |= UI_PIE_GESTURE_END_WAIT; - copy_v2_v2(block->pie_data.last_pos, event_xy); - block->pie_data.duration_gesture = duration; - } - } - - ui_handle_menu_button(C, event, menu); - - /* mouse move should always refresh the area for pie menus */ - ED_region_tag_redraw(region); - break; - - case LEFTMOUSE: - if (is_click_style) { - if (block->pie_data.flags & UI_PIE_INVALID_DIR) { - menu->menuretval = UI_RETURN_CANCEL; - } - else { - retval = ui_handle_menu_button(C, event, menu); - } - } - break; - - case EVT_ESCKEY: - case RIGHTMOUSE: - menu->menuretval = UI_RETURN_CANCEL; - break; - - case EVT_AKEY: - case EVT_BKEY: - case EVT_CKEY: - case EVT_DKEY: - case EVT_EKEY: - case EVT_FKEY: - case EVT_GKEY: - case EVT_HKEY: - case EVT_IKEY: - case EVT_JKEY: - case EVT_KKEY: - case EVT_LKEY: - case EVT_MKEY: - case EVT_NKEY: - case EVT_OKEY: - case EVT_PKEY: - case EVT_QKEY: - case EVT_RKEY: - case EVT_SKEY: - case EVT_TKEY: - case EVT_UKEY: - case EVT_VKEY: - case EVT_WKEY: - case EVT_XKEY: - case EVT_YKEY: - case EVT_ZKEY: { - if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) && - !IS_EVENT_MOD(event, shift, ctrl, oskey)) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->menu_key == event->type) { - ui_but_pie_button_activate(C, but, menu); - } - } - } - break; - } - -#define CASE_NUM_TO_DIR(n, d) \ - case (EVT_ZEROKEY + n): \ - case (EVT_PAD0 + n): { \ - if (num_dir == UI_RADIAL_NONE) { \ - num_dir = d; \ - } \ - } \ - (void)0 - - CASE_NUM_TO_DIR(1, UI_RADIAL_SW); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(2, UI_RADIAL_S); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(3, UI_RADIAL_SE); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(4, UI_RADIAL_W); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(6, UI_RADIAL_E); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(7, UI_RADIAL_NW); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(8, UI_RADIAL_N); - ATTR_FALLTHROUGH; - CASE_NUM_TO_DIR(9, UI_RADIAL_NE); - { - uiBut *but = ui_block_pie_dir_activate(block, event, num_dir); - retval = ui_but_pie_button_activate(C, but, menu); - break; - } -#undef CASE_NUM_TO_DIR - default: - retval = ui_handle_menu_button(C, event, menu); - break; - } - } - } - - return retval; -} - -static int ui_handle_menus_recursive(bContext *C, - const wmEvent *event, - uiPopupBlockHandle *menu, - int level, - const bool is_parent_inside, - const bool is_parent_menu, - const bool is_floating) -{ - int retval = WM_UI_HANDLER_CONTINUE; - bool do_towards_reinit = false; - - /* check if we have a submenu, and handle events for it first */ - uiBut *but = ui_region_find_active_but(menu->region); - uiHandleButtonData *data = (but) ? but->active : NULL; - uiPopupBlockHandle *submenu = (data) ? data->menu : NULL; - - if (submenu) { - uiBlock *block = menu->region->uiblocks.first; - const bool is_menu = ui_block_is_menu(block); - bool inside = false; - /* root pie menus accept the key that spawned - * them as double click to improve responsiveness */ - const bool do_recursion = (!(block->flag & UI_BLOCK_RADIAL) || - event->type != block->pie_data.event_type); - - if (do_recursion) { - if (is_parent_inside == false) { - int mx = event->x; - int my = event->y; - ui_window_to_block(menu->region, block, &mx, &my); - inside = BLI_rctf_isect_pt(&block->rect, mx, my); - } - - retval = ui_handle_menus_recursive( - C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false); - } - } - - /* now handle events for our own menu */ - if (retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) { - const bool do_but_search = (but && (but->type == UI_BTYPE_SEARCH_MENU)); - if (submenu && submenu->menuretval) { - const bool do_ret_out_parent = (submenu->menuretval & UI_RETURN_OUT_PARENT) != 0; - retval = ui_handle_menu_return_submenu(C, event, menu); - submenu = NULL; /* hint not to use this, it may be freed by call above */ - (void)submenu; - /* we may want to quit the submenu and handle the even in this menu, - * if its important to use it, check 'data->menu' first */ - if (((retval == WM_UI_HANDLER_BREAK) && do_ret_out_parent) == false) { - /* skip applying the event */ - return retval; - } - } - - if (do_but_search) { - uiBlock *block = menu->region->uiblocks.first; - - retval = ui_handle_menu_button(C, event, menu); - - if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { - /* when there is a active search button and we close it, - * we need to reinit the mouse coords T35346. */ - if (ui_region_find_active_but(menu->region) != but) { - do_towards_reinit = true; - } - } - } - else { - uiBlock *block = menu->region->uiblocks.first; - uiBut *listbox = ui_list_find_mouse_over(menu->region, event); - - if (block->flag & UI_BLOCK_RADIAL) { - retval = ui_pie_handler(C, event, menu); - } - else if (event->type == LEFTMOUSE || event->val != KM_DBL_CLICK) { - bool handled = false; - - if (listbox) { - const int retval_test = ui_handle_list_event(C, event, menu->region, listbox); - if (retval_test != WM_UI_HANDLER_CONTINUE) { - retval = retval_test; - handled = true; - } - } - - if (handled == false) { - retval = ui_handle_menu_event( - C, event, menu, level, is_parent_inside, is_parent_menu, is_floating); - } - } - } - } - - if (do_towards_reinit) { - ui_mouse_motion_towards_reinit(menu, &event->x); - } - - return retval; -} - -/** - * Allow setting menu return value from externals. - * E.g. WM might need to do this for exiting files correctly. - */ -void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable) -{ - uiPopupBlockHandle *menu = block->handle; - if (menu) { - menu->menuretval = enable ? (menu->menuretval | retval) : (menu->menuretval & retval); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name UI Event Handlers - * \{ */ - -static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(userdata)) -{ - /* here we handle buttons at the region level, non-modal */ - ARegion *region = CTX_wm_region(C); - int retval = WM_UI_HANDLER_CONTINUE; - - if (region == NULL || BLI_listbase_is_empty(®ion->uiblocks)) { - return retval; - } - - /* either handle events for already activated button or try to activate */ - uiBut *but = ui_region_find_active_but(region); - uiBut *listbox = ui_list_find_mouse_over(region, event); - - retval = ui_handler_panel_region(C, event, region, listbox ? listbox : but); - - if (retval == WM_UI_HANDLER_CONTINUE && listbox) { - retval = ui_handle_list_event(C, event, region, listbox); - - /* interactions with the listbox should disable tips */ - if (retval == WM_UI_HANDLER_BREAK) { - if (but) { - UI_but_tooltip_timer_remove(C, but); - } - } - } - - if (retval == WM_UI_HANDLER_CONTINUE) { - if (but) { - retval = ui_handle_button_event(C, event, but); - } - else { - retval = ui_handle_button_over(C, event, region); - } - } - - /* Re-enable tool-tips. */ - if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { - ui_blocks_set_tooltips(region, true); - } - - /* delayed apply callbacks */ - ui_apply_but_funcs_after(C); - - return retval; -} - -static void ui_region_handler_remove(bContext *C, void *UNUSED(userdata)) -{ - ARegion *region = CTX_wm_region(C); - if (region == NULL) { - return; - } - - UI_blocklist_free(C, ®ion->uiblocks); - - bScreen *screen = CTX_wm_screen(C); - if (screen == NULL) { - return; - } - - /* delayed apply callbacks, but not for screen level regions, those - * we rather do at the very end after closing them all, which will - * be done in ui_region_handler/window */ - if (BLI_findindex(&screen->regionbase, region) == -1) { - ui_apply_but_funcs_after(C); - } -} - -/* handle buttons at the window level, modal, for example while - * number sliding, text editing, or when a menu block is open */ -static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSED(userdata)) -{ - ARegion *menu_region = CTX_wm_menu(C); - ARegion *region = menu_region ? menu_region : CTX_wm_region(C); - int retval = WM_UI_HANDLER_CONTINUE; - - uiBut *but = ui_region_find_active_but(region); - - if (but) { - bScreen *screen = CTX_wm_screen(C); - uiBut *but_other; - - /* handle activated button events */ - uiHandleButtonData *data = but->active; - - if ((data->state == BUTTON_STATE_MENU_OPEN) && - /* Make sure this popup isn't dragging a button. - * can happen with popovers (see T67882). */ - (ui_region_find_active_but(data->menu->region) == NULL) && - /* make sure mouse isn't inside another menu (see T43247) */ - (ui_screen_region_find_mouse_over(screen, event) == NULL) && - (ELEM(but->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) && - (but_other = ui_but_find_mouse_over(region, event)) && (but != but_other) && - (ELEM(but_other->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) && - /* Hover-opening menu's doesn't work well for buttons over one another - * along the same axis the menu is opening on (see T71719). */ - (((data->menu->direction & (UI_DIR_LEFT | UI_DIR_RIGHT)) && - BLI_rctf_isect_rect_x(&but->rect, &but_other->rect, NULL)) || - ((data->menu->direction & (UI_DIR_DOWN | UI_DIR_UP)) && - BLI_rctf_isect_rect_y(&but->rect, &but_other->rect, NULL)))) { - /* if mouse moves to a different root-level menu button, - * open it to replace the current menu */ - if ((but_other->flag & UI_BUT_DISABLED) == 0) { - ui_handle_button_activate(C, region, but_other, BUTTON_ACTIVATE_OVER); - button_activate_state(C, but_other, BUTTON_STATE_MENU_OPEN); - retval = WM_UI_HANDLER_BREAK; - } - } - else if (data->state == BUTTON_STATE_MENU_OPEN) { - /* handle events for menus and their buttons recursively, - * this will handle events from the top to the bottom menu */ - if (data->menu) { - retval = ui_handle_menus_recursive(C, event, data->menu, 0, false, false, false); - } - - /* handle events for the activated button */ - if ((data->menu && (retval == WM_UI_HANDLER_CONTINUE)) || (event->type == TIMER)) { - if (data->menu && data->menu->menuretval) { - ui_handle_button_return_submenu(C, event, but); - retval = WM_UI_HANDLER_BREAK; - } - else { - retval = ui_handle_button_event(C, event, but); - } - } - } - else { - /* handle events for the activated button */ - retval = ui_handle_button_event(C, event, but); - } - } - - /* Re-enable tool-tips. */ - if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { - ui_blocks_set_tooltips(region, true); - } - - if (but && but->active && but->active->menu) { - /* Set correct context menu-region. The handling button above breaks if we set the region - * first, so only set it for executing the after-funcs. */ - CTX_wm_menu_set(C, but->active->menu->region); - } - - /* delayed apply callbacks */ - ui_apply_but_funcs_after(C); - - /* Reset to previous context region. */ - CTX_wm_menu_set(C, menu_region); - - /* Don't handle double-click events, - * these will be converted into regular clicks which we handle. */ - if (retval == WM_UI_HANDLER_CONTINUE) { - if (event->val == KM_DBL_CLICK) { - return WM_UI_HANDLER_CONTINUE; - } - } - - /* we block all events, this is modal interaction */ - return WM_UI_HANDLER_BREAK; -} - -/* two types of popups, one with operator + enum, other with regular callbacks */ -static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata) -{ - uiPopupBlockHandle *menu = userdata; - /* we block all events, this is modal interaction, - * except for drop events which is described below */ - int retval = WM_UI_HANDLER_BREAK; - bool reset_pie = false; - - ARegion *menu_region = CTX_wm_menu(C); - CTX_wm_menu_set(C, menu->region); - - if (event->type == EVT_DROP || event->val == KM_DBL_CLICK) { - /* EVT_DROP: - * If we're handling drop event we'll want it to be handled by popup callee as well, - * so it'll be possible to perform such operations as opening .blend files by dropping - * them into blender, even if there's opened popup like splash screen (sergey). - * KM_DBL_CLICK: - * Continue in case of double click so wm_handlers_do calls handler again with KM_PRESS - * event. This is needed to ensure correct button handling for fast clicking (T47532). - */ - - retval = WM_UI_HANDLER_CONTINUE; - } - - ui_handle_menus_recursive(C, event, menu, 0, false, false, true); - - /* free if done, does not free handle itself */ - if (menu->menuretval) { - wmWindow *win = CTX_wm_window(C); - /* copy values, we have to free first (closes region) */ - const uiPopupBlockHandle temp = *menu; - uiBlock *block = menu->region->uiblocks.first; - - /* set last pie event to allow chained pie spawning */ - if (block->flag & UI_BLOCK_RADIAL) { - win->pie_event_type_last = block->pie_data.event_type; - reset_pie = true; - } - - ui_popup_block_free(C, menu); - UI_popup_handlers_remove(&win->modalhandlers, menu); - CTX_wm_menu_set(C, NULL); - -#ifdef USE_DRAG_TOGGLE - { - WM_event_free_ui_handler_all(C, - &win->modalhandlers, - ui_handler_region_drag_toggle, - ui_handler_region_drag_toggle_remove); - } -#endif - - if ((temp.menuretval & UI_RETURN_OK) || (temp.menuretval & UI_RETURN_POPUP_OK)) { - if (temp.popup_func) { - temp.popup_func(C, temp.popup_arg, temp.retvalue); - } - } - else if (temp.cancel_func) { - temp.cancel_func(C, temp.popup_arg); - } - - WM_event_add_mousemove(win); - } - else { - /* Re-enable tool-tips */ - if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { - ui_blocks_set_tooltips(menu->region, true); - } - } - - /* delayed apply callbacks */ - ui_apply_but_funcs_after(C); - - if (reset_pie) { - /* Reacquire window in case pie invalidates it somehow. */ - wmWindow *win = CTX_wm_window(C); - - if (win) { - win->pie_event_type_last = EVENT_NONE; - } - } - - CTX_wm_region_set(C, menu_region); - - return retval; -} - -static void ui_popup_handler_remove(bContext *C, void *userdata) -{ - uiPopupBlockHandle *menu = userdata; - - /* More correct would be to expect UI_RETURN_CANCEL here, but not wanting to - * cancel when removing handlers because of file exit is a rare exception. - * So instead of setting cancel flag for all menus before removing handlers, - * just explicitly flag menu with UI_RETURN_OK to avoid canceling it. */ - if ((menu->menuretval & UI_RETURN_OK) == 0 && menu->cancel_func) { - menu->cancel_func(C, menu->popup_arg); - } - - /* free menu block if window is closed for some reason */ - ui_popup_block_free(C, menu); - - /* delayed apply callbacks */ - ui_apply_but_funcs_after(C); -} - -void UI_region_handlers_add(ListBase *handlers) -{ - WM_event_remove_ui_handler(handlers, ui_region_handler, ui_region_handler_remove, NULL, false); - WM_event_add_ui_handler(NULL, handlers, ui_region_handler, ui_region_handler_remove, NULL, 0); -} - -void UI_popup_handlers_add(bContext *C, - ListBase *handlers, - uiPopupBlockHandle *popup, - const char flag) -{ - WM_event_add_ui_handler(C, handlers, ui_popup_handler, ui_popup_handler_remove, popup, flag); -} - -void UI_popup_handlers_remove(ListBase *handlers, uiPopupBlockHandle *popup) -{ - LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) { - if (handler_base->type == WM_HANDLER_TYPE_UI) { - wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base; - - if (handler->handle_fn == ui_popup_handler && - handler->remove_fn == ui_popup_handler_remove && handler->user_data == popup) { - /* tag refresh parent popup */ - wmEventHandler_UI *handler_next = (wmEventHandler_UI *)handler->head.next; - if (handler_next && handler_next->head.type == WM_HANDLER_TYPE_UI && - handler_next->handle_fn == ui_popup_handler && - handler_next->remove_fn == ui_popup_handler_remove) { - uiPopupBlockHandle *parent_popup = handler_next->user_data; - ED_region_tag_refresh_ui(parent_popup->region); - } - break; - } - } - } - - WM_event_remove_ui_handler(handlers, ui_popup_handler, ui_popup_handler_remove, popup, false); -} - -void UI_popup_handlers_remove_all(bContext *C, ListBase *handlers) -{ - WM_event_free_ui_handler_all(C, handlers, ui_popup_handler, ui_popup_handler_remove); -} - -bool UI_textbutton_activate_rna(const bContext *C, - ARegion *region, - const void *rna_poin_data, - const char *rna_prop_id) -{ - uiBlock *block_text = NULL; - uiBut *but_text = NULL; - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->type == UI_BTYPE_TEXT) { - if (but->rnaprop && but->rnapoin.data == rna_poin_data) { - if (STREQ(RNA_property_identifier(but->rnaprop), rna_prop_id)) { - block_text = block; - but_text = but; - break; - } - } - } - } - if (but_text) { - break; - } - } - - if (but_text) { - UI_but_active_only(C, region, block_text, but_text); - return true; - } - return false; -} - -bool UI_textbutton_activate_but(const bContext *C, uiBut *actbut) -{ - ARegion *region = CTX_wm_region(C); - uiBlock *block_text = NULL; - uiBut *but_text = NULL; - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but == actbut && but->type == UI_BTYPE_TEXT) { - block_text = block; - but_text = but; - break; - } - } - - if (but_text) { - break; - } - } - - if (but_text) { - UI_but_active_only(C, region, block_text, but_text); - return true; - } - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Public Utilities - * \{ */ - -/* is called by notifier */ -void UI_screen_free_active_but(const bContext *C, bScreen *screen) -{ - wmWindow *win = CTX_wm_window(C); - - ED_screen_areas_iter (win, screen, area) { - LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { - uiBut *but = ui_region_find_active_but(region); - if (but) { - uiHandleButtonData *data = but->active; - - if (data->menu == NULL && data->searchbox == NULL) { - if (data->state == BUTTON_STATE_HIGHLIGHT) { - ui_but_active_free(C, but); - } - } - } - } - } -} - -/* returns true if highlighted button allows drop of names */ -/* called in region context */ -bool UI_but_active_drop_name(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - uiBut *but = ui_region_find_active_but(region); - - if (but) { - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - return true; - } - } - - return false; -} - -bool UI_but_active_drop_color(bContext *C) -{ - ARegion *region = CTX_wm_region(C); - - if (region) { - uiBut *but = ui_region_find_active_but(region); - - if (but && but->type == UI_BTYPE_COLOR) { - return true; - } - } - - return false; -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_handlers.cc b/source/blender/editors/interface/interface_handlers.cc new file mode 100644 index 00000000000..73ba9bdd192 --- /dev/null +++ b/source/blender/editors/interface/interface_handlers.cc @@ -0,0 +1,11164 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" +#include "DNA_curveprofile_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BLI_array.hh" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_string_cursor_utf8.h" +#include "BLI_string_utf8.h" +#include "BLI_utildefines.h" + +#include "PIL_time.h" + +#include "BKE_animsys.h" +#include "BKE_blender_undo.h" +#include "BKE_brush.h" +#include "BKE_colorband.h" +#include "BKE_colortools.h" +#include "BKE_context.h" +#include "BKE_curveprofile.h" +#include "BKE_movieclip.h" +#include "BKE_paint.h" +#include "BKE_report.h" +#include "BKE_screen.h" +#include "BKE_tracking.h" +#include "BKE_unit.h" + +#include "IMB_colormanagement.h" + +#include "ED_screen.h" +#include "ED_undo.h" + +#include "UI_interface.h" +#include "UI_view2d.h" + +#include "BLF_api.h" + +#include "interface_intern.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" +#include "wm_event_system.h" + +#ifdef WITH_INPUT_IME +# include "BLT_lang.h" +# include "BLT_translation.h" +# include "wm_window.h" +#endif + +using blender::Array; + +/* -------------------------------------------------------------------- */ +/** \name Feature Defines + * + * These defines allow developers to locally toggle functionality which + * may be useful for testing (especially conflicts in dragging). + * Ideally the code would be refactored to support this functionality in a less fragile way. + * Until then keep these defines. + * \{ */ + +/** Place the mouse at the scaled down location when un-grabbing. */ +#define USE_CONT_MOUSE_CORRECT +/** Support dragging toggle buttons. */ +#define USE_DRAG_TOGGLE + +/** Support dragging multiple number buttons at once. */ +#define USE_DRAG_MULTINUM + +/** Allow dragging/editing all other selected items at once. */ +#define USE_ALLSELECT + +/** + * Check to avoid very small mouse-moves from jumping away from keyboard navigation, + * while larger mouse motion will override keyboard input, see: T34936. + */ +#define USE_KEYNAV_LIMIT + +/** Support dragging popups by their header. */ +#define USE_DRAG_POPUP + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Local Defines + * \{ */ + +/** + * The buffer side used for password strings, where the password is stored internally, + * but not displayed. + */ +#define UI_MAX_PASSWORD_STR 128 + +/** + * When #USER_CONTINUOUS_MOUSE is disabled or tablet input is used, + * Use this as a maximum soft range for mapping cursor motion to the value. + * Otherwise min/max of #FLT_MAX, #INT_MAX cause small adjustments to jump to large numbers. + * + * This is needed for values such as location & dimensions which don't have a meaningful min/max, + * Instead of mapping cursor motion to the min/max, map the motion to the click-step. + * + * This value is multiplied by the click step to calculate a range to clamp the soft-range by. + * See: T68130 + */ +#define UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX 1000 + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Local Prototypes + * \{ */ + +static int ui_do_but_EXIT(bContext *C, + uiBut *but, + struct uiHandleButtonData *data, + const wmEvent *event); +static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b); +static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str); +static void button_tooltip_timer_reset(bContext *C, uiBut *but); + +#ifdef USE_KEYNAV_LIMIT +static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event); +static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event); +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Structs & Defines + * \{ */ + +#define BUTTON_FLASH_DELAY 0.020 +#define MENU_SCROLL_INTERVAL 0.1 +#define PIE_MENU_INTERVAL 0.01 +#define BUTTON_AUTO_OPEN_THRESH 0.2 +#define BUTTON_MOUSE_TOWARDS_THRESH 1.0 +/** Pixels to move the cursor to get out of keyboard navigation. */ +#define BUTTON_KEYNAV_PX_LIMIT 8 + +/** Margin around the menu, use to check if we're moving towards this rectangle (in pixels). */ +#define MENU_TOWARDS_MARGIN 20 +/** Tolerance for closing menus (in pixels). */ +#define MENU_TOWARDS_WIGGLE_ROOM 64 +/** Drag-lock distance threshold (in pixels). */ +#define BUTTON_DRAGLOCK_THRESH 3 + +typedef enum uiButtonActivateType { + BUTTON_ACTIVATE_OVER, + BUTTON_ACTIVATE, + BUTTON_ACTIVATE_APPLY, + BUTTON_ACTIVATE_TEXT_EDITING, + BUTTON_ACTIVATE_OPEN, +} uiButtonActivateType; + +typedef enum uiHandleButtonState { + BUTTON_STATE_INIT, + BUTTON_STATE_HIGHLIGHT, + BUTTON_STATE_WAIT_FLASH, + BUTTON_STATE_WAIT_RELEASE, + BUTTON_STATE_WAIT_KEY_EVENT, + BUTTON_STATE_NUM_EDITING, + BUTTON_STATE_TEXT_EDITING, + BUTTON_STATE_TEXT_SELECTING, + BUTTON_STATE_MENU_OPEN, + BUTTON_STATE_WAIT_DRAG, + BUTTON_STATE_EXIT, +} uiHandleButtonState; + +typedef enum uiMenuScrollType { + MENU_SCROLL_UP, + MENU_SCROLL_DOWN, + MENU_SCROLL_TOP, + MENU_SCROLL_BOTTOM, +} uiMenuScrollType; + +#ifdef USE_ALLSELECT + +/* Unfortunately there's no good way handle more generally: + * (propagate single clicks on layer buttons to other objects) */ +# define USE_ALLSELECT_LAYER_HACK + +typedef struct uiSelectContextElem { + PointerRNA ptr; + union { + bool val_b; + int val_i; + float val_f; + }; +} uiSelectContextElem; + +typedef struct uiSelectContextStore { + uiSelectContextElem *elems; + int elems_len; + bool do_free; + bool is_enabled; + /* When set, simply copy values (don't apply difference). + * Rules are: + * - dragging numbers uses delta. + * - typing in values will assign to all. */ + bool is_copy; +} uiSelectContextStore; + +static bool ui_selectcontext_begin(bContext *C, + uiBut *but, + struct uiSelectContextStore *selctx_data); +static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data); +static void ui_selectcontext_apply(bContext *C, + uiBut *but, + struct uiSelectContextStore *selctx_data, + const double value, + const double value_orig); + +# define IS_ALLSELECT_EVENT(event) ((event)->alt != 0) + +/** just show a tinted color so users know its activated */ +# define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE + +#endif /* USE_ALLSELECT */ + +#ifdef USE_DRAG_MULTINUM + +/** + * how far to drag before we check for gesture direction (in pixels), + * note: half the height of a button is about right... */ +# define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4) + +/** + * How far to drag horizontally + * before we stop checking which buttons the gesture spans (in pixels), + * locking down the buttons so we can drag freely without worrying about vertical movement. + */ +# define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4) + +/** + * How strict to be when detecting a vertical gesture: + * [0.5 == sloppy], [0.9 == strict], (unsigned dot-product). + * + * \note We should be quite strict here, + * since doing a vertical gesture by accident should be avoided, + * however with some care a user should be able to do a vertical movement without _missing_. + */ +# define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f) + +/* a simple version of uiHandleButtonData when accessing multiple buttons */ +struct uiButMultiState { + double origvalue; + uiBut *but; + +# ifdef USE_ALLSELECT + uiSelectContextStore select_others; +# endif +}; + +struct uiHandleButtonMulti { + enum { + /** gesture direction unknown, wait until mouse has moved enough... */ + BUTTON_MULTI_INIT_UNSET = 0, + /** vertical gesture detected, flag buttons interactively (UI_BUT_DRAG_MULTI) */ + BUTTON_MULTI_INIT_SETUP, + /** flag buttons finished, apply horizontal motion to active and flagged */ + BUTTON_MULTI_INIT_ENABLE, + /** vertical gesture _not_ detected, take no further action */ + BUTTON_MULTI_INIT_DISABLE, + } init; + + bool has_mbuts; /* any buttons flagged UI_BUT_DRAG_MULTI */ + LinkNode *mbuts; + uiButStore *bs_mbuts; + + bool is_proportional; + + /* In some cases we directly apply the changes to multiple buttons, + * so we don't want to do it twice. */ + bool skip; + + /* before activating, we need to check gesture direction accumulate signed cursor movement + * here so we can tell if this is a vertical motion or not. */ + float drag_dir[2]; + + /* values copied direct from event->x,y + * used to detect buttons between the current and initial mouse position */ + int drag_start[2]; + + /* store x location once BUTTON_MULTI_INIT_SETUP is set, + * moving outside this sets BUTTON_MULTI_INIT_ENABLE */ + int drag_lock_x; +}; + +#endif /* USE_DRAG_MULTINUM */ + +struct uiHandleButtonData { + wmWindowManager *wm; + wmWindow *window; + ScrArea *area; + ARegion *region; + + bool interactive; + + /* overall state */ + uiHandleButtonState state; + int retval; + /* booleans (could be made into flags) */ + bool cancel, escapecancel; + bool applied, applied_interactive; + bool changed_cursor; + wmTimer *flashtimer; + + /* edited value */ + /* use 'ui_textedit_string_set' to assign new strings */ + char *str; + char *origstr; + double value, origvalue, startvalue; + float vec[3], origvec[3]; +#if 0 /* UNUSED */ + int togdual, togonly; +#endif + ColorBand *coba; + + /* Tool-tip. */ + uint tooltip_force : 1; + + /* auto open */ + bool used_mouse; + wmTimer *autoopentimer; + + /* auto open (hold) */ + wmTimer *hold_action_timer; + + /* text selection/editing */ + /* size of 'str' (including terminator) */ + int maxlen; + /* Button text selection: + * extension direction, selextend, inside ui_do_but_TEX */ + int sel_pos_init; + /* Allow reallocating str/editstr and using 'maxlen' to track alloc size (maxlen + 1) */ + bool is_str_dynamic; + + /* number editing / dragging */ + /* coords are Window/uiBlock relative (depends on the button) */ + int draglastx, draglasty; + int dragstartx, dragstarty; + int draglastvalue; + int dragstartvalue; + bool dragchange, draglock; + int dragsel; + float dragf, dragfstart; + CBData *dragcbd; + + /** Soft min/max with #UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX applied. */ + float drag_map_soft_min; + float drag_map_soft_max; + +#ifdef USE_CONT_MOUSE_CORRECT + /* when ungrabbing buttons which are #ui_but_is_cursor_warp(), + * we may want to position them. + * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl() + * to get this into a usable space. */ + float ungrab_mval[2]; +#endif + + /* menu open (watch UI_screen_free_active_but) */ + uiPopupBlockHandle *menu; + int menuretval; + + /* search box (watch UI_screen_free_active_but) */ + ARegion *searchbox; +#ifdef USE_KEYNAV_LIMIT + struct uiKeyNavLock searchbox_keynav_state; +#endif + +#ifdef USE_DRAG_MULTINUM + /* Multi-buttons will be updated in unison with the active button. */ + uiHandleButtonMulti multi_data; +#endif + +#ifdef USE_ALLSELECT + uiSelectContextStore select_others; +#endif + + /* Text field undo. */ + struct uiUndoStack_Text *undo_stack_text; + + /* post activate */ + uiButtonActivateType posttype; + uiBut *postbut; +}; + +struct uiAfterFunc { + struct uiAfterFunc *next, *prev; + + uiButHandleFunc func; + void *func_arg1; + void *func_arg2; + + uiButHandleNFunc funcN; + void *func_argN; + + uiButHandleRenameFunc rename_func; + void *rename_arg1; + void *rename_orig; + + uiBlockHandleFunc handle_func; + void *handle_func_arg; + int retval; + + uiMenuHandleFunc butm_func; + void *butm_func_arg; + int a2; + + wmOperator *popup_op; + wmOperatorType *optype; + int opcontext; + PointerRNA *opptr; + + PointerRNA rnapoin; + PropertyRNA *rnaprop; + + void *search_arg; + uiButSearchArgFreeFn search_arg_free_fn; + + bContextStore *context; + + char undostr[BKE_UNDO_STR_MAX]; +}; + +static void button_activate_init(bContext *C, + ARegion *region, + uiBut *but, + uiButtonActivateType type); +static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state); +static void button_activate_exit( + bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree); +static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata); +static void ui_handle_button_activate(bContext *C, + ARegion *region, + uiBut *but, + uiButtonActivateType type); +static bool ui_do_but_extra_operator_icon(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event); +static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, + uiHandleButtonData *data, + const wmEvent *event); + +#ifdef USE_DRAG_MULTINUM +static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block); +static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but); +#endif + +/* buttons clipboard */ +static ColorBand but_copypaste_coba = {0}; +static CurveMapping but_copypaste_curve = {0}; +static bool but_copypaste_curve_alive = false; +static CurveProfile but_copypaste_profile = {0}; +static bool but_copypaste_profile_alive = false; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Queries + * \{ */ + +bool ui_but_is_editing(const uiBut *but) +{ + uiHandleButtonData *data = but->active; + return (data && ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)); +} + +/* assumes event type is MOUSEPAN */ +void ui_pan_to_scroll(const wmEvent *event, int *type, int *val) +{ + static int lastdy = 0; + const int dy = WM_event_absolute_delta_y(event); + + /* This event should be originally from event->type, + * converting wrong event into wheel is bad, see T33803. */ + BLI_assert(*type == MOUSEPAN); + + /* sign differs, reset */ + if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) { + lastdy = dy; + } + else { + lastdy += dy; + + if (abs(lastdy) > (int)UI_UNIT_Y) { + *val = KM_PRESS; + + if (dy > 0) { + *type = WHEELUPMOUSE; + } + else { + *type = WHEELDOWNMOUSE; + } + + lastdy = 0; + } + } +} + +static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b) +{ + return ((but_a->type == but_b->type) && (but_a->alignnr == but_b->alignnr) && + (but_a->poin == but_b->poin) && (but_a->rnapoin.type == but_b->rnapoin.type) && + (but_a->rnaprop == but_b->rnaprop)); +} + +/** + * Finds the pressed button in an aligned row (typically an expanded enum). + * + * \param direction: Use when there may be multiple buttons pressed. + */ +uiBut *ui_but_find_select_in_enum(uiBut *but, int direction) +{ + uiBut *but_iter = but; + uiBut *but_found = NULL; + BLI_assert(ELEM(direction, -1, 1)); + + while ((but_iter->prev) && ui_but_find_select_in_enum__cmp(but_iter->prev, but)) { + but_iter = but_iter->prev; + } + + while (but_iter && ui_but_find_select_in_enum__cmp(but_iter, but)) { + if (but_iter->flag & UI_SELECT) { + but_found = but_iter; + if (direction == 1) { + break; + } + } + but_iter = but_iter->next; + } + + return but_found; +} + +static float ui_mouse_scale_warp_factor(const bool shift) +{ + return shift ? 0.05f : 1.0f; +} + +static void ui_mouse_scale_warp(uiHandleButtonData *data, + const float mx, + const float my, + float *r_mx, + float *r_my, + const bool shift) +{ + const float fac = ui_mouse_scale_warp_factor(shift); + + /* slow down the mouse, this is fairly picky */ + *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac); + *r_my = (data->dragstarty * (1.0f - fac) + my * fac); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Utilities + * \{ */ + +/** + * Ignore mouse movements within some horizontal pixel threshold before starting to drag + */ +static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx) +{ + if (mx == data->draglastx) { + return false; + } + + if (data->draglock) { + if (abs(mx - data->dragstartx) <= BUTTON_DRAGLOCK_THRESH) { + return false; + } +#ifdef USE_DRAG_MULTINUM + if (ELEM(data->multi_data.init, + uiHandleButtonMulti::BUTTON_MULTI_INIT_UNSET, + uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP)) { + return false; + } +#endif + data->draglock = false; + data->dragstartx = mx; /* ignore mouse movement within drag-lock */ + } + + return true; +} + +static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop) +{ + /* Not very elegant, but ensures preference changes force re-save. */ + bool tag = false; + if (prop && !(RNA_property_flag(prop) & PROP_NO_DEG_UPDATE)) { + StructRNA *base = RNA_struct_base(ptr->type); + if (base == NULL) { + base = ptr->type; + } + if (ELEM(base, + &RNA_AddonPreferences, + &RNA_KeyConfigPreferences, + &RNA_KeyMapItem, + &RNA_UserAssetLibrary)) { + tag = true; + } + } + return tag; +} + +bool UI_but_is_userdef(const uiBut *but) +{ + /* This is read-only, RNA API isn't using const when it could. */ + return ui_rna_is_userdef((PointerRNA *)&but->rnapoin, but->rnaprop); +} + +static void ui_rna_update_preferences_dirty(PointerRNA *ptr, PropertyRNA *prop) +{ + if (ui_rna_is_userdef(ptr, prop)) { + U.runtime.is_dirty = true; + WM_main_add_notifier(NC_WINDOW, NULL); + } +} + +static void ui_but_update_preferences_dirty(uiBut *but) +{ + ui_rna_update_preferences_dirty(&but->rnapoin, but->rnaprop); +} + +static void ui_afterfunc_update_preferences_dirty(uiAfterFunc *after) +{ + ui_rna_update_preferences_dirty(&after->rnapoin, after->rnaprop); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Snap Values + * \{ */ + +enum eSnapType { + SNAP_OFF = 0, + SNAP_ON, + SNAP_ON_SMALL, +}; + +static enum eSnapType ui_event_to_snap(const wmEvent *event) +{ + return (event->ctrl) ? (event->shift) ? SNAP_ON_SMALL : SNAP_ON : SNAP_OFF; +} + +static bool ui_event_is_snap(const wmEvent *event) +{ + return (ELEM(event->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) || + ELEM(event->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY)); +} + +static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue) +{ + const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12; + BLI_assert(snap != SNAP_OFF); + *r_hue = roundf((*r_hue) * snap_increment) / snap_increment; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Apply/Revert + * \{ */ + +static ListBase UIAfterFuncs = {NULL, NULL}; + +static uiAfterFunc *ui_afterfunc_new(void) +{ + uiAfterFunc *after = (uiAfterFunc *)MEM_callocN(sizeof(uiAfterFunc), "uiAfterFunc"); + + BLI_addtail(&UIAfterFuncs, after); + + return after; +} + +/** + * For executing operators after the button is pressed. + * (some non operator buttons need to trigger operators), see: T37795. + * + * \note Can only call while handling buttons. + */ +PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props) +{ + PointerRNA *ptr = NULL; + uiAfterFunc *after = ui_afterfunc_new(); + + after->optype = ot; + after->opcontext = opcontext; + + if (create_props) { + ptr = (PointerRNA *)MEM_callocN(sizeof(PointerRNA), __func__); + WM_operator_properties_create_ptr(ptr, ot); + after->opptr = ptr; + } + + return ptr; +} + +static void popup_check(bContext *C, wmOperator *op) +{ + if (op && op->type->check) { + op->type->check(C, op); + } +} + +/** + * Check if a #uiAfterFunc is needed for this button. + */ +static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but) +{ + return (but->func || but->funcN || but->rename_func || but->optype || but->rnaprop || + block->handle_func || (but->type == UI_BTYPE_BUT_MENU && block->butm_func) || + (block->handle && block->handle->popup_op)); +} + +static void ui_apply_but_func(bContext *C, uiBut *but) +{ + uiBlock *block = but->block; + + /* these functions are postponed and only executed after all other + * handling is done, i.e. menus are closed, in order to avoid conflicts + * with these functions removing the buttons we are working with */ + + if (ui_afterfunc_check(block, but)) { + uiAfterFunc *after = ui_afterfunc_new(); + + if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) { + /* exception, this will crash due to removed button otherwise */ + but->func(C, but->func_arg1, but->func_arg2); + } + else { + after->func = but->func; + } + + after->func_arg1 = but->func_arg1; + after->func_arg2 = but->func_arg2; + + after->funcN = but->funcN; + after->func_argN = (but->func_argN) ? MEM_dupallocN(but->func_argN) : NULL; + + after->rename_func = but->rename_func; + after->rename_arg1 = but->rename_arg1; + after->rename_orig = but->rename_orig; /* needs free! */ + + after->handle_func = block->handle_func; + after->handle_func_arg = block->handle_func_arg; + after->retval = but->retval; + + if (but->type == UI_BTYPE_BUT_MENU) { + after->butm_func = block->butm_func; + after->butm_func_arg = block->butm_func_arg; + after->a2 = but->a2; + } + + if (block->handle) { + after->popup_op = block->handle->popup_op; + } + + after->optype = but->optype; + after->opcontext = but->opcontext; + after->opptr = but->opptr; + + after->rnapoin = but->rnapoin; + after->rnaprop = but->rnaprop; + + if (but->type == UI_BTYPE_SEARCH_MENU) { + uiButSearch *search_but = (uiButSearch *)but; + after->search_arg_free_fn = search_but->arg_free_fn; + after->search_arg = search_but->arg; + search_but->arg_free_fn = NULL; + search_but->arg = NULL; + } + + if (but->context) { + after->context = CTX_store_copy(but->context); + } + + but->optype = NULL; + but->opcontext = 0; + but->opptr = NULL; + } +} + +/* typically call ui_apply_but_undo(), ui_apply_but_autokey() */ +static void ui_apply_but_undo(uiBut *but) +{ + if (but->flag & UI_BUT_UNDO) { + const char *str = NULL; + size_t str_len_clip = SIZE_MAX - 1; + bool skip_undo = false; + + /* define which string to use for undo */ + if (but->type == UI_BTYPE_MENU) { + str = but->drawstr; + str_len_clip = ui_but_drawstr_len_without_sep_char(but); + } + else if (but->drawstr[0]) { + str = but->drawstr; + str_len_clip = ui_but_drawstr_len_without_sep_char(but); + } + else { + str = but->tip; + str_len_clip = ui_but_tip_len_only_first_line(but); + } + + /* fallback, else we don't get an undo! */ + if (str == NULL || str[0] == '\0' || str_len_clip == 0) { + str = "Unknown Action"; + str_len_clip = strlen(str); + } + + /* Optionally override undo when undo system doesn't support storing properties. */ + if (but->rnapoin.owner_id) { + /* Exception for renaming ID data, we always need undo pushes in this case, + * because undo systems track data by their ID, see: T67002. */ + extern PropertyRNA rna_ID_name; + /* Exception for active shape-key, since changing this in edit-mode updates + * the shape key from object mode data. */ + extern PropertyRNA rna_Object_active_shape_key_index; + if (ELEM(but->rnaprop, &rna_ID_name, &rna_Object_active_shape_key_index)) { + /* pass */ + } + else { + ID *id = but->rnapoin.owner_id; + if (!ED_undo_is_legacy_compatible_for_property((bContext *)but->block->evil_C, id)) { + skip_undo = true; + } + } + } + + if (skip_undo == false) { + /* XXX: disable all undo pushes from UI changes from sculpt mode as they cause memfile undo + * steps to be written which cause lag: T71434. */ + if (BKE_paintmode_get_active_from_context((bContext *)but->block->evil_C) == + PAINT_MODE_SCULPT) { + skip_undo = true; + } + } + + if (skip_undo) { + str = ""; + } + + /* delayed, after all other funcs run, popups are closed, etc */ + uiAfterFunc *after = ui_afterfunc_new(); + BLI_strncpy(after->undostr, str, min_zz(str_len_clip + 1, sizeof(after->undostr))); + } +} + +static void ui_apply_but_autokey(bContext *C, uiBut *but) +{ + Scene *scene = CTX_data_scene(C); + + /* try autokey */ + ui_but_anim_autokey(C, but, scene, scene->r.cfra); + + /* make a little report about what we've done! */ + if (but->rnaprop) { + char *buf; + + if (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD) { + return; + } + + buf = WM_prop_pystring_assign(C, &but->rnapoin, but->rnaprop, but->rnaindex); + if (buf) { + BKE_report(CTX_wm_reports(C), RPT_PROPERTY, buf); + MEM_freeN(buf); + + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, NULL); + } + } +} + +static void ui_apply_but_funcs_after(bContext *C) +{ + /* copy to avoid recursive calls */ + ListBase funcs = UIAfterFuncs; + BLI_listbase_clear(&UIAfterFuncs); + + LISTBASE_FOREACH_MUTABLE (uiAfterFunc *, afterf, &funcs) { + uiAfterFunc after = *afterf; /* copy to avoid memleak on exit() */ + BLI_freelinkN(&funcs, afterf); + + if (after.context) { + CTX_store_set(C, after.context); + } + + if (after.popup_op) { + popup_check(C, after.popup_op); + } + + PointerRNA opptr; + if (after.opptr) { + /* free in advance to avoid leak on exit */ + opptr = *after.opptr; + MEM_freeN(after.opptr); + } + + if (after.optype) { + WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL); + } + + if (after.opptr) { + WM_operator_properties_free(&opptr); + } + + if (after.rnapoin.data) { + RNA_property_update(C, &after.rnapoin, after.rnaprop); + } + + if (after.context) { + CTX_store_set(C, NULL); + CTX_store_free(after.context); + } + + if (after.func) { + after.func(C, after.func_arg1, after.func_arg2); + } + if (after.funcN) { + after.funcN(C, after.func_argN, after.func_arg2); + } + if (after.func_argN) { + MEM_freeN(after.func_argN); + } + + if (after.handle_func) { + after.handle_func(C, after.handle_func_arg, after.retval); + } + if (after.butm_func) { + after.butm_func(C, after.butm_func_arg, after.a2); + } + + if (after.rename_func) { + after.rename_func(C, after.rename_arg1, (char *)after.rename_orig); + } + if (after.rename_orig) { + MEM_freeN(after.rename_orig); + } + + if (after.search_arg_free_fn) { + after.search_arg_free_fn(after.search_arg); + } + + ui_afterfunc_update_preferences_dirty(&after); + + if (after.undostr[0]) { + ED_undo_push(C, after.undostr); + } + } +} + +static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_but_value_set(but, but->hardmin); + ui_apply_but_func(C, but); + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if (but->type == UI_BTYPE_MENU) { + ui_but_value_set(but, data->value); + } + + ui_but_update_edited(but); + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + const double value = ui_but_value_get(but); + int value_toggle; + if (but->bit) { + value_toggle = UI_BITBUT_VALUE_TOGGLED((int)value, but->bitnr); + } + else { + value_toggle = (value == 0.0); + if (ELEM(but->type, UI_BTYPE_TOGGLE_N, UI_BTYPE_ICON_TOGGLE_N, UI_BTYPE_CHECKBOX_N)) { + value_toggle = !value_toggle; + } + } + + ui_but_value_set(but, (double)value_toggle); + if (ELEM(but->type, UI_BTYPE_ICON_TOGGLE, UI_BTYPE_ICON_TOGGLE_N)) { + ui_but_update_edited(but); + } + + ui_apply_but_func(C, but); + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + ui_but_value_set(but, but->hardmax); + + ui_apply_but_func(C, but); + + /* states of other row buttons */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (bt != but && bt->poin == but->poin && ELEM(bt->type, UI_BTYPE_ROW, UI_BTYPE_LISTROW)) { + ui_but_update_edited(bt); + } + } + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if (!data->str) { + return; + } + + ui_but_string_set(C, but, data->str); + ui_but_update_edited(but); + + /* give butfunc a copy of the original text too. + * feature used for bone renaming, channels, etc. + * afterfunc frees rename_orig */ + if (data->origstr && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) { + /* In this case, we need to keep origstr available, + * to restore real org string in case we cancel after having typed something already. */ + but->rename_orig = BLI_strdup(data->origstr); + } + /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */ + else if (ui_afterfunc_check(but->block, but)) { + but->rename_orig = data->origstr; + data->origstr = NULL; + } + + void *orig_arg2 = but->func_arg2; + + /* If arg2 isn't in use already, pass the active search item through it. */ + if ((but->func_arg2 == NULL) && (but->type == UI_BTYPE_SEARCH_MENU)) { + uiButSearch *search_but = (uiButSearch *)but; + but->func_arg2 = search_but->item_active; + } + + ui_apply_but_func(C, but); + + but->func_arg2 = orig_arg2; + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if (data->str) { + ui_but_string_set(C, but, data->str); + ui_but_update_edited(but); + } + else { + ui_but_value_set(but, but->hardmax); + ui_apply_but_func(C, but); + } + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if (data->str) { + double value; + /* Check if the string value is a number and cancel if it's equal to the startvalue. */ + if (ui_but_string_eval_number(C, but, data->str, &value) && (value == data->startvalue)) { + data->cancel = true; + return; + } + + if (ui_but_string_set(C, but, data->str)) { + data->value = ui_but_value_get(but); + } + else { + data->cancel = true; + return; + } + } + else { + ui_but_value_set(but, data->value); + } + + ui_but_update_edited(but); + ui_apply_but_func(C, but); + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_but_v3_set(but, data->vec); + ui_but_update_edited(but); + ui_apply_but_func(C, but); + + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Drag Multi-Number + * \{ */ + +#ifdef USE_DRAG_MULTINUM + +/* small multi-but api */ +static void ui_multibut_add(uiHandleButtonData *data, uiBut *but) +{ + BLI_assert(but->flag & UI_BUT_DRAG_MULTI); + BLI_assert(data->multi_data.has_mbuts); + + uiButMultiState *mbut_state = (uiButMultiState *)MEM_callocN(sizeof(*mbut_state), __func__); + mbut_state->but = but; + mbut_state->origvalue = ui_but_value_get(but); +# ifdef USE_ALLSELECT + mbut_state->select_others.is_copy = data->select_others.is_copy; +# endif + + BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state); + + UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but); +} + +static uiButMultiState *ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but) +{ + for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) { + uiButMultiState *mbut_state = (uiButMultiState *)l->link; + + if (mbut_state->but == but) { + return mbut_state; + } + } + + return NULL; +} + +static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block) +{ + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->flag & UI_BUT_DRAG_MULTI) { + uiButMultiState *mbut_state = ui_multibut_lookup(data, but); + if (mbut_state) { + ui_but_value_set(but, mbut_state->origvalue); + +# ifdef USE_ALLSELECT + if (mbut_state->select_others.elems_len > 0) { + ui_selectcontext_apply( + C, but, &mbut_state->select_others, mbut_state->origvalue, mbut_state->origvalue); + } +# else + UNUSED_VARS(C); +# endif + } + } + } +} + +static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block) +{ +# ifdef USE_ALLSELECT + if (data->multi_data.mbuts) { + LinkNode *list = data->multi_data.mbuts; + while (list) { + LinkNode *next = list->next; + uiButMultiState *mbut_state = (uiButMultiState *)list->link; + + if (mbut_state->select_others.elems) { + MEM_freeN(mbut_state->select_others.elems); + } + + MEM_freeN(list->link); + MEM_freeN(list); + list = next; + } + } +# else + BLI_linklist_freeN(data->multi_data.mbuts); +# endif + + data->multi_data.mbuts = NULL; + + if (data->multi_data.bs_mbuts) { + UI_butstore_free(block, data->multi_data.bs_mbuts); + data->multi_data.bs_mbuts = NULL; + } +} + +static bool ui_multibut_states_tag(uiBut *but_active, + uiHandleButtonData *data, + const wmEvent *event) +{ + float seg[2][2]; + bool changed = false; + + seg[0][0] = data->multi_data.drag_start[0]; + seg[0][1] = data->multi_data.drag_start[1]; + + seg[1][0] = event->x; + seg[1][1] = event->y; + + BLI_assert(data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP); + + ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]); + ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]); + + data->multi_data.has_mbuts = false; + + /* follow ui_but_find_mouse_over_ex logic */ + LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) { + bool drag_prev = false; + bool drag_curr = false; + + /* re-set each time */ + if (but->flag & UI_BUT_DRAG_MULTI) { + but->flag &= ~UI_BUT_DRAG_MULTI; + drag_prev = true; + } + + if (ui_but_is_interactive(but, false)) { + + /* drag checks */ + if (but_active != but) { + if (ui_but_is_compatible(but_active, but)) { + + BLI_assert(but->active == NULL); + + /* finally check for overlap */ + if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) { + + but->flag |= UI_BUT_DRAG_MULTI; + data->multi_data.has_mbuts = true; + drag_curr = true; + } + } + } + } + + changed |= (drag_prev != drag_curr); + } + + return changed; +} + +static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data) +{ + BLI_assert(data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP); + BLI_assert(data->multi_data.has_mbuts); + + data->multi_data.bs_mbuts = UI_butstore_create(but_active->block); + + LISTBASE_FOREACH (uiBut *, but, &but_active->block->buttons) { + if (but->flag & UI_BUT_DRAG_MULTI) { + ui_multibut_add(data, but); + } + } + + /* edit buttons proportionally to eachother + * note: if we mix buttons which are proportional and others which are not, + * this may work a bit strangely */ + if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) || + ELEM(but_active->unit_type, RNA_SUBTYPE_UNIT_VALUE(PROP_UNIT_LENGTH))) { + if (data->origvalue != 0.0) { + data->multi_data.is_proportional = true; + } + } +} + +static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block) +{ + ARegion *region = data->region; + const double value_delta = data->value - data->origvalue; + const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) : + 0.0; + + BLI_assert(data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_ENABLE); + BLI_assert(data->multi_data.skip == false); + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (!(but->flag & UI_BUT_DRAG_MULTI)) { + continue; + } + + uiButMultiState *mbut_state = ui_multibut_lookup(data, but); + + if (mbut_state == NULL) { + /* Highly unlikely. */ + printf("%s: Can't find button\n", __func__); + /* While this avoids crashing, multi-button dragging will fail, + * which is still a bug from the user perspective. See T83651. */ + continue; + } + + void *active_back; + ui_but_execute_begin(C, region, but, &active_back); + +# ifdef USE_ALLSELECT + if (data->select_others.is_enabled) { + /* init once! */ + if (mbut_state->select_others.elems_len == 0) { + ui_selectcontext_begin(C, but, &mbut_state->select_others); + } + if (mbut_state->select_others.elems_len == 0) { + mbut_state->select_others.elems_len = -1; + } + } + + /* Needed so we apply the right deltas. */ + but->active->origvalue = mbut_state->origvalue; + but->active->select_others = mbut_state->select_others; + but->active->select_others.do_free = false; +# endif + + BLI_assert(active_back == NULL); + /* No need to check 'data->state' here. */ + if (data->str) { + /* Entering text (set all). */ + but->active->value = data->value; + ui_but_string_set(C, but, data->str); + } + else { + /* Dragging (use delta). */ + if (data->multi_data.is_proportional) { + but->active->value = mbut_state->origvalue * value_scale; + } + else { + but->active->value = mbut_state->origvalue + value_delta; + } + + /* Clamp based on soft limits, see T40154. */ + CLAMP(but->active->value, (double)but->softmin, (double)but->softmax); + } + + ui_but_execute_end(C, region, but, active_back); + } +} + +#endif /* USE_DRAG_MULTINUM */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Drag Toggle + * \{ */ + +#ifdef USE_DRAG_TOGGLE + +/* Helpers that wrap boolean functions, to support different kinds of buttons. */ + +static bool ui_drag_toggle_but_is_supported(const uiBut *but) +{ + if (but->flag & UI_BUT_DISABLED) { + return false; + } + if (ui_but_is_bool(but)) { + return true; + } + if (UI_but_is_decorator(but)) { + return ELEM(but->icon, + ICON_DECORATE, + ICON_DECORATE_KEYFRAME, + ICON_DECORATE_ANIMATE, + ICON_DECORATE_OVERRIDE); + } + return false; +} + +/* Button pushed state to compare if other buttons match. Can be more + * then just true or false for toggle buttons with more than 2 states. */ +static int ui_drag_toggle_but_pushed_state(bContext *C, uiBut *but) +{ + if (but->rnapoin.data == NULL && but->poin == NULL && but->icon) { + if (but->pushed_state_func) { + return but->pushed_state_func(C, but->pushed_state_arg); + } + /* Assume icon identifies a unique state, for buttons that + * work through functions callbacks and don't have an boolean + * value that indicates the state. */ + return but->icon + but->iconadd; + } + if (ui_but_is_bool(but)) { + return ui_but_is_pushed(but); + } + return 0; +} + +typedef struct uiDragToggleHandle { + /* init */ + int pushed_state; + float but_cent_start[2]; + + bool is_xy_lock_init; + bool xy_lock[2]; + + int xy_init[2]; + int xy_last[2]; +} uiDragToggleHandle; + +static bool ui_drag_toggle_set_xy_xy( + bContext *C, ARegion *region, const int pushed_state, const int xy_src[2], const int xy_dst[2]) +{ + /* popups such as layers won't re-evaluate on redraw */ + const bool do_check = (region->regiontype == RGN_TYPE_TEMPORARY); + bool changed = false; + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float xy_a_block[2] = {UNPACK2(xy_src)}; + float xy_b_block[2] = {UNPACK2(xy_dst)}; + + ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); + ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + /* Note: ctrl is always true here because (at least for now) + * we always want to consider text control in this case, even when not embossed. */ + if (ui_but_is_interactive(but, true)) { + if (BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) { + + /* execute the button */ + if (ui_drag_toggle_but_is_supported(but)) { + /* is it pressed? */ + const int pushed_state_but = ui_drag_toggle_but_pushed_state(C, but); + if (pushed_state_but != pushed_state) { + UI_but_execute(C, region, but); + if (do_check) { + ui_but_update_edited(but); + } + if (U.runtime.is_dirty == false) { + ui_but_update_preferences_dirty(but); + } + changed = true; + } + } + /* done */ + } + } + } + } + if (changed) { + /* apply now, not on release (or if handlers are canceled for whatever reason) */ + ui_apply_but_funcs_after(C); + } + + return changed; +} + +static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2]) +{ + ARegion *region = CTX_wm_region(C); + bool do_draw = false; + + /** + * Initialize Locking: + * + * Check if we need to initialize the lock axis by finding if the first + * button we mouse over is X or Y aligned, then lock the mouse to that axis after. + */ + if (drag_info->is_xy_lock_init == false) { + /* first store the buttons original coords */ + uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true); + + if (but) { + if (but->flag & UI_BUT_DRAG_LOCK) { + const float but_cent_new[2] = { + BLI_rctf_cent_x(&but->rect), + BLI_rctf_cent_y(&but->rect), + }; + + /* check if this is a different button, + * chances are high the button wont move about :) */ + if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) { + if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) < + fabsf(drag_info->but_cent_start[1] - but_cent_new[1])) { + drag_info->xy_lock[0] = true; + } + else { + drag_info->xy_lock[1] = true; + } + drag_info->is_xy_lock_init = true; + } + } + else { + drag_info->is_xy_lock_init = true; + } + } + } + /* done with axis locking */ + + int xy[2]; + xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0]; + xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1]; + + /* touch all buttons between last mouse coord and this one */ + do_draw = ui_drag_toggle_set_xy_xy(C, region, drag_info->pushed_state, drag_info->xy_last, xy); + + if (do_draw) { + ED_region_tag_redraw(region); + } + + copy_v2_v2_int(drag_info->xy_last, xy); +} + +static void ui_handler_region_drag_toggle_remove(bContext *UNUSED(C), void *userdata) +{ + uiDragToggleHandle *drag_info = (uiDragToggleHandle *)userdata; + MEM_freeN(drag_info); +} + +static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata) +{ + uiDragToggleHandle *drag_info = (uiDragToggleHandle *)userdata; + bool done = false; + + switch (event->type) { + case LEFTMOUSE: { + if (event->val == KM_RELEASE) { + done = true; + } + break; + } + case MOUSEMOVE: { + ui_drag_toggle_set(C, drag_info, &event->x); + break; + } + } + + if (done) { + wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + uiBut *but = ui_but_find_mouse_over_ex( + region, drag_info->xy_init[0], drag_info->xy_init[1], true); + + if (but) { + ui_apply_but_undo(but); + } + + WM_event_remove_ui_handler(&win->modalhandlers, + ui_handler_region_drag_toggle, + ui_handler_region_drag_toggle_remove, + drag_info, + false); + ui_handler_region_drag_toggle_remove(C, drag_info); + + WM_event_add_mousemove(win); + return WM_UI_HANDLER_BREAK; + } + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_but_is_drag_toggle(const uiBut *but) +{ + return ((ui_drag_toggle_but_is_supported(but) == true) && + /* Menu check is important so the button dragged over isn't removed instantly. */ + (ui_block_is_menu(but->block) == false)); +} + +#endif /* USE_DRAG_TOGGLE */ + +#ifdef USE_ALLSELECT + +static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore *selctx_data) +{ + PointerRNA lptr, idptr; + PropertyRNA *lprop; + bool success = false; + + char *path = NULL; + ListBase lb = {NULL}; + + PointerRNA ptr = but->rnapoin; + PropertyRNA *prop = but->rnaprop; + const int index = but->rnaindex; + + /* for now don't support whole colors */ + if (index == -1) { + return false; + } + + /* if there is a valid property that is editable... */ + if (ptr.data && prop) { + bool use_path_from_id; + + /* some facts we want to know */ + const bool is_array = RNA_property_array_check(prop); + const int rna_type = RNA_property_type(prop); + + if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) && + !BLI_listbase_is_empty(&lb)) { + selctx_data->elems_len = BLI_listbase_count(&lb); + selctx_data->elems = (uiSelectContextElem *)MEM_mallocN( + sizeof(uiSelectContextElem) * selctx_data->elems_len, __func__); + int i; + LISTBASE_FOREACH_INDEX (CollectionPointerLink *, link, &lb, i) { + if (i >= selctx_data->elems_len) { + break; + } + uiSelectContextElem *other = &selctx_data->elems[i]; + /* TODO,. de-duplicate copy_to_selected_button */ + if (link->ptr.data != ptr.data) { + if (use_path_from_id) { + /* Path relative to ID. */ + lprop = NULL; + RNA_id_pointer_create(link->ptr.owner_id, &idptr); + RNA_path_resolve_property(&idptr, path, &lptr, &lprop); + } + else if (path) { + /* Path relative to elements from list. */ + lprop = NULL; + RNA_path_resolve_property(&link->ptr, path, &lptr, &lprop); + } + else { + lptr = link->ptr; + lprop = prop; + } + + /* lptr might not be the same as link->ptr! */ + if ((lptr.data != ptr.data) && (lprop == prop) && RNA_property_editable(&lptr, lprop)) { + other->ptr = lptr; + if (is_array) { + if (rna_type == PROP_FLOAT) { + other->val_f = RNA_property_float_get_index(&lptr, lprop, index); + } + else if (rna_type == PROP_INT) { + other->val_i = RNA_property_int_get_index(&lptr, lprop, index); + } + /* ignored for now */ +# if 0 + else if (rna_type == PROP_BOOLEAN) { + other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index); + } +# endif + } + else { + if (rna_type == PROP_FLOAT) { + other->val_f = RNA_property_float_get(&lptr, lprop); + } + else if (rna_type == PROP_INT) { + other->val_i = RNA_property_int_get(&lptr, lprop); + } + /* ignored for now */ +# if 0 + else if (rna_type == PROP_BOOLEAN) { + other->val_b = RNA_property_boolean_get(&lptr, lprop); + } + else if (rna_type == PROP_ENUM) { + other->val_i = RNA_property_enum_get(&lptr, lprop); + } +# endif + } + + continue; + } + } + + selctx_data->elems_len -= 1; + i -= 1; + } + + success = (selctx_data->elems_len != 0); + } + } + + if (selctx_data->elems_len == 0) { + MEM_SAFE_FREE(selctx_data->elems); + } + + MEM_SAFE_FREE(path); + BLI_freelistN(&lb); + + /* caller can clear */ + selctx_data->do_free = true; + + if (success) { + but->flag |= UI_BUT_IS_SELECT_CONTEXT; + } + + return success; +} + +static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data) +{ + if (selctx_data->do_free) { + if (selctx_data->elems) { + MEM_freeN(selctx_data->elems); + } + } + + but->flag &= ~UI_BUT_IS_SELECT_CONTEXT; +} + +static void ui_selectcontext_apply(bContext *C, + uiBut *but, + uiSelectContextStore *selctx_data, + const double value, + const double value_orig) +{ + if (selctx_data->elems) { + PropertyRNA *prop = but->rnaprop; + PropertyRNA *lprop = but->rnaprop; + const int index = but->rnaindex; + const bool use_delta = (selctx_data->is_copy == false); + + union { + bool b; + int i; + float f; + PointerRNA p; + } delta, min, max; + + const bool is_array = RNA_property_array_check(prop); + const int rna_type = RNA_property_type(prop); + + if (rna_type == PROP_FLOAT) { + delta.f = use_delta ? (value - value_orig) : value; + RNA_property_float_range(&but->rnapoin, prop, &min.f, &max.f); + } + else if (rna_type == PROP_INT) { + delta.i = use_delta ? ((int)value - (int)value_orig) : (int)value; + RNA_property_int_range(&but->rnapoin, prop, &min.i, &max.i); + } + else if (rna_type == PROP_ENUM) { + /* Not a delta in fact. */ + delta.i = RNA_property_enum_get(&but->rnapoin, prop); + } + else if (rna_type == PROP_BOOLEAN) { + if (is_array) { + /* Not a delta in fact. */ + delta.b = RNA_property_boolean_get_index(&but->rnapoin, prop, index); + } + else { + /* Not a delta in fact. */ + delta.b = RNA_property_boolean_get(&but->rnapoin, prop); + } + } + else if (rna_type == PROP_POINTER) { + /* Not a delta in fact. */ + delta.p = RNA_property_pointer_get(&but->rnapoin, prop); + } + +# ifdef USE_ALLSELECT_LAYER_HACK + /* make up for not having 'handle_layer_buttons' */ + { + const PropertySubType subtype = RNA_property_subtype(prop); + + if ((rna_type == PROP_BOOLEAN) && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) && is_array && + /* could check for 'handle_layer_buttons' */ + but->func) { + wmWindow *win = CTX_wm_window(C); + if (!win->eventstate->shift) { + const int len = RNA_property_array_length(&but->rnapoin, prop); + bool *tmparray = (bool *)MEM_callocN(sizeof(bool) * len, __func__); + + tmparray[index] = true; + + for (int i = 0; i < selctx_data->elems_len; i++) { + uiSelectContextElem *other = &selctx_data->elems[i]; + PointerRNA lptr = other->ptr; + RNA_property_boolean_set_array(&lptr, lprop, tmparray); + RNA_property_update(C, &lptr, lprop); + } + + MEM_freeN(tmparray); + + return; + } + } + } +# endif + + for (int i = 0; i < selctx_data->elems_len; i++) { + uiSelectContextElem *other = &selctx_data->elems[i]; + PointerRNA lptr = other->ptr; + + if (rna_type == PROP_FLOAT) { + float other_value = use_delta ? (other->val_f + delta.f) : delta.f; + CLAMP(other_value, min.f, max.f); + if (is_array) { + RNA_property_float_set_index(&lptr, lprop, index, other_value); + } + else { + RNA_property_float_set(&lptr, lprop, other_value); + } + } + else if (rna_type == PROP_INT) { + int other_value = use_delta ? (other->val_i + delta.i) : delta.i; + CLAMP(other_value, min.i, max.i); + if (is_array) { + RNA_property_int_set_index(&lptr, lprop, index, other_value); + } + else { + RNA_property_int_set(&lptr, lprop, other_value); + } + } + else if (rna_type == PROP_BOOLEAN) { + const bool other_value = delta.b; + if (is_array) { + RNA_property_boolean_set_index(&lptr, lprop, index, other_value); + } + else { + RNA_property_boolean_set(&lptr, lprop, delta.b); + } + } + else if (rna_type == PROP_ENUM) { + const int other_value = delta.i; + BLI_assert(!is_array); + RNA_property_enum_set(&lptr, lprop, other_value); + } + else if (rna_type == PROP_POINTER) { + const PointerRNA other_value = delta.p; + RNA_property_pointer_set(&lptr, lprop, other_value, NULL); + } + + RNA_property_update(C, &lptr, prop); + } + } +} + +#endif /* USE_ALLSELECT */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Drag + * \{ */ + +static bool ui_but_drag_init(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + /* prevent other WM gestures to start while we try to drag */ + WM_gestures_remove(CTX_wm_window(C)); + + /* Clamp the maximum to half the UI unit size so a high user preference + * doesn't require the user to drag more than half the default button height. */ + const int drag_threshold = min_ii( + WM_event_drag_threshold(event), + (int)((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block))); + + if (abs(data->dragstartx - event->x) + abs(data->dragstarty - event->y) > drag_threshold) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + data->cancel = true; +#ifdef USE_DRAG_TOGGLE + if (ui_drag_toggle_but_is_supported(but)) { + uiDragToggleHandle *drag_info = (uiDragToggleHandle *)MEM_callocN(sizeof(*drag_info), + __func__); + ARegion *region_prev; + + /* call here because regular mouse-up event wont run, + * typically 'button_activate_exit()' handles this */ + ui_apply_but_autokey(C, but); + + drag_info->pushed_state = ui_drag_toggle_but_pushed_state(C, but); + drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect); + drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect); + copy_v2_v2_int(drag_info->xy_init, &event->x); + copy_v2_v2_int(drag_info->xy_last, &event->x); + + /* needed for toggle drag on popups */ + region_prev = CTX_wm_region(C); + CTX_wm_region_set(C, data->region); + + WM_event_add_ui_handler(C, + &data->window->modalhandlers, + ui_handler_region_drag_toggle, + ui_handler_region_drag_toggle_remove, + drag_info, + WM_HANDLER_BLOCKING); + + CTX_wm_region_set(C, region_prev); + + /* Initialize alignment for single row/column regions, + * otherwise we use the relative position of the first other button dragged over. */ + if (ELEM(data->region->regiontype, + RGN_TYPE_NAV_BAR, + RGN_TYPE_HEADER, + RGN_TYPE_TOOL_HEADER, + RGN_TYPE_FOOTER)) { + const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment); + int lock_axis = -1; + + if (ELEM(region_alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) { + lock_axis = 0; + } + else if (ELEM(region_alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) { + lock_axis = 1; + } + if (lock_axis != -1) { + drag_info->xy_lock[lock_axis] = true; + drag_info->is_xy_lock_init = true; + } + } + } + else +#endif + if (but->type == UI_BTYPE_COLOR) { + bool valid = false; + uiDragColorHandle *drag_info = (uiDragColorHandle *)MEM_callocN(sizeof(*drag_info), + __func__); + + /* TODO support more button pointer types */ + if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + ui_but_v3_get(but, drag_info->color); + drag_info->gamma_corrected = true; + valid = true; + } + else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { + ui_but_v3_get(but, drag_info->color); + drag_info->gamma_corrected = false; + valid = true; + } + else if (ELEM(but->pointype, UI_BUT_POIN_FLOAT, UI_BUT_POIN_CHAR)) { + ui_but_v3_get(but, drag_info->color); + copy_v3_v3(drag_info->color, (float *)but->poin); + valid = true; + } + + if (valid) { + WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, 0.0, WM_DRAG_FREE_DATA); + } + else { + MEM_freeN(drag_info); + return false; + } + } + else { + wmDrag *drag = WM_event_start_drag( + C, + but->icon, + but->dragtype, + but->dragpoin, + ui_but_value_get(but), + (but->dragflag & UI_BUT_DRAGPOIN_FREE) ? WM_DRAG_FREE_DATA : WM_DRAG_NOP); + /* wmDrag has ownership over dragpoin now, stop messing with it. */ + but->dragpoin = NULL; + + if (but->imb) { + WM_event_drag_image(drag, + but->imb, + but->imb_scale, + BLI_rctf_size_x(&but->rect), + BLI_rctf_size_y(&but->rect)); + } + } + return true; + } + + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Apply + * \{ */ + +static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + ui_apply_but_func(C, but); + data->retval = but->retval; + data->applied = true; +} + +static void ui_apply_but( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive) +{ + const eButType but_type = but->type; /* Store as const to quiet maybe uninitialized warning. */ + + data->retval = 0; + + /* if we cancel and have not applied yet, there is nothing to do, + * otherwise we have to restore the original value again */ + if (data->cancel) { + if (!data->applied) { + return; + } + + if (data->str) { + MEM_freeN(data->str); + } + data->str = data->origstr; + data->origstr = NULL; + data->value = data->origvalue; + copy_v3_v3(data->vec, data->origvec); + /* postpone clearing origdata */ + } + else { + /* We avoid applying interactive edits a second time + * at the end with the #uiHandleButtonData.applied_interactive flag. */ + if (interactive) { + data->applied_interactive = true; + } + else if (data->applied_interactive) { + return; + } + +#ifdef USE_ALLSELECT +# ifdef USE_DRAG_MULTINUM + if (but->flag & UI_BUT_DRAG_MULTI) { + /* pass */ + } + else +# endif + if (data->select_others.elems_len == 0) { + wmWindow *win = CTX_wm_window(C); + /* may have been enabled before activating */ + if (data->select_others.is_enabled || IS_ALLSELECT_EVENT(win->eventstate)) { + ui_selectcontext_begin(C, but, &data->select_others); + data->select_others.is_enabled = true; + } + } + if (data->select_others.elems_len == 0) { + /* Don't check again. */ + data->select_others.elems_len = -1; + } +#endif + } + + /* ensures we are writing actual values */ + char *editstr = but->editstr; + double *editval = but->editval; + float *editvec = but->editvec; + ColorBand *editcoba; + CurveMapping *editcumap; + CurveProfile *editprofile; + if (but_type == UI_BTYPE_COLORBAND) { + uiButColorBand *but_coba = (uiButColorBand *)but; + editcoba = but_coba->edit_coba; + } + else if (but_type == UI_BTYPE_CURVE) { + uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; + editcumap = but_cumap->edit_cumap; + } + else if (but_type == UI_BTYPE_CURVEPROFILE) { + uiButCurveProfile *but_profile = (uiButCurveProfile *)but; + editprofile = but_profile->edit_profile; + } + but->editstr = NULL; + but->editval = NULL; + but->editvec = NULL; + if (but_type == UI_BTYPE_COLORBAND) { + uiButColorBand *but_coba = (uiButColorBand *)but; + but_coba->edit_coba = NULL; + } + else if (but_type == UI_BTYPE_CURVE) { + uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; + but_cumap->edit_cumap = NULL; + } + else if (but_type == UI_BTYPE_CURVEPROFILE) { + uiButCurveProfile *but_profile = (uiButCurveProfile *)but; + but_profile->edit_profile = NULL; + } + + /* handle different types */ + switch (but_type) { + case UI_BTYPE_BUT: + case UI_BTYPE_DECORATOR: + ui_apply_but_BUT(C, but, data); + break; + case UI_BTYPE_TEXT: + case UI_BTYPE_SEARCH_MENU: + ui_apply_but_TEX(C, but, data); + break; + case UI_BTYPE_BUT_TOGGLE: + case UI_BTYPE_TOGGLE: + case UI_BTYPE_TOGGLE_N: + case UI_BTYPE_ICON_TOGGLE: + case UI_BTYPE_ICON_TOGGLE_N: + case UI_BTYPE_CHECKBOX: + case UI_BTYPE_CHECKBOX_N: + ui_apply_but_TOG(C, but, data); + break; + case UI_BTYPE_ROW: + case UI_BTYPE_LISTROW: + ui_apply_but_ROW(C, block, but, data); + break; + case UI_BTYPE_TAB: + ui_apply_but_TAB(C, but, data); + break; + case UI_BTYPE_SCROLL: + case UI_BTYPE_GRIP: + case UI_BTYPE_NUM: + case UI_BTYPE_NUM_SLIDER: + ui_apply_but_NUM(C, but, data); + break; + case UI_BTYPE_MENU: + case UI_BTYPE_BLOCK: + case UI_BTYPE_PULLDOWN: + ui_apply_but_BLOCK(C, but, data); + break; + case UI_BTYPE_COLOR: + if (data->cancel) { + ui_apply_but_VEC(C, but, data); + } + else { + ui_apply_but_BLOCK(C, but, data); + } + break; + case UI_BTYPE_BUT_MENU: + ui_apply_but_BUTM(C, but, data); + break; + case UI_BTYPE_UNITVEC: + case UI_BTYPE_HSVCUBE: + case UI_BTYPE_HSVCIRCLE: + ui_apply_but_VEC(C, but, data); + break; + case UI_BTYPE_COLORBAND: + ui_apply_but_COLORBAND(C, but, data); + break; + case UI_BTYPE_CURVE: + ui_apply_but_CURVE(C, but, data); + break; + case UI_BTYPE_CURVEPROFILE: + ui_apply_but_CURVEPROFILE(C, but, data); + break; + case UI_BTYPE_KEY_EVENT: + case UI_BTYPE_HOTKEY_EVENT: + ui_apply_but_BUT(C, but, data); + break; + case UI_BTYPE_IMAGE: + ui_apply_but_IMAGE(C, but, data); + break; + case UI_BTYPE_HISTOGRAM: + ui_apply_but_HISTOGRAM(C, but, data); + break; + case UI_BTYPE_WAVEFORM: + ui_apply_but_WAVEFORM(C, but, data); + break; + case UI_BTYPE_TRACK_PREVIEW: + ui_apply_but_TRACKPREVIEW(C, but, data); + break; + default: + break; + } + +#ifdef USE_DRAG_MULTINUM + if (data->multi_data.has_mbuts) { + if ((data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_ENABLE) && + (data->multi_data.skip == false)) { + if (data->cancel) { + ui_multibut_restore(C, data, block); + } + else { + ui_multibut_states_apply(C, data, block); + } + } + } +#endif + +#ifdef USE_ALLSELECT + ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue); +#endif + + if (data->cancel) { + data->origvalue = 0.0; + zero_v3(data->origvec); + } + + but->editstr = editstr; + but->editval = editval; + but->editvec = editvec; + if (but_type == UI_BTYPE_COLORBAND) { + uiButColorBand *but_coba = (uiButColorBand *)but; + but_coba->edit_coba = editcoba; + } + else if (but_type == UI_BTYPE_CURVE) { + uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; + but_cumap->edit_cumap = editcumap; + } + else if (but_type == UI_BTYPE_CURVEPROFILE) { + uiButCurveProfile *but_profile = (uiButCurveProfile *)but; + but_profile->edit_profile = editprofile; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Drop Event + * \{ */ + +/* only call if event type is EVT_DROP */ +static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data) +{ + /* drop event type has listbase customdata by default */ + ListBase *drags = (ListBase *)event->customdata; + + LISTBASE_FOREACH (wmDrag *, wmd, drags) { + /* TODO asset dropping. */ + if (wmd->type == WM_DRAG_ID) { + /* align these types with UI_but_active_drop_name */ + if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + ID *id = WM_drag_get_local_ID(wmd, 0); + + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + + ui_textedit_string_set(but, data, id->name + 2); + + if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) { + but->changed = true; + ui_searchbox_update(C, data->searchbox, but, true); + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Copy & Paste + * \{ */ + +static void ui_but_get_pasted_text_from_clipboard(char **buf_paste, int *buf_len) +{ + /* get only first line even if the clipboard contains multiple lines */ + int length; + char *text = WM_clipboard_text_get_firstline(false, &length); + + if (text) { + *buf_paste = text; + *buf_len = length; + } + else { + *buf_paste = (char *)MEM_callocN(sizeof(char), __func__); + *buf_len = 0; + } +} + +static int get_but_property_array_length(uiBut *but) +{ + return RNA_property_array_length(&but->rnapoin, but->rnaprop); +} + +static void ui_but_set_float_array( + bContext *C, uiBut *but, uiHandleButtonData *data, float *values, int array_length) +{ + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + for (int i = 0; i < array_length; i++) { + RNA_property_float_set_index(&but->rnapoin, but->rnaprop, i, values[i]); + } + if (data) { + if (but->type == UI_BTYPE_UNITVEC) { + BLI_assert(array_length == 3); + copy_v3_v3(data->vec, values); + } + else { + data->value = values[but->rnaindex]; + } + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); +} + +static void float_array_to_string(float *values, + int array_length, + char *output, + int output_len_max) +{ + /* to avoid buffer overflow attacks; numbers are quite arbitrary */ + BLI_assert(output_len_max > 15); + output_len_max -= 10; + + int current_index = 0; + output[current_index] = '['; + current_index++; + + for (int i = 0; i < array_length; i++) { + int length = BLI_snprintf( + output + current_index, output_len_max - current_index, "%f", values[i]); + current_index += length; + + if (i < array_length - 1) { + if (current_index < output_len_max) { + output[current_index + 0] = ','; + output[current_index + 1] = ' '; + current_index += 2; + } + } + } + + output[current_index + 0] = ']'; + output[current_index + 1] = '\0'; +} + +static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_len_max) +{ + const int array_length = get_but_property_array_length(but); + Array values(array_length); + RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values.data()); + float_array_to_string(values.data(), array_length, output, output_len_max); +} + +static bool parse_float_array(char *text, float *values, int expected_length) +{ + /* can parse max 4 floats for now */ + BLI_assert(0 <= expected_length && expected_length <= 4); + + float v[5]; + const int actual_length = sscanf( + text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]); + + if (actual_length == expected_length) { + memcpy(values, v, sizeof(float) * expected_length); + return true; + } + return false; +} + +static void ui_but_paste_numeric_array(bContext *C, + uiBut *but, + uiHandleButtonData *data, + char *buf_paste) +{ + const int array_length = get_but_property_array_length(but); + if (array_length > 4) { + /* not supported for now */ + return; + } + + Array values(array_length); + + if (parse_float_array(buf_paste, values.data(), array_length)) { + ui_but_set_float_array(C, but, data, values.data(), array_length); + } + else { + WM_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]"); + } +} + +static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_len_max) +{ + /* Get many decimal places, then strip trailing zeros. + * note: too high values start to give strange results */ + ui_but_string_get_ex(but, output, output_len_max, UI_PRECISION_FLOAT_MAX, false, NULL); + BLI_str_rstrip_float_zero(output, '\0'); +} + +static void ui_but_paste_numeric_value(bContext *C, + uiBut *but, + uiHandleButtonData *data, + char *buf_paste) +{ + double value; + if (ui_but_string_eval_number(C, but, buf_paste, &value)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + data->value = value; + ui_but_string_set(C, but, buf_paste); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + WM_report(RPT_ERROR, "Expected a number"); + } +} + +static void ui_but_paste_normalized_vector(bContext *C, + uiBut *but, + uiHandleButtonData *data, + char *buf_paste) +{ + float xyz[3]; + if (parse_float_array(buf_paste, xyz, 3)) { + if (normalize_v3(xyz) == 0.0f) { + /* better set Z up then have a zero vector */ + xyz[2] = 1.0; + } + ui_but_set_float_array(C, but, data, xyz, 3); + } + else { + WM_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'"); + } +} + +static void ui_but_copy_color(uiBut *but, char *output, int output_len_max) +{ + float rgba[4]; + + if (but->rnaprop && get_but_property_array_length(but) == 4) { + rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); + } + else { + rgba[3] = 1.0f; + } + + ui_but_v3_get(but, rgba); + + /* convert to linear color to do compatible copy between gamma and non-gamma */ + if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + srgb_to_linearrgb_v3_v3(rgba, rgba); + } + + float_array_to_string(rgba, 4, output, output_len_max); +} + +static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste) +{ + float rgba[4]; + if (parse_float_array(buf_paste, rgba, 4)) { + if (but->rnaprop) { + /* Assume linear colors in buffer. */ + if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + linearrgb_to_srgb_v3_v3(rgba, rgba); + } + + /* Some color properties are RGB, not RGBA. */ + const int array_len = get_but_property_array_length(but); + BLI_assert(ELEM(array_len, 3, 4)); + ui_but_set_float_array(C, but, NULL, rgba, array_len); + } + } + else { + WM_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'"); + } +} + +static void ui_but_copy_text(uiBut *but, char *output, int output_len_max) +{ + ui_but_string_get(but, output, output_len_max); +} + +static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste) +{ + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + ui_textedit_string_set(but, but->active, buf_paste); + + if (but->type == UI_BTYPE_SEARCH_MENU) { + but->changed = true; + ui_searchbox_update(C, data->searchbox, but, true); + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); +} + +static void ui_but_copy_colorband(uiBut *but) +{ + if (but->poin != NULL) { + memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand)); + } +} + +static void ui_but_paste_colorband(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if (but_copypaste_coba.tot != 0) { + if (!but->poin) { + but->poin = (char *)MEM_callocN(sizeof(ColorBand), "colorband"); + } + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand)); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } +} + +static void ui_but_copy_curvemapping(uiBut *but) +{ + if (but->poin != NULL) { + but_copypaste_curve_alive = true; + BKE_curvemapping_free_data(&but_copypaste_curve); + BKE_curvemapping_copy_data(&but_copypaste_curve, (CurveMapping *)but->poin); + } +} + +static void ui_but_paste_curvemapping(bContext *C, uiBut *but) +{ + if (but_copypaste_curve_alive) { + if (!but->poin) { + but->poin = (char *)MEM_callocN(sizeof(CurveMapping), "curvemapping"); + } + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + BKE_curvemapping_free_data((CurveMapping *)but->poin); + BKE_curvemapping_copy_data((CurveMapping *)but->poin, &but_copypaste_curve); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } +} + +static void ui_but_copy_CurveProfile(uiBut *but) +{ + if (but->poin != NULL) { + but_copypaste_profile_alive = true; + BKE_curveprofile_free_data(&but_copypaste_profile); + BKE_curveprofile_copy_data(&but_copypaste_profile, (CurveProfile *)but->poin); + } +} + +static void ui_but_paste_CurveProfile(bContext *C, uiBut *but) +{ + if (but_copypaste_profile_alive) { + if (!but->poin) { + but->poin = (char *)MEM_callocN(sizeof(CurveProfile), "CurveProfile"); + } + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + BKE_curveprofile_free_data((CurveProfile *)but->poin); + BKE_curveprofile_copy_data((CurveProfile *)but->poin, &but_copypaste_profile); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } +} + +static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_len_max) +{ + PointerRNA *opptr = UI_but_operator_ptr_get(but); + + char *str; + str = WM_operator_pystring_ex(C, NULL, false, true, but->optype, opptr); + BLI_strncpy(output, str, output_len_max); + MEM_freeN(str); +} + +static bool ui_but_copy_menu(uiBut *but, char *output, int output_len_max) +{ + MenuType *mt = UI_but_menutype_get(but); + if (mt) { + BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname); + return true; + } + return false; +} + +static bool ui_but_copy_popover(uiBut *but, char *output, int output_len_max) +{ + PanelType *pt = UI_but_paneltype_get(but); + if (pt) { + BLI_snprintf(output, output_len_max, "bpy.ops.wm.call_panel(name=\"%s\")", pt->idname); + return true; + } + return false; +} + +static void ui_but_copy(bContext *C, uiBut *but, const bool copy_array) +{ + if (ui_but_contains_password(but)) { + return; + } + + /* Arbitrary large value (allow for paths: 'PATH_MAX') */ + char buf[4096] = {0}; + const int buf_max_len = sizeof(buf); + + /* Left false for copying internal data (color-band for eg). */ + bool is_buf_set = false; + + const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL); + + switch (but->type) { + case UI_BTYPE_NUM: + case UI_BTYPE_NUM_SLIDER: + if (!has_required_data) { + break; + } + if (copy_array && ui_but_has_array_value(but)) { + ui_but_copy_numeric_array(but, buf, buf_max_len); + } + else { + ui_but_copy_numeric_value(but, buf, buf_max_len); + } + is_buf_set = true; + break; + + case UI_BTYPE_UNITVEC: + if (!has_required_data) { + break; + } + ui_but_copy_numeric_array(but, buf, buf_max_len); + is_buf_set = true; + break; + + case UI_BTYPE_COLOR: + if (!has_required_data) { + break; + } + ui_but_copy_color(but, buf, buf_max_len); + is_buf_set = true; + break; + + case UI_BTYPE_TEXT: + case UI_BTYPE_SEARCH_MENU: + if (!has_required_data) { + break; + } + ui_but_copy_text(but, buf, buf_max_len); + is_buf_set = true; + break; + + case UI_BTYPE_COLORBAND: + ui_but_copy_colorband(but); + break; + + case UI_BTYPE_CURVE: + ui_but_copy_curvemapping(but); + break; + + case UI_BTYPE_CURVEPROFILE: + ui_but_copy_CurveProfile(but); + break; + + case UI_BTYPE_BUT: + if (!but->optype) { + break; + } + ui_but_copy_operator(C, but, buf, buf_max_len); + is_buf_set = true; + break; + + case UI_BTYPE_MENU: + case UI_BTYPE_PULLDOWN: + if (ui_but_copy_menu(but, buf, buf_max_len)) { + is_buf_set = true; + } + break; + case UI_BTYPE_POPOVER: + if (ui_but_copy_popover(but, buf, buf_max_len)) { + is_buf_set = true; + } + break; + + default: + break; + } + + if (is_buf_set) { + WM_clipboard_text_set(buf, 0); + } +} + +static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array) +{ + BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */ + + int buf_paste_len = 0; + char *buf_paste; + ui_but_get_pasted_text_from_clipboard(&buf_paste, &buf_paste_len); + + const bool has_required_data = !(but->poin == NULL && but->rnapoin.data == NULL); + + switch (but->type) { + case UI_BTYPE_NUM: + case UI_BTYPE_NUM_SLIDER: + if (!has_required_data) { + break; + } + if (paste_array && ui_but_has_array_value(but)) { + ui_but_paste_numeric_array(C, but, data, buf_paste); + } + else { + ui_but_paste_numeric_value(C, but, data, buf_paste); + } + break; + + case UI_BTYPE_UNITVEC: + if (!has_required_data) { + break; + } + ui_but_paste_normalized_vector(C, but, data, buf_paste); + break; + + case UI_BTYPE_COLOR: + if (!has_required_data) { + break; + } + ui_but_paste_color(C, but, buf_paste); + break; + + case UI_BTYPE_TEXT: + case UI_BTYPE_SEARCH_MENU: + if (!has_required_data) { + break; + } + ui_but_paste_text(C, but, data, buf_paste); + break; + + case UI_BTYPE_COLORBAND: + ui_but_paste_colorband(C, but, data); + break; + + case UI_BTYPE_CURVE: + ui_but_paste_curvemapping(C, but); + break; + + case UI_BTYPE_CURVEPROFILE: + ui_but_paste_CurveProfile(C, but); + break; + + default: + break; + } + + MEM_freeN((void *)buf_paste); +} + +void ui_but_clipboard_free(void) +{ + BKE_curvemapping_free_data(&but_copypaste_curve); + BKE_curveprofile_free_data(&but_copypaste_profile); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Text Password + * + * Functions to convert password strings that should not be displayed + * to asterisk representation (e.g. 'mysecretpasswd' -> '*************') + * + * It converts every UTF-8 character to an asterisk, and also remaps + * the cursor position and selection start/end. + * + * \note remapping is used, because password could contain UTF-8 characters. + * + * \{ */ + +static int ui_text_position_from_hidden(uiBut *but, int pos) +{ + const char *butstr = (but->editstr) ? but->editstr : but->drawstr; + const char *strpos = butstr; + for (int i = 0; i < pos; i++) { + strpos = BLI_str_find_next_char_utf8(strpos, NULL); + } + + return (strpos - butstr); +} + +static int ui_text_position_to_hidden(uiBut *but, int pos) +{ + const char *butstr = (but->editstr) ? but->editstr : but->drawstr; + return BLI_strnlen_utf8(butstr, pos); +} + +void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], + uiBut *but, + const bool restore) +{ + if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) { + return; + } + + char *butstr = (but->editstr) ? but->editstr : but->drawstr; + + if (restore) { + /* restore original string */ + BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR); + + /* remap cursor positions */ + if (but->pos >= 0) { + but->pos = ui_text_position_from_hidden(but, but->pos); + but->selsta = ui_text_position_from_hidden(but, but->selsta); + but->selend = ui_text_position_from_hidden(but, but->selend); + } + } + else { + /* convert text to hidden text using asterisks (e.g. pass -> ****) */ + const size_t len = BLI_strlen_utf8(butstr); + + /* remap cursor positions */ + if (but->pos >= 0) { + but->pos = ui_text_position_to_hidden(but, but->pos); + but->selsta = ui_text_position_to_hidden(but, but->selsta); + but->selend = ui_text_position_to_hidden(but, but->selend); + } + + /* save original string */ + BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR); + memset(butstr, '*', len); + butstr[len] = '\0'; + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Text Selection/Editing + * \{ */ + +void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but) +{ + if (!but->active) { + return; + } + + /* most likely NULL, but let's check, and give it temp zero string */ + if (!but->active->str) { + but->active->str = (char *)MEM_callocN(1, "temp str"); + } + but->active->str[0] = 0; + + ui_apply_but_TEX(C, but, but->active); + button_activate_state(C, but, BUTTON_STATE_EXIT); +} + +static void ui_textedit_string_ensure_max_length(uiBut *but, uiHandleButtonData *data, int maxlen) +{ + BLI_assert(data->is_str_dynamic); + BLI_assert(data->str == but->editstr); + + if (maxlen > data->maxlen) { + data->str = but->editstr = (char *)MEM_reallocN(data->str, sizeof(char) * maxlen); + data->maxlen = maxlen; + } +} + +static void ui_textedit_string_set(uiBut *but, uiHandleButtonData *data, const char *str) +{ + if (data->is_str_dynamic) { + ui_textedit_string_ensure_max_length(but, data, strlen(str) + 1); + } + + if (UI_but_is_utf8(but)) { + BLI_strncpy_utf8(data->str, str, data->maxlen); + } + else { + BLI_strncpy(data->str, str, data->maxlen); + } +} + +static bool ui_textedit_delete_selection(uiBut *but, uiHandleButtonData *data) +{ + char *str = data->str; + const int len = strlen(str); + bool changed = false; + if (but->selsta != but->selend && len) { + memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1); + changed = true; + } + + but->pos = but->selend = but->selsta; + return changed; +} + +static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str), + const size_t str_step_ofs, + const rcti *glyph_step_bounds, + const int UNUSED(glyph_advance_x), + const rctf *glyph_bounds, + const int UNUSED(glyph_bearing[2]), + void *user_data) +{ + int *cursor_data = (int *)user_data; + const float center = glyph_step_bounds->xmin + (BLI_rctf_size_x(glyph_bounds) / 2.0f); + if (cursor_data[0] < center) { + cursor_data[1] = str_step_ofs; + return false; + } + return true; +} + +/** + * \param x: Screen space cursor location - #wmEvent.x + * + * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too. + */ +static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, const float x) +{ + /* XXX pass on as arg. */ + uiFontStyle fstyle = UI_style_get()->widget; + const float aspect = but->block->aspect; + + float startx = but->rect.xmin; + float starty_dummy = 0.0f; + char password_str[UI_MAX_PASSWORD_STR]; + /* treat 'str_last' as null terminator for str, no need to modify in-place */ + const char *str = but->editstr, *str_last; + + ui_block_to_window_fl(data->region, but->block, &startx, &starty_dummy); + + ui_fontscale(&fstyle.points, aspect); + + UI_fontstyle_set(&fstyle); + + if (fstyle.kerning == 1) { + /* for BLF_width */ + BLF_enable(fstyle.uifont_id, BLF_KERNING_DEFAULT); + } + + ui_but_text_password_hide(password_str, but, false); + + if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + if (but->flag & UI_HAS_ICON) { + startx += UI_DPI_ICON_SIZE / aspect; + } + } + startx += (UI_TEXT_MARGIN_X * U.widget_unit) / aspect; + + /* mouse dragged outside the widget to the left */ + if (x < startx) { + int i = but->ofs; + + str_last = &str[but->ofs]; + + while (i > 0) { + if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) { + /* 0.25 == scale factor for less sensitivity */ + if (BLF_width(fstyle.uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) { + break; + } + } + else { + break; /* unlikely but possible */ + } + } + but->ofs = i; + but->pos = but->ofs; + } + /* mouse inside the widget, mouse coords mapped in widget space */ + else { + str_last = &str[but->ofs]; + const int str_last_len = strlen(str_last); + const int x_pos = (int)(x - startx); + int glyph_data[2] = { + x_pos, /* horizontal position to test. */ + -1, /* Write the character offset here. */ + }; + BLF_boundbox_foreach_glyph(fstyle.uifont_id, + str + but->ofs, + INT_MAX, + ui_textedit_set_cursor_pos_foreach_glyph, + glyph_data); + /* If value untouched then we are to the right. */ + if (glyph_data[1] == -1) { + glyph_data[1] = str_last_len; + } + but->pos = glyph_data[1] + but->ofs; + } + + if (fstyle.kerning == 1) { + BLF_disable(fstyle.uifont_id, BLF_KERNING_DEFAULT); + } + + ui_but_text_password_hide(password_str, but, true); +} + +static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x) +{ + ui_textedit_set_cursor_pos(but, data, x); + + but->selsta = but->pos; + but->selend = data->sel_pos_init; + if (but->selend < but->selsta) { + SWAP(short, but->selsta, but->selend); + } + + ui_but_update(but); +} + +/** + * This is used for both utf8 and ascii + * + * For unicode buttons, \a buf is treated as unicode. + */ +static bool ui_textedit_insert_buf(uiBut *but, + uiHandleButtonData *data, + const char *buf, + int buf_len) +{ + int len = strlen(data->str); + const int len_new = len - (but->selend - but->selsta) + 1; + bool changed = false; + + if (data->is_str_dynamic) { + ui_textedit_string_ensure_max_length(but, data, len_new + buf_len); + } + + if (len_new <= data->maxlen) { + char *str = data->str; + size_t step = buf_len; + + /* type over the current selection */ + if ((but->selend - but->selsta) > 0) { + changed = ui_textedit_delete_selection(but, data); + len = strlen(str); + } + + if ((len + step >= data->maxlen) && (data->maxlen - (len + 1) > 0)) { + if (UI_but_is_utf8(but)) { + /* shorten 'step' to a utf8 aligned size that fits */ + BLI_strnlen_utf8_ex(buf, data->maxlen - (len + 1), &step); + } + else { + step = data->maxlen - (len + 1); + } + } + + if (step && (len + step < data->maxlen)) { + memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos); + memcpy(&str[but->pos], buf, step * sizeof(char)); + but->pos += step; + changed = true; + } + } + + return changed; +} + +static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, char ascii) +{ + const char buf[2] = {ascii, '\0'}; + + if (UI_but_is_utf8(but) && (BLI_str_utf8_size(buf) == -1)) { + printf( + "%s: entering invalid ascii char into an ascii key (%d)\n", __func__, (int)(uchar)ascii); + + return false; + } + + /* in some cases we want to allow invalid utf8 chars */ + return ui_textedit_insert_buf(but, data, buf, 1); +} + +static void ui_textedit_move(uiBut *but, + uiHandleButtonData *data, + eStrCursorJumpDirection direction, + const bool select, + eStrCursorJumpType jump) +{ + const char *str = data->str; + const int len = strlen(str); + const int pos_prev = but->pos; + const bool has_sel = (but->selend - but->selsta) > 0; + + ui_but_update(but); + + /* special case, quit selection and set cursor */ + if (has_sel && !select) { + if (jump == STRCUR_JUMP_ALL) { + but->selsta = but->selend = but->pos = direction ? len : 0; + } + else { + if (direction) { + but->selsta = but->pos = but->selend; + } + else { + but->pos = but->selend = but->selsta; + } + } + data->sel_pos_init = but->pos; + } + else { + int pos_i = but->pos; + BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true); + but->pos = pos_i; + + if (select) { + if (has_sel == false) { + data->sel_pos_init = pos_prev; + } + but->selsta = but->pos; + but->selend = data->sel_pos_init; + } + if (but->selend < but->selsta) { + SWAP(short, but->selsta, but->selend); + } + } +} + +static bool ui_textedit_delete(uiBut *but, + uiHandleButtonData *data, + eStrCursorJumpDirection direction, + eStrCursorJumpType jump) +{ + char *str = data->str; + const int len = strlen(str); + + bool changed = false; + + if (jump == STRCUR_JUMP_ALL) { + if (len) { + changed = true; + } + str[0] = '\0'; + but->pos = 0; + } + else if (direction) { /* delete */ + if ((but->selend - but->selsta) > 0) { + changed = ui_textedit_delete_selection(but, data); + } + else if (but->pos >= 0 && but->pos < len) { + int pos = but->pos; + int step; + BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true); + step = pos - but->pos; + memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step)); + changed = true; + } + } + else { /* backspace */ + if (len != 0) { + if ((but->selend - but->selsta) > 0) { + changed = ui_textedit_delete_selection(but, data); + } + else if (but->pos > 0) { + int pos = but->pos; + int step; + + BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true); + step = but->pos - pos; + memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos); + but->pos -= step; + changed = true; + } + } + } + + return changed; +} + +static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + char *str = data->str; + + int changed; + if (data->searchbox) { + changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->str); + } + else { + changed = but->autocomplete_func(C, str, but->autofunc_arg); + } + + but->pos = strlen(str); + but->selsta = but->selend = but->pos; + + return changed; +} + +/* mode for ui_textedit_copypaste() */ +enum { + UI_TEXTEDIT_PASTE = 1, + UI_TEXTEDIT_COPY, + UI_TEXTEDIT_CUT, +}; + +static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const int mode) +{ + bool changed = false; + + /* paste */ + if (mode == UI_TEXTEDIT_PASTE) { + /* extract the first line from the clipboard */ + int buf_len; + char *pbuf = WM_clipboard_text_get_firstline(false, &buf_len); + + if (pbuf) { + if (UI_but_is_utf8(but)) { + buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len); + } + + ui_textedit_insert_buf(but, data, pbuf, buf_len); + + changed = true; + + MEM_freeN(pbuf); + } + } + /* cut & copy */ + else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) { + /* copy the contents to the copypaste buffer */ + const int sellen = but->selend - but->selsta; + char *buf = (char *)MEM_mallocN(sizeof(char) * (sellen + 1), "ui_textedit_copypaste"); + + BLI_strncpy(buf, data->str + but->selsta, sellen + 1); + WM_clipboard_text_set(buf, 0); + MEM_freeN(buf); + + /* for cut only, delete the selection afterwards */ + if (mode == UI_TEXTEDIT_CUT) { + if ((but->selend - but->selsta) > 0) { + changed = ui_textedit_delete_selection(but, data); + } + } + } + + return changed; +} + +#ifdef WITH_INPUT_IME +/* enable ime, and set up uibut ime data */ +static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but)) +{ + /* XXX Is this really needed? */ + int x, y; + + BLI_assert(win->ime_data == NULL); + + /* enable IME and position to cursor, it's a trick */ + x = win->eventstate->x; + /* flip y and move down a bit, prevent the IME panel cover the edit button */ + y = win->eventstate->y - 12; + + wm_window_IME_begin(win, x, y, 0, 0, true); +} + +/* disable ime, and clear uibut ime data */ +static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but)) +{ + wm_window_IME_end(win); +} + +void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete) +{ + BLI_assert(but->active); + + ui_region_to_window(but->active->region, &x, &y); + wm_window_IME_begin(but->active->window, x, y - 4, 0, 0, complete); +} + +wmIMEData *ui_but_ime_data_get(uiBut *but) +{ + if (but->active && but->active->window) { + return but->active->window->ime_data; + } + else { + return NULL; + } +} +#endif /* WITH_INPUT_IME */ + +static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + wmWindow *win = data->window; + const bool is_num_but = ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER); + bool no_zero_strip = false; + + if (data->str) { + MEM_freeN(data->str); + data->str = NULL; + } + +#ifdef USE_DRAG_MULTINUM + /* this can happen from multi-drag */ + if (data->applied_interactive) { + /* remove any small changes so canceling edit doesn't restore invalid value: T40538 */ + data->cancel = true; + ui_apply_but(C, but->block, but, data, true); + data->cancel = false; + + data->applied_interactive = false; + } +#endif + +#ifdef USE_ALLSELECT + if (is_num_but) { + if (IS_ALLSELECT_EVENT(win->eventstate)) { + data->select_others.is_enabled = true; + data->select_others.is_copy = true; + } + } +#endif + + /* retrieve string */ + data->maxlen = ui_but_string_get_max_length(but); + if (data->maxlen != 0) { + data->str = (char *)MEM_callocN(sizeof(char) * data->maxlen, "textedit str"); + /* We do not want to truncate precision to default here, it's nice to show value, + * not to edit it - way too much precision is lost then. */ + ui_but_string_get_ex( + but, data->str, data->maxlen, UI_PRECISION_FLOAT_MAX, true, &no_zero_strip); + } + else { + data->is_str_dynamic = true; + data->str = ui_but_string_get_dynamic(but, &data->maxlen); + } + + if (ui_but_is_float(but) && !ui_but_is_unit(but) && !ui_but_anim_expression_get(but, NULL, 0) && + !no_zero_strip) { + BLI_str_rstrip_float_zero(data->str, '\0'); + } + + if (is_num_but) { + BLI_assert(data->is_str_dynamic == false); + ui_but_convert_to_unit_alt_name(but, data->str, data->maxlen); + } + + /* won't change from now on */ + const int len = strlen(data->str); + + data->origstr = BLI_strdupn(data->str, len); + data->sel_pos_init = 0; + + /* set cursor pos to the end of the text */ + but->editstr = data->str; + but->pos = len; + but->selsta = 0; + but->selend = len; + + /* Initialize undo history tracking. */ + data->undo_stack_text = ui_textedit_undo_stack_create(); + ui_textedit_undo_push(data->undo_stack_text, but->editstr, but->pos); + + /* optional searchbox */ + if (but->type == UI_BTYPE_SEARCH_MENU) { + uiButSearch *search_but = (uiButSearch *)but; + + data->searchbox = search_but->popup_create_fn(C, data->region, search_but); + ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */ + } + + /* reset alert flag (avoid confusion, will refresh on exit) */ + but->flag &= ~UI_BUT_REDALERT; + + ui_but_update(but); + + WM_cursor_modal_set(win, WM_CURSOR_TEXT_EDIT); + +#ifdef WITH_INPUT_IME + if (is_num_but == false && BLT_lang_is_ime_supported()) { + ui_textedit_ime_begin(win, but); + } +#endif +} + +static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + wmWindow *win = data->window; + + if (but) { + if (UI_but_is_utf8(but)) { + const int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr)); + /* not a file?, strip non utf-8 chars */ + if (strip) { + /* wont happen often so isn't that annoying to keep it here for a while */ + printf("%s: invalid utf8 - stripped chars %d\n", __func__, strip); + } + } + + if (data->searchbox) { + if (data->cancel == false) { + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + uiButSearch *but_search = (uiButSearch *)but; + + if ((ui_searchbox_apply(but, data->searchbox) == false) && + (ui_searchbox_find_index(data->searchbox, but->editstr) == -1) && + !but_search->results_are_suggestions) { + data->cancel = true; + + /* ensure menu (popup) too is closed! */ + data->escapecancel = true; + + WM_reportf(RPT_ERROR, "Failed to find '%s'", but->editstr); + WM_report_banner_show(); + } + } + + ui_searchbox_free(C, data->searchbox); + data->searchbox = NULL; + } + + but->editstr = NULL; + but->pos = -1; + } + + WM_cursor_modal_restore(win); + + /* Free text undo history text blocks. */ + ui_textedit_undo_stack_destroy(data->undo_stack_text); + data->undo_stack_text = NULL; + +#ifdef WITH_INPUT_IME + if (win->ime_data) { + ui_textedit_ime_end(win, but); + } +#endif +} + +static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data) +{ + /* label and roundbox can overlap real buttons (backdrops...) */ + if (ELEM(actbut->type, + UI_BTYPE_LABEL, + UI_BTYPE_SEPR, + UI_BTYPE_SEPR_LINE, + UI_BTYPE_ROUNDBOX, + UI_BTYPE_LISTBOX)) { + return; + } + + for (uiBut *but = actbut->next; but; but = but->next) { + if (ui_but_is_editable_as_text(but)) { + if (!(but->flag & UI_BUT_DISABLED)) { + data->postbut = but; + data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } + } + for (uiBut *but = (uiBut *)block->buttons.first; but != actbut; but = but->next) { + if (ui_but_is_editable_as_text(but)) { + if (!(but->flag & UI_BUT_DISABLED)) { + data->postbut = but; + data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } + } +} + +static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data) +{ + /* label and roundbox can overlap real buttons (backdrops...) */ + if (ELEM(actbut->type, + UI_BTYPE_LABEL, + UI_BTYPE_SEPR, + UI_BTYPE_SEPR_LINE, + UI_BTYPE_ROUNDBOX, + UI_BTYPE_LISTBOX)) { + return; + } + + for (uiBut *but = actbut->prev; but; but = but->prev) { + if (ui_but_is_editable_as_text(but)) { + if (!(but->flag & UI_BUT_DISABLED)) { + data->postbut = but; + data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } + } + for (uiBut *but = (uiBut *)block->buttons.last; but != actbut; but = but->prev) { + if (ui_but_is_editable_as_text(but)) { + if (!(but->flag & UI_BUT_DISABLED)) { + data->postbut = but; + data->posttype = BUTTON_ACTIVATE_TEXT_EDITING; + return; + } + } + } +} + +static void ui_do_but_textedit( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int retval = WM_UI_HANDLER_CONTINUE; + bool changed = false, inbox = false, update = false, skip_undo_push = false; + +#ifdef WITH_INPUT_IME + wmWindow *win = CTX_wm_window(C); + wmIMEData *ime_data = win->ime_data; + const bool is_ime_composing = ime_data && ime_data->is_ime_composing; +#else + const bool is_ime_composing = false; +#endif + + switch (event->type) { + case MOUSEMOVE: + case MOUSEPAN: + if (data->searchbox) { +#ifdef USE_KEYNAV_LIMIT + if ((event->type == MOUSEMOVE) && + ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event)) { + /* pass */ + } + else { + ui_searchbox_event(C, data->searchbox, but, data->region, event); + } +#else + ui_searchbox_event(C, data->searchbox, but, data->region, event); +#endif + } + ui_do_but_extra_operator_icons_mousemove(but, data, event); + + break; + case RIGHTMOUSE: + case EVT_ESCKEY: + if (event->val == KM_PRESS) { + /* Support search context menu. */ + if (event->type == RIGHTMOUSE) { + if (data->searchbox) { + if (ui_searchbox_event(C, data->searchbox, but, data->region, event)) { + /* Only break if the event was handled. */ + break; + } + } + } + +#ifdef WITH_INPUT_IME + /* skips button handling since it is not wanted */ + if (is_ime_composing) { + break; + } +#endif + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + } + break; + case LEFTMOUSE: { + /* Allow clicks on extra icons while editing. */ + if (ui_do_but_extra_operator_icon(C, but, data, event)) { + break; + } + + const bool had_selection = but->selsta != but->selend; + + /* exit on LMB only on RELEASE for searchbox, to mimic other popups, + * and allow multiple menu levels */ + if (data->searchbox) { + inbox = ui_searchbox_inside(data->searchbox, event->x, event->y); + } + + /* for double click: we do a press again for when you first click on button + * (selects all text, no cursor pos) */ + if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { + float mx = event->x; + float my = event->y; + ui_window_to_block_fl(data->region, block, &mx, &my); + + if (ui_but_contains_pt(but, mx, my)) { + ui_textedit_set_cursor_pos(but, data, event->x); + but->selsta = but->selend = but->pos; + data->sel_pos_init = but->pos; + + button_activate_state(C, but, BUTTON_STATE_TEXT_SELECTING); + retval = WM_UI_HANDLER_BREAK; + } + else if (inbox == false) { + /* if searchbox, click outside will cancel */ + if (data->searchbox) { + data->cancel = data->escapecancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + } + } + + /* only select a word in button if there was no selection before */ + if (event->val == KM_DBL_CLICK && had_selection == false) { + ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_DELIM); + ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_DELIM); + retval = WM_UI_HANDLER_BREAK; + changed = true; + } + else if (inbox) { + /* if we allow activation on key press, + * it gives problems launching operators T35713. */ + if (event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + } + } + break; + } + } + + if (event->val == KM_PRESS && !is_ime_composing) { + switch (event->type) { + case EVT_VKEY: + case EVT_XKEY: + case EVT_CKEY: + if (IS_EVENT_MOD(event, ctrl, oskey)) { + if (event->type == EVT_VKEY) { + changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_PASTE); + } + else if (event->type == EVT_CKEY) { + changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_COPY); + } + else if (event->type == EVT_XKEY) { + changed = ui_textedit_copypaste(but, data, UI_TEXTEDIT_CUT); + } + + retval = WM_UI_HANDLER_BREAK; + } + break; + case EVT_RIGHTARROWKEY: + ui_textedit_move(but, + data, + STRCUR_DIR_NEXT, + event->shift != 0, + event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); + retval = WM_UI_HANDLER_BREAK; + break; + case EVT_LEFTARROWKEY: + ui_textedit_move(but, + data, + STRCUR_DIR_PREV, + event->shift != 0, + event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); + retval = WM_UI_HANDLER_BREAK; + break; + case WHEELDOWNMOUSE: + case EVT_DOWNARROWKEY: + if (data->searchbox) { +#ifdef USE_KEYNAV_LIMIT + ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event); +#endif + ui_searchbox_event(C, data->searchbox, but, data->region, event); + break; + } + if (event->type == WHEELDOWNMOUSE) { + break; + } + ATTR_FALLTHROUGH; + case EVT_ENDKEY: + ui_textedit_move(but, data, STRCUR_DIR_NEXT, event->shift != 0, STRCUR_JUMP_ALL); + retval = WM_UI_HANDLER_BREAK; + break; + case WHEELUPMOUSE: + case EVT_UPARROWKEY: + if (data->searchbox) { +#ifdef USE_KEYNAV_LIMIT + ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event); +#endif + ui_searchbox_event(C, data->searchbox, but, data->region, event); + break; + } + if (event->type == WHEELUPMOUSE) { + break; + } + ATTR_FALLTHROUGH; + case EVT_HOMEKEY: + ui_textedit_move(but, data, STRCUR_DIR_PREV, event->shift != 0, STRCUR_JUMP_ALL); + retval = WM_UI_HANDLER_BREAK; + break; + case EVT_PADENTER: + case EVT_RETKEY: + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + break; + case EVT_DELKEY: + changed = ui_textedit_delete( + but, data, STRCUR_DIR_NEXT, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); + retval = WM_UI_HANDLER_BREAK; + break; + + case EVT_BACKSPACEKEY: + changed = ui_textedit_delete( + but, data, STRCUR_DIR_PREV, event->ctrl ? STRCUR_JUMP_DELIM : STRCUR_JUMP_NONE); + retval = WM_UI_HANDLER_BREAK; + break; + + case EVT_AKEY: + + /* Ctrl-A: Select all. */ +#if defined(__APPLE__) + /* OSX uses Command-A system-wide, so add it. */ + if ((event->oskey && !IS_EVENT_MOD(event, shift, alt, ctrl)) || + (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey))) +#else + if (event->ctrl && !IS_EVENT_MOD(event, shift, alt, oskey)) +#endif + { + ui_textedit_move(but, data, STRCUR_DIR_PREV, false, STRCUR_JUMP_ALL); + ui_textedit_move(but, data, STRCUR_DIR_NEXT, true, STRCUR_JUMP_ALL); + retval = WM_UI_HANDLER_BREAK; + } + break; + + case EVT_TABKEY: + /* There is a key conflict here, we can't tab with auto-complete. */ + if (but->autocomplete_func || data->searchbox) { + const int autocomplete = ui_textedit_autocomplete(C, but, data); + changed = autocomplete != AUTOCOMPLETE_NO_MATCH; + + if (autocomplete == AUTOCOMPLETE_FULL_MATCH) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (!IS_EVENT_MOD(event, ctrl, alt, oskey)) { + /* Use standard keys for cycling through buttons Tab, Shift-Tab to reverse. */ + if (event->shift) { + ui_textedit_prev_but(block, but, data); + } + else { + ui_textedit_next_but(block, but, data); + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + retval = WM_UI_HANDLER_BREAK; + break; + case EVT_ZKEY: { + /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */ + + const bool is_redo = (event->shift != 0); + if ( +#if defined(__APPLE__) + (event->oskey && !IS_EVENT_MOD(event, alt, ctrl)) || +#endif + (event->ctrl && !IS_EVENT_MOD(event, alt, oskey))) { + int undo_pos; + const char *undo_str = ui_textedit_undo( + data->undo_stack_text, is_redo ? 1 : -1, &undo_pos); + if (undo_str != NULL) { + ui_textedit_string_set(but, data, undo_str); + + /* Set the cursor & clear selection. */ + but->pos = undo_pos; + but->selsta = but->pos; + but->selend = but->pos; + changed = true; + } + retval = WM_UI_HANDLER_BREAK; + skip_undo_push = true; + } + break; + } + } + + if ((event->ascii || event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE) +#ifdef WITH_INPUT_IME + && !is_ime_composing && (!WM_event_is_ime_switch(event) || !BLT_lang_is_ime_supported()) +#endif + ) { + char ascii = event->ascii; + const char *utf8_buf = event->utf8_buf; + + /* exception that's useful for number buttons, some keyboard + * numpads have a comma instead of a period */ + if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER)) { /* could use data->min*/ + if (event->type == EVT_PADPERIOD && ascii == ',') { + ascii = '.'; + utf8_buf = NULL; /* force ascii fallback */ + } + } + + if (utf8_buf && utf8_buf[0]) { + const int utf8_buf_len = BLI_str_utf8_size(utf8_buf); + BLI_assert(utf8_buf_len != -1); + changed = ui_textedit_insert_buf(but, data, event->utf8_buf, utf8_buf_len); + } + else { + changed = ui_textedit_insert_ascii(but, data, ascii); + } + + retval = WM_UI_HANDLER_BREAK; + } + /* textbutton with this flag: do live update (e.g. for search buttons) */ + if (but->flag & UI_BUT_TEXTEDIT_UPDATE) { + update = true; + } + } + +#ifdef WITH_INPUT_IME + if (event->type == WM_IME_COMPOSITE_START || event->type == WM_IME_COMPOSITE_EVENT) { + changed = true; + + if (event->type == WM_IME_COMPOSITE_START && but->selend > but->selsta) { + ui_textedit_delete_selection(but, data); + } + if (event->type == WM_IME_COMPOSITE_EVENT && ime_data->result_len) { + ui_textedit_insert_buf(but, data, ime_data->str_result, ime_data->result_len); + } + } + else if (event->type == WM_IME_COMPOSITE_END) { + changed = true; + } +#endif + + if (changed) { + /* The undo stack may be NULL if an event exits editing. */ + if ((skip_undo_push == false) && (data->undo_stack_text != NULL)) { + ui_textedit_undo_push(data->undo_stack_text, data->str, but->pos); + } + + /* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */ + if (update && data->interactive) { + ui_apply_but(C, block, but, data, true); + } + else { + ui_but_update_edited(but); + } + but->changed = true; + + if (data->searchbox) { + ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */ + } + } + + if (changed || (retval == WM_UI_HANDLER_BREAK)) { + ED_region_tag_redraw(data->region); + } +} + +static void ui_do_but_textedit_select( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int retval = WM_UI_HANDLER_CONTINUE; + + switch (event->type) { + case MOUSEMOVE: { + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + ui_textedit_set_cursor_select(but, data, event->x); + retval = WM_UI_HANDLER_BREAK; + break; + } + case LEFTMOUSE: + if (event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + retval = WM_UI_HANDLER_BREAK; + break; + } + + if (retval == WM_UI_HANDLER_BREAK) { + ui_but_update(but); + ED_region_tag_redraw(data->region); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Number Editing (various types) + * \{ */ + +static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data) +{ + if (but->type == UI_BTYPE_CURVE) { + uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; + but_cumap->edit_cumap = (CurveMapping *)but->poin; + } + else if (but->type == UI_BTYPE_CURVEPROFILE) { + uiButCurveProfile *but_profile = (uiButCurveProfile *)but; + but_profile->edit_profile = (CurveProfile *)but->poin; + } + else if (but->type == UI_BTYPE_COLORBAND) { + uiButColorBand *but_coba = (uiButColorBand *)but; + data->coba = (ColorBand *)but->poin; + but_coba->edit_coba = data->coba; + } + else if (ELEM(but->type, + UI_BTYPE_UNITVEC, + UI_BTYPE_HSVCUBE, + UI_BTYPE_HSVCIRCLE, + UI_BTYPE_COLOR)) { + ui_but_v3_get(but, data->origvec); + copy_v3_v3(data->vec, data->origvec); + but->editvec = data->vec; + } + else { + float softrange, softmin, softmax; + + data->startvalue = ui_but_value_get(but); + data->origvalue = data->startvalue; + data->value = data->origvalue; + but->editval = &data->value; + + softmin = but->softmin; + softmax = but->softmax; + softrange = softmax - softmin; + + if ((but->type == UI_BTYPE_NUM) && (ui_but_is_cursor_warp(but) == false)) { + uiButNumber *number_but = (uiButNumber *)but; + /* Use a minimum so we have a predictable range, + * otherwise some float buttons get a large range. */ + const float value_step_float_min = 0.1f; + const bool is_float = ui_but_is_float(but); + const double value_step = is_float ? + (double)(number_but->step_size * UI_PRECISION_FLOAT_SCALE) : + (int)number_but->step_size; + const float drag_map_softrange_max = UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX * UI_DPI_FAC; + const float softrange_max = min_ff( + softrange, + 2 * (is_float ? min_ff(value_step, value_step_float_min) * + (drag_map_softrange_max / value_step_float_min) : + drag_map_softrange_max)); + + if (softrange > softrange_max) { + /* Center around the value, keeping in the real soft min/max range. */ + softmin = data->origvalue - (softrange_max / 2); + softmax = data->origvalue + (softrange_max / 2); + if (!isfinite(softmin)) { + softmin = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX); + } + if (!isfinite(softmax)) { + softmax = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX); + } + + if (softmin < but->softmin) { + softmin = but->softmin; + softmax = softmin + softrange_max; + } + else if (softmax > but->softmax) { + softmax = but->softmax; + softmin = softmax - softrange_max; + } + + /* Can happen at extreme values. */ + if (UNLIKELY(softmin == softmax)) { + if (data->origvalue > 0.0) { + softmin = nextafterf(softmin, -FLT_MAX); + } + else { + softmax = nextafterf(softmax, FLT_MAX); + } + } + + softrange = softmax - softmin; + } + } + + data->dragfstart = (softrange == 0.0f) ? 0.0f : ((float)data->value - softmin) / softrange; + data->dragf = data->dragfstart; + + data->drag_map_soft_min = softmin; + data->drag_map_soft_max = softmax; + } + + data->dragchange = false; + data->draglock = true; +} + +static void ui_numedit_end(uiBut *but, uiHandleButtonData *data) +{ + but->editval = NULL; + but->editvec = NULL; + if (but->type == UI_BTYPE_COLORBAND) { + uiButColorBand *but_coba = (uiButColorBand *)but; + but_coba->edit_coba = NULL; + } + else if (but->type == UI_BTYPE_CURVE) { + uiButCurveMapping *but_cumap = (uiButCurveMapping *)but; + but_cumap->edit_cumap = NULL; + } + else if (but->type == UI_BTYPE_CURVEPROFILE) { + uiButCurveProfile *but_profile = (uiButCurveProfile *)but; + but_profile->edit_profile = NULL; + } + data->dragstartx = 0; + data->draglastx = 0; + data->dragchange = false; + data->dragcbd = NULL; + data->dragsel = 0; +} + +static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data) +{ + if (data->interactive) { + ui_apply_but(C, block, but, data, true); + } + else { + ui_but_update(but); + } + + ED_region_tag_redraw(data->region); +} + +static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon) +{ + if (but->active->interactive) { + ui_apply_but(C, but->block, but, but->active, true); + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + WM_operator_name_call_ptr(C, + op_icon->optype_params->optype, + op_icon->optype_params->opcontext, + op_icon->optype_params->opptr); + + /* Force recreation of extra operator icons (pseudo update). */ + ui_but_extra_operator_icons_free(but); + + WM_event_add_mousemove(CTX_wm_window(C)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu/Popup Begin/End (various popup types) + * \{ */ + +static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + uiBlockCreateFunc func = NULL; + uiBlockHandleCreateFunc handlefunc = NULL; + uiMenuCreateFunc menufunc = NULL; + uiMenuCreateFunc popoverfunc = NULL; + void *arg = NULL; + + switch (but->type) { + case UI_BTYPE_BLOCK: + case UI_BTYPE_PULLDOWN: + if (but->menu_create_func) { + menufunc = but->menu_create_func; + arg = but->poin; + } + else { + func = but->block_create_func; + arg = but->poin ? but->poin : but->func_argN; + } + break; + case UI_BTYPE_MENU: + case UI_BTYPE_POPOVER: + BLI_assert(but->menu_create_func); + if ((but->type == UI_BTYPE_POPOVER) || ui_but_menu_draw_as_popover(but)) { + popoverfunc = but->menu_create_func; + } + else { + menufunc = but->menu_create_func; + } + arg = but->poin; + break; + case UI_BTYPE_COLOR: + ui_but_v3_get(but, data->origvec); + copy_v3_v3(data->vec, data->origvec); + but->editvec = data->vec; + + if (ui_but_menu_draw_as_popover(but)) { + popoverfunc = but->menu_create_func; + } + else { + handlefunc = ui_block_func_COLOR; + } + arg = but; + break; + + /* quiet warnings for unhandled types */ + default: + break; + } + + if (func || handlefunc) { + data->menu = ui_popup_block_create(C, data->region, but, func, handlefunc, arg, NULL); + if (but->block->handle) { + data->menu->popup = but->block->handle->popup; + } + } + else if (menufunc) { + data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg); + if (but->block->handle) { + data->menu->popup = but->block->handle->popup; + } + } + else if (popoverfunc) { + data->menu = ui_popover_panel_create(C, data->region, but, popoverfunc, arg); + if (but->block->handle) { + data->menu->popup = but->block->handle->popup; + } + } + +#ifdef USE_ALLSELECT + { + if (IS_ALLSELECT_EVENT(data->window->eventstate)) { + data->select_others.is_enabled = true; + } + } +#endif + + /* this makes adjacent blocks auto open from now on */ + // if (but->block->auto_open == 0) { + // but->block->auto_open = 1; + //} +} + +static void ui_block_open_end(bContext *C, uiBut *but, uiHandleButtonData *data) +{ + if (but) { + but->editval = NULL; + but->editvec = NULL; + + but->block->auto_open_last = PIL_check_seconds_timer(); + } + + if (data->menu) { + ui_popup_block_free(C, data->menu); + data->menu = NULL; + } +} + +int ui_but_menu_direction(uiBut *but) +{ + uiHandleButtonData *data = but->active; + + if (data && data->menu) { + return data->menu->direction; + } + + return 0; +} + +/** + * Hack for #uiList #UI_BTYPE_LISTROW buttons to "give" events to overlaying #UI_BTYPE_TEXT + * buttons (Ctrl-Click rename feature & co). + */ +static uiBut *ui_but_list_row_text_activate(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event, + uiButtonActivateType activate_type) +{ + ARegion *region = CTX_wm_region(C); + uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true); + + if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) { + /* exit listrow */ + data->cancel = true; + button_activate_exit(C, but, data, false, false); + + /* Activate the text button. */ + button_activate_init(C, region, labelbut, activate_type); + + return labelbut; + } + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Events for Various Button Types + * \{ */ + +static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + float xmax = but->rect.xmax; + const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */ + int x = event->x, y = event->y; + + ui_window_to_block(data->region, but->block, &x, &y); + if (!BLI_rctf_isect_pt(&but->rect, x, y)) { + return NULL; + } + + /* Same as in 'widget_draw_extra_icons', icon padding from the right edge. */ + xmax -= 0.2 * icon_size; + + /* Handle the padding space from the right edge as the last button. */ + if (x > xmax) { + return (uiButExtraOpIcon *)but->extra_op_icons.last; + } + + /* Inverse order, from right to left. */ + LISTBASE_FOREACH_BACKWARD (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { + if ((x > (xmax - icon_size)) && x <= xmax) { + return op_icon; + } + xmax -= icon_size; + } + + return NULL; +} + +static bool ui_do_but_extra_operator_icon(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + uiButExtraOpIcon *op_icon = ui_but_extra_operator_icon_mouse_over_get(but, data, event); + + if (!op_icon) { + return false; + } + + /* Only act on release, avoids some glitches. */ + if (event->val != KM_RELEASE) { + /* Still swallow events on the icon. */ + return true; + } + + ED_region_tag_redraw(data->region); + button_tooltip_timer_reset(C, but); + + ui_but_extra_operator_icon_apply(C, but, op_icon); + /* Note: 'but', 'data' may now be freed, don't access. */ + + return true; +} + +static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + uiButExtraOpIcon *old_highlighted = NULL; + + /* Unset highlighting of all first. */ + LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) { + if (op_icon->highlighted) { + old_highlighted = op_icon; + } + op_icon->highlighted = false; + } + + uiButExtraOpIcon *hovered = ui_but_extra_operator_icon_mouse_over_get(but, data, event); + + if (hovered) { + hovered->highlighted = true; + } + + if (old_highlighted != hovered) { + ED_region_tag_redraw_no_rebuild(data->region); + } +} + +#ifdef USE_DRAG_TOGGLE +/* Shared by any button that supports drag-toggle. */ +static bool ui_do_but_ANY_drag_toggle( + bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, int *r_retval) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) { +# if 0 /* UNUSED */ + data->togdual = event->ctrl; + data->togonly = !event->shift; +# endif + ui_apply_but(C, but->block, but, data, true); + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + *r_retval = WM_UI_HANDLER_BREAK; + return true; + } + } + else if (data->state == BUTTON_STATE_WAIT_DRAG) { + /* note: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into + * its own function */ + data->applied = false; + *r_retval = ui_do_but_EXIT(C, but, data, event); + return true; + } + return false; +} +#endif /* USE_DRAG_TOGGLE */ + +static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ +#ifdef USE_DRAG_TOGGLE + { + int retval; + if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) { + return retval; + } + } +#endif + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_WAIT_RELEASE); + return WM_UI_HANDLER_BREAK; + } + if (event->type == LEFTMOUSE && event->val == KM_RELEASE && but->block->handle) { + /* regular buttons will be 'UI_SELECT', menu items 'UI_ACTIVE' */ + if (!(but->flag & (UI_SELECT | UI_ACTIVE))) { + data->cancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_WAIT_RELEASE) { + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + if (!(but->flag & UI_SELECT)) { + data->cancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_HOTKEYEVT(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + but->drawstr[0] = 0; + but->modifier_key = 0; + button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { + if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + return WM_UI_HANDLER_CONTINUE; + } + if (event->type == EVT_UNKNOWNKEY) { + WM_report(RPT_WARNING, "Unsupported key: Unknown"); + return WM_UI_HANDLER_CONTINUE; + } + if (event->type == EVT_CAPSLOCKKEY) { + WM_report(RPT_WARNING, "Unsupported key: CapsLock"); + return WM_UI_HANDLER_CONTINUE; + } + + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + /* only cancel if click outside the button */ + if (ui_but_contains_point_px(but, but->active->region, event->x, event->y) == false) { + /* data->cancel doesn't work, this button opens immediate */ + if (but->flag & UI_BUT_IMMEDIATE) { + ui_but_value_set(but, 0); + } + else { + data->cancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + + /* always set */ + but->modifier_key = 0; + if (event->shift) { + but->modifier_key |= KM_SHIFT; + } + if (event->alt) { + but->modifier_key |= KM_ALT; + } + if (event->ctrl) { + but->modifier_key |= KM_CTRL; + } + if (event->oskey) { + but->modifier_key |= KM_OSKEY; + } + + ui_but_update(but); + ED_region_tag_redraw(data->region); + + if (event->val == KM_PRESS) { + if (ISHOTKEY(event->type) && (event->type != EVT_ESCKEY)) { + if (WM_key_event_string(event->type, false)[0]) { + ui_but_value_set(but, event->type); + } + else { + data->cancel = true; + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + if (event->type == EVT_ESCKEY) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_KEYEVT(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) { + if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + return WM_UI_HANDLER_CONTINUE; + } + + if (event->val == KM_PRESS) { + if (WM_key_event_string(event->type, false)[0]) { + ui_but_value_set(but, event->type); + } + else { + data->cancel = true; + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_TAB( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + const bool is_property = (but->rnaprop != NULL); + +#ifdef USE_DRAG_TOGGLE + if (is_property) { + int retval; + if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) { + return retval; + } + } +#endif + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0; + + if (is_property && ELEM(rna_type, PROP_POINTER, PROP_STRING) && (but->custom_data != NULL) && + (event->type == LEFTMOUSE) && ((event->val == KM_DBL_CLICK) || event->ctrl)) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + return WM_UI_HANDLER_BREAK; + } + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY)) { + const int event_val = (is_property) ? KM_PRESS : KM_CLICK; + if (event->val == event_val) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + } + else if (data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + return WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_TEX( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) && + event->val == KM_PRESS) { + if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && (!UI_but_is_utf8(but))) { + /* pass - allow filesel, enter to execute */ + } + else if (but->emboss == UI_EMBOSS_NONE && !event->ctrl) { + /* pass */ + } + else { + if (!ui_but_extra_operator_icon_mouse_over_get(but, data, event)) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + return WM_UI_HANDLER_BREAK; + } + } + } + else if (data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + return WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_SEARCH_UNLINK( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + /* unlink icon is on right */ + if (ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY)) { + /* doing this on KM_PRESS calls eyedropper after clicking unlink icon */ + if ((event->val == KM_RELEASE) && ui_do_but_extra_operator_icon(C, but, data, event)) { + return WM_UI_HANDLER_BREAK; + } + } + return ui_do_but_TEX(C, block, but, data, event); +} + +static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ +#ifdef USE_DRAG_TOGGLE + { + int retval; + if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) { + return retval; + } + } +#endif + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + bool do_activate = false; + if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY)) { + if (event->val == KM_PRESS) { + do_activate = true; + } + } + else if (event->type == LEFTMOUSE) { + if (ui_block_is_menu(but->block)) { + /* Behave like other menu items. */ + do_activate = (event->val == KM_RELEASE); + } + else { + /* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */ + do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK); + } + } + + if (do_activate) { +#if 0 /* UNUSED */ + data->togdual = event->ctrl; + data->togonly = !event->shift; +#endif + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) { + /* Support Ctrl-Wheel to cycle values on expanded enum rows. */ + if (but->type == UI_BTYPE_ROW) { + int type = event->type; + int val = event->val; + + /* Convert pan to scroll-wheel. */ + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + + if (type == MOUSEPAN) { + return WM_UI_HANDLER_BREAK; + } + } + + const int direction = (type == WHEELDOWNMOUSE) ? -1 : 1; + uiBut *but_select = ui_but_find_select_in_enum(but, direction); + if (but_select) { + uiBut *but_other = (direction == -1) ? but_select->next : but_select->prev; + if (but_other && ui_but_find_select_in_enum__cmp(but, but_other)) { + ARegion *region = data->region; + + data->cancel = true; + button_activate_exit(C, but, data, false, false); + + /* Activate the text button. */ + button_activate_init(C, region, but_other, BUTTON_ACTIVATE_OVER); + data = but_other->active; + if (data) { + ui_apply_but(C, but->block, but_other, but_other->active, true); + button_activate_exit(C, but_other, data, false, false); + + /* restore active button */ + button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); + } + else { + /* shouldn't happen */ + BLI_assert(0); + } + } + } + return WM_UI_HANDLER_BREAK; + } + } + } + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + + /* first handle click on icondrag type button */ + if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && but->dragpoin) { + if (ui_but_contains_point_px_icon(but, data->region, event)) { + + /* tell the button to wait and keep checking further events to + * see if it should start dragging */ + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_CONTINUE; + } + } +#ifdef USE_DRAG_TOGGLE + if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_is_drag_toggle(but)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_CONTINUE; + } +#endif + + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + int ret = WM_UI_HANDLER_BREAK; + /* XXX (a bit ugly) Special case handling for filebrowser drag button */ + if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) { + ret = WM_UI_HANDLER_CONTINUE; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + return ret; + } + } + else if (data->state == BUTTON_STATE_WAIT_DRAG) { + + /* this function also ends state */ + if (ui_but_drag_init(C, but, data, event)) { + return WM_UI_HANDLER_BREAK; + } + + /* If the mouse has been pressed and released, getting to + * this point without triggering a drag, then clear the + * drag state for this button and continue to pass on the event */ + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_CONTINUE; + } + + /* while waiting for a drag to be triggered, always block + * other events from getting handled */ + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +/* var names match ui_numedit_but_NUM */ +static float ui_numedit_apply_snapf( + uiBut *but, float tempf, float softmin, float softmax, const enum eSnapType snap) +{ + if (tempf == softmin || tempf == softmax || snap == SNAP_OFF) { + /* pass */ + } + else { + float softrange = softmax - softmin; + float fac = 1.0f; + + if (ui_but_is_unit(but)) { + UnitSettings *unit = but->block->unit; + const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but)); + + if (BKE_unit_is_valid(unit->system, unit_type)) { + fac = (float)BKE_unit_base_scalar(unit->system, unit_type); + if (ELEM(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) { + fac /= unit->scale_length; + } + } + } + + if (fac != 1.0f) { + /* snap in unit-space */ + tempf /= fac; + /* softmin /= fac; */ /* UNUSED */ + /* softmax /= fac; */ /* UNUSED */ + softrange /= fac; + } + + /* workaround, too high snapping values */ + /* snapping by 10's for float buttons is quite annoying (location, scale...), + * but allow for rotations */ + if (softrange >= 21.0f) { + UnitSettings *unit = but->block->unit; + const int unit_type = UI_but_unit_type_get(but); + if ((unit_type == PROP_UNIT_ROTATION) && (unit->system_rotation != USER_UNIT_ROT_RADIANS)) { + /* pass (degrees)*/ + } + else { + softrange = 20.0f; + } + } + + if (snap == SNAP_ON) { + if (softrange < 2.10f) { + tempf = roundf(tempf * 10.0f) * 0.1f; + } + else if (softrange < 21.0f) { + tempf = roundf(tempf); + } + else { + tempf = roundf(tempf * 0.1f) * 10.0f; + } + } + else if (snap == SNAP_ON_SMALL) { + if (softrange < 2.10f) { + tempf = roundf(tempf * 100.0f) * 0.01f; + } + else if (softrange < 21.0f) { + tempf = roundf(tempf * 10.0f) * 0.1f; + } + else { + tempf = roundf(tempf); + } + } + else { + BLI_assert(0); + } + + if (fac != 1.0f) { + tempf *= fac; + } + } + + return tempf; +} + +static float ui_numedit_apply_snap(int temp, + float softmin, + float softmax, + const enum eSnapType snap) +{ + if (ELEM(temp, softmin, softmax)) { + return temp; + } + + switch (snap) { + case SNAP_OFF: + break; + case SNAP_ON: + temp = 10 * (temp / 10); + break; + case SNAP_ON_SMALL: + temp = 100 * (temp / 100); + break; + } + + return temp; +} + +static bool ui_numedit_but_NUM(uiButNumber *number_but, + uiHandleButtonData *data, + int mx, + const bool is_motion, + const enum eSnapType snap, + float fac) +{ + uiBut *but = &number_but->but; + float deler, tempf; + int lvalue, temp; + bool changed = false; + const bool is_float = ui_but_is_float(but); + + /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */ + if ((is_motion || data->draglock) && (ui_but_dragedit_update_mval(data, mx) == false)) { + return changed; + } + + if (ui_but_is_cursor_warp(but)) { + const float softmin = but->softmin; + const float softmax = but->softmax; + const float softrange = softmax - softmin; + + /* Mouse location isn't screen clamped to the screen so use a linear mapping + * 2px == 1-int, or 1px == 1-ClickStep */ + if (is_float) { + fac *= 0.01f * number_but->step_size; + tempf = (float)data->startvalue + ((float)(mx - data->dragstartx) * fac); + tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap); + +#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */ + if (tempf < softmin) { + data->dragstartx -= (softmin - tempf) / fac; + tempf = softmin; + } + else if (tempf > softmax) { + data->dragstartx += (tempf - softmax) / fac; + tempf = softmax; + } +#else + CLAMP(tempf, softmin, softmax); +#endif + + if (tempf != (float)data->value) { + data->dragchange = true; + data->value = tempf; + changed = true; + } + } + else { + if (softrange > 256) { + fac = 1.0; + } /* 1px == 1 */ + else if (softrange > 32) { + fac = 1.0 / 2.0; + } /* 2px == 1 */ + else { + fac = 1.0 / 16.0; + } /* 16px == 1? */ + + temp = data->startvalue + (((double)mx - data->dragstartx) * (double)fac); + temp = ui_numedit_apply_snap(temp, softmin, softmax, snap); + +#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */ + if (temp < softmin) { + data->dragstartx -= (softmin - temp) / fac; + temp = softmin; + } + else if (temp > softmax) { + data->dragstartx += (temp - softmax) / fac; + temp = softmax; + } +#else + CLAMP(temp, softmin, softmax); +#endif + + if (temp != data->value) { + data->dragchange = true; + data->value = temp; + changed = true; + } + } + + data->draglastx = mx; + } + else { + /* Use 'but->softmin', 'but->softmax' when clamping values. */ + const float softmin = data->drag_map_soft_min; + const float softmax = data->drag_map_soft_max; + const float softrange = softmax - softmin; + + float non_linear_range_limit; + float non_linear_pixel_map; + float non_linear_scale; + + /* Use a non-linear mapping of the mouse drag especially for large floats + * (normal behavior) */ + deler = 500; + if (is_float) { + /* not needed for smaller float buttons */ + non_linear_range_limit = 11.0f; + non_linear_pixel_map = 500.0f; + } + else { + /* only scale large int buttons */ + non_linear_range_limit = 129.0f; + /* Larger for ints, we don't need to fine tune them. */ + non_linear_pixel_map = 250.0f; + + /* prevent large ranges from getting too out of control */ + if (softrange > 600) { + deler = powf(softrange, 0.75f); + } + else if (softrange < 25) { + deler = 50.0; + } + else if (softrange < 100) { + deler = 100.0; + } + } + deler /= fac; + + if (softrange > non_linear_range_limit) { + non_linear_scale = (float)abs(mx - data->dragstartx) / non_linear_pixel_map; + } + else { + non_linear_scale = 1.0f; + } + + if (is_float == false) { + /* at minimum, moving cursor 2 pixels should change an int button. */ + CLAMP_MIN(non_linear_scale, 0.5f * UI_DPI_FAC); + } + + data->dragf += (((float)(mx - data->draglastx)) / deler) * non_linear_scale; + + if (but->softmin == softmin) { + CLAMP_MIN(data->dragf, 0.0f); + } + if (but->softmax == softmax) { + CLAMP_MAX(data->dragf, 1.0f); + } + + data->draglastx = mx; + tempf = (softmin + data->dragf * softrange); + + if (!is_float) { + temp = round_fl_to_int(tempf); + + temp = ui_numedit_apply_snap(temp, but->softmin, but->softmax, snap); + + CLAMP(temp, but->softmin, but->softmax); + lvalue = (int)data->value; + + if (temp != lvalue) { + data->dragchange = true; + data->value = (double)temp; + changed = true; + } + } + else { + temp = 0; + tempf = ui_numedit_apply_snapf(but, tempf, but->softmin, but->softmax, snap); + + CLAMP(tempf, but->softmin, but->softmax); + + if (tempf != (float)data->value) { + data->dragchange = true; + data->value = tempf; + changed = true; + } + } + } + + return changed; +} + +static void ui_numedit_set_active(uiBut *but) +{ + const int oldflag = but->drawflag; + but->drawflag &= ~(UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT); + + uiHandleButtonData *data = but->active; + if (!data) { + return; + } + + /* Ignore once we start dragging. */ + if (data->dragchange == false) { + const float handle_width = min_ff(BLI_rctf_size_x(&but->rect) / 3, + BLI_rctf_size_y(&but->rect) * 0.7f); + /* we can click on the side arrows to increment/decrement, + * or click inside to edit the value directly */ + int mx = data->window->eventstate->x; + int my = data->window->eventstate->y; + ui_window_to_block(data->region, but->block, &mx, &my); + + if (mx < (but->rect.xmin + handle_width)) { + but->drawflag |= UI_BUT_ACTIVE_LEFT; + } + else if (mx > (but->rect.xmax - handle_width)) { + but->drawflag |= UI_BUT_ACTIVE_RIGHT; + } + } + + /* Don't change the cursor once pressed. */ + if ((but->flag & UI_SELECT) == 0) { + if ((but->drawflag & UI_BUT_ACTIVE_LEFT) || (but->drawflag & UI_BUT_ACTIVE_RIGHT)) { + if (data->changed_cursor) { + WM_cursor_modal_restore(data->window); + data->changed_cursor = false; + } + } + else { + if (data->changed_cursor == false) { + WM_cursor_modal_set(data->window, WM_CURSOR_X_MOVE); + data->changed_cursor = true; + } + } + } + + if (but->drawflag != oldflag) { + ED_region_tag_redraw(data->region); + } +} + +static int ui_do_but_NUM( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + uiButNumber *number_but = (uiButNumber *)but; + int click = 0; + int retval = WM_UI_HANDLER_CONTINUE; + + /* mouse location scaled to fit the UI */ + int mx = event->x; + int my = event->y; + /* mouse location kept at screen pixel coords */ + const int screen_mx = event->x; + + BLI_assert(but->type == UI_BTYPE_NUM); + + ui_window_to_block(data->region, block, &mx, &my); + ui_numedit_set_active(but); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + int type = event->type, val = event->val; + + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + } + + /* XXX hardcoded keymap check.... */ + if (type == MOUSEPAN && event->ctrl) { + /* allow accumulating values, otherwise scrolling gets preference */ + retval = WM_UI_HANDLER_BREAK; + } + else if (type == WHEELDOWNMOUSE && event->ctrl) { + mx = but->rect.xmin; + but->drawflag &= ~UI_BUT_ACTIVE_RIGHT; + but->drawflag |= UI_BUT_ACTIVE_LEFT; + click = 1; + } + else if (type == WHEELUPMOUSE && event->ctrl) { + mx = but->rect.xmax; + but->drawflag &= ~UI_BUT_ACTIVE_LEFT; + but->drawflag |= UI_BUT_ACTIVE_RIGHT; + click = 1; + } + else if (event->val == KM_PRESS) { + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + else if (event->type == LEFTMOUSE) { + data->dragstartx = data->draglastx = ui_but_is_cursor_warp(but) ? screen_mx : mx; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + click = 1; + } + else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + data->value = -data->value; + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + } + +#ifdef USE_DRAG_MULTINUM + copy_v2_v2_int(data->multi_data.drag_start, &event->x); +#endif + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + if (data->dragchange) { +#ifdef USE_DRAG_MULTINUM + /* If we started multi-button but didn't drag, then edit. */ + if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP) { + click = 1; + } + else +#endif + { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else { + click = 1; + } + } + else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { + const bool is_motion = (event->type == MOUSEMOVE); + const enum eSnapType snap = ui_event_to_snap(event); + float fac; + +#ifdef USE_DRAG_MULTINUM + data->multi_data.drag_dir[0] += abs(data->draglastx - mx); + data->multi_data.drag_dir[1] += abs(data->draglasty - my); +#endif + + fac = 1.0f; + if (event->shift) { + fac /= 10.0f; + } + + if (ui_numedit_but_NUM(number_but, + data, + (ui_but_is_cursor_warp(but) ? screen_mx : mx), + is_motion, + snap, + fac)) { + ui_numedit_apply(C, block, but, data); + } +#ifdef USE_DRAG_MULTINUM + else if (data->multi_data.has_mbuts) { + if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_ENABLE) { + ui_multibut_states_apply(C, data, block); + } + } +#endif + } + retval = WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + retval = WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + retval = WM_UI_HANDLER_BREAK; + } + + if (click) { + /* we can click on the side arrows to increment/decrement, + * or click inside to edit the value directly */ + + if (!ui_but_is_float(but)) { + /* Integer Value. */ + if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + const int value_step = (int)number_but->step_size; + BLI_assert(value_step > 0); + const int softmin = round_fl_to_int_clamp(but->softmin); + const int softmax = round_fl_to_int_clamp(but->softmax); + const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? + (double)max_ii(softmin, (int)data->value - value_step) : + (double)min_ii(softmax, (int)data->value + value_step); + if (value_test != data->value) { + data->value = (double)value_test; + } + else { + data->cancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + } + else { + /* Float Value. */ + if (but->drawflag & (UI_BUT_ACTIVE_LEFT | UI_BUT_ACTIVE_RIGHT)) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + const double value_step = (double)number_but->step_size * UI_PRECISION_FLOAT_SCALE; + BLI_assert(value_step > 0.0f); + const double value_test = (but->drawflag & UI_BUT_ACTIVE_LEFT) ? + (double)max_ff(but->softmin, + (float)(data->value - value_step)) : + (double)min_ff(but->softmax, + (float)(data->value + value_step)); + if (value_test != data->value) { + data->value = value_test; + } + else { + data->cancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + } + + retval = WM_UI_HANDLER_BREAK; + } + + data->draglastx = mx; + data->draglasty = my; + + return retval; +} + +static bool ui_numedit_but_SLI(uiBut *but, + uiHandleButtonData *data, + int mx, + const bool is_horizontal, + const bool is_motion, + const bool snap, + const bool shift) +{ + float cursor_x_range, f, tempf, softmin, softmax, softrange; + int temp, lvalue; + bool changed = false; + float mx_fl, my_fl; + + /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */ + if ((but->type != UI_BTYPE_SCROLL) && (is_motion || data->draglock) && + (ui_but_dragedit_update_mval(data, mx) == false)) { + return changed; + } + + softmin = but->softmin; + softmax = but->softmax; + softrange = softmax - softmin; + + /* yes, 'mx' as both x/y is intentional */ + ui_mouse_scale_warp(data, mx, mx, &mx_fl, &my_fl, shift); + + if (but->type == UI_BTYPE_NUM_SLIDER) { + cursor_x_range = BLI_rctf_size_x(&but->rect); + } + else if (but->type == UI_BTYPE_SCROLL) { + const float size = (is_horizontal) ? BLI_rctf_size_x(&but->rect) : + -BLI_rctf_size_y(&but->rect); + cursor_x_range = size * (but->softmax - but->softmin) / + (but->softmax - but->softmin + but->a1); + } + else { + const float ofs = (BLI_rctf_size_y(&but->rect) / 2.0f); + cursor_x_range = (BLI_rctf_size_x(&but->rect) - ofs); + } + + f = (mx_fl - data->dragstartx) / cursor_x_range + data->dragfstart; + CLAMP(f, 0.0f, 1.0f); + + /* deal with mouse correction */ +#ifdef USE_CONT_MOUSE_CORRECT + if (ui_but_is_cursor_warp(but)) { + /* OK but can go outside bounds */ + if (is_horizontal) { + data->ungrab_mval[0] = but->rect.xmin + (f * cursor_x_range); + data->ungrab_mval[1] = BLI_rctf_cent_y(&but->rect); + } + else { + data->ungrab_mval[1] = but->rect.ymin + (f * cursor_x_range); + data->ungrab_mval[0] = BLI_rctf_cent_x(&but->rect); + } + BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); + } +#endif + /* done correcting mouse */ + + tempf = softmin + f * softrange; + temp = round_fl_to_int(tempf); + + if (snap) { + if (ELEM(tempf, softmin, softmax)) { + /* pass */ + } + else if (ui_but_is_float(but)) { + + if (shift) { + if (ELEM(tempf, softmin, softmax)) { + } + else if (softrange < 2.10f) { + tempf = roundf(tempf * 100.0f) * 0.01f; + } + else if (softrange < 21.0f) { + tempf = roundf(tempf * 10.0f) * 0.1f; + } + else { + tempf = roundf(tempf); + } + } + else { + if (softrange < 2.10f) { + tempf = roundf(tempf * 10.0f) * 0.1f; + } + else if (softrange < 21.0f) { + tempf = roundf(tempf); + } + else { + tempf = roundf(tempf * 0.1f) * 10.0f; + } + } + } + else { + temp = 10 * (temp / 10); + tempf = temp; + } + } + + if (!ui_but_is_float(but)) { + lvalue = round(data->value); + + CLAMP(temp, softmin, softmax); + + if (temp != lvalue) { + data->value = temp; + data->dragchange = true; + changed = true; + } + } + else { + CLAMP(tempf, softmin, softmax); + + if (tempf != (float)data->value) { + data->value = tempf; + data->dragchange = true; + changed = true; + } + } + + return changed; +} + +static int ui_do_but_SLI( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int click = 0; + int retval = WM_UI_HANDLER_CONTINUE; + + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + int type = event->type, val = event->val; + + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + } + + /* XXX hardcoded keymap check.... */ + if (type == MOUSEPAN && event->ctrl) { + /* allow accumulating values, otherwise scrolling gets preference */ + retval = WM_UI_HANDLER_BREAK; + } + else if (type == WHEELDOWNMOUSE && event->ctrl) { + mx = but->rect.xmin; + click = 2; + } + else if (type == WHEELUPMOUSE && event->ctrl) { + mx = but->rect.xmax; + click = 2; + } + else if (event->val == KM_PRESS) { + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->ctrl) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval = WM_UI_HANDLER_BREAK; + } +#ifndef USE_ALLSELECT + /* alt-click on sides to get "arrows" like in UI_BTYPE_NUM buttons, + * and match wheel usage above */ + else if (event->type == LEFTMOUSE && event->alt) { + int halfpos = BLI_rctf_cent_x(&but->rect); + click = 2; + if (mx < halfpos) { + mx = but->rect.xmin; + } + else { + mx = but->rect.xmax; + } + } +#endif + else if (event->type == LEFTMOUSE) { + data->dragstartx = mx; + data->draglastx = mx; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + click = 1; + } + else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + data->value = -data->value; + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + } + } +#ifdef USE_DRAG_MULTINUM + copy_v2_v2_int(data->multi_data.drag_start, &event->x); +#endif + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + if (data->dragchange) { +#ifdef USE_DRAG_MULTINUM + /* If we started multi-button but didn't drag, then edit. */ + if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP) { + click = 1; + } + else +#endif + { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else { +#ifdef USE_CONT_MOUSE_CORRECT + /* reset! */ + copy_v2_fl(data->ungrab_mval, FLT_MAX); +#endif + click = 1; + } + } + else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { + const bool is_motion = (event->type == MOUSEMOVE); +#ifdef USE_DRAG_MULTINUM + data->multi_data.drag_dir[0] += abs(data->draglastx - mx); + data->multi_data.drag_dir[1] += abs(data->draglasty - my); +#endif + if (ui_numedit_but_SLI( + but, data, mx, true, is_motion, event->ctrl != 0, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + +#ifdef USE_DRAG_MULTINUM + else if (data->multi_data.has_mbuts) { + if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_ENABLE) { + ui_multibut_states_apply(C, data, block); + } + } +#endif + } + retval = WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_TEXT_EDITING) { + ui_do_but_textedit(C, block, but, data, event); + retval = WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_TEXT_SELECTING) { + ui_do_but_textedit_select(C, block, but, data, event); + retval = WM_UI_HANDLER_BREAK; + } + + if (click) { + if (click == 2) { + /* nudge slider to the left or right */ + float f, tempf, softmin, softmax, softrange; + int temp; + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + softmin = but->softmin; + softmax = but->softmax; + softrange = softmax - softmin; + + tempf = data->value; + temp = (int)data->value; + +#if 0 + if (but->type == SLI) { + /* same as below */ + f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect)); + } + else +#endif + { + f = (float)(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect)); + } + + f = softmin + f * softrange; + + if (!ui_but_is_float(but)) { + if (f < temp) { + temp--; + } + else { + temp++; + } + + if (temp >= softmin && temp <= softmax) { + data->value = temp; + } + else { + data->cancel = true; + } + } + else { + if (f < tempf) { + tempf -= 0.01f; + } + else { + tempf += 0.01f; + } + + if (tempf >= softmin && tempf <= softmax) { + data->value = tempf; + } + else { + data->cancel = true; + } + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + } + else { + /* edit the value directly */ + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + } + + data->draglastx = mx; + data->draglasty = my; + + return retval; +} + +static int ui_do_but_SCROLL( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int retval = WM_UI_HANDLER_CONTINUE; + const bool horizontal = (BLI_rctf_size_x(&but->rect) > BLI_rctf_size_y(&but->rect)); + + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->val == KM_PRESS) { + if (event->type == LEFTMOUSE) { + if (horizontal) { + data->dragstartx = mx; + data->draglastx = mx; + } + else { + data->dragstartx = my; + data->draglastx = my; + } + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == EVT_ESCKEY) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if (event->type == MOUSEMOVE) { + const bool is_motion = (event->type == MOUSEMOVE); + if (ui_numedit_but_SLI( + but, data, (horizontal) ? mx : my, horizontal, is_motion, false, false)) { + ui_numedit_apply(C, block, but, data); + } + } + + retval = WM_UI_HANDLER_BREAK; + } + + return retval; +} + +static int ui_do_but_GRIP( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int retval = WM_UI_HANDLER_CONTINUE; + const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect)); + + /* Note: Having to store org point in window space and recompute it to block "space" each time + * is not ideal, but this is a way to hack around behavior of ui_window_to_block(), which + * returns different results when the block is inside a panel or not... + * See T37739. + */ + + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->val == KM_PRESS) { + if (event->type == LEFTMOUSE) { + data->dragstartx = event->x; + data->dragstarty = event->y; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == EVT_ESCKEY) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if (event->type == MOUSEMOVE) { + int dragstartx = data->dragstartx; + int dragstarty = data->dragstarty; + ui_window_to_block(data->region, block, &dragstartx, &dragstarty); + data->value = data->origvalue + (horizontal ? mx - dragstartx : dragstarty - my); + ui_numedit_apply(C, block, but, data); + } + + retval = WM_UI_HANDLER_BREAK; + } + + return retval; +} + +static int ui_do_but_LISTROW(bContext *C, + uiBut *but, + uiHandleButtonData *data, + const wmEvent *event) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + /* hack to pass on ctrl+click and double click to overlapping text + * editing field for editing list item names + */ + if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS && + event->ctrl) || + (event->type == LEFTMOUSE && event->val == KM_DBL_CLICK)) { + uiBut *labelbut = ui_but_list_row_text_activate( + C, but, data, event, BUTTON_ACTIVATE_TEXT_EDITING); + if (labelbut) { + /* Nothing else to do. */ + return WM_UI_HANDLER_BREAK; + } + } + } + + return ui_do_but_EXIT(C, but, data, event); +} + +static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + + /* first handle click on icondrag type button */ + if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) { + if (ui_but_contains_point_px_icon(but, data->region, event)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_BREAK; + } + } +#ifdef USE_DRAG_TOGGLE + if (event->type == LEFTMOUSE && event->val == KM_PRESS && (ui_but_is_drag_toggle(but))) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_BREAK; + } +#endif + /* regular open menu */ + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + return WM_UI_HANDLER_BREAK; + } + if (ui_but_supports_cycling(but)) { + if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) { + int type = event->type; + int val = event->val; + + /* Convert pan to scroll-wheel. */ + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + + if (type == MOUSEPAN) { + return WM_UI_HANDLER_BREAK; + } + } + + const int direction = (type == WHEELDOWNMOUSE) ? 1 : -1; + + data->value = ui_but_menu_step(but, direction); + + button_activate_state(C, but, BUTTON_STATE_EXIT); + ui_apply_but(C, but->block, but, data, true); + + /* Button's state need to be changed to EXIT so moving mouse away from this mouse + * wouldn't lead to cancel changes made to this button, but changing state to EXIT also + * makes no button active for a while which leads to triggering operator when doing fast + * scrolling mouse wheel. using post activate stuff from button allows to make button be + * active again after checking for all all that mouse leave and cancel stuff, so quick + * scroll wouldn't be an issue anymore. Same goes for scrolling wheel in another + * direction below (sergey). + */ + data->postbut = but; + data->posttype = BUTTON_ACTIVATE_OVER; + + /* without this, a new interface that draws as result of the menu change + * won't register that the mouse is over it, eg: + * Alt+MouseWheel over the render slots, without this, + * the slot menu fails to switch a second time. + * + * The active state of the button could be maintained some other way + * and remove this mousemove event. + */ + WM_event_add_mousemove(data->window); + + return WM_UI_HANDLER_BREAK; + } + } + } + else if (data->state == BUTTON_STATE_WAIT_DRAG) { + + /* this function also ends state */ + if (ui_but_drag_init(C, but, data, event)) { + return WM_UI_HANDLER_BREAK; + } + + /* outside icon quit, not needed if drag activated */ + if (0 == ui_but_contains_point_px_icon(but, data->region, event)) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + data->cancel = true; + return WM_UI_HANDLER_BREAK; + } + + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + return WM_UI_HANDLER_BREAK; + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_UNITVEC( + uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap) +{ + float mrad; + bool changed = true; + + /* button is presumed square */ + /* if mouse moves outside of sphere, it does negative normal */ + + /* note that both data->vec and data->origvec should be normalized + * else we'll get a harmless but annoying jump when first clicking */ + + float *fp = data->origvec; + const float rad = BLI_rctf_size_x(&but->rect); + const float radsq = rad * rad; + + int mdx, mdy; + if (fp[2] > 0.0f) { + mdx = (rad * fp[0]); + mdy = (rad * fp[1]); + } + else if (fp[2] > -1.0f) { + mrad = rad / sqrtf(fp[0] * fp[0] + fp[1] * fp[1]); + + mdx = 2.0f * mrad * fp[0] - (rad * fp[0]); + mdy = 2.0f * mrad * fp[1] - (rad * fp[1]); + } + else { + mdx = mdy = 0; + } + + float dx = (float)(mx + mdx - data->dragstartx); + float dy = (float)(my + mdy - data->dragstarty); + + fp = data->vec; + mrad = dx * dx + dy * dy; + if (mrad < radsq) { /* inner circle */ + fp[0] = dx; + fp[1] = dy; + fp[2] = sqrtf(radsq - dx * dx - dy * dy); + } + else { /* outer circle */ + + mrad = rad / sqrtf(mrad); /* veclen */ + + dx *= (2.0f * mrad - 1.0f); + dy *= (2.0f * mrad - 1.0f); + + mrad = dx * dx + dy * dy; + if (mrad < radsq) { + fp[0] = dx; + fp[1] = dy; + fp[2] = -sqrtf(radsq - dx * dx - dy * dy); + } + } + normalize_v3(fp); + + if (snap != SNAP_OFF) { + const int snap_steps = (snap == SNAP_ON) ? 4 : 12; /* 45 or 15 degree increments */ + const float snap_steps_angle = M_PI / snap_steps; + float angle, angle_snap; + + /* round each axis of 'fp' to the next increment + * do this in "angle" space - this gives increments of same size */ + for (int i = 0; i < 3; i++) { + angle = asinf(fp[i]); + angle_snap = roundf((angle / snap_steps_angle)) * snap_steps_angle; + fp[i] = sinf(angle_snap); + } + normalize_v3(fp); + changed = !compare_v3v3(fp, data->origvec, FLT_EPSILON); + } + + data->draglastx = mx; + data->draglasty = my; + + return changed; +} + +static void ui_palette_set_active(uiButColor *color_but) +{ + if (color_but->is_pallete_color) { + Palette *palette = (Palette *)color_but->but.rnapoin.owner_id; + PaletteColor *color = (PaletteColor *)color_but->but.rnapoin.data; + palette->active_color = BLI_findindex(&palette->colors, color); + } +} + +static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + BLI_assert(but->type == UI_BTYPE_COLOR); + uiButColor *color_but = (uiButColor *)but; + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + /* first handle click on icondrag type button */ + if (event->type == LEFTMOUSE && but->dragpoin && event->val == KM_PRESS) { + ui_palette_set_active(color_but); + if (ui_but_contains_point_px_icon(but, data->region, event)) { + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_BREAK; + } + } +#ifdef USE_DRAG_TOGGLE + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + ui_palette_set_active(color_but); + button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG); + data->dragstartx = event->x; + data->dragstarty = event->y; + return WM_UI_HANDLER_BREAK; + } +#endif + /* regular open menu */ + if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) { + ui_palette_set_active(color_but); + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + return WM_UI_HANDLER_BREAK; + } + if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && event->ctrl) { + ColorPicker *cpicker = (ColorPicker *)but->custom_data; + float hsv_static[3] = {0.0f}; + float *hsv = cpicker ? cpicker->hsv_perceptual : hsv_static; + float col[3]; + + ui_but_v3_get(but, col); + rgb_to_hsv_compat_v(col, hsv); + + if (event->type == WHEELDOWNMOUSE) { + hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f); + } + else if (event->type == WHEELUPMOUSE) { + hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f); + } + else { + const float fac = 0.005 * (event->y - event->prevy); + hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f); + } + + hsv_to_rgb_v(hsv, data->vec); + ui_but_v3_set(but, data->vec); + + button_activate_state(C, but, BUTTON_STATE_EXIT); + ui_apply_but(C, but->block, but, data, true); + return WM_UI_HANDLER_BREAK; + } + if (color_but->is_pallete_color && (event->type == EVT_DELKEY) && (event->val == KM_PRESS)) { + Palette *palette = (Palette *)but->rnapoin.owner_id; + PaletteColor *color = (PaletteColor *)but->rnapoin.data; + + BKE_palette_color_remove(palette, color); + + button_activate_state(C, but, BUTTON_STATE_EXIT); + + /* this is risky. it works OK for now, + * but if it gives trouble we should delay execution */ + but->rnapoin = PointerRNA_NULL; + but->rnaprop = NULL; + + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_WAIT_DRAG) { + + /* this function also ends state */ + if (ui_but_drag_init(C, but, data, event)) { + return WM_UI_HANDLER_BREAK; + } + + /* outside icon quit, not needed if drag activated */ + if (0 == ui_but_contains_point_px_icon(but, data->region, event)) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + data->cancel = true; + return WM_UI_HANDLER_BREAK; + } + + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + if (color_but->is_pallete_color) { + if (!event->ctrl) { + float color[3]; + Paint *paint = BKE_paint_get_active_from_context(C); + Brush *brush = BKE_paint_brush(paint); + + if (brush->flag & BRUSH_USE_GRADIENT) { + float *target = &brush->gradient->data[brush->gradient->cur].r; + + if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target); + IMB_colormanagement_srgb_to_scene_linear_v3(target); + } + else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { + RNA_property_float_get_array(&but->rnapoin, but->rnaprop, target); + } + } + else { + Scene *scene = CTX_data_scene(C); + bool updated = false; + + if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color); + BKE_brush_color_set(scene, brush, color); + updated = true; + } + else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) { + RNA_property_float_get_array(&but->rnapoin, but->rnaprop, color); + IMB_colormanagement_scene_linear_to_srgb_v3(color); + BKE_brush_color_set(scene, brush, color); + updated = true; + } + + if (updated) { + PointerRNA brush_ptr; + PropertyRNA *brush_color_prop; + + RNA_id_pointer_create(&brush->id, &brush_ptr); + brush_color_prop = RNA_struct_find_property(&brush_ptr, "color"); + RNA_property_update(C, &brush_ptr, brush_color_prop); + } + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + } + } + else { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + } + return WM_UI_HANDLER_BREAK; + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_but_UNITVEC( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + const enum eSnapType snap = ui_event_to_snap(event); + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) { + ui_numedit_apply(C, block, but, data); + } + + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { + if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) { + const enum eSnapType snap = ui_event_to_snap(event); + if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +/* scales a vector so no axis exceeds max + * (could become BLI_math func) */ +static void clamp_axis_max_v3(float v[3], const float max) +{ + const float v_max = max_fff(v[0], v[1], v[2]); + if (v_max > max) { + mul_v3_fl(v, max / v_max); + if (v[0] > max) { + v[0] = max; + } + if (v[1] > max) { + v[1] = max; + } + if (v[2] > max) { + v[2] = max; + } + } +} + +static void ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube *hsv_but, + const float rgb[3], + float hsv[3]) +{ + if (hsv_but->gradient_type == UI_GRAD_L_ALT) { + rgb_to_hsl_compat_v(rgb, hsv); + } + else { + rgb_to_hsv_compat_v(rgb, hsv); + } +} + +static void ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube *hsv_but, + const float rgb[3], + float hsv[3]) +{ + if (hsv_but->gradient_type == UI_GRAD_L_ALT) { + rgb_to_hsl_v(rgb, hsv); + } + else { + rgb_to_hsv_v(rgb, hsv); + } +} + +static void ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube *hsv_but, + const float hsv[3], + float rgb[3]) +{ + if (hsv_but->gradient_type == UI_GRAD_L_ALT) { + hsl_to_rgb_v(hsv, rgb); + } + else { + hsv_to_rgb_v(hsv, rgb); + } +} + +static bool ui_numedit_but_HSVCUBE(uiBut *but, + uiHandleButtonData *data, + int mx, + int my, + const enum eSnapType snap, + const bool shift) +{ + const uiButHSVCube *hsv_but = (uiButHSVCube *)but; + ColorPicker *cpicker = (ColorPicker *)but->custom_data; + float *hsv = cpicker->hsv_perceptual; + float rgb[3]; + float x, y; + float mx_fl, my_fl; + const bool changed = true; + + ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift); + +#ifdef USE_CONT_MOUSE_CORRECT + if (ui_but_is_cursor_warp(but)) { + /* OK but can go outside bounds */ + data->ungrab_mval[0] = mx_fl; + data->ungrab_mval[1] = my_fl; + BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); + } +#endif + + ui_but_v3_get(but, rgb); + ui_scene_linear_to_perceptual_space(but, rgb); + + ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv); + + /* only apply the delta motion, not absolute */ + if (shift) { + rcti rect_i; + float xpos, ypos, hsvo[3]; + + BLI_rcti_rctf_copy(&rect_i, &but->rect); + + /* calculate original hsv again */ + copy_v3_v3(rgb, data->origvec); + ui_scene_linear_to_perceptual_space(but, rgb); + + copy_v3_v3(hsvo, hsv); + + ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsvo); + + /* and original position */ + ui_hsvcube_pos_from_vals(hsv_but, &rect_i, hsvo, &xpos, &ypos); + + mx_fl = xpos - (data->dragstartx - mx_fl); + my_fl = ypos - (data->dragstarty - my_fl); + } + + /* relative position within box */ + x = ((float)mx_fl - but->rect.xmin) / BLI_rctf_size_x(&but->rect); + y = ((float)my_fl - but->rect.ymin) / BLI_rctf_size_y(&but->rect); + CLAMP(x, 0.0f, 1.0f); + CLAMP(y, 0.0f, 1.0f); + + switch (hsv_but->gradient_type) { + case UI_GRAD_SV: + hsv[1] = x; + hsv[2] = y; + break; + case UI_GRAD_HV: + hsv[0] = x; + hsv[2] = y; + break; + case UI_GRAD_HS: + hsv[0] = x; + hsv[1] = y; + break; + case UI_GRAD_H: + hsv[0] = x; + break; + case UI_GRAD_S: + hsv[1] = x; + break; + case UI_GRAD_V: + hsv[2] = x; + break; + case UI_GRAD_L_ALT: + hsv[2] = y; + break; + case UI_GRAD_V_ALT: { + /* vertical 'value' strip */ + const float min = but->softmin, max = but->softmax; + /* exception only for value strip - use the range set in but->min/max */ + hsv[2] = y * (max - min) + min; + break; + } + default: + BLI_assert(0); + break; + } + + if (snap != SNAP_OFF) { + if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) { + ui_color_snap_hue(snap, &hsv[0]); + } + } + + ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb); + ui_perceptual_to_scene_linear_space(but, rgb); + + /* clamp because with color conversion we can exceed range T34295. */ + if (hsv_but->gradient_type == UI_GRAD_V_ALT) { + clamp_axis_max_v3(rgb, but->softmax); + } + + copy_v3_v3(data->vec, rgb); + + data->draglastx = mx; + data->draglasty = my; + + return changed; +} + +#ifdef WITH_INPUT_NDOF +static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but, + uiHandleButtonData *data, + const wmNDOFMotionData *ndof, + const enum eSnapType snap, + const bool shift) +{ + ColorPicker *cpicker = hsv_but->but.custom_data; + float *hsv = cpicker->hsv_perceptual; + const float hsv_v_max = max_ff(hsv[2], hsv_but->but.softmax); + float rgb[3]; + const float sensitivity = (shift ? 0.15f : 0.3f) * ndof->dt; + + ui_but_v3_get(&hsv_but->but, rgb); + ui_scene_linear_to_perceptual_space(&hsv_but->but, rgb); + ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv); + + switch (hsv_but->gradient_type) { + case UI_GRAD_SV: + hsv[1] += ndof->rvec[2] * sensitivity; + hsv[2] += ndof->rvec[0] * sensitivity; + break; + case UI_GRAD_HV: + hsv[0] += ndof->rvec[2] * sensitivity; + hsv[2] += ndof->rvec[0] * sensitivity; + break; + case UI_GRAD_HS: + hsv[0] += ndof->rvec[2] * sensitivity; + hsv[1] += ndof->rvec[0] * sensitivity; + break; + case UI_GRAD_H: + hsv[0] += ndof->rvec[2] * sensitivity; + break; + case UI_GRAD_S: + hsv[1] += ndof->rvec[2] * sensitivity; + break; + case UI_GRAD_V: + hsv[2] += ndof->rvec[2] * sensitivity; + break; + case UI_GRAD_V_ALT: + case UI_GRAD_L_ALT: + /* vertical 'value' strip */ + + /* exception only for value strip - use the range set in but->min/max */ + hsv[2] += ndof->rvec[0] * sensitivity; + + CLAMP(hsv[2], hsv_but->but.softmin, hsv_but->but.softmax); + break; + default: + BLI_assert(!"invalid hsv type"); + break; + } + + if (snap != SNAP_OFF) { + if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) { + ui_color_snap_hue(snap, &hsv[0]); + } + } + + /* ndof specific: the changes above aren't clamping */ + hsv_clamp_v(hsv, hsv_v_max); + + ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb); + ui_perceptual_to_scene_linear_space(&hsv_but->but, rgb); + + copy_v3_v3(data->vec, rgb); + ui_but_v3_set(&hsv_but->but, data->vec); +} +#endif /* WITH_INPUT_NDOF */ + +static int ui_do_but_HSVCUBE( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + uiButHSVCube *hsv_but = (uiButHSVCube *)but; + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + const enum eSnapType snap = ui_event_to_snap(event); + + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + + return WM_UI_HANDLER_BREAK; + } +#ifdef WITH_INPUT_NDOF + if (event->type == NDOF_MOTION) { + const wmNDOFMotionData *ndof = event->customdata; + const enum eSnapType snap = ui_event_to_snap(event); + + ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->shift != 0); + + button_activate_state(C, but, BUTTON_STATE_EXIT); + ui_apply_but(C, but->block, but, data, true); + + return WM_UI_HANDLER_BREAK; + } +#endif /* WITH_INPUT_NDOF */ + /* XXX hardcoded keymap check.... */ + if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { + if (ELEM(hsv_but->gradient_type, UI_GRAD_V_ALT, UI_GRAD_L_ALT)) { + int len; + + /* reset only value */ + + len = RNA_property_array_length(&but->rnapoin, but->rnaprop); + if (ELEM(len, 3, 4)) { + float rgb[3], def_hsv[3]; + float def[4]; + ColorPicker *cpicker = (ColorPicker *)but->custom_data; + float *hsv = cpicker->hsv_perceptual; + + RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def); + ui_rgb_to_color_picker_HSVCUBE_v(hsv_but, def, def_hsv); + + ui_but_v3_get(but, rgb); + ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv); + + def_hsv[0] = hsv[0]; + def_hsv[1] = hsv[1]; + + ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, def_hsv, rgb); + ui_but_v3_set(but, rgb); + + RNA_property_update(C, &but->rnapoin, but->rnaprop); + return WM_UI_HANDLER_BREAK; + } + } + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { + if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) { + const enum eSnapType snap = ui_event_to_snap(event); + + if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_HSVCIRCLE(uiBut *but, + uiHandleButtonData *data, + float mx, + float my, + const enum eSnapType snap, + const bool shift) +{ + const bool changed = true; + ColorPicker *cpicker = (ColorPicker *)but->custom_data; + float *hsv = cpicker->hsv_perceptual; + + float mx_fl, my_fl; + ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift); + +#ifdef USE_CONT_MOUSE_CORRECT + if (ui_but_is_cursor_warp(but)) { + /* OK but can go outside bounds */ + data->ungrab_mval[0] = mx_fl; + data->ungrab_mval[1] = my_fl; + { /* clamp */ + const float radius = min_ff(BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)) / 2.0f; + const float cent[2] = {BLI_rctf_cent_x(&but->rect), BLI_rctf_cent_y(&but->rect)}; + const float len = len_v2v2(cent, data->ungrab_mval); + if (len > radius) { + dist_ensure_v2_v2fl(data->ungrab_mval, cent, radius); + } + } + } +#endif + + rcti rect; + BLI_rcti_rctf_copy(&rect, &but->rect); + + float rgb[3]; + ui_but_v3_get(but, rgb); + ui_scene_linear_to_perceptual_space(but, rgb); + ui_color_picker_rgb_to_hsv_compat(rgb, hsv); + + /* exception, when using color wheel in 'locked' value state: + * allow choosing a hue for black values, by giving a tiny increment */ + if (cpicker->use_color_lock) { + if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */ + if (hsv[2] == 0.0f) { + hsv[2] = 0.0001f; + } + } + else { + if (hsv[2] == 0.0f) { + hsv[2] = 0.0001f; + } + if (hsv[2] >= 0.9999f) { + hsv[2] = 0.9999f; + } + } + } + + /* only apply the delta motion, not absolute */ + if (shift) { + float xpos, ypos, hsvo[3], rgbo[3]; + + /* calculate original hsv again */ + copy_v3_v3(hsvo, hsv); + copy_v3_v3(rgbo, data->origvec); + ui_scene_linear_to_perceptual_space(but, rgbo); + ui_color_picker_rgb_to_hsv_compat(rgbo, hsvo); + + /* and original position */ + ui_hsvcircle_pos_from_vals(cpicker, &rect, hsvo, &xpos, &ypos); + + mx_fl = xpos - (data->dragstartx - mx_fl); + my_fl = ypos - (data->dragstarty - my_fl); + } + + ui_hsvcircle_vals_from_pos(&rect, mx_fl, my_fl, hsv, hsv + 1); + + if ((cpicker->use_color_cubic) && (U.color_picker_type == USER_CP_CIRCLE_HSV)) { + hsv[1] = 1.0f - sqrt3f(1.0f - hsv[1]); + } + + if (snap != SNAP_OFF) { + ui_color_snap_hue(snap, &hsv[0]); + } + + ui_color_picker_hsv_to_rgb(hsv, rgb); + + if ((cpicker->use_luminosity_lock)) { + if (!is_zero_v3(rgb)) { + normalize_v3_length(rgb, cpicker->luminosity_lock_value); + } + } + + ui_perceptual_to_scene_linear_space(but, rgb); + ui_but_v3_set(but, rgb); + + data->draglastx = mx; + data->draglasty = my; + + return changed; +} + +#ifdef WITH_INPUT_NDOF +static void ui_ndofedit_but_HSVCIRCLE(uiBut *but, + uiHandleButtonData *data, + const wmNDOFMotionData *ndof, + const enum eSnapType snap, + const bool shift) +{ + ColorPicker *cpicker = but->custom_data; + float *hsv = cpicker->hsv_perceptual; + float rgb[3]; + float phi, r /*, sqr */ /* UNUSED */, v[2]; + const float sensitivity = (shift ? 0.06f : 0.3f) * ndof->dt; + + ui_but_v3_get(but, rgb); + ui_scene_linear_to_perceptual_space(but, rgb); + ui_color_picker_rgb_to_hsv_compat(rgb, hsv); + + /* Convert current color on hue/sat disc to circular coordinates phi, r */ + phi = fmodf(hsv[0] + 0.25f, 1.0f) * -2.0f * (float)M_PI; + r = hsv[1]; + /* sqr = r > 0.0f ? sqrtf(r) : 1; */ /* UNUSED */ + + /* Convert to 2d vectors */ + v[0] = r * cosf(phi); + v[1] = r * sinf(phi); + + /* Use ndof device y and x rotation to move the vector in 2d space */ + v[0] += ndof->rvec[2] * sensitivity; + v[1] += ndof->rvec[0] * sensitivity; + + /* convert back to polar coords on circle */ + phi = atan2f(v[0], v[1]) / (2.0f * (float)M_PI) + 0.5f; + + /* use ndof Y rotation to additionally rotate hue */ + phi += ndof->rvec[1] * sensitivity * 0.5f; + r = len_v2(v); + + /* convert back to hsv values, in range [0,1] */ + hsv[0] = phi; + hsv[1] = r; + + /* exception, when using color wheel in 'locked' value state: + * allow choosing a hue for black values, by giving a tiny increment */ + if (cpicker->use_color_lock) { + if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */ + if (hsv[2] == 0.0f) { + hsv[2] = 0.0001f; + } + } + else { + if (hsv[2] == 0.0f) { + hsv[2] = 0.0001f; + } + if (hsv[2] == 1.0f) { + hsv[2] = 0.9999f; + } + } + } + + if (snap != SNAP_OFF) { + ui_color_snap_hue(snap, &hsv[0]); + } + + hsv_clamp_v(hsv, FLT_MAX); + + ui_color_picker_hsv_to_rgb(hsv, data->vec); + + if (cpicker->use_luminosity_lock) { + if (!is_zero_v3(data->vec)) { + normalize_v3_length(data->vec, cpicker->luminosity_lock_value); + } + } + + ui_perceptual_to_scene_linear_space(but, data->vec); + ui_but_v3_set(but, data->vec); +} +#endif /* WITH_INPUT_NDOF */ + +static int ui_do_but_HSVCIRCLE( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + ColorPicker *cpicker = (ColorPicker *)but->custom_data; + float *hsv = cpicker->hsv_perceptual; + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + const enum eSnapType snap = ui_event_to_snap(event); + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + + return WM_UI_HANDLER_BREAK; + } +#ifdef WITH_INPUT_NDOF + if (event->type == NDOF_MOTION) { + const enum eSnapType snap = ui_event_to_snap(event); + const wmNDOFMotionData *ndof = event->customdata; + + ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->shift != 0); + + button_activate_state(C, but, BUTTON_STATE_EXIT); + ui_apply_but(C, but->block, but, data, true); + + return WM_UI_HANDLER_BREAK; + } +#endif /* WITH_INPUT_NDOF */ + /* XXX hardcoded keymap check.... */ + if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { + int len; + + /* reset only saturation */ + + len = RNA_property_array_length(&but->rnapoin, but->rnaprop); + if (len >= 3) { + float rgb[3], def_hsv[3]; + float *def = (float *)MEM_callocN(sizeof(float) * len, "reset_defaults - float"); + + RNA_property_float_get_default_array(&but->rnapoin, but->rnaprop, def); + ui_color_picker_hsv_to_rgb(def, def_hsv); + + ui_but_v3_get(but, rgb); + ui_color_picker_rgb_to_hsv_compat(rgb, hsv); + + def_hsv[0] = hsv[0]; + def_hsv[2] = hsv[2]; + + hsv_to_rgb_v(def_hsv, rgb); + ui_but_v3_set(but, rgb); + + RNA_property_update(C, &but->rnapoin, but->rnaprop); + + MEM_freeN(def); + } + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + /* XXX hardcoded keymap check.... */ + else if (event->type == WHEELDOWNMOUSE) { + hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f); + ui_but_hsv_set(but); /* converts to rgb */ + ui_numedit_apply(C, block, but, data); + } + else if (event->type == WHEELUPMOUSE) { + hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f); + ui_but_hsv_set(but); /* converts to rgb */ + ui_numedit_apply(C, block, but, data); + } + else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) { + if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) { + const enum eSnapType snap = ui_event_to_snap(event); + + if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int mx) +{ + bool changed = false; + + if (data->draglastx == mx) { + return changed; + } + + if (data->coba->tot == 0) { + return changed; + } + + const float dx = ((float)(mx - data->draglastx)) / BLI_rctf_size_x(&but->rect); + data->dragcbd->pos += dx; + CLAMP(data->dragcbd->pos, 0.0f, 1.0f); + + BKE_colorband_update_sort(data->coba); + data->dragcbd = data->coba->data + data->coba->cur; /* because qsort */ + + data->draglastx = mx; + changed = true; + + return changed; +} + +static int ui_do_but_COLORBAND( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + ColorBand *coba = (ColorBand *)but->poin; + + if (event->ctrl) { + /* insert new key on mouse location */ + const float pos = ((float)(mx - but->rect.xmin)) / BLI_rctf_size_x(&but->rect); + BKE_colorband_element_add(coba, pos); + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + CBData *cbd; + /* ignore zoom-level for mindist */ + int mindist = (50 * UI_DPI_FAC) * block->aspect; + int xco; + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + + /* activate new key when mouse is close */ + int a; + for (a = 0, cbd = coba->data; a < coba->tot; a++, cbd++) { + xco = but->rect.xmin + (cbd->pos * BLI_rctf_size_x(&but->rect)); + xco = abs(xco - mx); + if (a == coba->cur) { + /* Selected one disadvantage. */ + xco += 5; + } + if (xco < mindist) { + coba->cur = a; + mindist = xco; + } + } + + data->dragcbd = coba->data + coba->cur; + data->dragfstart = data->dragcbd->pos; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + } + + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == MOUSEMOVE) { + if (mx != data->draglastx || my != data->draglasty) { + if (ui_numedit_but_COLORBAND(but, data, mx)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) { + if (event->val == KM_PRESS) { + data->dragcbd->pos = data->dragfstart; + BKE_colorband_update_sort(data->coba); + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_CURVE(uiBlock *block, + uiBut *but, + uiHandleButtonData *data, + int evtx, + int evty, + bool snap, + const bool shift) +{ + CurveMapping *cumap = (CurveMapping *)but->poin; + CurveMap *cuma = cumap->cm + cumap->cur; + CurveMapPoint *cmp = cuma->curve; + bool changed = false; + + /* evtx evty and drag coords are absolute mousecoords, + * prevents errors when editing when layout changes */ + int mx = evtx; + int my = evty; + ui_window_to_block(data->region, block, &mx, &my); + int dragx = data->draglastx; + int dragy = data->draglasty; + ui_window_to_block(data->region, block, &dragx, &dragy); + + const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&cumap->curr); + const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&cumap->curr); + + if (snap) { + float d[2]; + + d[0] = mx - data->dragstartx; + d[1] = my - data->dragstarty; + + if (len_squared_v2(d) < (3.0f * 3.0f)) { + snap = false; + } + } + + float fx = (mx - dragx) / zoomx; + float fy = (my - dragy) / zoomy; + + if (data->dragsel != -1) { + CurveMapPoint *cmp_last = NULL; + const float mval_factor = ui_mouse_scale_warp_factor(shift); + bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */ + + fx *= mval_factor; + fy *= mval_factor; + + for (int a = 0; a < cuma->totpoint; a++) { + if (cmp[a].flag & CUMA_SELECT) { + const float origx = cmp[a].x, origy = cmp[a].y; + cmp[a].x += fx; + cmp[a].y += fy; + if (snap) { + cmp[a].x = 0.125f * roundf(8.0f * cmp[a].x); + cmp[a].y = 0.125f * roundf(8.0f * cmp[a].y); + } + if (cmp[a].x != origx || cmp[a].y != origy) { + moved_point = true; + } + + cmp_last = &cmp[a]; + } + } + + BKE_curvemapping_changed(cumap, false); + + if (moved_point) { + data->draglastx = evtx; + data->draglasty = evty; + changed = true; + +#ifdef USE_CONT_MOUSE_CORRECT + /* note: using 'cmp_last' is weak since there may be multiple points selected, + * but in practice this isn't really an issue */ + if (ui_but_is_cursor_warp(but)) { + /* OK but can go outside bounds */ + data->ungrab_mval[0] = but->rect.xmin + ((cmp_last->x - cumap->curr.xmin) * zoomx); + data->ungrab_mval[1] = but->rect.ymin + ((cmp_last->y - cumap->curr.ymin) * zoomy); + BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); + } +#endif + } + + data->dragchange = true; /* mark for selection */ + } + else { + /* clamp for clip */ + if (cumap->flag & CUMA_DO_CLIP) { + if (cumap->curr.xmin - fx < cumap->clipr.xmin) { + fx = cumap->curr.xmin - cumap->clipr.xmin; + } + else if (cumap->curr.xmax - fx > cumap->clipr.xmax) { + fx = cumap->curr.xmax - cumap->clipr.xmax; + } + if (cumap->curr.ymin - fy < cumap->clipr.ymin) { + fy = cumap->curr.ymin - cumap->clipr.ymin; + } + else if (cumap->curr.ymax - fy > cumap->clipr.ymax) { + fy = cumap->curr.ymax - cumap->clipr.ymax; + } + } + + cumap->curr.xmin -= fx; + cumap->curr.ymin -= fy; + cumap->curr.xmax -= fx; + cumap->curr.ymax -= fy; + + data->draglastx = evtx; + data->draglasty = evty; + + changed = true; + } + + return changed; +} + +static int ui_do_but_CURVE( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + bool changed = false; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + CurveMapping *cumap = (CurveMapping *)but->poin; + CurveMap *cuma = cumap->cm + cumap->cur; + const float m_xy[2] = {mx, my}; + float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius */ + int sel = -1; + + if (event->ctrl) { + float f_xy[2]; + BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy); + + BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]); + BKE_curvemapping_changed(cumap, false); + changed = true; + } + + /* check for selecting of a point */ + CurveMapPoint *cmp = cuma->curve; /* ctrl adds point, new malloc */ + for (int a = 0; a < cuma->totpoint; a++) { + float f_xy[2]; + BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[a].x); + const float dist_sq = len_squared_v2v2(m_xy, f_xy); + if (dist_sq < dist_min_sq) { + sel = a; + dist_min_sq = dist_sq; + } + } + + if (sel == -1) { + float f_xy[2], f_xy_prev[2]; + + /* if the click didn't select anything, check if it's clicked on the + * curve itself, and if so, add a point */ + cmp = cuma->table; + + BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[0].x); + + /* with 160px height 8px should translate to the old 0.05 coefficient at no zoom */ + dist_min_sq = square_f(U.dpi_fac * 8.0f); + + /* loop through the curve segment table and find what's near the mouse. */ + for (int i = 1; i <= CM_TABLE; i++) { + copy_v2_v2(f_xy_prev, f_xy); + BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[i].x); + + if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) { + BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy); + + BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]); + BKE_curvemapping_changed(cumap, false); + + changed = true; + + /* reset cmp back to the curve points again, + * rather than drawing segments */ + cmp = cuma->curve; + + /* find newly added point and make it 'sel' */ + for (int a = 0; a < cuma->totpoint; a++) { + if (cmp[a].x == f_xy[0]) { + sel = a; + } + } + break; + } + } + } + + if (sel != -1) { + /* ok, we move a point */ + /* deselect all if this one is deselect. except if we hold shift */ + if (!event->shift) { + for (int a = 0; a < cuma->totpoint; a++) { + cmp[a].flag &= ~CUMA_SELECT; + } + cmp[sel].flag |= CUMA_SELECT; + } + else { + cmp[sel].flag ^= CUMA_SELECT; + } + } + else { + /* move the view */ + data->cancel = true; + } + + data->dragsel = sel; + + data->dragstartx = event->x; + data->dragstarty = event->y; + data->draglastx = event->x; + data->draglasty = event->y; + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == MOUSEMOVE) { + if (event->x != data->draglastx || event->y != data->draglasty) { + + if (ui_numedit_but_CURVE( + block, but, data, event->x, event->y, event->ctrl != 0, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + if (data->dragsel != -1) { + CurveMapping *cumap = (CurveMapping *)but->poin; + CurveMap *cuma = cumap->cm + cumap->cur; + CurveMapPoint *cmp = cuma->curve; + + if (data->dragchange == false) { + /* deselect all, select one */ + if (!event->shift) { + for (int a = 0; a < cuma->totpoint; a++) { + cmp[a].flag &= ~CUMA_SELECT; + } + cmp[data->dragsel].flag |= CUMA_SELECT; + } + } + else { + BKE_curvemapping_changed(cumap, true); /* remove doubles */ + BKE_paint_invalidate_cursor_overlay(scene, view_layer, cumap); + } + } + + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + + return WM_UI_HANDLER_BREAK; + } + + /* UNUSED but keep for now */ + (void)changed; + + return WM_UI_HANDLER_CONTINUE; +} + +/* Same as ui_numedit_but_CURVE with some smaller changes. */ +static bool ui_numedit_but_CURVEPROFILE(uiBlock *block, + uiBut *but, + uiHandleButtonData *data, + int evtx, + int evty, + bool snap, + const bool shift) +{ + CurveProfile *profile = (CurveProfile *)but->poin; + CurveProfilePoint *pts = profile->path; + bool changed = false; + + /* evtx evty and drag coords are absolute mousecoords, + * prevents errors when editing when layout changes */ + int mx = evtx; + int my = evty; + ui_window_to_block(data->region, block, &mx, &my); + int dragx = data->draglastx; + int dragy = data->draglasty; + ui_window_to_block(data->region, block, &dragx, &dragy); + + const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect); + const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect); + + if (snap) { + float d[2] = {mx - data->dragstartx, data->dragstarty}; + + if (len_squared_v2(d) < (9.0f * U.dpi_fac)) { + snap = false; + } + } + + float fx = (mx - dragx) / zoomx; + float fy = (my - dragy) / zoomy; + + if (data->dragsel != -1) { + float last_x, last_y; + const float mval_factor = ui_mouse_scale_warp_factor(shift); + bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */ + + fx *= mval_factor; + fy *= mval_factor; + + /* Move all selected points. */ + const float delta[2] = {fx, fy}; + for (int a = 0; a < profile->path_len; a++) { + /* Don't move the last and first control points. */ + if (pts[a].flag & PROF_SELECT) { + moved_point |= BKE_curveprofile_move_point(profile, &pts[a], snap, delta); + last_x = pts[a].x; + last_y = pts[a].y; + } + else { + /* Move handles when they're selected but the control point isn't. */ + if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H1_SELECT) { + moved_point |= BKE_curveprofile_move_handle(&pts[a], true, snap, delta); + last_x = pts[a].h1_loc[0]; + last_y = pts[a].h1_loc[1]; + } + if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H2_SELECT) { + moved_point |= BKE_curveprofile_move_handle(&pts[a], false, snap, delta); + last_x = pts[a].h2_loc[0]; + last_y = pts[a].h2_loc[1]; + } + } + } + + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + + if (moved_point) { + data->draglastx = evtx; + data->draglasty = evty; + changed = true; +#ifdef USE_CONT_MOUSE_CORRECT + /* note: using 'cmp_last' is weak since there may be multiple points selected, + * but in practice this isn't really an issue */ + if (ui_but_is_cursor_warp(but)) { + /* OK but can go outside bounds */ + data->ungrab_mval[0] = but->rect.xmin + ((last_x - profile->view_rect.xmin) * zoomx); + data->ungrab_mval[1] = but->rect.ymin + ((last_y - profile->view_rect.ymin) * zoomy); + BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval); + } +#endif + } + data->dragchange = true; /* mark for selection */ + } + else { + /* Clamp the view rect when clipping is on. */ + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) { + fx = profile->view_rect.xmin - profile->clip_rect.xmin; + } + else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) { + fx = profile->view_rect.xmax - profile->clip_rect.xmax; + } + if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) { + fy = profile->view_rect.ymin - profile->clip_rect.ymin; + } + else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) { + fy = profile->view_rect.ymax - profile->clip_rect.ymax; + } + } + + profile->view_rect.xmin -= fx; + profile->view_rect.ymin -= fy; + profile->view_rect.xmax -= fx; + profile->view_rect.ymax -= fy; + + data->draglastx = evtx; + data->draglasty = evty; + + changed = true; + } + + return changed; +} + +/** + * Helper for #ui_do_but_CURVEPROFILE. Used to tell whether to select a control point's handles. + */ +static bool point_draw_handles(CurveProfilePoint *point) +{ + return (point->flag & PROF_SELECT && + (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) || + ELEM(point->flag, PROF_H1_SELECT, PROF_H2_SELECT); +} + +/** + * Interaction for curve profile widget. + * \note Uses hardcoded keys rather than the keymap. + */ +static int ui_do_but_CURVEPROFILE( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + CurveProfile *profile = (CurveProfile *)but->poin; + int mx = event->x; + int my = event->y; + + ui_window_to_block(data->region, block, &mx, &my); + + /* Move selected control points. */ + if (event->type == EVT_GKEY && event->val == KM_RELEASE) { + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + return WM_UI_HANDLER_BREAK; + } + + /* Delete selected control points. */ + if (event->type == EVT_XKEY && event->val == KM_RELEASE) { + BKE_curveprofile_remove_by_flag(profile, PROF_SELECT); + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + + /* Selecting, adding, and starting point movements. */ + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + const float m_xy[2] = {mx, my}; + + if (event->ctrl) { + float f_xy[2]; + BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy); + + BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]); + BKE_curveprofile_update(profile, PROF_UPDATE_CLIP); + } + + /* Check for selecting of a point by finding closest point in radius. */ + CurveProfilePoint *pts = profile->path; + float dist_min_sq = square_f(U.dpi_fac * 14.0f); /* 14 pixels radius for selecting points. */ + int i_selected = -1; + short selection_type = 0; /* For handle selection. */ + for (int i = 0; i < profile->path_len; i++) { + float f_xy[2]; + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x); + float dist_sq = len_squared_v2v2(m_xy, f_xy); + if (dist_sq < dist_min_sq) { + i_selected = i; + selection_type = PROF_SELECT; + dist_min_sq = dist_sq; + } + + /* Also select handles if the point is selected and it has the right handle type. */ + if (point_draw_handles(&pts[i])) { + if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) { + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h1_loc); + dist_sq = len_squared_v2v2(m_xy, f_xy); + if (dist_sq < dist_min_sq) { + i_selected = i; + selection_type = PROF_H1_SELECT; + dist_min_sq = dist_sq; + } + } + if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) { + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h2_loc); + dist_sq = len_squared_v2v2(m_xy, f_xy); + if (dist_sq < dist_min_sq) { + i_selected = i; + selection_type = PROF_H2_SELECT; + dist_min_sq = dist_sq; + } + } + } + } + + /* Add a point if the click was close to the path but not a control point or handle. */ + if (i_selected == -1) { + float f_xy[2], f_xy_prev[2]; + CurveProfilePoint *table = profile->table; + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[0].x); + + dist_min_sq = square_f(U.dpi_fac * 8.0f); /* 8 pixel radius from each table point. */ + + /* Loop through the path's high resolution table and find what's near the click. */ + for (int i = 1; i <= PROF_TABLE_LEN(profile->path_len); i++) { + copy_v2_v2(f_xy_prev, f_xy); + BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x); + + if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) { + BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy); + + CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]); + BKE_curveprofile_update(profile, PROF_UPDATE_CLIP); + + /* Get the index of the newly added point. */ + i_selected = (int)(new_pt - profile->path); + BLI_assert(i_selected >= 0 && i_selected <= profile->path_len); + selection_type = PROF_SELECT; + break; + } + } + } + + /* Change the flag for the point(s) if one was selected or added. */ + if (i_selected != -1) { + /* Deselect all if this one is deselected, except if we hold shift. */ + if (event->shift) { + pts[i_selected].flag ^= selection_type; + } + else { + for (int i = 0; i < profile->path_len; i++) { + // pts[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT); + profile->path[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT); + } + profile->path[i_selected].flag |= selection_type; + } + } + else { + /* Move the view. */ + data->cancel = true; + } + + data->dragsel = i_selected; + + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */ + if (event->type == MOUSEMOVE) { + if (mx != data->draglastx || my != data->draglasty) { + if (ui_numedit_but_CURVEPROFILE( + block, but, data, mx, my, event->ctrl != 0, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + /* Finish move. */ + if (data->dragsel != -1) { + + if (data->dragchange == false) { + /* Deselect all, select one. */ + } + else { + /* Remove doubles, clip after move. */ + BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP); + } + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my) +{ + Histogram *hist = (Histogram *)but->poin; + const bool changed = true; + const float dy = my - data->draglasty; + + /* scale histogram values (dy / 10 for better control) */ + const float yfac = min_ff(pow2f(hist->ymax), 1.0f) * 0.5f; + hist->ymax += (dy * 0.1f) * yfac; + + /* 0.1 allows us to see HDR colors up to 10 */ + CLAMP(hist->ymax, 0.1f, 100.0f); + + data->draglastx = mx; + data->draglasty = my; + + return changed; +} + +static int ui_do_but_HISTOGRAM( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) { + ui_numedit_apply(C, block, but, data); + } + + return WM_UI_HANDLER_BREAK; + } + /* XXX hardcoded keymap check.... */ + if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { + Histogram *hist = (Histogram *)but->poin; + hist->ymax = 1.0f; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == EVT_ESCKEY) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == MOUSEMOVE) { + if (mx != data->draglastx || my != data->draglasty) { + if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx, int my) +{ + Scopes *scopes = (Scopes *)but->poin; + const bool changed = true; + + const float dy = my - data->draglasty; + + /* scale waveform values */ + scopes->wavefrm_yfac += dy / 200.0f; + + CLAMP(scopes->wavefrm_yfac, 0.5f, 2.0f); + + data->draglastx = mx; + data->draglasty = my; + + return changed; +} + +static int ui_do_but_WAVEFORM( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if (ui_numedit_but_WAVEFORM(but, data, mx, my)) { + ui_numedit_apply(C, block, but, data); + } + + return WM_UI_HANDLER_BREAK; + } + /* XXX hardcoded keymap check.... */ + if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) { + Scopes *scopes = (Scopes *)but->poin; + scopes->wavefrm_yfac = 1.0f; + + button_activate_state(C, but, BUTTON_STATE_EXIT); + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == EVT_ESCKEY) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == MOUSEMOVE) { + if (mx != data->draglastx || my != data->draglasty) { + if (ui_numedit_but_WAVEFORM(but, data, mx, my)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static bool ui_numedit_but_TRACKPREVIEW( + bContext *C, uiBut *but, uiHandleButtonData *data, int mx, int my, const bool shift) +{ + MovieClipScopes *scopes = (MovieClipScopes *)but->poin; + const bool changed = true; + + float dx = mx - data->draglastx; + float dy = my - data->draglasty; + + if (shift) { + dx /= 5.0f; + dy /= 5.0f; + } + + if (!scopes->track_locked) { + const MovieClip *clip = CTX_data_edit_movieclip(C); + const int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, scopes->scene_framenr); + if (scopes->marker->framenr != clip_framenr) { + scopes->marker = BKE_tracking_marker_ensure(scopes->track, clip_framenr); + } + + scopes->marker->flag &= ~(MARKER_DISABLED | MARKER_TRACKED); + scopes->marker->pos[0] += -dx * scopes->slide_scale[0] / BLI_rctf_size_x(&but->block->rect); + scopes->marker->pos[1] += -dy * scopes->slide_scale[1] / BLI_rctf_size_y(&but->block->rect); + + WM_event_add_notifier(C, NC_MOVIECLIP | NA_EDITED, NULL); + } + + scopes->ok = 0; + + data->draglastx = mx; + data->draglasty = my; + + return changed; +} + +static int ui_do_but_TRACKPREVIEW( + bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event) +{ + int mx = event->x; + int my = event->y; + ui_window_to_block(data->region, block, &mx, &my); + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + if (event->type == LEFTMOUSE && event->val == KM_PRESS) { + data->dragstartx = mx; + data->dragstarty = my; + data->draglastx = mx; + data->draglasty = my; + button_activate_state(C, but, BUTTON_STATE_NUM_EDITING); + + /* also do drag the first time */ + if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + + return WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + if (event->type == EVT_ESCKEY) { + if (event->val == KM_PRESS) { + data->cancel = true; + data->escapecancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + } + else if (event->type == MOUSEMOVE) { + if (mx != data->draglastx || my != data->draglasty) { + if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->shift != 0)) { + ui_numedit_apply(C, block, but, data); + } + } + } + else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *event) +{ + uiHandleButtonData *data = but->active; + int retval = WM_UI_HANDLER_CONTINUE; + + const bool is_disabled = but->flag & UI_BUT_DISABLED; + + /* if but->pointype is set, but->poin should be too */ + BLI_assert(!but->pointype || but->poin); + + /* Only hard-coded stuff here, button interactions with configurable + * keymaps are handled using operators (see #ED_keymap_ui). */ + + if ((data->state == BUTTON_STATE_HIGHLIGHT) || (event->type == EVT_DROP)) { + + /* handle copy and paste */ + bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) && + !event->shift; + const bool do_copy = event->type == EVT_CKEY && is_press_ctrl_but_no_shift; + const bool do_paste = event->type == EVT_VKEY && is_press_ctrl_but_no_shift; + + /* Specific handling for listrows, we try to find their overlapping tex button. */ + if ((do_copy || do_paste) && but->type == UI_BTYPE_LISTROW) { + uiBut *labelbut = ui_but_list_row_text_activate(C, but, data, event, BUTTON_ACTIVATE_OVER); + if (labelbut) { + but = labelbut; + data = but->active; + } + } + + /* do copy first, because it is the only allowed operator when disabled */ + if (do_copy) { + ui_but_copy(C, but, event->alt); + return WM_UI_HANDLER_BREAK; + } + + /* handle menu */ + if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) && + (event->val == KM_PRESS)) { + /* RMB has two options now */ + if (ui_popup_context_menu_for_button(C, but)) { + return WM_UI_HANDLER_BREAK; + } + } + + if (is_disabled) { + return WM_UI_HANDLER_CONTINUE; + } + + if (do_paste) { + ui_but_paste(C, but, data, event->alt); + return WM_UI_HANDLER_BREAK; + } + + /* handle drop */ + if (event->type == EVT_DROP) { + ui_but_drop(C, event, but, data); + } + + if ((data->state == BUTTON_STATE_HIGHLIGHT) && + ELEM(event->type, LEFTMOUSE, EVT_BUT_OPEN, EVT_PADENTER, EVT_RETKEY) && + (event->val == KM_RELEASE) && + /* Only returns true if the event was handled. */ + ui_do_but_extra_operator_icon(C, but, data, event)) { + return WM_UI_HANDLER_BREAK; + } + } + + if (but->flag & UI_BUT_DISABLED) { + return WM_UI_HANDLER_BREAK; + } + + switch (but->type) { + case UI_BTYPE_BUT: + case UI_BTYPE_DECORATOR: + retval = ui_do_but_BUT(C, but, data, event); + break; + case UI_BTYPE_KEY_EVENT: + retval = ui_do_but_KEYEVT(C, but, data, event); + break; + case UI_BTYPE_HOTKEY_EVENT: + retval = ui_do_but_HOTKEYEVT(C, but, data, event); + break; + case UI_BTYPE_TAB: + retval = ui_do_but_TAB(C, block, but, data, event); + break; + case UI_BTYPE_BUT_TOGGLE: + case UI_BTYPE_TOGGLE: + case UI_BTYPE_ICON_TOGGLE: + case UI_BTYPE_ICON_TOGGLE_N: + case UI_BTYPE_TOGGLE_N: + case UI_BTYPE_CHECKBOX: + case UI_BTYPE_CHECKBOX_N: + case UI_BTYPE_ROW: + retval = ui_do_but_TOG(C, but, data, event); + break; + case UI_BTYPE_SCROLL: + retval = ui_do_but_SCROLL(C, block, but, data, event); + break; + case UI_BTYPE_GRIP: + retval = ui_do_but_GRIP(C, block, but, data, event); + break; + case UI_BTYPE_NUM: + retval = ui_do_but_NUM(C, block, but, data, event); + break; + case UI_BTYPE_NUM_SLIDER: + retval = ui_do_but_SLI(C, block, but, data, event); + break; + case UI_BTYPE_LISTBOX: + /* Nothing to do! */ + break; + case UI_BTYPE_LISTROW: + retval = ui_do_but_LISTROW(C, but, data, event); + break; + case UI_BTYPE_ROUNDBOX: + case UI_BTYPE_LABEL: + case UI_BTYPE_IMAGE: + case UI_BTYPE_PROGRESS_BAR: + case UI_BTYPE_NODE_SOCKET: + retval = ui_do_but_EXIT(C, but, data, event); + break; + case UI_BTYPE_HISTOGRAM: + retval = ui_do_but_HISTOGRAM(C, block, but, data, event); + break; + case UI_BTYPE_WAVEFORM: + retval = ui_do_but_WAVEFORM(C, block, but, data, event); + break; + case UI_BTYPE_VECTORSCOPE: + /* Nothing to do! */ + break; + case UI_BTYPE_TEXT: + case UI_BTYPE_SEARCH_MENU: + if ((but->type == UI_BTYPE_SEARCH_MENU) && (but->flag & UI_BUT_VALUE_CLEAR)) { + retval = ui_do_but_SEARCH_UNLINK(C, block, but, data, event); + if (retval & WM_UI_HANDLER_BREAK) { + break; + } + } + retval = ui_do_but_TEX(C, block, but, data, event); + break; + case UI_BTYPE_MENU: + case UI_BTYPE_POPOVER: + case UI_BTYPE_BLOCK: + case UI_BTYPE_PULLDOWN: + retval = ui_do_but_BLOCK(C, but, data, event); + break; + case UI_BTYPE_BUT_MENU: + retval = ui_do_but_BUT(C, but, data, event); + break; + case UI_BTYPE_COLOR: + retval = ui_do_but_COLOR(C, but, data, event); + break; + case UI_BTYPE_UNITVEC: + retval = ui_do_but_UNITVEC(C, block, but, data, event); + break; + case UI_BTYPE_COLORBAND: + retval = ui_do_but_COLORBAND(C, block, but, data, event); + break; + case UI_BTYPE_CURVE: + retval = ui_do_but_CURVE(C, block, but, data, event); + break; + case UI_BTYPE_CURVEPROFILE: + retval = ui_do_but_CURVEPROFILE(C, block, but, data, event); + break; + case UI_BTYPE_HSVCUBE: + retval = ui_do_but_HSVCUBE(C, block, but, data, event); + break; + case UI_BTYPE_HSVCIRCLE: + retval = ui_do_but_HSVCIRCLE(C, block, but, data, event); + break; + case UI_BTYPE_TRACK_PREVIEW: + retval = ui_do_but_TRACKPREVIEW(C, block, but, data, event); + break; + + /* quiet warnings for unhandled types */ + case UI_BTYPE_SEPR: + case UI_BTYPE_SEPR_LINE: + case UI_BTYPE_SEPR_SPACER: + case UI_BTYPE_EXTRA: + break; + } + +#ifdef USE_DRAG_MULTINUM + data = but->active; + if (data) { + if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) || + /* if we started dragging, progress on any event */ + (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP)) { + if (ELEM(but->type, UI_BTYPE_NUM, UI_BTYPE_NUM_SLIDER) && + ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) { + /* initialize! */ + if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_UNSET) { + /* --> (BUTTON_MULTI_INIT_SETUP | BUTTON_MULTI_INIT_DISABLE) */ + + const float margin_y = DRAG_MULTINUM_THRESHOLD_DRAG_Y / sqrtf(block->aspect); + + /* check if we have a vertical gesture */ + if (len_squared_v2(data->multi_data.drag_dir) > (margin_y * margin_y)) { + const float dir_nor_y[2] = {0.0, 1.0f}; + float dir_nor_drag[2]; + + normalize_v2_v2(dir_nor_drag, data->multi_data.drag_dir); + + if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) { + data->multi_data.init = uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP; + data->multi_data.drag_lock_x = event->x; + } + else { + data->multi_data.init = uiHandleButtonMulti::BUTTON_MULTI_INIT_DISABLE; + } + } + } + else if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP) { + /* --> (BUTTON_MULTI_INIT_ENABLE) */ + const float margin_x = DRAG_MULTINUM_THRESHOLD_DRAG_X / sqrtf(block->aspect); + /* Check if we're don't setting buttons. */ + if ((data->str && + ELEM(data->state, BUTTON_STATE_TEXT_EDITING, BUTTON_STATE_NUM_EDITING)) || + ((abs(data->multi_data.drag_lock_x - event->x) > margin_x) && + /* Just to be sure, check we're dragging more horizontally then vertically. */ + abs(event->prevx - event->x) > abs(event->prevy - event->y))) { + if (data->multi_data.has_mbuts) { + ui_multibut_states_create(but, data); + data->multi_data.init = uiHandleButtonMulti::BUTTON_MULTI_INIT_ENABLE; + } + else { + data->multi_data.init = uiHandleButtonMulti::BUTTON_MULTI_INIT_DISABLE; + } + } + } + + if (data->multi_data.init == uiHandleButtonMulti::BUTTON_MULTI_INIT_SETUP) { + if (ui_multibut_states_tag(but, data, event)) { + ED_region_tag_redraw(data->region); + } + } + } + } + } +#endif /* USE_DRAG_MULTINUM */ + + return retval; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Tool Tip + * \{ */ + +static void ui_blocks_set_tooltips(ARegion *region, const bool enable) +{ + if (!region) { + return; + } + + /* we disabled buttons when when they were already shown, and + * re-enable them on mouse move */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + block->tooltipdisabled = !enable; + } +} + +/** + * Recreate tool-tip (use to update dynamic tips) + */ +void UI_but_tooltip_refresh(bContext *C, uiBut *but) +{ + uiHandleButtonData *data = but->active; + if (data) { + bScreen *screen = WM_window_get_active_screen(data->window); + if (screen->tool_tip && screen->tool_tip->region) { + WM_tooltip_refresh(C, data->window); + } + } +} + +/** + * Removes tool-tip timer from active but + * (meaning tool-tip is disabled until it's re-enabled again). + */ +void UI_but_tooltip_timer_remove(bContext *C, uiBut *but) +{ + uiHandleButtonData *data = but->active; + if (data) { + if (data->autoopentimer) { + WM_event_remove_timer(data->wm, data->window, data->autoopentimer); + data->autoopentimer = NULL; + } + + if (data->window) { + WM_tooltip_clear(C, data->window); + } + } +} + +static ARegion *ui_but_tooltip_init( + bContext *C, ARegion *region, int *pass, double *r_pass_delay, bool *r_exit_on_event) +{ + bool is_label = false; + if (*pass == 1) { + is_label = true; + (*pass)--; + (*r_pass_delay) = UI_TOOLTIP_DELAY - UI_TOOLTIP_DELAY_LABEL; + } + + uiBut *but = UI_region_active_but_get(region); + *r_exit_on_event = false; + if (but) { + return UI_tooltip_create_from_button(C, region, but, is_label); + } + return NULL; +} + +static void button_tooltip_timer_reset(bContext *C, uiBut *but) +{ + wmWindowManager *wm = CTX_wm_manager(C); + uiHandleButtonData *data = but->active; + + WM_tooltip_timer_clear(C, data->window); + + if ((U.flag & USER_TOOLTIPS) || (data->tooltip_force)) { + if (!but->block->tooltipdisabled) { + if (!wm->drags.first) { + const bool is_label = UI_but_has_tooltip_label(but); + const double delay = is_label ? UI_TOOLTIP_DELAY_LABEL : UI_TOOLTIP_DELAY; + WM_tooltip_timer_init_ex( + C, data->window, data->area, data->region, ui_but_tooltip_init, delay); + if (is_label) { + bScreen *screen = WM_window_get_active_screen(data->window); + if (screen->tool_tip) { + screen->tool_tip->pass = 1; + } + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button State Handling + * \{ */ + +static bool button_modal_state(uiHandleButtonState state) +{ + return ELEM(state, + BUTTON_STATE_WAIT_RELEASE, + BUTTON_STATE_WAIT_KEY_EVENT, + BUTTON_STATE_NUM_EDITING, + BUTTON_STATE_TEXT_EDITING, + BUTTON_STATE_TEXT_SELECTING, + BUTTON_STATE_MENU_OPEN); +} + +static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state) +{ + uiHandleButtonData *data = but->active; + if (data->state == state) { + return; + } + + /* Highlight has timers for tool-tips and auto open. */ + if (state == BUTTON_STATE_HIGHLIGHT) { + but->flag &= ~UI_SELECT; + + button_tooltip_timer_reset(C, but); + + /* Automatic open pull-down block timer. */ + if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER) || + /* Menu button types may draw as popovers, check for this case + * ignoring other kinds of menus (mainly enums). (see T66538). */ + ((but->type == UI_BTYPE_MENU) && + (UI_but_paneltype_get(but) || ui_but_menu_draw_as_popover(but)))) { + if (data->used_mouse && !data->autoopentimer) { + int time; + + if (but->block->auto_open == true) { /* test for toolbox */ + time = 1; + } + else if ((but->block->flag & UI_BLOCK_LOOP && but->type != UI_BTYPE_BLOCK) || + (but->block->auto_open == true)) { + time = 5 * U.menuthreshold2; + } + else if (U.uiflag & USER_MENUOPENAUTO) { + time = 5 * U.menuthreshold1; + } + else { + time = -1; /* do nothing */ + } + + if (time >= 0) { + data->autoopentimer = WM_event_add_timer( + data->wm, data->window, TIMER, 0.02 * (double)time); + } + } + } + } + else { + but->flag |= UI_SELECT; + UI_but_tooltip_timer_remove(C, but); + } + + /* text editing */ + if (state == BUTTON_STATE_TEXT_EDITING && data->state != BUTTON_STATE_TEXT_SELECTING) { + ui_textedit_begin(C, but, data); + } + else if (data->state == BUTTON_STATE_TEXT_EDITING && state != BUTTON_STATE_TEXT_SELECTING) { + ui_textedit_end(C, but, data); + } + else if (data->state == BUTTON_STATE_TEXT_SELECTING && state != BUTTON_STATE_TEXT_EDITING) { + ui_textedit_end(C, but, data); + } + + /* number editing */ + if (state == BUTTON_STATE_NUM_EDITING) { + if (ui_but_is_cursor_warp(but)) { + WM_cursor_grab_enable(CTX_wm_window(C), WM_CURSOR_WRAP_XY, true, NULL); + } + ui_numedit_begin(but, data); + } + else if (data->state == BUTTON_STATE_NUM_EDITING) { + ui_numedit_end(but, data); + + if (but->flag & UI_BUT_DRIVEN) { + /* Only warn when editing stepping/dragging the value. + * No warnings should show for editing driver expressions though! + */ + if (state != BUTTON_STATE_TEXT_EDITING) { + WM_report(RPT_INFO, + "Can't edit driven number value, see graph editor for the driver setup."); + } + } + + if (ui_but_is_cursor_warp(but)) { + +#ifdef USE_CONT_MOUSE_CORRECT + /* stereo3d has issues with changing cursor location so rather avoid */ + if (data->ungrab_mval[0] != FLT_MAX && !WM_stereo3d_enabled(data->window, false)) { + int mouse_ungrab_xy[2]; + ui_block_to_window_fl( + data->region, but->block, &data->ungrab_mval[0], &data->ungrab_mval[1]); + mouse_ungrab_xy[0] = data->ungrab_mval[0]; + mouse_ungrab_xy[1] = data->ungrab_mval[1]; + + WM_cursor_grab_disable(data->window, mouse_ungrab_xy); + } + else { + WM_cursor_grab_disable(data->window, NULL); + } +#else + WM_cursor_grab_disable(data->window, NULL); +#endif + } + } + /* menu open */ + if (state == BUTTON_STATE_MENU_OPEN) { + ui_block_open_begin(C, but, data); + } + else if (data->state == BUTTON_STATE_MENU_OPEN) { + ui_block_open_end(C, but, data); + } + + /* add a short delay before exiting, to ensure there is some feedback */ + if (state == BUTTON_STATE_WAIT_FLASH) { + data->flashtimer = WM_event_add_timer(data->wm, data->window, TIMER, BUTTON_FLASH_DELAY); + } + else if (data->flashtimer) { + WM_event_remove_timer(data->wm, data->window, data->flashtimer); + data->flashtimer = NULL; + } + + /* add hold timer if it's used */ + if (state == BUTTON_STATE_WAIT_RELEASE && (but->hold_func != NULL)) { + data->hold_action_timer = WM_event_add_timer( + data->wm, data->window, TIMER, BUTTON_AUTO_OPEN_THRESH); + } + else if (data->hold_action_timer) { + WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); + data->hold_action_timer = NULL; + } + + /* add a blocking ui handler at the window handler for blocking, modal states + * but not for popups, because we already have a window level handler*/ + if (!(but->block->handle && but->block->handle->popup)) { + if (button_modal_state(state)) { + if (!button_modal_state(data->state)) { + WM_event_add_ui_handler( + C, &data->window->modalhandlers, ui_handler_region_menu, NULL, data, 0); + } + } + else { + if (button_modal_state(data->state)) { + /* true = postpone free */ + WM_event_remove_ui_handler( + &data->window->modalhandlers, ui_handler_region_menu, NULL, data, true); + } + } + } + + /* wait for mousemove to enable drag */ + if (state == BUTTON_STATE_WAIT_DRAG) { + but->flag &= ~UI_SELECT; + } + + data->state = state; + + if (state != BUTTON_STATE_EXIT) { + /* When objects for eg. are removed, running ui_but_update() can access + * the removed data - so disable update on exit. Also in case of + * highlight when not in a popup menu, we remove because data used in + * button below popup might have been removed by action of popup. Needs + * a more reliable solution... */ + if (state != BUTTON_STATE_HIGHLIGHT || (but->block->flag & UI_BLOCK_LOOP)) { + ui_but_update(but); + } + } + + /* redraw */ + ED_region_tag_redraw_no_rebuild(data->region); +} + +static void button_activate_init(bContext *C, + ARegion *region, + uiBut *but, + uiButtonActivateType type) +{ + /* Only ever one active button! */ + BLI_assert(ui_region_find_active_but(region) == NULL); + + /* setup struct */ + uiHandleButtonData *data = (uiHandleButtonData *)MEM_callocN(sizeof(uiHandleButtonData), + "uiHandleButtonData"); + data->wm = CTX_wm_manager(C); + data->window = CTX_wm_window(C); + data->area = CTX_wm_area(C); + BLI_assert(region != NULL); + data->region = region; + +#ifdef USE_CONT_MOUSE_CORRECT + copy_v2_fl(data->ungrab_mval, FLT_MAX); +#endif + + if (ELEM(but->type, UI_BTYPE_CURVE, UI_BTYPE_CURVEPROFILE, UI_BTYPE_SEARCH_MENU)) { + /* XXX curve is temp */ + } + else { + if ((but->flag & UI_BUT_UPDATE_DELAY) == 0) { + data->interactive = true; + } + } + + data->state = BUTTON_STATE_INIT; + + /* activate button */ + but->flag |= UI_ACTIVE; + + but->active = data; + + /* we disable auto_open in the block after a threshold, because we still + * want to allow auto opening adjacent menus even if no button is activated + * in between going over to the other button, but only for a short while */ + if (type == BUTTON_ACTIVATE_OVER && but->block->auto_open == true) { + if (but->block->auto_open_last + BUTTON_AUTO_OPEN_THRESH < PIL_check_seconds_timer()) { + but->block->auto_open = false; + } + } + + if (type == BUTTON_ACTIVATE_OVER) { + data->used_mouse = true; + } + button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); + + /* activate right away */ + if (but->flag & UI_BUT_IMMEDIATE) { + if (but->type == UI_BTYPE_HOTKEY_EVENT) { + button_activate_state(C, but, BUTTON_STATE_WAIT_KEY_EVENT); + } + /* .. more to be added here */ + } + + if (type == BUTTON_ACTIVATE_OPEN) { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + + /* activate first button in submenu */ + if (data->menu && data->menu->region) { + ARegion *subar = data->menu->region; + uiBlock *subblock = (uiBlock *)subar->uiblocks.first; + uiBut *subbut; + + if (subblock) { + subbut = ui_but_first(subblock); + + if (subbut) { + ui_handle_button_activate(C, subar, subbut, BUTTON_ACTIVATE); + } + } + } + } + else if (type == BUTTON_ACTIVATE_TEXT_EDITING) { + button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); + } + else if (type == BUTTON_ACTIVATE_APPLY) { + button_activate_state(C, but, BUTTON_STATE_WAIT_FLASH); + } + + if (but->type == UI_BTYPE_GRIP) { + const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect)); + WM_cursor_modal_set(data->window, horizontal ? WM_CURSOR_X_MOVE : WM_CURSOR_Y_MOVE); + } + else if (but->type == UI_BTYPE_NUM) { + ui_numedit_set_active(but); + } + + if (UI_but_has_tooltip_label(but)) { + /* Show a label for this button. */ + bScreen *screen = WM_window_get_active_screen(data->window); + if ((PIL_check_seconds_timer() - WM_tooltip_time_closed()) < 0.1) { + WM_tooltip_immediate_init(C, CTX_wm_window(C), data->area, region, ui_but_tooltip_init); + if (screen->tool_tip) { + screen->tool_tip->pass = 1; + } + } + } +} + +static void button_activate_exit( + bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree) +{ + wmWindow *win = data->window; + uiBlock *block = but->block; + + if (but->type == UI_BTYPE_GRIP) { + WM_cursor_modal_restore(win); + } + + /* ensure we are in the exit state */ + if (data->state != BUTTON_STATE_EXIT) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + + /* apply the button action or value */ + if (!onfree) { + ui_apply_but(C, block, but, data, false); + } + +#ifdef USE_DRAG_MULTINUM + if (data->multi_data.has_mbuts) { + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + if (bt->flag & UI_BUT_DRAG_MULTI) { + bt->flag &= ~UI_BUT_DRAG_MULTI; + + if (!data->cancel) { + ui_apply_but_autokey(C, bt); + } + } + } + + ui_multibut_free(data, block); + } +#endif + + /* if this button is in a menu, this will set the button return + * value to the button value and the menu return value to ok, the + * menu return value will be picked up and the menu will close */ + if (block->handle && !(block->flag & UI_BLOCK_KEEP_OPEN)) { + if (!data->cancel || data->escapecancel) { + uiPopupBlockHandle *menu; + + menu = block->handle; + menu->butretval = data->retval; + menu->menuretval = (data->cancel) ? UI_RETURN_CANCEL : UI_RETURN_OK; + } + } + + if (!onfree && !data->cancel) { + /* autokey & undo push */ + ui_apply_but_undo(but); + ui_apply_but_autokey(C, but); + +#ifdef USE_ALLSELECT + { + /* only RNA from this button is used */ + uiBut but_temp = *but; + uiSelectContextStore *selctx_data = &data->select_others; + for (int i = 0; i < selctx_data->elems_len; i++) { + uiSelectContextElem *other = &selctx_data->elems[i]; + but_temp.rnapoin = other->ptr; + ui_apply_but_autokey(C, &but_temp); + } + } +#endif + + /* popup menu memory */ + if (block->flag & UI_BLOCK_POPUP_MEMORY) { + ui_popup_menu_memory_set(block, but); + } + + if (U.runtime.is_dirty == false) { + ui_but_update_preferences_dirty(but); + } + } + + /* Disable tool-tips until mouse-move + last active flag. */ + LISTBASE_FOREACH (uiBlock *, block_iter, &data->region->uiblocks) { + LISTBASE_FOREACH (uiBut *, bt, &block_iter->buttons) { + bt->flag &= ~UI_BUT_LAST_ACTIVE; + } + + block_iter->tooltipdisabled = true; + } + + ui_blocks_set_tooltips(data->region, false); + + /* clean up */ + if (data->str) { + MEM_freeN(data->str); + } + if (data->origstr) { + MEM_freeN(data->origstr); + } + +#ifdef USE_ALLSELECT + ui_selectcontext_end(but, &data->select_others); +#endif + + if (data->changed_cursor) { + WM_cursor_modal_restore(data->window); + } + + /* redraw and refresh (for popups) */ + ED_region_tag_redraw_no_rebuild(data->region); + ED_region_tag_refresh_ui(data->region); + + /* clean up button */ + if (but->active) { + MEM_freeN(but->active); + but->active = NULL; + } + + but->flag &= ~(UI_ACTIVE | UI_SELECT); + but->flag |= UI_BUT_LAST_ACTIVE; + if (!onfree) { + ui_but_update(but); + } + + /* adds empty mousemove in queue for re-init handler, in case mouse is + * still over a button. We cannot just check for this ourselves because + * at this point the mouse may be over a button in another region */ + if (mousemove) { + WM_event_add_mousemove(CTX_wm_window(C)); + } +} + +void ui_but_active_free(const bContext *C, uiBut *but) +{ + /* this gets called when the button somehow disappears while it is still + * active, this is bad for user interaction, but we need to handle this + * case cleanly anyway in case it happens */ + if (but->active) { + uiHandleButtonData *data = but->active; + data->cancel = true; + button_activate_exit((bContext *)C, but, data, false, true); + } +} + +/* returns the active button with an optional checking function */ +static uiBut *ui_context_button_active(const ARegion *region, bool (*but_check_cb)(const uiBut *)) +{ + uiBut *but_found = NULL; + + while (region) { + uiBut *activebut = NULL; + + /* find active button */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->active) { + activebut = but; + } + else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) { + activebut = but; + } + } + } + + if (activebut && (but_check_cb == NULL || but_check_cb(activebut))) { + uiHandleButtonData *data = activebut->active; + + but_found = activebut; + + /* Recurse into opened menu, like color-picker case. */ + if (data && data->menu && (region != data->menu->region)) { + region = data->menu->region; + } + else { + return but_found; + } + } + else { + /* no active button */ + return but_found; + } + } + + return but_found; +} + +static bool ui_context_rna_button_active_test(const uiBut *but) +{ + return (but->rnapoin.data != NULL); +} +static uiBut *ui_context_rna_button_active(const bContext *C) +{ + return ui_context_button_active(CTX_wm_region(C), ui_context_rna_button_active_test); +} + +uiBut *UI_context_active_but_get(const bContext *C) +{ + return ui_context_button_active(CTX_wm_region(C), NULL); +} + +/* + * Version of #UI_context_active_get() that uses the result of #CTX_wm_menu() + * if set. Does not traverse into parent menus, which may be wanted in some + * cases. + */ +uiBut *UI_context_active_but_get_respect_menu(const bContext *C) +{ + ARegion *region_menu = CTX_wm_menu(C); + return ui_context_button_active(region_menu ? region_menu : CTX_wm_region(C), NULL); +} + +uiBut *UI_region_active_but_get(const ARegion *region) +{ + return ui_context_button_active(region, NULL); +} + +uiBut *UI_region_but_find_rect_over(const ARegion *region, const rcti *rect_px) +{ + return ui_but_find_rect_over(region, rect_px); +} + +uiBlock *UI_region_block_find_mouse_over(const struct ARegion *region, + const int xy[2], + bool only_clip) +{ + return ui_block_find_mouse_over_ex(region, xy[0], xy[1], only_clip); +} + +/** + * Version of #UI_context_active_but_get that also returns RNA property info. + * Helper function for insert keyframe, reset to default, etc operators. + * + * \return active button, NULL if none found or if it doesn't contain valid RNA data. + */ +uiBut *UI_context_active_but_prop_get(const bContext *C, + struct PointerRNA *r_ptr, + struct PropertyRNA **r_prop, + int *r_index) +{ + uiBut *activebut = ui_context_rna_button_active(C); + + if (activebut && activebut->rnapoin.data) { + *r_ptr = activebut->rnapoin; + *r_prop = activebut->rnaprop; + *r_index = activebut->rnaindex; + } + else { + memset(r_ptr, 0, sizeof(*r_ptr)); + *r_prop = NULL; + *r_index = 0; + } + + return activebut; +} + +void UI_context_active_but_prop_handle(bContext *C) +{ + uiBut *activebut = ui_context_rna_button_active(C); + if (activebut) { + /* TODO, look into a better way to handle the button change + * currently this is mainly so reset defaults works for the + * operator redo panel - campbell */ + uiBlock *block = activebut->block; + if (block->handle_func) { + block->handle_func(C, block->handle_func_arg, activebut->retval); + } + } +} + +void UI_context_active_but_clear(bContext *C, wmWindow *win, ARegion *region) +{ + wm_event_handler_ui_cancel_ex(C, win, region, false); +} + +wmOperator *UI_context_active_operator_get(const struct bContext *C) +{ + ARegion *region_ctx = CTX_wm_region(C); + + /* background mode */ + if (region_ctx == NULL) { + return NULL; + } + + /* scan active regions ui */ + LISTBASE_FOREACH (uiBlock *, block, ®ion_ctx->uiblocks) { + if (block->ui_operator) { + return block->ui_operator; + } + } + + /* scan popups */ + { + bScreen *screen = CTX_wm_screen(C); + + LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { + if (region == region_ctx) { + continue; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (block->ui_operator) { + return block->ui_operator; + } + } + } + } + + return NULL; +} + +/** + * Try to find a search-box region opened from a button in \a button_region. + */ +ARegion *UI_region_searchbox_region_get(const ARegion *button_region) +{ + uiBut *but = UI_region_active_but_get(button_region); + return (but != NULL) ? but->active->searchbox : NULL; +} + +/* helper function for insert keyframe, reset to default, etc operators */ +void UI_context_update_anim_flag(const bContext *C) +{ + Scene *scene = CTX_data_scene(C); + ARegion *region = CTX_wm_region(C); + struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C); + const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct( + depsgraph, (scene) ? scene->r.cfra : 0.0f); + + while (region) { + /* find active button */ + uiBut *activebut = NULL; + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + ui_but_anim_flag(but, &anim_eval_context); + ui_but_override_flag(CTX_data_main(C), but); + if (UI_but_is_decorator(but)) { + ui_but_anim_decorate_update_from_flag((uiButDecorator *)but); + } + + ED_region_tag_redraw(region); + + if (but->active) { + activebut = but; + } + else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) { + activebut = but; + } + } + } + + if (activebut) { + /* Always recurse into opened menu, so all buttons update (like color-picker). */ + uiHandleButtonData *data = activebut->active; + if (data && data->menu) { + region = data->menu->region; + } + else { + return; + } + } + else { + /* no active button */ + return; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Activation Handling + * \{ */ + +static uiBut *ui_but_find_open_event(ARegion *region, const wmEvent *event) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but == event->customdata) { + return but; + } + } + } + return NULL; +} + +static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *region) +{ + if (event->type == MOUSEMOVE) { + uiBut *but = ui_but_find_mouse_over(region, event); + if (but) { + button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); + + if (event->alt && but->active) { + /* Display tool-tips if holding Alt on mouse-over when tool-tips are disabled in the + * preferences. */ + but->active->tooltip_force = true; + } + } + } + else if (event->type == EVT_BUT_OPEN) { + uiBut *but = ui_but_find_open_event(region, event); + if (but) { + button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); + ui_do_button(C, but->block, but, event); + } + } + + return WM_UI_HANDLER_CONTINUE; +} + +/** + * Exported to interface.c: #UI_but_active_only() + * \note The region is only for the button. + * The context needs to be set by the caller. + */ +void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but) +{ + wmWindow *win = CTX_wm_window(C); + + button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); + + wmEvent event; + wm_event_init_from_window(win, &event); + event.type = EVT_BUT_OPEN; + event.val = KM_PRESS; + event.is_repeat = false; + event.customdata = but; + event.customdatafree = false; + + ui_do_button(C, but->block, but, &event); +} + +/** + * Simulate moving the mouse over a button (or navigating to it with arrow keys). + * + * exported so menus can start with a highlighted button, + * even if the mouse isn't over it + */ +void ui_but_activate_over(bContext *C, ARegion *region, uiBut *but) +{ + button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); +} + +void ui_but_execute_begin(struct bContext *UNUSED(C), + struct ARegion *region, + uiBut *but, + void **active_back) +{ + BLI_assert(region != NULL); + BLI_assert(BLI_findindex(®ion->uiblocks, but->block) != -1); + /* note: ideally we would not have to change 'but->active' however + * some functions we call don't use data (as they should be doing) */ + + *active_back = but->active; + uiHandleButtonData *data = (uiHandleButtonData *)MEM_callocN(sizeof(uiHandleButtonData), + "uiHandleButtonData_Fake"); + but->active = data; + BLI_assert(region != NULL); + data->region = region; +} + +void ui_but_execute_end(struct bContext *C, + struct ARegion *UNUSED(region), + uiBut *but, + void *active_back) +{ + ui_apply_but(C, but->block, but, but->active, true); + + if ((but->flag & UI_BUT_DRAG_MULTI) == 0) { + ui_apply_but_autokey(C, but); + } + /* use onfree event so undo is handled by caller and apply is already done above */ + button_activate_exit((bContext *)C, but, but->active, false, true); + but->active = (uiHandleButtonData *)active_back; +} + +static void ui_handle_button_activate(bContext *C, + ARegion *region, + uiBut *but, + uiButtonActivateType type) +{ + uiBut *oldbut = ui_region_find_active_but(region); + if (oldbut) { + uiHandleButtonData *data = oldbut->active; + data->cancel = true; + button_activate_exit(C, oldbut, data, false, false); + } + + button_activate_init(C, region, but, type); +} + +/** + * Use for key accelerator or default key to activate the button even if its not active. + */ +static bool ui_handle_button_activate_by_type(bContext *C, ARegion *region, uiBut *but) +{ + if (but->type == UI_BTYPE_BUT_MENU) { + /* mainly for operator buttons */ + ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_APPLY); + } + else if (ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) { + /* open sub-menus (like right arrow key) */ + ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN); + } + else if (but->type == UI_BTYPE_MENU) { + /* activate menu items */ + ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE); + } + else { +#ifdef DEBUG + printf("%s: error, unhandled type: %u\n", __func__, but->type); +#endif + return false; + } + return true; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Handle Events for Activated Buttons + * \{ */ + +static bool ui_button_value_default(uiBut *but, double *r_value) +{ + if (but->rnaprop != NULL && ui_but_is_rna_valid(but)) { + const int type = RNA_property_type(but->rnaprop); + if (ELEM(type, PROP_FLOAT, PROP_INT)) { + double default_value; + switch (type) { + case PROP_INT: + if (RNA_property_array_check(but->rnaprop)) { + default_value = (double)RNA_property_int_get_default_index( + &but->rnapoin, but->rnaprop, but->rnaindex); + } + else { + default_value = (double)RNA_property_int_get_default(&but->rnapoin, but->rnaprop); + } + break; + case PROP_FLOAT: + if (RNA_property_array_check(but->rnaprop)) { + default_value = (double)RNA_property_float_get_default_index( + &but->rnapoin, but->rnaprop, but->rnaindex); + } + else { + default_value = (double)RNA_property_float_get_default(&but->rnapoin, but->rnaprop); + } + break; + } + *r_value = default_value; + return true; + } + } + return false; +} + +static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but) +{ + uiHandleButtonData *data = but->active; + const uiHandleButtonState state_orig = data->state; + + uiBlock *block = but->block; + ARegion *region = data->region; + + int retval = WM_UI_HANDLER_CONTINUE; + + if (data->state == BUTTON_STATE_HIGHLIGHT) { + switch (event->type) { + case WINDEACTIVATE: + case EVT_BUT_CANCEL: + data->cancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + break; +#ifdef USE_UI_POPOVER_ONCE + case LEFTMOUSE: { + if (event->val == KM_RELEASE) { + if (block->flag & UI_BLOCK_POPOVER_ONCE) { + if (!(but->flag & UI_BUT_DISABLED)) { + if (ui_but_is_popover_once_compat(but)) { + data->cancel = false; + button_activate_state(C, but, BUTTON_STATE_EXIT); + retval = WM_UI_HANDLER_BREAK; + /* Cancel because this `but` handles all events and we don't want + * the parent button's update function to do anything. + * + * Causes issues with buttons defined by #uiItemFullR_with_popover. */ + block->handle->menuretval = UI_RETURN_CANCEL; + } + else if (ui_but_is_editable_as_text(but)) { + ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_TEXT_EDITING); + retval = WM_UI_HANDLER_BREAK; + } + } + } + } + break; + } +#endif + case MOUSEMOVE: { + uiBut *but_other = ui_but_find_mouse_over(region, event); + bool exit = false; + + /* always deactivate button for pie menus, + * else moving to blank space will leave activated */ + if ((!ui_block_is_menu(block) || ui_block_is_pie_menu(block)) && + !ui_but_contains_point_px(but, region, event->x, event->y)) { + exit = true; + } + else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) { + exit = true; + } + + if (exit) { + data->cancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else if (event->x != event->prevx || event->y != event->prevy) { + /* Re-enable tool-tip on mouse move. */ + ui_blocks_set_tooltips(region, true); + button_tooltip_timer_reset(C, but); + } + + /* Update extra icons states. */ + ui_do_but_extra_operator_icons_mousemove(but, data, event); + + break; + } + case TIMER: { + /* Handle menu auto open timer. */ + if (event->customdata == data->autoopentimer) { + WM_event_remove_timer(data->wm, data->window, data->autoopentimer); + data->autoopentimer = NULL; + + if (ui_but_contains_point_px(but, region, event->x, event->y) || but->active) { + button_activate_state(C, but, BUTTON_STATE_MENU_OPEN); + } + } + + break; + } + /* XXX hardcoded keymap check... but anyway, + * while view changes, tool-tips should be removed */ + case WHEELUPMOUSE: + case WHEELDOWNMOUSE: + case MIDDLEMOUSE: + case MOUSEPAN: + UI_but_tooltip_timer_remove(C, but); + ATTR_FALLTHROUGH; + default: + break; + } + + /* handle button type specific events */ + retval = ui_do_button(C, block, but, event); + } + else if (data->state == BUTTON_STATE_WAIT_RELEASE) { + switch (event->type) { + case WINDEACTIVATE: + data->cancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + break; + + case TIMER: { + if (event->customdata == data->hold_action_timer) { + if (true) { + data->cancel = true; + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + else { + /* Do this so we can still mouse-up, closing the menu and running the button. + * This is nice to support but there are times when the button gets left pressed. + * Keep disabled for now. */ + WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); + data->hold_action_timer = NULL; + } + retval = WM_UI_HANDLER_CONTINUE; + but->hold_func(C, data->region, but); + } + break; + } + case MOUSEMOVE: { + /* deselect the button when moving the mouse away */ + /* also de-activate for buttons that only show highlights */ + if (ui_but_contains_point_px(but, region, event->x, event->y)) { + + /* Drag on a hold button (used in the toolbar) now opens it immediately. */ + if (data->hold_action_timer) { + if (but->flag & UI_SELECT) { + if (len_manhattan_v2v2_int(&event->x, &event->prevx) <= + WM_EVENT_CURSOR_MOTION_THRESHOLD) { + /* pass */ + } + else { + WM_event_remove_timer(data->wm, data->window, data->hold_action_timer); + data->hold_action_timer = WM_event_add_timer(data->wm, data->window, TIMER, 0.0f); + } + } + } + + if (!(but->flag & UI_SELECT)) { + but->flag |= (UI_SELECT | UI_ACTIVE); + data->cancel = false; + ED_region_tag_redraw_no_rebuild(data->region); + } + } + else { + if (but->flag & UI_SELECT) { + but->flag &= ~(UI_SELECT | UI_ACTIVE); + data->cancel = true; + ED_region_tag_redraw_no_rebuild(data->region); + } + } + break; + } + default: + /* otherwise catch mouse release event */ + ui_do_button(C, block, but, event); + break; + } + + retval = WM_UI_HANDLER_BREAK; + } + else if (data->state == BUTTON_STATE_WAIT_FLASH) { + switch (event->type) { + case TIMER: { + if (event->customdata == data->flashtimer) { + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + break; + } + } + + retval = WM_UI_HANDLER_CONTINUE; + } + else if (data->state == BUTTON_STATE_MENU_OPEN) { + /* check for exit because of mouse-over another button */ + switch (event->type) { + case MOUSEMOVE: { + uiBut *bt; + + if (data->menu && data->menu->region) { + if (ui_region_contains_point_px(data->menu->region, event->x, event->y)) { + break; + } + } + + bt = ui_but_find_mouse_over(region, event); + + if (bt && bt->active != data) { + if (but->type != UI_BTYPE_COLOR) { /* exception */ + data->cancel = true; + } + button_activate_state(C, but, BUTTON_STATE_EXIT); + } + break; + } + case RIGHTMOUSE: { + if (event->val == KM_PRESS) { + uiBut *bt = ui_but_find_mouse_over(region, event); + if (bt && bt->active == data) { + button_activate_state(C, bt, BUTTON_STATE_HIGHLIGHT); + } + } + break; + } + } + + ui_do_button(C, block, but, event); + retval = WM_UI_HANDLER_CONTINUE; + } + else { + retval = ui_do_button(C, block, but, event); + // retval = WM_UI_HANDLER_BREAK; XXX why ? + } + + /* may have been re-allocated above (eyedropper for eg) */ + data = but->active; + if (data && data->state == BUTTON_STATE_EXIT) { + uiBut *post_but = data->postbut; + const uiButtonActivateType post_type = data->posttype; + + /* Reset the button value when empty text is typed. */ + if ((data->cancel == false) && (data->str != NULL) && (data->str[0] == '\0') && + (but->rnaprop && ELEM(RNA_property_type(but->rnaprop), PROP_FLOAT, PROP_INT))) { + MEM_SAFE_FREE(data->str); + ui_button_value_default(but, &data->value); + +#ifdef USE_DRAG_MULTINUM + if (data->multi_data.mbuts) { + for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) { + uiButMultiState *state = (uiButMultiState *)l->link; + uiBut *but_iter = state->but; + double default_value; + + if (ui_button_value_default(but_iter, &default_value)) { + ui_but_value_set(but_iter, default_value); + } + } + } + data->multi_data.skip = true; +#endif + } + + button_activate_exit(C, but, data, (post_but == NULL), false); + + /* for jumping to the next button with tab while text editing */ + if (post_but) { + /* The post_but still has previous ranges (without the changes in active button considered), + * needs refreshing the ranges. */ + ui_but_range_set_soft(post_but); + ui_but_range_set_hard(post_but); + + button_activate_init(C, region, post_but, post_type); + } + else if (!((event->type == EVT_BUT_CANCEL) && (event->val == 1))) { + /* XXX issue is because WM_event_add_mousemove(wm) is a bad hack and not reliable, + * if that gets coded better this bypass can go away too. + * + * This is needed to make sure if a button was active, + * it stays active while the mouse is over it. + * This avoids adding mousemoves, see: T33466. */ + if (ELEM(state_orig, BUTTON_STATE_INIT, BUTTON_STATE_HIGHLIGHT, BUTTON_STATE_WAIT_DRAG)) { + if (ui_but_find_mouse_over(region, event) == but) { + button_activate_init(C, region, but, BUTTON_ACTIVATE_OVER); + } + } + } + } + + return retval; +} + +static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox) +{ + int retval = WM_UI_HANDLER_CONTINUE; + int type = event->type, val = event->val; + int scroll_dir = 1; + bool redraw = false; + + uiList *ui_list = (uiList *)listbox->custom_data; + if (!ui_list || !ui_list->dyn_data) { + return retval; + } + uiListDyn *dyn_data = ui_list->dyn_data; + + int mx = event->x; + int my = event->y; + ui_window_to_block(region, listbox->block, &mx, &my); + + /* Convert pan to scroll-wheel. */ + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + + /* 'ui_pan_to_scroll' gives the absolute direction. */ + if (event->is_direction_inverted) { + scroll_dir = -1; + } + + /* If type still is mouse-pan, we call it handled, since delta-y accumulate. */ + /* also see wm_event_system.c do_wheel_ui hack */ + if (type == MOUSEPAN) { + retval = WM_UI_HANDLER_BREAK; + } + } + + if (val == KM_PRESS) { + if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) && + !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) || + ((ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->ctrl && + !IS_EVENT_MOD(event, shift, alt, oskey)))) { + const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop); + int value, min, max, inc; + + /* activate up/down the list */ + value = value_orig; + if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) { + inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? 1 : -1; + } + else { + inc = ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE) ? -1 : 1; + } + + if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) { + /* If we have a display order different from + * collection order, we have some work! */ + int *org_order = (int *)MEM_mallocN(dyn_data->items_shown * sizeof(int), __func__); + const int *new_order = dyn_data->items_filter_neworder; + int org_idx = -1, len = dyn_data->items_len; + int current_idx = -1; + const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE; + + for (int i = 0; i < len; i++) { + if (!dyn_data->items_filter_flags || + ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) { + org_order[new_order ? new_order[++org_idx] : ++org_idx] = i; + if (i == value) { + current_idx = new_order ? new_order[org_idx] : org_idx; + } + } + else if (i == value && org_idx >= 0) { + current_idx = -(new_order ? new_order[org_idx] : org_idx) - 1; + } + } + /* Now, org_order maps displayed indices to real indices, + * and current_idx either contains the displayed index of active value (positive), + * or its more-nearest one (negated). + */ + if (current_idx < 0) { + current_idx = (current_idx * -1) + (inc < 0 ? inc : inc - 1); + } + else { + current_idx += inc; + } + CLAMP(current_idx, 0, dyn_data->items_shown - 1); + value = org_order[current_idx]; + MEM_freeN(org_order); + } + else { + value += inc; + } + + CLAMP(value, 0, dyn_data->items_len - 1); + + RNA_property_int_range(&listbox->rnapoin, listbox->rnaprop, &min, &max); + CLAMP(value, min, max); + + if (value != value_orig) { + RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value); + RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop); + + ui_apply_but_undo(listbox); + + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + redraw = true; + } + retval = WM_UI_HANDLER_BREAK; + } + else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && event->shift) { + /* We now have proper grip, but keep this anyway! */ + if (ui_list->list_grip < (dyn_data->visual_height_min - UI_LIST_AUTO_SIZE_THRESHOLD)) { + ui_list->list_grip = dyn_data->visual_height; + } + ui_list->list_grip += (type == WHEELUPMOUSE) ? -1 : 1; + + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + + redraw = true; + retval = WM_UI_HANDLER_BREAK; + } + else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { + if (dyn_data->height > dyn_data->visual_height) { + /* list template will clamp */ + ui_list->list_scroll += scroll_dir * ((type == WHEELUPMOUSE) ? -1 : 1); + + redraw = true; + retval = WM_UI_HANDLER_BREAK; + } + } + } + + if (redraw) { + ED_region_tag_redraw(region); + ED_region_tag_refresh_ui(region); + } + + return retval; +} + +static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but) +{ + uiHandleButtonData *data = but->active; + uiPopupBlockHandle *menu = data->menu; + + /* copy over return values from the closing menu */ + if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_UPDATE)) { + if (but->type == UI_BTYPE_COLOR) { + copy_v3_v3(data->vec, menu->retvec); + } + else if (but->type == UI_BTYPE_MENU) { + data->value = menu->retvalue; + } + } + + if (menu->menuretval & UI_RETURN_UPDATE) { + if (data->interactive) { + ui_apply_but(C, but->block, but, data, true); + } + else { + ui_but_update(but); + } + + menu->menuretval = 0; + } + + /* now change button state or exit, which will close the submenu */ + if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_CANCEL)) { + if (menu->menuretval != UI_RETURN_OK) { + data->cancel = true; + } + + button_activate_exit(C, but, data, true, false); + } + else if (menu->menuretval & UI_RETURN_OUT) { + if (event->type == MOUSEMOVE && + ui_but_contains_point_px(but, data->region, event->x, event->y)) { + button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); + } + else { + if (ISKEYBOARD(event->type)) { + /* keyboard menu hierarchy navigation, going back to previous level */ + but->active->used_mouse = false; + button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT); + } + else { + data->cancel = true; + button_activate_exit(C, but, data, true, false); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu Towards (mouse motion logic) + * \{ */ + +/** + * Function used to prevent losing the open menu when using nested pull-downs, + * when moving mouse towards the pull-down menu over other buttons that could + * steal the highlight from the current button, only checks: + * + * - while mouse moves in triangular area defined old mouse position and + * left/right side of new menu. + * - only for 1 second. + */ + +static void ui_mouse_motion_towards_init_ex(uiPopupBlockHandle *menu, + const int xy[2], + const bool force) +{ + BLI_assert(((uiBlock *)menu->region->uiblocks.first)->flag & + (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)); + + if (!menu->dotowards || force) { + menu->dotowards = true; + menu->towards_xy[0] = xy[0]; + menu->towards_xy[1] = xy[1]; + + if (force) { + menu->towardstime = DBL_MAX; /* unlimited time */ + } + else { + menu->towardstime = PIL_check_seconds_timer(); + } + } +} + +static void ui_mouse_motion_towards_init(uiPopupBlockHandle *menu, const int xy[2]) +{ + ui_mouse_motion_towards_init_ex(menu, xy, false); +} + +static void ui_mouse_motion_towards_reinit(uiPopupBlockHandle *menu, const int xy[2]) +{ + ui_mouse_motion_towards_init_ex(menu, xy, true); +} + +static bool ui_mouse_motion_towards_check(uiBlock *block, + uiPopupBlockHandle *menu, + const int xy[2], + const bool use_wiggle_room) +{ + BLI_assert(block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)); + + /* annoying fix for T36269, this is a bit odd but in fact works quite well + * don't mouse-out of a menu if another menu has been created after it. + * if this causes problems we could remove it and check on a different fix - campbell */ + if (menu->region->next) { + /* am I the last menu (test) */ + ARegion *region = menu->region->next; + do { + uiBlock *block_iter = (uiBlock *)region->uiblocks.first; + if (block_iter && ui_block_is_menu(block_iter)) { + return true; + } + } while ((region = region->next)); + } + /* annoying fix end! */ + + if (!menu->dotowards) { + return false; + } + + float oldp[2] = {menu->towards_xy[0], menu->towards_xy[1]}; + const float newp[2] = {xy[0], xy[1]}; + if (len_squared_v2v2(oldp, newp) < (4.0f * 4.0f)) { + return menu->dotowards; + } + + /* verify that we are moving towards one of the edges of the + * menu block, in other words, in the triangle formed by the + * initial mouse location and two edge points. */ + rctf rect_px; + ui_block_to_window_rctf(menu->region, block, &rect_px, &block->rect); + + const float margin = MENU_TOWARDS_MARGIN; + + const float p1[2] = {rect_px.xmin - margin, rect_px.ymin - margin}; + const float p2[2] = {rect_px.xmax + margin, rect_px.ymin - margin}; + const float p3[2] = {rect_px.xmax + margin, rect_px.ymax + margin}; + const float p4[2] = {rect_px.xmin - margin, rect_px.ymax + margin}; + + /* allow for some wiggle room, if the user moves a few pixels away, + * don't immediately quit (only for top level menus) */ + if (use_wiggle_room) { + const float cent[2] = {BLI_rctf_cent_x(&rect_px), BLI_rctf_cent_y(&rect_px)}; + float delta[2]; + + sub_v2_v2v2(delta, oldp, cent); + normalize_v2_length(delta, MENU_TOWARDS_WIGGLE_ROOM); + add_v2_v2(oldp, delta); + } + + bool closer = (isect_point_tri_v2(newp, oldp, p1, p2) || + isect_point_tri_v2(newp, oldp, p2, p3) || + isect_point_tri_v2(newp, oldp, p3, p4) || isect_point_tri_v2(newp, oldp, p4, p1)); + + if (!closer) { + menu->dotowards = false; + } + + /* 1 second timer */ + if (PIL_check_seconds_timer() - menu->towardstime > BUTTON_MOUSE_TOWARDS_THRESH) { + menu->dotowards = false; + } + + return menu->dotowards; +} + +#ifdef USE_KEYNAV_LIMIT +static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEvent *event) +{ + keynav->is_keynav = true; + copy_v2_v2_int(keynav->event_xy, &event->x); +} +/** + * Return true if key-input isn't blocking mouse-motion, + * or if the mouse-motion is enough to disable key-input. + */ +static bool ui_mouse_motion_keynav_test(struct uiKeyNavLock *keynav, const wmEvent *event) +{ + if (keynav->is_keynav && + (len_manhattan_v2v2_int(keynav->event_xy, &event->x) > BUTTON_KEYNAV_PX_LIMIT)) { + keynav->is_keynav = false; + } + + return keynav->is_keynav; +} +#endif /* USE_KEYNAV_LIMIT */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu Scroll + * \{ */ + +static char ui_menu_scroll_test(uiBlock *block, int my) +{ + if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) { + if (block->flag & UI_BLOCK_CLIPTOP) { + if (my > block->rect.ymax - UI_MENU_SCROLL_MOUSE) { + return 't'; + } + } + if (block->flag & UI_BLOCK_CLIPBOTTOM) { + if (my < block->rect.ymin + UI_MENU_SCROLL_MOUSE) { + return 'b'; + } + } + } + return 0; +} + +static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float dy) +{ + BLI_assert(dy != 0.0f); + + const int scroll_pad = ui_block_is_menu(block) ? UI_MENU_SCROLL_PAD : UI_UNIT_Y * 0.5f; + + if (dy < 0.0f) { + /* Stop at top item, extra 0.5 UI_UNIT_Y makes it snap nicer. */ + float ymax = -FLT_MAX; + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + ymax = max_ff(ymax, bt->rect.ymax); + } + if (ymax + dy - UI_UNIT_Y * 0.5f < block->rect.ymax - scroll_pad) { + dy = block->rect.ymax - ymax - scroll_pad; + } + } + else { + /* Stop at bottom item, extra 0.5 UI_UNIT_Y makes it snap nicer. */ + float ymin = FLT_MAX; + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + ymin = min_ff(ymin, bt->rect.ymin); + } + if (ymin + dy + UI_UNIT_Y * 0.5f > block->rect.ymin + scroll_pad) { + dy = block->rect.ymin - ymin + scroll_pad; + } + } + + /* remember scroll offset for refreshes */ + block->handle->scrolloffset += dy; + + /* apply scroll offset */ + LISTBASE_FOREACH (uiBut *, bt, &block->buttons) { + bt->rect.ymin += dy; + bt->rect.ymax += dy; + } + + /* set flags again */ + ui_popup_block_scrolltest(block); + + ED_region_tag_redraw(region); +} + +/** Scroll to activated button. */ +static bool ui_menu_scroll_to_but(ARegion *region, uiBlock *block, uiBut *but_target) +{ + float dy = 0.0; + if (block->flag & UI_BLOCK_CLIPTOP) { + if (but_target->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) { + dy = block->rect.ymax - but_target->rect.ymax - UI_MENU_SCROLL_ARROW; + } + } + if (block->flag & UI_BLOCK_CLIPBOTTOM) { + if (but_target->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) { + dy = block->rect.ymin - but_target->rect.ymin + UI_MENU_SCROLL_ARROW; + } + } + if (dy != 0.0f) { + ui_menu_scroll_apply_offset_y(region, block, dy); + return true; + } + return false; +} + +/** Scroll to y location (in block space, see #ui_window_to_block). */ +static bool ui_menu_scroll_to_y(ARegion *region, uiBlock *block, int y) +{ + const char test = ui_menu_scroll_test(block, y); + float dy = 0.0f; + if (test == 't') { + dy = -UI_UNIT_Y; /* scroll to the top */ + } + else if (test == 'b') { + dy = UI_UNIT_Y; /* scroll to the bottom */ + } + if (dy != 0.0f) { + ui_menu_scroll_apply_offset_y(region, block, dy); + return true; + } + return false; +} + +static bool ui_menu_scroll_step(ARegion *region, uiBlock *block, const int scroll_dir) +{ + int my; + if (scroll_dir == 1) { + if ((block->flag & UI_BLOCK_CLIPTOP) == 0) { + return false; + } + my = block->rect.ymax + UI_UNIT_Y; + } + else if (scroll_dir == -1) { + if ((block->flag & UI_BLOCK_CLIPBOTTOM) == 0) { + return false; + } + my = block->rect.ymin - UI_UNIT_Y; + } + else { + BLI_assert(0); + return false; + } + + return ui_menu_scroll_to_y(region, block, my); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu Event Handling + * \{ */ + +static void ui_region_auto_open_clear(ARegion *region) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + block->auto_open = false; + } +} + +/** + * Special function to handle nested menus. + * let the parent menu get the event. + * + * This allows a menu to be open, + * but send key events to the parent if there's no active buttons. + * + * Without this keyboard navigation from menu's wont work. + */ +static bool ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle *menu, + const uiBut *but, + const int level, + const int retval) +{ + if ((level != 0) && (but == NULL)) { + menu->menuretval = UI_RETURN_OUT | UI_RETURN_OUT_PARENT; + (void)retval; /* so release builds with strict flags are happy as well */ + BLI_assert(retval == WM_UI_HANDLER_CONTINUE); + return true; + } + return false; +} + +static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu) +{ + ARegion *region = menu->region; + uiBut *but = ui_region_find_active_but(region); + + if (but) { + /* Its possible there is an active menu item NOT under the mouse, + * in this case ignore mouse clicks outside the button (but Enter etc is accepted) */ + if (event->val == KM_RELEASE) { + /* pass, needed so we can exit active menu-items when click-dragging out of them */ + } + else if (but->type == UI_BTYPE_SEARCH_MENU) { + /* Pass, needed so search popup can have RMB context menu. + * This may be useful for other interactions which happen in the search popup + * without being directly over the search button. */ + } + else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) { + /* pass, skip for dialogs */ + } + else if (!ui_region_contains_point_px(but->active->region, event->x, event->y)) { + /* Pass, needed to click-exit outside of non-floating menus. */ + ui_region_auto_open_clear(but->active->region); + } + else if ((!ELEM(event->type, MOUSEMOVE, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN)) && + ISMOUSE(event->type)) { + if (!ui_but_contains_point_px(but, but->active->region, event->x, event->y)) { + but = NULL; + } + } + } + + int retval; + if (but) { + ScrArea *ctx_area = CTX_wm_area(C); + ARegion *ctx_region = CTX_wm_region(C); + + if (menu->ctx_area) { + CTX_wm_area_set(C, menu->ctx_area); + } + if (menu->ctx_region) { + CTX_wm_region_set(C, menu->ctx_region); + } + + retval = ui_handle_button_event(C, event, but); + + if (menu->ctx_area) { + CTX_wm_area_set(C, ctx_area); + } + if (menu->ctx_region) { + CTX_wm_region_set(C, ctx_region); + } + } + else { + retval = ui_handle_button_over(C, event, region); + } + + return retval; +} + +float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2]) +{ + float seg1[2]; + + if (block->pie_data.flags & UI_PIE_INITIAL_DIRECTION) { + copy_v2_v2(seg1, block->pie_data.pie_center_init); + } + else { + copy_v2_v2(seg1, block->pie_data.pie_center_spawned); + } + + float seg2[2]; + sub_v2_v2v2(seg2, event_xy, seg1); + + const float len = normalize_v2_v2(block->pie_data.pie_dir, seg2); + + if (len < U.pie_menu_threshold * U.dpi_fac) { + block->pie_data.flags |= UI_PIE_INVALID_DIR; + } + else { + block->pie_data.flags &= ~UI_PIE_INVALID_DIR; + } + + return len; +} + +static int ui_handle_menu_event(bContext *C, + const wmEvent *event, + uiPopupBlockHandle *menu, + int level, + const bool is_parent_inside, + const bool is_parent_menu, + const bool is_floating) +{ + uiBut *but; + ARegion *region = menu->region; + uiBlock *block = (uiBlock *)region->uiblocks.first; + + int retval = WM_UI_HANDLER_CONTINUE; + + int mx = event->x; + int my = event->y; + ui_window_to_block(region, block, &mx, &my); + + /* check if mouse is inside block */ + const bool inside = BLI_rctf_isect_pt(&block->rect, mx, my); + /* check for title dragging */ + const bool inside_title = inside && ((my + (UI_UNIT_Y * 1.5f)) > block->rect.ymax); + + /* if there's an active modal button, don't check events or outside, except for search menu */ + but = ui_region_find_active_but(region); + +#ifdef USE_DRAG_POPUP + if (menu->is_grab) { + if (event->type == LEFTMOUSE) { + menu->is_grab = false; + retval = WM_UI_HANDLER_BREAK; + } + else { + if (event->type == MOUSEMOVE) { + int mdiff[2]; + + sub_v2_v2v2_int(mdiff, &event->x, menu->grab_xy_prev); + copy_v2_v2_int(menu->grab_xy_prev, &event->x); + + add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff); + + ui_popup_translate(region, mdiff); + } + + return retval; + } + } +#endif + + if (but && button_modal_state(but->active->state)) { + if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { + /* if a button is activated modal, always reset the start mouse + * position of the towards mechanism to avoid losing focus, + * and don't handle events */ + ui_mouse_motion_towards_reinit(menu, &event->x); + } + } + else if (event->type == TIMER) { + if (event->customdata == menu->scrolltimer) { + ui_menu_scroll_to_y(region, block, my); + } + } + else { + /* for ui_mouse_motion_towards_block */ + if (event->type == MOUSEMOVE) { + if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { + ui_mouse_motion_towards_init(menu, &event->x); + } + + /* add menu scroll timer, if needed */ + if (ui_menu_scroll_test(block, my)) { + if (menu->scrolltimer == NULL) { + menu->scrolltimer = WM_event_add_timer( + CTX_wm_manager(C), CTX_wm_window(C), TIMER, MENU_SCROLL_INTERVAL); + } + } + } + + /* first block own event func */ + if (block->block_event_func && block->block_event_func(C, block, event)) { + /* pass */ + } /* events not for active search menu button */ + else { + int act = 0; + + switch (event->type) { + + /* Closing sub-levels of pull-downs. + * + * The actual event is handled by the button under the cursor. + * This is done so we can right click on menu items even when they have sub-menus open. + */ + case RIGHTMOUSE: + if (inside == false) { + if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { + if (block->saferct.first) { + /* Currently right clicking on a top level pull-down (typically in the header) + * just closes the menu and doesn't support immediately handling the RMB event. + * + * To support we would need UI_RETURN_OUT_PARENT to be handled by + * top-level buttons, not just menus. Note that this isn't very important + * since it's easy to manually close these menus by clicking on them. */ + menu->menuretval = (level > 0 && is_parent_inside) ? UI_RETURN_OUT_PARENT : + UI_RETURN_OUT; + } + } + retval = WM_UI_HANDLER_BREAK; + } + break; + + /* Closing sub-levels of pull-downs. */ + case EVT_LEFTARROWKEY: + if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { + if (block->saferct.first) { + menu->menuretval = UI_RETURN_OUT; + } + } + + retval = WM_UI_HANDLER_BREAK; + break; + + /* Opening sub-levels of pull-downs. */ + case EVT_RIGHTARROWKEY: + if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) { + + if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { + break; + } + + but = ui_region_find_active_but(region); + + if (!but) { + /* no item active, we make first active */ + if (block->direction & UI_DIR_UP) { + but = ui_but_last(block); + } + else { + but = ui_but_first(block); + } + } + + if (but && ELEM(but->type, UI_BTYPE_BLOCK, UI_BTYPE_PULLDOWN)) { + ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE_OPEN); + } + } + + retval = WM_UI_HANDLER_BREAK; + break; + + /* Smooth scrolling for popovers. */ + case MOUSEPAN: { + if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { + /* pass */ + } + else if (!ui_block_is_menu(block)) { + if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) { + const float dy = event->y - event->prevy; + if (dy != 0.0f) { + ui_menu_scroll_apply_offset_y(region, block, dy); + + if (but) { + but->active->cancel = true; + button_activate_exit(C, but, but->active, false, false); + } + WM_event_add_mousemove(CTX_wm_window(C)); + } + } + break; + } + ATTR_FALLTHROUGH; + } + case WHEELUPMOUSE: + case WHEELDOWNMOUSE: { + if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { + /* pass */ + } + else if (!ui_block_is_menu(block)) { + const int scroll_dir = (event->type == WHEELUPMOUSE) ? 1 : -1; + if (ui_menu_scroll_step(region, block, scroll_dir)) { + if (but) { + but->active->cancel = true; + button_activate_exit(C, but, but->active, false, false); + } + WM_event_add_mousemove(CTX_wm_window(C)); + } + break; + } + ATTR_FALLTHROUGH; + } + case EVT_UPARROWKEY: + case EVT_DOWNARROWKEY: + case EVT_PAGEUPKEY: + case EVT_PAGEDOWNKEY: + case EVT_HOMEKEY: + case EVT_ENDKEY: + /* Arrow-keys: only handle for block_loop blocks. */ + if (IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { + /* pass */ + } + else if (inside || (block->flag & UI_BLOCK_LOOP)) { + int type = event->type; + int val = event->val; + + /* Convert pan to scroll-wheel. */ + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + } + + if (val == KM_PRESS) { + /* Determine scroll operation. */ + uiMenuScrollType scrolltype; + const bool ui_block_flipped = (block->flag & UI_BLOCK_IS_FLIP) != 0; + + if (ELEM(type, EVT_PAGEUPKEY, EVT_HOMEKEY)) { + scrolltype = ui_block_flipped ? MENU_SCROLL_TOP : MENU_SCROLL_BOTTOM; + } + else if (ELEM(type, EVT_PAGEDOWNKEY, EVT_ENDKEY)) { + scrolltype = ui_block_flipped ? MENU_SCROLL_BOTTOM : MENU_SCROLL_TOP; + } + else if (ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE)) { + scrolltype = ui_block_flipped ? MENU_SCROLL_UP : MENU_SCROLL_DOWN; + } + else { + scrolltype = ui_block_flipped ? MENU_SCROLL_DOWN : MENU_SCROLL_UP; + } + + if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { + break; + } + +#ifdef USE_KEYNAV_LIMIT + ui_mouse_motion_keynav_init(&menu->keynav_state, event); +#endif + + but = ui_region_find_active_but(region); + if (but) { + /* Apply scroll operation. */ + if (scrolltype == MENU_SCROLL_DOWN) { + but = ui_but_next(but); + } + else if (scrolltype == MENU_SCROLL_UP) { + but = ui_but_prev(but); + } + else if (scrolltype == MENU_SCROLL_TOP) { + but = ui_but_first(block); + } + else if (scrolltype == MENU_SCROLL_BOTTOM) { + but = ui_but_last(block); + } + } + + if (!but) { + /* wrap button or no active button*/ + uiBut *but_wrap = NULL; + if (ELEM(scrolltype, MENU_SCROLL_UP, MENU_SCROLL_BOTTOM)) { + but_wrap = ui_but_last(block); + } + else if (ELEM(scrolltype, MENU_SCROLL_DOWN, MENU_SCROLL_TOP)) { + but_wrap = ui_but_first(block); + } + if (but_wrap) { + but = but_wrap; + } + } + + if (but) { + ui_handle_button_activate(C, region, but, BUTTON_ACTIVATE); + ui_menu_scroll_to_but(region, block, but); + } + } + + retval = WM_UI_HANDLER_BREAK; + } + + break; + + case EVT_ONEKEY: + case EVT_PAD1: + act = 1; + ATTR_FALLTHROUGH; + case EVT_TWOKEY: + case EVT_PAD2: + if (act == 0) { + act = 2; + } + ATTR_FALLTHROUGH; + case EVT_THREEKEY: + case EVT_PAD3: + if (act == 0) { + act = 3; + } + ATTR_FALLTHROUGH; + case EVT_FOURKEY: + case EVT_PAD4: + if (act == 0) { + act = 4; + } + ATTR_FALLTHROUGH; + case EVT_FIVEKEY: + case EVT_PAD5: + if (act == 0) { + act = 5; + } + ATTR_FALLTHROUGH; + case EVT_SIXKEY: + case EVT_PAD6: + if (act == 0) { + act = 6; + } + ATTR_FALLTHROUGH; + case EVT_SEVENKEY: + case EVT_PAD7: + if (act == 0) { + act = 7; + } + ATTR_FALLTHROUGH; + case EVT_EIGHTKEY: + case EVT_PAD8: + if (act == 0) { + act = 8; + } + ATTR_FALLTHROUGH; + case EVT_NINEKEY: + case EVT_PAD9: + if (act == 0) { + act = 9; + } + ATTR_FALLTHROUGH; + case EVT_ZEROKEY: + case EVT_PAD0: + if (act == 0) { + act = 10; + } + + if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) { + int count; + + if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { + break; + } + + /* Only respond to explicit press to avoid the event that opened the menu + * activating an item when the key is held. */ + if (event->is_repeat) { + break; + } + + if (event->alt) { + act += 10; + } + + count = 0; + for (but = (uiBut *)block->buttons.first; but; but = but->next) { + bool doit = false; + + if (!ELEM(but->type, + UI_BTYPE_LABEL, + UI_BTYPE_SEPR, + UI_BTYPE_SEPR_LINE, + UI_BTYPE_IMAGE)) { + count++; + } + + /* exception for rna layer buts */ + if (but->rnapoin.data && but->rnaprop && + ELEM(RNA_property_subtype(but->rnaprop), PROP_LAYER, PROP_LAYER_MEMBER)) { + if (but->rnaindex == act - 1) { + doit = true; + } + } + else if (ELEM(but->type, + UI_BTYPE_BUT, + UI_BTYPE_BUT_MENU, + UI_BTYPE_MENU, + UI_BTYPE_BLOCK, + UI_BTYPE_PULLDOWN) && + count == act) { + doit = true; + } + + if (!(but->flag & UI_BUT_DISABLED) && doit) { + /* activate buttons but open menu's */ + uiButtonActivateType activate; + if (but->type == UI_BTYPE_PULLDOWN) { + activate = BUTTON_ACTIVATE_OPEN; + } + else { + activate = BUTTON_ACTIVATE_APPLY; + } + + ui_handle_button_activate(C, region, but, activate); + break; + } + } + + retval = WM_UI_HANDLER_BREAK; + } + break; + + /* Handle keystrokes on menu items */ + case EVT_AKEY: + case EVT_BKEY: + case EVT_CKEY: + case EVT_DKEY: + case EVT_EKEY: + case EVT_FKEY: + case EVT_GKEY: + case EVT_HKEY: + case EVT_IKEY: + case EVT_JKEY: + case EVT_KKEY: + case EVT_LKEY: + case EVT_MKEY: + case EVT_NKEY: + case EVT_OKEY: + case EVT_PKEY: + case EVT_QKEY: + case EVT_RKEY: + case EVT_SKEY: + case EVT_TKEY: + case EVT_UKEY: + case EVT_VKEY: + case EVT_WKEY: + case EVT_XKEY: + case EVT_YKEY: + case EVT_ZKEY: { + if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) && + !IS_EVENT_MOD(event, shift, ctrl, oskey) && + /* Only respond to explicit press to avoid the event that opened the menu + * activating an item when the key is held. */ + !event->is_repeat) { + if (ui_menu_pass_event_to_parent_if_nonactive(menu, but, level, retval)) { + break; + } + + for (but = (uiBut *)block->buttons.first; but; but = but->next) { + if (!(but->flag & UI_BUT_DISABLED) && but->menu_key == event->type) { + if (but->type == UI_BTYPE_BUT) { + UI_but_execute(C, region, but); + } + else { + ui_handle_button_activate_by_type(C, region, but); + } + break; + } + } + + retval = WM_UI_HANDLER_BREAK; + } + break; + } + } + } + + /* here we check return conditions for menus */ + if (block->flag & UI_BLOCK_LOOP) { + /* If we click outside the block, verify if we clicked on the + * button that opened us, otherwise we need to close, + * + * note that there is an exception for root level menus and + * popups which you can click again to close. + * + * Events handled above may have already set the return value, + * don't overwrite them, see: T61015. + */ + if ((inside == false) && (menu->menuretval == 0)) { + uiSafetyRct *saferct = (uiSafetyRct *)block->saferct.first; + + if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) { + if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) { + if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) { + /* for root menus, allow clicking to close */ + if (block->flag & UI_BLOCK_OUT_1) { + menu->menuretval = UI_RETURN_OK; + } + else { + menu->menuretval = UI_RETURN_OUT; + } + } + else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) { + if (block->flag & UI_BLOCK_OUT_1) { + menu->menuretval = UI_RETURN_OK; + } + else { + menu->menuretval = UI_RETURN_OUT; + } + } + } + else if (ELEM(event->val, KM_RELEASE, KM_CLICK)) { + /* For buttons that use a hold function, + * exit when mouse-up outside the menu. */ + if (block->flag & UI_BLOCK_POPUP_HOLD) { + /* Note, we could check the cursor is over the parent button. */ + menu->menuretval = UI_RETURN_CANCEL; + retval = WM_UI_HANDLER_CONTINUE; + } + } + } + } + + if (menu->menuretval) { + /* pass */ + } +#ifdef USE_KEYNAV_LIMIT + else if ((event->type == MOUSEMOVE) && + ui_mouse_motion_keynav_test(&menu->keynav_state, event)) { + /* Don't handle the mouse-move if we're using key-navigation. */ + retval = WM_UI_HANDLER_BREAK; + } +#endif + else if (event->type == EVT_ESCKEY && event->val == KM_PRESS) { + /* Escape cancels this and all preceding menus. */ + menu->menuretval = UI_RETURN_CANCEL; + } + else if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER) && event->val == KM_PRESS) { + uiBut *but_default = ui_region_find_first_but_test_flag( + region, UI_BUT_ACTIVE_DEFAULT, UI_HIDDEN); + if ((but_default != NULL) && (but_default->active == NULL)) { + if (but_default->type == UI_BTYPE_BUT) { + UI_but_execute(C, region, but_default); + } + else { + ui_handle_button_activate_by_type(C, region, but_default); + } + } + else { + uiBut *but_active = ui_region_find_active_but(region); + + /* enter will always close this block, we let the event + * get handled by the button if it is activated, otherwise we cancel */ + if (but_active == NULL) { + menu->menuretval = UI_RETURN_CANCEL | UI_RETURN_POPUP_OK; + } + } + } +#ifdef USE_DRAG_POPUP + else if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && + (inside && is_floating && inside_title)) { + if (!but || !ui_but_contains_point_px(but, region, event->x, event->y)) { + if (but) { + UI_but_tooltip_timer_remove(C, but); + } + + menu->is_grab = true; + copy_v2_v2_int(menu->grab_xy_prev, &event->x); + retval = WM_UI_HANDLER_BREAK; + } + } +#endif + else { + + /* check mouse moving outside of the menu */ + if (inside == false && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) { + uiSafetyRct *saferct; + + ui_mouse_motion_towards_check(block, menu, &event->x, is_parent_inside == false); + + /* Check for all parent rects, enables arrow-keys to be used. */ + for (saferct = (uiSafetyRct *)block->saferct.first; saferct; saferct = saferct->next) { + /* for mouse move we only check our own rect, for other + * events we check all preceding block rects too to make + * arrow keys navigation work */ + if (event->type != MOUSEMOVE || saferct == block->saferct.first) { + if (BLI_rctf_isect_pt(&saferct->parent, (float)event->x, (float)event->y)) { + break; + } + if (BLI_rctf_isect_pt(&saferct->safety, (float)event->x, (float)event->y)) { + break; + } + } + } + + /* strict check, and include the parent rect */ + if (!menu->dotowards && !saferct) { + if (block->flag & UI_BLOCK_OUT_1) { + menu->menuretval = UI_RETURN_OK; + } + else { + menu->menuretval = UI_RETURN_OUT; + } + } + else if (menu->dotowards && event->type == MOUSEMOVE) { + retval = WM_UI_HANDLER_BREAK; + } + } + } + + /* end switch */ + } + } + + /* if we are didn't handle the event yet, lets pass it on to + * buttons inside this region. disabled inside check .. not sure + * anymore why it was there? but it meant enter didn't work + * for example when mouse was not over submenu */ + if ((event->type == TIMER) || + (/*inside &&*/ (!menu->menuretval || (menu->menuretval & UI_RETURN_UPDATE)) && + retval == WM_UI_HANDLER_CONTINUE)) { + retval = ui_handle_menu_button(C, event, menu); + } + +#ifdef USE_UI_POPOVER_ONCE + if (block->flag & UI_BLOCK_POPOVER_ONCE) { + if ((event->type == LEFTMOUSE) && (event->val == KM_RELEASE)) { + UI_popover_once_clear((uiPopover *)menu->popup_create_vars.arg); + block->flag &= ~UI_BLOCK_POPOVER_ONCE; + } + } +#endif + + /* Don't handle double click events, rehandle as regular press/release. */ + if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) { + return retval; + } + + /* if we set a menu return value, ensure we continue passing this on to + * lower menus and buttons, so always set continue then, and if we are + * inside the region otherwise, ensure we swallow the event */ + if (menu->menuretval) { + return WM_UI_HANDLER_CONTINUE; + } + if (inside) { + return WM_UI_HANDLER_BREAK; + } + return retval; +} + +static int ui_handle_menu_return_submenu(bContext *C, + const wmEvent *event, + uiPopupBlockHandle *menu) +{ + ARegion *region = menu->region; + uiBlock *block = (uiBlock *)region->uiblocks.first; + + uiBut *but = ui_region_find_active_but(region); + + BLI_assert(but); + + uiHandleButtonData *data = but->active; + uiPopupBlockHandle *submenu = data->menu; + + if (submenu->menuretval) { + bool update; + + /* first decide if we want to close our own menu cascading, if + * so pass on the sub menu return value to our own menu handle */ + if ((submenu->menuretval & UI_RETURN_OK) || (submenu->menuretval & UI_RETURN_CANCEL)) { + if (!(block->flag & UI_BLOCK_KEEP_OPEN)) { + menu->menuretval = submenu->menuretval; + menu->butretval = data->retval; + } + } + + update = (submenu->menuretval & UI_RETURN_UPDATE) != 0; + + /* now let activated button in this menu exit, which + * will actually close the submenu too */ + ui_handle_button_return_submenu(C, event, but); + + if (update) { + submenu->menuretval = 0; + } + } + + if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { + /* for cases where close does not cascade, allow the user to + * move the mouse back towards the menu without closing */ + ui_mouse_motion_towards_reinit(menu, &event->x); + } + + if (menu->menuretval) { + return WM_UI_HANDLER_CONTINUE; + } + return WM_UI_HANDLER_BREAK; +} + +static bool ui_but_pie_menu_supported_apply(uiBut *but) +{ + return (!ELEM(but->type, UI_BTYPE_NUM_SLIDER, UI_BTYPE_NUM)); +} + +static int ui_but_pie_menu_apply(bContext *C, + uiPopupBlockHandle *menu, + uiBut *but, + bool force_close) +{ + const int retval = WM_UI_HANDLER_BREAK; + + if (but && ui_but_pie_menu_supported_apply(but)) { + if (but->type == UI_BTYPE_MENU) { + /* forcing the pie menu to close will not handle menus */ + if (!force_close) { + uiBut *active_but = ui_region_find_active_but(menu->region); + + if (active_but) { + button_activate_exit(C, active_but, active_but->active, false, false); + } + + button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OPEN); + return retval; + } + menu->menuretval = UI_RETURN_CANCEL; + } + else { + button_activate_exit((bContext *)C, but, but->active, false, false); + + menu->menuretval = UI_RETURN_OK; + } + } + else { + menu->menuretval = UI_RETURN_CANCEL; + + ED_region_tag_redraw(menu->region); + } + + return retval; +} + +static uiBut *ui_block_pie_dir_activate(uiBlock *block, const wmEvent *event, RadialDirection dir) +{ + if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->pie_dir == dir && !ELEM(but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE)) { + return but; + } + } + } + + return NULL; +} + +static int ui_but_pie_button_activate(bContext *C, uiBut *but, uiPopupBlockHandle *menu) +{ + if (but == NULL) { + return WM_UI_HANDLER_BREAK; + } + + uiBut *active_but = ui_region_find_active_but(menu->region); + + if (active_but) { + button_activate_exit(C, active_but, active_but->active, false, false); + } + + button_activate_init(C, menu->region, but, BUTTON_ACTIVATE_OVER); + return ui_but_pie_menu_apply(C, menu, but, false); +} + +static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu) +{ + /* we block all events, this is modal interaction, + * except for drop events which is described below */ + int retval = WM_UI_HANDLER_BREAK; + + if (event->type == EVT_DROP) { + /* may want to leave this here for later if we support pie ovens */ + + retval = WM_UI_HANDLER_CONTINUE; + } + + ARegion *region = menu->region; + uiBlock *block = (uiBlock *)region->uiblocks.first; + + const bool is_click_style = (block->pie_data.flags & UI_PIE_CLICK_STYLE); + + /* if there's an active modal button, don't check events or outside, except for search menu */ + uiBut *but_active = ui_region_find_active_but(region); + + if (menu->scrolltimer == NULL) { + menu->scrolltimer = WM_event_add_timer( + CTX_wm_manager(C), CTX_wm_window(C), TIMER, PIE_MENU_INTERVAL); + menu->scrolltimer->duration = 0.0; + } + + const double duration = menu->scrolltimer->duration; + + float event_xy[2] = {event->x, event->y}; + + ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]); + + /* Distance from initial point. */ + const float dist = ui_block_calc_pie_segment(block, event_xy); + + if (but_active && button_modal_state(but_active->active->state)) { + retval = ui_handle_menu_button(C, event, menu); + } + else { + if (event->type == TIMER) { + if (event->customdata == menu->scrolltimer) { + /* deactivate initial direction after a while */ + if (duration > 0.01 * U.pie_initial_timeout) { + block->pie_data.flags &= ~UI_PIE_INITIAL_DIRECTION; + } + + /* handle animation */ + if (!(block->pie_data.flags & UI_PIE_ANIMATION_FINISHED)) { + const double final_time = 0.01 * U.pie_animation_timeout; + float fac = duration / final_time; + const float pie_radius = U.pie_menu_radius * UI_DPI_FAC; + + if (fac > 1.0f) { + fac = 1.0f; + block->pie_data.flags |= UI_PIE_ANIMATION_FINISHED; + } + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->pie_dir != UI_RADIAL_NONE) { + float vec[2]; + float center[2]; + + ui_but_pie_dir((RadialDirection)but->pie_dir, vec); + + center[0] = (vec[0] > 0.01f) ? 0.5f : ((vec[0] < -0.01f) ? -0.5f : 0.0f); + center[1] = (vec[1] > 0.99f) ? 0.5f : ((vec[1] < -0.99f) ? -0.5f : 0.0f); + + center[0] *= BLI_rctf_size_x(&but->rect); + center[1] *= BLI_rctf_size_y(&but->rect); + + mul_v2_fl(vec, pie_radius); + add_v2_v2(vec, center); + mul_v2_fl(vec, fac); + add_v2_v2(vec, block->pie_data.pie_center_spawned); + + BLI_rctf_recenter(&but->rect, vec[0], vec[1]); + } + } + block->pie_data.alphafac = fac; + + ED_region_tag_redraw(region); + } + } + + /* Check pie velocity here if gesture has ended. */ + if (block->pie_data.flags & UI_PIE_GESTURE_END_WAIT) { + float len_sq = 10; + + /* use a time threshold to ensure we leave time to the mouse to move */ + if (duration - block->pie_data.duration_gesture > 0.02) { + len_sq = len_squared_v2v2(event_xy, block->pie_data.last_pos); + copy_v2_v2(block->pie_data.last_pos, event_xy); + block->pie_data.duration_gesture = duration; + } + + if (len_sq < 1.0f) { + uiBut *but = ui_region_find_active_but(menu->region); + + if (but) { + return ui_but_pie_menu_apply(C, menu, but, true); + } + } + } + } + + if (event->type == block->pie_data.event_type && !is_click_style) { + if (event->val != KM_RELEASE) { + ui_handle_menu_button(C, event, menu); + + if (len_squared_v2v2(event_xy, block->pie_data.pie_center_init) > PIE_CLICK_THRESHOLD_SQ) { + block->pie_data.flags |= UI_PIE_DRAG_STYLE; + } + /* why redraw here? It's simple, we are getting many double click events here. + * Those operate like mouse move events almost */ + ED_region_tag_redraw(region); + } + else { + if ((duration < 0.01 * U.pie_tap_timeout) && + !(block->pie_data.flags & UI_PIE_DRAG_STYLE)) { + block->pie_data.flags |= UI_PIE_CLICK_STYLE; + } + else { + uiBut *but = ui_region_find_active_but(menu->region); + + if (but && (U.pie_menu_confirm > 0) && + (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) { + return ui_but_pie_menu_apply(C, menu, but, true); + } + + retval = ui_but_pie_menu_apply(C, menu, but, true); + } + } + } + else { + /* direction from numpad */ + RadialDirection num_dir = UI_RADIAL_NONE; + + switch (event->type) { + case MOUSEMOVE: + if (!is_click_style) { + const float len_sq = len_squared_v2v2(event_xy, block->pie_data.pie_center_init); + + /* here we use the initial position explicitly */ + if (len_sq > PIE_CLICK_THRESHOLD_SQ) { + block->pie_data.flags |= UI_PIE_DRAG_STYLE; + } + + /* here instead, we use the offset location to account for the initial + * direction timeout */ + if ((U.pie_menu_confirm > 0) && + (dist >= U.dpi_fac * (U.pie_menu_threshold + U.pie_menu_confirm))) { + block->pie_data.flags |= UI_PIE_GESTURE_END_WAIT; + copy_v2_v2(block->pie_data.last_pos, event_xy); + block->pie_data.duration_gesture = duration; + } + } + + ui_handle_menu_button(C, event, menu); + + /* mouse move should always refresh the area for pie menus */ + ED_region_tag_redraw(region); + break; + + case LEFTMOUSE: + if (is_click_style) { + if (block->pie_data.flags & UI_PIE_INVALID_DIR) { + menu->menuretval = UI_RETURN_CANCEL; + } + else { + retval = ui_handle_menu_button(C, event, menu); + } + } + break; + + case EVT_ESCKEY: + case RIGHTMOUSE: + menu->menuretval = UI_RETURN_CANCEL; + break; + + case EVT_AKEY: + case EVT_BKEY: + case EVT_CKEY: + case EVT_DKEY: + case EVT_EKEY: + case EVT_FKEY: + case EVT_GKEY: + case EVT_HKEY: + case EVT_IKEY: + case EVT_JKEY: + case EVT_KKEY: + case EVT_LKEY: + case EVT_MKEY: + case EVT_NKEY: + case EVT_OKEY: + case EVT_PKEY: + case EVT_QKEY: + case EVT_RKEY: + case EVT_SKEY: + case EVT_TKEY: + case EVT_UKEY: + case EVT_VKEY: + case EVT_WKEY: + case EVT_XKEY: + case EVT_YKEY: + case EVT_ZKEY: { + if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) && + !IS_EVENT_MOD(event, shift, ctrl, oskey)) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->menu_key == event->type) { + ui_but_pie_button_activate(C, but, menu); + } + } + } + break; + } + +#define CASE_NUM_TO_DIR(n, d) \ + case (EVT_ZEROKEY + n): \ + case (EVT_PAD0 + n): { \ + if (num_dir == UI_RADIAL_NONE) { \ + num_dir = d; \ + } \ + } \ + (void)0 + + CASE_NUM_TO_DIR(1, UI_RADIAL_SW); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(2, UI_RADIAL_S); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(3, UI_RADIAL_SE); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(4, UI_RADIAL_W); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(6, UI_RADIAL_E); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(7, UI_RADIAL_NW); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(8, UI_RADIAL_N); + ATTR_FALLTHROUGH; + CASE_NUM_TO_DIR(9, UI_RADIAL_NE); + { + uiBut *but = ui_block_pie_dir_activate(block, event, num_dir); + retval = ui_but_pie_button_activate(C, but, menu); + break; + } +#undef CASE_NUM_TO_DIR + default: + retval = ui_handle_menu_button(C, event, menu); + break; + } + } + } + + return retval; +} + +static int ui_handle_menus_recursive(bContext *C, + const wmEvent *event, + uiPopupBlockHandle *menu, + int level, + const bool is_parent_inside, + const bool is_parent_menu, + const bool is_floating) +{ + int retval = WM_UI_HANDLER_CONTINUE; + bool do_towards_reinit = false; + + /* check if we have a submenu, and handle events for it first */ + uiBut *but = ui_region_find_active_but(menu->region); + uiHandleButtonData *data = (but) ? but->active : NULL; + uiPopupBlockHandle *submenu = (data) ? data->menu : NULL; + + if (submenu) { + uiBlock *block = (uiBlock *)menu->region->uiblocks.first; + const bool is_menu = ui_block_is_menu(block); + bool inside = false; + /* root pie menus accept the key that spawned + * them as double click to improve responsiveness */ + const bool do_recursion = (!(block->flag & UI_BLOCK_RADIAL) || + event->type != block->pie_data.event_type); + + if (do_recursion) { + if (is_parent_inside == false) { + int mx = event->x; + int my = event->y; + ui_window_to_block(menu->region, block, &mx, &my); + inside = BLI_rctf_isect_pt(&block->rect, mx, my); + } + + retval = ui_handle_menus_recursive( + C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false); + } + } + + /* now handle events for our own menu */ + if (retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) { + const bool do_but_search = (but && (but->type == UI_BTYPE_SEARCH_MENU)); + if (submenu && submenu->menuretval) { + const bool do_ret_out_parent = (submenu->menuretval & UI_RETURN_OUT_PARENT) != 0; + retval = ui_handle_menu_return_submenu(C, event, menu); + submenu = NULL; /* hint not to use this, it may be freed by call above */ + (void)submenu; + /* we may want to quit the submenu and handle the even in this menu, + * if its important to use it, check 'data->menu' first */ + if (((retval == WM_UI_HANDLER_BREAK) && do_ret_out_parent) == false) { + /* skip applying the event */ + return retval; + } + } + + if (do_but_search) { + uiBlock *block = (uiBlock *)menu->region->uiblocks.first; + + retval = ui_handle_menu_button(C, event, menu); + + if (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER)) { + /* when there is a active search button and we close it, + * we need to reinit the mouse coords T35346. */ + if (ui_region_find_active_but(menu->region) != but) { + do_towards_reinit = true; + } + } + } + else { + uiBlock *block = (uiBlock *)menu->region->uiblocks.first; + uiBut *listbox = ui_list_find_mouse_over(menu->region, event); + + if (block->flag & UI_BLOCK_RADIAL) { + retval = ui_pie_handler(C, event, menu); + } + else if (event->type == LEFTMOUSE || event->val != KM_DBL_CLICK) { + bool handled = false; + + if (listbox) { + const int retval_test = ui_handle_list_event(C, event, menu->region, listbox); + if (retval_test != WM_UI_HANDLER_CONTINUE) { + retval = retval_test; + handled = true; + } + } + + if (handled == false) { + retval = ui_handle_menu_event( + C, event, menu, level, is_parent_inside, is_parent_menu, is_floating); + } + } + } + } + + if (do_towards_reinit) { + ui_mouse_motion_towards_reinit(menu, &event->x); + } + + return retval; +} + +/** + * Allow setting menu return value from externals. + * E.g. WM might need to do this for exiting files correctly. + */ +void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable) +{ + uiPopupBlockHandle *menu = block->handle; + if (menu) { + menu->menuretval = enable ? (menu->menuretval | retval) : (menu->menuretval & retval); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Event Handlers + * \{ */ + +static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(userdata)) +{ + /* here we handle buttons at the region level, non-modal */ + ARegion *region = CTX_wm_region(C); + int retval = WM_UI_HANDLER_CONTINUE; + + if (region == NULL || BLI_listbase_is_empty(®ion->uiblocks)) { + return retval; + } + + /* either handle events for already activated button or try to activate */ + uiBut *but = ui_region_find_active_but(region); + uiBut *listbox = ui_list_find_mouse_over(region, event); + + retval = ui_handler_panel_region(C, event, region, listbox ? listbox : but); + + if (retval == WM_UI_HANDLER_CONTINUE && listbox) { + retval = ui_handle_list_event(C, event, region, listbox); + + /* interactions with the listbox should disable tips */ + if (retval == WM_UI_HANDLER_BREAK) { + if (but) { + UI_but_tooltip_timer_remove(C, but); + } + } + } + + if (retval == WM_UI_HANDLER_CONTINUE) { + if (but) { + retval = ui_handle_button_event(C, event, but); + } + else { + retval = ui_handle_button_over(C, event, region); + } + } + + /* Re-enable tool-tips. */ + if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { + ui_blocks_set_tooltips(region, true); + } + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); + + return retval; +} + +static void ui_region_handler_remove(bContext *C, void *UNUSED(userdata)) +{ + ARegion *region = CTX_wm_region(C); + if (region == NULL) { + return; + } + + UI_blocklist_free(C, ®ion->uiblocks); + + bScreen *screen = CTX_wm_screen(C); + if (screen == NULL) { + return; + } + + /* delayed apply callbacks, but not for screen level regions, those + * we rather do at the very end after closing them all, which will + * be done in ui_region_handler/window */ + if (BLI_findindex(&screen->regionbase, region) == -1) { + ui_apply_but_funcs_after(C); + } +} + +/* handle buttons at the window level, modal, for example while + * number sliding, text editing, or when a menu block is open */ +static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSED(userdata)) +{ + ARegion *menu_region = CTX_wm_menu(C); + ARegion *region = menu_region ? menu_region : CTX_wm_region(C); + int retval = WM_UI_HANDLER_CONTINUE; + + uiBut *but = ui_region_find_active_but(region); + + if (but) { + bScreen *screen = CTX_wm_screen(C); + uiBut *but_other; + + /* handle activated button events */ + uiHandleButtonData *data = but->active; + + if ((data->state == BUTTON_STATE_MENU_OPEN) && + /* Make sure this popup isn't dragging a button. + * can happen with popovers (see T67882). */ + (ui_region_find_active_but(data->menu->region) == NULL) && + /* make sure mouse isn't inside another menu (see T43247) */ + (ui_screen_region_find_mouse_over(screen, event) == NULL) && + (ELEM(but->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) && + (but_other = ui_but_find_mouse_over(region, event)) && (but != but_other) && + (ELEM(but_other->type, UI_BTYPE_PULLDOWN, UI_BTYPE_POPOVER, UI_BTYPE_MENU)) && + /* Hover-opening menu's doesn't work well for buttons over one another + * along the same axis the menu is opening on (see T71719). */ + (((data->menu->direction & (UI_DIR_LEFT | UI_DIR_RIGHT)) && + BLI_rctf_isect_rect_x(&but->rect, &but_other->rect, NULL)) || + ((data->menu->direction & (UI_DIR_DOWN | UI_DIR_UP)) && + BLI_rctf_isect_rect_y(&but->rect, &but_other->rect, NULL)))) { + /* if mouse moves to a different root-level menu button, + * open it to replace the current menu */ + if ((but_other->flag & UI_BUT_DISABLED) == 0) { + ui_handle_button_activate(C, region, but_other, BUTTON_ACTIVATE_OVER); + button_activate_state(C, but_other, BUTTON_STATE_MENU_OPEN); + retval = WM_UI_HANDLER_BREAK; + } + } + else if (data->state == BUTTON_STATE_MENU_OPEN) { + /* handle events for menus and their buttons recursively, + * this will handle events from the top to the bottom menu */ + if (data->menu) { + retval = ui_handle_menus_recursive(C, event, data->menu, 0, false, false, false); + } + + /* handle events for the activated button */ + if ((data->menu && (retval == WM_UI_HANDLER_CONTINUE)) || (event->type == TIMER)) { + if (data->menu && data->menu->menuretval) { + ui_handle_button_return_submenu(C, event, but); + retval = WM_UI_HANDLER_BREAK; + } + else { + retval = ui_handle_button_event(C, event, but); + } + } + } + else { + /* handle events for the activated button */ + retval = ui_handle_button_event(C, event, but); + } + } + + /* Re-enable tool-tips. */ + if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { + ui_blocks_set_tooltips(region, true); + } + + if (but && but->active && but->active->menu) { + /* Set correct context menu-region. The handling button above breaks if we set the region + * first, so only set it for executing the after-funcs. */ + CTX_wm_menu_set(C, but->active->menu->region); + } + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); + + /* Reset to previous context region. */ + CTX_wm_menu_set(C, menu_region); + + /* Don't handle double-click events, + * these will be converted into regular clicks which we handle. */ + if (retval == WM_UI_HANDLER_CONTINUE) { + if (event->val == KM_DBL_CLICK) { + return WM_UI_HANDLER_CONTINUE; + } + } + + /* we block all events, this is modal interaction */ + return WM_UI_HANDLER_BREAK; +} + +/* two types of popups, one with operator + enum, other with regular callbacks */ +static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata) +{ + uiPopupBlockHandle *menu = (uiPopupBlockHandle *)userdata; + /* we block all events, this is modal interaction, + * except for drop events which is described below */ + int retval = WM_UI_HANDLER_BREAK; + bool reset_pie = false; + + ARegion *menu_region = CTX_wm_menu(C); + CTX_wm_menu_set(C, menu->region); + + if (event->type == EVT_DROP || event->val == KM_DBL_CLICK) { + /* EVT_DROP: + * If we're handling drop event we'll want it to be handled by popup callee as well, + * so it'll be possible to perform such operations as opening .blend files by dropping + * them into blender, even if there's opened popup like splash screen (sergey). + * KM_DBL_CLICK: + * Continue in case of double click so wm_handlers_do calls handler again with KM_PRESS + * event. This is needed to ensure correct button handling for fast clicking (T47532). + */ + + retval = WM_UI_HANDLER_CONTINUE; + } + + ui_handle_menus_recursive(C, event, menu, 0, false, false, true); + + /* free if done, does not free handle itself */ + if (menu->menuretval) { + wmWindow *win = CTX_wm_window(C); + /* copy values, we have to free first (closes region) */ + const uiPopupBlockHandle temp = *menu; + uiBlock *block = (uiBlock *)menu->region->uiblocks.first; + + /* set last pie event to allow chained pie spawning */ + if (block->flag & UI_BLOCK_RADIAL) { + win->pie_event_type_last = block->pie_data.event_type; + reset_pie = true; + } + + ui_popup_block_free(C, menu); + UI_popup_handlers_remove(&win->modalhandlers, menu); + CTX_wm_menu_set(C, NULL); + +#ifdef USE_DRAG_TOGGLE + { + WM_event_free_ui_handler_all(C, + &win->modalhandlers, + ui_handler_region_drag_toggle, + ui_handler_region_drag_toggle_remove); + } +#endif + + if ((temp.menuretval & UI_RETURN_OK) || (temp.menuretval & UI_RETURN_POPUP_OK)) { + if (temp.popup_func) { + temp.popup_func(C, temp.popup_arg, temp.retvalue); + } + } + else if (temp.cancel_func) { + temp.cancel_func(C, temp.popup_arg); + } + + WM_event_add_mousemove(win); + } + else { + /* Re-enable tool-tips */ + if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) { + ui_blocks_set_tooltips(menu->region, true); + } + } + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); + + if (reset_pie) { + /* Reacquire window in case pie invalidates it somehow. */ + wmWindow *win = CTX_wm_window(C); + + if (win) { + win->pie_event_type_last = EVENT_NONE; + } + } + + CTX_wm_region_set(C, menu_region); + + return retval; +} + +static void ui_popup_handler_remove(bContext *C, void *userdata) +{ + uiPopupBlockHandle *menu = (uiPopupBlockHandle *)userdata; + + /* More correct would be to expect UI_RETURN_CANCEL here, but not wanting to + * cancel when removing handlers because of file exit is a rare exception. + * So instead of setting cancel flag for all menus before removing handlers, + * just explicitly flag menu with UI_RETURN_OK to avoid canceling it. */ + if ((menu->menuretval & UI_RETURN_OK) == 0 && menu->cancel_func) { + menu->cancel_func(C, menu->popup_arg); + } + + /* free menu block if window is closed for some reason */ + ui_popup_block_free(C, menu); + + /* delayed apply callbacks */ + ui_apply_but_funcs_after(C); +} + +void UI_region_handlers_add(ListBase *handlers) +{ + WM_event_remove_ui_handler(handlers, ui_region_handler, ui_region_handler_remove, NULL, false); + WM_event_add_ui_handler(NULL, handlers, ui_region_handler, ui_region_handler_remove, NULL, 0); +} + +void UI_popup_handlers_add(bContext *C, + ListBase *handlers, + uiPopupBlockHandle *popup, + const char flag) +{ + WM_event_add_ui_handler(C, handlers, ui_popup_handler, ui_popup_handler_remove, popup, flag); +} + +void UI_popup_handlers_remove(ListBase *handlers, uiPopupBlockHandle *popup) +{ + LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) { + if (handler_base->type == WM_HANDLER_TYPE_UI) { + wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base; + + if (handler->handle_fn == ui_popup_handler && + handler->remove_fn == ui_popup_handler_remove && handler->user_data == popup) { + /* tag refresh parent popup */ + wmEventHandler_UI *handler_next = (wmEventHandler_UI *)handler->head.next; + if (handler_next && handler_next->head.type == WM_HANDLER_TYPE_UI && + handler_next->handle_fn == ui_popup_handler && + handler_next->remove_fn == ui_popup_handler_remove) { + uiPopupBlockHandle *parent_popup = (uiPopupBlockHandle *)handler_next->user_data; + ED_region_tag_refresh_ui(parent_popup->region); + } + break; + } + } + } + + WM_event_remove_ui_handler(handlers, ui_popup_handler, ui_popup_handler_remove, popup, false); +} + +void UI_popup_handlers_remove_all(bContext *C, ListBase *handlers) +{ + WM_event_free_ui_handler_all(C, handlers, ui_popup_handler, ui_popup_handler_remove); +} + +bool UI_textbutton_activate_rna(const bContext *C, + ARegion *region, + const void *rna_poin_data, + const char *rna_prop_id) +{ + uiBlock *block_text = NULL; + uiBut *but_text = NULL; + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->type == UI_BTYPE_TEXT) { + if (but->rnaprop && but->rnapoin.data == rna_poin_data) { + if (STREQ(RNA_property_identifier(but->rnaprop), rna_prop_id)) { + block_text = block; + but_text = but; + break; + } + } + } + } + if (but_text) { + break; + } + } + + if (but_text) { + UI_but_active_only(C, region, block_text, but_text); + return true; + } + return false; +} + +bool UI_textbutton_activate_but(const bContext *C, uiBut *actbut) +{ + ARegion *region = CTX_wm_region(C); + uiBlock *block_text = NULL; + uiBut *but_text = NULL; + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but == actbut && but->type == UI_BTYPE_TEXT) { + block_text = block; + but_text = but; + break; + } + } + + if (but_text) { + break; + } + } + + if (but_text) { + UI_but_active_only(C, region, block_text, but_text); + return true; + } + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Public Utilities + * \{ */ + +/* is called by notifier */ +void UI_screen_free_active_but(const bContext *C, bScreen *screen) +{ + wmWindow *win = CTX_wm_window(C); + + ED_screen_areas_iter (win, screen, area) { + LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { + uiBut *but = ui_region_find_active_but(region); + if (but) { + uiHandleButtonData *data = but->active; + + if (data->menu == NULL && data->searchbox == NULL) { + if (data->state == BUTTON_STATE_HIGHLIGHT) { + ui_but_active_free(C, but); + } + } + } + } + } +} + +/* returns true if highlighted button allows drop of names */ +/* called in region context */ +bool UI_but_active_drop_name(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + uiBut *but = ui_region_find_active_but(region); + + if (but) { + if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { + return true; + } + } + + return false; +} + +bool UI_but_active_drop_color(bContext *C) +{ + ARegion *region = CTX_wm_region(C); + + if (region) { + uiBut *but = ui_region_find_active_but(region); + + if (but && but->type == UI_BTYPE_COLOR) { + return true; + } + } + + return false; +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_icons_event.c b/source/blender/editors/interface/interface_icons_event.c deleted file mode 100644 index 3962ff6a702..00000000000 --- a/source/blender/editors/interface/interface_icons_event.c +++ /dev/null @@ -1,236 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - * - * A special set of icons to represent input devices, - * this is a mix of text (via fonts) and a handful of custom glyphs for special keys. - * - * Event codes are used as identifiers. - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "GPU_batch.h" -#include "GPU_immediate.h" -#include "GPU_state.h" - -#include "BLI_blenlib.h" -#include "BLI_math_vector.h" -#include "BLI_utildefines.h" - -#include "DNA_brush_types.h" -#include "DNA_curve_types.h" -#include "DNA_dynamicpaint_types.h" -#include "DNA_object_types.h" -#include "DNA_screen_types.h" -#include "DNA_space_types.h" -#include "DNA_workspace_types.h" - -#include "RNA_access.h" -#include "RNA_enum_types.h" - -#include "BKE_appdir.h" -#include "BKE_icons.h" -#include "BKE_studiolight.h" - -#include "IMB_imbuf.h" -#include "IMB_imbuf_types.h" -#include "IMB_thumbs.h" - -#include "BLF_api.h" - -#include "DEG_depsgraph.h" - -#include "DRW_engine.h" - -#include "ED_datafiles.h" -#include "ED_keyframes_draw.h" -#include "ED_render.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -static void icon_draw_rect_input_text(const rctf *rect, - const float color[4], - const char *str, - int font_size) -{ - BLF_batch_draw_flush(); - const int font_id = BLF_default(); - BLF_color4fv(font_id, color); - BLF_size(font_id, font_size * U.pixelsize, U.dpi); - float width, height; - BLF_width_and_height(font_id, str, BLF_DRAW_STR_DUMMY_MAX, &width, &height); - const float x = rect->xmin + (((rect->xmax - rect->xmin) - width) / 2.0f); - const float y = rect->ymin + (((rect->ymax - rect->ymin) - height) / 2.0f); - BLF_position(font_id, x, y, 0.0f); - BLF_draw(font_id, str, BLF_DRAW_STR_DUMMY_MAX); - BLF_batch_draw_flush(); -} - -static void icon_draw_rect_input_symbol(const rctf *rect, const float color[4], const char *str) -{ - BLF_batch_draw_flush(); - const int font_id = blf_mono_font; - BLF_color4fv(font_id, color); - BLF_size(font_id, 19 * U.pixelsize, U.dpi); - const float x = rect->xmin + (2.0f * U.pixelsize); - const float y = rect->ymin + (1.0f * U.pixelsize); - BLF_position(font_id, x, y, 0.0f); - BLF_draw(font_id, str, BLF_DRAW_STR_DUMMY_MAX); - BLF_batch_draw_flush(); -} - -void icon_draw_rect_input(float x, - float y, - int w, - int h, - float UNUSED(alpha), - short event_type, - short UNUSED(event_value)) -{ - float color[4]; - GPU_line_width(1.0f); - UI_GetThemeColor4fv(TH_TEXT, color); - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_aa( - &(const rctf){ - .xmin = (int)x - U.pixelsize, - .xmax = (int)(x + w), - .ymin = (int)y, - .ymax = (int)(y + h), - }, - false, - 3.0f * U.pixelsize, - color); - - const enum { - UNIX, - MACOS, - MSWIN, - } platform = - -#if defined(__APPLE__) - MACOS -#elif defined(_WIN32) - MSWIN -#else - UNIX -#endif - ; - - const rctf rect = { - .xmin = x, - .ymin = y, - .xmax = x + w, - .ymax = y + h, - }; - - if ((event_type >= EVT_AKEY) && (event_type <= EVT_ZKEY)) { - const char str[2] = {'A' + (event_type - EVT_AKEY), '\0'}; - icon_draw_rect_input_text(&rect, color, str, 13); - } - else if ((event_type >= EVT_F1KEY) && (event_type <= EVT_F12KEY)) { - char str[4]; - SNPRINTF(str, "F%d", 1 + (event_type - EVT_F1KEY)); - icon_draw_rect_input_text(&rect, color, str, event_type > EVT_F9KEY ? 8 : 10); - } - else if (event_type == EVT_LEFTSHIFTKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x87, 0xa7, 0x0}); - } - else if (event_type == EVT_LEFTCTRLKEY) { - if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0x83, 0x0}); - } - else { - icon_draw_rect_input_text(&rect, color, "Ctrl", 9); - } - } - else if (event_type == EVT_LEFTALTKEY) { - if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0xa5, 0x0}); - } - else { - icon_draw_rect_input_text(&rect, color, "Alt", 10); - } - } - else if (event_type == EVT_OSKEY) { - if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0x98, 0x0}); - } - else if (platform == MSWIN) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x9d, 0x96, 0x0}); - } - else { - icon_draw_rect_input_text(&rect, color, "OS", 10); - } - } - else if (event_type == EVT_DELKEY) { - icon_draw_rect_input_text(&rect, color, "Del", 9); - } - else if (event_type == EVT_TABKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0xad, 0xbe, 0x0}); - } - else if (event_type == EVT_HOMEKEY) { - icon_draw_rect_input_text(&rect, color, "Home", 6); - } - else if (event_type == EVT_ENDKEY) { - icon_draw_rect_input_text(&rect, color, "End", 8); - } - else if (event_type == EVT_RETKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8f, 0x8e, 0x0}); - } - else if (event_type == EVT_ESCKEY) { - if (platform == MACOS) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8e, 0x8b, 0x0}); - } - else { - icon_draw_rect_input_text(&rect, color, "Esc", 8); - } - } - else if (event_type == EVT_PAGEUPKEY) { - icon_draw_rect_input_text(&rect, color, (const char[]){'P', 0xe2, 0x86, 0x91, 0x0}, 8); - } - else if (event_type == EVT_PAGEDOWNKEY) { - icon_draw_rect_input_text(&rect, color, (const char[]){'P', 0xe2, 0x86, 0x93, 0x0}, 8); - } - else if (event_type == EVT_LEFTARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x90, 0x0}); - } - else if (event_type == EVT_UPARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x91, 0x0}); - } - else if (event_type == EVT_RIGHTARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x92, 0x0}); - } - else if (event_type == EVT_DOWNARROWKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x93, 0x0}); - } - else if (event_type == EVT_SPACEKEY) { - icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x90, 0xa3, 0x0}); - } -} diff --git a/source/blender/editors/interface/interface_icons_event.cc b/source/blender/editors/interface/interface_icons_event.cc new file mode 100644 index 00000000000..33e3969ed2f --- /dev/null +++ b/source/blender/editors/interface/interface_icons_event.cc @@ -0,0 +1,236 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * A special set of icons to represent input devices, + * this is a mix of text (via fonts) and a handful of custom glyphs for special keys. + * + * Event codes are used as identifiers. + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "GPU_batch.h" +#include "GPU_immediate.h" +#include "GPU_state.h" + +#include "BLI_blenlib.h" +#include "BLI_math_vector.h" +#include "BLI_utildefines.h" + +#include "DNA_brush_types.h" +#include "DNA_curve_types.h" +#include "DNA_dynamicpaint_types.h" +#include "DNA_object_types.h" +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_workspace_types.h" + +#include "RNA_access.h" +#include "RNA_enum_types.h" + +#include "BKE_appdir.h" +#include "BKE_icons.h" +#include "BKE_studiolight.h" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" +#include "IMB_thumbs.h" + +#include "BLF_api.h" + +#include "DEG_depsgraph.h" + +#include "DRW_engine.h" + +#include "ED_datafiles.h" +#include "ED_keyframes_draw.h" +#include "ED_render.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +static void icon_draw_rect_input_text(const rctf *rect, + const float color[4], + const char *str, + int font_size) +{ + BLF_batch_draw_flush(); + const int font_id = BLF_default(); + BLF_color4fv(font_id, color); + BLF_size(font_id, font_size * U.pixelsize, U.dpi); + float width, height; + BLF_width_and_height(font_id, str, BLF_DRAW_STR_DUMMY_MAX, &width, &height); + const float x = rect->xmin + (((rect->xmax - rect->xmin) - width) / 2.0f); + const float y = rect->ymin + (((rect->ymax - rect->ymin) - height) / 2.0f); + BLF_position(font_id, x, y, 0.0f); + BLF_draw(font_id, str, BLF_DRAW_STR_DUMMY_MAX); + BLF_batch_draw_flush(); +} + +static void icon_draw_rect_input_symbol(const rctf *rect, const float color[4], const char *str) +{ + BLF_batch_draw_flush(); + const int font_id = blf_mono_font; + BLF_color4fv(font_id, color); + BLF_size(font_id, 19 * U.pixelsize, U.dpi); + const float x = rect->xmin + (2.0f * U.pixelsize); + const float y = rect->ymin + (1.0f * U.pixelsize); + BLF_position(font_id, x, y, 0.0f); + BLF_draw(font_id, str, BLF_DRAW_STR_DUMMY_MAX); + BLF_batch_draw_flush(); +} + +void icon_draw_rect_input(float x, + float y, + int w, + int h, + float UNUSED(alpha), + short event_type, + short UNUSED(event_value)) +{ + float color[4]; + GPU_line_width(1.0f); + UI_GetThemeColor4fv(TH_TEXT, color); + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_aa( + &(const rctf){ + (int)x - U.pixelsize, + (int)(x + w), + (int)y, + (int)(y + h), + }, + false, + 3.0f * U.pixelsize, + color); + + const enum { + UNIX, + MACOS, + MSWIN, + } platform = + +#if defined(__APPLE__) + MACOS +#elif defined(_WIN32) + MSWIN +#else + UNIX +#endif + ; + + const rctf rect = { + x, + x + w, + y, + y + h, + }; + + if ((event_type >= EVT_AKEY) && (event_type <= EVT_ZKEY)) { + const char str[2] = {'A' + (event_type - EVT_AKEY), '\0'}; + icon_draw_rect_input_text(&rect, color, str, 13); + } + else if ((event_type >= EVT_F1KEY) && (event_type <= EVT_F12KEY)) { + char str[4]; + SNPRINTF(str, "F%d", 1 + (event_type - EVT_F1KEY)); + icon_draw_rect_input_text(&rect, color, str, event_type > EVT_F9KEY ? 8 : 10); + } + else if (event_type == EVT_LEFTSHIFTKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x87, 0xa7, 0x0}); + } + else if (event_type == EVT_LEFTCTRLKEY) { + if (platform == MACOS) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0x83, 0x0}); + } + else { + icon_draw_rect_input_text(&rect, color, "Ctrl", 9); + } + } + else if (event_type == EVT_LEFTALTKEY) { + if (platform == MACOS) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0xa5, 0x0}); + } + else { + icon_draw_rect_input_text(&rect, color, "Alt", 10); + } + } + else if (event_type == EVT_OSKEY) { + if (platform == MACOS) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8c, 0x98, 0x0}); + } + else if (platform == MSWIN) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x9d, 0x96, 0x0}); + } + else { + icon_draw_rect_input_text(&rect, color, "OS", 10); + } + } + else if (event_type == EVT_DELKEY) { + icon_draw_rect_input_text(&rect, color, "Del", 9); + } + else if (event_type == EVT_TABKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0xad, 0xbe, 0x0}); + } + else if (event_type == EVT_HOMEKEY) { + icon_draw_rect_input_text(&rect, color, "Home", 6); + } + else if (event_type == EVT_ENDKEY) { + icon_draw_rect_input_text(&rect, color, "End", 8); + } + else if (event_type == EVT_RETKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8f, 0x8e, 0x0}); + } + else if (event_type == EVT_ESCKEY) { + if (platform == MACOS) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x8e, 0x8b, 0x0}); + } + else { + icon_draw_rect_input_text(&rect, color, "Esc", 8); + } + } + else if (event_type == EVT_PAGEUPKEY) { + icon_draw_rect_input_text(&rect, color, (const char[]){'P', 0xe2, 0x86, 0x91, 0x0}, 8); + } + else if (event_type == EVT_PAGEDOWNKEY) { + icon_draw_rect_input_text(&rect, color, (const char[]){'P', 0xe2, 0x86, 0x93, 0x0}, 8); + } + else if (event_type == EVT_LEFTARROWKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x90, 0x0}); + } + else if (event_type == EVT_UPARROWKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x91, 0x0}); + } + else if (event_type == EVT_RIGHTARROWKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x92, 0x0}); + } + else if (event_type == EVT_DOWNARROWKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x86, 0x93, 0x0}); + } + else if (event_type == EVT_SPACEKEY) { + icon_draw_rect_input_symbol(&rect, color, (const char[]){0xe2, 0x90, 0xa3, 0x0}); + } +} diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 0e465be5bf6..82e263e18f6 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -111,7 +111,7 @@ typedef enum RadialDirection { UI_RADIAL_NW = 7, } RadialDirection; -extern const char ui_radial_dir_order[8]; +extern const RadialDirection ui_radial_dir_order[8]; extern const char ui_radial_dir_to_numpad[8]; extern const short ui_radial_dir_to_angle[8]; @@ -1068,7 +1068,7 @@ void ui_item_menutype_func(struct bContext *C, struct uiLayout *layout, void *ar void ui_item_paneltype_func(struct bContext *C, struct uiLayout *layout, void *arg_pt); /* interface_button_group.c */ -void ui_block_new_button_group(uiBlock *block, uiButtonGroupFlag flag); +struct uiButtonGroup *ui_block_new_button_group(uiBlock *block); void ui_button_group_add_but(uiBlock *block, uiBut *but); void ui_button_group_replace_but_ptr(uiBlock *block, const void *old_but_ptr, uiBut *new_but); void ui_block_free_button_groups(uiBlock *block); diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c deleted file mode 100644 index 8f2871ce18b..00000000000 --- a/source/blender/editors/interface/interface_layout.c +++ /dev/null @@ -1,5961 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_armature_types.h" -#include "DNA_screen_types.h" -#include "DNA_userdef_types.h" - -#include "BLI_alloca.h" -#include "BLI_dynstr.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "BKE_anim_data.h" -#include "BKE_armature.h" -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_idprop.h" -#include "BKE_screen.h" - -#include "RNA_access.h" - -#include "UI_interface.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "interface_intern.h" - -/* Show an icon button after each RNA button to use to quickly set keyframes, - * this is a way to display animation/driven/override status, see T54951. */ -#define UI_PROP_DECORATE -/* Alternate draw mode where some buttons can use single icon width, - * giving more room for the text at the expense of nicely aligned text. */ -#define UI_PROP_SEP_ICON_WIDTH_EXCEPTION - -/* -------------------------------------------------------------------- */ -/** \name Structs and Defines - * \{ */ - -#define UI_OPERATOR_ERROR_RET(_ot, _opname, return_statement) \ - if (ot == NULL) { \ - ui_item_disabled(layout, _opname); \ - RNA_warning("'%s' unknown operator", _opname); \ - return_statement; \ - } \ - (void)0 - -#define UI_ITEM_PROP_SEP_DIVIDE 0.4f - -/* uiLayoutRoot */ - -typedef struct uiLayoutRoot { - struct uiLayoutRoot *next, *prev; - - int type; - int opcontext; - - int emw, emh; - int padding; - - uiMenuHandleFunc handlefunc; - void *argv; - - const uiStyle *style; - uiBlock *block; - uiLayout *layout; -} uiLayoutRoot; - -/* Item */ - -typedef enum uiItemType { - ITEM_BUTTON, - - ITEM_LAYOUT_ROW, - ITEM_LAYOUT_COLUMN, - ITEM_LAYOUT_COLUMN_FLOW, - ITEM_LAYOUT_ROW_FLOW, - ITEM_LAYOUT_GRID_FLOW, - ITEM_LAYOUT_BOX, - ITEM_LAYOUT_ABSOLUTE, - ITEM_LAYOUT_SPLIT, - ITEM_LAYOUT_OVERLAP, - ITEM_LAYOUT_RADIAL, - - ITEM_LAYOUT_ROOT -#if 0 - TEMPLATE_COLUMN_FLOW, - TEMPLATE_SPLIT, - TEMPLATE_BOX, - - TEMPLATE_HEADER, - TEMPLATE_HEADER_ID, -#endif -} uiItemType; - -typedef struct uiItem { - void *next, *prev; - uiItemType type; - int flag; -} uiItem; - -enum { - UI_ITEM_AUTO_FIXED_SIZE = 1 << 0, - UI_ITEM_FIXED_SIZE = 1 << 1, - - UI_ITEM_BOX_ITEM = 1 << 2, /* The item is "inside" a box item */ - UI_ITEM_PROP_SEP = 1 << 3, - UI_ITEM_INSIDE_PROP_SEP = 1 << 4, - /* Show an icon button next to each property (to set keyframes, show status). - * Enabled by default, depends on 'UI_ITEM_PROP_SEP'. */ - UI_ITEM_PROP_DECORATE = 1 << 5, - UI_ITEM_PROP_DECORATE_NO_PAD = 1 << 6, -}; - -typedef struct uiButtonItem { - uiItem item; - uiBut *but; -} uiButtonItem; - -struct uiLayout { - uiItem item; - - uiLayoutRoot *root; - bContextStore *context; - uiLayout *parent; - ListBase items; - - char heading[UI_MAX_NAME_STR]; - - /** Sub layout to add child items, if not the layout itself. */ - uiLayout *child_items_layout; - - int x, y, w, h; - float scale[2]; - short space; - bool align; - bool active; - bool active_default; - bool activate_init; - bool enabled; - bool redalert; - bool keepaspect; - /** For layouts inside gridflow, they and their items shall never have a fixed maximal size. */ - bool variable_size; - char alignment; - eUIEmbossType emboss; - /** for fixed width or height to avoid UI size changes */ - float units[2]; -}; - -typedef struct uiLayoutItemFlow { - uiLayout litem; - int number; - int totcol; -} uiLayoutItemFlow; - -typedef struct uiLayoutItemGridFlow { - uiLayout litem; - - /* Extra parameters */ - bool row_major; /* Fill first row first, instead of filling first column first. */ - bool even_columns; /* Same width for all columns. */ - bool even_rows; /* Same height for all rows. */ - /** - * - If positive, absolute fixed number of columns. - * - If 0, fully automatic (based on available width). - * - If negative, automatic but only generates number of columns/rows - * multiple of given (absolute) value. - */ - int columns_len; - - /* Pure internal runtime storage. */ - int tot_items, tot_columns, tot_rows; -} uiLayoutItemGridFlow; - -typedef struct uiLayoutItemBx { - uiLayout litem; - uiBut *roundbox; -} uiLayoutItemBx; - -typedef struct uiLayoutItemSplit { - uiLayout litem; - float percentage; -} uiLayoutItemSplit; - -typedef struct uiLayoutItemRoot { - uiLayout litem; -} uiLayoutItemRoot; - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Item - * \{ */ - -static const char *ui_item_name_add_colon(const char *name, char namestr[UI_MAX_NAME_STR]) -{ - const int len = strlen(name); - - if (len != 0 && len + 1 < UI_MAX_NAME_STR) { - memcpy(namestr, name, len); - namestr[len] = ':'; - namestr[len + 1] = '\0'; - return namestr; - } - - return name; -} - -static int ui_item_fit( - int item, int pos, int all, int available, bool is_last, int alignment, float *extra_pixel) -{ - /* available == 0 is unlimited */ - if (ELEM(0, available, all)) { - return item; - } - - if (all > available) { - /* contents is bigger than available space */ - if (is_last) { - return available - pos; - } - - const float width = *extra_pixel + (item * available) / (float)all; - *extra_pixel = width - (int)width; - return (int)width; - } - - /* contents is smaller or equal to available space */ - if (alignment == UI_LAYOUT_ALIGN_EXPAND) { - if (is_last) { - return available - pos; - } - - const float width = *extra_pixel + (item * available) / (float)all; - *extra_pixel = width - (int)width; - return (int)width; - } - return item; -} - -/* variable button size in which direction? */ -#define UI_ITEM_VARY_X 1 -#define UI_ITEM_VARY_Y 2 - -static int ui_layout_vary_direction(uiLayout *layout) -{ - return ((ELEM(layout->root->type, UI_LAYOUT_HEADER, UI_LAYOUT_PIEMENU) || - (layout->alignment != UI_LAYOUT_ALIGN_EXPAND)) ? - UI_ITEM_VARY_X : - UI_ITEM_VARY_Y); -} - -static bool ui_layout_variable_size(uiLayout *layout) -{ - /* Note that this code is probably a bit flakey, we'd probably want to know whether it's - * variable in X and/or Y, etc. But for now it mimics previous one, - * with addition of variable flag set for children of grid-flow layouts. */ - return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size; -} - -/* estimated size of text + icon */ -static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) -{ - const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f); - - if (icon && !name[0]) { - return unit_x; /* icon only */ - } - - if (ui_layout_variable_size(layout)) { - if (!icon && !name[0]) { - return unit_x; /* No icon or name. */ - } - if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) { - layout->item.flag |= UI_ITEM_FIXED_SIZE; - } - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - float margin = compact ? 1.25 : 1.50; - if (icon) { - /* It may seem odd that the icon only adds (unit_x / 4) - * but taking margins into account its fine, except - * in compact mode a bit more margin is required. */ - margin += compact ? 0.35 : 0.25; - } - return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin); - } - return unit_x * 10; -} - -static void ui_item_size(uiItem *item, int *r_w, int *r_h) -{ - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - if (r_w) { - *r_w = BLI_rctf_size_x(&bitem->but->rect); - } - if (r_h) { - *r_h = BLI_rctf_size_y(&bitem->but->rect); - } - } - else { - uiLayout *litem = (uiLayout *)item; - - if (r_w) { - *r_w = litem->w; - } - if (r_h) { - *r_h = litem->h; - } - } -} - -static void ui_item_offset(uiItem *item, int *r_x, int *r_y) -{ - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - if (r_x) { - *r_x = bitem->but->rect.xmin; - } - if (r_y) { - *r_y = bitem->but->rect.ymin; - } - } - else { - if (r_x) { - *r_x = 0; - } - if (r_y) { - *r_y = 0; - } - } -} - -static void ui_item_position(uiItem *item, int x, int y, int w, int h) -{ - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - bitem->but->rect.xmin = x; - bitem->but->rect.ymin = y; - bitem->but->rect.xmax = x + w; - bitem->but->rect.ymax = y + h; - - ui_but_update(bitem->but); /* for strlen */ - } - else { - uiLayout *litem = (uiLayout *)item; - - litem->x = x; - litem->y = y + h; - litem->w = w; - litem->h = h; - } -} - -static void ui_item_move(uiItem *item, int delta_xmin, int delta_xmax) -{ - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - bitem->but->rect.xmin += delta_xmin; - bitem->but->rect.xmax += delta_xmax; - - ui_but_update(bitem->but); /* for strlen */ - } - else { - uiLayout *litem = (uiLayout *)item; - - if (delta_xmin > 0) { - litem->x += delta_xmin; - } - else { - litem->w += delta_xmax; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Special RNA Items - * \{ */ - -int uiLayoutGetLocalDir(const uiLayout *layout) -{ - switch (layout->item.type) { - case ITEM_LAYOUT_ROW: - case ITEM_LAYOUT_ROOT: - case ITEM_LAYOUT_OVERLAP: - return UI_LAYOUT_HORIZONTAL; - case ITEM_LAYOUT_COLUMN: - case ITEM_LAYOUT_COLUMN_FLOW: - case ITEM_LAYOUT_GRID_FLOW: - case ITEM_LAYOUT_SPLIT: - case ITEM_LAYOUT_ABSOLUTE: - case ITEM_LAYOUT_BOX: - default: - return UI_LAYOUT_VERTICAL; - } -} - -static uiLayout *ui_item_local_sublayout(uiLayout *test, uiLayout *layout, bool align) -{ - uiLayout *sub; - if (uiLayoutGetLocalDir(test) == UI_LAYOUT_HORIZONTAL) { - sub = uiLayoutRow(layout, align); - } - else { - sub = uiLayoutColumn(layout, align); - } - - sub->space = 0; - return sub; -} - -static void ui_layer_but_cb(bContext *C, void *arg_but, void *arg_index) -{ - wmWindow *win = CTX_wm_window(C); - uiBut *but = arg_but; - PointerRNA *ptr = &but->rnapoin; - PropertyRNA *prop = but->rnaprop; - const int index = POINTER_AS_INT(arg_index); - const int shift = win->eventstate->shift; - const int len = RNA_property_array_length(ptr, prop); - - if (!shift) { - RNA_property_boolean_set_index(ptr, prop, index, true); - - for (int i = 0; i < len; i++) { - if (i != index) { - RNA_property_boolean_set_index(ptr, prop, i, 0); - } - } - - RNA_property_update(C, ptr, prop); - - LISTBASE_FOREACH (uiBut *, cbut, &but->block->buttons) { - ui_but_update(cbut); - } - } -} - -/* create buttons for an item with an RNA array */ -static void ui_item_array(uiLayout *layout, - uiBlock *block, - const char *name, - int icon, - PointerRNA *ptr, - PropertyRNA *prop, - int len, - int x, - int y, - int w, - int UNUSED(h), - bool expand, - bool slider, - int toggle, - bool icon_only, - bool compact, - bool show_text) -{ - const uiStyle *style = layout->root->style; - - /* retrieve type and subtype */ - const PropertyType type = RNA_property_type(prop); - const PropertySubType subtype = RNA_property_subtype(prop); - - uiLayout *sub = ui_item_local_sublayout(layout, layout, 1); - UI_block_layout_set_current(block, sub); - - /* create label */ - if (name[0] && show_text) { - uiDefBut(block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); - } - - /* create buttons */ - if (type == PROP_BOOLEAN && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER)) { - /* special check for layer layout */ - const int cols = (len >= 20) ? 2 : 1; - const int colbuts = len / (2 * cols); - uint layer_used = 0; - uint layer_active = 0; - - UI_block_layout_set_current(block, uiLayoutAbsolute(layout, false)); - - const int butw = UI_UNIT_X * 0.75; - const int buth = UI_UNIT_X * 0.75; - - if (ptr->type == &RNA_Armature) { - bArmature *arm = ptr->data; - - layer_used = arm->layer_used; - - if (arm->edbo) { - if (arm->act_edbone) { - layer_active |= arm->act_edbone->layer; - } - } - else { - if (arm->act_bone) { - layer_active |= arm->act_bone->layer; - } - } - } - - for (int b = 0; b < cols; b++) { - UI_block_align_begin(block); - - for (int a = 0; a < colbuts; a++) { - const int layer_num = a + b * colbuts; - const uint layer_flag = (1u << layer_num); - - if (layer_used & layer_flag) { - if (layer_active & layer_flag) { - icon = ICON_LAYER_ACTIVE; - } - else { - icon = ICON_LAYER_USED; - } - } - else { - icon = ICON_BLANK1; - } - - uiBut *but = uiDefAutoButR( - block, ptr, prop, layer_num, "", icon, x + butw * a, y + buth, butw, buth); - if (subtype == PROP_LAYER_MEMBER) { - UI_but_func_set(but, ui_layer_but_cb, but, POINTER_FROM_INT(layer_num)); - } - } - for (int a = 0; a < colbuts; a++) { - const int layer_num = a + len / 2 + b * colbuts; - const uint layer_flag = (1u << layer_num); - - if (layer_used & layer_flag) { - if (layer_active & layer_flag) { - icon = ICON_LAYER_ACTIVE; - } - else { - icon = ICON_LAYER_USED; - } - } - else { - icon = ICON_BLANK1; - } - - uiBut *but = uiDefAutoButR( - block, ptr, prop, layer_num, "", icon, x + butw * a, y, butw, buth); - if (subtype == PROP_LAYER_MEMBER) { - UI_but_func_set(but, ui_layer_but_cb, but, POINTER_FROM_INT(layer_num)); - } - } - UI_block_align_end(block); - - x += colbuts * butw + style->buttonspacex; - } - } - else if (subtype == PROP_MATRIX) { - int totdim, dim_size[3]; /* 3 == RNA_MAX_ARRAY_DIMENSION */ - int row, col; - - UI_block_layout_set_current(block, uiLayoutAbsolute(layout, true)); - - totdim = RNA_property_array_dimension(ptr, prop, dim_size); - if (totdim != 2) { - /* Only 2D matrices supported in UI so far. */ - return; - } - - w /= dim_size[0]; - /* h /= dim_size[1]; */ /* UNUSED */ - - for (int a = 0; a < len; a++) { - col = a % dim_size[0]; - row = a / dim_size[0]; - - uiBut *but = uiDefAutoButR(block, - ptr, - prop, - a, - "", - ICON_NONE, - x + w * col, - y + (dim_size[1] * UI_UNIT_Y) - (row * UI_UNIT_Y), - w, - UI_UNIT_Y); - if (slider && but->type == UI_BTYPE_NUM) { - uiButNumber *number_but = (uiButNumber *)but; - - but->a1 = number_but->step_size; - but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER); - } - } - } - else if (subtype == PROP_DIRECTION && !expand) { - uiDefButR_prop(block, - UI_BTYPE_UNITVEC, - 0, - name, - x, - y, - UI_UNIT_X * 3, - UI_UNIT_Y * 3, - ptr, - prop, - -1, - 0, - 0, - -1, - -1, - NULL); - } - else { - /* note, this block of code is a bit arbitrary and has just been made - * to work with common cases, but may need to be re-worked */ - - /* special case, boolean array in a menu, this could be used in a more generic way too */ - if (ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA) && !expand && ELEM(len, 3, 4)) { - uiDefAutoButR(block, ptr, prop, -1, "", ICON_NONE, 0, 0, w, UI_UNIT_Y); - } - else { - /* Even if 'expand' is false, we expand anyway. */ - - /* layout for known array subtypes */ - char str[3] = {'\0'}; - - if (!icon_only && show_text) { - if (type != PROP_BOOLEAN) { - str[1] = ':'; - } - } - - /* show checkboxes for rna on a non-emboss block (menu for eg) */ - bool *boolarr = NULL; - if (type == PROP_BOOLEAN && - ELEM(layout->root->block->emboss, UI_EMBOSS_NONE, UI_EMBOSS_PULLDOWN)) { - boolarr = MEM_callocN(sizeof(bool) * len, __func__); - RNA_property_boolean_get_array(ptr, prop, boolarr); - } - - const char *str_buf = show_text ? str : ""; - for (int a = 0; a < len; a++) { - if (!icon_only && show_text) { - str[0] = RNA_property_array_item_char(prop, a); - } - if (boolarr) { - icon = boolarr[a] ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; - } - - const int width_item = ((compact && type == PROP_BOOLEAN) ? - min_ii(w, ui_text_icon_width(layout, str_buf, icon, false)) : - w); - - uiBut *but = uiDefAutoButR( - block, ptr, prop, a, str_buf, icon, 0, 0, width_item, UI_UNIT_Y); - if (slider && but->type == UI_BTYPE_NUM) { - uiButNumber *number_but = (uiButNumber *)but; - - but->a1 = number_but->step_size; - but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER); - } - if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) { - but->type = UI_BTYPE_TOGGLE; - } - if ((a == 0) && (subtype == PROP_AXISANGLE)) { - UI_but_unit_type_set(but, PROP_UNIT_ROTATION); - } - } - - if (boolarr) { - MEM_freeN(boolarr); - } - } - } - - UI_block_layout_set_current(block, layout); -} - -static void ui_item_enum_expand_handle(bContext *C, void *arg1, void *arg2) -{ - wmWindow *win = CTX_wm_window(C); - - if (!win->eventstate->shift) { - uiBut *but = (uiBut *)arg1; - const int enum_value = POINTER_AS_INT(arg2); - - int current_value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); - if (!(current_value & enum_value)) { - current_value = enum_value; - } - else { - current_value &= enum_value; - } - RNA_property_enum_set(&but->rnapoin, but->rnaprop, current_value); - } -} - -/** - * Draw a single enum button, a utility for #ui_item_enum_expand_exec - */ -static void ui_item_enum_expand_elem_exec(uiLayout *layout, - uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - const char *uiname, - const int h, - const eButType but_type, - const bool icon_only, - const EnumPropertyItem *item, - const bool is_first) -{ - const char *name = (!uiname || uiname[0]) ? item->name : ""; - const int icon = item->icon; - const int value = item->value; - const int itemw = ui_text_icon_width(block->curlayout, icon_only ? "" : name, icon, 0); - - uiBut *but; - if (icon && name[0] && !icon_only) { - but = uiDefIconTextButR_prop( - block, but_type, 0, icon, name, 0, 0, itemw, h, ptr, prop, -1, 0, value, -1, -1, NULL); - } - else if (icon) { - const int w = (is_first) ? itemw : ceilf(itemw - U.pixelsize); - but = uiDefIconButR_prop( - block, but_type, 0, icon, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); - } - else { - but = uiDefButR_prop( - block, but_type, 0, name, 0, 0, itemw, h, ptr, prop, -1, 0, value, -1, -1, NULL); - } - - if (RNA_property_flag(prop) & PROP_ENUM_FLAG) { - /* If this is set, assert since we're clobbering someone elses callback. */ - /* Buttons get their block's func by default, so we cannot assert in that case either. */ - BLI_assert(ELEM(but->func, NULL, block->func)); - UI_but_func_set(but, ui_item_enum_expand_handle, but, POINTER_FROM_INT(value)); - } - - if (uiLayoutGetLocalDir(layout) != UI_LAYOUT_HORIZONTAL) { - but->drawflag |= UI_BUT_TEXT_LEFT; - } - - /* Allow quick, inaccurate swipe motions to switch tabs - * (no need to keep cursor over them). */ - if (but_type == UI_BTYPE_TAB) { - but->flag |= UI_BUT_DRAG_LOCK; - } -} - -static void ui_item_enum_expand_exec(uiLayout *layout, - uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - const char *uiname, - const int h, - const eButType but_type, - const bool icon_only) -{ - /* XXX: The way this function currently handles uiname parameter - * is insane and inconsistent with general UI API: - * - * - uiname is the *enum property* label. - * - when it is NULL or empty, we do not draw *enum items* labels, - * this doubles the icon_only parameter. - * - we *never* draw (i.e. really use) the enum label uiname, it is just used as a mere flag! - * - * Unfortunately, fixing this implies an API "soft break", so better to defer it for later... :/ - * - mont29 - */ - - BLI_assert(RNA_property_type(prop) == PROP_ENUM); - - const bool radial = (layout->root->type == UI_LAYOUT_PIEMENU); - - bool free; - const EnumPropertyItem *item_array; - if (radial) { - RNA_property_enum_items_gettexted_all(block->evil_C, ptr, prop, &item_array, NULL, &free); - } - else { - RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item_array, NULL, &free); - } - - /* We don't want nested rows, cols in menus. */ - uiLayout *layout_radial = NULL; - if (radial) { - if (layout->root->layout == layout) { - layout_radial = uiLayoutRadial(layout); - UI_block_layout_set_current(block, layout_radial); - } - else { - if (layout->item.type == ITEM_LAYOUT_RADIAL) { - layout_radial = layout; - } - UI_block_layout_set_current(block, layout); - } - } - else if (ELEM(layout->item.type, ITEM_LAYOUT_GRID_FLOW, ITEM_LAYOUT_COLUMN_FLOW) || - layout->root->type == UI_LAYOUT_MENU) { - UI_block_layout_set_current(block, layout); - } - else { - UI_block_layout_set_current(block, ui_item_local_sublayout(layout, layout, 1)); - } - - for (const EnumPropertyItem *item = item_array; item->identifier; item++) { - const bool is_first = item == item_array; - - if (!item->identifier[0]) { - const EnumPropertyItem *next_item = item + 1; - - /* Separate items, potentially with a label. */ - if (next_item->identifier) { - /* Item without identifier but with name: - * Add group label for the following items. */ - if (item->name) { - if (!is_first) { - uiItemS(block->curlayout); - } - uiItemL(block->curlayout, item->name, item->icon); - } - else if (radial && layout_radial) { - uiItemS(layout_radial); - } - else { - uiItemS(block->curlayout); - } - } - continue; - } - - ui_item_enum_expand_elem_exec( - layout, block, ptr, prop, uiname, h, but_type, icon_only, item, is_first); - } - - UI_block_layout_set_current(block, layout); - - if (free) { - MEM_freeN((void *)item_array); - } -} -static void ui_item_enum_expand(uiLayout *layout, - uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - const char *uiname, - const int h, - const bool icon_only) -{ - ui_item_enum_expand_exec(layout, block, ptr, prop, uiname, h, UI_BTYPE_ROW, icon_only); -} -static void ui_item_enum_expand_tabs(uiLayout *layout, - bContext *C, - uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - PointerRNA *ptr_highlight, - PropertyRNA *prop_highlight, - const char *uiname, - const int h, - const bool icon_only) -{ - uiBut *last = block->buttons.last; - - ui_item_enum_expand_exec(layout, block, ptr, prop, uiname, h, UI_BTYPE_TAB, icon_only); - BLI_assert(last != block->buttons.last); - - for (uiBut *tab = last ? last->next : block->buttons.first; tab; tab = tab->next) { - UI_but_drawflag_enable(tab, ui_but_align_opposite_to_area_align_get(CTX_wm_region(C))); - } - - const bool use_custom_highlight = (prop_highlight != NULL); - - if (use_custom_highlight) { - const int highlight_array_len = RNA_property_array_length(ptr_highlight, prop_highlight); - bool *highlight_array = alloca(sizeof(bool) * highlight_array_len); - RNA_property_boolean_get_array(ptr_highlight, prop_highlight, highlight_array); - int i = 0; - for (uiBut *tab_but = last ? last->next : block->buttons.first; - (tab_but != NULL) && (i < highlight_array_len); - tab_but = tab_but->next, i++) { - SET_FLAG_FROM_TEST(tab_but->flag, !highlight_array[i], UI_BUT_INACTIVE); - } - } -} - -/* callback for keymap item change button */ -static void ui_keymap_but_cb(bContext *UNUSED(C), void *but_v, void *UNUSED(key_v)) -{ - uiBut *but = but_v; - - RNA_boolean_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) != 0); - RNA_boolean_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) != 0); - RNA_boolean_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) != 0); - RNA_boolean_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) != 0); -} - -/** - * Create label + button for RNA property - * - * \param w_hint: For varying width layout, this becomes the label width. - * Otherwise it's used to fit both items into it. - */ -static uiBut *ui_item_with_label(uiLayout *layout, - uiBlock *block, - const char *name, - int icon, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - int x, - int y, - int w_hint, - int h, - int flag) -{ - uiLayout *sub = layout; - int prop_but_width = w_hint; -#ifdef UI_PROP_DECORATE - uiLayout *layout_prop_decorate = NULL; - const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0); - const bool use_prop_decorate = use_prop_sep && (layout->item.flag & UI_ITEM_PROP_DECORATE) && - (layout->item.flag & UI_ITEM_PROP_DECORATE_NO_PAD) == 0; -#endif - - const bool is_keymapitem_ptr = RNA_struct_is_a(ptr->type, &RNA_KeyMapItem); - if ((flag & UI_ITEM_R_FULL_EVENT) && !is_keymapitem_ptr) { - RNA_warning("Data is not a keymap item struct: %s. Ignoring 'full_event' option.", - RNA_struct_identifier(ptr->type)); - } - - UI_block_layout_set_current(block, layout); - - /* Only add new row if more than 1 item will be added. */ - if (name[0] -#ifdef UI_PROP_DECORATE - || use_prop_decorate -#endif - ) { - /* Also avoid setting 'align' if possible. Set the space to zero instead as aligning a large - * number of labels can end up aligning thousands of buttons when displaying key-map search (a - * heavy operation), see: T78636. */ - sub = uiLayoutRow(layout, layout->align); - sub->space = 0; - } - - if (name[0]) { -#ifdef UI_PROP_DECORATE - if (use_prop_sep) { - layout_prop_decorate = uiItemL_respect_property_split(layout, name, 0); - } - else -#endif - { - int w_label; - if (ui_layout_variable_size(layout)) { - /* w_hint is width for label in this case. - * Use a default width for property button(s) */ - prop_but_width = UI_UNIT_X * 5; - w_label = w_hint; - } - else { - w_label = w_hint / 3; - } - uiDefBut(block, UI_BTYPE_LABEL, 0, name, x, y, w_label, h, NULL, 0.0, 0.0, 0, 0, ""); - } - } - - const PropertyType type = RNA_property_type(prop); - const PropertySubType subtype = RNA_property_subtype(prop); - - uiBut *but; - if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) { - UI_block_layout_set_current(block, uiLayoutRow(sub, true)); - but = uiDefAutoButR(block, ptr, prop, index, "", icon, x, y, prop_but_width - UI_UNIT_X, h); - - /* BUTTONS_OT_file_browse calls UI_context_active_but_prop_get_filebrowser */ - uiDefIconButO(block, - UI_BTYPE_BUT, - subtype == PROP_DIRPATH ? "BUTTONS_OT_directory_browse" : - "BUTTONS_OT_file_browse", - WM_OP_INVOKE_DEFAULT, - ICON_FILEBROWSER, - x, - y, - UI_UNIT_X, - h, - NULL); - } - else if (flag & UI_ITEM_R_EVENT) { - but = uiDefButR_prop(block, - UI_BTYPE_KEY_EVENT, - 0, - name, - x, - y, - prop_but_width, - h, - ptr, - prop, - index, - 0, - 0, - -1, - -1, - NULL); - } - else if ((flag & UI_ITEM_R_FULL_EVENT) && is_keymapitem_ptr) { - char buf[128]; - - WM_keymap_item_to_string(ptr->data, false, buf, sizeof(buf)); - - but = uiDefButR_prop(block, - UI_BTYPE_HOTKEY_EVENT, - 0, - buf, - x, - y, - prop_but_width, - h, - ptr, - prop, - 0, - 0, - 0, - -1, - -1, - NULL); - UI_but_func_set(but, ui_keymap_but_cb, but, NULL); - if (flag & UI_ITEM_R_IMMEDIATE) { - UI_but_flag_enable(but, UI_BUT_IMMEDIATE); - } - } - else { - const char *str = (type == PROP_ENUM && !(flag & UI_ITEM_R_ICON_ONLY)) ? NULL : ""; - but = uiDefAutoButR(block, ptr, prop, index, str, icon, x, y, prop_but_width, h); - } - -#ifdef UI_PROP_DECORATE - /* Only for alignment. */ - if (use_prop_decorate) { /* Note that sep flag may have been unset meanwhile. */ - uiItemL(layout_prop_decorate ? layout_prop_decorate : sub, NULL, ICON_BLANK1); - } -#endif /* UI_PROP_DECORATE */ - - UI_block_layout_set_current(block, layout); - return but; -} - -void UI_context_active_but_prop_get_filebrowser(const bContext *C, - PointerRNA *r_ptr, - PropertyRNA **r_prop, - bool *r_is_undo, - bool *r_is_userdef) -{ - ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); - uiBut *prevbut = NULL; - - memset(r_ptr, 0, sizeof(*r_ptr)); - *r_prop = NULL; - *r_is_undo = false; - *r_is_userdef = false; - - if (!region) { - return; - } - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but && but->rnapoin.data) { - if (RNA_property_type(but->rnaprop) == PROP_STRING) { - prevbut = but; - } - } - - /* find the button before the active one */ - if ((but->flag & UI_BUT_LAST_ACTIVE) && prevbut) { - *r_ptr = prevbut->rnapoin; - *r_prop = prevbut->rnaprop; - *r_is_undo = (prevbut->flag & UI_BUT_UNDO) != 0; - *r_is_userdef = UI_but_is_userdef(prevbut); - return; - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Button Items - * \{ */ - -/** - * Update a buttons tip with an enum's description if possible. - */ -static void ui_but_tip_from_enum_item(uiBut *but, const EnumPropertyItem *item) -{ - if (but->tip == NULL || but->tip[0] == '\0') { - if (item->description && item->description[0] && - !(but->optype && but->optype->get_description)) { - but->tip = item->description; - } - } -} - -/* disabled item */ -static void ui_item_disabled(uiLayout *layout, const char *name) -{ - uiBlock *block = layout->root->block; - - UI_block_layout_set_current(block, layout); - - if (!name) { - name = ""; - } - - const int w = ui_text_icon_width(layout, name, 0, 0); - - uiBut *but = uiDefBut( - block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); - UI_but_disable(but, ""); -} - -/** - * Operator Item - * \param r_opptr: Optional, initialize with operator properties when not NULL. - * Will always be written to even in the case of errors. - */ -static uiBut *uiItemFullO_ptr_ex(uiLayout *layout, - wmOperatorType *ot, - const char *name, - int icon, - IDProperty *properties, - int context, - int flag, - PointerRNA *r_opptr) -{ - /* Take care to fill 'r_opptr' whatever happens. */ - uiBlock *block = layout->root->block; - - if (!name) { - if (ot && ot->srna && (flag & UI_ITEM_R_ICON_ONLY) == 0) { - name = WM_operatortype_name(ot, NULL); - } - else { - name = ""; - } - } - - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - UI_block_layout_set_current(block, layout); - ui_block_new_button_group(block, 0); - - const int w = ui_text_icon_width(layout, name, icon, 0); - - const eUIEmbossType prev_emboss = layout->emboss; - if (flag & UI_ITEM_R_NO_BG) { - layout->emboss = UI_EMBOSS_NONE; - } - - /* create the button */ - uiBut *but; - if (icon) { - if (name[0]) { - but = uiDefIconTextButO_ptr( - block, UI_BTYPE_BUT, ot, context, icon, name, 0, 0, w, UI_UNIT_Y, NULL); - } - else { - but = uiDefIconButO_ptr(block, UI_BTYPE_BUT, ot, context, icon, 0, 0, w, UI_UNIT_Y, NULL); - } - } - else { - but = uiDefButO_ptr(block, UI_BTYPE_BUT, ot, context, name, 0, 0, w, UI_UNIT_Y, NULL); - } - - BLI_assert(but->optype != NULL); - - if (flag & UI_ITEM_R_NO_BG) { - layout->emboss = prev_emboss; - } - - if (flag & UI_ITEM_O_DEPRESS) { - but->flag |= UI_SELECT_DRAW; - } - - if (flag & UI_ITEM_R_ICON_ONLY) { - UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT); - } - - if (layout->redalert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - if (layout->active_default) { - UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT); - } - - /* assign properties */ - if (properties || r_opptr) { - PointerRNA *opptr = UI_but_operator_ptr_get(but); - if (properties) { - opptr->data = properties; - } - else { - const IDPropertyTemplate val = {0}; - opptr->data = IDP_New(IDP_GROUP, &val, "wmOperatorProperties"); - } - if (r_opptr) { - *r_opptr = *opptr; - } - } - - return but; -} - -static void ui_item_menu_hold(struct bContext *C, ARegion *butregion, uiBut *but) -{ - uiPopupMenu *pup = UI_popup_menu_begin(C, "", ICON_NONE); - uiLayout *layout = UI_popup_menu_layout(pup); - uiBlock *block = layout->root->block; - UI_popup_menu_but_set(pup, butregion, but); - - block->flag |= UI_BLOCK_POPUP_HOLD; - block->flag |= UI_BLOCK_IS_FLIP; - - char direction = UI_DIR_DOWN; - if (!but->drawstr[0]) { - switch (RGN_ALIGN_ENUM_FROM_MASK(butregion->alignment)) { - case RGN_ALIGN_LEFT: - direction = UI_DIR_RIGHT; - break; - case RGN_ALIGN_RIGHT: - direction = UI_DIR_LEFT; - break; - case RGN_ALIGN_BOTTOM: - direction = UI_DIR_UP; - break; - default: - direction = UI_DIR_DOWN; - break; - } - } - UI_block_direction_set(block, direction); - - const char *menu_id = but->hold_argN; - MenuType *mt = WM_menutype_find(menu_id, true); - if (mt) { - uiLayoutSetContextFromBut(layout, but); - UI_menutype_draw(C, mt, layout); - } - else { - uiItemL(layout, "Menu Missing:", ICON_NONE); - uiItemL(layout, menu_id, ICON_NONE); - } - UI_popup_menu_end(C, pup); -} - -void uiItemFullO_ptr(uiLayout *layout, - wmOperatorType *ot, - const char *name, - int icon, - IDProperty *properties, - int context, - int flag, - PointerRNA *r_opptr) -{ - uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr); -} - -void uiItemFullOMenuHold_ptr(uiLayout *layout, - wmOperatorType *ot, - const char *name, - int icon, - IDProperty *properties, - int context, - int flag, - const char *menu_id, - PointerRNA *r_opptr) -{ - uiBut *but = uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr); - UI_but_func_hold_set(but, ui_item_menu_hold, BLI_strdup(menu_id)); -} - -void uiItemFullO(uiLayout *layout, - const char *opname, - const char *name, - int icon, - IDProperty *properties, - int context, - int flag, - PointerRNA *r_opptr) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - - UI_OPERATOR_ERROR_RET(ot, opname, { - if (r_opptr) { - *r_opptr = PointerRNA_NULL; - } - return; - }); - - uiItemFullO_ptr(layout, ot, name, icon, properties, context, flag, r_opptr); -} - -static const char *ui_menu_enumpropname(uiLayout *layout, - PointerRNA *ptr, - PropertyRNA *prop, - int retval) -{ - bool free; - const EnumPropertyItem *item; - RNA_property_enum_items(layout->root->block->evil_C, ptr, prop, &item, NULL, &free); - - const char *name; - if (RNA_enum_name(item, retval, &name)) { - name = CTX_IFACE_(RNA_property_translation_context(prop), name); - } - else { - name = ""; - } - - if (free) { - MEM_freeN((void *)item); - } - - return name; -} - -void uiItemEnumO_ptr(uiLayout *layout, - wmOperatorType *ot, - const char *name, - int icon, - const char *propname, - int value) -{ - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - - PropertyRNA *prop = RNA_struct_find_property(&ptr, propname); - if (prop == NULL) { - RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); - return; - } - - RNA_property_enum_set(&ptr, prop, value); - - if (!name) { - name = ui_menu_enumpropname(layout, &ptr, prop, value); - } - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} -void uiItemEnumO(uiLayout *layout, - const char *opname, - const char *name, - int icon, - const char *propname, - int value) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - - if (ot) { - uiItemEnumO_ptr(layout, ot, name, icon, propname, value); - } - else { - ui_item_disabled(layout, opname); - RNA_warning("unknown operator '%s'", opname); - } -} - -BLI_INLINE bool ui_layout_is_radial(const uiLayout *layout) -{ - return (layout->item.type == ITEM_LAYOUT_RADIAL) || - ((layout->item.type == ITEM_LAYOUT_ROOT) && (layout->root->type == UI_LAYOUT_PIEMENU)); -} - -/** - * Create ui items for enum items in \a item_array. - * - * A version of #uiItemsFullEnumO that takes pre-calculated item array. - */ -void uiItemsFullEnumO_items(uiLayout *layout, - wmOperatorType *ot, - PointerRNA ptr, - PropertyRNA *prop, - IDProperty *properties, - int context, - int flag, - const EnumPropertyItem *item_array, - int totitem) -{ - const char *propname = RNA_property_identifier(prop); - if (RNA_property_type(prop) != PROP_ENUM) { - RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname); - return; - } - - uiLayout *target, *split = NULL; - uiBlock *block = layout->root->block; - const bool radial = ui_layout_is_radial(layout); - - if (radial) { - target = uiLayoutRadial(layout); - } - else if ((uiLayoutGetLocalDir(layout) == UI_LAYOUT_HORIZONTAL) && (flag & UI_ITEM_R_ICON_ONLY)) { - target = layout; - UI_block_layout_set_current(block, target); - - /* Add a blank button to the beginning of the row. */ - uiDefIconBut(block, - UI_BTYPE_LABEL, - 0, - ICON_BLANK1, - 0, - 0, - 1.25f * UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - NULL); - } - else { - split = uiLayoutSplit(layout, 0.0f, false); - target = uiLayoutColumn(split, layout->align); - } - - bool last_iter = false; - const EnumPropertyItem *item = item_array; - for (int i = 1; item->identifier && !last_iter; i++, item++) { - /* handle oversized pies */ - if (radial && (totitem > PIE_MAX_ITEMS) && (i >= PIE_MAX_ITEMS)) { - if (item->name) { /* only visible items */ - const EnumPropertyItem *tmp; - - /* Check if there are more visible items for the next level. If not, we don't - * add a new level and add the remaining item instead of the 'more' button. */ - for (tmp = item + 1; tmp->identifier; tmp++) { - if (tmp->name) { - break; - } - } - - if (tmp->identifier) { /* only true if loop above found item and did early-exit */ - ui_pie_menu_level_create( - block, ot, propname, properties, item_array, totitem, context, flag); - /* break since rest of items is handled in new pie level */ - break; - } - last_iter = true; - } - else { - continue; - } - } - - if (item->identifier[0]) { - PointerRNA tptr; - WM_operator_properties_create_ptr(&tptr, ot); - if (properties) { - if (tptr.data) { - IDP_FreeProperty(tptr.data); - } - tptr.data = IDP_CopyProperty(properties); - } - RNA_property_enum_set(&tptr, prop, item->value); - - uiItemFullO_ptr(target, - ot, - (flag & UI_ITEM_R_ICON_ONLY) ? NULL : item->name, - item->icon, - tptr.data, - context, - flag, - NULL); - - ui_but_tip_from_enum_item(block->buttons.last, item); - } - else { - if (item->name) { - if (item != item_array && !radial && split != NULL) { - target = uiLayoutColumn(split, layout->align); - - /* inconsistent, but menus with labels do not look good flipped */ - block->flag |= UI_BLOCK_NO_FLIP; - } - - uiBut *but; - if (item->icon || radial) { - uiItemL(target, item->name, item->icon); - - but = block->buttons.last; - } - else { - /* Do not use uiItemL here, as our root layout is a menu one, - * it will add a fake blank icon! */ - but = uiDefBut(block, - UI_BTYPE_LABEL, - 0, - item->name, - 0, - 0, - UI_UNIT_X * 5, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - uiItemS(target); - } - ui_but_tip_from_enum_item(but, item); - } - else { - if (radial) { - /* invisible dummy button to ensure all items are - * always at the same position */ - uiItemS(target); - } - else { - /* XXX bug here, columns draw bottom item badly */ - uiItemS(target); - } - } - } - } -} - -void uiItemsFullEnumO(uiLayout *layout, - const char *opname, - const char *propname, - IDProperty *properties, - int context, - int flag) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - - if (!ot || !ot->srna) { - ui_item_disabled(layout, opname); - RNA_warning("%s '%s'", ot ? "unknown operator" : "operator missing srna", opname); - return; - } - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, 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, propname); - - /* don't let bad properties slip through */ - BLI_assert((prop == NULL) || (RNA_property_type(prop) == PROP_ENUM)); - - uiBlock *block = layout->root->block; - if (prop && RNA_property_type(prop) == PROP_ENUM) { - const EnumPropertyItem *item_array = NULL; - int totitem; - bool free; - - if (ui_layout_is_radial(layout)) { - /* XXX: While "_all()" guarantees spatial stability, - * it's bad when an enum has > 8 items total, - * but only a small subset will ever be shown at once - * (e.g. Mode Switch menu, after the introduction of GP editing modes). - */ -#if 0 - RNA_property_enum_items_gettexted_all( - block->evil_C, &ptr, prop, &item_array, &totitem, &free); -#else - RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, &totitem, &free); -#endif - } - else { - RNA_property_enum_items_gettexted(block->evil_C, &ptr, prop, &item_array, &totitem, &free); - } - - /* add items */ - uiItemsFullEnumO_items(layout, ot, ptr, prop, properties, context, flag, item_array, totitem); - - if (free) { - MEM_freeN((void *)item_array); - } - - /* intentionally don't touch UI_BLOCK_IS_FLIP here, - * we don't know the context this is called in */ - } - else if (prop && RNA_property_type(prop) != PROP_ENUM) { - RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname); - return; - } - else { - RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); - return; - } -} - -void uiItemsEnumO(uiLayout *layout, const char *opname, const char *propname) -{ - uiItemsFullEnumO(layout, opname, propname, NULL, layout->root->opcontext, 0); -} - -/* for use in cases where we have */ -void uiItemEnumO_value(uiLayout *layout, - const char *name, - int icon, - const char *opname, - const char *propname, - int value) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - - /* enum lookup */ - PropertyRNA *prop = RNA_struct_find_property(&ptr, propname); - if (prop == NULL) { - RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); - return; - } - - RNA_property_enum_set(&ptr, prop, value); - - /* same as uiItemEnumO */ - if (!name) { - name = ui_menu_enumpropname(layout, &ptr, prop, value); - } - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} - -void uiItemEnumO_string(uiLayout *layout, - const char *name, - int icon, - const char *opname, - const char *propname, - const char *value_str) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - - PropertyRNA *prop = RNA_struct_find_property(&ptr, propname); - if (prop == NULL) { - RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); - return; - } - - /* enum lookup */ - /* no need for translations here */ - const EnumPropertyItem *item; - bool free; - RNA_property_enum_items(layout->root->block->evil_C, &ptr, prop, &item, NULL, &free); - - int value; - if (item == NULL || RNA_enum_value_from_id(item, value_str, &value) == 0) { - if (free) { - MEM_freeN((void *)item); - } - RNA_warning("%s.%s, enum %s not found", RNA_struct_identifier(ptr.type), propname, value_str); - return; - } - - if (free) { - MEM_freeN((void *)item); - } - - RNA_property_enum_set(&ptr, prop, value); - - /* same as uiItemEnumO */ - if (!name) { - name = ui_menu_enumpropname(layout, &ptr, prop, value); - } - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} - -void uiItemBooleanO(uiLayout *layout, - const char *name, - int icon, - const char *opname, - const char *propname, - int value) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - RNA_boolean_set(&ptr, propname, value); - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} - -void uiItemIntO(uiLayout *layout, - const char *name, - int icon, - const char *opname, - const char *propname, - int value) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - RNA_int_set(&ptr, propname, value); - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} - -void uiItemFloatO(uiLayout *layout, - const char *name, - int icon, - const char *opname, - const char *propname, - float value) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - RNA_float_set(&ptr, propname, value); - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} - -void uiItemStringO(uiLayout *layout, - const char *name, - int icon, - const char *opname, - const char *propname, - const char *value) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - PointerRNA ptr; - WM_operator_properties_create_ptr(&ptr, ot); - RNA_string_set(&ptr, propname, value); - - uiItemFullO_ptr(layout, ot, name, icon, ptr.data, layout->root->opcontext, 0, NULL); -} - -void uiItemO(uiLayout *layout, const char *name, int icon, const char *opname) -{ - uiItemFullO(layout, opname, name, icon, NULL, layout->root->opcontext, 0, NULL); -} - -/* RNA property items */ - -static void ui_item_rna_size(uiLayout *layout, - const char *name, - int icon, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - bool icon_only, - bool compact, - int *r_w, - int *r_h) -{ - int w = 0, h; - - /* arbitrary extended width by type */ - const PropertyType type = RNA_property_type(prop); - const PropertySubType subtype = RNA_property_subtype(prop); - const int len = RNA_property_array_length(ptr, prop); - - bool is_checkbox_only = false; - if (!name[0] && !icon_only) { - if (ELEM(type, PROP_STRING, PROP_POINTER)) { - name = "non-empty text"; - } - else if (type == PROP_BOOLEAN) { - if (icon == ICON_NONE) { - /* Exception for checkboxes, they need a little less space to align nicely. */ - is_checkbox_only = true; - } - icon = ICON_DOT; - } - else if (type == PROP_ENUM) { - /* Find the longest enum item name, instead of using a dummy text! */ - const EnumPropertyItem *item_array; - bool free; - RNA_property_enum_items_gettexted( - layout->root->block->evil_C, ptr, prop, &item_array, NULL, &free); - - for (const EnumPropertyItem *item = item_array; item->identifier; item++) { - if (item->identifier[0]) { - w = max_ii(w, ui_text_icon_width(layout, item->name, item->icon, compact)); - } - } - if (free) { - MEM_freeN((void *)item_array); - } - } - } - - if (!w) { - if (type == PROP_ENUM && icon_only) { - w = ui_text_icon_width(layout, "", ICON_BLANK1, compact); - if (index != RNA_ENUM_VALUE) { - w += 0.6f * UI_UNIT_X; - } - } - else { - /* not compact for float/int buttons, looks too squashed */ - w = ui_text_icon_width( - layout, name, icon, ELEM(type, PROP_FLOAT, PROP_INT) ? false : compact); - } - } - h = UI_UNIT_Y; - - /* increase height for arrays */ - if (index == RNA_NO_INDEX && len > 0) { - if (!name[0] && icon == ICON_NONE) { - h = 0; - } - if (layout->item.flag & UI_ITEM_PROP_SEP) { - h = 0; - } - if (ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER)) { - h += 2 * UI_UNIT_Y; - } - else if (subtype == PROP_MATRIX) { - h += ceilf(sqrtf(len)) * UI_UNIT_Y; - } - else { - h += len * UI_UNIT_Y; - } - } - - /* Increase width requirement if in a variable size layout. */ - if (ui_layout_variable_size(layout)) { - if (type == PROP_BOOLEAN && name[0]) { - w += UI_UNIT_X / 5; - } - else if (is_checkbox_only) { - w -= UI_UNIT_X / 4; - } - else if (type == PROP_ENUM && !icon_only) { - w += UI_UNIT_X / 4; - } - else if (ELEM(type, PROP_FLOAT, PROP_INT)) { - w += UI_UNIT_X * 3; - } - } - - *r_w = w; - *r_h = h; -} - -static bool ui_item_rna_is_expand(PropertyRNA *prop, int index, int item_flag) -{ - const bool is_array = RNA_property_array_check(prop); - const int subtype = RNA_property_subtype(prop); - return is_array && (index == RNA_NO_INDEX) && - ((item_flag & UI_ITEM_R_EXPAND) || - !ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA, PROP_DIRECTION)); -} - -/** - * Find first layout ancestor (or self) with a heading set. - * - * \returns the layout to add the heading to as fallback (i.e. if it can't be placed in a split - * layout). Its #uiLayout.heading member can be cleared to mark the heading as added (so - * it's not added multiple times). Returns a pointer to the heading - */ -static uiLayout *ui_layout_heading_find(uiLayout *cur_layout) -{ - for (uiLayout *parent = cur_layout; parent; parent = parent->parent) { - if (parent->heading[0]) { - return parent; - } - } - - return NULL; -} - -static void ui_layout_heading_label_add(uiLayout *layout, - uiLayout *heading_layout, - bool right_align, - bool respect_prop_split) -{ - const int prev_alignment = layout->alignment; - - if (right_align) { - uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_RIGHT); - } - - if (respect_prop_split) { - uiItemL_respect_property_split(layout, heading_layout->heading, ICON_NONE); - } - else { - uiItemL(layout, heading_layout->heading, ICON_NONE); - } - /* After adding the heading label, we have to mark it somehow as added, so it's not added again - * for other items in this layout. For now just clear it. */ - heading_layout->heading[0] = '\0'; - - layout->alignment = prev_alignment; -} - -/** - * Hack to add further items in a row into the second part of the split layout, so the label part - * keeps a fixed size. - * \return The layout to place further items in for the split layout. - */ -static uiLayout *ui_item_prop_split_layout_hack(uiLayout *layout_parent, uiLayout *layout_split) -{ - /* Tag item as using property split layout, this is inherited to children so they can get special - * treatment if needed. */ - layout_parent->item.flag |= UI_ITEM_INSIDE_PROP_SEP; - - if (layout_parent->item.type == ITEM_LAYOUT_ROW) { - /* Prevent further splits within the row. */ - uiLayoutSetPropSep(layout_parent, false); - - layout_parent->child_items_layout = uiLayoutRow(layout_split, true); - return layout_parent->child_items_layout; - } - return layout_split; -} - -void uiItemFullR(uiLayout *layout, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - int value, - int flag, - const char *name, - int icon) -{ - uiBlock *block = layout->root->block; - char namestr[UI_MAX_NAME_STR]; - const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0); - const bool inside_prop_sep = ((layout->item.flag & UI_ITEM_INSIDE_PROP_SEP) != 0); - /* Columns can define a heading to insert. If the first item added to a split layout doesn't have - * a label to display in the first column, the heading is inserted there. Otherwise it's inserted - * as a new row before the first item. */ - uiLayout *heading_layout = ui_layout_heading_find(layout); - /* Although checkboxes use the split layout, they are an exception and should only place their - * label in the second column, to not make that almost empty. - * - * Keep using 'use_prop_sep' instead of disabling it entirely because - * we need the ability to have decorators still. */ - bool use_prop_sep_split_label = use_prop_sep; - bool use_split_empty_name = (flag & UI_ITEM_R_SPLIT_EMPTY_NAME); - -#ifdef UI_PROP_DECORATE - struct { - bool use_prop_decorate; - int len; - uiLayout *layout; - uiBut *but; - } ui_decorate = { - .use_prop_decorate = (((layout->item.flag & UI_ITEM_PROP_DECORATE) != 0) && use_prop_sep), - }; -#endif /* UI_PROP_DECORATE */ - - UI_block_layout_set_current(block, layout); - ui_block_new_button_group(block, 0); - - /* retrieve info */ - const PropertyType type = RNA_property_type(prop); - const bool is_array = RNA_property_array_check(prop); - const int len = (is_array) ? RNA_property_array_length(ptr, prop) : 0; - - const bool icon_only = (flag & UI_ITEM_R_ICON_ONLY) != 0; - - /* Boolean with -1 to signify that the value depends on the presence of an icon. */ - const int toggle = ((flag & UI_ITEM_R_TOGGLE) ? 1 : ((flag & UI_ITEM_R_ICON_NEVER) ? 0 : -1)); - const bool no_icon = (toggle == 0); - - /* set name and icon */ - if (!name) { - if (!icon_only) { - name = RNA_property_ui_name(prop); - } - else { - name = ""; - } - } - - if (type != PROP_BOOLEAN) { - flag &= ~UI_ITEM_R_CHECKBOX_INVERT; - } - - if (flag & UI_ITEM_R_ICON_ONLY) { - /* pass */ - } - else if (ELEM(type, PROP_INT, PROP_FLOAT, PROP_STRING, PROP_POINTER)) { - if (use_prop_sep == false) { - name = ui_item_name_add_colon(name, namestr); - } - } - else if (type == PROP_BOOLEAN && is_array && index == RNA_NO_INDEX) { - if (use_prop_sep == false) { - name = ui_item_name_add_colon(name, namestr); - } - } - else if (type == PROP_ENUM && index != RNA_ENUM_VALUE) { - if (flag & UI_ITEM_R_COMPACT) { - name = ""; - } - else { - if (use_prop_sep == false) { - name = ui_item_name_add_colon(name, namestr); - } - } - } - - if (no_icon == false) { - if (icon == ICON_NONE) { - icon = RNA_property_ui_icon(prop); - } - - /* Menus and pie-menus don't show checkbox without this. */ - if ((layout->root->type == UI_LAYOUT_MENU) || - /* Use checkboxes only as a fallback in pie-menu's, when no icon is defined. */ - ((layout->root->type == UI_LAYOUT_PIEMENU) && (icon == ICON_NONE))) { - const int prop_flag = RNA_property_flag(prop); - if (type == PROP_BOOLEAN) { - if ((is_array == false) || (index != RNA_NO_INDEX)) { - if (prop_flag & PROP_ICONS_CONSECUTIVE) { - icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */ - } - else if (is_array) { - icon = (RNA_property_boolean_get_index(ptr, prop, index)) ? ICON_CHECKBOX_HLT : - ICON_CHECKBOX_DEHLT; - } - else { - icon = (RNA_property_boolean_get(ptr, prop)) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; - } - } - } - else if (type == PROP_ENUM) { - if (index == RNA_ENUM_VALUE) { - const int enum_value = RNA_property_enum_get(ptr, prop); - if (prop_flag & PROP_ICONS_CONSECUTIVE) { - icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */ - } - else if (prop_flag & PROP_ENUM_FLAG) { - icon = (enum_value & value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; - } - else { - icon = (enum_value == value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; - } - } - } - } - } - -#ifdef UI_PROP_SEP_ICON_WIDTH_EXCEPTION - if (use_prop_sep) { - if (type == PROP_BOOLEAN && (icon == ICON_NONE) && !icon_only) { - use_prop_sep_split_label = false; - /* For check-boxes we make an exception: We allow showing them in a split row even without - * label. It typically relates to its neighbor items, so no need for an extra label. */ - use_split_empty_name = true; - } - } -#endif - - if ((type == PROP_ENUM) && (RNA_property_flag(prop) & PROP_ENUM_FLAG)) { - flag |= UI_ITEM_R_EXPAND; - } - - const bool slider = (flag & UI_ITEM_R_SLIDER) != 0; - const bool expand = (flag & UI_ITEM_R_EXPAND) != 0; - const bool no_bg = (flag & UI_ITEM_R_NO_BG) != 0; - const bool compact = (flag & UI_ITEM_R_COMPACT) != 0; - - /* get size */ - int w, h; - ui_item_rna_size(layout, name, icon, ptr, prop, index, icon_only, compact, &w, &h); - - const eUIEmbossType prev_emboss = layout->emboss; - if (no_bg) { - layout->emboss = UI_EMBOSS_NONE; - } - - uiBut *but = NULL; - - /* Split the label / property. */ - uiLayout *layout_parent = layout; - - if (use_prop_sep) { - uiLayout *layout_row = NULL; -#ifdef UI_PROP_DECORATE - if (ui_decorate.use_prop_decorate) { - layout_row = uiLayoutRow(layout, true); - layout_row->space = 0; - ui_decorate.len = max_ii(1, len); - } -#endif /* UI_PROP_DECORATE */ - - if ((name[0] == '\0') && !use_split_empty_name) { - /* Ensure we get a column when text is not set. */ - layout = uiLayoutColumn(layout_row ? layout_row : layout, true); - layout->space = 0; - if (heading_layout) { - ui_layout_heading_label_add(layout, heading_layout, false, false); - } - } - else { - uiLayout *layout_split = uiLayoutSplit( - layout_row ? layout_row : layout, UI_ITEM_PROP_SEP_DIVIDE, true); - bool label_added = false; - uiLayout *layout_sub = uiLayoutColumn(layout_split, true); - layout_sub->space = 0; - - if (!use_prop_sep_split_label) { - /* Pass */ - } - else if (ui_item_rna_is_expand(prop, index, flag)) { - char name_with_suffix[UI_MAX_DRAW_STR + 2]; - char str[2] = {'\0'}; - for (int a = 0; a < len; a++) { - str[0] = RNA_property_array_item_char(prop, a); - const bool use_prefix = (a == 0 && name && name[0]); - if (use_prefix) { - char *s = name_with_suffix; - s += STRNCPY_RLEN(name_with_suffix, name); - *s++ = ' '; - *s++ = str[0]; - *s++ = '\0'; - } - but = uiDefBut(block, - UI_BTYPE_LABEL, - 0, - use_prefix ? name_with_suffix : str, - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - but->drawflag |= UI_BUT_TEXT_RIGHT; - but->drawflag &= ~UI_BUT_TEXT_LEFT; - - label_added = true; - } - } - else { - if (name) { - but = uiDefBut( - block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); - but->drawflag |= UI_BUT_TEXT_RIGHT; - but->drawflag &= ~UI_BUT_TEXT_LEFT; - - label_added = true; - } - } - - if (!label_added && heading_layout) { - ui_layout_heading_label_add(layout_sub, heading_layout, true, false); - } - - layout_split = ui_item_prop_split_layout_hack(layout_parent, layout_split); - - /* Watch out! We can only write into the new layout now. */ - if ((type == PROP_ENUM) && (flag & UI_ITEM_R_EXPAND)) { - /* Expanded enums each have their own name. */ - - /* Often expanded enum's are better arranged into a row, - * so check the existing layout. */ - if (uiLayoutGetLocalDir(layout) == UI_LAYOUT_HORIZONTAL) { - layout = uiLayoutRow(layout_split, true); - } - else { - layout = uiLayoutColumn(layout_split, true); - } - } - else { - if (use_prop_sep_split_label) { - name = ""; - } - layout = uiLayoutColumn(layout_split, true); - } - layout->space = 0; - } - -#ifdef UI_PROP_DECORATE - if (ui_decorate.use_prop_decorate) { - ui_decorate.layout = uiLayoutColumn(layout_row, true); - ui_decorate.layout->space = 0; - UI_block_layout_set_current(block, layout); - ui_decorate.but = block->buttons.last; - - /* Clear after. */ - layout->item.flag |= UI_ITEM_PROP_DECORATE_NO_PAD; - } -#endif /* UI_PROP_DECORATE */ - } - /* End split. */ - else if (heading_layout) { - /* Could not add heading to split layout, fallback to inserting it to the layout with the - * heading itself. */ - ui_layout_heading_label_add(heading_layout, heading_layout, false, false); - } - - /* array property */ - if (index == RNA_NO_INDEX && is_array) { - if (inside_prop_sep) { - /* Within a split row, add array items to a column so they match the column layout of - * previous items (e.g. transform vector with lock icon for each item). */ - layout = uiLayoutColumn(layout, true); - } - - ui_item_array(layout, - block, - name, - icon, - ptr, - prop, - len, - 0, - 0, - w, - h, - expand, - slider, - toggle, - icon_only, - compact, - !use_prop_sep_split_label); - } - /* enum item */ - else if (type == PROP_ENUM && index == RNA_ENUM_VALUE) { - if (icon && name[0] && !icon_only) { - uiDefIconTextButR_prop( - block, UI_BTYPE_ROW, 0, icon, name, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); - } - else if (icon) { - uiDefIconButR_prop( - block, UI_BTYPE_ROW, 0, icon, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); - } - else { - uiDefButR_prop( - block, UI_BTYPE_ROW, 0, name, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); - } - } - /* expanded enum */ - else if (type == PROP_ENUM && expand) { - ui_item_enum_expand(layout, block, ptr, prop, name, h, icon_only); - } - /* property with separate label */ - else if (ELEM(type, PROP_ENUM, PROP_STRING, PROP_POINTER)) { - but = ui_item_with_label(layout, block, name, icon, ptr, prop, index, 0, 0, w, h, flag); - but = ui_but_add_search(but, ptr, prop, NULL, NULL); - - if (layout->redalert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - if (layout->activate_init) { - UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); - } - } - /* single button */ - else { - but = uiDefAutoButR(block, ptr, prop, index, name, icon, 0, 0, w, h); - - if (slider && but->type == UI_BTYPE_NUM) { - uiButNumber *num_but = (uiButNumber *)but; - - but->a1 = num_but->step_size; - but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER); - } - - if (flag & UI_ITEM_R_CHECKBOX_INVERT) { - if (ELEM(but->type, - UI_BTYPE_CHECKBOX, - UI_BTYPE_CHECKBOX_N, - UI_BTYPE_ICON_TOGGLE, - UI_BTYPE_ICON_TOGGLE_N)) { - but->drawflag |= UI_BUT_CHECKBOX_INVERT; - } - } - - if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) { - but->type = UI_BTYPE_TOGGLE; - } - - if (layout->redalert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - if (layout->activate_init) { - UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); - } - } - - /* The resulting button may have the icon set since boolean button drawing - * is being 'helpful' and adding an icon for us. - * In this case we want the ability not to have an icon. - * - * We could pass an argument not to set the icon to begin with however this is the one case - * the functionality is needed. */ - if (but && no_icon) { - if ((icon == ICON_NONE) && (but->icon != ICON_NONE)) { - ui_def_but_icon_clear(but); - } - } - - /* Mark non-embossed textfields inside a listbox. */ - if (but && (block->flag & UI_BLOCK_LIST_ITEM) && (but->type == UI_BTYPE_TEXT) && - (but->emboss & UI_EMBOSS_NONE)) { - UI_but_flag_enable(but, UI_BUT_LIST_ITEM); - } - -#ifdef UI_PROP_DECORATE - if (ui_decorate.use_prop_decorate) { - uiBut *but_decorate = ui_decorate.but ? ui_decorate.but->next : block->buttons.first; - const bool use_blank_decorator = (flag & UI_ITEM_R_FORCE_BLANK_DECORATE); - uiLayout *layout_col = uiLayoutColumn(ui_decorate.layout, false); - layout_col->space = 0; - layout_col->emboss = UI_EMBOSS_NONE; - - int i; - for (i = 0; i < ui_decorate.len && but_decorate; i++) { - PointerRNA *ptr_dec = use_blank_decorator ? NULL : &but_decorate->rnapoin; - PropertyRNA *prop_dec = use_blank_decorator ? NULL : but_decorate->rnaprop; - - /* The icons are set in 'ui_but_anim_flag' */ - uiItemDecoratorR_prop(layout_col, ptr_dec, prop_dec, but_decorate->rnaindex); - but = block->buttons.last; - - /* Order the decorator after the button we decorate, this is used so we can always - * do a quick lookup. */ - BLI_remlink(&block->buttons, but); - BLI_insertlinkafter(&block->buttons, but_decorate, but); - but_decorate = but->next; - } - BLI_assert(ELEM(i, 1, ui_decorate.len)); - - layout->item.flag &= ~UI_ITEM_PROP_DECORATE_NO_PAD; - } -#endif /* UI_PROP_DECORATE */ - - if (no_bg) { - layout->emboss = prev_emboss; - } - - /* ensure text isn't added to icon_only buttons */ - if (but && icon_only) { - BLI_assert(but->str[0] == '\0'); - } -} - -void uiItemR( - uiLayout *layout, PointerRNA *ptr, const char *propname, int flag, const char *name, int icon) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop) { - ui_item_disabled(layout, propname); - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiItemFullR(layout, ptr, prop, RNA_NO_INDEX, 0, flag, name, icon); -} - -/** - * Use a wrapper function since re-implementing all the logic in this function would be messy. - */ -void uiItemFullR_with_popover(uiLayout *layout, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - int value, - int flag, - const char *name, - int icon, - const char *panel_type) -{ - uiBlock *block = layout->root->block; - uiBut *but = block->buttons.last; - uiItemFullR(layout, ptr, prop, index, value, flag, name, icon); - but = but->next; - while (but) { - if (but->rnaprop == prop && ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)) { - ui_but_rna_menu_convert_to_panel_type(but, panel_type); - break; - } - but = but->next; - } - if (but == NULL) { - const char *propname = RNA_property_identifier(prop); - ui_item_disabled(layout, panel_type); - RNA_warning("property could not use a popover: %s.%s (%s)", - RNA_struct_identifier(ptr->type), - propname, - panel_type); - } -} - -void uiItemFullR_with_menu(uiLayout *layout, - PointerRNA *ptr, - PropertyRNA *prop, - int index, - int value, - int flag, - const char *name, - int icon, - const char *menu_type) -{ - uiBlock *block = layout->root->block; - uiBut *but = block->buttons.last; - uiItemFullR(layout, ptr, prop, index, value, flag, name, icon); - but = but->next; - while (but) { - if (but->rnaprop == prop && but->type == UI_BTYPE_MENU) { - ui_but_rna_menu_convert_to_menu_type(but, menu_type); - break; - } - but = but->next; - } - if (but == NULL) { - const char *propname = RNA_property_identifier(prop); - ui_item_disabled(layout, menu_type); - RNA_warning("property could not use a menu: %s.%s (%s)", - RNA_struct_identifier(ptr->type), - propname, - menu_type); - } -} - -void uiItemEnumR_prop(uiLayout *layout, - const char *name, - int icon, - struct PointerRNA *ptr, - PropertyRNA *prop, - int value) -{ - if (RNA_property_type(prop) != PROP_ENUM) { - const char *propname = RNA_property_identifier(prop); - ui_item_disabled(layout, propname); - RNA_warning("property not an enum: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiItemFullR(layout, ptr, prop, RNA_ENUM_VALUE, value, 0, name, icon); -} - -void uiItemEnumR(uiLayout *layout, - const char *name, - int icon, - struct PointerRNA *ptr, - const char *propname, - int value) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (prop == NULL) { - ui_item_disabled(layout, propname); - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiItemFullR(layout, ptr, prop, RNA_ENUM_VALUE, value, 0, name, icon); -} - -void uiItemEnumR_string_prop(uiLayout *layout, - struct PointerRNA *ptr, - PropertyRNA *prop, - const char *value, - const char *name, - int icon) -{ - if (UNLIKELY(RNA_property_type(prop) != PROP_ENUM)) { - const char *propname = RNA_property_identifier(prop); - ui_item_disabled(layout, propname); - RNA_warning("not an enum property: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - const EnumPropertyItem *item; - bool free; - RNA_property_enum_items(layout->root->block->evil_C, ptr, prop, &item, NULL, &free); - - int ivalue; - if (!RNA_enum_value_from_id(item, value, &ivalue)) { - const char *propname = RNA_property_identifier(prop); - if (free) { - MEM_freeN((void *)item); - } - ui_item_disabled(layout, propname); - RNA_warning("enum property value not found: %s", value); - return; - } - - for (int a = 0; item[a].identifier; a++) { - if (item[a].identifier[0] == '\0') { - /* Skip enum item separators. */ - continue; - } - if (item[a].value == ivalue) { - const char *item_name = name ? - name : - CTX_IFACE_(RNA_property_translation_context(prop), item[a].name); - const int flag = item_name[0] ? 0 : UI_ITEM_R_ICON_ONLY; - - uiItemFullR( - layout, ptr, prop, RNA_ENUM_VALUE, ivalue, flag, item_name, icon ? icon : item[a].icon); - break; - } - } - - if (free) { - MEM_freeN((void *)item); - } -} - -void uiItemEnumR_string(uiLayout *layout, - struct PointerRNA *ptr, - const char *propname, - const char *value, - const char *name, - int icon) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - if (UNLIKELY(prop == NULL)) { - ui_item_disabled(layout, propname); - RNA_warning("enum property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - uiItemEnumR_string_prop(layout, ptr, prop, value, name, icon); -} - -void uiItemsEnumR(uiLayout *layout, struct PointerRNA *ptr, const char *propname) -{ - uiBlock *block = layout->root->block; - - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop) { - ui_item_disabled(layout, propname); - RNA_warning("enum property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - if (RNA_property_type(prop) != PROP_ENUM) { - RNA_warning("not an enum property: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiLayout *split = uiLayoutSplit(layout, 0.0f, false); - uiLayout *column = uiLayoutColumn(split, false); - - int totitem; - const EnumPropertyItem *item; - bool free; - RNA_property_enum_items_gettexted(block->evil_C, ptr, prop, &item, &totitem, &free); - - for (int i = 0; i < totitem; i++) { - if (item[i].identifier[0]) { - uiItemEnumR_prop(column, item[i].name, item[i].icon, ptr, prop, item[i].value); - ui_but_tip_from_enum_item(block->buttons.last, &item[i]); - } - else { - if (item[i].name) { - if (i != 0) { - column = uiLayoutColumn(split, false); - /* inconsistent, but menus with labels do not look good flipped */ - block->flag |= UI_BLOCK_NO_FLIP; - } - - uiItemL(column, item[i].name, ICON_NONE); - uiBut *bt = block->buttons.last; - bt->drawflag = UI_BUT_TEXT_LEFT; - - ui_but_tip_from_enum_item(bt, &item[i]); - } - else { - uiItemS(column); - } - } - } - - if (free) { - MEM_freeN((void *)item); - } - - /* intentionally don't touch UI_BLOCK_IS_FLIP here, - * we don't know the context this is called in */ -} - -/* Pointer RNA button with search */ - -static void search_id_collection(StructRNA *ptype, PointerRNA *r_ptr, PropertyRNA **r_prop) -{ - /* look for collection property in Main */ - /* NOTE: using global Main is OK-ish here, UI shall not access other Mains anyway. */ - RNA_main_pointer_create(G_MAIN, r_ptr); - - *r_prop = NULL; - - RNA_STRUCT_BEGIN (r_ptr, iprop) { - /* if it's a collection and has same pointer type, we've got it */ - if (RNA_property_type(iprop) == PROP_COLLECTION) { - StructRNA *srna = RNA_property_pointer_type(r_ptr, iprop); - - if (ptype == srna) { - *r_prop = iprop; - break; - } - } - } - RNA_STRUCT_END; -} - -static void ui_rna_collection_search_arg_free_fn(void *ptr) -{ - uiRNACollectionSearch *coll_search = ptr; - UI_butstore_free(coll_search->butstore_block, coll_search->butstore); - MEM_freeN(ptr); -} - -/** - * \note May reallocate \a but, so the possibly new address is returned. - */ -uiBut *ui_but_add_search( - uiBut *but, PointerRNA *ptr, PropertyRNA *prop, PointerRNA *searchptr, PropertyRNA *searchprop) -{ - /* for ID's we do automatic lookup */ - PointerRNA sptr; - if (!searchprop) { - if (RNA_property_type(prop) == PROP_POINTER) { - StructRNA *ptype = RNA_property_pointer_type(ptr, prop); - search_id_collection(ptype, &sptr, &searchprop); - searchptr = &sptr; - } - } - - /* turn button into search button */ - if (searchprop) { - uiRNACollectionSearch *coll_search = MEM_mallocN(sizeof(*coll_search), __func__); - uiButSearch *search_but; - - but = ui_but_change_type(but, UI_BTYPE_SEARCH_MENU); - search_but = (uiButSearch *)but; - search_but->rnasearchpoin = *searchptr; - search_but->rnasearchprop = searchprop; - but->hardmax = MAX2(but->hardmax, 256.0f); - but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; - if (RNA_property_is_unlink(prop)) { - but->flag |= UI_BUT_VALUE_CLEAR; - } - - coll_search->target_ptr = *ptr; - coll_search->target_prop = prop; - coll_search->search_ptr = *searchptr; - coll_search->search_prop = searchprop; - coll_search->search_but = but; - coll_search->butstore_block = but->block; - coll_search->butstore = UI_butstore_create(coll_search->butstore_block); - UI_butstore_register(coll_search->butstore, &coll_search->search_but); - - if (RNA_property_type(prop) == PROP_ENUM) { - /* XXX, this will have a menu string, - * but in this case we just want the text */ - but->str[0] = 0; - } - - UI_but_func_search_set(but, - ui_searchbox_create_generic, - ui_rna_collection_search_update_fn, - coll_search, - false, - ui_rna_collection_search_arg_free_fn, - NULL, - NULL); - } - else if (but->type == UI_BTYPE_SEARCH_MENU) { - /* In case we fail to find proper searchprop, - * so other code might have already set but->type to search menu... */ - but->flag |= UI_BUT_DISABLED; - } - - return but; -} - -void uiItemPointerR_prop(uiLayout *layout, - PointerRNA *ptr, - PropertyRNA *prop, - PointerRNA *searchptr, - PropertyRNA *searchprop, - const char *name, - int icon) -{ - const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0); - - ui_block_new_button_group(uiLayoutGetBlock(layout), 0); - - const PropertyType type = RNA_property_type(prop); - if (!ELEM(type, PROP_POINTER, PROP_STRING, PROP_ENUM)) { - RNA_warning("Property %s.%s must be a pointer, string or enum", - RNA_struct_identifier(ptr->type), - RNA_property_identifier(prop)); - return; - } - if (RNA_property_type(searchprop) != PROP_COLLECTION) { - RNA_warning("search collection property is not a collection type: %s.%s", - RNA_struct_identifier(searchptr->type), - RNA_property_identifier(searchprop)); - return; - } - - /* get icon & name */ - if (icon == ICON_NONE) { - StructRNA *icontype; - if (type == PROP_POINTER) { - icontype = RNA_property_pointer_type(ptr, prop); - } - else { - icontype = RNA_property_pointer_type(searchptr, searchprop); - } - - icon = RNA_struct_ui_icon(icontype); - } - if (!name) { - name = RNA_property_ui_name(prop); - } - - char namestr[UI_MAX_NAME_STR]; - if (use_prop_sep == false) { - name = ui_item_name_add_colon(name, namestr); - } - - /* create button */ - uiBlock *block = uiLayoutGetBlock(layout); - - int w, h; - ui_item_rna_size(layout, name, icon, ptr, prop, 0, 0, false, &w, &h); - w += UI_UNIT_X; /* X icon needs more space */ - uiBut *but = ui_item_with_label(layout, block, name, icon, ptr, prop, 0, 0, 0, w, h, 0); - - ui_but_add_search(but, ptr, prop, searchptr, searchprop); -} - -void uiItemPointerR(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - PointerRNA *searchptr, - const char *searchpropname, - const char *name, - int icon) -{ - /* validate arguments */ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - if (!prop) { - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - PropertyRNA *searchprop = RNA_struct_find_property(searchptr, searchpropname); - if (!searchprop) { - RNA_warning("search collection property not found: %s.%s", - RNA_struct_identifier(searchptr->type), - searchpropname); - return; - } - - uiItemPointerR_prop(layout, ptr, prop, searchptr, searchprop, name, icon); -} - -/* menu item */ -void ui_item_menutype_func(bContext *C, uiLayout *layout, void *arg_mt) -{ - MenuType *mt = (MenuType *)arg_mt; - - UI_menutype_draw(C, mt, layout); - - /* Menus are created flipped (from event handling point of view). */ - layout->root->block->flag ^= UI_BLOCK_IS_FLIP; -} - -void ui_item_paneltype_func(bContext *C, uiLayout *layout, void *arg_pt) -{ - PanelType *pt = (PanelType *)arg_pt; - UI_paneltype_draw(C, pt, layout); - - /* panels are created flipped (from event handling pov) */ - layout->root->block->flag ^= UI_BLOCK_IS_FLIP; -} - -static uiBut *ui_item_menu(uiLayout *layout, - const char *name, - int icon, - uiMenuCreateFunc func, - void *arg, - void *argN, - const char *tip, - bool force_menu) -{ - uiBlock *block = layout->root->block; - uiLayout *heading_layout = ui_layout_heading_find(layout); - - UI_block_layout_set_current(block, layout); - ui_block_new_button_group(block, 0); - - if (!name) { - name = ""; - } - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - int w = ui_text_icon_width(layout, name, icon, 1); - const int h = UI_UNIT_Y; - - if (layout->root->type == UI_LAYOUT_HEADER) { /* ugly .. */ - if (icon == ICON_NONE && force_menu) { - /* pass */ - } - else if (force_menu) { - w += 0.6f * UI_UNIT_X; - } - else { - if (name[0]) { - w -= UI_UNIT_X / 2; - } - } - } - - if (heading_layout) { - ui_layout_heading_label_add(layout, heading_layout, true, true); - } - - uiBut *but; - if (name[0] && icon) { - but = uiDefIconTextMenuBut(block, func, arg, icon, name, 0, 0, w, h, tip); - } - else if (icon) { - but = uiDefIconMenuBut(block, func, arg, icon, 0, 0, w, h, tip); - if (force_menu && name[0]) { - UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); - } - } - else { - but = uiDefMenuBut(block, func, arg, name, 0, 0, w, h, tip); - } - - if (argN) { - /* ugly .. */ - if (arg != argN) { - but->poin = (char *)but; - } - but->func_argN = argN; - } - - if (ELEM(layout->root->type, UI_LAYOUT_PANEL, UI_LAYOUT_TOOLBAR) || - /* We never want a drop-down in menu! */ - (force_menu && layout->root->type != UI_LAYOUT_MENU)) { - UI_but_type_set_menu_from_pulldown(but); - } - - return but; -} - -void uiItemM_ptr(uiLayout *layout, MenuType *mt, const char *name, int icon) -{ - if (!name) { - name = CTX_IFACE_(mt->translation_context, mt->label); - } - - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - ui_item_menu(layout, - name, - icon, - ui_item_menutype_func, - mt, - NULL, - mt->description ? TIP_(mt->description) : "", - false); -} - -void uiItemM(uiLayout *layout, const char *menuname, const char *name, int icon) -{ - MenuType *mt = WM_menutype_find(menuname, false); - if (mt == NULL) { - RNA_warning("not found %s", menuname); - return; - } - uiItemM_ptr(layout, mt, name, icon); -} - -void uiItemMContents(uiLayout *layout, const char *menuname) -{ - MenuType *mt = WM_menutype_find(menuname, false); - if (mt == NULL) { - RNA_warning("not found %s", menuname); - return; - } - - uiBlock *block = layout->root->block; - bContext *C = block->evil_C; - UI_menutype_draw(C, mt, layout); -} - -/** - * Insert a decorator item for a button with the same property as \a prop. - * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a prop. - */ -void uiItemDecoratorR_prop(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop, int index) -{ - uiBlock *block = layout->root->block; - - UI_block_layout_set_current(block, layout); - uiLayout *col = uiLayoutColumn(layout, false); - col->space = 0; - col->emboss = UI_EMBOSS_NONE; - - if (ELEM(NULL, ptr, prop) || !RNA_property_animateable(ptr, prop)) { - uiBut *but = uiDefIconBut(block, - UI_BTYPE_DECORATOR, - 0, - ICON_BLANK1, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - ""); - but->flag |= UI_BUT_DISABLED; - return; - } - - const bool is_expand = ui_item_rna_is_expand(prop, index, 0); - const bool is_array = RNA_property_array_check(prop); - - /* Loop for the array-case, but only do in case of an expanded array. */ - for (int i = 0; i < (is_expand ? RNA_property_array_length(ptr, prop) : 1); i++) { - uiButDecorator *decorator_but = (uiButDecorator *)uiDefIconBut(block, - UI_BTYPE_DECORATOR, - 0, - ICON_DOT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Animate property")); - - UI_but_func_set(&decorator_but->but, ui_but_anim_decorate_cb, decorator_but, NULL); - decorator_but->but.flag |= UI_BUT_UNDO | UI_BUT_DRAG_LOCK; - /* Reusing RNA search members, setting actual RNA data has many side-effects. */ - decorator_but->rnapoin = *ptr; - decorator_but->rnaprop = prop; - /* ui_def_but_rna() sets non-array buttons to have a RNA index of 0. */ - decorator_but->rnaindex = (!is_array || is_expand) ? i : index; - } -} - -/** - * Insert a decorator item for a button with the same property as \a prop. - * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a propname. - */ -void uiItemDecoratorR(uiLayout *layout, PointerRNA *ptr, const char *propname, int index) -{ - PropertyRNA *prop = NULL; - - if (ptr && propname) { - /* validate arguments */ - prop = RNA_struct_find_property(ptr, propname); - if (!prop) { - ui_item_disabled(layout, propname); - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - } - - /* ptr and prop are allowed to be NULL here. */ - uiItemDecoratorR_prop(layout, ptr, prop, index); -} - -/* popover */ -void uiItemPopoverPanel_ptr( - uiLayout *layout, const bContext *C, PanelType *pt, const char *name, int icon) -{ - if (!name) { - name = CTX_IFACE_(pt->translation_context, pt->label); - } - - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - const bool ok = (pt->poll == NULL) || pt->poll(C, pt); - if (ok && (pt->draw_header != NULL)) { - layout = uiLayoutRow(layout, true); - Panel panel = { - .type = pt, - .layout = layout, - .flag = PNL_POPOVER, - }; - pt->draw_header(C, &panel); - } - uiBut *but = ui_item_menu( - layout, name, icon, ui_item_paneltype_func, pt, NULL, pt->description, true); - but->type = UI_BTYPE_POPOVER; - if (!ok) { - but->flag |= UI_BUT_DISABLED; - } -} - -void uiItemPopoverPanel( - uiLayout *layout, const bContext *C, const char *panel_type, const char *name, int icon) -{ - PanelType *pt = WM_paneltype_find(panel_type, true); - if (pt == NULL) { - RNA_warning("Panel type not found '%s'", panel_type); - return; - } - uiItemPopoverPanel_ptr(layout, C, pt, name, icon); -} - -void uiItemPopoverPanelFromGroup(uiLayout *layout, - bContext *C, - int space_id, - int region_id, - const char *context, - const char *category) -{ - SpaceType *st = BKE_spacetype_from_id(space_id); - if (st == NULL) { - RNA_warning("space type not found %d", space_id); - return; - } - ARegionType *art = BKE_regiontype_from_id(st, region_id); - if (art == NULL) { - RNA_warning("region type not found %d", region_id); - return; - } - - LISTBASE_FOREACH (PanelType *, pt, &art->paneltypes) { - /* Causes too many panels, check context. */ - if (pt->parent_id[0] == '\0') { - if (/* (*context == '\0') || */ STREQ(pt->context, context)) { - if ((*category == '\0') || STREQ(pt->category, category)) { - if (pt->poll == NULL || pt->poll(C, pt)) { - uiItemPopoverPanel_ptr(layout, C, pt, NULL, ICON_NONE); - } - } - } - } - } -} - -/* label item */ -static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon) -{ - uiBlock *block = layout->root->block; - - UI_block_layout_set_current(block, layout); - ui_block_new_button_group(block, 0); - - if (!name) { - name = ""; - } - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - const int w = ui_text_icon_width(layout, name, icon, 0); - - uiBut *but; - if (icon && name[0]) { - but = uiDefIconTextBut( - block, UI_BTYPE_LABEL, 0, icon, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL); - } - else if (icon) { - but = uiDefIconBut( - block, UI_BTYPE_LABEL, 0, icon, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL); - } - else { - but = uiDefBut(block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL); - } - - /* to compensate for string size padding in ui_text_icon_width, - * make text aligned right if the layout is aligned right. - */ - if (uiLayoutGetAlignment(layout) == UI_LAYOUT_ALIGN_RIGHT) { - but->drawflag &= ~UI_BUT_TEXT_LEFT; /* default, needs to be unset */ - but->drawflag |= UI_BUT_TEXT_RIGHT; - } - - /* Mark as a label inside a listbox. */ - if (block->flag & UI_BLOCK_LIST_ITEM) { - but->flag |= UI_BUT_LIST_ITEM; - } - - if (layout->redalert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - return but; -} - -void uiItemL_ex( - uiLayout *layout, const char *name, int icon, const bool highlight, const bool redalert) -{ - uiBut *but = uiItemL_(layout, name, icon); - - if (highlight) { - /* TODO: add another flag for this. */ - UI_but_flag_enable(but, UI_SELECT_DRAW); - } - - if (redalert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } -} - -void uiItemL(uiLayout *layout, const char *name, int icon) -{ - uiItemL_(layout, name, icon); -} - -/** - * Normally, we handle the split layout in #uiItemFullR(), but there are other cases where the - * logic is needed. Ideally, #uiItemFullR() could just call this, but it currently has too many - * special needs. - */ -uiPropertySplitWrapper uiItemPropertySplitWrapperCreate(uiLayout *parent_layout) -{ - uiPropertySplitWrapper split_wrapper = {NULL}; - - uiLayout *layout_row = uiLayoutRow(parent_layout, true); - uiLayout *layout_split = uiLayoutSplit(layout_row, UI_ITEM_PROP_SEP_DIVIDE, true); - - split_wrapper.label_column = uiLayoutColumn(layout_split, true); - split_wrapper.label_column->alignment = UI_LAYOUT_ALIGN_RIGHT; - split_wrapper.property_row = ui_item_prop_split_layout_hack(parent_layout, layout_split); - split_wrapper.decorate_column = uiLayoutColumn(layout_row, true); - - return split_wrapper; -} - -/* - * Helper to add a label and creates a property split layout if needed. - */ -uiLayout *uiItemL_respect_property_split(uiLayout *layout, const char *text, int icon) -{ - if (layout->item.flag & UI_ITEM_PROP_SEP) { - uiBlock *block = uiLayoutGetBlock(layout); - const uiPropertySplitWrapper split_wrapper = uiItemPropertySplitWrapperCreate(layout); - /* Further items added to 'layout' will automatically be added to split_wrapper.property_row */ - - uiItemL_(split_wrapper.label_column, text, icon); - UI_block_layout_set_current(block, split_wrapper.property_row); - - return split_wrapper.decorate_column; - } - - char namestr[UI_MAX_NAME_STR]; - if (text) { - text = ui_item_name_add_colon(text, namestr); - } - uiItemL_(layout, text, icon); - - return layout; -} - -void uiItemLDrag(uiLayout *layout, PointerRNA *ptr, const char *name, int icon) -{ - uiBut *but = uiItemL_(layout, name, icon); - - if (ptr && ptr->type) { - if (RNA_struct_is_ID(ptr->type)) { - UI_but_drag_set_id(but, ptr->owner_id); - } - } -} - -/* value item */ -void uiItemV(uiLayout *layout, const char *name, int icon, int argval) -{ - /* label */ - uiBlock *block = layout->root->block; - int *retvalue = (block->handle) ? &block->handle->retvalue : NULL; - - UI_block_layout_set_current(block, layout); - - if (!name) { - name = ""; - } - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - const int w = ui_text_icon_width(layout, name, icon, 0); - - if (icon && name[0]) { - uiDefIconTextButI(block, - UI_BTYPE_BUT, - argval, - icon, - name, - 0, - 0, - w, - UI_UNIT_Y, - retvalue, - 0.0, - 0.0, - 0, - -1, - ""); - } - else if (icon) { - uiDefIconButI( - block, UI_BTYPE_BUT, argval, icon, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, -1, ""); - } - else { - uiDefButI( - block, UI_BTYPE_BUT, argval, name, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, -1, ""); - } -} - -/* separator item */ -void uiItemS_ex(uiLayout *layout, float factor) -{ - uiBlock *block = layout->root->block; - const bool is_menu = ui_block_is_menu(block); - if (is_menu && !UI_block_can_add_separator(block)) { - return; - } - int space = (is_menu) ? 0.45f * UI_UNIT_X : 0.3f * UI_UNIT_X; - space *= factor; - - UI_block_layout_set_current(block, layout); - uiDefBut(block, - (is_menu) ? UI_BTYPE_SEPR_LINE : UI_BTYPE_SEPR, - 0, - "", - 0, - 0, - space, - space, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); -} - -/* separator item */ -void uiItemS(uiLayout *layout) -{ - uiItemS_ex(layout, 1.0f); -} - -/* Flexible spacing. */ -void uiItemSpacer(uiLayout *layout) -{ - uiBlock *block = layout->root->block; - const bool is_popup = ui_block_is_popup_any(block); - - if (is_popup) { - printf("Error: separator_spacer() not supported in popups.\n"); - return; - } - - if (block->direction & UI_DIR_RIGHT) { - printf("Error: separator_spacer() only supported in horizontal blocks.\n"); - return; - } - - UI_block_layout_set_current(block, layout); - uiDefBut(block, - UI_BTYPE_SEPR_SPACER, - 0, - "", - 0, - 0, - 0.3f * UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); -} - -/* level items */ -void uiItemMenuF(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *arg) -{ - if (!func) { - return; - } - - ui_item_menu(layout, name, icon, func, arg, NULL, "", false); -} - -/** - * Version of #uiItemMenuF that free's `argN`. - */ -void uiItemMenuFN(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *argN) -{ - if (!func) { - return; - } - - /* Second 'argN' only ensures it gets freed. */ - ui_item_menu(layout, name, icon, func, argN, argN, "", false); -} - -typedef struct MenuItemLevel { - int opcontext; - /* don't use pointers to the strings because python can dynamically - * allocate strings and free before the menu draws, see T27304. */ - char opname[OP_MAX_TYPENAME]; - char propname[MAX_IDPROP_NAME]; - PointerRNA rnapoin; -} MenuItemLevel; - -static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, void *arg) -{ - MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN); - - uiLayoutSetOperatorContext(layout, lvl->opcontext); - uiItemsEnumO(layout, lvl->opname, lvl->propname); - - layout->root->block->flag |= UI_BLOCK_IS_FLIP; - - /* override default, needed since this was assumed pre 2.70 */ - UI_block_direction_set(layout->root->block, UI_DIR_DOWN); -} - -void uiItemMenuEnumO_ptr(uiLayout *layout, - bContext *C, - wmOperatorType *ot, - const char *propname, - const char *name, - int icon) -{ - /* Caller must check */ - BLI_assert(ot->srna != NULL); - - if (name == NULL) { - name = WM_operatortype_name(ot, NULL); - } - - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - MenuItemLevel *lvl = MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel"); - BLI_strncpy(lvl->opname, ot->idname, sizeof(lvl->opname)); - BLI_strncpy(lvl->propname, propname, sizeof(lvl->propname)); - lvl->opcontext = layout->root->opcontext; - - uiBut *but = ui_item_menu(layout, name, icon, menu_item_enum_opname_menu, NULL, lvl, NULL, true); - - /* add hotkey here, lower UI code can't detect it */ - if ((layout->root->block->flag & UI_BLOCK_LOOP) && (ot->prop && ot->invoke)) { - char keybuf[128]; - if (WM_key_event_operator_string( - C, ot->idname, layout->root->opcontext, NULL, false, keybuf, sizeof(keybuf))) { - ui_but_add_shortcut(but, keybuf, false); - } - } -} - -void uiItemMenuEnumO(uiLayout *layout, - bContext *C, - const char *opname, - const char *propname, - const char *name, - int icon) -{ - wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ - - UI_OPERATOR_ERROR_RET(ot, opname, return ); - - if (!ot->srna) { - ui_item_disabled(layout, opname); - RNA_warning("operator missing srna '%s'", opname); - return; - } - - uiItemMenuEnumO_ptr(layout, C, ot, propname, name, icon); -} - -static void menu_item_enum_rna_menu(bContext *UNUSED(C), uiLayout *layout, void *arg) -{ - MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN); - - uiLayoutSetOperatorContext(layout, lvl->opcontext); - uiItemsEnumR(layout, &lvl->rnapoin, lvl->propname); - layout->root->block->flag |= UI_BLOCK_IS_FLIP; -} - -void uiItemMenuEnumR_prop( - uiLayout *layout, struct PointerRNA *ptr, PropertyRNA *prop, const char *name, int icon) -{ - if (!name) { - name = RNA_property_ui_name(prop); - } - if (layout->root->type == UI_LAYOUT_MENU && !icon) { - icon = ICON_BLANK1; - } - - MenuItemLevel *lvl = MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel"); - lvl->rnapoin = *ptr; - BLI_strncpy(lvl->propname, RNA_property_identifier(prop), sizeof(lvl->propname)); - lvl->opcontext = layout->root->opcontext; - - ui_item_menu(layout, - name, - icon, - menu_item_enum_rna_menu, - NULL, - lvl, - RNA_property_description(prop), - false); -} - -void uiItemMenuEnumR( - uiLayout *layout, struct PointerRNA *ptr, const char *propname, const char *name, int icon) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - if (!prop) { - ui_item_disabled(layout, propname); - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiItemMenuEnumR_prop(layout, ptr, prop, name, icon); -} - -void uiItemTabsEnumR_prop(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - PropertyRNA *prop, - PointerRNA *ptr_highlight, - PropertyRNA *prop_highlight, - bool icon_only) -{ - uiBlock *block = layout->root->block; - - UI_block_layout_set_current(block, layout); - ui_item_enum_expand_tabs( - layout, C, block, ptr, prop, ptr_highlight, prop_highlight, NULL, UI_UNIT_Y, icon_only); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Layout Items - * \{ */ - -/* single-row layout */ -static void ui_litem_estimate_row(uiLayout *litem) -{ - int itemw, itemh; - bool min_size_flag = true; - - litem->w = 0; - litem->h = 0; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - - min_size_flag = min_size_flag && (item->flag & UI_ITEM_FIXED_SIZE); - - litem->w += itemw; - litem->h = MAX2(itemh, litem->h); - - if (item->next) { - litem->w += litem->space; - } - } - - if (min_size_flag) { - litem->item.flag |= UI_ITEM_FIXED_SIZE; - } -} - -static int ui_litem_min_width(int itemw) -{ - return MIN2(2 * UI_UNIT_X, itemw); -} - -static void ui_litem_layout_row(uiLayout *litem) -{ - uiItem *last_free_item = NULL; - int x, neww, newtotw, itemw, minw, itemh, offset; - int freew, fixedx, freex, flag = 0, lastw = 0; - float extra_pixel; - - /* x = litem->x; */ /* UNUSED */ - const int y = litem->y; - int w = litem->w; - int totw = 0; - int tot = 0; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - totw += itemw; - tot++; - } - - if (totw == 0) { - return; - } - - if (w != 0) { - w -= (tot - 1) * litem->space; - } - int fixedw = 0; - - /* keep clamping items to fixed minimum size until all are done */ - do { - freew = 0; - x = 0; - flag = 0; - newtotw = totw; - extra_pixel = 0.0f; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - if (item->flag & UI_ITEM_AUTO_FIXED_SIZE) { - continue; - } - - ui_item_size(item, &itemw, &itemh); - minw = ui_litem_min_width(itemw); - - if (w - lastw > 0) { - neww = ui_item_fit(itemw, x, totw, w - lastw, !item->next, litem->alignment, &extra_pixel); - } - else { - neww = 0; /* no space left, all will need clamping to minimum size */ - } - - x += neww; - - bool min_flag = item->flag & UI_ITEM_FIXED_SIZE; - /* ignore min flag for rows with right or center alignment */ - if (item->type != ITEM_BUTTON && - ELEM(((uiLayout *)item)->alignment, UI_LAYOUT_ALIGN_RIGHT, UI_LAYOUT_ALIGN_CENTER) && - litem->alignment == UI_LAYOUT_ALIGN_EXPAND && - ((uiItem *)litem)->flag & UI_ITEM_FIXED_SIZE) { - min_flag = false; - } - - if ((neww < minw || min_flag) && w != 0) { - /* fixed size */ - item->flag |= UI_ITEM_AUTO_FIXED_SIZE; - if (item->type != ITEM_BUTTON && item->flag & UI_ITEM_FIXED_SIZE) { - minw = itemw; - } - fixedw += minw; - flag = 1; - newtotw -= itemw; - } - else { - /* keep free size */ - item->flag &= ~UI_ITEM_AUTO_FIXED_SIZE; - freew += itemw; - } - } - - totw = newtotw; - lastw = fixedw; - } while (flag); - - freex = 0; - fixedx = 0; - extra_pixel = 0.0f; - x = litem->x; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - minw = ui_litem_min_width(itemw); - - if (item->flag & UI_ITEM_AUTO_FIXED_SIZE) { - /* fixed minimum size items */ - if (item->type != ITEM_BUTTON && item->flag & UI_ITEM_FIXED_SIZE) { - minw = itemw; - } - itemw = ui_item_fit( - minw, fixedx, fixedw, min_ii(w, fixedw), !item->next, litem->alignment, &extra_pixel); - fixedx += itemw; - } - else { - /* free size item */ - itemw = ui_item_fit( - itemw, freex, freew, w - fixedw, !item->next, litem->alignment, &extra_pixel); - freex += itemw; - last_free_item = item; - } - - /* align right/center */ - offset = 0; - if (litem->alignment == UI_LAYOUT_ALIGN_RIGHT) { - if (freew + fixedw > 0 && freew + fixedw < w) { - offset = w - (fixedw + freew); - } - } - else if (litem->alignment == UI_LAYOUT_ALIGN_CENTER) { - if (freew + fixedw > 0 && freew + fixedw < w) { - offset = (w - (fixedw + freew)) / 2; - } - } - - /* position item */ - ui_item_position(item, x + offset, y - itemh, itemw, itemh); - - x += itemw; - if (item->next) { - x += litem->space; - } - } - - /* add extra pixel */ - uiItem *last_item = litem->items.last; - extra_pixel = litem->w - (x - litem->x); - if (extra_pixel > 0 && litem->alignment == UI_LAYOUT_ALIGN_EXPAND && last_free_item && - last_item && last_item->flag & UI_ITEM_AUTO_FIXED_SIZE) { - ui_item_move(last_free_item, 0, extra_pixel); - for (uiItem *item = last_free_item->next; item; item = item->next) { - ui_item_move(item, extra_pixel, extra_pixel); - } - } - - litem->w = x - litem->x; - litem->h = litem->y - y; - litem->x = x; - litem->y = y; -} - -/* single-column layout */ -static void ui_litem_estimate_column(uiLayout *litem, bool is_box) -{ - int itemw, itemh; - bool min_size_flag = true; - - litem->w = 0; - litem->h = 0; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - - min_size_flag = min_size_flag && (item->flag & UI_ITEM_FIXED_SIZE); - - litem->w = MAX2(litem->w, itemw); - litem->h += itemh; - - if (item->next && (!is_box || item != litem->items.first)) { - litem->h += litem->space; - } - } - - if (min_size_flag) { - litem->item.flag |= UI_ITEM_FIXED_SIZE; - } -} - -static void ui_litem_layout_column(uiLayout *litem, bool is_box, bool is_menu) -{ - const int x = litem->x; - int y = litem->y; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - int itemw, itemh; - ui_item_size(item, &itemw, &itemh); - - y -= itemh; - ui_item_position(item, x, y, is_menu ? itemw : litem->w, itemh); - - if (item->next && (!is_box || item != litem->items.first)) { - y -= litem->space; - } - - if (is_box) { - item->flag |= UI_ITEM_BOX_ITEM; - } - } - - litem->h = litem->y - y; - litem->x = x; - litem->y = y; -} - -/* calculates the angle of a specified button in a radial menu, - * stores a float vector in unit circle */ -static RadialDirection ui_get_radialbut_vec(float vec[2], short itemnum) -{ - if (itemnum >= PIE_MAX_ITEMS) { - itemnum %= PIE_MAX_ITEMS; - printf("Warning: Pie menus with more than %i items are currently unsupported\n", - PIE_MAX_ITEMS); - } - - const RadialDirection dir = ui_radial_dir_order[itemnum]; - ui_but_pie_dir(dir, vec); - - return dir; -} - -static bool ui_item_is_radial_displayable(uiItem *item) -{ - - if ((item->type == ITEM_BUTTON) && (((uiButtonItem *)item)->but->type == UI_BTYPE_LABEL)) { - return false; - } - - return true; -} - -static bool ui_item_is_radial_drawable(uiButtonItem *bitem) -{ - - if (ELEM(bitem->but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) { - return false; - } - - return true; -} - -static void ui_litem_layout_radial(uiLayout *litem) -{ - int itemh, itemw; - int itemnum = 0; - int totitems = 0; - - /* For the radial layout we will use Matt Ebb's design - * for radiation, see http://mattebb.com/weblog/radiation/ - * also the old code at http://developer.blender.org/T5103 - */ - - const int pie_radius = U.pie_menu_radius * UI_DPI_FAC; - - const int x = litem->x; - const int y = litem->y; - - int minx = x, miny = y, maxx = x, maxy = y; - - /* first count total items */ - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - totitems++; - } - - if (totitems < 5) { - litem->root->block->pie_data.flags |= UI_PIE_DEGREES_RANGE_LARGE; - } - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - /* not all button types are drawn in a radial menu, do filtering here */ - if (ui_item_is_radial_displayable(item)) { - RadialDirection dir; - float vec[2]; - float factor[2]; - - dir = ui_get_radialbut_vec(vec, itemnum); - factor[0] = (vec[0] > 0.01f) ? 0.0f : ((vec[0] < -0.01f) ? -1.0f : -0.5f); - factor[1] = (vec[1] > 0.99f) ? 0.0f : ((vec[1] < -0.99f) ? -1.0f : -0.5f); - - itemnum++; - - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - bitem->but->pie_dir = dir; - /* scale the buttons */ - bitem->but->rect.ymax *= 1.5f; - /* add a little bit more here to include number */ - bitem->but->rect.xmax += 1.5f * UI_UNIT_X; - /* enable drawing as pie item if supported by widget */ - if (ui_item_is_radial_drawable(bitem)) { - bitem->but->emboss = UI_EMBOSS_RADIAL; - bitem->but->drawflag |= UI_BUT_ICON_LEFT; - } - } - - ui_item_size(item, &itemw, &itemh); - - ui_item_position(item, - x + vec[0] * pie_radius + factor[0] * itemw, - y + vec[1] * pie_radius + factor[1] * itemh, - itemw, - itemh); - - minx = min_ii(minx, x + vec[0] * pie_radius - itemw / 2); - maxx = max_ii(maxx, x + vec[0] * pie_radius + itemw / 2); - miny = min_ii(miny, y + vec[1] * pie_radius - itemh / 2); - maxy = max_ii(maxy, y + vec[1] * pie_radius + itemh / 2); - } - } - - litem->x = minx; - litem->y = miny; - litem->w = maxx - minx; - litem->h = maxy - miny; -} - -/* root layout */ -static void ui_litem_estimate_root(uiLayout *UNUSED(litem)) -{ - /* nothing to do */ -} - -static void ui_litem_layout_root_radial(uiLayout *litem) -{ - /* first item is pie menu title, align on center of menu */ - uiItem *item = litem->items.first; - - if (item->type == ITEM_BUTTON) { - int itemh, itemw, x, y; - x = litem->x; - y = litem->y; - - ui_item_size(item, &itemw, &itemh); - - ui_item_position( - item, x - itemw / 2, y + U.dpi_fac * (U.pie_menu_threshold + 9.0f), itemw, itemh); - } -} - -static void ui_litem_layout_root(uiLayout *litem) -{ - if (litem->root->type == UI_LAYOUT_HEADER) { - ui_litem_layout_row(litem); - } - else if (litem->root->type == UI_LAYOUT_PIEMENU) { - ui_litem_layout_root_radial(litem); - } - else if (litem->root->type == UI_LAYOUT_MENU) { - ui_litem_layout_column(litem, false, true); - } - else { - ui_litem_layout_column(litem, false, false); - } -} - -/* box layout */ -static void ui_litem_estimate_box(uiLayout *litem) -{ - const uiStyle *style = litem->root->style; - - ui_litem_estimate_column(litem, true); - - int boxspace = style->boxspace; - if (litem->root->type == UI_LAYOUT_HEADER) { - boxspace = 0; - } - litem->w += 2 * boxspace; - litem->h += 2 * boxspace; -} - -static void ui_litem_layout_box(uiLayout *litem) -{ - uiLayoutItemBx *box = (uiLayoutItemBx *)litem; - const uiStyle *style = litem->root->style; - - int boxspace = style->boxspace; - if (litem->root->type == UI_LAYOUT_HEADER) { - boxspace = 0; - } - - const int w = litem->w; - const int h = litem->h; - - litem->x += boxspace; - litem->y -= boxspace; - - if (w != 0) { - litem->w -= 2 * boxspace; - } - if (h != 0) { - litem->h -= 2 * boxspace; - } - - ui_litem_layout_column(litem, true, false); - - litem->x -= boxspace; - litem->y -= boxspace; - - if (w != 0) { - litem->w += 2 * boxspace; - } - if (h != 0) { - litem->h += 2 * boxspace; - } - - /* roundbox around the sublayout */ - uiBut *but = box->roundbox; - but->rect.xmin = litem->x; - but->rect.ymin = litem->y; - but->rect.xmax = litem->x + litem->w; - but->rect.ymax = litem->y + litem->h; -} - -/* multi-column layout, automatically flowing to the next */ -static void ui_litem_estimate_column_flow(uiLayout *litem) -{ - const uiStyle *style = litem->root->style; - uiLayoutItemFlow *flow = (uiLayoutItemFlow *)litem; - int itemw, itemh, maxw = 0; - - /* compute max needed width and total height */ - int toth = 0; - int totitem = 0; - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - maxw = MAX2(maxw, itemw); - toth += itemh; - totitem++; - } - - if (flow->number <= 0) { - /* auto compute number of columns, not very good */ - if (maxw == 0) { - flow->totcol = 1; - return; - } - - flow->totcol = max_ii(litem->root->emw / maxw, 1); - flow->totcol = min_ii(flow->totcol, totitem); - } - else { - flow->totcol = flow->number; - } - - /* compute sizes */ - int x = 0; - int y = 0; - int emy = 0; - int miny = 0; - - maxw = 0; - const int emh = toth / flow->totcol; - - /* create column per column */ - int col = 0; - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - - y -= itemh + style->buttonspacey; - miny = min_ii(miny, y); - emy -= itemh; - maxw = max_ii(itemw, maxw); - - /* decide to go to next one */ - if (col < flow->totcol - 1 && emy <= -emh) { - x += maxw + litem->space; - maxw = 0; - y = 0; - emy = 0; /* need to reset height again for next column */ - col++; - } - } - - litem->w = x; - litem->h = litem->y - miny; -} - -static void ui_litem_layout_column_flow(uiLayout *litem) -{ - const uiStyle *style = litem->root->style; - uiLayoutItemFlow *flow = (uiLayoutItemFlow *)litem; - int col, emh, itemw, itemh; - - /* compute max needed width and total height */ - int toth = 0; - int totitem = 0; - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - toth += itemh; - totitem++; - } - - /* compute sizes */ - int x = litem->x; - int y = litem->y; - int emy = 0; - int miny = 0; - - int w = litem->w - (flow->totcol - 1) * style->columnspace; - emh = toth / flow->totcol; - - /* create column per column */ - col = 0; - w = (litem->w - (flow->totcol - 1) * style->columnspace) / flow->totcol; - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_size(item, &itemw, &itemh); - - itemw = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? w : min_ii(w, itemw); - - y -= itemh; - emy -= itemh; - ui_item_position(item, x, y, itemw, itemh); - y -= style->buttonspacey; - miny = min_ii(miny, y); - - /* decide to go to next one */ - if (col < flow->totcol - 1 && emy <= -emh) { - x += w + style->columnspace; - y = litem->y; - emy = 0; /* need to reset height again for next column */ - col++; - - const int remaining_width = litem->w - (x - litem->x); - const int remaining_width_between_columns = (flow->totcol - col - 1) * style->columnspace; - const int remaining_columns = flow->totcol - col; - w = (remaining_width - remaining_width_between_columns) / remaining_columns; - } - } - - litem->h = litem->y - miny; - litem->x = x; - litem->y = miny; -} - -/* multi-column and multi-row layout. */ -typedef struct UILayoutGridFlowInput { - /* General layout control settings. */ - const bool row_major : 1; /* Fill rows before columns */ - const bool even_columns : 1; /* All columns will have same width. */ - const bool even_rows : 1; /* All rows will have same height. */ - const int space_x; /* Space between columns. */ - const int space_y; /* Space between rows. */ - /* Real data about current position and size of this layout item - * (either estimated, or final values). */ - const int litem_w; /* Layout item width. */ - const int litem_x; /* Layout item X position. */ - const int litem_y; /* Layout item Y position. */ - /* Actual number of columns and rows to generate (computed from first pass usually). */ - const int tot_columns; /* Number of columns. */ - const int tot_rows; /* Number of rows. */ -} UILayoutGridFlowInput; - -typedef struct UILayoutGridFlowOutput { - int *tot_items; /* Total number of items in this grid layout. */ - /* Width / X pos data. */ - float *global_avg_w; /* Computed average width of the columns. */ - int *cos_x_array; /* Computed X coordinate of each column. */ - int *widths_array; /* Computed width of each column. */ - int *tot_w; /* Computed total width. */ - /* Height / Y pos data. */ - int *global_max_h; /* Computed height of the tallest item in the grid. */ - int *cos_y_array; /* Computed Y coordinate of each column. */ - int *heights_array; /* Computed height of each column. */ - int *tot_h; /* Computed total height. */ -} UILayoutGridFlowOutput; - -static void ui_litem_grid_flow_compute(ListBase *items, - UILayoutGridFlowInput *parameters, - UILayoutGridFlowOutput *results) -{ - float tot_w = 0.0f, tot_h = 0.0f; - float global_avg_w = 0.0f, global_totweight_w = 0.0f; - int global_max_h = 0; - - float *avg_w = NULL, *totweight_w = NULL; - int *max_h = NULL; - - BLI_assert( - parameters->tot_columns != 0 || - (results->cos_x_array == NULL && results->widths_array == NULL && results->tot_w == NULL)); - BLI_assert( - parameters->tot_rows != 0 || - (results->cos_y_array == NULL && results->heights_array == NULL && results->tot_h == NULL)); - - if (results->tot_items) { - *results->tot_items = 0; - } - - if (items->first == NULL) { - if (results->global_avg_w) { - *results->global_avg_w = 0.0f; - } - if (results->global_max_h) { - *results->global_max_h = 0; - } - return; - } - - if (parameters->tot_columns != 0) { - avg_w = BLI_array_alloca(avg_w, parameters->tot_columns); - totweight_w = BLI_array_alloca(totweight_w, parameters->tot_columns); - memset(avg_w, 0, sizeof(*avg_w) * parameters->tot_columns); - memset(totweight_w, 0, sizeof(*totweight_w) * parameters->tot_columns); - } - if (parameters->tot_rows != 0) { - max_h = BLI_array_alloca(max_h, parameters->tot_rows); - memset(max_h, 0, sizeof(*max_h) * parameters->tot_rows); - } - - int i = 0; - LISTBASE_FOREACH (uiItem *, item, items) { - int item_w, item_h; - ui_item_size(item, &item_w, &item_h); - - global_avg_w += (float)(item_w * item_w); - global_totweight_w += (float)item_w; - global_max_h = max_ii(global_max_h, item_h); - - if (parameters->tot_rows != 0 && parameters->tot_columns != 0) { - const int index_col = parameters->row_major ? i % parameters->tot_columns : - i / parameters->tot_rows; - const int index_row = parameters->row_major ? i / parameters->tot_columns : - i % parameters->tot_rows; - - avg_w[index_col] += (float)(item_w * item_w); - totweight_w[index_col] += (float)item_w; - - max_h[index_row] = max_ii(max_h[index_row], item_h); - } - - if (results->tot_items) { - (*results->tot_items)++; - } - i++; - } - - /* Finalize computing of column average sizes */ - global_avg_w /= global_totweight_w; - if (parameters->tot_columns != 0) { - for (i = 0; i < parameters->tot_columns; i++) { - avg_w[i] /= totweight_w[i]; - tot_w += avg_w[i]; - } - if (parameters->even_columns) { - tot_w = ceilf(global_avg_w) * parameters->tot_columns; - } - } - /* Finalize computing of rows max sizes */ - if (parameters->tot_rows != 0) { - for (i = 0; i < parameters->tot_rows; i++) { - tot_h += max_h[i]; - } - if (parameters->even_rows) { - tot_h = global_max_h * parameters->tot_columns; - } - } - - /* Compute positions and sizes of all cells. */ - if (results->cos_x_array != NULL && results->widths_array != NULL) { - /* We enlarge/narrow columns evenly to match available width. */ - const float wfac = (float)(parameters->litem_w - - (parameters->tot_columns - 1) * parameters->space_x) / - tot_w; - - for (int col = 0; col < parameters->tot_columns; col++) { - results->cos_x_array[col] = (col ? results->cos_x_array[col - 1] + - results->widths_array[col - 1] + parameters->space_x : - parameters->litem_x); - if (parameters->even_columns) { - /* (< remaining width > - < space between remaining columns >) / < remaining columns > */ - results->widths_array[col] = (((parameters->litem_w - - (results->cos_x_array[col] - parameters->litem_x)) - - (parameters->tot_columns - col - 1) * parameters->space_x) / - (parameters->tot_columns - col)); - } - else if (col == parameters->tot_columns - 1) { - /* Last column copes width rounding errors... */ - results->widths_array[col] = parameters->litem_w - - (results->cos_x_array[col] - parameters->litem_x); - } - else { - results->widths_array[col] = (int)(avg_w[col] * wfac); - } - } - } - if (results->cos_y_array != NULL && results->heights_array != NULL) { - for (int row = 0; row < parameters->tot_rows; row++) { - if (parameters->even_rows) { - results->heights_array[row] = global_max_h; - } - else { - results->heights_array[row] = max_h[row]; - } - results->cos_y_array[row] = (row ? results->cos_y_array[row - 1] - parameters->space_y - - results->heights_array[row] : - parameters->litem_y - results->heights_array[row]); - } - } - - if (results->global_avg_w) { - *results->global_avg_w = global_avg_w; - } - if (results->global_max_h) { - *results->global_max_h = global_max_h; - } - if (results->tot_w) { - *results->tot_w = (int)tot_w + parameters->space_x * (parameters->tot_columns - 1); - } - if (results->tot_h) { - *results->tot_h = tot_h + parameters->space_y * (parameters->tot_rows - 1); - } -} - -static void ui_litem_estimate_grid_flow(uiLayout *litem) -{ - const uiStyle *style = litem->root->style; - uiLayoutItemGridFlow *gflow = (uiLayoutItemGridFlow *)litem; - - const int space_x = style->columnspace; - const int space_y = style->buttonspacey; - - /* Estimate average needed width and height per item. */ - { - float avg_w; - int max_h; - - ui_litem_grid_flow_compute(&litem->items, - &((UILayoutGridFlowInput){ - .row_major = gflow->row_major, - .even_columns = gflow->even_columns, - .even_rows = gflow->even_rows, - .litem_w = litem->w, - .litem_x = litem->x, - .litem_y = litem->y, - .space_x = space_x, - .space_y = space_y, - }), - &((UILayoutGridFlowOutput){ - .tot_items = &gflow->tot_items, - .global_avg_w = &avg_w, - .global_max_h = &max_h, - })); - - if (gflow->tot_items == 0) { - litem->w = litem->h = 0; - gflow->tot_columns = gflow->tot_rows = 0; - return; - } - - /* Even in varying column width case, - * we fix our columns number from weighted average width of items, - * a proper solving of required width would be too costly, - * and this should give reasonably good results in all reasonable cases. */ - if (gflow->columns_len > 0) { - gflow->tot_columns = gflow->columns_len; - } - else { - if (avg_w == 0.0f) { - gflow->tot_columns = 1; - } - else { - gflow->tot_columns = min_ii(max_ii((int)(litem->w / avg_w), 1), gflow->tot_items); - } - } - gflow->tot_rows = (int)ceilf((float)gflow->tot_items / gflow->tot_columns); - - /* Try to tweak number of columns and rows to get better filling of last column or row, - * and apply 'modulo' value to number of columns or rows. - * Note that modulo does not prevent ending with fewer columns/rows than modulo, if mandatory - * to avoid empty column/row. */ - { - const int modulo = (gflow->columns_len < -1) ? -gflow->columns_len : 0; - const int step = modulo ? modulo : 1; - - if (gflow->row_major) { - /* Adjust number of columns to be multiple of given modulo. */ - if (modulo && gflow->tot_columns % modulo != 0 && gflow->tot_columns > modulo) { - gflow->tot_columns = gflow->tot_columns - (gflow->tot_columns % modulo); - } - /* Find smallest number of columns conserving computed optimal number of rows. */ - for (gflow->tot_rows = (int)ceilf((float)gflow->tot_items / gflow->tot_columns); - (gflow->tot_columns - step) > 0 && - (int)ceilf((float)gflow->tot_items / (gflow->tot_columns - step)) <= gflow->tot_rows; - gflow->tot_columns -= step) { - /* pass */ - } - } - else { - /* Adjust number of rows to be multiple of given modulo. */ - if (modulo && gflow->tot_rows % modulo != 0) { - gflow->tot_rows = min_ii(gflow->tot_rows + modulo - (gflow->tot_rows % modulo), - gflow->tot_items); - } - /* Find smallest number of rows conserving computed optimal number of columns. */ - for (gflow->tot_columns = (int)ceilf((float)gflow->tot_items / gflow->tot_rows); - (gflow->tot_rows - step) > 0 && - (int)ceilf((float)gflow->tot_items / (gflow->tot_rows - step)) <= gflow->tot_columns; - gflow->tot_rows -= step) { - /* pass */ - } - } - } - - /* Set evenly-spaced axes size - * (quick optimization in case we have even columns and rows). */ - if (gflow->even_columns && gflow->even_rows) { - litem->w = (int)(gflow->tot_columns * avg_w) + space_x * (gflow->tot_columns - 1); - litem->h = (int)(gflow->tot_rows * max_h) + space_y * (gflow->tot_rows - 1); - return; - } - } - - /* Now that we have our final number of columns and rows, - * we can compute actual needed space for non-evenly sized axes. */ - { - int tot_w, tot_h; - - ui_litem_grid_flow_compute(&litem->items, - &((UILayoutGridFlowInput){ - .row_major = gflow->row_major, - .even_columns = gflow->even_columns, - .even_rows = gflow->even_rows, - .litem_w = litem->w, - .litem_x = litem->x, - .litem_y = litem->y, - .space_x = space_x, - .space_y = space_y, - .tot_columns = gflow->tot_columns, - .tot_rows = gflow->tot_rows, - }), - &((UILayoutGridFlowOutput){ - .tot_w = &tot_w, - .tot_h = &tot_h, - })); - - litem->w = tot_w; - litem->h = tot_h; - } -} - -static void ui_litem_layout_grid_flow(uiLayout *litem) -{ - const uiStyle *style = litem->root->style; - uiLayoutItemGridFlow *gflow = (uiLayoutItemGridFlow *)litem; - - if (gflow->tot_items == 0) { - litem->w = litem->h = 0; - return; - } - - BLI_assert(gflow->tot_columns > 0); - BLI_assert(gflow->tot_rows > 0); - - const int space_x = style->columnspace; - const int space_y = style->buttonspacey; - - int *widths = BLI_array_alloca(widths, gflow->tot_columns); - int *heights = BLI_array_alloca(heights, gflow->tot_rows); - int *cos_x = BLI_array_alloca(cos_x, gflow->tot_columns); - int *cos_y = BLI_array_alloca(cos_y, gflow->tot_rows); - - /* This time we directly compute coordinates and sizes of all cells. */ - ui_litem_grid_flow_compute(&litem->items, - &((UILayoutGridFlowInput){ - .row_major = gflow->row_major, - .even_columns = gflow->even_columns, - .even_rows = gflow->even_rows, - .litem_w = litem->w, - .litem_x = litem->x, - .litem_y = litem->y, - .space_x = space_x, - .space_y = space_y, - .tot_columns = gflow->tot_columns, - .tot_rows = gflow->tot_rows, - }), - &((UILayoutGridFlowOutput){ - .cos_x_array = cos_x, - .cos_y_array = cos_y, - .widths_array = widths, - .heights_array = heights, - })); - - int i; - LISTBASE_FOREACH_INDEX (uiItem *, item, &litem->items, i) { - const int col = gflow->row_major ? i % gflow->tot_columns : i / gflow->tot_rows; - const int row = gflow->row_major ? i / gflow->tot_columns : i % gflow->tot_rows; - int item_w, item_h; - ui_item_size(item, &item_w, &item_h); - - const int w = widths[col]; - const int h = heights[row]; - - item_w = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? w : min_ii(w, item_w); - item_h = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? h : min_ii(h, item_h); - - ui_item_position(item, cos_x[col], cos_y[row], item_w, item_h); - } - - litem->h = litem->y - cos_y[gflow->tot_rows - 1]; - litem->x = (cos_x[gflow->tot_columns - 1] - litem->x) + widths[gflow->tot_columns - 1]; - litem->y = litem->y - litem->h; -} - -/* free layout */ -static void ui_litem_estimate_absolute(uiLayout *litem) -{ - int minx = 1e6; - int miny = 1e6; - litem->w = 0; - litem->h = 0; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - int itemx, itemy, itemw, itemh; - ui_item_offset(item, &itemx, &itemy); - ui_item_size(item, &itemw, &itemh); - - minx = min_ii(minx, itemx); - miny = min_ii(miny, itemy); - - litem->w = MAX2(litem->w, itemx + itemw); - litem->h = MAX2(litem->h, itemy + itemh); - } - - litem->w -= minx; - litem->h -= miny; -} - -static void ui_litem_layout_absolute(uiLayout *litem) -{ - float scalex = 1.0f, scaley = 1.0f; - int x, y, newx, newy, itemx, itemy, itemh, itemw; - - int minx = 1e6; - int miny = 1e6; - int totw = 0; - int toth = 0; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_offset(item, &itemx, &itemy); - ui_item_size(item, &itemw, &itemh); - - minx = min_ii(minx, itemx); - miny = min_ii(miny, itemy); - - totw = max_ii(totw, itemx + itemw); - toth = max_ii(toth, itemy + itemh); - } - - totw -= minx; - toth -= miny; - - if (litem->w && totw > 0) { - scalex = (float)litem->w / (float)totw; - } - if (litem->h && toth > 0) { - scaley = (float)litem->h / (float)toth; - } - - x = litem->x; - y = litem->y - scaley * toth; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - ui_item_offset(item, &itemx, &itemy); - ui_item_size(item, &itemw, &itemh); - - if (scalex != 1.0f) { - newx = (itemx - minx) * scalex; - itemw = (itemx - minx + itemw) * scalex - newx; - itemx = minx + newx; - } - - if (scaley != 1.0f) { - newy = (itemy - miny) * scaley; - itemh = (itemy - miny + itemh) * scaley - newy; - itemy = miny + newy; - } - - ui_item_position(item, x + itemx - minx, y + itemy - miny, itemw, itemh); - } - - litem->w = scalex * totw; - litem->h = litem->y - y; - litem->x = x + litem->w; - litem->y = y; -} - -/* split layout */ -static void ui_litem_estimate_split(uiLayout *litem) -{ - ui_litem_estimate_row(litem); - litem->item.flag &= ~UI_ITEM_FIXED_SIZE; -} - -static void ui_litem_layout_split(uiLayout *litem) -{ - uiLayoutItemSplit *split = (uiLayoutItemSplit *)litem; - float extra_pixel = 0.0f; - const int tot = BLI_listbase_count(&litem->items); - - if (tot == 0) { - return; - } - - int x = litem->x; - const int y = litem->y; - - const float percentage = (split->percentage == 0.0f) ? 1.0f / (float)tot : split->percentage; - - const int w = (litem->w - (tot - 1) * litem->space); - int colw = w * percentage; - colw = MAX2(colw, 0); - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - int itemh; - ui_item_size(item, NULL, &itemh); - - ui_item_position(item, x, y - itemh, colw, itemh); - x += colw; - - if (item->next) { - const float width = extra_pixel + (w - (int)(w * percentage)) / ((float)tot - 1); - extra_pixel = width - (int)width; - colw = (int)width; - colw = MAX2(colw, 0); - - x += litem->space; - } - } - - litem->w = x - litem->x; - litem->h = litem->y - y; - litem->x = x; - litem->y = y; -} - -/* overlap layout */ -static void ui_litem_estimate_overlap(uiLayout *litem) -{ - litem->w = 0; - litem->h = 0; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - int itemw, itemh; - ui_item_size(item, &itemw, &itemh); - - litem->w = MAX2(itemw, litem->w); - litem->h = MAX2(itemh, litem->h); - } -} - -static void ui_litem_layout_overlap(uiLayout *litem) -{ - - const int x = litem->x; - const int y = litem->y; - - LISTBASE_FOREACH (uiItem *, item, &litem->items) { - int itemw, itemh; - ui_item_size(item, &itemw, &itemh); - ui_item_position(item, x, y - itemh, litem->w, itemh); - - litem->h = MAX2(litem->h, itemh); - } - - litem->x = x; - litem->y = y - litem->h; -} - -static void ui_litem_init_from_parent(uiLayout *litem, uiLayout *layout, int align) -{ - litem->root = layout->root; - litem->align = align; - /* Children of gridflow layout shall never have "ideal big size" returned as estimated size. */ - litem->variable_size = layout->variable_size || layout->item.type == ITEM_LAYOUT_GRID_FLOW; - litem->active = true; - litem->enabled = true; - litem->context = layout->context; - litem->redalert = layout->redalert; - litem->w = layout->w; - litem->emboss = layout->emboss; - litem->item.flag = (layout->item.flag & - (UI_ITEM_PROP_SEP | UI_ITEM_PROP_DECORATE | UI_ITEM_INSIDE_PROP_SEP)); - - if (layout->child_items_layout) { - BLI_addtail(&layout->child_items_layout->items, litem); - litem->parent = layout->child_items_layout; - } - else { - BLI_addtail(&layout->items, litem); - litem->parent = layout; - } -} - -static void ui_layout_heading_set(uiLayout *layout, const char *heading) -{ - BLI_assert(layout->heading[0] == '\0'); - if (heading) { - STRNCPY(layout->heading, heading); - } -} - -/* layout create functions */ -uiLayout *uiLayoutRow(uiLayout *layout, bool align) -{ - uiLayout *litem = MEM_callocN(sizeof(uiLayout), "uiLayoutRow"); - ui_litem_init_from_parent(litem, layout, align); - - litem->item.type = ITEM_LAYOUT_ROW; - litem->space = (align) ? 0 : layout->root->style->buttonspacex; - - UI_block_layout_set_current(layout->root->block, litem); - - return litem; -} - -/** - * See #uiLayoutColumnWithHeading(). - */ -uiLayout *uiLayoutRowWithHeading(uiLayout *layout, bool align, const char *heading) -{ - uiLayout *litem = uiLayoutRow(layout, align); - ui_layout_heading_set(litem, heading); - return litem; -} - -uiLayout *uiLayoutColumn(uiLayout *layout, bool align) -{ - uiLayout *litem = MEM_callocN(sizeof(uiLayout), "uiLayoutColumn"); - ui_litem_init_from_parent(litem, layout, align); - - litem->item.type = ITEM_LAYOUT_COLUMN; - litem->space = (align) ? 0 : layout->root->style->buttonspacey; - - UI_block_layout_set_current(layout->root->block, litem); - - return litem; -} - -/** - * Variant of #uiLayoutColumn() that sets a heading label for the layout if the first item is - * added through #uiItemFullR(). If split layout is used and the item has no string to add to the - * first split-column, the heading is added there instead. Otherwise the heading inserted with a - * new row. - */ -uiLayout *uiLayoutColumnWithHeading(uiLayout *layout, bool align, const char *heading) -{ - uiLayout *litem = uiLayoutColumn(layout, align); - ui_layout_heading_set(litem, heading); - return litem; -} - -uiLayout *uiLayoutColumnFlow(uiLayout *layout, int number, bool align) -{ - uiLayoutItemFlow *flow = MEM_callocN(sizeof(uiLayoutItemFlow), "uiLayoutItemFlow"); - ui_litem_init_from_parent(&flow->litem, layout, align); - - flow->litem.item.type = ITEM_LAYOUT_COLUMN_FLOW; - flow->litem.space = (flow->litem.align) ? 0 : layout->root->style->columnspace; - flow->number = number; - - UI_block_layout_set_current(layout->root->block, &flow->litem); - - return &flow->litem; -} - -uiLayout *uiLayoutGridFlow(uiLayout *layout, - bool row_major, - int columns_len, - bool even_columns, - bool even_rows, - bool align) -{ - uiLayoutItemGridFlow *flow = MEM_callocN(sizeof(uiLayoutItemGridFlow), __func__); - flow->litem.item.type = ITEM_LAYOUT_GRID_FLOW; - ui_litem_init_from_parent(&flow->litem, layout, align); - - flow->litem.space = (flow->litem.align) ? 0 : layout->root->style->columnspace; - flow->row_major = row_major; - flow->columns_len = columns_len; - flow->even_columns = even_columns; - flow->even_rows = even_rows; - - UI_block_layout_set_current(layout->root->block, &flow->litem); - - return &flow->litem; -} - -static uiLayoutItemBx *ui_layout_box(uiLayout *layout, int type) -{ - uiLayoutItemBx *box = MEM_callocN(sizeof(uiLayoutItemBx), "uiLayoutItemBx"); - ui_litem_init_from_parent(&box->litem, layout, false); - - box->litem.item.type = ITEM_LAYOUT_BOX; - box->litem.space = layout->root->style->columnspace; - - UI_block_layout_set_current(layout->root->block, &box->litem); - - box->roundbox = uiDefBut(layout->root->block, type, 0, "", 0, 0, 0, 0, NULL, 0.0, 0.0, 0, 0, ""); - - return box; -} - -uiLayout *uiLayoutRadial(uiLayout *layout) -{ - /* radial layouts are only valid for radial menus */ - if (layout->root->type != UI_LAYOUT_PIEMENU) { - return ui_item_local_sublayout(layout, layout, 0); - } - - /* only one radial wheel per root layout is allowed, so check and return that, if it exists */ - LISTBASE_FOREACH (uiItem *, item, &layout->root->layout->items) { - uiLayout *litem = (uiLayout *)item; - if (litem->item.type == ITEM_LAYOUT_RADIAL) { - UI_block_layout_set_current(layout->root->block, litem); - return litem; - } - } - - uiLayout *litem = MEM_callocN(sizeof(uiLayout), "uiLayoutRadial"); - ui_litem_init_from_parent(litem, layout, false); - - litem->item.type = ITEM_LAYOUT_RADIAL; - - UI_block_layout_set_current(layout->root->block, litem); - - return litem; -} - -uiLayout *uiLayoutBox(uiLayout *layout) -{ - return (uiLayout *)ui_layout_box(layout, UI_BTYPE_ROUNDBOX); -} - -/** - * Check all buttons defined in this layout, - * and set any button flagged as UI_BUT_LIST_ITEM as active/selected. - * Needed to handle correctly text colors of active (selected) list item. - */ -void ui_layout_list_set_labels_active(uiLayout *layout) -{ - LISTBASE_FOREACH (uiButtonItem *, bitem, &layout->items) { - if (bitem->item.type != ITEM_BUTTON) { - ui_layout_list_set_labels_active((uiLayout *)(&bitem->item)); - } - else if (bitem->but->flag & UI_BUT_LIST_ITEM) { - UI_but_flag_enable(bitem->but, UI_SELECT); - } - } -} - -uiLayout *uiLayoutListBox(uiLayout *layout, - uiList *ui_list, - PointerRNA *actptr, - PropertyRNA *actprop) -{ - uiLayoutItemBx *box = ui_layout_box(layout, UI_BTYPE_LISTBOX); - uiBut *but = box->roundbox; - - but->custom_data = ui_list; - - but->rnapoin = *actptr; - but->rnaprop = actprop; - - /* only for the undo string */ - if (but->flag & UI_BUT_UNDO) { - but->tip = RNA_property_description(actprop); - } - - return (uiLayout *)box; -} - -uiLayout *uiLayoutAbsolute(uiLayout *layout, bool align) -{ - uiLayout *litem = MEM_callocN(sizeof(uiLayout), "uiLayoutAbsolute"); - ui_litem_init_from_parent(litem, layout, align); - - litem->item.type = ITEM_LAYOUT_ABSOLUTE; - - UI_block_layout_set_current(layout->root->block, litem); - - return litem; -} - -uiBlock *uiLayoutAbsoluteBlock(uiLayout *layout) -{ - uiBlock *block = uiLayoutGetBlock(layout); - uiLayoutAbsolute(layout, false); - - return block; -} - -uiLayout *uiLayoutOverlap(uiLayout *layout) -{ - uiLayout *litem = MEM_callocN(sizeof(uiLayout), "uiLayoutOverlap"); - ui_litem_init_from_parent(litem, layout, false); - - litem->item.type = ITEM_LAYOUT_OVERLAP; - - UI_block_layout_set_current(layout->root->block, litem); - - return litem; -} - -uiLayout *uiLayoutSplit(uiLayout *layout, float percentage, bool align) -{ - uiLayoutItemSplit *split = MEM_callocN(sizeof(uiLayoutItemSplit), "uiLayoutItemSplit"); - ui_litem_init_from_parent(&split->litem, layout, align); - - split->litem.item.type = ITEM_LAYOUT_SPLIT; - split->litem.space = layout->root->style->columnspace; - split->percentage = percentage; - - UI_block_layout_set_current(layout->root->block, &split->litem); - - return &split->litem; -} - -void uiLayoutSetActive(uiLayout *layout, bool active) -{ - layout->active = active; -} - -void uiLayoutSetActiveDefault(uiLayout *layout, bool active_default) -{ - layout->active_default = active_default; -} - -void uiLayoutSetActivateInit(uiLayout *layout, bool activate_init) -{ - layout->activate_init = activate_init; -} - -void uiLayoutSetEnabled(uiLayout *layout, bool enabled) -{ - layout->enabled = enabled; -} - -void uiLayoutSetRedAlert(uiLayout *layout, bool redalert) -{ - layout->redalert = redalert; -} - -void uiLayoutSetKeepAspect(uiLayout *layout, bool keepaspect) -{ - layout->keepaspect = keepaspect; -} - -void uiLayoutSetAlignment(uiLayout *layout, char alignment) -{ - layout->alignment = alignment; -} - -void uiLayoutSetScaleX(uiLayout *layout, float scale) -{ - layout->scale[0] = scale; -} - -void uiLayoutSetScaleY(uiLayout *layout, float scale) -{ - layout->scale[1] = scale; -} - -void uiLayoutSetUnitsX(uiLayout *layout, float unit) -{ - layout->units[0] = unit; -} - -void uiLayoutSetUnitsY(uiLayout *layout, float unit) -{ - layout->units[1] = unit; -} - -void uiLayoutSetEmboss(uiLayout *layout, eUIEmbossType emboss) -{ - layout->emboss = emboss; -} - -bool uiLayoutGetPropSep(uiLayout *layout) -{ - return (layout->item.flag & UI_ITEM_PROP_SEP) != 0; -} - -void uiLayoutSetPropSep(uiLayout *layout, bool is_sep) -{ - SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_SEP); -} - -bool uiLayoutGetPropDecorate(uiLayout *layout) -{ - return (layout->item.flag & UI_ITEM_PROP_DECORATE) != 0; -} - -void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep) -{ - SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_DECORATE); -} - -bool uiLayoutGetActive(uiLayout *layout) -{ - return layout->active; -} - -bool uiLayoutGetActiveDefault(uiLayout *layout) -{ - return layout->active_default; -} - -bool uiLayoutGetActivateInit(uiLayout *layout) -{ - return layout->activate_init; -} - -bool uiLayoutGetEnabled(uiLayout *layout) -{ - return layout->enabled; -} - -bool uiLayoutGetRedAlert(uiLayout *layout) -{ - return layout->redalert; -} - -bool uiLayoutGetKeepAspect(uiLayout *layout) -{ - return layout->keepaspect; -} - -int uiLayoutGetAlignment(uiLayout *layout) -{ - return layout->alignment; -} - -int uiLayoutGetWidth(uiLayout *layout) -{ - return layout->w; -} - -float uiLayoutGetScaleX(uiLayout *layout) -{ - return layout->scale[0]; -} - -float uiLayoutGetScaleY(uiLayout *layout) -{ - return layout->scale[1]; -} - -float uiLayoutGetUnitsX(uiLayout *layout) -{ - return layout->units[0]; -} - -float uiLayoutGetUnitsY(uiLayout *layout) -{ - return layout->units[1]; -} - -eUIEmbossType uiLayoutGetEmboss(uiLayout *layout) -{ - if (layout->emboss == UI_EMBOSS_UNDEFINED) { - return layout->root->block->emboss; - } - return layout->emboss; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Block Layout Search Filtering - * \{ */ - -/* Disabled for performance reasons, but this could be turned on in the future. */ -// #define PROPERTY_SEARCH_USE_TOOLTIPS - -static bool block_search_panel_label_matches(const uiBlock *block, const char *search_string) -{ - if ((block->panel != NULL) && (block->panel->type != NULL)) { - if (BLI_strcasestr(block->panel->type->label, search_string)) { - return true; - } - } - return false; -} - -/** - * Returns true if a button or the data / operator it represents matches the search filter. - */ -static bool button_matches_search_filter(uiBut *but, const char *search_filter) -{ - /* Do the shorter checks first for better performance in case there is a match. */ - if (BLI_strcasestr(but->str, search_filter)) { - return true; - } - - if (but->optype != NULL) { - if (BLI_strcasestr(but->optype->name, search_filter)) { - return true; - } - } - - if (but->rnaprop != NULL) { - if (BLI_strcasestr(RNA_property_ui_name(but->rnaprop), search_filter)) { - return true; - } -#ifdef PROPERTY_SEARCH_USE_TOOLTIPS - if (BLI_strcasestr(RNA_property_description(but->rnaprop), search_filter)) { - return true; - } -#endif - - /* Search through labels of enum property items if they are in a drop-down menu. - * Unfortunately we have no #bContext here so we cannot search through RNA enums - * with dynamic entries (or "itemf" functions) which require context. */ - if (but->type == UI_BTYPE_MENU) { - PointerRNA *ptr = &but->rnapoin; - PropertyRNA *enum_prop = but->rnaprop; - - int items_len; - const EnumPropertyItem *items_array = NULL; - bool free; - RNA_property_enum_items_gettexted(NULL, ptr, enum_prop, &items_array, &items_len, &free); - - if (items_array == NULL) { - return false; - } - - for (int i = 0; i < items_len; i++) { - /* Check for NULL name field which enums use for separators. */ - if (items_array[i].name == NULL) { - continue; - } - if (BLI_strcasestr(items_array[i].name, search_filter)) { - return true; - } - } - if (free) { - MEM_freeN((EnumPropertyItem *)items_array); - } - } - } - - return false; -} - -/** - * Test for a search result within a specific button group. - */ -static bool button_group_has_search_match(uiButtonGroup *button_group, const char *search_filter) -{ - LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { - uiBut *but = link->data; - if (button_matches_search_filter(but, search_filter)) { - return true; - } - } - - return false; -} - -/** - * Apply the search filter, tagging all buttons with whether they match or not. - * Tag every button in the group as a result if any button in the group matches. - * - * \note It would be great to return early here if we found a match, but because - * the results may be visible we have to continue searching the entire block. - * - * \return True if the block has any search results. - */ -static bool block_search_filter_tag_buttons(uiBlock *block, const char *search_filter) -{ - bool has_result = false; - LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { - if (button_group_has_search_match(button_group, search_filter)) { - has_result = true; - } - else { - LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { - uiBut *but = link->data; - but->flag |= UI_SEARCH_FILTER_NO_MATCH; - } - } - } - return has_result; -} - -/** - * Apply property search behavior, setting panel flags and deactivating buttons that don't match. - * - * \note Must not be run after #UI_block_layout_resolve. - */ -bool UI_block_apply_search_filter(uiBlock *block, const char *search_filter) -{ - if (search_filter == NULL || search_filter[0] == '\0') { - return false; - } - - Panel *panel = block->panel; - - if (panel != NULL && panel->type->flag & PANEL_TYPE_NO_SEARCH) { - /* Panels for active blocks should always have a type, otherwise they wouldn't be created. */ - BLI_assert(block->panel->type != NULL); - if (panel->type->flag & PANEL_TYPE_NO_SEARCH) { - return false; - } - } - - const bool panel_label_matches = block_search_panel_label_matches(block, search_filter); - - const bool has_result = (panel_label_matches) ? - true : - block_search_filter_tag_buttons(block, search_filter); - - if (panel != NULL) { - if (has_result) { - ui_panel_tag_search_filter_match(block->panel); - } - } - - return has_result; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Layout - * \{ */ - -static void ui_item_scale(uiLayout *litem, const float scale[2]) -{ - int x, y, w, h; - - LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) { - if (item->type != ITEM_BUTTON) { - uiLayout *subitem = (uiLayout *)item; - ui_item_scale(subitem, scale); - } - - ui_item_size(item, &w, &h); - ui_item_offset(item, &x, &y); - - if (scale[0] != 0.0f) { - x *= scale[0]; - w *= scale[0]; - } - - if (scale[1] != 0.0f) { - y *= scale[1]; - h *= scale[1]; - } - - ui_item_position(item, x, y, w, h); - } -} - -static void ui_item_estimate(uiItem *item) -{ - if (item->type != ITEM_BUTTON) { - uiLayout *litem = (uiLayout *)item; - - LISTBASE_FOREACH (uiItem *, subitem, &litem->items) { - ui_item_estimate(subitem); - } - - if (BLI_listbase_is_empty(&litem->items)) { - litem->w = 0; - litem->h = 0; - return; - } - - if (litem->scale[0] != 0.0f || litem->scale[1] != 0.0f) { - ui_item_scale(litem, litem->scale); - } - - switch (litem->item.type) { - case ITEM_LAYOUT_COLUMN: - ui_litem_estimate_column(litem, false); - break; - case ITEM_LAYOUT_COLUMN_FLOW: - ui_litem_estimate_column_flow(litem); - break; - case ITEM_LAYOUT_GRID_FLOW: - ui_litem_estimate_grid_flow(litem); - break; - case ITEM_LAYOUT_ROW: - ui_litem_estimate_row(litem); - break; - case ITEM_LAYOUT_BOX: - ui_litem_estimate_box(litem); - break; - case ITEM_LAYOUT_ROOT: - ui_litem_estimate_root(litem); - break; - case ITEM_LAYOUT_ABSOLUTE: - ui_litem_estimate_absolute(litem); - break; - case ITEM_LAYOUT_SPLIT: - ui_litem_estimate_split(litem); - break; - case ITEM_LAYOUT_OVERLAP: - ui_litem_estimate_overlap(litem); - break; - default: - break; - } - - /* Force fixed size. */ - if (litem->units[0] > 0) { - litem->w = UI_UNIT_X * litem->units[0]; - } - if (litem->units[1] > 0) { - litem->h = UI_UNIT_Y * litem->units[1]; - } - } -} - -static void ui_item_align(uiLayout *litem, short nr) -{ - LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) { - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; -#ifndef USE_UIBUT_SPATIAL_ALIGN - if (ui_but_can_align(bitem->but)) -#endif - { - if (!bitem->but->alignnr) { - bitem->but->alignnr = nr; - } - } - } - else if (item->type == ITEM_LAYOUT_ABSOLUTE) { - /* pass */ - } - else if (item->type == ITEM_LAYOUT_OVERLAP) { - /* pass */ - } - else if (item->type == ITEM_LAYOUT_BOX) { - uiLayoutItemBx *box = (uiLayoutItemBx *)item; - if (!box->roundbox->alignnr) { - box->roundbox->alignnr = nr; - } - } - else if (((uiLayout *)item)->align) { - ui_item_align((uiLayout *)item, nr); - } - } -} - -static void ui_item_flag(uiLayout *litem, int flag) -{ - LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) { - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - bitem->but->flag |= flag; - } - else { - ui_item_flag((uiLayout *)item, flag); - } - } -} - -static void ui_item_layout(uiItem *item) -{ - if (item->type != ITEM_BUTTON) { - uiLayout *litem = (uiLayout *)item; - - if (BLI_listbase_is_empty(&litem->items)) { - return; - } - - if (litem->align) { - ui_item_align(litem, ++litem->root->block->alignnr); - } - if (!litem->active) { - ui_item_flag(litem, UI_BUT_INACTIVE); - } - if (!litem->enabled) { - ui_item_flag(litem, UI_BUT_DISABLED); - } - - switch (litem->item.type) { - case ITEM_LAYOUT_COLUMN: - ui_litem_layout_column(litem, false, false); - break; - case ITEM_LAYOUT_COLUMN_FLOW: - ui_litem_layout_column_flow(litem); - break; - case ITEM_LAYOUT_GRID_FLOW: - ui_litem_layout_grid_flow(litem); - break; - case ITEM_LAYOUT_ROW: - ui_litem_layout_row(litem); - break; - case ITEM_LAYOUT_BOX: - ui_litem_layout_box(litem); - break; - case ITEM_LAYOUT_ROOT: - ui_litem_layout_root(litem); - break; - case ITEM_LAYOUT_ABSOLUTE: - ui_litem_layout_absolute(litem); - break; - case ITEM_LAYOUT_SPLIT: - ui_litem_layout_split(litem); - break; - case ITEM_LAYOUT_OVERLAP: - ui_litem_layout_overlap(litem); - break; - case ITEM_LAYOUT_RADIAL: - ui_litem_layout_radial(litem); - break; - default: - break; - } - - LISTBASE_FOREACH (uiItem *, subitem, &litem->items) { - if (item->flag & UI_ITEM_BOX_ITEM) { - subitem->flag |= UI_ITEM_BOX_ITEM; - } - ui_item_layout(subitem); - } - } - else { - if (item->flag & UI_ITEM_BOX_ITEM) { - uiButtonItem *bitem = (uiButtonItem *)item; - bitem->but->drawflag |= UI_BUT_BOX_ITEM; - } - } -} - -static void ui_layout_end(uiBlock *block, uiLayout *layout, int *r_x, int *r_y) -{ - if (layout->root->handlefunc) { - UI_block_func_handle_set(block, layout->root->handlefunc, layout->root->argv); - } - - ui_item_estimate(&layout->item); - ui_item_layout(&layout->item); - - if (r_x) { - *r_x = layout->x; - } - if (r_y) { - *r_y = layout->y; - } -} - -static void ui_layout_free(uiLayout *layout) -{ - LISTBASE_FOREACH_MUTABLE (uiItem *, item, &layout->items) { - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - bitem->but->layout = NULL; - MEM_freeN(item); - } - else { - ui_layout_free((uiLayout *)item); - } - } - - MEM_freeN(layout); -} - -static void ui_layout_add_padding_button(uiLayoutRoot *root) -{ - if (root->padding) { - /* add an invisible button for padding */ - uiBlock *block = root->block; - uiLayout *prev_layout = block->curlayout; - - block->curlayout = root->layout; - uiDefBut( - block, UI_BTYPE_SEPR, 0, "", 0, 0, root->padding, root->padding, NULL, 0.0, 0.0, 0, 0, ""); - block->curlayout = prev_layout; - } -} - -uiLayout *UI_block_layout(uiBlock *block, - int dir, - int type, - int x, - int y, - int size, - int em, - int padding, - const uiStyle *style) -{ - uiLayoutRoot *root = MEM_callocN(sizeof(uiLayoutRoot), "uiLayoutRoot"); - root->type = type; - root->style = style; - root->block = block; - root->padding = padding; - root->opcontext = WM_OP_INVOKE_REGION_WIN; - - uiLayout *layout = MEM_callocN(sizeof(uiLayout), "uiLayout"); - layout->item.type = (type == UI_LAYOUT_VERT_BAR) ? ITEM_LAYOUT_COLUMN : ITEM_LAYOUT_ROOT; - - /* Only used when 'UI_ITEM_PROP_SEP' is set. */ - layout->item.flag = UI_ITEM_PROP_DECORATE; - - layout->x = x; - layout->y = y; - layout->root = root; - layout->space = style->templatespace; - layout->active = true; - layout->enabled = true; - layout->context = NULL; - layout->emboss = UI_EMBOSS_UNDEFINED; - - if (ELEM(type, UI_LAYOUT_MENU, UI_LAYOUT_PIEMENU)) { - layout->space = 0; - } - - if (dir == UI_LAYOUT_HORIZONTAL) { - layout->h = size; - layout->root->emh = em * UI_UNIT_Y; - } - else { - layout->w = size; - layout->root->emw = em * UI_UNIT_X; - } - - block->curlayout = layout; - root->layout = layout; - BLI_addtail(&block->layouts, root); - - ui_layout_add_padding_button(root); - - return layout; -} - -uiBlock *uiLayoutGetBlock(uiLayout *layout) -{ - return layout->root->block; -} - -int uiLayoutGetOperatorContext(uiLayout *layout) -{ - return layout->root->opcontext; -} - -void UI_block_layout_set_current(uiBlock *block, uiLayout *layout) -{ - block->curlayout = layout; -} - -void ui_layout_add_but(uiLayout *layout, uiBut *but) -{ - uiButtonItem *bitem = MEM_callocN(sizeof(uiButtonItem), "uiButtonItem"); - bitem->item.type = ITEM_BUTTON; - bitem->but = but; - - int w, h; - ui_item_size((uiItem *)bitem, &w, &h); - /* XXX uiBut hasn't scaled yet - * we can flag the button as not expandable, depending on its size */ - if (w <= 2 * UI_UNIT_X && (!but->str || but->str[0] == '\0')) { - bitem->item.flag |= UI_ITEM_FIXED_SIZE; - } - - if (layout->child_items_layout) { - BLI_addtail(&layout->child_items_layout->items, bitem); - } - else { - BLI_addtail(&layout->items, bitem); - } - but->layout = layout; - - if (layout->context) { - but->context = layout->context; - but->context->used = true; - } - - if (layout->emboss != UI_EMBOSS_UNDEFINED) { - but->emboss = layout->emboss; - } - - ui_button_group_add_but(uiLayoutGetBlock(layout), but); -} - -bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but) -{ - ListBase *child_list = layout->child_items_layout ? &layout->child_items_layout->items : - &layout->items; - - LISTBASE_FOREACH (uiItem *, item, child_list) { - if (item->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)item; - - if (bitem->but == old_but_ptr) { - bitem->but = new_but; - return true; - } - } - else { - if (ui_layout_replace_but_ptr((uiLayout *)item, old_but_ptr, new_but)) { - return true; - } - } - } - - return false; -} - -void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size) -{ - if (fixed_size) { - layout->item.flag |= UI_ITEM_FIXED_SIZE; - } - else { - layout->item.flag &= ~UI_ITEM_FIXED_SIZE; - } -} - -bool uiLayoutGetFixedSize(uiLayout *layout) -{ - return (layout->item.flag & UI_ITEM_FIXED_SIZE) != 0; -} - -void uiLayoutSetOperatorContext(uiLayout *layout, int opcontext) -{ - layout->root->opcontext = opcontext; -} - -void uiLayoutSetFunc(uiLayout *layout, uiMenuHandleFunc handlefunc, void *argv) -{ - layout->root->handlefunc = handlefunc; - layout->root->argv = argv; -} - -/** - * Used for property search when the layout process needs to be cancelled in order to avoid - * computing the locations for buttons, but the layout items created while adding the buttons - * must still be freed. - */ -void UI_block_layout_free(uiBlock *block) -{ - LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) { - ui_layout_free(root->layout); - MEM_freeN(root); - } -} - -void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y) -{ - BLI_assert(block->active); - - if (r_x) { - *r_x = 0; - } - if (r_y) { - *r_y = 0; - } - - block->curlayout = NULL; - - LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) { - ui_layout_add_padding_button(root); - - /* NULL in advance so we don't interfere when adding button */ - ui_layout_end(block, root->layout, r_x, r_y); - ui_layout_free(root->layout); - MEM_freeN(root); - } - - BLI_listbase_clear(&block->layouts); - - /* XXX silly trick, interface_templates.c doesn't get linked - * because it's not used by other files in this module? */ - { - UI_template_fix_linking(); - } -} - -void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *ptr) -{ - uiBlock *block = layout->root->block; - layout->context = CTX_store_add(&block->contexts, name, ptr); -} - -bContextStore *uiLayoutGetContextStore(uiLayout *layout) -{ - return layout->context; -} - -void uiLayoutContextCopy(uiLayout *layout, bContextStore *context) -{ - uiBlock *block = layout->root->block; - layout->context = CTX_store_add_all(&block->contexts, context); -} - -void uiLayoutSetContextFromBut(uiLayout *layout, uiBut *but) -{ - if (but->opptr) { - uiLayoutSetContextPointer(layout, "button_operator", but->opptr); - } - - if (but->rnapoin.data && but->rnaprop) { - /* TODO: index could be supported as well */ - PointerRNA ptr_prop; - RNA_pointer_create(NULL, &RNA_Property, but->rnaprop, &ptr_prop); - uiLayoutSetContextPointer(layout, "button_prop", &ptr_prop); - uiLayoutSetContextPointer(layout, "button_pointer", &but->rnapoin); - } -} - -/* this is a bit of a hack but best keep it in one place at least */ -wmOperatorType *UI_but_operatortype_get_from_enum_menu(uiBut *but, PropertyRNA **r_prop) -{ - if (r_prop != NULL) { - *r_prop = NULL; - } - - if (but->menu_create_func == menu_item_enum_opname_menu) { - MenuItemLevel *lvl = but->func_argN; - wmOperatorType *ot = WM_operatortype_find(lvl->opname, false); - if ((ot != NULL) && (r_prop != NULL)) { - *r_prop = RNA_struct_type_find_property(ot->srna, lvl->propname); - } - return ot; - } - return NULL; -} - -/* this is a bit of a hack but best keep it in one place at least */ -MenuType *UI_but_menutype_get(uiBut *but) -{ - if (but->menu_create_func == ui_item_menutype_func) { - return (MenuType *)but->poin; - } - return NULL; -} - -/* this is a bit of a hack but best keep it in one place at least */ -PanelType *UI_but_paneltype_get(uiBut *but) -{ - if (but->menu_create_func == ui_item_paneltype_func) { - return (PanelType *)but->poin; - } - return NULL; -} - -void UI_menutype_draw(bContext *C, MenuType *mt, struct uiLayout *layout) -{ - Menu menu = { - .layout = layout, - .type = mt, - }; - - if (G.debug & G_DEBUG_WM) { - printf("%s: opening menu \"%s\"\n", __func__, mt->idname); - } - - if (layout->context) { - CTX_store_set(C, layout->context); - } - - mt->draw(C, &menu); - - if (layout->context) { - CTX_store_set(C, NULL); - } -} - -static bool ui_layout_has_panel_label(const uiLayout *layout, const PanelType *pt) -{ - LISTBASE_FOREACH (uiItem *, subitem, &layout->items) { - if (subitem->type == ITEM_BUTTON) { - uiButtonItem *bitem = (uiButtonItem *)subitem; - if (!(bitem->but->flag & UI_HIDDEN) && STREQ(bitem->but->str, pt->label)) { - return true; - } - } - else { - uiLayout *litem = (uiLayout *)subitem; - if (ui_layout_has_panel_label(litem, pt)) { - return true; - } - } - } - - return false; -} - -static void ui_paneltype_draw_impl(bContext *C, PanelType *pt, uiLayout *layout, bool show_header) -{ - Panel *panel = MEM_callocN(sizeof(Panel), "popover panel"); - panel->type = pt; - panel->flag = PNL_POPOVER; - - uiLayout *last_item = layout->items.last; - - /* Draw main panel. */ - if (show_header) { - uiLayout *row = uiLayoutRow(layout, false); - if (pt->draw_header) { - panel->layout = row; - pt->draw_header(C, panel); - panel->layout = NULL; - } - - /* draw_header() is often used to add a checkbox to the header. If we add the label like below - * the label is disconnected from the checkbox, adding a weird looking gap. As workaround, let - * the checkbox add the label instead. */ - if (!ui_layout_has_panel_label(row, pt)) { - uiItemL(row, CTX_IFACE_(pt->translation_context, pt->label), ICON_NONE); - } - } - - panel->layout = layout; - pt->draw(C, panel); - panel->layout = NULL; - BLI_assert(panel->runtime.custom_data_ptr == NULL); - - MEM_freeN(panel); - - /* Draw child panels. */ - LISTBASE_FOREACH (LinkData *, link, &pt->children) { - PanelType *child_pt = link->data; - - if (child_pt->poll == NULL || child_pt->poll(C, child_pt)) { - /* Add space if something was added to the layout. */ - if (last_item != layout->items.last) { - uiItemS(layout); - last_item = layout->items.last; - } - - uiLayout *col = uiLayoutColumn(layout, false); - ui_paneltype_draw_impl(C, child_pt, col, true); - } - } -} - -/** - * Used for popup panels only. - */ -void UI_paneltype_draw(bContext *C, PanelType *pt, uiLayout *layout) -{ - if (layout->context) { - CTX_store_set(C, layout->context); - } - - ui_paneltype_draw_impl(C, pt, layout, false); - - if (layout->context) { - CTX_store_set(C, NULL); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Layout (Debugging/Introspection) - * - * Serialize the layout as a Python compatible dictionary, - * - * \note Proper string escaping isn't used, - * triple quotes are used to prevent single quotes from interfering with Python syntax. - * If we want this to be fool-proof, we would need full Python compatible string escape support. - * As we don't use triple quotes in the UI it's good-enough in practice. - * \{ */ - -static void ui_layout_introspect_button(DynStr *ds, uiButtonItem *bitem) -{ - uiBut *but = bitem->but; - BLI_dynstr_appendf(ds, "'type':%d, ", (int)but->type); - BLI_dynstr_appendf(ds, "'draw_string':'''%s''', ", but->drawstr); - /* Not exactly needed, rna has this. */ - BLI_dynstr_appendf(ds, "'tip':'''%s''', ", but->tip ? but->tip : ""); - - if (but->optype) { - char *opstr = WM_operator_pystring_ex( - but->block->evil_C, NULL, false, true, but->optype, but->opptr); - BLI_dynstr_appendf(ds, "'operator':'''%s''', ", opstr ? opstr : ""); - MEM_freeN(opstr); - } - - { - PropertyRNA *prop = NULL; - wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, &prop); - if (ot) { - char *opstr = WM_operator_pystring_ex(but->block->evil_C, NULL, false, true, ot, NULL); - BLI_dynstr_appendf(ds, "'operator':'''%s''', ", opstr ? opstr : ""); - BLI_dynstr_appendf(ds, "'property':'''%s''', ", prop ? RNA_property_identifier(prop) : ""); - MEM_freeN(opstr); - } - } - - if (but->rnaprop) { - BLI_dynstr_appendf(ds, - "'rna':'%s.%s[%d]', ", - RNA_struct_identifier(but->rnapoin.type), - RNA_property_identifier(but->rnaprop), - but->rnaindex); - } -} - -static void ui_layout_introspect_items(DynStr *ds, ListBase *lb) -{ - uiItem *item; - - BLI_dynstr_append(ds, "["); - - for (item = lb->first; item; item = item->next) { - - BLI_dynstr_append(ds, "{"); - -#define CASE_ITEM(id) \ - case id: { \ - const char *id_str = STRINGIFY(id); \ - BLI_dynstr_append(ds, "'type': '"); \ - /* Skip 'ITEM_'. */ \ - BLI_dynstr_append(ds, id_str + 5); \ - BLI_dynstr_append(ds, "', "); \ - break; \ - } \ - ((void)0) - - switch (item->type) { - CASE_ITEM(ITEM_BUTTON); - CASE_ITEM(ITEM_LAYOUT_ROW); - CASE_ITEM(ITEM_LAYOUT_COLUMN); - CASE_ITEM(ITEM_LAYOUT_COLUMN_FLOW); - CASE_ITEM(ITEM_LAYOUT_ROW_FLOW); - CASE_ITEM(ITEM_LAYOUT_BOX); - CASE_ITEM(ITEM_LAYOUT_ABSOLUTE); - CASE_ITEM(ITEM_LAYOUT_SPLIT); - CASE_ITEM(ITEM_LAYOUT_OVERLAP); - CASE_ITEM(ITEM_LAYOUT_ROOT); - CASE_ITEM(ITEM_LAYOUT_GRID_FLOW); - CASE_ITEM(ITEM_LAYOUT_RADIAL); - } - -#undef CASE_ITEM - - switch (item->type) { - case ITEM_BUTTON: - ui_layout_introspect_button(ds, (uiButtonItem *)item); - break; - default: - BLI_dynstr_append(ds, "'items':"); - ui_layout_introspect_items(ds, &((uiLayout *)item)->items); - break; - } - - BLI_dynstr_append(ds, "}"); - - if (item != lb->last) { - BLI_dynstr_append(ds, ", "); - } - } - /* Don't use a comma here as it's not needed and - * causes the result to evaluate to a tuple of 1. */ - BLI_dynstr_append(ds, "]"); -} - -/** - * Evaluate layout items as a Python dictionary. - */ -const char *UI_layout_introspect(uiLayout *layout) -{ - DynStr *ds = BLI_dynstr_new(); - uiLayout layout_copy = *layout; - layout_copy.item.next = NULL; - layout_copy.item.prev = NULL; - ListBase layout_dummy_list = {&layout_copy, &layout_copy}; - ui_layout_introspect_items(ds, &layout_dummy_list); - const char *result = BLI_dynstr_get_cstring(ds); - BLI_dynstr_free(ds); - return result; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Alert Box with Big Icon - * \{ */ - -/** - * Helper to add a big icon and create a split layout for alert popups. - * Returns the layout to place further items into the alert box. - */ -uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon) -{ - const uiStyle *style = UI_style_get_dpi(); - const short icon_size = 64 * U.dpi_fac; - const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points); - const int dialog_width = icon_size + (text_points_max * size * U.dpi_fac); - /* By default, the space between icon and text/buttons will be equal to the 'columnspace', - this extra padding will add some space by increasing the left column width, - making the icon placement more symmetrical, between the block edge and the text. */ - const float icon_padding = 5.0f * U.dpi_fac; - /* Calculate the factor of the fixed icon column depending on the block width. */ - const float split_factor = ((float)icon_size + icon_padding) / - (float)(dialog_width - style->columnspace); - - uiLayout *block_layout = UI_block_layout( - block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, dialog_width, 0, 0, style); - - /* Split layout to put alert icon on left side. */ - uiLayout *split_block = uiLayoutSplit(block_layout, split_factor, false); - - /* Alert icon on the left. */ - uiLayout *layout = uiLayoutRow(split_block, false); - /* Using 'align_left' with 'row' avoids stretching the icon along the width of column. */ - uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_LEFT); - uiDefButAlert(block, icon, 0, 0, icon_size, icon_size); - - /* The rest of the content on the right. */ - layout = uiLayoutColumn(split_block, false); - - return layout; -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_layout.cc b/source/blender/editors/interface/interface_layout.cc new file mode 100644 index 00000000000..f376821cf8d --- /dev/null +++ b/source/blender/editors/interface/interface_layout.cc @@ -0,0 +1,5967 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_armature_types.h" +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_array.hh" +#include "BLI_dynstr.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_anim_data.h" +#include "BKE_armature.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "UI_interface.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "interface_intern.h" + +using blender::Array; + +/* Show an icon button after each RNA button to use to quickly set keyframes, + * this is a way to display animation/driven/override status, see T54951. */ +#define UI_PROP_DECORATE +/* Alternate draw mode where some buttons can use single icon width, + * giving more room for the text at the expense of nicely aligned text. */ +#define UI_PROP_SEP_ICON_WIDTH_EXCEPTION + +/* -------------------------------------------------------------------- */ +/** \name Structs and Defines + * \{ */ + +#define UI_OPERATOR_ERROR_RET(_ot, _opname, return_statement) \ + if (ot == NULL) { \ + ui_item_disabled(layout, _opname); \ + RNA_warning("'%s' unknown operator", _opname); \ + return_statement; \ + } \ + (void)0 + +#define UI_ITEM_PROP_SEP_DIVIDE 0.4f + +/* uiLayoutRoot */ + +struct uiLayoutRoot { + struct uiLayoutRoot *next, *prev; + + int type; + int opcontext; + + int emw, emh; + int padding; + + uiMenuHandleFunc handlefunc; + void *argv; + + const uiStyle *style; + uiBlock *block; + uiLayout *layout; +}; + +/* Item */ + +enum uiItemType { + ITEM_BUTTON, + + ITEM_LAYOUT_ROW, + ITEM_LAYOUT_COLUMN, + ITEM_LAYOUT_COLUMN_FLOW, + ITEM_LAYOUT_ROW_FLOW, + ITEM_LAYOUT_GRID_FLOW, + ITEM_LAYOUT_BOX, + ITEM_LAYOUT_ABSOLUTE, + ITEM_LAYOUT_SPLIT, + ITEM_LAYOUT_OVERLAP, + ITEM_LAYOUT_RADIAL, + + ITEM_LAYOUT_ROOT +#if 0 + TEMPLATE_COLUMN_FLOW, + TEMPLATE_SPLIT, + TEMPLATE_BOX, + + TEMPLATE_HEADER, + TEMPLATE_HEADER_ID, +#endif +}; + +typedef struct uiItem { + struct uiItem *next, *prev; + uiItemType type; + int flag; +} uiItem; + +enum { + UI_ITEM_AUTO_FIXED_SIZE = 1 << 0, + UI_ITEM_FIXED_SIZE = 1 << 1, + + UI_ITEM_BOX_ITEM = 1 << 2, /* The item is "inside" a box item */ + UI_ITEM_PROP_SEP = 1 << 3, + UI_ITEM_INSIDE_PROP_SEP = 1 << 4, + /* Show an icon button next to each property (to set keyframes, show status). + * Enabled by default, depends on 'UI_ITEM_PROP_SEP'. */ + UI_ITEM_PROP_DECORATE = 1 << 5, + UI_ITEM_PROP_DECORATE_NO_PAD = 1 << 6, +}; + +struct uiButtonItem { + uiItem item; + uiBut *but; +}; + +struct uiLayout { + uiItem item; + + uiLayoutRoot *root; + bContextStore *context; + uiLayout *parent; + ListBase items; + + char heading[UI_MAX_NAME_STR]; + + /** Sub layout to add child items, if not the layout itself. */ + uiLayout *child_items_layout; + + int x, y, w, h; + float scale[2]; + short space; + bool align; + bool active; + bool active_default; + bool activate_init; + bool enabled; + bool redalert; + bool keepaspect; + /** For layouts inside gridflow, they and their items shall never have a fixed maximal size. */ + bool variable_size; + char alignment; + eUIEmbossType emboss; + /** for fixed width or height to avoid UI size changes */ + float units[2]; +}; + +struct uiLayoutItemFlow { + uiLayout litem; + int number; + int totcol; +}; + +struct uiLayoutItemGridFlow { + uiLayout litem; + + /* Extra parameters */ + bool row_major; /* Fill first row first, instead of filling first column first. */ + bool even_columns; /* Same width for all columns. */ + bool even_rows; /* Same height for all rows. */ + /** + * - If positive, absolute fixed number of columns. + * - If 0, fully automatic (based on available width). + * - If negative, automatic but only generates number of columns/rows + * multiple of given (absolute) value. + */ + int columns_len; + + /* Pure internal runtime storage. */ + int tot_items, tot_columns, tot_rows; +}; + +struct uiLayoutItemBx { + uiLayout litem; + uiBut *roundbox; +}; + +struct uiLayoutItemSplit { + uiLayout litem; + float percentage; +}; + +struct uiLayoutItemRoot { + uiLayout litem; +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Item + * \{ */ + +static const char *ui_item_name_add_colon(const char *name, char namestr[UI_MAX_NAME_STR]) +{ + const int len = strlen(name); + + if (len != 0 && len + 1 < UI_MAX_NAME_STR) { + memcpy(namestr, name, len); + namestr[len] = ':'; + namestr[len + 1] = '\0'; + return namestr; + } + + return name; +} + +static int ui_item_fit( + int item, int pos, int all, int available, bool is_last, int alignment, float *extra_pixel) +{ + /* available == 0 is unlimited */ + if (ELEM(0, available, all)) { + return item; + } + + if (all > available) { + /* contents is bigger than available space */ + if (is_last) { + return available - pos; + } + + const float width = *extra_pixel + (item * available) / (float)all; + *extra_pixel = width - (int)width; + return (int)width; + } + + /* contents is smaller or equal to available space */ + if (alignment == UI_LAYOUT_ALIGN_EXPAND) { + if (is_last) { + return available - pos; + } + + const float width = *extra_pixel + (item * available) / (float)all; + *extra_pixel = width - (int)width; + return (int)width; + } + return item; +} + +/* variable button size in which direction? */ +#define UI_ITEM_VARY_X 1 +#define UI_ITEM_VARY_Y 2 + +static int ui_layout_vary_direction(uiLayout *layout) +{ + return ((ELEM(layout->root->type, UI_LAYOUT_HEADER, UI_LAYOUT_PIEMENU) || + (layout->alignment != UI_LAYOUT_ALIGN_EXPAND)) ? + UI_ITEM_VARY_X : + UI_ITEM_VARY_Y); +} + +static bool ui_layout_variable_size(uiLayout *layout) +{ + /* Note that this code is probably a bit flakey, we'd probably want to know whether it's + * variable in X and/or Y, etc. But for now it mimics previous one, + * with addition of variable flag set for children of grid-flow layouts. */ + return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size; +} + +/* estimated size of text + icon */ +static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact) +{ + const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f); + + if (icon && !name[0]) { + return unit_x; /* icon only */ + } + + if (ui_layout_variable_size(layout)) { + if (!icon && !name[0]) { + return unit_x; /* No icon or name. */ + } + if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) { + layout->item.flag |= UI_ITEM_FIXED_SIZE; + } + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + float margin = compact ? 1.25 : 1.50; + if (icon) { + /* It may seem odd that the icon only adds (unit_x / 4) + * but taking margins into account its fine, except + * in compact mode a bit more margin is required. */ + margin += compact ? 0.35 : 0.25; + } + return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin); + } + return unit_x * 10; +} + +static void ui_item_size(uiItem *item, int *r_w, int *r_h) +{ + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + if (r_w) { + *r_w = BLI_rctf_size_x(&bitem->but->rect); + } + if (r_h) { + *r_h = BLI_rctf_size_y(&bitem->but->rect); + } + } + else { + uiLayout *litem = (uiLayout *)item; + + if (r_w) { + *r_w = litem->w; + } + if (r_h) { + *r_h = litem->h; + } + } +} + +static void ui_item_offset(uiItem *item, int *r_x, int *r_y) +{ + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + if (r_x) { + *r_x = bitem->but->rect.xmin; + } + if (r_y) { + *r_y = bitem->but->rect.ymin; + } + } + else { + if (r_x) { + *r_x = 0; + } + if (r_y) { + *r_y = 0; + } + } +} + +static void ui_item_position(uiItem *item, int x, int y, int w, int h) +{ + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + bitem->but->rect.xmin = x; + bitem->but->rect.ymin = y; + bitem->but->rect.xmax = x + w; + bitem->but->rect.ymax = y + h; + + ui_but_update(bitem->but); /* for strlen */ + } + else { + uiLayout *litem = (uiLayout *)item; + + litem->x = x; + litem->y = y + h; + litem->w = w; + litem->h = h; + } +} + +static void ui_item_move(uiItem *item, int delta_xmin, int delta_xmax) +{ + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + bitem->but->rect.xmin += delta_xmin; + bitem->but->rect.xmax += delta_xmax; + + ui_but_update(bitem->but); /* for strlen */ + } + else { + uiLayout *litem = (uiLayout *)item; + + if (delta_xmin > 0) { + litem->x += delta_xmin; + } + else { + litem->w += delta_xmax; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Special RNA Items + * \{ */ + +int uiLayoutGetLocalDir(const uiLayout *layout) +{ + switch (layout->item.type) { + case ITEM_LAYOUT_ROW: + case ITEM_LAYOUT_ROOT: + case ITEM_LAYOUT_OVERLAP: + return UI_LAYOUT_HORIZONTAL; + case ITEM_LAYOUT_COLUMN: + case ITEM_LAYOUT_COLUMN_FLOW: + case ITEM_LAYOUT_GRID_FLOW: + case ITEM_LAYOUT_SPLIT: + case ITEM_LAYOUT_ABSOLUTE: + case ITEM_LAYOUT_BOX: + default: + return UI_LAYOUT_VERTICAL; + } +} + +static uiLayout *ui_item_local_sublayout(uiLayout *test, uiLayout *layout, bool align) +{ + uiLayout *sub; + if (uiLayoutGetLocalDir(test) == UI_LAYOUT_HORIZONTAL) { + sub = uiLayoutRow(layout, align); + } + else { + sub = uiLayoutColumn(layout, align); + } + + sub->space = 0; + return sub; +} + +static void ui_layer_but_cb(bContext *C, void *arg_but, void *arg_index) +{ + wmWindow *win = CTX_wm_window(C); + uiBut *but = (uiBut *)arg_but; + PointerRNA *ptr = &but->rnapoin; + PropertyRNA *prop = but->rnaprop; + const int index = POINTER_AS_INT(arg_index); + const int shift = win->eventstate->shift; + const int len = RNA_property_array_length(ptr, prop); + + if (!shift) { + RNA_property_boolean_set_index(ptr, prop, index, true); + + for (int i = 0; i < len; i++) { + if (i != index) { + RNA_property_boolean_set_index(ptr, prop, i, 0); + } + } + + RNA_property_update(C, ptr, prop); + + LISTBASE_FOREACH (uiBut *, cbut, &but->block->buttons) { + ui_but_update(cbut); + } + } +} + +/* create buttons for an item with an RNA array */ +static void ui_item_array(uiLayout *layout, + uiBlock *block, + const char *name, + int icon, + PointerRNA *ptr, + PropertyRNA *prop, + int len, + int x, + int y, + int w, + int UNUSED(h), + bool expand, + bool slider, + int toggle, + bool icon_only, + bool compact, + bool show_text) +{ + const uiStyle *style = layout->root->style; + + /* retrieve type and subtype */ + const PropertyType type = RNA_property_type(prop); + const PropertySubType subtype = RNA_property_subtype(prop); + + uiLayout *sub = ui_item_local_sublayout(layout, layout, 1); + UI_block_layout_set_current(block, sub); + + /* create label */ + if (name[0] && show_text) { + uiDefBut(block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + } + + /* create buttons */ + if (type == PROP_BOOLEAN && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER)) { + /* special check for layer layout */ + const int cols = (len >= 20) ? 2 : 1; + const int colbuts = len / (2 * cols); + uint layer_used = 0; + uint layer_active = 0; + + UI_block_layout_set_current(block, uiLayoutAbsolute(layout, false)); + + const int butw = UI_UNIT_X * 0.75; + const int buth = UI_UNIT_X * 0.75; + + if (ptr->type == &RNA_Armature) { + bArmature *arm = (bArmature *)ptr->data; + + layer_used = arm->layer_used; + + if (arm->edbo) { + if (arm->act_edbone) { + layer_active |= arm->act_edbone->layer; + } + } + else { + if (arm->act_bone) { + layer_active |= arm->act_bone->layer; + } + } + } + + for (int b = 0; b < cols; b++) { + UI_block_align_begin(block); + + for (int a = 0; a < colbuts; a++) { + const int layer_num = a + b * colbuts; + const uint layer_flag = (1u << layer_num); + + if (layer_used & layer_flag) { + if (layer_active & layer_flag) { + icon = ICON_LAYER_ACTIVE; + } + else { + icon = ICON_LAYER_USED; + } + } + else { + icon = ICON_BLANK1; + } + + uiBut *but = uiDefAutoButR( + block, ptr, prop, layer_num, "", icon, x + butw * a, y + buth, butw, buth); + if (subtype == PROP_LAYER_MEMBER) { + UI_but_func_set(but, ui_layer_but_cb, but, POINTER_FROM_INT(layer_num)); + } + } + for (int a = 0; a < colbuts; a++) { + const int layer_num = a + len / 2 + b * colbuts; + const uint layer_flag = (1u << layer_num); + + if (layer_used & layer_flag) { + if (layer_active & layer_flag) { + icon = ICON_LAYER_ACTIVE; + } + else { + icon = ICON_LAYER_USED; + } + } + else { + icon = ICON_BLANK1; + } + + uiBut *but = uiDefAutoButR( + block, ptr, prop, layer_num, "", icon, x + butw * a, y, butw, buth); + if (subtype == PROP_LAYER_MEMBER) { + UI_but_func_set(but, ui_layer_but_cb, but, POINTER_FROM_INT(layer_num)); + } + } + UI_block_align_end(block); + + x += colbuts * butw + style->buttonspacex; + } + } + else if (subtype == PROP_MATRIX) { + int totdim, dim_size[3]; /* 3 == RNA_MAX_ARRAY_DIMENSION */ + int row, col; + + UI_block_layout_set_current(block, uiLayoutAbsolute(layout, true)); + + totdim = RNA_property_array_dimension(ptr, prop, dim_size); + if (totdim != 2) { + /* Only 2D matrices supported in UI so far. */ + return; + } + + w /= dim_size[0]; + /* h /= dim_size[1]; */ /* UNUSED */ + + for (int a = 0; a < len; a++) { + col = a % dim_size[0]; + row = a / dim_size[0]; + + uiBut *but = uiDefAutoButR(block, + ptr, + prop, + a, + "", + ICON_NONE, + x + w * col, + y + (dim_size[1] * UI_UNIT_Y) - (row * UI_UNIT_Y), + w, + UI_UNIT_Y); + if (slider && but->type == UI_BTYPE_NUM) { + uiButNumber *number_but = (uiButNumber *)but; + + but->a1 = number_but->step_size; + but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER); + } + } + } + else if (subtype == PROP_DIRECTION && !expand) { + uiDefButR_prop(block, + UI_BTYPE_UNITVEC, + 0, + name, + x, + y, + UI_UNIT_X * 3, + UI_UNIT_Y * 3, + ptr, + prop, + -1, + 0, + 0, + -1, + -1, + NULL); + } + else { + /* note, this block of code is a bit arbitrary and has just been made + * to work with common cases, but may need to be re-worked */ + + /* special case, boolean array in a menu, this could be used in a more generic way too */ + if (ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA) && !expand && ELEM(len, 3, 4)) { + uiDefAutoButR(block, ptr, prop, -1, "", ICON_NONE, 0, 0, w, UI_UNIT_Y); + } + else { + /* Even if 'expand' is false, we expand anyway. */ + + /* layout for known array subtypes */ + char str[3] = {'\0'}; + + if (!icon_only && show_text) { + if (type != PROP_BOOLEAN) { + str[1] = ':'; + } + } + + /* show checkboxes for rna on a non-emboss block (menu for eg) */ + bool *boolarr = NULL; + if (type == PROP_BOOLEAN && + ELEM(layout->root->block->emboss, UI_EMBOSS_NONE, UI_EMBOSS_PULLDOWN)) { + boolarr = (bool *)MEM_callocN(sizeof(bool) * len, __func__); + RNA_property_boolean_get_array(ptr, prop, boolarr); + } + + const char *str_buf = show_text ? str : ""; + for (int a = 0; a < len; a++) { + if (!icon_only && show_text) { + str[0] = RNA_property_array_item_char(prop, a); + } + if (boolarr) { + icon = boolarr[a] ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; + } + + const int width_item = ((compact && type == PROP_BOOLEAN) ? + min_ii(w, ui_text_icon_width(layout, str_buf, icon, false)) : + w); + + uiBut *but = uiDefAutoButR( + block, ptr, prop, a, str_buf, icon, 0, 0, width_item, UI_UNIT_Y); + if (slider && but->type == UI_BTYPE_NUM) { + uiButNumber *number_but = (uiButNumber *)but; + + but->a1 = number_but->step_size; + but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER); + } + if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) { + but->type = UI_BTYPE_TOGGLE; + } + if ((a == 0) && (subtype == PROP_AXISANGLE)) { + UI_but_unit_type_set(but, PROP_UNIT_ROTATION); + } + } + + if (boolarr) { + MEM_freeN(boolarr); + } + } + } + + UI_block_layout_set_current(block, layout); +} + +static void ui_item_enum_expand_handle(bContext *C, void *arg1, void *arg2) +{ + wmWindow *win = CTX_wm_window(C); + + if (!win->eventstate->shift) { + uiBut *but = (uiBut *)arg1; + const int enum_value = POINTER_AS_INT(arg2); + + int current_value = RNA_property_enum_get(&but->rnapoin, but->rnaprop); + if (!(current_value & enum_value)) { + current_value = enum_value; + } + else { + current_value &= enum_value; + } + RNA_property_enum_set(&but->rnapoin, but->rnaprop, current_value); + } +} + +/** + * Draw a single enum button, a utility for #ui_item_enum_expand_exec + */ +static void ui_item_enum_expand_elem_exec(uiLayout *layout, + uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + const char *uiname, + const int h, + const eButType but_type, + const bool icon_only, + const EnumPropertyItem *item, + const bool is_first) +{ + const char *name = (!uiname || uiname[0]) ? item->name : ""; + const int icon = item->icon; + const int value = item->value; + const int itemw = ui_text_icon_width(block->curlayout, icon_only ? "" : name, icon, 0); + + uiBut *but; + if (icon && name[0] && !icon_only) { + but = uiDefIconTextButR_prop( + block, but_type, 0, icon, name, 0, 0, itemw, h, ptr, prop, -1, 0, value, -1, -1, NULL); + } + else if (icon) { + const int w = (is_first) ? itemw : ceilf(itemw - U.pixelsize); + but = uiDefIconButR_prop( + block, but_type, 0, icon, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); + } + else { + but = uiDefButR_prop( + block, but_type, 0, name, 0, 0, itemw, h, ptr, prop, -1, 0, value, -1, -1, NULL); + } + + if (RNA_property_flag(prop) & PROP_ENUM_FLAG) { + /* If this is set, assert since we're clobbering someone elses callback. */ + /* Buttons get their block's func by default, so we cannot assert in that case either. */ + BLI_assert(ELEM(but->func, NULL, block->func)); + UI_but_func_set(but, ui_item_enum_expand_handle, but, POINTER_FROM_INT(value)); + } + + if (uiLayoutGetLocalDir(layout) != UI_LAYOUT_HORIZONTAL) { + but->drawflag |= UI_BUT_TEXT_LEFT; + } + + /* Allow quick, inaccurate swipe motions to switch tabs + * (no need to keep cursor over them). */ + if (but_type == UI_BTYPE_TAB) { + but->flag |= UI_BUT_DRAG_LOCK; + } +} + +static void ui_item_enum_expand_exec(uiLayout *layout, + uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + const char *uiname, + const int h, + const eButType but_type, + const bool icon_only) +{ + /* XXX: The way this function currently handles uiname parameter + * is insane and inconsistent with general UI API: + * + * - uiname is the *enum property* label. + * - when it is NULL or empty, we do not draw *enum items* labels, + * this doubles the icon_only parameter. + * - we *never* draw (i.e. really use) the enum label uiname, it is just used as a mere flag! + * + * Unfortunately, fixing this implies an API "soft break", so better to defer it for later... :/ + * - mont29 + */ + + BLI_assert(RNA_property_type(prop) == PROP_ENUM); + + const bool radial = (layout->root->type == UI_LAYOUT_PIEMENU); + + bool free; + const EnumPropertyItem *item_array; + if (radial) { + RNA_property_enum_items_gettexted_all( + (bContext *)block->evil_C, ptr, prop, &item_array, NULL, &free); + } + else { + RNA_property_enum_items_gettexted( + (bContext *)block->evil_C, ptr, prop, &item_array, NULL, &free); + } + + /* We don't want nested rows, cols in menus. */ + uiLayout *layout_radial = NULL; + if (radial) { + if (layout->root->layout == layout) { + layout_radial = uiLayoutRadial(layout); + UI_block_layout_set_current(block, layout_radial); + } + else { + if (layout->item.type == ITEM_LAYOUT_RADIAL) { + layout_radial = layout; + } + UI_block_layout_set_current(block, layout); + } + } + else if (ELEM(layout->item.type, ITEM_LAYOUT_GRID_FLOW, ITEM_LAYOUT_COLUMN_FLOW) || + layout->root->type == UI_LAYOUT_MENU) { + UI_block_layout_set_current(block, layout); + } + else { + UI_block_layout_set_current(block, ui_item_local_sublayout(layout, layout, 1)); + } + + for (const EnumPropertyItem *item = item_array; item->identifier; item++) { + const bool is_first = item == item_array; + + if (!item->identifier[0]) { + const EnumPropertyItem *next_item = item + 1; + + /* Separate items, potentially with a label. */ + if (next_item->identifier) { + /* Item without identifier but with name: + * Add group label for the following items. */ + if (item->name) { + if (!is_first) { + uiItemS(block->curlayout); + } + uiItemL(block->curlayout, item->name, item->icon); + } + else if (radial && layout_radial) { + uiItemS(layout_radial); + } + else { + uiItemS(block->curlayout); + } + } + continue; + } + + ui_item_enum_expand_elem_exec( + layout, block, ptr, prop, uiname, h, but_type, icon_only, item, is_first); + } + + UI_block_layout_set_current(block, layout); + + if (free) { + MEM_freeN((void *)item_array); + } +} +static void ui_item_enum_expand(uiLayout *layout, + uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + const char *uiname, + const int h, + const bool icon_only) +{ + ui_item_enum_expand_exec(layout, block, ptr, prop, uiname, h, UI_BTYPE_ROW, icon_only); +} +static void ui_item_enum_expand_tabs(uiLayout *layout, + bContext *C, + uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + PointerRNA *ptr_highlight, + PropertyRNA *prop_highlight, + const char *uiname, + const int h, + const bool icon_only) +{ + uiBut *last = (uiBut *)block->buttons.last; + + ui_item_enum_expand_exec(layout, block, ptr, prop, uiname, h, UI_BTYPE_TAB, icon_only); + BLI_assert(last != block->buttons.last); + + for (uiBut *tab = last ? last->next : (uiBut *)block->buttons.first; tab; tab = tab->next) { + UI_but_drawflag_enable(tab, ui_but_align_opposite_to_area_align_get(CTX_wm_region(C))); + } + + const bool use_custom_highlight = (prop_highlight != NULL); + + if (use_custom_highlight) { + const int highlight_array_len = RNA_property_array_length(ptr_highlight, prop_highlight); + Array highlight_array(highlight_array_len); + RNA_property_boolean_get_array(ptr_highlight, prop_highlight, highlight_array.data()); + int i = 0; + for (uiBut *tab_but = last ? last->next : (uiBut *)block->buttons.first; + (tab_but != NULL) && (i < highlight_array_len); + tab_but = tab_but->next, i++) { + SET_FLAG_FROM_TEST(tab_but->flag, !highlight_array[i], UI_BUT_INACTIVE); + } + } +} + +/* callback for keymap item change button */ +static void ui_keymap_but_cb(bContext *UNUSED(C), void *but_v, void *UNUSED(key_v)) +{ + uiBut *but = (uiBut *)but_v; + + RNA_boolean_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) != 0); + RNA_boolean_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) != 0); + RNA_boolean_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) != 0); + RNA_boolean_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) != 0); +} + +/** + * Create label + button for RNA property + * + * \param w_hint: For varying width layout, this becomes the label width. + * Otherwise it's used to fit both items into it. + */ +static uiBut *ui_item_with_label(uiLayout *layout, + uiBlock *block, + const char *name, + int icon, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + int x, + int y, + int w_hint, + int h, + int flag) +{ + uiLayout *sub = layout; + int prop_but_width = w_hint; +#ifdef UI_PROP_DECORATE + uiLayout *layout_prop_decorate = NULL; + const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0); + const bool use_prop_decorate = use_prop_sep && (layout->item.flag & UI_ITEM_PROP_DECORATE) && + (layout->item.flag & UI_ITEM_PROP_DECORATE_NO_PAD) == 0; +#endif + + const bool is_keymapitem_ptr = RNA_struct_is_a(ptr->type, &RNA_KeyMapItem); + if ((flag & UI_ITEM_R_FULL_EVENT) && !is_keymapitem_ptr) { + RNA_warning("Data is not a keymap item struct: %s. Ignoring 'full_event' option.", + RNA_struct_identifier(ptr->type)); + } + + UI_block_layout_set_current(block, layout); + + /* Only add new row if more than 1 item will be added. */ + if (name[0] +#ifdef UI_PROP_DECORATE + || use_prop_decorate +#endif + ) { + /* Also avoid setting 'align' if possible. Set the space to zero instead as aligning a large + * number of labels can end up aligning thousands of buttons when displaying key-map search (a + * heavy operation), see: T78636. */ + sub = uiLayoutRow(layout, layout->align); + sub->space = 0; + } + + if (name[0]) { +#ifdef UI_PROP_DECORATE + if (use_prop_sep) { + layout_prop_decorate = uiItemL_respect_property_split(layout, name, 0); + } + else +#endif + { + int w_label; + if (ui_layout_variable_size(layout)) { + /* w_hint is width for label in this case. + * Use a default width for property button(s) */ + prop_but_width = UI_UNIT_X * 5; + w_label = w_hint; + } + else { + w_label = w_hint / 3; + } + uiDefBut(block, UI_BTYPE_LABEL, 0, name, x, y, w_label, h, NULL, 0.0, 0.0, 0, 0, ""); + } + } + + const PropertyType type = RNA_property_type(prop); + const PropertySubType subtype = RNA_property_subtype(prop); + + uiBut *but; + if (ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) { + UI_block_layout_set_current(block, uiLayoutRow(sub, true)); + but = uiDefAutoButR(block, ptr, prop, index, "", icon, x, y, prop_but_width - UI_UNIT_X, h); + + /* BUTTONS_OT_file_browse calls UI_context_active_but_prop_get_filebrowser */ + uiDefIconButO(block, + UI_BTYPE_BUT, + subtype == PROP_DIRPATH ? "BUTTONS_OT_directory_browse" : + "BUTTONS_OT_file_browse", + WM_OP_INVOKE_DEFAULT, + ICON_FILEBROWSER, + x, + y, + UI_UNIT_X, + h, + NULL); + } + else if (flag & UI_ITEM_R_EVENT) { + but = uiDefButR_prop(block, + UI_BTYPE_KEY_EVENT, + 0, + name, + x, + y, + prop_but_width, + h, + ptr, + prop, + index, + 0, + 0, + -1, + -1, + NULL); + } + else if ((flag & UI_ITEM_R_FULL_EVENT) && is_keymapitem_ptr) { + char buf[128]; + + WM_keymap_item_to_string((const wmKeyMapItem *)ptr->data, false, buf, sizeof(buf)); + + but = uiDefButR_prop(block, + UI_BTYPE_HOTKEY_EVENT, + 0, + buf, + x, + y, + prop_but_width, + h, + ptr, + prop, + 0, + 0, + 0, + -1, + -1, + NULL); + UI_but_func_set(but, ui_keymap_but_cb, but, NULL); + if (flag & UI_ITEM_R_IMMEDIATE) { + UI_but_flag_enable(but, UI_BUT_IMMEDIATE); + } + } + else { + const char *str = (type == PROP_ENUM && !(flag & UI_ITEM_R_ICON_ONLY)) ? NULL : ""; + but = uiDefAutoButR(block, ptr, prop, index, str, icon, x, y, prop_but_width, h); + } + +#ifdef UI_PROP_DECORATE + /* Only for alignment. */ + if (use_prop_decorate) { /* Note that sep flag may have been unset meanwhile. */ + uiItemL(layout_prop_decorate ? layout_prop_decorate : sub, NULL, ICON_BLANK1); + } +#endif /* UI_PROP_DECORATE */ + + UI_block_layout_set_current(block, layout); + return but; +} + +void UI_context_active_but_prop_get_filebrowser(const bContext *C, + PointerRNA *r_ptr, + PropertyRNA **r_prop, + bool *r_is_undo, + bool *r_is_userdef) +{ + ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C); + uiBut *prevbut = NULL; + + memset(r_ptr, 0, sizeof(*r_ptr)); + *r_prop = NULL; + *r_is_undo = false; + *r_is_userdef = false; + + if (!region) { + return; + } + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but && but->rnapoin.data) { + if (RNA_property_type(but->rnaprop) == PROP_STRING) { + prevbut = but; + } + } + + /* find the button before the active one */ + if ((but->flag & UI_BUT_LAST_ACTIVE) && prevbut) { + *r_ptr = prevbut->rnapoin; + *r_prop = prevbut->rnaprop; + *r_is_undo = (prevbut->flag & UI_BUT_UNDO) != 0; + *r_is_userdef = UI_but_is_userdef(prevbut); + return; + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Button Items + * \{ */ + +/** + * Update a buttons tip with an enum's description if possible. + */ +static void ui_but_tip_from_enum_item(uiBut *but, const EnumPropertyItem *item) +{ + if (but->tip == NULL || but->tip[0] == '\0') { + if (item->description && item->description[0] && + !(but->optype && but->optype->get_description)) { + but->tip = item->description; + } + } +} + +/* disabled item */ +static void ui_item_disabled(uiLayout *layout, const char *name) +{ + uiBlock *block = layout->root->block; + + UI_block_layout_set_current(block, layout); + + if (!name) { + name = ""; + } + + const int w = ui_text_icon_width(layout, name, 0, 0); + + uiBut *but = uiDefBut( + block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + UI_but_disable(but, ""); +} + +/** + * Operator Item + * \param r_opptr: Optional, initialize with operator properties when not NULL. + * Will always be written to even in the case of errors. + */ +static uiBut *uiItemFullO_ptr_ex(uiLayout *layout, + wmOperatorType *ot, + const char *name, + int icon, + IDProperty *properties, + int context, + int flag, + PointerRNA *r_opptr) +{ + /* Take care to fill 'r_opptr' whatever happens. */ + uiBlock *block = layout->root->block; + + if (!name) { + if (ot && ot->srna && (flag & UI_ITEM_R_ICON_ONLY) == 0) { + name = WM_operatortype_name(ot, NULL); + } + else { + name = ""; + } + } + + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + UI_block_layout_set_current(block, layout); + ui_block_new_button_group(block); + + const int w = ui_text_icon_width(layout, name, icon, 0); + + const eUIEmbossType prev_emboss = layout->emboss; + if (flag & UI_ITEM_R_NO_BG) { + layout->emboss = UI_EMBOSS_NONE; + } + + /* create the button */ + uiBut *but; + if (icon) { + if (name[0]) { + but = uiDefIconTextButO_ptr( + block, UI_BTYPE_BUT, ot, context, icon, name, 0, 0, w, UI_UNIT_Y, NULL); + } + else { + but = uiDefIconButO_ptr(block, UI_BTYPE_BUT, ot, context, icon, 0, 0, w, UI_UNIT_Y, NULL); + } + } + else { + but = uiDefButO_ptr(block, UI_BTYPE_BUT, ot, context, name, 0, 0, w, UI_UNIT_Y, NULL); + } + + BLI_assert(but->optype != NULL); + + if (flag & UI_ITEM_R_NO_BG) { + layout->emboss = prev_emboss; + } + + if (flag & UI_ITEM_O_DEPRESS) { + but->flag |= UI_SELECT_DRAW; + } + + if (flag & UI_ITEM_R_ICON_ONLY) { + UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT); + } + + if (layout->redalert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + if (layout->active_default) { + UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT); + } + + /* assign properties */ + if (properties || r_opptr) { + PointerRNA *opptr = UI_but_operator_ptr_get(but); + if (properties) { + opptr->data = properties; + } + else { + const IDPropertyTemplate val = {0}; + opptr->data = IDP_New(IDP_GROUP, &val, "wmOperatorProperties"); + } + if (r_opptr) { + *r_opptr = *opptr; + } + } + + return but; +} + +static void ui_item_menu_hold(struct bContext *C, ARegion *butregion, uiBut *but) +{ + uiPopupMenu *pup = UI_popup_menu_begin(C, "", ICON_NONE); + uiLayout *layout = UI_popup_menu_layout(pup); + uiBlock *block = layout->root->block; + UI_popup_menu_but_set(pup, butregion, but); + + block->flag |= UI_BLOCK_POPUP_HOLD; + block->flag |= UI_BLOCK_IS_FLIP; + + char direction = UI_DIR_DOWN; + if (!but->drawstr[0]) { + switch (RGN_ALIGN_ENUM_FROM_MASK(butregion->alignment)) { + case RGN_ALIGN_LEFT: + direction = UI_DIR_RIGHT; + break; + case RGN_ALIGN_RIGHT: + direction = UI_DIR_LEFT; + break; + case RGN_ALIGN_BOTTOM: + direction = UI_DIR_UP; + break; + default: + direction = UI_DIR_DOWN; + break; + } + } + UI_block_direction_set(block, direction); + + const char *menu_id = (const char *)but->hold_argN; + MenuType *mt = WM_menutype_find(menu_id, true); + if (mt) { + uiLayoutSetContextFromBut(layout, but); + UI_menutype_draw(C, mt, layout); + } + else { + uiItemL(layout, "Menu Missing:", ICON_NONE); + uiItemL(layout, menu_id, ICON_NONE); + } + UI_popup_menu_end(C, pup); +} + +void uiItemFullO_ptr(uiLayout *layout, + wmOperatorType *ot, + const char *name, + int icon, + IDProperty *properties, + int context, + int flag, + PointerRNA *r_opptr) +{ + uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr); +} + +void uiItemFullOMenuHold_ptr(uiLayout *layout, + wmOperatorType *ot, + const char *name, + int icon, + IDProperty *properties, + int context, + int flag, + const char *menu_id, + PointerRNA *r_opptr) +{ + uiBut *but = uiItemFullO_ptr_ex(layout, ot, name, icon, properties, context, flag, r_opptr); + UI_but_func_hold_set(but, ui_item_menu_hold, BLI_strdup(menu_id)); +} + +void uiItemFullO(uiLayout *layout, + const char *opname, + const char *name, + int icon, + IDProperty *properties, + int context, + int flag, + PointerRNA *r_opptr) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + + UI_OPERATOR_ERROR_RET(ot, opname, { + if (r_opptr) { + *r_opptr = PointerRNA_NULL; + } + return; + }); + + uiItemFullO_ptr(layout, ot, name, icon, properties, context, flag, r_opptr); +} + +static const char *ui_menu_enumpropname(uiLayout *layout, + PointerRNA *ptr, + PropertyRNA *prop, + int retval) +{ + bool free; + const EnumPropertyItem *item; + RNA_property_enum_items((bContext *)layout->root->block->evil_C, ptr, prop, &item, NULL, &free); + + const char *name; + if (RNA_enum_name(item, retval, &name)) { + name = CTX_IFACE_(RNA_property_translation_context(prop), name); + } + else { + name = ""; + } + + if (free) { + MEM_freeN((void *)item); + } + + return name; +} + +void uiItemEnumO_ptr(uiLayout *layout, + wmOperatorType *ot, + const char *name, + int icon, + const char *propname, + int value) +{ + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + + PropertyRNA *prop = RNA_struct_find_property(&ptr, propname); + if (prop == NULL) { + RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); + return; + } + + RNA_property_enum_set(&ptr, prop, value); + + if (!name) { + name = ui_menu_enumpropname(layout, &ptr, prop, value); + } + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} +void uiItemEnumO(uiLayout *layout, + const char *opname, + const char *name, + int icon, + const char *propname, + int value) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + + if (ot) { + uiItemEnumO_ptr(layout, ot, name, icon, propname, value); + } + else { + ui_item_disabled(layout, opname); + RNA_warning("unknown operator '%s'", opname); + } +} + +BLI_INLINE bool ui_layout_is_radial(const uiLayout *layout) +{ + return (layout->item.type == ITEM_LAYOUT_RADIAL) || + ((layout->item.type == ITEM_LAYOUT_ROOT) && (layout->root->type == UI_LAYOUT_PIEMENU)); +} + +/** + * Create ui items for enum items in \a item_array. + * + * A version of #uiItemsFullEnumO that takes pre-calculated item array. + */ +void uiItemsFullEnumO_items(uiLayout *layout, + wmOperatorType *ot, + PointerRNA ptr, + PropertyRNA *prop, + IDProperty *properties, + int context, + int flag, + const EnumPropertyItem *item_array, + int totitem) +{ + const char *propname = RNA_property_identifier(prop); + if (RNA_property_type(prop) != PROP_ENUM) { + RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname); + return; + } + + uiLayout *target, *split = NULL; + uiBlock *block = layout->root->block; + const bool radial = ui_layout_is_radial(layout); + + if (radial) { + target = uiLayoutRadial(layout); + } + else if ((uiLayoutGetLocalDir(layout) == UI_LAYOUT_HORIZONTAL) && (flag & UI_ITEM_R_ICON_ONLY)) { + target = layout; + UI_block_layout_set_current(block, target); + + /* Add a blank button to the beginning of the row. */ + uiDefIconBut(block, + UI_BTYPE_LABEL, + 0, + ICON_BLANK1, + 0, + 0, + 1.25f * UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + NULL); + } + else { + split = uiLayoutSplit(layout, 0.0f, false); + target = uiLayoutColumn(split, layout->align); + } + + bool last_iter = false; + const EnumPropertyItem *item = item_array; + for (int i = 1; item->identifier && !last_iter; i++, item++) { + /* handle oversized pies */ + if (radial && (totitem > PIE_MAX_ITEMS) && (i >= PIE_MAX_ITEMS)) { + if (item->name) { /* only visible items */ + const EnumPropertyItem *tmp; + + /* Check if there are more visible items for the next level. If not, we don't + * add a new level and add the remaining item instead of the 'more' button. */ + for (tmp = item + 1; tmp->identifier; tmp++) { + if (tmp->name) { + break; + } + } + + if (tmp->identifier) { /* only true if loop above found item and did early-exit */ + ui_pie_menu_level_create( + block, ot, propname, properties, item_array, totitem, context, flag); + /* break since rest of items is handled in new pie level */ + break; + } + last_iter = true; + } + else { + continue; + } + } + + if (item->identifier[0]) { + PointerRNA tptr; + WM_operator_properties_create_ptr(&tptr, ot); + if (properties) { + if (tptr.data) { + IDP_FreeProperty((IDProperty *)tptr.data); + } + tptr.data = IDP_CopyProperty(properties); + } + RNA_property_enum_set(&tptr, prop, item->value); + + uiItemFullO_ptr(target, + ot, + (flag & UI_ITEM_R_ICON_ONLY) ? NULL : item->name, + item->icon, + (IDProperty *)tptr.data, + context, + flag, + NULL); + + ui_but_tip_from_enum_item((uiBut *)block->buttons.last, item); + } + else { + if (item->name) { + if (item != item_array && !radial && split != NULL) { + target = uiLayoutColumn(split, layout->align); + + /* inconsistent, but menus with labels do not look good flipped */ + block->flag |= UI_BLOCK_NO_FLIP; + } + + uiBut *but; + if (item->icon || radial) { + uiItemL(target, item->name, item->icon); + + but = (uiBut *)block->buttons.last; + } + else { + /* Do not use uiItemL here, as our root layout is a menu one, + * it will add a fake blank icon! */ + but = uiDefBut(block, + UI_BTYPE_LABEL, + 0, + item->name, + 0, + 0, + UI_UNIT_X * 5, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + 0, + ""); + uiItemS(target); + } + ui_but_tip_from_enum_item(but, item); + } + else { + if (radial) { + /* invisible dummy button to ensure all items are + * always at the same position */ + uiItemS(target); + } + else { + /* XXX bug here, columns draw bottom item badly */ + uiItemS(target); + } + } + } + } +} + +void uiItemsFullEnumO(uiLayout *layout, + const char *opname, + const char *propname, + IDProperty *properties, + int context, + int flag) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + + if (!ot || !ot->srna) { + ui_item_disabled(layout, opname); + RNA_warning("%s '%s'", ot ? "unknown operator" : "operator missing srna", opname); + return; + } + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, 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, propname); + + /* don't let bad properties slip through */ + BLI_assert((prop == NULL) || (RNA_property_type(prop) == PROP_ENUM)); + + uiBlock *block = layout->root->block; + if (prop && RNA_property_type(prop) == PROP_ENUM) { + const EnumPropertyItem *item_array = NULL; + int totitem; + bool free; + + if (ui_layout_is_radial(layout)) { + /* XXX: While "_all()" guarantees spatial stability, + * it's bad when an enum has > 8 items total, + * but only a small subset will ever be shown at once + * (e.g. Mode Switch menu, after the introduction of GP editing modes). + */ +#if 0 + RNA_property_enum_items_gettexted_all( + (bContext *)block->evil_C, &ptr, prop, &item_array, &totitem, &free); +#else + RNA_property_enum_items_gettexted( + (bContext *)block->evil_C, &ptr, prop, &item_array, &totitem, &free); +#endif + } + else { + RNA_property_enum_items_gettexted( + (bContext *)block->evil_C, &ptr, prop, &item_array, &totitem, &free); + } + + /* add items */ + uiItemsFullEnumO_items(layout, ot, ptr, prop, properties, context, flag, item_array, totitem); + + if (free) { + MEM_freeN((void *)item_array); + } + + /* intentionally don't touch UI_BLOCK_IS_FLIP here, + * we don't know the context this is called in */ + } + else if (prop && RNA_property_type(prop) != PROP_ENUM) { + RNA_warning("%s.%s, not an enum type", RNA_struct_identifier(ptr.type), propname); + return; + } + else { + RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); + return; + } +} + +void uiItemsEnumO(uiLayout *layout, const char *opname, const char *propname) +{ + uiItemsFullEnumO(layout, opname, propname, NULL, layout->root->opcontext, 0); +} + +/* for use in cases where we have */ +void uiItemEnumO_value(uiLayout *layout, + const char *name, + int icon, + const char *opname, + const char *propname, + int value) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + + /* enum lookup */ + PropertyRNA *prop = RNA_struct_find_property(&ptr, propname); + if (prop == NULL) { + RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); + return; + } + + RNA_property_enum_set(&ptr, prop, value); + + /* same as uiItemEnumO */ + if (!name) { + name = ui_menu_enumpropname(layout, &ptr, prop, value); + } + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} + +void uiItemEnumO_string(uiLayout *layout, + const char *name, + int icon, + const char *opname, + const char *propname, + const char *value_str) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + + PropertyRNA *prop = RNA_struct_find_property(&ptr, propname); + if (prop == NULL) { + RNA_warning("%s.%s not found", RNA_struct_identifier(ptr.type), propname); + return; + } + + /* enum lookup */ + /* no need for translations here */ + const EnumPropertyItem *item; + bool free; + RNA_property_enum_items((bContext *)layout->root->block->evil_C, &ptr, prop, &item, NULL, &free); + + int value; + if (item == NULL || RNA_enum_value_from_id(item, value_str, &value) == 0) { + if (free) { + MEM_freeN((void *)item); + } + RNA_warning("%s.%s, enum %s not found", RNA_struct_identifier(ptr.type), propname, value_str); + return; + } + + if (free) { + MEM_freeN((void *)item); + } + + RNA_property_enum_set(&ptr, prop, value); + + /* same as uiItemEnumO */ + if (!name) { + name = ui_menu_enumpropname(layout, &ptr, prop, value); + } + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} + +void uiItemBooleanO(uiLayout *layout, + const char *name, + int icon, + const char *opname, + const char *propname, + int value) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + RNA_boolean_set(&ptr, propname, value); + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} + +void uiItemIntO(uiLayout *layout, + const char *name, + int icon, + const char *opname, + const char *propname, + int value) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + RNA_int_set(&ptr, propname, value); + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} + +void uiItemFloatO(uiLayout *layout, + const char *name, + int icon, + const char *opname, + const char *propname, + float value) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + RNA_float_set(&ptr, propname, value); + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} + +void uiItemStringO(uiLayout *layout, + const char *name, + int icon, + const char *opname, + const char *propname, + const char *value) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + PointerRNA ptr; + WM_operator_properties_create_ptr(&ptr, ot); + RNA_string_set(&ptr, propname, value); + + uiItemFullO_ptr( + layout, ot, name, icon, (IDProperty *)ptr.data, layout->root->opcontext, 0, NULL); +} + +void uiItemO(uiLayout *layout, const char *name, int icon, const char *opname) +{ + uiItemFullO(layout, opname, name, icon, NULL, layout->root->opcontext, 0, NULL); +} + +/* RNA property items */ + +static void ui_item_rna_size(uiLayout *layout, + const char *name, + int icon, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + bool icon_only, + bool compact, + int *r_w, + int *r_h) +{ + int w = 0, h; + + /* arbitrary extended width by type */ + const PropertyType type = RNA_property_type(prop); + const PropertySubType subtype = RNA_property_subtype(prop); + const int len = RNA_property_array_length(ptr, prop); + + bool is_checkbox_only = false; + if (!name[0] && !icon_only) { + if (ELEM(type, PROP_STRING, PROP_POINTER)) { + name = "non-empty text"; + } + else if (type == PROP_BOOLEAN) { + if (icon == ICON_NONE) { + /* Exception for checkboxes, they need a little less space to align nicely. */ + is_checkbox_only = true; + } + icon = ICON_DOT; + } + else if (type == PROP_ENUM) { + /* Find the longest enum item name, instead of using a dummy text! */ + const EnumPropertyItem *item_array; + bool free; + RNA_property_enum_items_gettexted( + (bContext *)layout->root->block->evil_C, ptr, prop, &item_array, NULL, &free); + + for (const EnumPropertyItem *item = item_array; item->identifier; item++) { + if (item->identifier[0]) { + w = max_ii(w, ui_text_icon_width(layout, item->name, item->icon, compact)); + } + } + if (free) { + MEM_freeN((void *)item_array); + } + } + } + + if (!w) { + if (type == PROP_ENUM && icon_only) { + w = ui_text_icon_width(layout, "", ICON_BLANK1, compact); + if (index != RNA_ENUM_VALUE) { + w += 0.6f * UI_UNIT_X; + } + } + else { + /* not compact for float/int buttons, looks too squashed */ + w = ui_text_icon_width( + layout, name, icon, ELEM(type, PROP_FLOAT, PROP_INT) ? false : compact); + } + } + h = UI_UNIT_Y; + + /* increase height for arrays */ + if (index == RNA_NO_INDEX && len > 0) { + if (!name[0] && icon == ICON_NONE) { + h = 0; + } + if (layout->item.flag & UI_ITEM_PROP_SEP) { + h = 0; + } + if (ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER)) { + h += 2 * UI_UNIT_Y; + } + else if (subtype == PROP_MATRIX) { + h += ceilf(sqrtf(len)) * UI_UNIT_Y; + } + else { + h += len * UI_UNIT_Y; + } + } + + /* Increase width requirement if in a variable size layout. */ + if (ui_layout_variable_size(layout)) { + if (type == PROP_BOOLEAN && name[0]) { + w += UI_UNIT_X / 5; + } + else if (is_checkbox_only) { + w -= UI_UNIT_X / 4; + } + else if (type == PROP_ENUM && !icon_only) { + w += UI_UNIT_X / 4; + } + else if (ELEM(type, PROP_FLOAT, PROP_INT)) { + w += UI_UNIT_X * 3; + } + } + + *r_w = w; + *r_h = h; +} + +static bool ui_item_rna_is_expand(PropertyRNA *prop, int index, int item_flag) +{ + const bool is_array = RNA_property_array_check(prop); + const int subtype = RNA_property_subtype(prop); + return is_array && (index == RNA_NO_INDEX) && + ((item_flag & UI_ITEM_R_EXPAND) || + !ELEM(subtype, PROP_COLOR, PROP_COLOR_GAMMA, PROP_DIRECTION)); +} + +/** + * Find first layout ancestor (or self) with a heading set. + * + * \returns the layout to add the heading to as fallback (i.e. if it can't be placed in a split + * layout). Its #uiLayout.heading member can be cleared to mark the heading as added (so + * it's not added multiple times). Returns a pointer to the heading + */ +static uiLayout *ui_layout_heading_find(uiLayout *cur_layout) +{ + for (uiLayout *parent = cur_layout; parent; parent = parent->parent) { + if (parent->heading[0]) { + return parent; + } + } + + return NULL; +} + +static void ui_layout_heading_label_add(uiLayout *layout, + uiLayout *heading_layout, + bool right_align, + bool respect_prop_split) +{ + const int prev_alignment = layout->alignment; + + if (right_align) { + uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_RIGHT); + } + + if (respect_prop_split) { + uiItemL_respect_property_split(layout, heading_layout->heading, ICON_NONE); + } + else { + uiItemL(layout, heading_layout->heading, ICON_NONE); + } + /* After adding the heading label, we have to mark it somehow as added, so it's not added again + * for other items in this layout. For now just clear it. */ + heading_layout->heading[0] = '\0'; + + layout->alignment = prev_alignment; +} + +/** + * Hack to add further items in a row into the second part of the split layout, so the label part + * keeps a fixed size. + * \return The layout to place further items in for the split layout. + */ +static uiLayout *ui_item_prop_split_layout_hack(uiLayout *layout_parent, uiLayout *layout_split) +{ + /* Tag item as using property split layout, this is inherited to children so they can get special + * treatment if needed. */ + layout_parent->item.flag |= UI_ITEM_INSIDE_PROP_SEP; + + if (layout_parent->item.type == ITEM_LAYOUT_ROW) { + /* Prevent further splits within the row. */ + uiLayoutSetPropSep(layout_parent, false); + + layout_parent->child_items_layout = uiLayoutRow(layout_split, true); + return layout_parent->child_items_layout; + } + return layout_split; +} + +void uiItemFullR(uiLayout *layout, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + int value, + int flag, + const char *name, + int icon) +{ + uiBlock *block = layout->root->block; + char namestr[UI_MAX_NAME_STR]; + const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0); + const bool inside_prop_sep = ((layout->item.flag & UI_ITEM_INSIDE_PROP_SEP) != 0); + /* Columns can define a heading to insert. If the first item added to a split layout doesn't have + * a label to display in the first column, the heading is inserted there. Otherwise it's inserted + * as a new row before the first item. */ + uiLayout *heading_layout = ui_layout_heading_find(layout); + /* Although checkboxes use the split layout, they are an exception and should only place their + * label in the second column, to not make that almost empty. + * + * Keep using 'use_prop_sep' instead of disabling it entirely because + * we need the ability to have decorators still. */ + bool use_prop_sep_split_label = use_prop_sep; + bool use_split_empty_name = (flag & UI_ITEM_R_SPLIT_EMPTY_NAME); + +#ifdef UI_PROP_DECORATE + struct { + bool use_prop_decorate; + int len; + uiLayout *layout; + uiBut *but; + } ui_decorate = { + .use_prop_decorate = (((layout->item.flag & UI_ITEM_PROP_DECORATE) != 0) && use_prop_sep), + }; +#endif /* UI_PROP_DECORATE */ + + UI_block_layout_set_current(block, layout); + ui_block_new_button_group(block); + + /* retrieve info */ + const PropertyType type = RNA_property_type(prop); + const bool is_array = RNA_property_array_check(prop); + const int len = (is_array) ? RNA_property_array_length(ptr, prop) : 0; + + const bool icon_only = (flag & UI_ITEM_R_ICON_ONLY) != 0; + + /* Boolean with -1 to signify that the value depends on the presence of an icon. */ + const int toggle = ((flag & UI_ITEM_R_TOGGLE) ? 1 : ((flag & UI_ITEM_R_ICON_NEVER) ? 0 : -1)); + const bool no_icon = (toggle == 0); + + /* set name and icon */ + if (!name) { + if (!icon_only) { + name = RNA_property_ui_name(prop); + } + else { + name = ""; + } + } + + if (type != PROP_BOOLEAN) { + flag &= ~UI_ITEM_R_CHECKBOX_INVERT; + } + + if (flag & UI_ITEM_R_ICON_ONLY) { + /* pass */ + } + else if (ELEM(type, PROP_INT, PROP_FLOAT, PROP_STRING, PROP_POINTER)) { + if (use_prop_sep == false) { + name = ui_item_name_add_colon(name, namestr); + } + } + else if (type == PROP_BOOLEAN && is_array && index == RNA_NO_INDEX) { + if (use_prop_sep == false) { + name = ui_item_name_add_colon(name, namestr); + } + } + else if (type == PROP_ENUM && index != RNA_ENUM_VALUE) { + if (flag & UI_ITEM_R_COMPACT) { + name = ""; + } + else { + if (use_prop_sep == false) { + name = ui_item_name_add_colon(name, namestr); + } + } + } + + if (no_icon == false) { + if (icon == ICON_NONE) { + icon = RNA_property_ui_icon(prop); + } + + /* Menus and pie-menus don't show checkbox without this. */ + if ((layout->root->type == UI_LAYOUT_MENU) || + /* Use checkboxes only as a fallback in pie-menu's, when no icon is defined. */ + ((layout->root->type == UI_LAYOUT_PIEMENU) && (icon == ICON_NONE))) { + const int prop_flag = RNA_property_flag(prop); + if (type == PROP_BOOLEAN) { + if ((is_array == false) || (index != RNA_NO_INDEX)) { + if (prop_flag & PROP_ICONS_CONSECUTIVE) { + icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */ + } + else if (is_array) { + icon = (RNA_property_boolean_get_index(ptr, prop, index)) ? ICON_CHECKBOX_HLT : + ICON_CHECKBOX_DEHLT; + } + else { + icon = (RNA_property_boolean_get(ptr, prop)) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; + } + } + } + else if (type == PROP_ENUM) { + if (index == RNA_ENUM_VALUE) { + const int enum_value = RNA_property_enum_get(ptr, prop); + if (prop_flag & PROP_ICONS_CONSECUTIVE) { + icon = ICON_CHECKBOX_DEHLT; /* but->iconadd will set to correct icon */ + } + else if (prop_flag & PROP_ENUM_FLAG) { + icon = (enum_value & value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; + } + else { + icon = (enum_value == value) ? ICON_CHECKBOX_HLT : ICON_CHECKBOX_DEHLT; + } + } + } + } + } + +#ifdef UI_PROP_SEP_ICON_WIDTH_EXCEPTION + if (use_prop_sep) { + if (type == PROP_BOOLEAN && (icon == ICON_NONE) && !icon_only) { + use_prop_sep_split_label = false; + /* For check-boxes we make an exception: We allow showing them in a split row even without + * label. It typically relates to its neighbor items, so no need for an extra label. */ + use_split_empty_name = true; + } + } +#endif + + if ((type == PROP_ENUM) && (RNA_property_flag(prop) & PROP_ENUM_FLAG)) { + flag |= UI_ITEM_R_EXPAND; + } + + const bool slider = (flag & UI_ITEM_R_SLIDER) != 0; + const bool expand = (flag & UI_ITEM_R_EXPAND) != 0; + const bool no_bg = (flag & UI_ITEM_R_NO_BG) != 0; + const bool compact = (flag & UI_ITEM_R_COMPACT) != 0; + + /* get size */ + int w, h; + ui_item_rna_size(layout, name, icon, ptr, prop, index, icon_only, compact, &w, &h); + + const eUIEmbossType prev_emboss = layout->emboss; + if (no_bg) { + layout->emboss = UI_EMBOSS_NONE; + } + + uiBut *but = NULL; + + /* Split the label / property. */ + uiLayout *layout_parent = layout; + + if (use_prop_sep) { + uiLayout *layout_row = NULL; +#ifdef UI_PROP_DECORATE + if (ui_decorate.use_prop_decorate) { + layout_row = uiLayoutRow(layout, true); + layout_row->space = 0; + ui_decorate.len = max_ii(1, len); + } +#endif /* UI_PROP_DECORATE */ + + if ((name[0] == '\0') && !use_split_empty_name) { + /* Ensure we get a column when text is not set. */ + layout = uiLayoutColumn(layout_row ? layout_row : layout, true); + layout->space = 0; + if (heading_layout) { + ui_layout_heading_label_add(layout, heading_layout, false, false); + } + } + else { + uiLayout *layout_split = uiLayoutSplit( + layout_row ? layout_row : layout, UI_ITEM_PROP_SEP_DIVIDE, true); + bool label_added = false; + uiLayout *layout_sub = uiLayoutColumn(layout_split, true); + layout_sub->space = 0; + + if (!use_prop_sep_split_label) { + /* Pass */ + } + else if (ui_item_rna_is_expand(prop, index, flag)) { + char name_with_suffix[UI_MAX_DRAW_STR + 2]; + char str[2] = {'\0'}; + for (int a = 0; a < len; a++) { + str[0] = RNA_property_array_item_char(prop, a); + const bool use_prefix = (a == 0 && name && name[0]); + if (use_prefix) { + char *s = name_with_suffix; + s += STRNCPY_RLEN(name_with_suffix, name); + *s++ = ' '; + *s++ = str[0]; + *s++ = '\0'; + } + but = uiDefBut(block, + UI_BTYPE_LABEL, + 0, + use_prefix ? name_with_suffix : str, + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + 0, + ""); + but->drawflag |= UI_BUT_TEXT_RIGHT; + but->drawflag &= ~UI_BUT_TEXT_LEFT; + + label_added = true; + } + } + else { + if (name) { + but = uiDefBut( + block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, ""); + but->drawflag |= UI_BUT_TEXT_RIGHT; + but->drawflag &= ~UI_BUT_TEXT_LEFT; + + label_added = true; + } + } + + if (!label_added && heading_layout) { + ui_layout_heading_label_add(layout_sub, heading_layout, true, false); + } + + layout_split = ui_item_prop_split_layout_hack(layout_parent, layout_split); + + /* Watch out! We can only write into the new layout now. */ + if ((type == PROP_ENUM) && (flag & UI_ITEM_R_EXPAND)) { + /* Expanded enums each have their own name. */ + + /* Often expanded enum's are better arranged into a row, + * so check the existing layout. */ + if (uiLayoutGetLocalDir(layout) == UI_LAYOUT_HORIZONTAL) { + layout = uiLayoutRow(layout_split, true); + } + else { + layout = uiLayoutColumn(layout_split, true); + } + } + else { + if (use_prop_sep_split_label) { + name = ""; + } + layout = uiLayoutColumn(layout_split, true); + } + layout->space = 0; + } + +#ifdef UI_PROP_DECORATE + if (ui_decorate.use_prop_decorate) { + ui_decorate.layout = uiLayoutColumn(layout_row, true); + ui_decorate.layout->space = 0; + UI_block_layout_set_current(block, layout); + ui_decorate.but = (uiBut *)block->buttons.last; + + /* Clear after. */ + layout->item.flag |= UI_ITEM_PROP_DECORATE_NO_PAD; + } +#endif /* UI_PROP_DECORATE */ + } + /* End split. */ + else if (heading_layout) { + /* Could not add heading to split layout, fallback to inserting it to the layout with the + * heading itself. */ + ui_layout_heading_label_add(heading_layout, heading_layout, false, false); + } + + /* array property */ + if (index == RNA_NO_INDEX && is_array) { + if (inside_prop_sep) { + /* Within a split row, add array items to a column so they match the column layout of + * previous items (e.g. transform vector with lock icon for each item). */ + layout = uiLayoutColumn(layout, true); + } + + ui_item_array(layout, + block, + name, + icon, + ptr, + prop, + len, + 0, + 0, + w, + h, + expand, + slider, + toggle, + icon_only, + compact, + !use_prop_sep_split_label); + } + /* enum item */ + else if (type == PROP_ENUM && index == RNA_ENUM_VALUE) { + if (icon && name[0] && !icon_only) { + uiDefIconTextButR_prop( + block, UI_BTYPE_ROW, 0, icon, name, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); + } + else if (icon) { + uiDefIconButR_prop( + block, UI_BTYPE_ROW, 0, icon, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); + } + else { + uiDefButR_prop( + block, UI_BTYPE_ROW, 0, name, 0, 0, w, h, ptr, prop, -1, 0, value, -1, -1, NULL); + } + } + /* expanded enum */ + else if (type == PROP_ENUM && expand) { + ui_item_enum_expand(layout, block, ptr, prop, name, h, icon_only); + } + /* property with separate label */ + else if (ELEM(type, PROP_ENUM, PROP_STRING, PROP_POINTER)) { + but = ui_item_with_label(layout, block, name, icon, ptr, prop, index, 0, 0, w, h, flag); + but = ui_but_add_search(but, ptr, prop, NULL, NULL); + + if (layout->redalert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + if (layout->activate_init) { + UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); + } + } + /* single button */ + else { + but = uiDefAutoButR(block, ptr, prop, index, name, icon, 0, 0, w, h); + + if (slider && but->type == UI_BTYPE_NUM) { + uiButNumber *num_but = (uiButNumber *)but; + + but->a1 = num_but->step_size; + but = ui_but_change_type(but, UI_BTYPE_NUM_SLIDER); + } + + if (flag & UI_ITEM_R_CHECKBOX_INVERT) { + if (ELEM(but->type, + UI_BTYPE_CHECKBOX, + UI_BTYPE_CHECKBOX_N, + UI_BTYPE_ICON_TOGGLE, + UI_BTYPE_ICON_TOGGLE_N)) { + but->drawflag |= UI_BUT_CHECKBOX_INVERT; + } + } + + if ((toggle == 1) && but->type == UI_BTYPE_CHECKBOX) { + but->type = UI_BTYPE_TOGGLE; + } + + if (layout->redalert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + if (layout->activate_init) { + UI_but_flag_enable(but, UI_BUT_ACTIVATE_ON_INIT); + } + } + + /* The resulting button may have the icon set since boolean button drawing + * is being 'helpful' and adding an icon for us. + * In this case we want the ability not to have an icon. + * + * We could pass an argument not to set the icon to begin with however this is the one case + * the functionality is needed. */ + if (but && no_icon) { + if ((icon == ICON_NONE) && (but->icon != ICON_NONE)) { + ui_def_but_icon_clear(but); + } + } + + /* Mark non-embossed textfields inside a listbox. */ + if (but && (block->flag & UI_BLOCK_LIST_ITEM) && (but->type == UI_BTYPE_TEXT) && + (but->emboss & UI_EMBOSS_NONE)) { + UI_but_flag_enable(but, UI_BUT_LIST_ITEM); + } + +#ifdef UI_PROP_DECORATE + if (ui_decorate.use_prop_decorate) { + uiBut *but_decorate = ui_decorate.but ? ui_decorate.but->next : (uiBut *)block->buttons.first; + const bool use_blank_decorator = (flag & UI_ITEM_R_FORCE_BLANK_DECORATE); + uiLayout *layout_col = uiLayoutColumn(ui_decorate.layout, false); + layout_col->space = 0; + layout_col->emboss = UI_EMBOSS_NONE; + + int i; + for (i = 0; i < ui_decorate.len && but_decorate; i++) { + PointerRNA *ptr_dec = use_blank_decorator ? NULL : &but_decorate->rnapoin; + PropertyRNA *prop_dec = use_blank_decorator ? NULL : but_decorate->rnaprop; + + /* The icons are set in 'ui_but_anim_flag' */ + uiItemDecoratorR_prop(layout_col, ptr_dec, prop_dec, but_decorate->rnaindex); + but = (uiBut *)block->buttons.last; + + /* Order the decorator after the button we decorate, this is used so we can always + * do a quick lookup. */ + BLI_remlink(&block->buttons, but); + BLI_insertlinkafter(&block->buttons, but_decorate, but); + but_decorate = but->next; + } + BLI_assert(ELEM(i, 1, ui_decorate.len)); + + layout->item.flag &= ~UI_ITEM_PROP_DECORATE_NO_PAD; + } +#endif /* UI_PROP_DECORATE */ + + if (no_bg) { + layout->emboss = prev_emboss; + } + + /* ensure text isn't added to icon_only buttons */ + if (but && icon_only) { + BLI_assert(but->str[0] == '\0'); + } +} + +void uiItemR( + uiLayout *layout, PointerRNA *ptr, const char *propname, int flag, const char *name, int icon) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop) { + ui_item_disabled(layout, propname); + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiItemFullR(layout, ptr, prop, RNA_NO_INDEX, 0, flag, name, icon); +} + +/** + * Use a wrapper function since re-implementing all the logic in this function would be messy. + */ +void uiItemFullR_with_popover(uiLayout *layout, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + int value, + int flag, + const char *name, + int icon, + const char *panel_type) +{ + uiBlock *block = layout->root->block; + uiBut *but = (uiBut *)block->buttons.last; + uiItemFullR(layout, ptr, prop, index, value, flag, name, icon); + but = but->next; + while (but) { + if (but->rnaprop == prop && ELEM(but->type, UI_BTYPE_MENU, UI_BTYPE_COLOR)) { + ui_but_rna_menu_convert_to_panel_type(but, panel_type); + break; + } + but = but->next; + } + if (but == NULL) { + const char *propname = RNA_property_identifier(prop); + ui_item_disabled(layout, panel_type); + RNA_warning("property could not use a popover: %s.%s (%s)", + RNA_struct_identifier(ptr->type), + propname, + panel_type); + } +} + +void uiItemFullR_with_menu(uiLayout *layout, + PointerRNA *ptr, + PropertyRNA *prop, + int index, + int value, + int flag, + const char *name, + int icon, + const char *menu_type) +{ + uiBlock *block = layout->root->block; + uiBut *but = (uiBut *)block->buttons.last; + uiItemFullR(layout, ptr, prop, index, value, flag, name, icon); + but = but->next; + while (but) { + if (but->rnaprop == prop && but->type == UI_BTYPE_MENU) { + ui_but_rna_menu_convert_to_menu_type(but, menu_type); + break; + } + but = but->next; + } + if (but == NULL) { + const char *propname = RNA_property_identifier(prop); + ui_item_disabled(layout, menu_type); + RNA_warning("property could not use a menu: %s.%s (%s)", + RNA_struct_identifier(ptr->type), + propname, + menu_type); + } +} + +void uiItemEnumR_prop(uiLayout *layout, + const char *name, + int icon, + struct PointerRNA *ptr, + PropertyRNA *prop, + int value) +{ + if (RNA_property_type(prop) != PROP_ENUM) { + const char *propname = RNA_property_identifier(prop); + ui_item_disabled(layout, propname); + RNA_warning("property not an enum: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiItemFullR(layout, ptr, prop, RNA_ENUM_VALUE, value, 0, name, icon); +} + +void uiItemEnumR(uiLayout *layout, + const char *name, + int icon, + struct PointerRNA *ptr, + const char *propname, + int value) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (prop == NULL) { + ui_item_disabled(layout, propname); + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiItemFullR(layout, ptr, prop, RNA_ENUM_VALUE, value, 0, name, icon); +} + +void uiItemEnumR_string_prop(uiLayout *layout, + struct PointerRNA *ptr, + PropertyRNA *prop, + const char *value, + const char *name, + int icon) +{ + if (UNLIKELY(RNA_property_type(prop) != PROP_ENUM)) { + const char *propname = RNA_property_identifier(prop); + ui_item_disabled(layout, propname); + RNA_warning("not an enum property: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + const EnumPropertyItem *item; + bool free; + RNA_property_enum_items((bContext *)layout->root->block->evil_C, ptr, prop, &item, NULL, &free); + + int ivalue; + if (!RNA_enum_value_from_id(item, value, &ivalue)) { + const char *propname = RNA_property_identifier(prop); + if (free) { + MEM_freeN((void *)item); + } + ui_item_disabled(layout, propname); + RNA_warning("enum property value not found: %s", value); + return; + } + + for (int a = 0; item[a].identifier; a++) { + if (item[a].identifier[0] == '\0') { + /* Skip enum item separators. */ + continue; + } + if (item[a].value == ivalue) { + const char *item_name = name ? + name : + CTX_IFACE_(RNA_property_translation_context(prop), item[a].name); + const int flag = item_name[0] ? 0 : UI_ITEM_R_ICON_ONLY; + + uiItemFullR( + layout, ptr, prop, RNA_ENUM_VALUE, ivalue, flag, item_name, icon ? icon : item[a].icon); + break; + } + } + + if (free) { + MEM_freeN((void *)item); + } +} + +void uiItemEnumR_string(uiLayout *layout, + struct PointerRNA *ptr, + const char *propname, + const char *value, + const char *name, + int icon) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + if (UNLIKELY(prop == NULL)) { + ui_item_disabled(layout, propname); + RNA_warning("enum property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + uiItemEnumR_string_prop(layout, ptr, prop, value, name, icon); +} + +void uiItemsEnumR(uiLayout *layout, struct PointerRNA *ptr, const char *propname) +{ + uiBlock *block = layout->root->block; + + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop) { + ui_item_disabled(layout, propname); + RNA_warning("enum property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + if (RNA_property_type(prop) != PROP_ENUM) { + RNA_warning("not an enum property: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiLayout *split = uiLayoutSplit(layout, 0.0f, false); + uiLayout *column = uiLayoutColumn(split, false); + + int totitem; + const EnumPropertyItem *item; + bool free; + RNA_property_enum_items_gettexted((bContext *)block->evil_C, ptr, prop, &item, &totitem, &free); + + for (int i = 0; i < totitem; i++) { + if (item[i].identifier[0]) { + uiItemEnumR_prop(column, item[i].name, item[i].icon, ptr, prop, item[i].value); + ui_but_tip_from_enum_item((uiBut *)block->buttons.last, &item[i]); + } + else { + if (item[i].name) { + if (i != 0) { + column = uiLayoutColumn(split, false); + /* inconsistent, but menus with labels do not look good flipped */ + block->flag |= UI_BLOCK_NO_FLIP; + } + + uiItemL(column, item[i].name, ICON_NONE); + uiBut *bt = (uiBut *)block->buttons.last; + bt->drawflag = UI_BUT_TEXT_LEFT; + + ui_but_tip_from_enum_item(bt, &item[i]); + } + else { + uiItemS(column); + } + } + } + + if (free) { + MEM_freeN((void *)item); + } + + /* intentionally don't touch UI_BLOCK_IS_FLIP here, + * we don't know the context this is called in */ +} + +/* Pointer RNA button with search */ + +static void search_id_collection(StructRNA *ptype, PointerRNA *r_ptr, PropertyRNA **r_prop) +{ + /* look for collection property in Main */ + /* NOTE: using global Main is OK-ish here, UI shall not access other Mains anyway. */ + RNA_main_pointer_create(G_MAIN, r_ptr); + + *r_prop = NULL; + + RNA_STRUCT_BEGIN (r_ptr, iprop) { + /* if it's a collection and has same pointer type, we've got it */ + if (RNA_property_type(iprop) == PROP_COLLECTION) { + StructRNA *srna = RNA_property_pointer_type(r_ptr, iprop); + + if (ptype == srna) { + *r_prop = iprop; + break; + } + } + } + RNA_STRUCT_END; +} + +static void ui_rna_collection_search_arg_free_fn(void *ptr) +{ + uiRNACollectionSearch *coll_search = (uiRNACollectionSearch *)ptr; + UI_butstore_free(coll_search->butstore_block, coll_search->butstore); + MEM_freeN(ptr); +} + +/** + * \note May reallocate \a but, so the possibly new address is returned. + */ +uiBut *ui_but_add_search( + uiBut *but, PointerRNA *ptr, PropertyRNA *prop, PointerRNA *searchptr, PropertyRNA *searchprop) +{ + /* for ID's we do automatic lookup */ + PointerRNA sptr; + if (!searchprop) { + if (RNA_property_type(prop) == PROP_POINTER) { + StructRNA *ptype = RNA_property_pointer_type(ptr, prop); + search_id_collection(ptype, &sptr, &searchprop); + searchptr = &sptr; + } + } + + /* turn button into search button */ + if (searchprop) { + uiRNACollectionSearch *coll_search = (uiRNACollectionSearch *)MEM_mallocN(sizeof(*coll_search), + __func__); + uiButSearch *search_but; + + but = ui_but_change_type(but, UI_BTYPE_SEARCH_MENU); + search_but = (uiButSearch *)but; + search_but->rnasearchpoin = *searchptr; + search_but->rnasearchprop = searchprop; + but->hardmax = MAX2(but->hardmax, 256.0f); + but->drawflag |= UI_BUT_ICON_LEFT | UI_BUT_TEXT_LEFT; + if (RNA_property_is_unlink(prop)) { + but->flag |= UI_BUT_VALUE_CLEAR; + } + + coll_search->target_ptr = *ptr; + coll_search->target_prop = prop; + coll_search->search_ptr = *searchptr; + coll_search->search_prop = searchprop; + coll_search->search_but = but; + coll_search->butstore_block = but->block; + coll_search->butstore = UI_butstore_create(coll_search->butstore_block); + UI_butstore_register(coll_search->butstore, &coll_search->search_but); + + if (RNA_property_type(prop) == PROP_ENUM) { + /* XXX, this will have a menu string, + * but in this case we just want the text */ + but->str[0] = 0; + } + + UI_but_func_search_set(but, + ui_searchbox_create_generic, + ui_rna_collection_search_update_fn, + coll_search, + false, + ui_rna_collection_search_arg_free_fn, + NULL, + NULL); + } + else if (but->type == UI_BTYPE_SEARCH_MENU) { + /* In case we fail to find proper searchprop, + * so other code might have already set but->type to search menu... */ + but->flag |= UI_BUT_DISABLED; + } + + return but; +} + +void uiItemPointerR_prop(uiLayout *layout, + PointerRNA *ptr, + PropertyRNA *prop, + PointerRNA *searchptr, + PropertyRNA *searchprop, + const char *name, + int icon) +{ + const bool use_prop_sep = ((layout->item.flag & UI_ITEM_PROP_SEP) != 0); + + ui_block_new_button_group(uiLayoutGetBlock(layout)); + + const PropertyType type = RNA_property_type(prop); + if (!ELEM(type, PROP_POINTER, PROP_STRING, PROP_ENUM)) { + RNA_warning("Property %s.%s must be a pointer, string or enum", + RNA_struct_identifier(ptr->type), + RNA_property_identifier(prop)); + return; + } + if (RNA_property_type(searchprop) != PROP_COLLECTION) { + RNA_warning("search collection property is not a collection type: %s.%s", + RNA_struct_identifier(searchptr->type), + RNA_property_identifier(searchprop)); + return; + } + + /* get icon & name */ + if (icon == ICON_NONE) { + StructRNA *icontype; + if (type == PROP_POINTER) { + icontype = RNA_property_pointer_type(ptr, prop); + } + else { + icontype = RNA_property_pointer_type(searchptr, searchprop); + } + + icon = RNA_struct_ui_icon(icontype); + } + if (!name) { + name = RNA_property_ui_name(prop); + } + + char namestr[UI_MAX_NAME_STR]; + if (use_prop_sep == false) { + name = ui_item_name_add_colon(name, namestr); + } + + /* create button */ + uiBlock *block = uiLayoutGetBlock(layout); + + int w, h; + ui_item_rna_size(layout, name, icon, ptr, prop, 0, 0, false, &w, &h); + w += UI_UNIT_X; /* X icon needs more space */ + uiBut *but = ui_item_with_label(layout, block, name, icon, ptr, prop, 0, 0, 0, w, h, 0); + + ui_but_add_search(but, ptr, prop, searchptr, searchprop); +} + +void uiItemPointerR(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + PointerRNA *searchptr, + const char *searchpropname, + const char *name, + int icon) +{ + /* validate arguments */ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + if (!prop) { + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + PropertyRNA *searchprop = RNA_struct_find_property(searchptr, searchpropname); + if (!searchprop) { + RNA_warning("search collection property not found: %s.%s", + RNA_struct_identifier(searchptr->type), + searchpropname); + return; + } + + uiItemPointerR_prop(layout, ptr, prop, searchptr, searchprop, name, icon); +} + +/* menu item */ +void ui_item_menutype_func(bContext *C, uiLayout *layout, void *arg_mt) +{ + MenuType *mt = (MenuType *)arg_mt; + + UI_menutype_draw(C, mt, layout); + + /* Menus are created flipped (from event handling point of view). */ + layout->root->block->flag ^= UI_BLOCK_IS_FLIP; +} + +void ui_item_paneltype_func(bContext *C, uiLayout *layout, void *arg_pt) +{ + PanelType *pt = (PanelType *)arg_pt; + UI_paneltype_draw(C, pt, layout); + + /* panels are created flipped (from event handling pov) */ + layout->root->block->flag ^= UI_BLOCK_IS_FLIP; +} + +static uiBut *ui_item_menu(uiLayout *layout, + const char *name, + int icon, + uiMenuCreateFunc func, + void *arg, + void *argN, + const char *tip, + bool force_menu) +{ + uiBlock *block = layout->root->block; + uiLayout *heading_layout = ui_layout_heading_find(layout); + + UI_block_layout_set_current(block, layout); + ui_block_new_button_group(block); + + if (!name) { + name = ""; + } + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + int w = ui_text_icon_width(layout, name, icon, 1); + const int h = UI_UNIT_Y; + + if (layout->root->type == UI_LAYOUT_HEADER) { /* ugly .. */ + if (icon == ICON_NONE && force_menu) { + /* pass */ + } + else if (force_menu) { + w += 0.6f * UI_UNIT_X; + } + else { + if (name[0]) { + w -= UI_UNIT_X / 2; + } + } + } + + if (heading_layout) { + ui_layout_heading_label_add(layout, heading_layout, true, true); + } + + uiBut *but; + if (name[0] && icon) { + but = uiDefIconTextMenuBut(block, func, arg, icon, name, 0, 0, w, h, tip); + } + else if (icon) { + but = uiDefIconMenuBut(block, func, arg, icon, 0, 0, w, h, tip); + if (force_menu && name[0]) { + UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); + } + } + else { + but = uiDefMenuBut(block, func, arg, name, 0, 0, w, h, tip); + } + + if (argN) { + /* ugly .. */ + if (arg != argN) { + but->poin = (char *)but; + } + but->func_argN = argN; + } + + if (ELEM(layout->root->type, UI_LAYOUT_PANEL, UI_LAYOUT_TOOLBAR) || + /* We never want a drop-down in menu! */ + (force_menu && layout->root->type != UI_LAYOUT_MENU)) { + UI_but_type_set_menu_from_pulldown(but); + } + + return but; +} + +void uiItemM_ptr(uiLayout *layout, MenuType *mt, const char *name, int icon) +{ + if (!name) { + name = CTX_IFACE_(mt->translation_context, mt->label); + } + + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + ui_item_menu(layout, + name, + icon, + ui_item_menutype_func, + mt, + NULL, + mt->description ? TIP_(mt->description) : "", + false); +} + +void uiItemM(uiLayout *layout, const char *menuname, const char *name, int icon) +{ + MenuType *mt = WM_menutype_find(menuname, false); + if (mt == NULL) { + RNA_warning("not found %s", menuname); + return; + } + uiItemM_ptr(layout, mt, name, icon); +} + +void uiItemMContents(uiLayout *layout, const char *menuname) +{ + MenuType *mt = WM_menutype_find(menuname, false); + if (mt == NULL) { + RNA_warning("not found %s", menuname); + return; + } + + uiBlock *block = layout->root->block; + UI_menutype_draw((bContext *)block->evil_C, mt, layout); +} + +/** + * Insert a decorator item for a button with the same property as \a prop. + * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a prop. + */ +void uiItemDecoratorR_prop(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop, int index) +{ + uiBlock *block = layout->root->block; + + UI_block_layout_set_current(block, layout); + uiLayout *col = uiLayoutColumn(layout, false); + col->space = 0; + col->emboss = UI_EMBOSS_NONE; + + if (ELEM(NULL, ptr, prop) || !RNA_property_animateable(ptr, prop)) { + uiBut *but = uiDefIconBut(block, + UI_BTYPE_DECORATOR, + 0, + ICON_BLANK1, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + ""); + but->flag |= UI_BUT_DISABLED; + return; + } + + const bool is_expand = ui_item_rna_is_expand(prop, index, 0); + const bool is_array = RNA_property_array_check(prop); + + /* Loop for the array-case, but only do in case of an expanded array. */ + for (int i = 0; i < (is_expand ? RNA_property_array_length(ptr, prop) : 1); i++) { + uiButDecorator *decorator_but = (uiButDecorator *)uiDefIconBut(block, + UI_BTYPE_DECORATOR, + 0, + ICON_DOT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Animate property")); + + UI_but_func_set(&decorator_but->but, ui_but_anim_decorate_cb, decorator_but, NULL); + decorator_but->but.flag |= UI_BUT_UNDO | UI_BUT_DRAG_LOCK; + /* Reusing RNA search members, setting actual RNA data has many side-effects. */ + decorator_but->rnapoin = *ptr; + decorator_but->rnaprop = prop; + /* ui_def_but_rna() sets non-array buttons to have a RNA index of 0. */ + decorator_but->rnaindex = (!is_array || is_expand) ? i : index; + } +} + +/** + * Insert a decorator item for a button with the same property as \a prop. + * To force inserting a blank dummy element, NULL can be passed for \a ptr and \a propname. + */ +void uiItemDecoratorR(uiLayout *layout, PointerRNA *ptr, const char *propname, int index) +{ + PropertyRNA *prop = NULL; + + if (ptr && propname) { + /* validate arguments */ + prop = RNA_struct_find_property(ptr, propname); + if (!prop) { + ui_item_disabled(layout, propname); + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + } + + /* ptr and prop are allowed to be NULL here. */ + uiItemDecoratorR_prop(layout, ptr, prop, index); +} + +/* popover */ +void uiItemPopoverPanel_ptr( + uiLayout *layout, const bContext *C, PanelType *pt, const char *name, int icon) +{ + if (!name) { + name = CTX_IFACE_(pt->translation_context, pt->label); + } + + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + const bool ok = (pt->poll == NULL) || pt->poll(C, pt); + if (ok && (pt->draw_header != NULL)) { + layout = uiLayoutRow(layout, true); + Panel panel = { + .type = pt, + .layout = layout, + .flag = PNL_POPOVER, + }; + pt->draw_header(C, &panel); + } + uiBut *but = ui_item_menu( + layout, name, icon, ui_item_paneltype_func, pt, NULL, pt->description, true); + but->type = UI_BTYPE_POPOVER; + if (!ok) { + but->flag |= UI_BUT_DISABLED; + } +} + +void uiItemPopoverPanel( + uiLayout *layout, const bContext *C, const char *panel_type, const char *name, int icon) +{ + PanelType *pt = WM_paneltype_find(panel_type, true); + if (pt == NULL) { + RNA_warning("Panel type not found '%s'", panel_type); + return; + } + uiItemPopoverPanel_ptr(layout, C, pt, name, icon); +} + +void uiItemPopoverPanelFromGroup(uiLayout *layout, + bContext *C, + int space_id, + int region_id, + const char *context, + const char *category) +{ + SpaceType *st = BKE_spacetype_from_id(space_id); + if (st == NULL) { + RNA_warning("space type not found %d", space_id); + return; + } + ARegionType *art = BKE_regiontype_from_id(st, region_id); + if (art == NULL) { + RNA_warning("region type not found %d", region_id); + return; + } + + LISTBASE_FOREACH (PanelType *, pt, &art->paneltypes) { + /* Causes too many panels, check context. */ + if (pt->parent_id[0] == '\0') { + if (/* (*context == '\0') || */ STREQ(pt->context, context)) { + if ((*category == '\0') || STREQ(pt->category, category)) { + if (pt->poll == NULL || pt->poll(C, pt)) { + uiItemPopoverPanel_ptr(layout, C, pt, NULL, ICON_NONE); + } + } + } + } + } +} + +/* label item */ +static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon) +{ + uiBlock *block = layout->root->block; + + UI_block_layout_set_current(block, layout); + ui_block_new_button_group(block); + + if (!name) { + name = ""; + } + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + const int w = ui_text_icon_width(layout, name, icon, 0); + + uiBut *but; + if (icon && name[0]) { + but = uiDefIconTextBut( + block, UI_BTYPE_LABEL, 0, icon, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL); + } + else if (icon) { + but = uiDefIconBut( + block, UI_BTYPE_LABEL, 0, icon, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL); + } + else { + but = uiDefBut(block, UI_BTYPE_LABEL, 0, name, 0, 0, w, UI_UNIT_Y, NULL, 0.0, 0.0, 0, 0, NULL); + } + + /* to compensate for string size padding in ui_text_icon_width, + * make text aligned right if the layout is aligned right. + */ + if (uiLayoutGetAlignment(layout) == UI_LAYOUT_ALIGN_RIGHT) { + but->drawflag &= ~UI_BUT_TEXT_LEFT; /* default, needs to be unset */ + but->drawflag |= UI_BUT_TEXT_RIGHT; + } + + /* Mark as a label inside a listbox. */ + if (block->flag & UI_BLOCK_LIST_ITEM) { + but->flag |= UI_BUT_LIST_ITEM; + } + + if (layout->redalert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + return but; +} + +void uiItemL_ex( + uiLayout *layout, const char *name, int icon, const bool highlight, const bool redalert) +{ + uiBut *but = uiItemL_(layout, name, icon); + + if (highlight) { + /* TODO: add another flag for this. */ + UI_but_flag_enable(but, UI_SELECT_DRAW); + } + + if (redalert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } +} + +void uiItemL(uiLayout *layout, const char *name, int icon) +{ + uiItemL_(layout, name, icon); +} + +/** + * Normally, we handle the split layout in #uiItemFullR(), but there are other cases where the + * logic is needed. Ideally, #uiItemFullR() could just call this, but it currently has too many + * special needs. + */ +uiPropertySplitWrapper uiItemPropertySplitWrapperCreate(uiLayout *parent_layout) +{ + uiPropertySplitWrapper split_wrapper = {NULL}; + + uiLayout *layout_row = uiLayoutRow(parent_layout, true); + uiLayout *layout_split = uiLayoutSplit(layout_row, UI_ITEM_PROP_SEP_DIVIDE, true); + + split_wrapper.label_column = uiLayoutColumn(layout_split, true); + split_wrapper.label_column->alignment = UI_LAYOUT_ALIGN_RIGHT; + split_wrapper.property_row = ui_item_prop_split_layout_hack(parent_layout, layout_split); + split_wrapper.decorate_column = uiLayoutColumn(layout_row, true); + + return split_wrapper; +} + +/* + * Helper to add a label and creates a property split layout if needed. + */ +uiLayout *uiItemL_respect_property_split(uiLayout *layout, const char *text, int icon) +{ + if (layout->item.flag & UI_ITEM_PROP_SEP) { + uiBlock *block = uiLayoutGetBlock(layout); + const uiPropertySplitWrapper split_wrapper = uiItemPropertySplitWrapperCreate(layout); + /* Further items added to 'layout' will automatically be added to split_wrapper.property_row */ + + uiItemL_(split_wrapper.label_column, text, icon); + UI_block_layout_set_current(block, split_wrapper.property_row); + + return split_wrapper.decorate_column; + } + + char namestr[UI_MAX_NAME_STR]; + if (text) { + text = ui_item_name_add_colon(text, namestr); + } + uiItemL_(layout, text, icon); + + return layout; +} + +void uiItemLDrag(uiLayout *layout, PointerRNA *ptr, const char *name, int icon) +{ + uiBut *but = uiItemL_(layout, name, icon); + + if (ptr && ptr->type) { + if (RNA_struct_is_ID(ptr->type)) { + UI_but_drag_set_id(but, ptr->owner_id); + } + } +} + +/* value item */ +void uiItemV(uiLayout *layout, const char *name, int icon, int argval) +{ + /* label */ + uiBlock *block = layout->root->block; + int *retvalue = (block->handle) ? &block->handle->retvalue : NULL; + + UI_block_layout_set_current(block, layout); + + if (!name) { + name = ""; + } + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + const int w = ui_text_icon_width(layout, name, icon, 0); + + if (icon && name[0]) { + uiDefIconTextButI(block, + UI_BTYPE_BUT, + argval, + icon, + name, + 0, + 0, + w, + UI_UNIT_Y, + retvalue, + 0.0, + 0.0, + 0, + -1, + ""); + } + else if (icon) { + uiDefIconButI( + block, UI_BTYPE_BUT, argval, icon, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, -1, ""); + } + else { + uiDefButI( + block, UI_BTYPE_BUT, argval, name, 0, 0, w, UI_UNIT_Y, retvalue, 0.0, 0.0, 0, -1, ""); + } +} + +/* separator item */ +void uiItemS_ex(uiLayout *layout, float factor) +{ + uiBlock *block = layout->root->block; + const bool is_menu = ui_block_is_menu(block); + if (is_menu && !UI_block_can_add_separator(block)) { + return; + } + int space = (is_menu) ? 0.45f * UI_UNIT_X : 0.3f * UI_UNIT_X; + space *= factor; + + UI_block_layout_set_current(block, layout); + uiDefBut(block, + (is_menu) ? UI_BTYPE_SEPR_LINE : UI_BTYPE_SEPR, + 0, + "", + 0, + 0, + space, + space, + NULL, + 0.0, + 0.0, + 0, + 0, + ""); +} + +/* separator item */ +void uiItemS(uiLayout *layout) +{ + uiItemS_ex(layout, 1.0f); +} + +/* Flexible spacing. */ +void uiItemSpacer(uiLayout *layout) +{ + uiBlock *block = layout->root->block; + const bool is_popup = ui_block_is_popup_any(block); + + if (is_popup) { + printf("Error: separator_spacer() not supported in popups.\n"); + return; + } + + if (block->direction & UI_DIR_RIGHT) { + printf("Error: separator_spacer() only supported in horizontal blocks.\n"); + return; + } + + UI_block_layout_set_current(block, layout); + uiDefBut(block, + UI_BTYPE_SEPR_SPACER, + 0, + "", + 0, + 0, + 0.3f * UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + 0, + ""); +} + +/* level items */ +void uiItemMenuF(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *arg) +{ + if (!func) { + return; + } + + ui_item_menu(layout, name, icon, func, arg, NULL, "", false); +} + +/** + * Version of #uiItemMenuF that free's `argN`. + */ +void uiItemMenuFN(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc func, void *argN) +{ + if (!func) { + return; + } + + /* Second 'argN' only ensures it gets freed. */ + ui_item_menu(layout, name, icon, func, argN, argN, "", false); +} + +typedef struct MenuItemLevel { + int opcontext; + /* don't use pointers to the strings because python can dynamically + * allocate strings and free before the menu draws, see T27304. */ + char opname[OP_MAX_TYPENAME]; + char propname[MAX_IDPROP_NAME]; + PointerRNA rnapoin; +} MenuItemLevel; + +static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, void *arg) +{ + MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN); + + uiLayoutSetOperatorContext(layout, lvl->opcontext); + uiItemsEnumO(layout, lvl->opname, lvl->propname); + + layout->root->block->flag |= UI_BLOCK_IS_FLIP; + + /* override default, needed since this was assumed pre 2.70 */ + UI_block_direction_set(layout->root->block, UI_DIR_DOWN); +} + +void uiItemMenuEnumO_ptr(uiLayout *layout, + bContext *C, + wmOperatorType *ot, + const char *propname, + const char *name, + int icon) +{ + /* Caller must check */ + BLI_assert(ot->srna != NULL); + + if (name == NULL) { + name = WM_operatortype_name(ot, NULL); + } + + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + MenuItemLevel *lvl = (MenuItemLevel *)MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel"); + BLI_strncpy(lvl->opname, ot->idname, sizeof(lvl->opname)); + BLI_strncpy(lvl->propname, propname, sizeof(lvl->propname)); + lvl->opcontext = layout->root->opcontext; + + uiBut *but = ui_item_menu(layout, name, icon, menu_item_enum_opname_menu, NULL, lvl, NULL, true); + + /* add hotkey here, lower UI code can't detect it */ + if ((layout->root->block->flag & UI_BLOCK_LOOP) && (ot->prop && ot->invoke)) { + char keybuf[128]; + if (WM_key_event_operator_string( + C, ot->idname, layout->root->opcontext, NULL, false, keybuf, sizeof(keybuf))) { + ui_but_add_shortcut(but, keybuf, false); + } + } +} + +void uiItemMenuEnumO(uiLayout *layout, + bContext *C, + const char *opname, + const char *propname, + const char *name, + int icon) +{ + wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */ + + UI_OPERATOR_ERROR_RET(ot, opname, return ); + + if (!ot->srna) { + ui_item_disabled(layout, opname); + RNA_warning("operator missing srna '%s'", opname); + return; + } + + uiItemMenuEnumO_ptr(layout, C, ot, propname, name, icon); +} + +static void menu_item_enum_rna_menu(bContext *UNUSED(C), uiLayout *layout, void *arg) +{ + MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN); + + uiLayoutSetOperatorContext(layout, lvl->opcontext); + uiItemsEnumR(layout, &lvl->rnapoin, lvl->propname); + layout->root->block->flag |= UI_BLOCK_IS_FLIP; +} + +void uiItemMenuEnumR_prop( + uiLayout *layout, struct PointerRNA *ptr, PropertyRNA *prop, const char *name, int icon) +{ + if (!name) { + name = RNA_property_ui_name(prop); + } + if (layout->root->type == UI_LAYOUT_MENU && !icon) { + icon = ICON_BLANK1; + } + + MenuItemLevel *lvl = (MenuItemLevel *)MEM_callocN(sizeof(MenuItemLevel), "MenuItemLevel"); + lvl->rnapoin = *ptr; + BLI_strncpy(lvl->propname, RNA_property_identifier(prop), sizeof(lvl->propname)); + lvl->opcontext = layout->root->opcontext; + + ui_item_menu(layout, + name, + icon, + menu_item_enum_rna_menu, + NULL, + lvl, + RNA_property_description(prop), + false); +} + +void uiItemMenuEnumR( + uiLayout *layout, struct PointerRNA *ptr, const char *propname, const char *name, int icon) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + if (!prop) { + ui_item_disabled(layout, propname); + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiItemMenuEnumR_prop(layout, ptr, prop, name, icon); +} + +void uiItemTabsEnumR_prop(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + PointerRNA *ptr_highlight, + PropertyRNA *prop_highlight, + bool icon_only) +{ + uiBlock *block = layout->root->block; + + UI_block_layout_set_current(block, layout); + ui_item_enum_expand_tabs( + layout, C, block, ptr, prop, ptr_highlight, prop_highlight, NULL, UI_UNIT_Y, icon_only); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Layout Items + * \{ */ + +/* single-row layout */ +static void ui_litem_estimate_row(uiLayout *litem) +{ + int itemw, itemh; + bool min_size_flag = true; + + litem->w = 0; + litem->h = 0; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + + min_size_flag = min_size_flag && (item->flag & UI_ITEM_FIXED_SIZE); + + litem->w += itemw; + litem->h = MAX2(itemh, litem->h); + + if (item->next) { + litem->w += litem->space; + } + } + + if (min_size_flag) { + litem->item.flag |= UI_ITEM_FIXED_SIZE; + } +} + +static int ui_litem_min_width(int itemw) +{ + return MIN2(2 * UI_UNIT_X, itemw); +} + +static void ui_litem_layout_row(uiLayout *litem) +{ + uiItem *last_free_item = NULL; + int x, neww, newtotw, itemw, minw, itemh, offset; + int freew, fixedx, freex, flag = 0, lastw = 0; + float extra_pixel; + + /* x = litem->x; */ /* UNUSED */ + const int y = litem->y; + int w = litem->w; + int totw = 0; + int tot = 0; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + totw += itemw; + tot++; + } + + if (totw == 0) { + return; + } + + if (w != 0) { + w -= (tot - 1) * litem->space; + } + int fixedw = 0; + + /* keep clamping items to fixed minimum size until all are done */ + do { + freew = 0; + x = 0; + flag = 0; + newtotw = totw; + extra_pixel = 0.0f; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + if (item->flag & UI_ITEM_AUTO_FIXED_SIZE) { + continue; + } + + ui_item_size(item, &itemw, &itemh); + minw = ui_litem_min_width(itemw); + + if (w - lastw > 0) { + neww = ui_item_fit(itemw, x, totw, w - lastw, !item->next, litem->alignment, &extra_pixel); + } + else { + neww = 0; /* no space left, all will need clamping to minimum size */ + } + + x += neww; + + bool min_flag = item->flag & UI_ITEM_FIXED_SIZE; + /* ignore min flag for rows with right or center alignment */ + if (item->type != ITEM_BUTTON && + ELEM(((uiLayout *)item)->alignment, UI_LAYOUT_ALIGN_RIGHT, UI_LAYOUT_ALIGN_CENTER) && + litem->alignment == UI_LAYOUT_ALIGN_EXPAND && + ((uiItem *)litem)->flag & UI_ITEM_FIXED_SIZE) { + min_flag = false; + } + + if ((neww < minw || min_flag) && w != 0) { + /* fixed size */ + item->flag |= UI_ITEM_AUTO_FIXED_SIZE; + if (item->type != ITEM_BUTTON && item->flag & UI_ITEM_FIXED_SIZE) { + minw = itemw; + } + fixedw += minw; + flag = 1; + newtotw -= itemw; + } + else { + /* keep free size */ + item->flag &= ~UI_ITEM_AUTO_FIXED_SIZE; + freew += itemw; + } + } + + totw = newtotw; + lastw = fixedw; + } while (flag); + + freex = 0; + fixedx = 0; + extra_pixel = 0.0f; + x = litem->x; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + minw = ui_litem_min_width(itemw); + + if (item->flag & UI_ITEM_AUTO_FIXED_SIZE) { + /* fixed minimum size items */ + if (item->type != ITEM_BUTTON && item->flag & UI_ITEM_FIXED_SIZE) { + minw = itemw; + } + itemw = ui_item_fit( + minw, fixedx, fixedw, min_ii(w, fixedw), !item->next, litem->alignment, &extra_pixel); + fixedx += itemw; + } + else { + /* free size item */ + itemw = ui_item_fit( + itemw, freex, freew, w - fixedw, !item->next, litem->alignment, &extra_pixel); + freex += itemw; + last_free_item = item; + } + + /* align right/center */ + offset = 0; + if (litem->alignment == UI_LAYOUT_ALIGN_RIGHT) { + if (freew + fixedw > 0 && freew + fixedw < w) { + offset = w - (fixedw + freew); + } + } + else if (litem->alignment == UI_LAYOUT_ALIGN_CENTER) { + if (freew + fixedw > 0 && freew + fixedw < w) { + offset = (w - (fixedw + freew)) / 2; + } + } + + /* position item */ + ui_item_position(item, x + offset, y - itemh, itemw, itemh); + + x += itemw; + if (item->next) { + x += litem->space; + } + } + + /* add extra pixel */ + uiItem *last_item = (uiItem *)litem->items.last; + extra_pixel = litem->w - (x - litem->x); + if (extra_pixel > 0 && litem->alignment == UI_LAYOUT_ALIGN_EXPAND && last_free_item && + last_item && last_item->flag & UI_ITEM_AUTO_FIXED_SIZE) { + ui_item_move(last_free_item, 0, extra_pixel); + for (uiItem *item = (uiItem *)last_free_item->next; item; item = item->next) { + ui_item_move(item, extra_pixel, extra_pixel); + } + } + + litem->w = x - litem->x; + litem->h = litem->y - y; + litem->x = x; + litem->y = y; +} + +/* single-column layout */ +static void ui_litem_estimate_column(uiLayout *litem, bool is_box) +{ + int itemw, itemh; + bool min_size_flag = true; + + litem->w = 0; + litem->h = 0; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + + min_size_flag = min_size_flag && (item->flag & UI_ITEM_FIXED_SIZE); + + litem->w = MAX2(litem->w, itemw); + litem->h += itemh; + + if (item->next && (!is_box || item != litem->items.first)) { + litem->h += litem->space; + } + } + + if (min_size_flag) { + litem->item.flag |= UI_ITEM_FIXED_SIZE; + } +} + +static void ui_litem_layout_column(uiLayout *litem, bool is_box, bool is_menu) +{ + const int x = litem->x; + int y = litem->y; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + int itemw, itemh; + ui_item_size(item, &itemw, &itemh); + + y -= itemh; + ui_item_position(item, x, y, is_menu ? itemw : litem->w, itemh); + + if (item->next && (!is_box || item != litem->items.first)) { + y -= litem->space; + } + + if (is_box) { + item->flag |= UI_ITEM_BOX_ITEM; + } + } + + litem->h = litem->y - y; + litem->x = x; + litem->y = y; +} + +/* calculates the angle of a specified button in a radial menu, + * stores a float vector in unit circle */ +static RadialDirection ui_get_radialbut_vec(float vec[2], short itemnum) +{ + if (itemnum >= PIE_MAX_ITEMS) { + itemnum %= PIE_MAX_ITEMS; + printf("Warning: Pie menus with more than %i items are currently unsupported\n", + PIE_MAX_ITEMS); + } + + const RadialDirection dir = ui_radial_dir_order[itemnum]; + ui_but_pie_dir(dir, vec); + + return dir; +} + +static bool ui_item_is_radial_displayable(uiItem *item) +{ + + if ((item->type == ITEM_BUTTON) && (((uiButtonItem *)item)->but->type == UI_BTYPE_LABEL)) { + return false; + } + + return true; +} + +static bool ui_item_is_radial_drawable(uiButtonItem *bitem) +{ + + if (ELEM(bitem->but->type, UI_BTYPE_SEPR, UI_BTYPE_SEPR_LINE, UI_BTYPE_SEPR_SPACER)) { + return false; + } + + return true; +} + +static void ui_litem_layout_radial(uiLayout *litem) +{ + int itemh, itemw; + int itemnum = 0; + int totitems = 0; + + /* For the radial layout we will use Matt Ebb's design + * for radiation, see http://mattebb.com/weblog/radiation/ + * also the old code at http://developer.blender.org/T5103 + */ + + const int pie_radius = U.pie_menu_radius * UI_DPI_FAC; + + const int x = litem->x; + const int y = litem->y; + + int minx = x, miny = y, maxx = x, maxy = y; + + /* first count total items */ + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + totitems++; + } + + if (totitems < 5) { + litem->root->block->pie_data.flags |= UI_PIE_DEGREES_RANGE_LARGE; + } + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + /* not all button types are drawn in a radial menu, do filtering here */ + if (ui_item_is_radial_displayable(item)) { + RadialDirection dir; + float vec[2]; + float factor[2]; + + dir = ui_get_radialbut_vec(vec, itemnum); + factor[0] = (vec[0] > 0.01f) ? 0.0f : ((vec[0] < -0.01f) ? -1.0f : -0.5f); + factor[1] = (vec[1] > 0.99f) ? 0.0f : ((vec[1] < -0.99f) ? -1.0f : -0.5f); + + itemnum++; + + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + bitem->but->pie_dir = dir; + /* scale the buttons */ + bitem->but->rect.ymax *= 1.5f; + /* add a little bit more here to include number */ + bitem->but->rect.xmax += 1.5f * UI_UNIT_X; + /* enable drawing as pie item if supported by widget */ + if (ui_item_is_radial_drawable(bitem)) { + bitem->but->emboss = UI_EMBOSS_RADIAL; + bitem->but->drawflag |= UI_BUT_ICON_LEFT; + } + } + + ui_item_size(item, &itemw, &itemh); + + ui_item_position(item, + x + vec[0] * pie_radius + factor[0] * itemw, + y + vec[1] * pie_radius + factor[1] * itemh, + itemw, + itemh); + + minx = min_ii(minx, x + vec[0] * pie_radius - itemw / 2); + maxx = max_ii(maxx, x + vec[0] * pie_radius + itemw / 2); + miny = min_ii(miny, y + vec[1] * pie_radius - itemh / 2); + maxy = max_ii(maxy, y + vec[1] * pie_radius + itemh / 2); + } + } + + litem->x = minx; + litem->y = miny; + litem->w = maxx - minx; + litem->h = maxy - miny; +} + +/* root layout */ +static void ui_litem_estimate_root(uiLayout *UNUSED(litem)) +{ + /* nothing to do */ +} + +static void ui_litem_layout_root_radial(uiLayout *litem) +{ + /* first item is pie menu title, align on center of menu */ + uiItem *item = (uiItem *)litem->items.first; + + if (item->type == ITEM_BUTTON) { + int itemh, itemw, x, y; + x = litem->x; + y = litem->y; + + ui_item_size(item, &itemw, &itemh); + + ui_item_position( + item, x - itemw / 2, y + U.dpi_fac * (U.pie_menu_threshold + 9.0f), itemw, itemh); + } +} + +static void ui_litem_layout_root(uiLayout *litem) +{ + if (litem->root->type == UI_LAYOUT_HEADER) { + ui_litem_layout_row(litem); + } + else if (litem->root->type == UI_LAYOUT_PIEMENU) { + ui_litem_layout_root_radial(litem); + } + else if (litem->root->type == UI_LAYOUT_MENU) { + ui_litem_layout_column(litem, false, true); + } + else { + ui_litem_layout_column(litem, false, false); + } +} + +/* box layout */ +static void ui_litem_estimate_box(uiLayout *litem) +{ + const uiStyle *style = litem->root->style; + + ui_litem_estimate_column(litem, true); + + int boxspace = style->boxspace; + if (litem->root->type == UI_LAYOUT_HEADER) { + boxspace = 0; + } + litem->w += 2 * boxspace; + litem->h += 2 * boxspace; +} + +static void ui_litem_layout_box(uiLayout *litem) +{ + uiLayoutItemBx *box = (uiLayoutItemBx *)litem; + const uiStyle *style = litem->root->style; + + int boxspace = style->boxspace; + if (litem->root->type == UI_LAYOUT_HEADER) { + boxspace = 0; + } + + const int w = litem->w; + const int h = litem->h; + + litem->x += boxspace; + litem->y -= boxspace; + + if (w != 0) { + litem->w -= 2 * boxspace; + } + if (h != 0) { + litem->h -= 2 * boxspace; + } + + ui_litem_layout_column(litem, true, false); + + litem->x -= boxspace; + litem->y -= boxspace; + + if (w != 0) { + litem->w += 2 * boxspace; + } + if (h != 0) { + litem->h += 2 * boxspace; + } + + /* roundbox around the sublayout */ + uiBut *but = box->roundbox; + but->rect.xmin = litem->x; + but->rect.ymin = litem->y; + but->rect.xmax = litem->x + litem->w; + but->rect.ymax = litem->y + litem->h; +} + +/* multi-column layout, automatically flowing to the next */ +static void ui_litem_estimate_column_flow(uiLayout *litem) +{ + const uiStyle *style = litem->root->style; + uiLayoutItemFlow *flow = (uiLayoutItemFlow *)litem; + int itemw, itemh, maxw = 0; + + /* compute max needed width and total height */ + int toth = 0; + int totitem = 0; + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + maxw = MAX2(maxw, itemw); + toth += itemh; + totitem++; + } + + if (flow->number <= 0) { + /* auto compute number of columns, not very good */ + if (maxw == 0) { + flow->totcol = 1; + return; + } + + flow->totcol = max_ii(litem->root->emw / maxw, 1); + flow->totcol = min_ii(flow->totcol, totitem); + } + else { + flow->totcol = flow->number; + } + + /* compute sizes */ + int x = 0; + int y = 0; + int emy = 0; + int miny = 0; + + maxw = 0; + const int emh = toth / flow->totcol; + + /* create column per column */ + int col = 0; + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + + y -= itemh + style->buttonspacey; + miny = min_ii(miny, y); + emy -= itemh; + maxw = max_ii(itemw, maxw); + + /* decide to go to next one */ + if (col < flow->totcol - 1 && emy <= -emh) { + x += maxw + litem->space; + maxw = 0; + y = 0; + emy = 0; /* need to reset height again for next column */ + col++; + } + } + + litem->w = x; + litem->h = litem->y - miny; +} + +static void ui_litem_layout_column_flow(uiLayout *litem) +{ + const uiStyle *style = litem->root->style; + uiLayoutItemFlow *flow = (uiLayoutItemFlow *)litem; + int col, emh, itemw, itemh; + + /* compute max needed width and total height */ + int toth = 0; + int totitem = 0; + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + toth += itemh; + totitem++; + } + + /* compute sizes */ + int x = litem->x; + int y = litem->y; + int emy = 0; + int miny = 0; + + int w = litem->w - (flow->totcol - 1) * style->columnspace; + emh = toth / flow->totcol; + + /* create column per column */ + col = 0; + w = (litem->w - (flow->totcol - 1) * style->columnspace) / flow->totcol; + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_size(item, &itemw, &itemh); + + itemw = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? w : min_ii(w, itemw); + + y -= itemh; + emy -= itemh; + ui_item_position(item, x, y, itemw, itemh); + y -= style->buttonspacey; + miny = min_ii(miny, y); + + /* decide to go to next one */ + if (col < flow->totcol - 1 && emy <= -emh) { + x += w + style->columnspace; + y = litem->y; + emy = 0; /* need to reset height again for next column */ + col++; + + const int remaining_width = litem->w - (x - litem->x); + const int remaining_width_between_columns = (flow->totcol - col - 1) * style->columnspace; + const int remaining_columns = flow->totcol - col; + w = (remaining_width - remaining_width_between_columns) / remaining_columns; + } + } + + litem->h = litem->y - miny; + litem->x = x; + litem->y = miny; +} + +/* multi-column and multi-row layout. */ +struct UILayoutGridFlowInput { + /* General layout control settings. */ + const bool row_major = true; /* Fill rows before columns */ + const bool even_columns = true; /* All columns will have same width. */ + const bool even_rows = true; /* All rows will have same height. */ + const int space_x; /* Space between columns. */ + const int space_y; /* Space between rows. */ + /* Real data about current position and size of this layout item + * (either estimated, or final values). */ + const int litem_w; /* Layout item width. */ + const int litem_x; /* Layout item X position. */ + const int litem_y; /* Layout item Y position. */ + /* Actual number of columns and rows to generate (computed from first pass usually). */ + const int tot_columns; /* Number of columns. */ + const int tot_rows; /* Number of rows. */ +}; + +struct UILayoutGridFlowOutput { + int *tot_items; /* Total number of items in this grid layout. */ + /* Width / X pos data. */ + float *global_avg_w; /* Computed average width of the columns. */ + int *cos_x_array; /* Computed X coordinate of each column. */ + int *widths_array; /* Computed width of each column. */ + int *tot_w; /* Computed total width. */ + /* Height / Y pos data. */ + int *global_max_h; /* Computed height of the tallest item in the grid. */ + int *cos_y_array; /* Computed Y coordinate of each column. */ + int *heights_array; /* Computed height of each column. */ + int *tot_h; /* Computed total height. */ +}; + +static void ui_litem_grid_flow_compute(ListBase *items, + UILayoutGridFlowInput *parameters, + UILayoutGridFlowOutput *results) +{ + float tot_w = 0.0f, tot_h = 0.0f; + float global_avg_w = 0.0f, global_totweight_w = 0.0f; + int global_max_h = 0; + + Array avg_w(parameters->tot_columns, 0.0f); + Array totweight_w(parameters->tot_columns, 0.0f); + Array max_h(parameters->tot_rows, 0); + + BLI_assert( + parameters->tot_columns != 0 || + (results->cos_x_array == NULL && results->widths_array == NULL && results->tot_w == NULL)); + BLI_assert( + parameters->tot_rows != 0 || + (results->cos_y_array == NULL && results->heights_array == NULL && results->tot_h == NULL)); + + if (results->tot_items) { + *results->tot_items = 0; + } + + if (items->first == NULL) { + if (results->global_avg_w) { + *results->global_avg_w = 0.0f; + } + if (results->global_max_h) { + *results->global_max_h = 0; + } + return; + } + + int i = 0; + LISTBASE_FOREACH (uiItem *, item, items) { + int item_w, item_h; + ui_item_size(item, &item_w, &item_h); + + global_avg_w += (float)(item_w * item_w); + global_totweight_w += (float)item_w; + global_max_h = max_ii(global_max_h, item_h); + + if (parameters->tot_rows != 0 && parameters->tot_columns != 0) { + const int index_col = parameters->row_major ? i % parameters->tot_columns : + i / parameters->tot_rows; + const int index_row = parameters->row_major ? i / parameters->tot_columns : + i % parameters->tot_rows; + + avg_w[index_col] += (float)(item_w * item_w); + totweight_w[index_col] += (float)item_w; + + max_h[index_row] = max_ii(max_h[index_row], item_h); + } + + if (results->tot_items) { + (*results->tot_items)++; + } + i++; + } + + /* Finalize computing of column average sizes */ + global_avg_w /= global_totweight_w; + if (parameters->tot_columns != 0) { + for (i = 0; i < parameters->tot_columns; i++) { + avg_w[i] /= totweight_w[i]; + tot_w += avg_w[i]; + } + if (parameters->even_columns) { + tot_w = ceilf(global_avg_w) * parameters->tot_columns; + } + } + /* Finalize computing of rows max sizes */ + if (parameters->tot_rows != 0) { + for (i = 0; i < parameters->tot_rows; i++) { + tot_h += max_h[i]; + } + if (parameters->even_rows) { + tot_h = global_max_h * parameters->tot_columns; + } + } + + /* Compute positions and sizes of all cells. */ + if (results->cos_x_array != NULL && results->widths_array != NULL) { + /* We enlarge/narrow columns evenly to match available width. */ + const float wfac = (float)(parameters->litem_w - + (parameters->tot_columns - 1) * parameters->space_x) / + tot_w; + + for (int col = 0; col < parameters->tot_columns; col++) { + results->cos_x_array[col] = (col ? results->cos_x_array[col - 1] + + results->widths_array[col - 1] + parameters->space_x : + parameters->litem_x); + if (parameters->even_columns) { + /* (< remaining width > - < space between remaining columns >) / < remaining columns > */ + results->widths_array[col] = (((parameters->litem_w - + (results->cos_x_array[col] - parameters->litem_x)) - + (parameters->tot_columns - col - 1) * parameters->space_x) / + (parameters->tot_columns - col)); + } + else if (col == parameters->tot_columns - 1) { + /* Last column copes width rounding errors... */ + results->widths_array[col] = parameters->litem_w - + (results->cos_x_array[col] - parameters->litem_x); + } + else { + results->widths_array[col] = (int)(avg_w[col] * wfac); + } + } + } + if (results->cos_y_array != NULL && results->heights_array != NULL) { + for (int row = 0; row < parameters->tot_rows; row++) { + if (parameters->even_rows) { + results->heights_array[row] = global_max_h; + } + else { + results->heights_array[row] = max_h[row]; + } + results->cos_y_array[row] = (row ? results->cos_y_array[row - 1] - parameters->space_y - + results->heights_array[row] : + parameters->litem_y - results->heights_array[row]); + } + } + + if (results->global_avg_w) { + *results->global_avg_w = global_avg_w; + } + if (results->global_max_h) { + *results->global_max_h = global_max_h; + } + if (results->tot_w) { + *results->tot_w = (int)tot_w + parameters->space_x * (parameters->tot_columns - 1); + } + if (results->tot_h) { + *results->tot_h = tot_h + parameters->space_y * (parameters->tot_rows - 1); + } +} + +static void ui_litem_estimate_grid_flow(uiLayout *litem) +{ + const uiStyle *style = litem->root->style; + uiLayoutItemGridFlow *gflow = (uiLayoutItemGridFlow *)litem; + + const int space_x = style->columnspace; + const int space_y = style->buttonspacey; + + /* Estimate average needed width and height per item. */ + { + float avg_w; + int max_h; + + UILayoutGridFlowInput grid_flow_input{ + gflow->row_major, + gflow->even_columns, + gflow->even_rows, + litem->w, + litem->x, + litem->y, + space_x, + space_y, + }; + UILayoutGridFlowOutput grid_flow_output; + grid_flow_output.tot_items = &gflow->tot_items; + grid_flow_output.global_avg_w = &avg_w; + grid_flow_output.global_max_h = &max_h; + + ui_litem_grid_flow_compute(&litem->items, &grid_flow_input, &grid_flow_output); + + if (gflow->tot_items == 0) { + litem->w = litem->h = 0; + gflow->tot_columns = gflow->tot_rows = 0; + return; + } + + /* Even in varying column width case, + * we fix our columns number from weighted average width of items, + * a proper solving of required width would be too costly, + * and this should give reasonably good results in all reasonable cases. */ + if (gflow->columns_len > 0) { + gflow->tot_columns = gflow->columns_len; + } + else { + if (avg_w == 0.0f) { + gflow->tot_columns = 1; + } + else { + gflow->tot_columns = min_ii(max_ii((int)(litem->w / avg_w), 1), gflow->tot_items); + } + } + gflow->tot_rows = (int)ceilf((float)gflow->tot_items / gflow->tot_columns); + + /* Try to tweak number of columns and rows to get better filling of last column or row, + * and apply 'modulo' value to number of columns or rows. + * Note that modulo does not prevent ending with fewer columns/rows than modulo, if mandatory + * to avoid empty column/row. */ + { + const int modulo = (gflow->columns_len < -1) ? -gflow->columns_len : 0; + const int step = modulo ? modulo : 1; + + if (gflow->row_major) { + /* Adjust number of columns to be multiple of given modulo. */ + if (modulo && gflow->tot_columns % modulo != 0 && gflow->tot_columns > modulo) { + gflow->tot_columns = gflow->tot_columns - (gflow->tot_columns % modulo); + } + /* Find smallest number of columns conserving computed optimal number of rows. */ + for (gflow->tot_rows = (int)ceilf((float)gflow->tot_items / gflow->tot_columns); + (gflow->tot_columns - step) > 0 && + (int)ceilf((float)gflow->tot_items / (gflow->tot_columns - step)) <= gflow->tot_rows; + gflow->tot_columns -= step) { + /* pass */ + } + } + else { + /* Adjust number of rows to be multiple of given modulo. */ + if (modulo && gflow->tot_rows % modulo != 0) { + gflow->tot_rows = min_ii(gflow->tot_rows + modulo - (gflow->tot_rows % modulo), + gflow->tot_items); + } + /* Find smallest number of rows conserving computed optimal number of columns. */ + for (gflow->tot_columns = (int)ceilf((float)gflow->tot_items / gflow->tot_rows); + (gflow->tot_rows - step) > 0 && + (int)ceilf((float)gflow->tot_items / (gflow->tot_rows - step)) <= gflow->tot_columns; + gflow->tot_rows -= step) { + /* pass */ + } + } + } + + /* Set evenly-spaced axes size + * (quick optimization in case we have even columns and rows). */ + if (gflow->even_columns && gflow->even_rows) { + litem->w = (int)(gflow->tot_columns * avg_w) + space_x * (gflow->tot_columns - 1); + litem->h = (int)(gflow->tot_rows * max_h) + space_y * (gflow->tot_rows - 1); + return; + } + } + + /* Now that we have our final number of columns and rows, + * we can compute actual needed space for non-evenly sized axes. */ + { + int tot_w, tot_h; + + UILayoutGridFlowInput grid_flow_input{ + gflow->row_major, + gflow->even_columns, + gflow->even_rows, + litem->w, + litem->x, + litem->y, + space_x, + space_y, + gflow->tot_columns, + gflow->tot_rows, + }; + + UILayoutGridFlowOutput grid_flow_output; + grid_flow_output.tot_w = &tot_w; + grid_flow_output.tot_h = &tot_h; + + ui_litem_grid_flow_compute(&litem->items, &grid_flow_input, &grid_flow_output); + + litem->w = tot_w; + litem->h = tot_h; + } +} + +static void ui_litem_layout_grid_flow(uiLayout *litem) +{ + const uiStyle *style = litem->root->style; + uiLayoutItemGridFlow *gflow = (uiLayoutItemGridFlow *)litem; + + if (gflow->tot_items == 0) { + litem->w = litem->h = 0; + return; + } + + BLI_assert(gflow->tot_columns > 0); + BLI_assert(gflow->tot_rows > 0); + + const int space_x = style->columnspace; + const int space_y = style->buttonspacey; + + Array widths(gflow->tot_columns); + Array heights(gflow->tot_rows); + Array cos_x(gflow->tot_columns); + Array cos_y(gflow->tot_rows); + + UILayoutGridFlowInput grid_flow_input{ + gflow->row_major, + gflow->even_columns, + gflow->even_rows, + litem->w, + litem->x, + litem->y, + space_x, + space_y, + gflow->tot_columns, + gflow->tot_rows, + }; + + UILayoutGridFlowOutput grid_flow_output; + grid_flow_output.cos_x_array = cos_x.data(); + grid_flow_output.cos_y_array = cos_y.data(); + grid_flow_output.widths_array = widths.data(); + grid_flow_output.heights_array = heights.data(); + + /* This time we directly compute coordinates and sizes of all cells. */ + ui_litem_grid_flow_compute(&litem->items, &grid_flow_input, &grid_flow_output); + + int i; + LISTBASE_FOREACH_INDEX (uiItem *, item, &litem->items, i) { + const int col = gflow->row_major ? i % gflow->tot_columns : i / gflow->tot_rows; + const int row = gflow->row_major ? i / gflow->tot_columns : i % gflow->tot_rows; + int item_w, item_h; + ui_item_size(item, &item_w, &item_h); + + const int w = widths[col]; + const int h = heights[row]; + + item_w = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? w : min_ii(w, item_w); + item_h = (litem->alignment == UI_LAYOUT_ALIGN_EXPAND) ? h : min_ii(h, item_h); + + ui_item_position(item, cos_x[col], cos_y[row], item_w, item_h); + } + + litem->h = litem->y - cos_y[gflow->tot_rows - 1]; + litem->x = (cos_x[gflow->tot_columns - 1] - litem->x) + widths[gflow->tot_columns - 1]; + litem->y = litem->y - litem->h; +} + +/* free layout */ +static void ui_litem_estimate_absolute(uiLayout *litem) +{ + int minx = 1e6; + int miny = 1e6; + litem->w = 0; + litem->h = 0; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + int itemx, itemy, itemw, itemh; + ui_item_offset(item, &itemx, &itemy); + ui_item_size(item, &itemw, &itemh); + + minx = min_ii(minx, itemx); + miny = min_ii(miny, itemy); + + litem->w = MAX2(litem->w, itemx + itemw); + litem->h = MAX2(litem->h, itemy + itemh); + } + + litem->w -= minx; + litem->h -= miny; +} + +static void ui_litem_layout_absolute(uiLayout *litem) +{ + float scalex = 1.0f, scaley = 1.0f; + int x, y, newx, newy, itemx, itemy, itemh, itemw; + + int minx = 1e6; + int miny = 1e6; + int totw = 0; + int toth = 0; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_offset(item, &itemx, &itemy); + ui_item_size(item, &itemw, &itemh); + + minx = min_ii(minx, itemx); + miny = min_ii(miny, itemy); + + totw = max_ii(totw, itemx + itemw); + toth = max_ii(toth, itemy + itemh); + } + + totw -= minx; + toth -= miny; + + if (litem->w && totw > 0) { + scalex = (float)litem->w / (float)totw; + } + if (litem->h && toth > 0) { + scaley = (float)litem->h / (float)toth; + } + + x = litem->x; + y = litem->y - scaley * toth; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + ui_item_offset(item, &itemx, &itemy); + ui_item_size(item, &itemw, &itemh); + + if (scalex != 1.0f) { + newx = (itemx - minx) * scalex; + itemw = (itemx - minx + itemw) * scalex - newx; + itemx = minx + newx; + } + + if (scaley != 1.0f) { + newy = (itemy - miny) * scaley; + itemh = (itemy - miny + itemh) * scaley - newy; + itemy = miny + newy; + } + + ui_item_position(item, x + itemx - minx, y + itemy - miny, itemw, itemh); + } + + litem->w = scalex * totw; + litem->h = litem->y - y; + litem->x = x + litem->w; + litem->y = y; +} + +/* split layout */ +static void ui_litem_estimate_split(uiLayout *litem) +{ + ui_litem_estimate_row(litem); + litem->item.flag &= ~UI_ITEM_FIXED_SIZE; +} + +static void ui_litem_layout_split(uiLayout *litem) +{ + uiLayoutItemSplit *split = (uiLayoutItemSplit *)litem; + float extra_pixel = 0.0f; + const int tot = BLI_listbase_count(&litem->items); + + if (tot == 0) { + return; + } + + int x = litem->x; + const int y = litem->y; + + const float percentage = (split->percentage == 0.0f) ? 1.0f / (float)tot : split->percentage; + + const int w = (litem->w - (tot - 1) * litem->space); + int colw = w * percentage; + colw = MAX2(colw, 0); + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + int itemh; + ui_item_size(item, NULL, &itemh); + + ui_item_position(item, x, y - itemh, colw, itemh); + x += colw; + + if (item->next) { + const float width = extra_pixel + (w - (int)(w * percentage)) / ((float)tot - 1); + extra_pixel = width - (int)width; + colw = (int)width; + colw = MAX2(colw, 0); + + x += litem->space; + } + } + + litem->w = x - litem->x; + litem->h = litem->y - y; + litem->x = x; + litem->y = y; +} + +/* overlap layout */ +static void ui_litem_estimate_overlap(uiLayout *litem) +{ + litem->w = 0; + litem->h = 0; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + int itemw, itemh; + ui_item_size(item, &itemw, &itemh); + + litem->w = MAX2(itemw, litem->w); + litem->h = MAX2(itemh, litem->h); + } +} + +static void ui_litem_layout_overlap(uiLayout *litem) +{ + + const int x = litem->x; + const int y = litem->y; + + LISTBASE_FOREACH (uiItem *, item, &litem->items) { + int itemw, itemh; + ui_item_size(item, &itemw, &itemh); + ui_item_position(item, x, y - itemh, litem->w, itemh); + + litem->h = MAX2(litem->h, itemh); + } + + litem->x = x; + litem->y = y - litem->h; +} + +static void ui_litem_init_from_parent(uiLayout *litem, uiLayout *layout, int align) +{ + litem->root = layout->root; + litem->align = align; + /* Children of gridflow layout shall never have "ideal big size" returned as estimated size. */ + litem->variable_size = layout->variable_size || layout->item.type == ITEM_LAYOUT_GRID_FLOW; + litem->active = true; + litem->enabled = true; + litem->context = layout->context; + litem->redalert = layout->redalert; + litem->w = layout->w; + litem->emboss = layout->emboss; + litem->item.flag = (layout->item.flag & + (UI_ITEM_PROP_SEP | UI_ITEM_PROP_DECORATE | UI_ITEM_INSIDE_PROP_SEP)); + + if (layout->child_items_layout) { + BLI_addtail(&layout->child_items_layout->items, litem); + litem->parent = layout->child_items_layout; + } + else { + BLI_addtail(&layout->items, litem); + litem->parent = layout; + } +} + +static void ui_layout_heading_set(uiLayout *layout, const char *heading) +{ + BLI_assert(layout->heading[0] == '\0'); + if (heading) { + STRNCPY(layout->heading, heading); + } +} + +/* layout create functions */ +uiLayout *uiLayoutRow(uiLayout *layout, bool align) +{ + uiLayout *litem = (uiLayout *)MEM_callocN(sizeof(uiLayout), "uiLayoutRow"); + ui_litem_init_from_parent(litem, layout, align); + + litem->item.type = ITEM_LAYOUT_ROW; + litem->space = (align) ? 0 : layout->root->style->buttonspacex; + + UI_block_layout_set_current(layout->root->block, litem); + + return litem; +} + +/** + * See #uiLayoutColumnWithHeading(). + */ +uiLayout *uiLayoutRowWithHeading(uiLayout *layout, bool align, const char *heading) +{ + uiLayout *litem = uiLayoutRow(layout, align); + ui_layout_heading_set(litem, heading); + return litem; +} + +uiLayout *uiLayoutColumn(uiLayout *layout, bool align) +{ + uiLayout *litem = (uiLayout *)MEM_callocN(sizeof(uiLayout), "uiLayoutColumn"); + ui_litem_init_from_parent(litem, layout, align); + + litem->item.type = ITEM_LAYOUT_COLUMN; + litem->space = (align) ? 0 : layout->root->style->buttonspacey; + + UI_block_layout_set_current(layout->root->block, litem); + + return litem; +} + +/** + * Variant of #uiLayoutColumn() that sets a heading label for the layout if the first item is + * added through #uiItemFullR(). If split layout is used and the item has no string to add to the + * first split-column, the heading is added there instead. Otherwise the heading inserted with a + * new row. + */ +uiLayout *uiLayoutColumnWithHeading(uiLayout *layout, bool align, const char *heading) +{ + uiLayout *litem = uiLayoutColumn(layout, align); + ui_layout_heading_set(litem, heading); + return litem; +} + +uiLayout *uiLayoutColumnFlow(uiLayout *layout, int number, bool align) +{ + uiLayoutItemFlow *flow = (uiLayoutItemFlow *)MEM_callocN(sizeof(uiLayoutItemFlow), + "uiLayoutItemFlow"); + ui_litem_init_from_parent(&flow->litem, layout, align); + + flow->litem.item.type = ITEM_LAYOUT_COLUMN_FLOW; + flow->litem.space = (flow->litem.align) ? 0 : layout->root->style->columnspace; + flow->number = number; + + UI_block_layout_set_current(layout->root->block, &flow->litem); + + return &flow->litem; +} + +uiLayout *uiLayoutGridFlow(uiLayout *layout, + bool row_major, + int columns_len, + bool even_columns, + bool even_rows, + bool align) +{ + uiLayoutItemGridFlow *flow = (uiLayoutItemGridFlow *)MEM_callocN(sizeof(uiLayoutItemGridFlow), + __func__); + flow->litem.item.type = ITEM_LAYOUT_GRID_FLOW; + ui_litem_init_from_parent(&flow->litem, layout, align); + + flow->litem.space = (flow->litem.align) ? 0 : layout->root->style->columnspace; + flow->row_major = row_major; + flow->columns_len = columns_len; + flow->even_columns = even_columns; + flow->even_rows = even_rows; + + UI_block_layout_set_current(layout->root->block, &flow->litem); + + return &flow->litem; +} + +static uiLayoutItemBx *ui_layout_box(uiLayout *layout, int type) +{ + uiLayoutItemBx *box = (uiLayoutItemBx *)MEM_callocN(sizeof(uiLayoutItemBx), "uiLayoutItemBx"); + ui_litem_init_from_parent(&box->litem, layout, false); + + box->litem.item.type = ITEM_LAYOUT_BOX; + box->litem.space = layout->root->style->columnspace; + + UI_block_layout_set_current(layout->root->block, &box->litem); + + box->roundbox = uiDefBut(layout->root->block, type, 0, "", 0, 0, 0, 0, NULL, 0.0, 0.0, 0, 0, ""); + + return box; +} + +uiLayout *uiLayoutRadial(uiLayout *layout) +{ + /* radial layouts are only valid for radial menus */ + if (layout->root->type != UI_LAYOUT_PIEMENU) { + return ui_item_local_sublayout(layout, layout, 0); + } + + /* only one radial wheel per root layout is allowed, so check and return that, if it exists */ + LISTBASE_FOREACH (uiItem *, item, &layout->root->layout->items) { + uiLayout *litem = (uiLayout *)item; + if (litem->item.type == ITEM_LAYOUT_RADIAL) { + UI_block_layout_set_current(layout->root->block, litem); + return litem; + } + } + + uiLayout *litem = (uiLayout *)MEM_callocN(sizeof(uiLayout), "uiLayoutRadial"); + ui_litem_init_from_parent(litem, layout, false); + + litem->item.type = ITEM_LAYOUT_RADIAL; + + UI_block_layout_set_current(layout->root->block, litem); + + return litem; +} + +uiLayout *uiLayoutBox(uiLayout *layout) +{ + return (uiLayout *)ui_layout_box(layout, UI_BTYPE_ROUNDBOX); +} + +/** + * Check all buttons defined in this layout, + * and set any button flagged as UI_BUT_LIST_ITEM as active/selected. + * Needed to handle correctly text colors of active (selected) list item. + */ +void ui_layout_list_set_labels_active(uiLayout *layout) +{ + LISTBASE_FOREACH (uiButtonItem *, bitem, &layout->items) { + if (bitem->item.type != ITEM_BUTTON) { + ui_layout_list_set_labels_active((uiLayout *)(&bitem->item)); + } + else if (bitem->but->flag & UI_BUT_LIST_ITEM) { + UI_but_flag_enable(bitem->but, UI_SELECT); + } + } +} + +uiLayout *uiLayoutListBox(uiLayout *layout, + uiList *ui_list, + PointerRNA *actptr, + PropertyRNA *actprop) +{ + uiLayoutItemBx *box = ui_layout_box(layout, UI_BTYPE_LISTBOX); + uiBut *but = box->roundbox; + + but->custom_data = ui_list; + + but->rnapoin = *actptr; + but->rnaprop = actprop; + + /* only for the undo string */ + if (but->flag & UI_BUT_UNDO) { + but->tip = RNA_property_description(actprop); + } + + return (uiLayout *)box; +} + +uiLayout *uiLayoutAbsolute(uiLayout *layout, bool align) +{ + uiLayout *litem = (uiLayout *)MEM_callocN(sizeof(uiLayout), "uiLayoutAbsolute"); + ui_litem_init_from_parent(litem, layout, align); + + litem->item.type = ITEM_LAYOUT_ABSOLUTE; + + UI_block_layout_set_current(layout->root->block, litem); + + return litem; +} + +uiBlock *uiLayoutAbsoluteBlock(uiLayout *layout) +{ + uiBlock *block = uiLayoutGetBlock(layout); + uiLayoutAbsolute(layout, false); + + return block; +} + +uiLayout *uiLayoutOverlap(uiLayout *layout) +{ + uiLayout *litem = (uiLayout *)MEM_callocN(sizeof(uiLayout), "uiLayoutOverlap"); + ui_litem_init_from_parent(litem, layout, false); + + litem->item.type = ITEM_LAYOUT_OVERLAP; + + UI_block_layout_set_current(layout->root->block, litem); + + return litem; +} + +uiLayout *uiLayoutSplit(uiLayout *layout, float percentage, bool align) +{ + uiLayoutItemSplit *split = (uiLayoutItemSplit *)MEM_callocN(sizeof(uiLayoutItemSplit), + "uiLayoutItemSplit"); + ui_litem_init_from_parent(&split->litem, layout, align); + + split->litem.item.type = ITEM_LAYOUT_SPLIT; + split->litem.space = layout->root->style->columnspace; + split->percentage = percentage; + + UI_block_layout_set_current(layout->root->block, &split->litem); + + return &split->litem; +} + +void uiLayoutSetActive(uiLayout *layout, bool active) +{ + layout->active = active; +} + +void uiLayoutSetActiveDefault(uiLayout *layout, bool active_default) +{ + layout->active_default = active_default; +} + +void uiLayoutSetActivateInit(uiLayout *layout, bool activate_init) +{ + layout->activate_init = activate_init; +} + +void uiLayoutSetEnabled(uiLayout *layout, bool enabled) +{ + layout->enabled = enabled; +} + +void uiLayoutSetRedAlert(uiLayout *layout, bool redalert) +{ + layout->redalert = redalert; +} + +void uiLayoutSetKeepAspect(uiLayout *layout, bool keepaspect) +{ + layout->keepaspect = keepaspect; +} + +void uiLayoutSetAlignment(uiLayout *layout, char alignment) +{ + layout->alignment = alignment; +} + +void uiLayoutSetScaleX(uiLayout *layout, float scale) +{ + layout->scale[0] = scale; +} + +void uiLayoutSetScaleY(uiLayout *layout, float scale) +{ + layout->scale[1] = scale; +} + +void uiLayoutSetUnitsX(uiLayout *layout, float unit) +{ + layout->units[0] = unit; +} + +void uiLayoutSetUnitsY(uiLayout *layout, float unit) +{ + layout->units[1] = unit; +} + +void uiLayoutSetEmboss(uiLayout *layout, eUIEmbossType emboss) +{ + layout->emboss = emboss; +} + +bool uiLayoutGetPropSep(uiLayout *layout) +{ + return (layout->item.flag & UI_ITEM_PROP_SEP) != 0; +} + +void uiLayoutSetPropSep(uiLayout *layout, bool is_sep) +{ + SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_SEP); +} + +bool uiLayoutGetPropDecorate(uiLayout *layout) +{ + return (layout->item.flag & UI_ITEM_PROP_DECORATE) != 0; +} + +void uiLayoutSetPropDecorate(uiLayout *layout, bool is_sep) +{ + SET_FLAG_FROM_TEST(layout->item.flag, is_sep, UI_ITEM_PROP_DECORATE); +} + +bool uiLayoutGetActive(uiLayout *layout) +{ + return layout->active; +} + +bool uiLayoutGetActiveDefault(uiLayout *layout) +{ + return layout->active_default; +} + +bool uiLayoutGetActivateInit(uiLayout *layout) +{ + return layout->activate_init; +} + +bool uiLayoutGetEnabled(uiLayout *layout) +{ + return layout->enabled; +} + +bool uiLayoutGetRedAlert(uiLayout *layout) +{ + return layout->redalert; +} + +bool uiLayoutGetKeepAspect(uiLayout *layout) +{ + return layout->keepaspect; +} + +int uiLayoutGetAlignment(uiLayout *layout) +{ + return layout->alignment; +} + +int uiLayoutGetWidth(uiLayout *layout) +{ + return layout->w; +} + +float uiLayoutGetScaleX(uiLayout *layout) +{ + return layout->scale[0]; +} + +float uiLayoutGetScaleY(uiLayout *layout) +{ + return layout->scale[1]; +} + +float uiLayoutGetUnitsX(uiLayout *layout) +{ + return layout->units[0]; +} + +float uiLayoutGetUnitsY(uiLayout *layout) +{ + return layout->units[1]; +} + +eUIEmbossType uiLayoutGetEmboss(uiLayout *layout) +{ + if (layout->emboss == UI_EMBOSS_UNDEFINED) { + return layout->root->block->emboss; + } + return layout->emboss; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Block Layout Search Filtering + * \{ */ + +/* Disabled for performance reasons, but this could be turned on in the future. */ +// #define PROPERTY_SEARCH_USE_TOOLTIPS + +static bool block_search_panel_label_matches(const uiBlock *block, const char *search_string) +{ + if ((block->panel != NULL) && (block->panel->type != NULL)) { + if (BLI_strcasestr(block->panel->type->label, search_string)) { + return true; + } + } + return false; +} + +/** + * Returns true if a button or the data / operator it represents matches the search filter. + */ +static bool button_matches_search_filter(uiBut *but, const char *search_filter) +{ + /* Do the shorter checks first for better performance in case there is a match. */ + if (BLI_strcasestr(but->str, search_filter)) { + return true; + } + + if (but->optype != NULL) { + if (BLI_strcasestr(but->optype->name, search_filter)) { + return true; + } + } + + if (but->rnaprop != NULL) { + if (BLI_strcasestr(RNA_property_ui_name(but->rnaprop), search_filter)) { + return true; + } +#ifdef PROPERTY_SEARCH_USE_TOOLTIPS + if (BLI_strcasestr(RNA_property_description(but->rnaprop), search_filter)) { + return true; + } +#endif + + /* Search through labels of enum property items if they are in a drop-down menu. + * Unfortunately we have no #bContext here so we cannot search through RNA enums + * with dynamic entries (or "itemf" functions) which require context. */ + if (but->type == UI_BTYPE_MENU) { + PointerRNA *ptr = &but->rnapoin; + PropertyRNA *enum_prop = but->rnaprop; + + int items_len; + const EnumPropertyItem *items_array = NULL; + bool free; + RNA_property_enum_items_gettexted(NULL, ptr, enum_prop, &items_array, &items_len, &free); + + if (items_array == NULL) { + return false; + } + + for (int i = 0; i < items_len; i++) { + /* Check for NULL name field which enums use for separators. */ + if (items_array[i].name == NULL) { + continue; + } + if (BLI_strcasestr(items_array[i].name, search_filter)) { + return true; + } + } + if (free) { + MEM_freeN((EnumPropertyItem *)items_array); + } + } + } + + return false; +} + +/** + * Test for a search result within a specific button group. + */ +static bool button_group_has_search_match(uiButtonGroup *button_group, const char *search_filter) +{ + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = (uiBut *)link->data; + if (button_matches_search_filter(but, search_filter)) { + return true; + } + } + + return false; +} + +/** + * Apply the search filter, tagging all buttons with whether they match or not. + * Tag every button in the group as a result if any button in the group matches. + * + * \note It would be great to return early here if we found a match, but because + * the results may be visible we have to continue searching the entire block. + * + * \return True if the block has any search results. + */ +static bool block_search_filter_tag_buttons(uiBlock *block, const char *search_filter) +{ + bool has_result = false; + LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { + if (button_group_has_search_match(button_group, search_filter)) { + has_result = true; + } + else { + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = (uiBut *)link->data; + but->flag |= UI_SEARCH_FILTER_NO_MATCH; + } + } + } + return has_result; +} + +/** + * Apply property search behavior, setting panel flags and deactivating buttons that don't match. + * + * \note Must not be run after #UI_block_layout_resolve. + */ +bool UI_block_apply_search_filter(uiBlock *block, const char *search_filter) +{ + if (search_filter == NULL || search_filter[0] == '\0') { + return false; + } + + Panel *panel = block->panel; + + if (panel != NULL && panel->type->flag & PANEL_TYPE_NO_SEARCH) { + /* Panels for active blocks should always have a type, otherwise they wouldn't be created. */ + BLI_assert(block->panel->type != NULL); + if (panel->type->flag & PANEL_TYPE_NO_SEARCH) { + return false; + } + } + + const bool panel_label_matches = block_search_panel_label_matches(block, search_filter); + + const bool has_result = (panel_label_matches) ? + true : + block_search_filter_tag_buttons(block, search_filter); + + if (panel != NULL) { + if (has_result) { + ui_panel_tag_search_filter_match(block->panel); + } + } + + return has_result; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Layout + * \{ */ + +static void ui_item_scale(uiLayout *litem, const float scale[2]) +{ + int x, y, w, h; + + LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) { + if (item->type != ITEM_BUTTON) { + uiLayout *subitem = (uiLayout *)item; + ui_item_scale(subitem, scale); + } + + ui_item_size(item, &w, &h); + ui_item_offset(item, &x, &y); + + if (scale[0] != 0.0f) { + x *= scale[0]; + w *= scale[0]; + } + + if (scale[1] != 0.0f) { + y *= scale[1]; + h *= scale[1]; + } + + ui_item_position(item, x, y, w, h); + } +} + +static void ui_item_estimate(uiItem *item) +{ + if (item->type != ITEM_BUTTON) { + uiLayout *litem = (uiLayout *)item; + + LISTBASE_FOREACH (uiItem *, subitem, &litem->items) { + ui_item_estimate(subitem); + } + + if (BLI_listbase_is_empty(&litem->items)) { + litem->w = 0; + litem->h = 0; + return; + } + + if (litem->scale[0] != 0.0f || litem->scale[1] != 0.0f) { + ui_item_scale(litem, litem->scale); + } + + switch (litem->item.type) { + case ITEM_LAYOUT_COLUMN: + ui_litem_estimate_column(litem, false); + break; + case ITEM_LAYOUT_COLUMN_FLOW: + ui_litem_estimate_column_flow(litem); + break; + case ITEM_LAYOUT_GRID_FLOW: + ui_litem_estimate_grid_flow(litem); + break; + case ITEM_LAYOUT_ROW: + ui_litem_estimate_row(litem); + break; + case ITEM_LAYOUT_BOX: + ui_litem_estimate_box(litem); + break; + case ITEM_LAYOUT_ROOT: + ui_litem_estimate_root(litem); + break; + case ITEM_LAYOUT_ABSOLUTE: + ui_litem_estimate_absolute(litem); + break; + case ITEM_LAYOUT_SPLIT: + ui_litem_estimate_split(litem); + break; + case ITEM_LAYOUT_OVERLAP: + ui_litem_estimate_overlap(litem); + break; + default: + break; + } + + /* Force fixed size. */ + if (litem->units[0] > 0) { + litem->w = UI_UNIT_X * litem->units[0]; + } + if (litem->units[1] > 0) { + litem->h = UI_UNIT_Y * litem->units[1]; + } + } +} + +static void ui_item_align(uiLayout *litem, short nr) +{ + LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) { + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; +#ifndef USE_UIBUT_SPATIAL_ALIGN + if (ui_but_can_align(bitem->but)) +#endif + { + if (!bitem->but->alignnr) { + bitem->but->alignnr = nr; + } + } + } + else if (item->type == ITEM_LAYOUT_ABSOLUTE) { + /* pass */ + } + else if (item->type == ITEM_LAYOUT_OVERLAP) { + /* pass */ + } + else if (item->type == ITEM_LAYOUT_BOX) { + uiLayoutItemBx *box = (uiLayoutItemBx *)item; + if (!box->roundbox->alignnr) { + box->roundbox->alignnr = nr; + } + } + else if (((uiLayout *)item)->align) { + ui_item_align((uiLayout *)item, nr); + } + } +} + +static void ui_item_flag(uiLayout *litem, int flag) +{ + LISTBASE_FOREACH_BACKWARD (uiItem *, item, &litem->items) { + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + bitem->but->flag |= flag; + } + else { + ui_item_flag((uiLayout *)item, flag); + } + } +} + +static void ui_item_layout(uiItem *item) +{ + if (item->type != ITEM_BUTTON) { + uiLayout *litem = (uiLayout *)item; + + if (BLI_listbase_is_empty(&litem->items)) { + return; + } + + if (litem->align) { + ui_item_align(litem, ++litem->root->block->alignnr); + } + if (!litem->active) { + ui_item_flag(litem, UI_BUT_INACTIVE); + } + if (!litem->enabled) { + ui_item_flag(litem, UI_BUT_DISABLED); + } + + switch (litem->item.type) { + case ITEM_LAYOUT_COLUMN: + ui_litem_layout_column(litem, false, false); + break; + case ITEM_LAYOUT_COLUMN_FLOW: + ui_litem_layout_column_flow(litem); + break; + case ITEM_LAYOUT_GRID_FLOW: + ui_litem_layout_grid_flow(litem); + break; + case ITEM_LAYOUT_ROW: + ui_litem_layout_row(litem); + break; + case ITEM_LAYOUT_BOX: + ui_litem_layout_box(litem); + break; + case ITEM_LAYOUT_ROOT: + ui_litem_layout_root(litem); + break; + case ITEM_LAYOUT_ABSOLUTE: + ui_litem_layout_absolute(litem); + break; + case ITEM_LAYOUT_SPLIT: + ui_litem_layout_split(litem); + break; + case ITEM_LAYOUT_OVERLAP: + ui_litem_layout_overlap(litem); + break; + case ITEM_LAYOUT_RADIAL: + ui_litem_layout_radial(litem); + break; + default: + break; + } + + LISTBASE_FOREACH (uiItem *, subitem, &litem->items) { + if (item->flag & UI_ITEM_BOX_ITEM) { + subitem->flag |= UI_ITEM_BOX_ITEM; + } + ui_item_layout(subitem); + } + } + else { + if (item->flag & UI_ITEM_BOX_ITEM) { + uiButtonItem *bitem = (uiButtonItem *)item; + bitem->but->drawflag |= UI_BUT_BOX_ITEM; + } + } +} + +static void ui_layout_end(uiBlock *block, uiLayout *layout, int *r_x, int *r_y) +{ + if (layout->root->handlefunc) { + UI_block_func_handle_set(block, layout->root->handlefunc, layout->root->argv); + } + + ui_item_estimate(&layout->item); + ui_item_layout(&layout->item); + + if (r_x) { + *r_x = layout->x; + } + if (r_y) { + *r_y = layout->y; + } +} + +static void ui_layout_free(uiLayout *layout) +{ + LISTBASE_FOREACH_MUTABLE (uiItem *, item, &layout->items) { + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + bitem->but->layout = NULL; + MEM_freeN(item); + } + else { + ui_layout_free((uiLayout *)item); + } + } + + MEM_freeN(layout); +} + +static void ui_layout_add_padding_button(uiLayoutRoot *root) +{ + if (root->padding) { + /* add an invisible button for padding */ + uiBlock *block = root->block; + uiLayout *prev_layout = block->curlayout; + + block->curlayout = root->layout; + uiDefBut( + block, UI_BTYPE_SEPR, 0, "", 0, 0, root->padding, root->padding, NULL, 0.0, 0.0, 0, 0, ""); + block->curlayout = prev_layout; + } +} + +uiLayout *UI_block_layout(uiBlock *block, + int dir, + int type, + int x, + int y, + int size, + int em, + int padding, + const uiStyle *style) +{ + uiLayoutRoot *root = (uiLayoutRoot *)MEM_callocN(sizeof(uiLayoutRoot), "uiLayoutRoot"); + root->type = type; + root->style = style; + root->block = block; + root->padding = padding; + root->opcontext = WM_OP_INVOKE_REGION_WIN; + + uiLayout *layout = (uiLayout *)MEM_callocN(sizeof(uiLayout), "uiLayout"); + layout->item.type = (type == UI_LAYOUT_VERT_BAR) ? ITEM_LAYOUT_COLUMN : ITEM_LAYOUT_ROOT; + + /* Only used when 'UI_ITEM_PROP_SEP' is set. */ + layout->item.flag = UI_ITEM_PROP_DECORATE; + + layout->x = x; + layout->y = y; + layout->root = root; + layout->space = style->templatespace; + layout->active = true; + layout->enabled = true; + layout->context = NULL; + layout->emboss = UI_EMBOSS_UNDEFINED; + + if (ELEM(type, UI_LAYOUT_MENU, UI_LAYOUT_PIEMENU)) { + layout->space = 0; + } + + if (dir == UI_LAYOUT_HORIZONTAL) { + layout->h = size; + layout->root->emh = em * UI_UNIT_Y; + } + else { + layout->w = size; + layout->root->emw = em * UI_UNIT_X; + } + + block->curlayout = layout; + root->layout = layout; + BLI_addtail(&block->layouts, root); + + ui_layout_add_padding_button(root); + + return layout; +} + +uiBlock *uiLayoutGetBlock(uiLayout *layout) +{ + return layout->root->block; +} + +int uiLayoutGetOperatorContext(uiLayout *layout) +{ + return layout->root->opcontext; +} + +void UI_block_layout_set_current(uiBlock *block, uiLayout *layout) +{ + block->curlayout = layout; +} + +void ui_layout_add_but(uiLayout *layout, uiBut *but) +{ + uiButtonItem *bitem = (uiButtonItem *)MEM_callocN(sizeof(uiButtonItem), "uiButtonItem"); + bitem->item.type = ITEM_BUTTON; + bitem->but = but; + + int w, h; + ui_item_size((uiItem *)bitem, &w, &h); + /* XXX uiBut hasn't scaled yet + * we can flag the button as not expandable, depending on its size */ + if (w <= 2 * UI_UNIT_X && (!but->str || but->str[0] == '\0')) { + bitem->item.flag |= UI_ITEM_FIXED_SIZE; + } + + if (layout->child_items_layout) { + BLI_addtail(&layout->child_items_layout->items, bitem); + } + else { + BLI_addtail(&layout->items, bitem); + } + but->layout = layout; + + if (layout->context) { + but->context = layout->context; + but->context->used = true; + } + + if (layout->emboss != UI_EMBOSS_UNDEFINED) { + but->emboss = layout->emboss; + } + + ui_button_group_add_but(uiLayoutGetBlock(layout), but); +} + +bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but) +{ + ListBase *child_list = layout->child_items_layout ? &layout->child_items_layout->items : + &layout->items; + + LISTBASE_FOREACH (uiItem *, item, child_list) { + if (item->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)item; + + if (bitem->but == old_but_ptr) { + bitem->but = new_but; + return true; + } + } + else { + if (ui_layout_replace_but_ptr((uiLayout *)item, old_but_ptr, new_but)) { + return true; + } + } + } + + return false; +} + +void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size) +{ + if (fixed_size) { + layout->item.flag |= UI_ITEM_FIXED_SIZE; + } + else { + layout->item.flag &= ~UI_ITEM_FIXED_SIZE; + } +} + +bool uiLayoutGetFixedSize(uiLayout *layout) +{ + return (layout->item.flag & UI_ITEM_FIXED_SIZE) != 0; +} + +void uiLayoutSetOperatorContext(uiLayout *layout, int opcontext) +{ + layout->root->opcontext = opcontext; +} + +void uiLayoutSetFunc(uiLayout *layout, uiMenuHandleFunc handlefunc, void *argv) +{ + layout->root->handlefunc = handlefunc; + layout->root->argv = argv; +} + +/** + * Used for property search when the layout process needs to be cancelled in order to avoid + * computing the locations for buttons, but the layout items created while adding the buttons + * must still be freed. + */ +void UI_block_layout_free(uiBlock *block) +{ + LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) { + ui_layout_free(root->layout); + MEM_freeN(root); + } +} + +void UI_block_layout_resolve(uiBlock *block, int *r_x, int *r_y) +{ + BLI_assert(block->active); + + if (r_x) { + *r_x = 0; + } + if (r_y) { + *r_y = 0; + } + + block->curlayout = NULL; + + LISTBASE_FOREACH_MUTABLE (uiLayoutRoot *, root, &block->layouts) { + ui_layout_add_padding_button(root); + + /* NULL in advance so we don't interfere when adding button */ + ui_layout_end(block, root->layout, r_x, r_y); + ui_layout_free(root->layout); + MEM_freeN(root); + } + + BLI_listbase_clear(&block->layouts); + + /* XXX silly trick, interface_templates.c doesn't get linked + * because it's not used by other files in this module? */ + { + UI_template_fix_linking(); + } +} + +void uiLayoutSetContextPointer(uiLayout *layout, const char *name, PointerRNA *ptr) +{ + uiBlock *block = layout->root->block; + layout->context = CTX_store_add(&block->contexts, name, ptr); +} + +bContextStore *uiLayoutGetContextStore(uiLayout *layout) +{ + return layout->context; +} + +void uiLayoutContextCopy(uiLayout *layout, bContextStore *context) +{ + uiBlock *block = layout->root->block; + layout->context = CTX_store_add_all(&block->contexts, context); +} + +void uiLayoutSetContextFromBut(uiLayout *layout, uiBut *but) +{ + if (but->opptr) { + uiLayoutSetContextPointer(layout, "button_operator", but->opptr); + } + + if (but->rnapoin.data && but->rnaprop) { + /* TODO: index could be supported as well */ + PointerRNA ptr_prop; + RNA_pointer_create(NULL, &RNA_Property, but->rnaprop, &ptr_prop); + uiLayoutSetContextPointer(layout, "button_prop", &ptr_prop); + uiLayoutSetContextPointer(layout, "button_pointer", &but->rnapoin); + } +} + +/* this is a bit of a hack but best keep it in one place at least */ +wmOperatorType *UI_but_operatortype_get_from_enum_menu(uiBut *but, PropertyRNA **r_prop) +{ + if (r_prop != NULL) { + *r_prop = NULL; + } + + if (but->menu_create_func == menu_item_enum_opname_menu) { + MenuItemLevel *lvl = (MenuItemLevel *)but->func_argN; + wmOperatorType *ot = WM_operatortype_find(lvl->opname, false); + if ((ot != NULL) && (r_prop != NULL)) { + *r_prop = RNA_struct_type_find_property(ot->srna, lvl->propname); + } + return ot; + } + return NULL; +} + +/* this is a bit of a hack but best keep it in one place at least */ +MenuType *UI_but_menutype_get(uiBut *but) +{ + if (but->menu_create_func == ui_item_menutype_func) { + return (MenuType *)but->poin; + } + return NULL; +} + +/* this is a bit of a hack but best keep it in one place at least */ +PanelType *UI_but_paneltype_get(uiBut *but) +{ + if (but->menu_create_func == ui_item_paneltype_func) { + return (PanelType *)but->poin; + } + return NULL; +} + +void UI_menutype_draw(bContext *C, MenuType *mt, struct uiLayout *layout) +{ + Menu menu = {mt, layout}; + + if (G.debug & G_DEBUG_WM) { + printf("%s: opening menu \"%s\"\n", __func__, mt->idname); + } + + if (layout->context) { + CTX_store_set(C, layout->context); + } + + mt->draw(C, &menu); + + if (layout->context) { + CTX_store_set(C, NULL); + } +} + +static bool ui_layout_has_panel_label(const uiLayout *layout, const PanelType *pt) +{ + LISTBASE_FOREACH (uiItem *, subitem, &layout->items) { + if (subitem->type == ITEM_BUTTON) { + uiButtonItem *bitem = (uiButtonItem *)subitem; + if (!(bitem->but->flag & UI_HIDDEN) && STREQ(bitem->but->str, pt->label)) { + return true; + } + } + else { + uiLayout *litem = (uiLayout *)subitem; + if (ui_layout_has_panel_label(litem, pt)) { + return true; + } + } + } + + return false; +} + +static void ui_paneltype_draw_impl(bContext *C, PanelType *pt, uiLayout *layout, bool show_header) +{ + Panel *panel = (Panel *)MEM_callocN(sizeof(Panel), "popover panel"); + panel->type = pt; + panel->flag = PNL_POPOVER; + + uiLayout *last_item = (uiLayout *)layout->items.last; + + /* Draw main panel. */ + if (show_header) { + uiLayout *row = uiLayoutRow(layout, false); + if (pt->draw_header) { + panel->layout = row; + pt->draw_header(C, panel); + panel->layout = NULL; + } + + /* draw_header() is often used to add a checkbox to the header. If we add the label like below + * the label is disconnected from the checkbox, adding a weird looking gap. As workaround, let + * the checkbox add the label instead. */ + if (!ui_layout_has_panel_label(row, pt)) { + uiItemL(row, CTX_IFACE_(pt->translation_context, pt->label), ICON_NONE); + } + } + + panel->layout = layout; + pt->draw(C, panel); + panel->layout = NULL; + BLI_assert(panel->runtime.custom_data_ptr == NULL); + + MEM_freeN(panel); + + /* Draw child panels. */ + LISTBASE_FOREACH (LinkData *, link, &pt->children) { + PanelType *child_pt = (PanelType *)link->data; + + if (child_pt->poll == NULL || child_pt->poll(C, child_pt)) { + /* Add space if something was added to the layout. */ + if (last_item != layout->items.last) { + uiItemS(layout); + last_item = (uiLayout *)layout->items.last; + } + + uiLayout *col = uiLayoutColumn(layout, false); + ui_paneltype_draw_impl(C, child_pt, col, true); + } + } +} + +/** + * Used for popup panels only. + */ +void UI_paneltype_draw(bContext *C, PanelType *pt, uiLayout *layout) +{ + if (layout->context) { + CTX_store_set(C, layout->context); + } + + ui_paneltype_draw_impl(C, pt, layout, false); + + if (layout->context) { + CTX_store_set(C, NULL); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Layout (Debugging/Introspection) + * + * Serialize the layout as a Python compatible dictionary, + * + * \note Proper string escaping isn't used, + * triple quotes are used to prevent single quotes from interfering with Python syntax. + * If we want this to be fool-proof, we would need full Python compatible string escape support. + * As we don't use triple quotes in the UI it's good-enough in practice. + * \{ */ + +static void ui_layout_introspect_button(DynStr *ds, uiButtonItem *bitem) +{ + uiBut *but = bitem->but; + BLI_dynstr_appendf(ds, "'type':%d, ", (int)but->type); + BLI_dynstr_appendf(ds, "'draw_string':'''%s''', ", but->drawstr); + /* Not exactly needed, rna has this. */ + BLI_dynstr_appendf(ds, "'tip':'''%s''', ", but->tip ? but->tip : ""); + + if (but->optype) { + char *opstr = WM_operator_pystring_ex( + (bContext *)but->block->evil_C, NULL, false, true, but->optype, but->opptr); + BLI_dynstr_appendf(ds, "'operator':'''%s''', ", opstr ? opstr : ""); + MEM_freeN(opstr); + } + + { + PropertyRNA *prop = NULL; + wmOperatorType *ot = UI_but_operatortype_get_from_enum_menu(but, &prop); + if (ot) { + char *opstr = WM_operator_pystring_ex( + (bContext *)but->block->evil_C, NULL, false, true, ot, NULL); + BLI_dynstr_appendf(ds, "'operator':'''%s''', ", opstr ? opstr : ""); + BLI_dynstr_appendf(ds, "'property':'''%s''', ", prop ? RNA_property_identifier(prop) : ""); + MEM_freeN(opstr); + } + } + + if (but->rnaprop) { + BLI_dynstr_appendf(ds, + "'rna':'%s.%s[%d]', ", + RNA_struct_identifier(but->rnapoin.type), + RNA_property_identifier(but->rnaprop), + but->rnaindex); + } +} + +static void ui_layout_introspect_items(DynStr *ds, ListBase *lb) +{ + uiItem *item; + + BLI_dynstr_append(ds, "["); + + for (item = (uiItem *)lb->first; item; item = item->next) { + + BLI_dynstr_append(ds, "{"); + +#define CASE_ITEM(id) \ + case id: { \ + const char *id_str = STRINGIFY(id); \ + BLI_dynstr_append(ds, "'type': '"); \ + /* Skip 'ITEM_'. */ \ + BLI_dynstr_append(ds, id_str + 5); \ + BLI_dynstr_append(ds, "', "); \ + break; \ + } \ + ((void)0) + + switch (item->type) { + CASE_ITEM(ITEM_BUTTON); + CASE_ITEM(ITEM_LAYOUT_ROW); + CASE_ITEM(ITEM_LAYOUT_COLUMN); + CASE_ITEM(ITEM_LAYOUT_COLUMN_FLOW); + CASE_ITEM(ITEM_LAYOUT_ROW_FLOW); + CASE_ITEM(ITEM_LAYOUT_BOX); + CASE_ITEM(ITEM_LAYOUT_ABSOLUTE); + CASE_ITEM(ITEM_LAYOUT_SPLIT); + CASE_ITEM(ITEM_LAYOUT_OVERLAP); + CASE_ITEM(ITEM_LAYOUT_ROOT); + CASE_ITEM(ITEM_LAYOUT_GRID_FLOW); + CASE_ITEM(ITEM_LAYOUT_RADIAL); + } + +#undef CASE_ITEM + + switch (item->type) { + case ITEM_BUTTON: + ui_layout_introspect_button(ds, (uiButtonItem *)item); + break; + default: + BLI_dynstr_append(ds, "'items':"); + ui_layout_introspect_items(ds, &((uiLayout *)item)->items); + break; + } + + BLI_dynstr_append(ds, "}"); + + if (item != lb->last) { + BLI_dynstr_append(ds, ", "); + } + } + /* Don't use a comma here as it's not needed and + * causes the result to evaluate to a tuple of 1. */ + BLI_dynstr_append(ds, "]"); +} + +/** + * Evaluate layout items as a Python dictionary. + */ +const char *UI_layout_introspect(uiLayout *layout) +{ + DynStr *ds = BLI_dynstr_new(); + uiLayout layout_copy = *layout; + layout_copy.item.next = NULL; + layout_copy.item.prev = NULL; + ListBase layout_dummy_list = {&layout_copy, &layout_copy}; + ui_layout_introspect_items(ds, &layout_dummy_list); + const char *result = BLI_dynstr_get_cstring(ds); + BLI_dynstr_free(ds); + return result; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Alert Box with Big Icon + * \{ */ + +/** + * Helper to add a big icon and create a split layout for alert popups. + * Returns the layout to place further items into the alert box. + */ +uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon) +{ + const uiStyle *style = UI_style_get_dpi(); + const short icon_size = 64 * U.dpi_fac; + const int text_points_max = MAX2(style->widget.points, style->widgetlabel.points); + const int dialog_width = icon_size + (text_points_max * size * U.dpi_fac); + /* By default, the space between icon and text/buttons will be equal to the 'columnspace', + this extra padding will add some space by increasing the left column width, + making the icon placement more symmetrical, between the block edge and the text. */ + const float icon_padding = 5.0f * U.dpi_fac; + /* Calculate the factor of the fixed icon column depending on the block width. */ + const float split_factor = ((float)icon_size + icon_padding) / + (float)(dialog_width - style->columnspace); + + uiLayout *block_layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, dialog_width, 0, 0, style); + + /* Split layout to put alert icon on left side. */ + uiLayout *split_block = uiLayoutSplit(block_layout, split_factor, false); + + /* Alert icon on the left. */ + uiLayout *layout = uiLayoutRow(split_block, false); + /* Using 'align_left' with 'row' avoids stretching the icon along the width of column. */ + uiLayoutSetAlignment(layout, UI_LAYOUT_ALIGN_LEFT); + uiDefButAlert(block, icon, 0, 0, icon_size, icon_size); + + /* The rest of the content on the right. */ + layout = uiLayoutColumn(split_block, false); + + return layout; +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c deleted file mode 100644 index 6505a7cd76a..00000000000 --- a/source/blender/editors/interface/interface_panel.c +++ /dev/null @@ -1,2684 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - */ - -/* a full doc with API notes can be found in - * bf-blender/trunk/blender/doc/guides/interface_API.txt */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "PIL_time.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "DNA_screen_types.h" -#include "DNA_userdef_types.h" - -#include "BKE_context.h" -#include "BKE_screen.h" - -#include "RNA_access.h" - -#include "BLF_api.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" -#include "UI_resources.h" -#include "UI_view2d.h" - -#include "GPU_batch_presets.h" -#include "GPU_immediate.h" -#include "GPU_matrix.h" -#include "GPU_state.h" - -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Defines & Structs - * \{ */ - -#define ANIMATION_TIME 0.30 -#define ANIMATION_INTERVAL 0.02 - -typedef enum uiPanelRuntimeFlag { - PANEL_LAST_ADDED = (1 << 0), - PANEL_ACTIVE = (1 << 2), - PANEL_WAS_ACTIVE = (1 << 3), - PANEL_ANIM_ALIGN = (1 << 4), - PANEL_NEW_ADDED = (1 << 5), - PANEL_SEARCH_FILTER_MATCH = (1 << 7), - /** - * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH) - * instead of #PNL_CLOSED. Set to true on every property search update. - */ - PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8), - /** The Panel was before the start of the current / latest layout pass. */ - PANEL_WAS_CLOSED = (1 << 9), - /** - * Set when the panel is being dragged and while it animates back to its aligned - * position. Unlike #PANEL_STATE_ANIMATION, this is applied to sub-panels as well. - */ - PANEL_IS_DRAG_DROP = (1 << 10), - /** Draw a border with the active color around the panel. */ - PANEL_ACTIVE_BORDER = (1 << 11), -} uiPanelRuntimeFlag; - -/* The state of the mouse position relative to the panel. */ -typedef enum uiPanelMouseState { - PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */ - PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */ - PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */ -} uiPanelMouseState; - -typedef enum uiHandlePanelState { - PANEL_STATE_DRAG, - PANEL_STATE_ANIMATION, - PANEL_STATE_EXIT, -} uiHandlePanelState; - -typedef struct uiHandlePanelData { - uiHandlePanelState state; - - /* Animation. */ - wmTimer *animtimer; - double starttime; - - /* Dragging. */ - int startx, starty; - int startofsx, startofsy; - float start_cur_xmin, start_cur_ymin; -} uiHandlePanelData; - -typedef struct PanelSort { - Panel *panel; - int new_offset_x; - int new_offset_y; -} PanelSort; - -static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel); -static int get_panel_real_size_y(const Panel *panel); -static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state); -static int compare_panel(const void *a, const void *b); -static bool panel_type_context_poll(ARegion *region, - const PanelType *panel_type, - const char *context); - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Local Functions - * \{ */ - -static bool panel_active_animation_changed(ListBase *lb, - Panel **r_panel_animation, - bool *r_no_animation) -{ - LISTBASE_FOREACH (Panel *, panel, lb) { - /* Detect panel active flag changes. */ - if (!(panel->type && panel->type->parent)) { - if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) { - return true; - } - if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) { - return true; - } - } - - /* Detect changes in panel expansions. */ - if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) { - *r_panel_animation = panel; - return false; - } - - if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) { - if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) { - return true; - } - } - - /* Detect animation. */ - if (panel->activedata) { - uiHandlePanelData *data = panel->activedata; - if (data->state == PANEL_STATE_ANIMATION) { - *r_panel_animation = panel; - } - else { - /* Don't animate while handling other interaction. */ - *r_no_animation = true; - } - } - if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) { - *r_panel_animation = panel; - } - } - - return false; -} - -/** - * \return True if the properties editor switch tabs since the last layout pass. - */ -static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region) -{ - if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) { - SpaceProperties *sbuts = area->spacedata.first; - - if (sbuts->mainbo != sbuts->mainb) { - return true; - } - } - - return false; -} - -static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation) -{ - *r_panel_animation = NULL; - - if (properties_space_needs_realign(area, region)) { - return true; - } - - /* Detect if a panel was added or removed. */ - Panel *panel_animation = NULL; - bool no_animation = false; - if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) { - return true; - } - - /* Detect panel marked for animation, if we're not already animating. */ - if (panel_animation) { - if (!no_animation) { - *r_panel_animation = panel_animation; - } - return true; - } - - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Functions for Instanced Panels - * \{ */ - -static Panel *panel_add_instanced(ARegion *region, - ListBase *panels, - PanelType *panel_type, - PointerRNA *custom_data) -{ - Panel *panel = MEM_callocN(sizeof(Panel), __func__); - panel->type = panel_type; - BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); - - panel->runtime.custom_data_ptr = custom_data; - panel->runtime_flag |= PANEL_NEW_ADDED; - - /* Add the panel's children too. Although they aren't instanced panels, we can still use this - * function to create them, as UI_panel_begin does other things we don't need to do. */ - LISTBASE_FOREACH (LinkData *, child, &panel_type->children) { - PanelType *child_type = child->data; - panel_add_instanced(region, &panel->children, child_type, custom_data); - } - - /* Make sure the panel is added to the end of the display-order as well. This is needed for - * loading existing files. - * - * Note: We could use special behavior to place it after the panel that starts the list of - * instanced panels, but that would add complexity that isn't needed for now. */ - int max_sortorder = 0; - LISTBASE_FOREACH (Panel *, existing_panel, panels) { - if (existing_panel->sortorder > max_sortorder) { - max_sortorder = existing_panel->sortorder; - } - } - panel->sortorder = max_sortorder + 1; - - BLI_addtail(panels, panel); - - return panel; -} - -/** - * Called in situations where panels need to be added dynamically rather than - * having only one panel corresponding to each #PanelType. - */ -Panel *UI_panel_add_instanced(const bContext *C, - ARegion *region, - ListBase *panels, - const char *panel_idname, - PointerRNA *custom_data) -{ - ARegionType *region_type = region->type; - - PanelType *panel_type = BLI_findstring( - ®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname)); - - if (panel_type == NULL) { - printf("Panel type '%s' not found.\n", panel_idname); - return NULL; - } - - Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data); - - /* Do this after #panel_add_instatnced so all sub-panels are added. */ - panel_set_expansion_from_list_data(C, new_panel); - - return new_panel; -} - -/** - * Find a unique key to append to the #PanelType.idname for the lookup to the panel's #uiBlock. - * Needed for instanced panels, where there can be multiple with the same type and identifier. - */ -void UI_list_panel_unique_str(Panel *panel, char *r_name) -{ - /* The panel sort-order will be unique for a specific panel type because the instanced - * panel list is regenerated for every change in the data order / length. */ - snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder); -} - -/** - * Free a panel and its children. Custom data is shared by the panel and its children - * and is freed by #UI_panels_free_instanced. - * - * \note The only panels that should need to be deleted at runtime are panels with the - * #PANEL_TYPE_INSTANCED flag set. - */ -static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel) -{ - /* Recursively delete children. */ - LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) { - panel_delete(C, region, &panel->children, child); - } - BLI_freelistN(&panel->children); - - BLI_remlink(panels, panel); - if (panel->activedata) { - MEM_freeN(panel->activedata); - } - MEM_freeN(panel); -} - -/** - * Remove instanced panels from the region's panel list. - * - * \note Can be called with NULL \a C, but it should be avoided because - * handlers might not be removed. - */ -void UI_panels_free_instanced(const bContext *C, ARegion *region) -{ - /* Delete panels with the instanced flag. */ - LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) { - if ((panel->type != NULL) && (panel->type->flag & PANEL_TYPE_INSTANCED)) { - /* Make sure the panel's handler is removed before deleting it. */ - if (C != NULL && panel->activedata != NULL) { - panel_activate_state(C, panel, PANEL_STATE_EXIT); - } - - /* Free panel's custom data. */ - if (panel->runtime.custom_data_ptr != NULL) { - MEM_freeN(panel->runtime.custom_data_ptr); - } - - /* Free the panel and its sub-panels. */ - panel_delete(C, region, ®ion->panels, panel); - } - } -} - -/** - * Check if the instanced panels in the region's panels correspond to the list of data the panels - * represent. Returns false if the panels have been reordered or if the types from the list data - * don't match in any way. - * - * \param data: The list of data to check against the instanced panels. - * \param panel_idname_func: Function to find the #PanelType.idname for each item in the data list. - * For a readability and generality, this lookup happens separately for each type of panel list. - */ -bool UI_panel_list_matches_data(ARegion *region, - ListBase *data, - uiListPanelIDFromDataFunc panel_idname_func) -{ - /* Check for NULL data. */ - int data_len = 0; - Link *data_link = NULL; - if (data == NULL) { - data_len = 0; - data_link = NULL; - } - else { - data_len = BLI_listbase_count(data); - data_link = data->first; - } - - int i = 0; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { - /* The panels were reordered by drag and drop. */ - if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) { - return false; - } - - /* We reached the last data item before the last instanced panel. */ - if (data_link == NULL) { - return false; - } - - /* Check if the panel type matches the panel type from the data item. */ - char panel_idname[MAX_NAME]; - panel_idname_func(data_link, panel_idname); - if (!STREQ(panel_idname, panel->type->idname)) { - return false; - } - - data_link = data_link->next; - i++; - } - } - - /* If we didn't make it to the last list item, the panel list isn't complete. */ - if (i != data_len) { - return false; - } - - return true; -} - -static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel) -{ - /* Without a type we cannot access the reorder callback. */ - if (drag_panel->type == NULL) { - return; - } - /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */ - if (drag_panel->type->reorder == NULL) { - return; - } - - char *context = NULL; - if (!UI_panel_category_is_visible(region)) { - context = drag_panel->type->context; - } - - /* Find how many instanced panels with this context string. */ - int list_panels_len = 0; - int start_index = -1; - LISTBASE_FOREACH (const Panel *, panel, ®ion->panels) { - if (panel->type) { - if (panel->type->flag & PANEL_TYPE_INSTANCED) { - if (panel_type_context_poll(region, panel->type, context)) { - if (panel == drag_panel) { - BLI_assert(start_index == -1); /* This panel should only appear once. */ - start_index = list_panels_len; - } - list_panels_len++; - } - } - } - } - BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */ - - /* Sort the matching instanced panels by their display order. */ - PanelSort *panel_sort = MEM_callocN(list_panels_len * sizeof(*panel_sort), __func__); - PanelSort *sort_index = panel_sort; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->type) { - if (panel->type->flag & PANEL_TYPE_INSTANCED) { - if (panel_type_context_poll(region, panel->type, context)) { - sort_index->panel = panel; - sort_index++; - } - } - } - } - qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel); - - /* Find how many of those panels are above this panel. */ - int move_to_index = 0; - for (; move_to_index < list_panels_len; move_to_index++) { - if (panel_sort[move_to_index].panel == drag_panel) { - break; - } - } - - MEM_freeN(panel_sort); - - if (move_to_index == start_index) { - /* In this case, the reorder was not changed, so don't do any updates or call the callback. */ - return; - } - - /* Set the bit to tell the interface to instanced the list. */ - drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; - - /* Finally, move this panel's list item to the new index in its list. */ - drag_panel->type->reorder(C, drag_panel, move_to_index); -} - -/** - * Recursive implementation for #panel_set_expansion_from_list_data. - * - * \return Whether the closed flag for the panel or any sub-panels changed. - */ -static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index) -{ - const bool open = (flag & (1 << *flag_index)); - bool changed = (open == UI_panel_is_closed(panel)); - - SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED); - - LISTBASE_FOREACH (Panel *, child, &panel->children) { - *flag_index = *flag_index + 1; - changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index); - } - return changed; -} - -/** - * Set the expansion of the panel and its sub-panels from the flag stored in the - * corresponding list data. The flag has expansion stored in each bit in depth first order. - */ -static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel) -{ - BLI_assert(panel->type != NULL); - BLI_assert(panel->type->flag & PANEL_TYPE_INSTANCED); - if (panel->type->get_list_data_expand_flag == NULL) { - /* Instanced panel doesn't support loading expansion. */ - return; - } - - const short expand_flag = panel->type->get_list_data_expand_flag(C, panel); - short flag_index = 0; - - /* Start panel animation if the open state was changed. */ - if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } -} - -/** - * Set expansion based on the data for instanced panels. - */ -static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - PanelType *panel_type = panel->type; - if (panel_type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { - panel_set_expansion_from_list_data(C, panel); - } - } - } -} - -/** - * Recursive implementation for #set_panels_list_data_expand_flag. - */ -static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index) -{ - const bool open = !(panel->flag & PNL_CLOSED); - SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index)); - - LISTBASE_FOREACH (const Panel *, child, &panel->children) { - *flag_index = *flag_index + 1; - get_panel_expand_flag(child, flag, flag_index); - } -} - -/** - * Call the callback to store the panel and sub-panel expansion settings in the list item that - * corresponds to each instanced panel. - * - * \note This needs to iterate through all of the region's panels because the panel with changed - * expansion might have been the sub-panel of an instanced panel, meaning it might not know - * which list item it corresponds to. - */ -static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - PanelType *panel_type = panel->type; - if (panel_type == NULL) { - continue; - } - - /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */ - if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) { - short expand_flag; - short flag_index = 0; - get_panel_expand_flag(panel, &expand_flag, &flag_index); - if (panel->type->set_list_data_expand_flag) { - panel->type->set_list_data_expand_flag(C, panel, expand_flag); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panels - * \{ */ - -static bool panel_custom_data_active_get(const Panel *panel) -{ - /* The caller should make sure the panel is active and has a type. */ - BLI_assert(UI_panel_is_active(panel)); - BLI_assert(panel->type != NULL); - - if (panel->type->active_property[0] != '\0') { - PointerRNA *ptr = UI_panel_custom_data_get(panel); - if (ptr != NULL && !RNA_pointer_is_null(ptr)) { - return RNA_boolean_get(ptr, panel->type->active_property); - } - } - - return false; -} - -static void panel_custom_data_active_set(Panel *panel) -{ - /* Since the panel is interacted with, it should be active and have a type. */ - BLI_assert(UI_panel_is_active(panel)); - BLI_assert(panel->type != NULL); - - if (panel->type->active_property[0] != '\0') { - PointerRNA *ptr = UI_panel_custom_data_get(panel); - BLI_assert(RNA_struct_find_property(ptr, panel->type->active_property) != NULL); - if (ptr != NULL && !RNA_pointer_is_null(ptr)) { - RNA_boolean_set(ptr, panel->type->active_property, true); - } - } -} - -/** - * Set flag state for a panel and its sub-panels. - */ -static void panel_set_flag_recursive(Panel *panel, short flag, bool value) -{ - SET_FLAG_FROM_TEST(panel->flag, value, flag); - - LISTBASE_FOREACH (Panel *, child, &panel->children) { - panel_set_flag_recursive(child, flag, value); - } -} - -/** - * Set runtime flag state for a panel and its sub-panels. - */ -static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value) -{ - SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag); - - LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) { - panel_set_runtime_flag_recursive(sub_panel, flag, value); - } -} - -static void panels_collapse_all(ARegion *region, const Panel *from_panel) -{ - const bool has_category_tabs = UI_panel_category_is_visible(region); - const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : NULL; - const PanelType *from_pt = from_panel->type; - - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - PanelType *pt = panel->type; - - /* Close panels with headers in the same context. */ - if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) { - if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) { - if ((panel->flag & PNL_PIN) || !category || !pt->category[0] || - STREQ(pt->category, category)) { - panel->flag |= PNL_CLOSED; - } - } - } - } -} - -static bool panel_type_context_poll(ARegion *region, - const PanelType *panel_type, - const char *context) -{ - if (UI_panel_category_is_visible(region)) { - return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); - } - - if (panel_type->context[0] && STREQ(panel_type->context, context)) { - return true; - } - - return false; -} - -Panel *UI_panel_find_by_type(ListBase *lb, const PanelType *pt) -{ - const char *idname = pt->idname; - - LISTBASE_FOREACH (Panel *, panel, lb) { - if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) { - return panel; - } - } - return NULL; -} - -/** - * \note \a panel should be return value from #UI_panel_find_by_type and can be NULL. - */ -Panel *UI_panel_begin( - ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open) -{ - Panel *panel_last; - const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); - const char *idname = pt->idname; - const bool newpanel = (panel == NULL); - - if (newpanel) { - panel = MEM_callocN(sizeof(Panel), __func__); - panel->type = pt; - BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname)); - - if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { - panel->flag |= PNL_CLOSED; - panel->runtime_flag |= PANEL_WAS_CLOSED; - } - - panel->ofsx = 0; - panel->ofsy = 0; - panel->sizex = 0; - panel->sizey = 0; - panel->blocksizex = 0; - panel->blocksizey = 0; - panel->runtime_flag |= PANEL_NEW_ADDED; - - BLI_addtail(lb, panel); - } - else { - /* Panel already exists. */ - panel->type = pt; - } - - panel->runtime.block = block; - - BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname)); - - /* If a new panel is added, we insert it right after the panel that was last added. - * This way new panels are inserted in the right place between versions. */ - for (panel_last = lb->first; panel_last; panel_last = panel_last->next) { - if (panel_last->runtime_flag & PANEL_LAST_ADDED) { - BLI_remlink(lb, panel); - BLI_insertlinkafter(lb, panel_last, panel); - break; - } - } - - if (newpanel) { - panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0; - - LISTBASE_FOREACH (Panel *, panel_next, lb) { - if (panel_next != panel && panel_next->sortorder >= panel->sortorder) { - panel_next->sortorder++; - } - } - } - - if (panel_last) { - panel_last->runtime_flag &= ~PANEL_LAST_ADDED; - } - - /* Assign the new panel to the block. */ - block->panel = panel; - panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED; - if (region->alignment == RGN_ALIGN_FLOAT) { - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - } - - *r_open = false; - - if (UI_panel_is_closed(panel)) { - return panel; - } - - *r_open = true; - - return panel; -} - -/** - * Create the panel header button group, used to mark which buttons are part of - * panel headers for the panel search process that happens later. This Should be - * called before adding buttons for the panel's header layout. - */ -void UI_panel_header_buttons_begin(Panel *panel) -{ - uiBlock *block = panel->runtime.block; - - ui_block_new_button_group(block, UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); -} - -/** - * Finish the button group for the panel header to avoid putting panel body buttons in it. - */ -void UI_panel_header_buttons_end(Panel *panel) -{ - uiBlock *block = panel->runtime.block; - - /* A button group should always be created in #UI_panel_header_buttons_begin. */ - BLI_assert(!BLI_listbase_is_empty(&block->button_groups)); - - uiButtonGroup *button_group = block->button_groups.last; - - button_group->flag &= ~UI_BUTTON_GROUP_LOCK; - - /* Repurpose the first header button group if it is empty, in case the first button added to - * the panel doesn't add a new group (if the button is created directly rather than through an - * interface layout call). */ - if (BLI_listbase_is_single(&block->button_groups) && - BLI_listbase_is_empty(&button_group->buttons)) { - button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER; - } - else { - /* Always add a new button group. Although this may result in many empty groups, without it, - * new buttons in the panel body not protected with a #ui_block_new_button_group call would - * end up in the panel header group. */ - ui_block_new_button_group(block, 0); - } -} - -static float panel_region_offset_x_get(const ARegion *region) -{ - if (UI_panel_category_is_visible(region)) { - if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) { - return UI_PANEL_CATEGORY_MARGIN_WIDTH; - } - } - - return 0.0f; -} - -/** - * Starting from the "block size" set in #UI_panel_end, calculate the full size - * of the panel including the sub-panel headers and buttons. - */ -static void panel_calculate_size_recursive(ARegion *region, Panel *panel) -{ - int width = panel->blocksizex; - int height = panel->blocksizey; - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - if (child_panel->runtime_flag & PANEL_ACTIVE) { - panel_calculate_size_recursive(region, child_panel); - width = max_ii(width, child_panel->sizex); - height += get_panel_real_size_y(child_panel); - } - } - - /* Update total panel size. */ - if (panel->runtime_flag & PANEL_NEW_ADDED) { - panel->runtime_flag &= ~PANEL_NEW_ADDED; - panel->sizex = width; - panel->sizey = height; - } - else { - const int old_sizex = panel->sizex, old_sizey = panel->sizey; - const int old_region_ofsx = panel->runtime.region_ofsx; - - /* Update width/height if non-zero. */ - if (width != 0) { - panel->sizex = width; - } - if (height != 0 || !UI_panel_is_closed(panel)) { - panel->sizey = height; - } - - /* Check if we need to do an animation. */ - if (panel->sizex != old_sizex || panel->sizey != old_sizey) { - panel->runtime_flag |= PANEL_ANIM_ALIGN; - panel->ofsy += old_sizey - panel->sizey; - } - - panel->runtime.region_ofsx = panel_region_offset_x_get(region); - if (old_region_ofsx != panel->runtime.region_ofsx) { - panel->runtime_flag |= PANEL_ANIM_ALIGN; - } - } -} - -void UI_panel_end(Panel *panel, int width, int height) -{ - /* Store the size of the buttons layout in the panel. The actual panel size - * (including sub-panels) is calculated in #UI_panels_end. */ - panel->blocksizex = width; - panel->blocksizey = height; -} - -static void ui_offset_panel_block(uiBlock *block) -{ - const uiStyle *style = UI_style_get_dpi(); - - /* Compute bounds and offset. */ - ui_block_bounds_calc(block); - - const int ofsy = block->panel->sizey - style->panelspace; - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->rect.ymin += ofsy; - but->rect.ymax += ofsy; - } - - block->rect.xmax = block->panel->sizex; - block->rect.ymax = block->panel->sizey; - block->rect.xmin = block->rect.ymin = 0.0; -} - -void ui_panel_tag_search_filter_match(Panel *panel) -{ - panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH; -} - -static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) -{ - *filter_matches |= panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH; - - /* If the panel has no match we need to make sure that its children are too. */ - if (!*filter_matches) { - LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { - panel_matches_search_filter_recursive(child_panel, filter_matches); - } - } -} - -/** - * Find whether a panel or any of its sub-panels contain a property that matches the search filter, - * depending on the search process running in #UI_block_apply_search_filter earlier. - */ -bool UI_panel_matches_search_filter(const Panel *panel) -{ - bool search_filter_matches = false; - panel_matches_search_filter_recursive(panel, &search_filter_matches); - return search_filter_matches; -} - -/** - * Set the flag telling the panel to use its search result status for its expansion. - */ -static void panel_set_expansion_from_search_filter_recursive(const bContext *C, - Panel *panel, - const bool use_search_closed) -{ - /* This has to run on inactive panels that may not have a type, - * but we can prevent running on header-less panels in some cases. */ - if (panel->type == NULL || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) { - SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH); - } - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - /* Don't check if the sub-panel is active, otherwise the - * expansion won't be reset when the parent is closed. */ - panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed); - } -} - -/** - * Set the flag telling every panel to override its expansion with its search result status. - */ -static void region_panels_set_expansion_from_search_filter(const bContext *C, - ARegion *region, - const bool use_search_closed) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - /* Don't check if the panel is active, otherwise the expansion won't - * be correct when switching back to tab after exiting search. */ - panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed); - } - set_panels_list_data_expand_flag(C, region); -} - -/** - * Hide buttons in invisible layouts, which are created because buttons must be - * added for all panels in order to search, even panels that will end up closed. - */ -static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) -{ - uiBlock *block = panel->runtime.block; - BLI_assert(block != NULL); - BLI_assert(block->active); - if (parent_panel != NULL && UI_panel_is_closed(parent_panel)) { - /* The parent panel is closed, so this panel can be completely removed. */ - UI_block_set_search_only(block, true); - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - but->flag |= UI_HIDDEN; - } - } - else if (UI_panel_is_closed(panel)) { - /* If sub-panels have no search results but the parent panel does, then the parent panel open - * and the sub-panels will close. In that case there must be a way to hide the buttons in the - * panel but keep the header buttons. */ - LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { - if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) { - continue; - } - LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { - uiBut *but = link->data; - but->flag |= UI_HIDDEN; - } - } - } - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - if (child_panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(child_panel->runtime.block != NULL); - panel_remove_invisible_layouts_recursive(child_panel, panel); - } - } -} - -static void region_panels_remove_invisible_layouts(ARegion *region) -{ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime.block != NULL); - panel_remove_invisible_layouts_recursive(panel, NULL); - } - } -} - -/** - * Get the panel's expansion state, taking into account - * expansion set from property search if it applies. - */ -bool UI_panel_is_closed(const Panel *panel) -{ - /* Header-less panels can never be closed, otherwise they could disappear. */ - if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) { - return false; - } - - if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) { - return !UI_panel_matches_search_filter(panel); - } - - return panel->flag & PNL_CLOSED; -} - -bool UI_panel_is_active(const Panel *panel) -{ - return panel->runtime_flag & PANEL_ACTIVE; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Drawing - * \{ */ - -/** - * Draw panels, selected (panels currently being dragged) on top. - */ -void UI_panels_draw(const bContext *C, ARegion *region) -{ - /* Draw in reverse order, because #uiBlocks are added in reverse order - * and we need child panels to draw on top. */ - LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && !UI_panel_is_dragging(block->panel) && - !UI_block_is_search_only(block)) { - UI_block_draw(C, block); - } - } - - LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel && UI_panel_is_dragging(block->panel) && - !UI_block_is_search_only(block)) { - UI_block_draw(C, block); - } - } -} - -#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */ - -/* For button layout next to label. */ -void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y) -{ - Panel *panel = block->panel; - const bool is_subpanel = (panel->type && panel->type->parent); - - *r_x = UI_UNIT_X * 1.0f; - *r_y = UI_UNIT_Y * 1.5f; - - if (is_subpanel) { - *r_x += (0.7f * UI_UNIT_X); - } -} - -static void panel_title_color_get(const Panel *panel, - const bool show_background, - const bool region_search_filter_active, - uchar r_color[4]) -{ - if (!show_background) { - /* Use menu colors for floating panels. */ - bTheme *btheme = UI_GetTheme(); - const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; - copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); - return; - } - - const bool search_match = UI_panel_matches_search_filter(panel); - - UI_GetThemeColor4ubv(TH_TITLE, r_color); - if (region_search_filter_active && !search_match) { - r_color[0] *= 0.5; - r_color[1] *= 0.5; - r_color[2] *= 0.5; - } -} - -static void panel_draw_highlight_border(const Panel *panel, - const rcti *rect, - const rcti *header_rect) -{ - const bool draw_box_style = panel->type->flag & PANEL_TYPE_DRAW_BOX; - const bool is_subpanel = panel->type->parent != NULL; - if (is_subpanel) { - return; - } - - float radius; - if (draw_box_style) { - /* Use the theme for box widgets. */ - const uiWidgetColors *box_wcol = &UI_GetTheme()->tui.wcol_box; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - radius = box_wcol->roundness * U.widget_unit; - } - else { - UI_draw_roundbox_corner_set(UI_CNR_NONE); - radius = 0.0f; - } - - float color[4]; - UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin, - .ymax = header_rect->ymax, - }, - false, - radius, - color); -} - -static void panel_draw_aligned_widgets(const uiStyle *style, - const Panel *panel, - const rcti *header_rect, - const float aspect, - const bool show_pin, - const bool show_background, - const bool region_search_filter_active) -{ - const bool is_subpanel = panel->type->parent != NULL; - const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle; - - const int header_height = BLI_rcti_size_y(header_rect); - const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect); - - /* Offset triangle and text to the right for subpanels. */ - const rcti widget_rect = { - .xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0), - .xmax = header_rect->xmax, - .ymin = header_rect->ymin, - .ymax = header_rect->ymax, - }; - - uchar title_color[4]; - panel_title_color_get(panel, show_background, region_search_filter_active, title_color); - title_color[3] = 255; - - /* Draw collapse icon. */ - { - rctf collapse_rect = { - .xmin = widget_rect.xmin, - .xmax = widget_rect.xmin + header_height, - .ymin = widget_rect.ymin, - .ymax = widget_rect.ymax, - }; - BLI_rctf_scale(&collapse_rect, 0.25f); - - float triangle_color[4]; - rgba_uchar_to_float(triangle_color, title_color); - - ui_draw_anti_tria_rect(&collapse_rect, UI_panel_is_closed(panel) ? 'h' : 'v', triangle_color); - } - - /* Draw text label. */ - if (panel->drawname[0] != '\0') { - /* + 0.001f to avoid flirting with float inaccuracy .*/ - const rcti title_rect = { - .xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f, - .xmax = widget_rect.xmax, - .ymin = widget_rect.ymin - 2.0f / aspect, - .ymax = widget_rect.ymax, - }; - UI_fontstyle_draw(fontstyle, - &title_rect, - panel->drawname, - title_color, - &(struct uiFontStyleDraw_Params){ - .align = UI_STYLE_TEXT_LEFT, - }); - } - - /* Draw the pin icon. */ - if (show_pin && (panel->flag & PNL_PIN)) { - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f, - widget_rect.ymin + 5.0f / aspect, - ICON_PINNED, - aspect * U.inv_dpi_fac, - 1.0f, - 0.0f, - title_color, - false); - GPU_blend(GPU_BLEND_NONE); - } - - /* Draw drag widget. */ - if (!is_subpanel && show_background) { - const int drag_widget_size = header_height * 0.7f; - GPU_matrix_push(); - /* The magic numbers here center the widget vertically and offset it to the left. - * Currently this depends on the height of the header, although it could be independent. */ - GPU_matrix_translate_2f(widget_rect.xmax - scaled_unit * 1.15, - widget_rect.ymin + (header_height - drag_widget_size) * 0.5f); - - const int col_tint = 84; - float color_high[4], color_dark[4]; - UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, color_high); - UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, color_dark); - - GPUBatch *batch = GPU_batch_preset_panel_drag_widget( - U.pixelsize, color_high, color_dark, drag_widget_size); - GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_FLAT_COLOR); - GPU_batch_draw(batch); - GPU_matrix_pop(); - } -} - -static void panel_draw_aligned_backdrop(const Panel *panel, - const rcti *rect, - const rcti *header_rect) -{ - const bool draw_box_style = panel->type->flag & PANEL_TYPE_DRAW_BOX; - const bool is_subpanel = panel->type->parent != NULL; - const bool is_open = !UI_panel_is_closed(panel); - - if (is_subpanel && !is_open) { - return; - } - - const uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - /* Draw with an opaque box backdrop for box style panels. */ - if (draw_box_style) { - /* Use the theme for box widgets. */ - const uiWidgetColors *box_wcol = &UI_GetTheme()->tui.wcol_box; - - if (is_subpanel) { - /* Use rounded bottom corners for the last subpanel. */ - if (panel->next == NULL) { - UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT); - float color[4]; - UI_GetThemeColor4fv(TH_PANEL_SUB_BACK, color); - /* Change the width a little bit to line up with sides. */ - UI_draw_roundbox_aa( - &(const rctf){ - .xmin = rect->xmin + U.pixelsize, - .xmax = rect->xmax - U.pixelsize, - .ymin = rect->ymin + U.pixelsize, - .ymax = rect->ymax, - }, - true, - box_wcol->roundness * U.widget_unit, - color); - } - else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformThemeColor(TH_PANEL_SUB_BACK); - immRectf(pos, rect->xmin + U.pixelsize, rect->ymin, rect->xmax - U.pixelsize, rect->ymax); - immUnbindProgram(); - } - } - else { - /* Expand the top a tiny bit to give header buttons equal size above and below. */ - rcti box_rect = { - .xmin = rect->xmin, - .xmax = rect->xmax, - .ymin = is_open ? rect->ymin : header_rect->ymin, - .ymax = header_rect->ymax + U.pixelsize, - }; - ui_draw_box_opaque(&box_rect, UI_CNR_ALL); - - /* Mimic the border between aligned box widgets for the bottom of the header. */ - if (is_open) { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Top line. */ - immUniformColor4ubv(box_wcol->outline); - immRectf(pos, rect->xmin, header_rect->ymin - U.pixelsize, rect->xmax, header_rect->ymin); - - /* Bottom "shadow" line. */ - immUniformThemeColor(TH_WIDGET_EMBOSS); - immRectf(pos, - rect->xmin, - header_rect->ymin - U.pixelsize, - rect->xmax, - header_rect->ymin - U.pixelsize - 1); - - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); - } - } - } - else { - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - GPU_blend(GPU_BLEND_ALPHA); - - /* Panel backdrop. */ - if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { - immUniformThemeColor(is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK); - immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); - } - - /* Panel header backdrops for non sub-panels. */ - if (!is_subpanel) { - immUniformThemeColor(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER); - immRectf(pos, rect->xmin, header_rect->ymin, rect->xmax, header_rect->ymax); - } - - GPU_blend(GPU_BLEND_NONE); - immUnbindProgram(); - } -} - -/** - * Draw a panel integrated in buttons-window, tool/property lists etc. - */ -void ui_draw_aligned_panel(const uiStyle *style, - const uiBlock *block, - const rcti *rect, - const bool show_pin, - const bool show_background, - const bool region_search_filter_active) -{ - const Panel *panel = block->panel; - - /* Add 0.001f to prevent flicker from float inaccuracy. */ - const rcti header_rect = { - rect->xmin, - rect->xmax, - rect->ymax, - rect->ymax + floor(PNL_HEADER / block->aspect + 0.001f), - }; - - if (show_background) { - panel_draw_aligned_backdrop(panel, rect, &header_rect); - } - - /* Draw the widgets and text in the panel header. */ - if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) { - panel_draw_aligned_widgets(style, - panel, - &header_rect, - block->aspect, - show_pin, - show_background, - region_search_filter_active); - } - - if (panel_custom_data_active_get(panel)) { - panel_draw_highlight_border(panel, rect, &header_rect); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Category Drawing (Tabs) - * \{ */ - -#define TABS_PADDING_BETWEEN_FACTOR 4.0f -#define TABS_PADDING_TEXT_FACTOR 6.0f - -/** - * Draw vertical tabs on the left side of the region, one tab per category. - */ -void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) -{ - // #define USE_FLAT_INACTIVE - const bool is_left = RGN_ALIGN_ENUM_FROM_MASK(region->alignment != RGN_ALIGN_RIGHT); - View2D *v2d = ®ion->v2d; - const uiStyle *style = UI_style_get(); - const uiFontStyle *fstyle = &style->widget; - const int fontid = fstyle->uifont_id; - short fstyle_points = fstyle->points; - const float aspect = ((uiBlock *)region->uiblocks.first)->aspect; - const float zoom = 1.0f / aspect; - const int px = U.pixelsize; - const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom); - const float dpi_fac = UI_DPI_FAC; - /* Padding of tabs around text. */ - const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px; - /* Padding between tabs. */ - const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom); - bTheme *btheme = UI_GetTheme(); - const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom; - const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) : - (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT); - bool is_alpha; - bool do_scaletabs = false; -#ifdef USE_FLAT_INACTIVE - bool is_active_prev = false; -#endif - float scaletabs = 1.0f; - /* Same for all tabs. */ - /* Intentionally don't scale by 'px'. */ - const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width); - const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3); - const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f; - - int y_ofs = tab_v_pad; - - /* Primary theme colors. */ - uchar theme_col_back[4]; - uchar theme_col_text[3]; - uchar theme_col_text_hi[3]; - - /* Tab colors. */ - uchar theme_col_tab_bg[4]; - float theme_col_tab_active[4]; - float theme_col_tab_inactive[4]; - float theme_col_tab_outline[4]; - - UI_GetThemeColor4ubv(TH_BACK, theme_col_back); - UI_GetThemeColor3ubv(TH_TEXT, theme_col_text); - UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi); - - UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg); - UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active); - UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive); - UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline); - - is_alpha = (region->overlap && (theme_col_back[3] != 255)); - - if (fstyle->kerning == 1) { - BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } - - BLF_enable(fontid, BLF_ROTATION); - BLF_rotation(fontid, M_PI_2); - // UI_fontstyle_set(&style->widget); - ui_fontscale(&fstyle_points, aspect / (U.pixelsize * 1.1f)); - BLF_size(fontid, fstyle_points, U.dpi); - - /* Check the region type supports categories to avoid an assert - * for showing 3D view panels in the properties space. */ - if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) { - BLI_assert(UI_panel_category_is_visible(region)); - } - - /* Calculate tab rectangle and check if we need to scale down. */ - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - rcti *rct = &pc_dyn->rect; - const char *category_id = pc_dyn->idname; - const char *category_id_draw = IFACE_(category_id); - const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); - - rct->xmin = rct_xmin; - rct->xmax = rct_xmax; - - rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2)); - rct->ymax = v2d->mask.ymax - (y_ofs); - - y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2); - } - - if (y_ofs > BLI_rcti_size_y(&v2d->mask)) { - scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs; - - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - rcti *rct = &pc_dyn->rect; - rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; - rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; - } - - do_scaletabs = true; - } - - /* Begin drawing. */ - GPU_line_smooth(true); - - uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - /* Draw the background. */ - if (is_alpha) { - GPU_blend(GPU_BLEND_ALPHA); - immUniformColor4ubv(theme_col_tab_bg); - } - else { - immUniformColor3ubv(theme_col_tab_bg); - } - - if (is_left) { - immRecti( - pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax); - } - else { - immRecti( - pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax); - } - - if (is_alpha) { - GPU_blend(GPU_BLEND_NONE); - } - - immUnbindProgram(); - - LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { - const rcti *rct = &pc_dyn->rect; - const char *category_id = pc_dyn->idname; - const char *category_id_draw = IFACE_(category_id); - const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2); - size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX; -#if 0 - int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); -#endif - - const bool is_active = STREQ(category_id, category_id_active); - - GPU_blend(GPU_BLEND_ALPHA); - -#ifdef USE_FLAT_INACTIVE - /* Draw line between inactive tabs. */ - if (is_active == false && is_active_prev == false && pc_dyn->prev) { - pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f); - immRecti(pos, - is_left ? v2d->mask.xmin + (category_tabs_width / 5) : - v2d->mask.xmax - (category_tabs_width / 5), - rct->ymax + px, - is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) : - (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5), - rct->ymax + (px * 3)); - immUnbindProgram(); - } - - is_active_prev = is_active; - - if (is_active) -#endif - { - /* Draw filled rectangle and outline for tab. */ - UI_draw_roundbox_corner_set(roundboxtype); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin, - .xmax = rct->xmax, - .ymin = rct->ymin, - .ymax = rct->ymax, - }, - true, - tab_curve_radius, - is_active ? theme_col_tab_active : theme_col_tab_inactive); - UI_draw_roundbox_4fv( - &(const rctf){ - .xmin = rct->xmin, - .xmax = rct->xmax, - .ymin = rct->ymin, - .ymax = rct->ymax, - }, - false, - tab_curve_radius, - theme_col_tab_outline); - - /* Disguise the outline on one side to join the tab to the panel. */ - pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - - immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive); - immRecti(pos, - is_left ? rct->xmax - px : rct->xmin, - rct->ymin + px, - is_left ? rct->xmax : rct->xmin + px, - rct->ymax - px); - immUnbindProgram(); - } - - /* Tab titles. */ - - if (do_scaletabs) { - category_draw_len = BLF_width_to_strlen( - fontid, category_id_draw, category_draw_len, category_width, NULL); - } - - BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); - BLF_color3ubv(fontid, theme_col_text); - BLF_draw(fontid, category_id_draw, category_draw_len); - - GPU_blend(GPU_BLEND_NONE); - - /* Not essential, but allows events to be handled right up to the region edge (T38171). */ - if (is_left) { - pc_dyn->rect.xmin = v2d->mask.xmin; - } - else { - pc_dyn->rect.xmax = v2d->mask.xmax; - } - } - - GPU_line_smooth(false); - - BLF_disable(fontid, BLF_ROTATION); - - if (fstyle->kerning == 1) { - BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); - } -} - -#undef TABS_PADDING_BETWEEN_FACTOR -#undef TABS_PADDING_TEXT_FACTOR - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Alignment - * \{ */ - -static int get_panel_size_y(const Panel *panel) -{ - if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { - return panel->sizey; - } - - return PNL_HEADER + panel->sizey; -} - -static int get_panel_real_size_y(const Panel *panel) -{ - const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey; - - if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { - return sizey; - } - - return PNL_HEADER + sizey; -} - -int UI_panel_size_y(const Panel *panel) -{ - return get_panel_real_size_y(panel); -} - -/** - * This function is needed because #uiBlock and Panel itself don't - * change #Panel.sizey or location when closed. - */ -static int get_panel_real_ofsy(Panel *panel) -{ - if (UI_panel_is_closed(panel)) { - return panel->ofsy + panel->sizey; - } - return panel->ofsy; -} - -bool UI_panel_is_dragging(const Panel *panel) -{ - return panel->runtime_flag & PANEL_IS_DRAG_DROP; -} - -/** - * \note about sorting: - * The #Panel.sortorder has a lower value for new panels being added. - * however, that only works to insert a single panel, when more new panels get - * added the coordinates of existing panels and the previously stored to-be-inserted - * panels do not match for sorting. - */ - -static int find_highest_panel(const void *a, const void *b) -{ - const Panel *panel_a = ((PanelSort *)a)->panel; - const Panel *panel_b = ((PanelSort *)b)->panel; - - /* Stick uppermost header-less panels to the top of the region - - * prevent them from being sorted (multiple header-less panels have to be sorted though). */ - if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) { - /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */ - } - else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) { - return -1; - } - else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) { - return 1; - } - - if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) { - return 1; - } - if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) { - return -1; - } - if (panel_a->sortorder > panel_b->sortorder) { - return 1; - } - if (panel_a->sortorder < panel_b->sortorder) { - return -1; - } - - return 0; -} - -static int compare_panel(const void *a, const void *b) -{ - const Panel *panel_a = ((PanelSort *)a)->panel; - const Panel *panel_b = ((PanelSort *)b)->panel; - - if (panel_a->sortorder > panel_b->sortorder) { - return 1; - } - if (panel_a->sortorder < panel_b->sortorder) { - return -1; - } - - return 0; -} - -static void align_sub_panels(Panel *panel) -{ - /* Position sub panels. */ - int ofsy = panel->ofsy + panel->sizey - panel->blocksizey; - - LISTBASE_FOREACH (Panel *, pachild, &panel->children) { - if (pachild->runtime_flag & PANEL_ACTIVE) { - pachild->ofsx = panel->ofsx; - pachild->ofsy = ofsy - get_panel_size_y(pachild); - ofsy -= get_panel_real_size_y(pachild); - - if (pachild->children.first) { - align_sub_panels(pachild); - } - } - } -} - -/** - * Calculate the position and order of panels as they are opened, closed, and dragged. - */ -static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag) -{ - /* Count active panels. */ - int active_panels_len = 0; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - /* These panels should have types since they are currently displayed to the user. */ - BLI_assert(panel->type != NULL); - active_panels_len++; - } - } - if (active_panels_len == 0) { - return false; - } - - /* Sort panels. */ - PanelSort *panel_sort = MEM_mallocN(sizeof(PanelSort) * active_panels_len, __func__); - { - PanelSort *ps = panel_sort; - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - ps->panel = panel; - ps++; - } - } - } - - if (drag) { - /* While dragging, sort based on location and update #Panel.sortorder. */ - qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel); - for (int i = 0; i < active_panels_len; i++) { - panel_sort[i].panel->sortorder = i; - } - } - else { - /* Otherwise use #Panel.sortorder. */ - qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel); - } - - /* X offset. */ - const int region_offset_x = panel_region_offset_x_get(region); - for (int i = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - const bool use_box = ps->panel->type->flag & PANEL_TYPE_DRAW_BOX; - ps->panel->runtime.region_ofsx = region_offset_x; - ps->new_offset_x = region_offset_x + ((use_box) ? UI_PANEL_BOX_STYLE_MARGIN : 0); - } - - /* Y offset. */ - for (int i = 0, y = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - y -= get_panel_real_size_y(ps->panel); - - const bool use_box = ps->panel->type->flag & PANEL_TYPE_DRAW_BOX; - if (use_box) { - y -= UI_PANEL_BOX_STYLE_MARGIN; - } - ps->new_offset_y = y; - /* The header still draws offset by the size of closed panels, so apply the offset here. */ - if (UI_panel_is_closed(ps->panel)) { - panel_sort[i].new_offset_y -= ps->panel->sizey; - } - } - - /* Interpolate based on the input factor. */ - bool changed = false; - for (int i = 0; i < active_panels_len; i++) { - PanelSort *ps = &panel_sort[i]; - if (ps->panel->flag & PNL_SELECT) { - continue; - } - - if (ps->new_offset_x != ps->panel->ofsx) { - const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor); - ps->panel->ofsx = round_fl_to_int(x); - changed = true; - } - if (ps->new_offset_y != ps->panel->ofsy) { - const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor); - ps->panel->ofsy = round_fl_to_int(y); - changed = true; - } - } - - /* Set locations for tabbed and sub panels. */ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - if (panel->children.first) { - align_sub_panels(panel); - } - } - } - - MEM_freeN(panel_sort); - - return changed; -} - -static void ui_panels_size(ARegion *region, int *r_x, int *r_y) -{ - int sizex = 0; - int sizey = 0; - - /* Compute size taken up by panels, for setting in view2d. */ - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - const int pa_sizex = panel->ofsx + panel->sizex; - const int pa_sizey = get_panel_real_ofsy(panel); - - sizex = max_ii(sizex, pa_sizex); - sizey = min_ii(sizey, pa_sizey); - } - } - - if (sizex == 0) { - sizex = UI_PANEL_WIDTH; - } - if (sizey == 0) { - sizey = -UI_PANEL_WIDTH; - } - - *r_x = sizex; - *r_y = sizey; -} - -static void ui_do_animate(bContext *C, Panel *panel) -{ - uiHandlePanelData *data = panel->activedata; - ARegion *region = CTX_wm_region(C); - - float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME; - fac = min_ff(sqrtf(fac), 1.0f); - - if (uiAlignPanelStep(region, fac, false)) { - ED_region_tag_redraw(region); - } - else { - if (UI_panel_is_dragging(panel)) { - /* Note: doing this in #panel_activate_state would require - * removing `const` for context in many other places. */ - reorder_instanced_panel_list(C, region, panel); - } - - panel_activate_state(C, panel, PANEL_STATE_EXIT); - } -} - -static void panels_layout_begin_clear_flags(ListBase *lb) -{ - LISTBASE_FOREACH (Panel *, panel, lb) { - /* Flags to copy over to the next layout pass. */ - const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP; - - const bool was_active = panel->runtime_flag & PANEL_ACTIVE; - const bool was_closed = UI_panel_is_closed(panel); - panel->runtime_flag &= flag_copy; - SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE); - SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED); - - panels_layout_begin_clear_flags(&panel->children); - } -} - -void UI_panels_begin(const bContext *UNUSED(C), ARegion *region) -{ - /* Set all panels as inactive, so that at the end we know which ones were used. Also - * clear other flags so we know later that their values were set for the current redraw. */ - panels_layout_begin_clear_flags(®ion->panels); -} - -void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) -{ - ScrArea *area = CTX_wm_area(C); - - region_panels_set_expansion_from_list_data(C, region); - - const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE; - - if (properties_space_needs_realign(area, region)) { - region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); - } - else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { - region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); - } - - if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { - /* Clean up the extra panels and buttons created for searching. */ - region_panels_remove_invisible_layouts(region); - } - - LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { - if (panel->runtime_flag & PANEL_ACTIVE) { - BLI_assert(panel->runtime.block != NULL); - panel_calculate_size_recursive(region, panel); - } - } - - /* Offset contents. */ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (block->active && block->panel) { - ui_offset_panel_block(block); - } - } - - /* Re-align, possibly with animation. */ - Panel *panel; - if (panels_need_realign(area, region, &panel)) { - if (panel) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - else { - uiAlignPanelStep(region, 1.0, false); - } - } - - /* Compute size taken up by panels. */ - ui_panels_size(region, r_x, r_y); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Panel Dragging - * \{ */ - -#define DRAG_REGION_PAD (PNL_HEADER * 0.5) -static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) -{ - uiHandlePanelData *data = panel->activedata; - ARegion *region = CTX_wm_region(C); - - /* Keep the drag position in the region with a small pad to keep the panel visible. */ - const int y = clamp_i(event->y, region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); - - float dy = (float)(y - data->starty); - - /* Adjust for region zoom. */ - dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); - - /* Add the movement of the view due to edge scrolling while dragging. */ - dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); - - panel->ofsy = data->startofsy + round_fl_to_int(dy); - - uiAlignPanelStep(region, 0.2f, true); - - ED_region_tag_redraw(region); -} -#undef DRAG_REGION_PAD - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Region Level Panel Interaction - * \{ */ - -static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, - const Panel *panel, - const int mx, - const int my) -{ - if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) { - return PANEL_MOUSE_OUTSIDE; - } - - if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) { - return PANEL_MOUSE_INSIDE_HEADER; - } - - if (!UI_panel_is_closed(panel)) { - if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) { - return PANEL_MOUSE_INSIDE_CONTENT; - } - } - - return PANEL_MOUSE_OUTSIDE; -} - -typedef struct uiPanelDragCollapseHandle { - bool was_first_open; - int xy_init[2]; -} uiPanelDragCollapseHandle; - -static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata) -{ - uiPanelDragCollapseHandle *dragcol_data = userdata; - MEM_freeN(dragcol_data); -} - -static void ui_panel_drag_collapse(const bContext *C, - const uiPanelDragCollapseHandle *dragcol_data, - const int xy_dst[2]) -{ - ARegion *region = CTX_wm_region(C); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float xy_a_block[2] = {UNPACK2(dragcol_data->xy_init)}; - float xy_b_block[2] = {UNPACK2(xy_dst)}; - Panel *panel = block->panel; - - if (panel == NULL || (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER))) { - continue; - } - const int oldflag = panel->flag; - - /* Lock axis. */ - xy_b_block[0] = dragcol_data->xy_init[0]; - - /* Use cursor coords in block space. */ - ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); - ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); - - /* Set up `rect` to match header size. */ - rctf rect = block->rect; - rect.ymin = rect.ymax; - rect.ymax = rect.ymin + PNL_HEADER; - - /* Touch all panels between last mouse coordinate and the current one. */ - if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) { - /* Force panel to open or close. */ - panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; - SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED); - - /* If panel->flag has changed this means a panel was opened/closed here. */ - if (panel->flag != oldflag) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - } - } - /* Update the instanced panel data expand flags with the changes made here. */ - set_panels_list_data_expand_flag(C, region); -} - -/** - * Panel drag-collapse (modal handler). - * Clicking and dragging over panels toggles their collapse state based on the panel - * that was first dragged over. If it was open all affected panels including the initial - * one are closed and vice versa. - */ -static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata) -{ - wmWindow *win = CTX_wm_window(C); - uiPanelDragCollapseHandle *dragcol_data = userdata; - short retval = WM_UI_HANDLER_CONTINUE; - - switch (event->type) { - case MOUSEMOVE: - ui_panel_drag_collapse(C, dragcol_data, &event->x); - - retval = WM_UI_HANDLER_BREAK; - break; - case LEFTMOUSE: - if (event->val == KM_RELEASE) { - /* Done! */ - WM_event_remove_ui_handler(&win->modalhandlers, - ui_panel_drag_collapse_handler, - ui_panel_drag_collapse_handler_remove, - dragcol_data, - true); - ui_panel_drag_collapse_handler_remove(C, dragcol_data); - } - /* Don't let any left-mouse event fall through! */ - retval = WM_UI_HANDLER_BREAK; - break; - } - - return retval; -} - -static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open) -{ - wmWindow *win = CTX_wm_window(C); - const wmEvent *event = win->eventstate; - uiPanelDragCollapseHandle *dragcol_data = MEM_mallocN(sizeof(*dragcol_data), __func__); - - dragcol_data->was_first_open = was_open; - copy_v2_v2_int(dragcol_data->xy_init, &event->x); - - WM_event_add_ui_handler(C, - &win->modalhandlers, - ui_panel_drag_collapse_handler, - ui_panel_drag_collapse_handler_remove, - dragcol_data, - 0); -} - -/** - * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc. - * Code currently assumes layout style for location of widgets - * - * \param mx: The mouse x coordinate, in panel space. - */ -static void ui_handle_panel_header(const bContext *C, - const uiBlock *block, - const int mx, - const int event_type, - const short ctrl, - const short shift) -{ - Panel *panel = block->panel; - ARegion *region = CTX_wm_region(C); - - BLI_assert(panel->type != NULL); - BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); - - const bool is_subpanel = (panel->type->parent != NULL); - const bool use_pin = UI_panel_category_is_visible(region) && !is_subpanel; - const bool show_pin = use_pin && (panel->flag & PNL_PIN); - const bool show_drag = !is_subpanel; - - /* Handle panel pinning. */ - if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) { - panel->flag ^= PNL_PIN; - ED_region_tag_redraw(region); - return; - } - - float expansion_area_xmax = block->rect.xmax; - if (show_drag) { - expansion_area_xmax -= (PNL_ICON * 1.5f); - } - if (show_pin) { - expansion_area_xmax -= PNL_ICON; - } - - /* Collapse and expand panels. */ - if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) { - if (ctrl && !is_subpanel) { - /* For parent panels, collapse all other panels or toggle children. */ - if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) { - panels_collapse_all(region, panel); - - /* Reset the view - we don't want to display a view without content. */ - UI_view2d_offset(®ion->v2d, 0.0f, 1.0f); - } - else { - /* If a panel has sub-panels and it's open, toggle the expansion - * of the sub-panels (based on the expansion of the first sub-panel). */ - Panel *first_child = panel->children.first; - BLI_assert(first_child != NULL); - panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child)); - panel->flag |= PNL_CLOSED; - } - } - - SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED); - - if (event_type == LEFTMOUSE) { - ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel)); - } - - /* Set panel custom data (modifier) active when expanding subpanels, but not top-level - * panels to allow collapsing and expanding without setting the active element. */ - if (is_subpanel) { - panel_custom_data_active_set(panel); - } - - set_panels_list_data_expand_flag(C, region); - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - return; - } - - /* Handle panel dragging. For now don't allow dragging in floating regions. */ - if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) { - const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f); - const float drag_area_xmax = block->rect.xmax; - if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) { - panel_activate_state(C, panel, PANEL_STATE_DRAG); - return; - } - } - - /* Handle panel unpinning. */ - if (show_pin) { - const float pin_area_xmin = expansion_area_xmax; - const float pin_area_xmax = pin_area_xmin + PNL_ICON; - if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) { - panel->flag ^= PNL_PIN; - ED_region_tag_redraw(region); - return; - } - } -} - -bool UI_panel_category_is_visible(const ARegion *region) -{ - /* Check for more than one category. */ - return region->panels_category.first && - region->panels_category.first != region->panels_category.last; -} - -PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname) -{ - return BLI_findstring(®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname)); -} - -PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname) -{ - return BLI_findstring( - ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname)); -} - -static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback) -{ - ListBase *lb = ®ion->panels_category_active; - PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname); - - if (pc_act) { - BLI_remlink(lb, pc_act); - } - else { - pc_act = MEM_callocN(sizeof(PanelCategoryStack), __func__); - BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname)); - } - - if (fallback) { - /* For fall-backs, add at the end so explicitly chosen categories have priority. */ - BLI_addtail(lb, pc_act); - } - else { - BLI_addhead(lb, pc_act); - } - - /* Validate all active panels. We could do this on load, they are harmless - - * but we should remove them somewhere. - * (Add-ons could define panels and gather cruft over time). */ - { - PanelCategoryStack *pc_act_next; - /* intentionally skip first */ - pc_act_next = pc_act->next; - while ((pc_act = pc_act_next)) { - pc_act_next = pc_act->next; - if (!BLI_findstring( - ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) { - BLI_remlink(lb, pc_act); - MEM_freeN(pc_act); - } - } - } -} - -void UI_panel_category_active_set(ARegion *region, const char *idname) -{ - ui_panel_category_active_set(region, idname, false); -} - -void UI_panel_category_active_set_default(ARegion *region, const char *idname) -{ - if (!UI_panel_category_active_find(region, idname)) { - ui_panel_category_active_set(region, idname, true); - } -} - -const char *UI_panel_category_active_get(ARegion *region, bool set_fallback) -{ - LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) { - if (UI_panel_category_find(region, pc_act->idname)) { - return pc_act->idname; - } - } - - if (set_fallback) { - PanelCategoryDyn *pc_dyn = region->panels_category.first; - if (pc_dyn) { - ui_panel_category_active_set(region, pc_dyn->idname, true); - return pc_dyn->idname; - } - } - - return NULL; -} - -static PanelCategoryDyn *panel_categories_find_mouse_over(ARegion *region, const wmEvent *event) -{ - LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) { - if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) { - return ptd; - } - } - - return NULL; -} - -void UI_panel_category_add(ARegion *region, const char *name) -{ - PanelCategoryDyn *pc_dyn = MEM_callocN(sizeof(*pc_dyn), __func__); - BLI_addtail(®ion->panels_category, pc_dyn); - - BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname)); - - /* 'pc_dyn->rect' must be set on draw. */ -} - -void UI_panel_category_clear_all(ARegion *region) -{ - BLI_freelistN(®ion->panels_category); -} - -static int ui_handle_panel_category_cycling(const wmEvent *event, - ARegion *region, - const uiBut *active_but) -{ - const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE); - const bool inside_tabregion = - ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ? - (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) : - (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin)); - - /* If mouse is inside non-tab region, ctrl key is required. */ - if (is_mousewheel && !event->ctrl && !inside_tabregion) { - return WM_UI_HANDLER_CONTINUE; - } - - if (active_but && ui_but_supports_cycling(active_but)) { - /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */ - } - else { - const char *category = UI_panel_category_active_get(region, false); - if (LIKELY(category)) { - PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category); - if (LIKELY(pc_dyn)) { - if (is_mousewheel) { - /* We can probably get rid of this and only allow Ctrl-Tabbing. */ - pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev; - } - else { - const bool backwards = event->shift; - pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next; - if (!pc_dyn) { - /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */ - pc_dyn = backwards ? region->panels_category.last : region->panels_category.first; - } - } - - if (pc_dyn) { - /* Intentionally don't reset scroll in this case, - * allowing for quick browsing between tabs. */ - UI_panel_category_active_set(region, pc_dyn->idname); - ED_region_tag_redraw(region); - } - } - } - return WM_UI_HANDLER_BREAK; - } - - return WM_UI_HANDLER_CONTINUE; -} - -/** - * Handle region panel events like opening and closing panels, changing categories, etc. - * - * \note Could become a modal key-map. - */ -int ui_handler_panel_region(bContext *C, - const wmEvent *event, - ARegion *region, - const uiBut *active_but) -{ - /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ - if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { - return WM_UI_HANDLER_CONTINUE; - } - - /* We only use KM_PRESS events in this function, so it's simpler to return early. */ - if (event->val != KM_PRESS) { - return WM_UI_HANDLER_CONTINUE; - } - - /* Scroll-bars can overlap panels now, they have handling priority. */ - if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->x, event->y)) { - return WM_UI_HANDLER_CONTINUE; - } - - int retval = WM_UI_HANDLER_CONTINUE; - - /* Handle category tabs. */ - if (UI_panel_category_is_visible(region)) { - if (event->type == LEFTMOUSE) { - PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event); - if (pc_dyn) { - UI_panel_category_active_set(region, pc_dyn->idname); - ED_region_tag_redraw(region); - - /* Reset scroll to the top (T38348). */ - UI_view2d_offset(®ion->v2d, -1.0f, 1.0f); - - retval = WM_UI_HANDLER_BREAK; - } - } - else if ((event->type == EVT_TABKEY && event->ctrl) || - ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { - /* Cycle tabs. */ - retval = ui_handle_panel_category_cycling(event, region, active_but); - } - } - - if (retval == WM_UI_HANDLER_BREAK) { - return retval; - } - - const bool region_has_active_button = (ui_region_find_active_but(region) != NULL); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - Panel *panel = block->panel; - if (panel == NULL || panel->type == NULL) { - continue; - } - /* We can't expand or collapse panels without headers, they would disappear. */ - if (panel->type->flag & PANEL_TYPE_NO_HEADER) { - continue; - } - - int mx = event->x; - int my = event->y; - ui_window_to_block(region, block, &mx, &my); - - const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); - - if (mouse_state != PANEL_MOUSE_OUTSIDE) { - /* Mark panels that have been interacted with so their expansion - * doesn't reset when property search finishes. */ - SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED); - panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; - - /* The panel collapse / expand key "A" is special as it takes priority over - * active button handling. */ - if (event->type == EVT_AKEY && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { - retval = WM_UI_HANDLER_BREAK; - ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift); - break; - } - } - - /* Don't do any other panel handling with an active button. */ - if (region_has_active_button) { - continue; - } - - if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) { - /* All mouse clicks inside panel headers should return in break. */ - if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) { - retval = WM_UI_HANDLER_BREAK; - ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift); - } - else if (event->type == RIGHTMOUSE) { - retval = WM_UI_HANDLER_BREAK; - ui_popup_context_menu_for_panel(C, region, block->panel); - } - break; - } - } - - return retval; -} - -static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) -{ - panel->runtime.custom_data_ptr = custom_data; - - LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { - ui_panel_custom_data_set_recursive(child_panel, custom_data); - } -} - -void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) -{ - BLI_assert(panel->type != NULL); - - /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ - if (panel->runtime.custom_data_ptr != NULL) { - MEM_freeN(panel->runtime.custom_data_ptr); - } - - ui_panel_custom_data_set_recursive(panel, custom_data); -} - -PointerRNA *UI_panel_custom_data_get(const Panel *panel) -{ - return panel->runtime.custom_data_ptr; -} - -PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) -{ - ARegion *region = CTX_wm_region(C); - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - Panel *panel = block->panel; - if (panel == NULL) { - continue; - } - - int mx = event->x; - int my = event->y; - ui_window_to_block(region, block, &mx, &my); - const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); - if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { - return UI_panel_custom_data_get(panel); - } - } - - return NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Window Level Modal Panel Interaction - * \{ */ - -/* Note, this is modal handler and should not swallow events for animation. */ -static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) -{ - Panel *panel = userdata; - uiHandlePanelData *data = panel->activedata; - - /* Verify if we can stop. */ - if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { - panel_activate_state(C, panel, PANEL_STATE_ANIMATION); - } - else if (event->type == MOUSEMOVE) { - if (data->state == PANEL_STATE_DRAG) { - ui_do_drag(C, event, panel); - } - } - else if (event->type == TIMER && event->customdata == data->animtimer) { - if (data->state == PANEL_STATE_ANIMATION) { - ui_do_animate(C, panel); - } - else if (data->state == PANEL_STATE_DRAG) { - ui_do_drag(C, event, panel); - } - } - - data = panel->activedata; - - if (data && data->state == PANEL_STATE_ANIMATION) { - return WM_UI_HANDLER_CONTINUE; - } - return WM_UI_HANDLER_BREAK; -} - -static void ui_handler_remove_panel(bContext *C, void *userdata) -{ - Panel *panel = userdata; - - panel_activate_state(C, panel, PANEL_STATE_EXIT); -} - -static void panel_handle_data_ensure(const bContext *C, - wmWindow *win, - const ARegion *region, - Panel *panel, - const uiHandlePanelState state) -{ - if (panel->activedata == NULL) { - panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__); - WM_event_add_ui_handler( - C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0); - } - - uiHandlePanelData *data = panel->activedata; - - data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); - - data->state = state; - data->startx = win->eventstate->x; - data->starty = win->eventstate->y; - data->startofsx = panel->ofsx; - data->startofsy = panel->ofsy; - data->start_cur_xmin = region->v2d.cur.xmin; - data->start_cur_ymin = region->v2d.cur.ymin; - data->starttime = PIL_check_seconds_timer(); -} - -/** - * \note "select" and "drag drop" flags: First, the panel is "picked up" and both flags are set. - * Then when the mouse releases and the panel starts animating to its aligned position, PNL_SELECT - * is unset. When the animation finishes, PANEL_IS_DRAG_DROP is cleared. - */ -static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state) -{ - uiHandlePanelData *data = panel->activedata; - wmWindow *win = CTX_wm_window(C); - ARegion *region = CTX_wm_region(C); - - if (data != NULL && data->state == state) { - return; - } - - if (state == PANEL_STATE_DRAG) { - panel_custom_data_active_set(panel); - - panel_set_flag_recursive(panel, PNL_SELECT, true); - panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, true); - - panel_handle_data_ensure(C, win, region, panel, state); - - /* Initiate edge panning during drags for scrolling beyond the initial region view. */ - wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); - ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true); - } - else if (state == PANEL_STATE_ANIMATION) { - panel_set_flag_recursive(panel, PNL_SELECT, false); - - panel_handle_data_ensure(C, win, region, panel, state); - } - else if (state == PANEL_STATE_EXIT) { - panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, false); - - BLI_assert(data != NULL); - - if (data->animtimer) { - WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer); - data->animtimer = NULL; - } - - MEM_freeN(data); - panel->activedata = NULL; - - WM_event_remove_ui_handler( - &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false); - } - - ED_region_tag_redraw(region); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_panel.cc b/source/blender/editors/interface/interface_panel.cc new file mode 100644 index 00000000000..74d19bb4e6a --- /dev/null +++ b/source/blender/editors/interface/interface_panel.cc @@ -0,0 +1,2692 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +/* a full doc with API notes can be found in + * bf-blender/trunk/blender/doc/guides/interface_API.txt */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "PIL_time.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "DNA_screen_types.h" +#include "DNA_userdef_types.h" + +#include "BKE_context.h" +#include "BKE_screen.h" + +#include "RNA_access.h" + +#include "BLF_api.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_resources.h" +#include "UI_view2d.h" + +#include "GPU_batch_presets.h" +#include "GPU_immediate.h" +#include "GPU_matrix.h" +#include "GPU_state.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Defines & Structs + * \{ */ + +#define ANIMATION_TIME 0.30 +#define ANIMATION_INTERVAL 0.02 + +typedef enum uiPanelRuntimeFlag { + PANEL_LAST_ADDED = (1 << 0), + PANEL_ACTIVE = (1 << 2), + PANEL_WAS_ACTIVE = (1 << 3), + PANEL_ANIM_ALIGN = (1 << 4), + PANEL_NEW_ADDED = (1 << 5), + PANEL_SEARCH_FILTER_MATCH = (1 << 7), + /** + * Use the status set by property search (#PANEL_SEARCH_FILTER_MATCH) + * instead of #PNL_CLOSED. Set to true on every property search update. + */ + PANEL_USE_CLOSED_FROM_SEARCH = (1 << 8), + /** The Panel was before the start of the current / latest layout pass. */ + PANEL_WAS_CLOSED = (1 << 9), + /** + * Set when the panel is being dragged and while it animates back to its aligned + * position. Unlike #PANEL_STATE_ANIMATION, this is applied to sub-panels as well. + */ + PANEL_IS_DRAG_DROP = (1 << 10), + /** Draw a border with the active color around the panel. */ + PANEL_ACTIVE_BORDER = (1 << 11), +} uiPanelRuntimeFlag; + +/* The state of the mouse position relative to the panel. */ +typedef enum uiPanelMouseState { + PANEL_MOUSE_OUTSIDE, /** Mouse is not in the panel. */ + PANEL_MOUSE_INSIDE_CONTENT, /** Mouse is in the actual panel content. */ + PANEL_MOUSE_INSIDE_HEADER, /** Mouse is in the panel header. */ +} uiPanelMouseState; + +typedef enum uiHandlePanelState { + PANEL_STATE_DRAG, + PANEL_STATE_ANIMATION, + PANEL_STATE_EXIT, +} uiHandlePanelState; + +typedef struct uiHandlePanelData { + uiHandlePanelState state; + + /* Animation. */ + wmTimer *animtimer; + double starttime; + + /* Dragging. */ + int startx, starty; + int startofsx, startofsy; + float start_cur_xmin, start_cur_ymin; +} uiHandlePanelData; + +typedef struct PanelSort { + Panel *panel; + int new_offset_x; + int new_offset_y; +} PanelSort; + +static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel); +static int get_panel_real_size_y(const Panel *panel); +static void panel_activate_state(const bContext *C, Panel *panel, uiHandlePanelState state); +static int compare_panel(const void *a, const void *b); +static bool panel_type_context_poll(ARegion *region, + const PanelType *panel_type, + const char *context); + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Local Functions + * \{ */ + +static bool panel_active_animation_changed(ListBase *lb, + Panel **r_panel_animation, + bool *r_no_animation) +{ + LISTBASE_FOREACH (Panel *, panel, lb) { + /* Detect panel active flag changes. */ + if (!(panel->type && panel->type->parent)) { + if ((panel->runtime_flag & PANEL_WAS_ACTIVE) && !(panel->runtime_flag & PANEL_ACTIVE)) { + return true; + } + if (!(panel->runtime_flag & PANEL_WAS_ACTIVE) && (panel->runtime_flag & PANEL_ACTIVE)) { + return true; + } + } + + /* Detect changes in panel expansions. */ + if ((bool)(panel->runtime_flag & PANEL_WAS_CLOSED) != UI_panel_is_closed(panel)) { + *r_panel_animation = panel; + return false; + } + + if ((panel->runtime_flag & PANEL_ACTIVE) && !UI_panel_is_closed(panel)) { + if (panel_active_animation_changed(&panel->children, r_panel_animation, r_no_animation)) { + return true; + } + } + + /* Detect animation. */ + if (panel->activedata) { + uiHandlePanelData *data = (uiHandlePanelData *)panel->activedata; + if (data->state == PANEL_STATE_ANIMATION) { + *r_panel_animation = panel; + } + else { + /* Don't animate while handling other interaction. */ + *r_no_animation = true; + } + } + if ((panel->runtime_flag & PANEL_ANIM_ALIGN) && !(*r_panel_animation)) { + *r_panel_animation = panel; + } + } + + return false; +} + +/** + * \return True if the properties editor switch tabs since the last layout pass. + */ +static bool properties_space_needs_realign(const ScrArea *area, const ARegion *region) +{ + if (area->spacetype == SPACE_PROPERTIES && region->regiontype == RGN_TYPE_WINDOW) { + SpaceProperties *sbuts = (SpaceProperties *)area->spacedata.first; + + if (sbuts->mainbo != sbuts->mainb) { + return true; + } + } + + return false; +} + +static bool panels_need_realign(const ScrArea *area, ARegion *region, Panel **r_panel_animation) +{ + *r_panel_animation = NULL; + + if (properties_space_needs_realign(area, region)) { + return true; + } + + /* Detect if a panel was added or removed. */ + Panel *panel_animation = NULL; + bool no_animation = false; + if (panel_active_animation_changed(®ion->panels, &panel_animation, &no_animation)) { + return true; + } + + /* Detect panel marked for animation, if we're not already animating. */ + if (panel_animation) { + if (!no_animation) { + *r_panel_animation = panel_animation; + } + return true; + } + + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Functions for Instanced Panels + * \{ */ + +static Panel *panel_add_instanced(ARegion *region, + ListBase *panels, + PanelType *panel_type, + PointerRNA *custom_data) +{ + Panel *panel = (Panel *)MEM_callocN(sizeof(Panel), __func__); + panel->type = panel_type; + BLI_strncpy(panel->panelname, panel_type->idname, sizeof(panel->panelname)); + + panel->runtime.custom_data_ptr = custom_data; + panel->runtime_flag |= PANEL_NEW_ADDED; + + /* Add the panel's children too. Although they aren't instanced panels, we can still use this + * function to create them, as UI_panel_begin does other things we don't need to do. */ + LISTBASE_FOREACH (LinkData *, child, &panel_type->children) { + PanelType *child_type = (PanelType *)child->data; + panel_add_instanced(region, &panel->children, child_type, custom_data); + } + + /* Make sure the panel is added to the end of the display-order as well. This is needed for + * loading existing files. + * + * Note: We could use special behavior to place it after the panel that starts the list of + * instanced panels, but that would add complexity that isn't needed for now. */ + int max_sortorder = 0; + LISTBASE_FOREACH (Panel *, existing_panel, panels) { + if (existing_panel->sortorder > max_sortorder) { + max_sortorder = existing_panel->sortorder; + } + } + panel->sortorder = max_sortorder + 1; + + BLI_addtail(panels, panel); + + return panel; +} + +/** + * Called in situations where panels need to be added dynamically rather than + * having only one panel corresponding to each #PanelType. + */ +Panel *UI_panel_add_instanced(const bContext *C, + ARegion *region, + ListBase *panels, + const char *panel_idname, + PointerRNA *custom_data) +{ + ARegionType *region_type = region->type; + + PanelType *panel_type = (PanelType *)BLI_findstring( + ®ion_type->paneltypes, panel_idname, offsetof(PanelType, idname)); + + if (panel_type == NULL) { + printf("Panel type '%s' not found.\n", panel_idname); + return NULL; + } + + Panel *new_panel = panel_add_instanced(region, panels, panel_type, custom_data); + + /* Do this after #panel_add_instatnced so all sub-panels are added. */ + panel_set_expansion_from_list_data(C, new_panel); + + return new_panel; +} + +/** + * Find a unique key to append to the #PanelType.idname for the lookup to the panel's #uiBlock. + * Needed for instanced panels, where there can be multiple with the same type and identifier. + */ +void UI_list_panel_unique_str(Panel *panel, char *r_name) +{ + /* The panel sort-order will be unique for a specific panel type because the instanced + * panel list is regenerated for every change in the data order / length. */ + snprintf(r_name, INSTANCED_PANEL_UNIQUE_STR_LEN, "%d", panel->sortorder); +} + +/** + * Free a panel and its children. Custom data is shared by the panel and its children + * and is freed by #UI_panels_free_instanced. + * + * \note The only panels that should need to be deleted at runtime are panels with the + * #PANEL_TYPE_INSTANCED flag set. + */ +static void panel_delete(const bContext *C, ARegion *region, ListBase *panels, Panel *panel) +{ + /* Recursively delete children. */ + LISTBASE_FOREACH_MUTABLE (Panel *, child, &panel->children) { + panel_delete(C, region, &panel->children, child); + } + BLI_freelistN(&panel->children); + + BLI_remlink(panels, panel); + if (panel->activedata) { + MEM_freeN(panel->activedata); + } + MEM_freeN(panel); +} + +/** + * Remove instanced panels from the region's panel list. + * + * \note Can be called with NULL \a C, but it should be avoided because + * handlers might not be removed. + */ +void UI_panels_free_instanced(const bContext *C, ARegion *region) +{ + /* Delete panels with the instanced flag. */ + LISTBASE_FOREACH_MUTABLE (Panel *, panel, ®ion->panels) { + if ((panel->type != NULL) && (panel->type->flag & PANEL_TYPE_INSTANCED)) { + /* Make sure the panel's handler is removed before deleting it. */ + if (C != NULL && panel->activedata != NULL) { + panel_activate_state(C, panel, PANEL_STATE_EXIT); + } + + /* Free panel's custom data. */ + if (panel->runtime.custom_data_ptr != NULL) { + MEM_freeN(panel->runtime.custom_data_ptr); + } + + /* Free the panel and its sub-panels. */ + panel_delete(C, region, ®ion->panels, panel); + } + } +} + +/** + * Check if the instanced panels in the region's panels correspond to the list of data the panels + * represent. Returns false if the panels have been reordered or if the types from the list data + * don't match in any way. + * + * \param data: The list of data to check against the instanced panels. + * \param panel_idname_func: Function to find the #PanelType.idname for each item in the data list. + * For a readability and generality, this lookup happens separately for each type of panel list. + */ +bool UI_panel_list_matches_data(ARegion *region, + ListBase *data, + uiListPanelIDFromDataFunc panel_idname_func) +{ + /* Check for NULL data. */ + int data_len = 0; + Link *data_link = NULL; + if (data == NULL) { + data_len = 0; + data_link = NULL; + } + else { + data_len = BLI_listbase_count(data); + data_link = (Link *)data->first; + } + + int i = 0; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { + /* The panels were reordered by drag and drop. */ + if (panel->flag & PNL_INSTANCED_LIST_ORDER_CHANGED) { + return false; + } + + /* We reached the last data item before the last instanced panel. */ + if (data_link == NULL) { + return false; + } + + /* Check if the panel type matches the panel type from the data item. */ + char panel_idname[MAX_NAME]; + panel_idname_func(data_link, panel_idname); + if (!STREQ(panel_idname, panel->type->idname)) { + return false; + } + + data_link = data_link->next; + i++; + } + } + + /* If we didn't make it to the last list item, the panel list isn't complete. */ + if (i != data_len) { + return false; + } + + return true; +} + +static void reorder_instanced_panel_list(bContext *C, ARegion *region, Panel *drag_panel) +{ + /* Without a type we cannot access the reorder callback. */ + if (drag_panel->type == NULL) { + return; + } + /* Don't reorder if this instanced panel doesn't support drag and drop reordering. */ + if (drag_panel->type->reorder == NULL) { + return; + } + + char *context = NULL; + if (!UI_panel_category_is_visible(region)) { + context = drag_panel->type->context; + } + + /* Find how many instanced panels with this context string. */ + int list_panels_len = 0; + int start_index = -1; + LISTBASE_FOREACH (const Panel *, panel, ®ion->panels) { + if (panel->type) { + if (panel->type->flag & PANEL_TYPE_INSTANCED) { + if (panel_type_context_poll(region, panel->type, context)) { + if (panel == drag_panel) { + BLI_assert(start_index == -1); /* This panel should only appear once. */ + start_index = list_panels_len; + } + list_panels_len++; + } + } + } + } + BLI_assert(start_index != -1); /* The drag panel should definitely be in the list. */ + + /* Sort the matching instanced panels by their display order. */ + PanelSort *panel_sort = (PanelSort *)MEM_callocN(list_panels_len * sizeof(*panel_sort), + __func__); + PanelSort *sort_index = panel_sort; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->type) { + if (panel->type->flag & PANEL_TYPE_INSTANCED) { + if (panel_type_context_poll(region, panel->type, context)) { + sort_index->panel = panel; + sort_index++; + } + } + } + } + qsort(panel_sort, list_panels_len, sizeof(*panel_sort), compare_panel); + + /* Find how many of those panels are above this panel. */ + int move_to_index = 0; + for (; move_to_index < list_panels_len; move_to_index++) { + if (panel_sort[move_to_index].panel == drag_panel) { + break; + } + } + + MEM_freeN(panel_sort); + + if (move_to_index == start_index) { + /* In this case, the reorder was not changed, so don't do any updates or call the callback. */ + return; + } + + /* Set the bit to tell the interface to instanced the list. */ + drag_panel->flag |= PNL_INSTANCED_LIST_ORDER_CHANGED; + + /* Finally, move this panel's list item to the new index in its list. */ + drag_panel->type->reorder(C, drag_panel, move_to_index); +} + +/** + * Recursive implementation for #panel_set_expansion_from_list_data. + * + * \return Whether the closed flag for the panel or any sub-panels changed. + */ +static bool panel_set_expand_from_list_data_recursive(Panel *panel, short flag, short *flag_index) +{ + const bool open = (flag & (1 << *flag_index)); + bool changed = (open == UI_panel_is_closed(panel)); + + SET_FLAG_FROM_TEST(panel->flag, !open, PNL_CLOSED); + + LISTBASE_FOREACH (Panel *, child, &panel->children) { + *flag_index = *flag_index + 1; + changed |= panel_set_expand_from_list_data_recursive(child, flag, flag_index); + } + return changed; +} + +/** + * Set the expansion of the panel and its sub-panels from the flag stored in the + * corresponding list data. The flag has expansion stored in each bit in depth first order. + */ +static void panel_set_expansion_from_list_data(const bContext *C, Panel *panel) +{ + BLI_assert(panel->type != NULL); + BLI_assert(panel->type->flag & PANEL_TYPE_INSTANCED); + if (panel->type->get_list_data_expand_flag == NULL) { + /* Instanced panel doesn't support loading expansion. */ + return; + } + + const short expand_flag = panel->type->get_list_data_expand_flag(C, panel); + short flag_index = 0; + + /* Start panel animation if the open state was changed. */ + if (panel_set_expand_from_list_data_recursive(panel, expand_flag, &flag_index)) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } +} + +/** + * Set expansion based on the data for instanced panels. + */ +static void region_panels_set_expansion_from_list_data(const bContext *C, ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + PanelType *panel_type = panel->type; + if (panel_type != NULL && panel->type->flag & PANEL_TYPE_INSTANCED) { + panel_set_expansion_from_list_data(C, panel); + } + } + } +} + +/** + * Recursive implementation for #set_panels_list_data_expand_flag. + */ +static void get_panel_expand_flag(const Panel *panel, short *flag, short *flag_index) +{ + const bool open = !(panel->flag & PNL_CLOSED); + SET_FLAG_FROM_TEST(*flag, open, (1 << *flag_index)); + + LISTBASE_FOREACH (const Panel *, child, &panel->children) { + *flag_index = *flag_index + 1; + get_panel_expand_flag(child, flag, flag_index); + } +} + +/** + * Call the callback to store the panel and sub-panel expansion settings in the list item that + * corresponds to each instanced panel. + * + * \note This needs to iterate through all of the region's panels because the panel with changed + * expansion might have been the sub-panel of an instanced panel, meaning it might not know + * which list item it corresponds to. + */ +static void set_panels_list_data_expand_flag(const bContext *C, const ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + PanelType *panel_type = panel->type; + if (panel_type == NULL) { + continue; + } + + /* Check for #PANEL_ACTIVE so we only set the expand flag for active panels. */ + if (panel_type->flag & PANEL_TYPE_INSTANCED && panel->runtime_flag & PANEL_ACTIVE) { + short expand_flag; + short flag_index = 0; + get_panel_expand_flag(panel, &expand_flag, &flag_index); + if (panel->type->set_list_data_expand_flag) { + panel->type->set_list_data_expand_flag(C, panel, expand_flag); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panels + * \{ */ + +static bool panel_custom_data_active_get(const Panel *panel) +{ + /* The caller should make sure the panel is active and has a type. */ + BLI_assert(UI_panel_is_active(panel)); + BLI_assert(panel->type != NULL); + + if (panel->type->active_property[0] != '\0') { + PointerRNA *ptr = UI_panel_custom_data_get(panel); + if (ptr != NULL && !RNA_pointer_is_null(ptr)) { + return RNA_boolean_get(ptr, panel->type->active_property); + } + } + + return false; +} + +static void panel_custom_data_active_set(Panel *panel) +{ + /* Since the panel is interacted with, it should be active and have a type. */ + BLI_assert(UI_panel_is_active(panel)); + BLI_assert(panel->type != NULL); + + if (panel->type->active_property[0] != '\0') { + PointerRNA *ptr = UI_panel_custom_data_get(panel); + BLI_assert(RNA_struct_find_property(ptr, panel->type->active_property) != NULL); + if (ptr != NULL && !RNA_pointer_is_null(ptr)) { + RNA_boolean_set(ptr, panel->type->active_property, true); + } + } +} + +/** + * Set flag state for a panel and its sub-panels. + */ +static void panel_set_flag_recursive(Panel *panel, short flag, bool value) +{ + SET_FLAG_FROM_TEST(panel->flag, value, flag); + + LISTBASE_FOREACH (Panel *, child, &panel->children) { + panel_set_flag_recursive(child, flag, value); + } +} + +/** + * Set runtime flag state for a panel and its sub-panels. + */ +static void panel_set_runtime_flag_recursive(Panel *panel, short flag, bool value) +{ + SET_FLAG_FROM_TEST(panel->runtime_flag, value, flag); + + LISTBASE_FOREACH (Panel *, sub_panel, &panel->children) { + panel_set_runtime_flag_recursive(sub_panel, flag, value); + } +} + +static void panels_collapse_all(ARegion *region, const Panel *from_panel) +{ + const bool has_category_tabs = UI_panel_category_is_visible(region); + const char *category = has_category_tabs ? UI_panel_category_active_get(region, false) : NULL; + const PanelType *from_pt = from_panel->type; + + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + PanelType *pt = panel->type; + + /* Close panels with headers in the same context. */ + if (pt && from_pt && !(pt->flag & PANEL_TYPE_NO_HEADER)) { + if (!pt->context[0] || !from_pt->context[0] || STREQ(pt->context, from_pt->context)) { + if ((panel->flag & PNL_PIN) || !category || !pt->category[0] || + STREQ(pt->category, category)) { + panel->flag |= PNL_CLOSED; + } + } + } + } +} + +static bool panel_type_context_poll(ARegion *region, + const PanelType *panel_type, + const char *context) +{ + if (UI_panel_category_is_visible(region)) { + return STREQ(panel_type->category, UI_panel_category_active_get(region, false)); + } + + if (panel_type->context[0] && STREQ(panel_type->context, context)) { + return true; + } + + return false; +} + +Panel *UI_panel_find_by_type(ListBase *lb, const PanelType *pt) +{ + const char *idname = pt->idname; + + LISTBASE_FOREACH (Panel *, panel, lb) { + if (STREQLEN(panel->panelname, idname, sizeof(panel->panelname))) { + return panel; + } + } + return NULL; +} + +/** + * \note \a panel should be return value from #UI_panel_find_by_type and can be NULL. + */ +Panel *UI_panel_begin( + ARegion *region, ListBase *lb, uiBlock *block, PanelType *pt, Panel *panel, bool *r_open) +{ + Panel *panel_last; + const char *drawname = CTX_IFACE_(pt->translation_context, pt->label); + const char *idname = pt->idname; + const bool newpanel = (panel == NULL); + + if (newpanel) { + panel = (Panel *)MEM_callocN(sizeof(Panel), __func__); + panel->type = pt; + BLI_strncpy(panel->panelname, idname, sizeof(panel->panelname)); + + if (pt->flag & PANEL_TYPE_DEFAULT_CLOSED) { + panel->flag |= PNL_CLOSED; + panel->runtime_flag |= PANEL_WAS_CLOSED; + } + + panel->ofsx = 0; + panel->ofsy = 0; + panel->sizex = 0; + panel->sizey = 0; + panel->blocksizex = 0; + panel->blocksizey = 0; + panel->runtime_flag |= PANEL_NEW_ADDED; + + BLI_addtail(lb, panel); + } + else { + /* Panel already exists. */ + panel->type = pt; + } + + panel->runtime.block = block; + + BLI_strncpy(panel->drawname, drawname, sizeof(panel->drawname)); + + /* If a new panel is added, we insert it right after the panel that was last added. + * This way new panels are inserted in the right place between versions. */ + for (panel_last = (Panel *)lb->first; panel_last; panel_last = panel_last->next) { + if (panel_last->runtime_flag & PANEL_LAST_ADDED) { + BLI_remlink(lb, panel); + BLI_insertlinkafter(lb, panel_last, panel); + break; + } + } + + if (newpanel) { + panel->sortorder = (panel_last) ? panel_last->sortorder + 1 : 0; + + LISTBASE_FOREACH (Panel *, panel_next, lb) { + if (panel_next != panel && panel_next->sortorder >= panel->sortorder) { + panel_next->sortorder++; + } + } + } + + if (panel_last) { + panel_last->runtime_flag &= ~PANEL_LAST_ADDED; + } + + /* Assign the new panel to the block. */ + block->panel = panel; + panel->runtime_flag |= PANEL_ACTIVE | PANEL_LAST_ADDED; + if (region->alignment == RGN_ALIGN_FLOAT) { + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + } + + *r_open = false; + + if (UI_panel_is_closed(panel)) { + return panel; + } + + *r_open = true; + + return panel; +} + +/** + * Create the panel header button group, used to mark which buttons are part of + * panel headers for the panel search process that happens later. This Should be + * called before adding buttons for the panel's header layout. + */ +void UI_panel_header_buttons_begin(Panel *panel) +{ + uiBlock *block = panel->runtime.block; + + uiButtonGroup *new_group = ui_block_new_button_group(block); + new_group->flag = (UI_BUTTON_GROUP_LOCK | UI_BUTTON_GROUP_PANEL_HEADER); +} + +/** + * Finish the button group for the panel header to avoid putting panel body buttons in it. + */ +void UI_panel_header_buttons_end(Panel *panel) +{ + uiBlock *block = panel->runtime.block; + + /* A button group should always be created in #UI_panel_header_buttons_begin. */ + BLI_assert(!BLI_listbase_is_empty(&block->button_groups)); + + uiButtonGroup *button_group = (uiButtonGroup *)block->button_groups.last; + + button_group->flag &= ~UI_BUTTON_GROUP_LOCK; + + /* Repurpose the first header button group if it is empty, in case the first button added to + * the panel doesn't add a new group (if the button is created directly rather than through an + * interface layout call). */ + if (BLI_listbase_is_single(&block->button_groups) && + BLI_listbase_is_empty(&button_group->buttons)) { + button_group->flag &= ~UI_BUTTON_GROUP_PANEL_HEADER; + } + else { + /* Always add a new button group. Although this may result in many empty groups, without it, + * new buttons in the panel body not protected with a #ui_block_new_button_group call would + * end up in the panel header group. */ + ui_block_new_button_group(block); + } +} + +static float panel_region_offset_x_get(const ARegion *region) +{ + if (UI_panel_category_is_visible(region)) { + if (RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) { + return UI_PANEL_CATEGORY_MARGIN_WIDTH; + } + } + + return 0.0f; +} + +/** + * Starting from the "block size" set in #UI_panel_end, calculate the full size + * of the panel including the sub-panel headers and buttons. + */ +static void panel_calculate_size_recursive(ARegion *region, Panel *panel) +{ + int width = panel->blocksizex; + int height = panel->blocksizey; + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (child_panel->runtime_flag & PANEL_ACTIVE) { + panel_calculate_size_recursive(region, child_panel); + width = max_ii(width, child_panel->sizex); + height += get_panel_real_size_y(child_panel); + } + } + + /* Update total panel size. */ + if (panel->runtime_flag & PANEL_NEW_ADDED) { + panel->runtime_flag &= ~PANEL_NEW_ADDED; + panel->sizex = width; + panel->sizey = height; + } + else { + const int old_sizex = panel->sizex, old_sizey = panel->sizey; + const int old_region_ofsx = panel->runtime.region_ofsx; + + /* Update width/height if non-zero. */ + if (width != 0) { + panel->sizex = width; + } + if (height != 0 || !UI_panel_is_closed(panel)) { + panel->sizey = height; + } + + /* Check if we need to do an animation. */ + if (panel->sizex != old_sizex || panel->sizey != old_sizey) { + panel->runtime_flag |= PANEL_ANIM_ALIGN; + panel->ofsy += old_sizey - panel->sizey; + } + + panel->runtime.region_ofsx = panel_region_offset_x_get(region); + if (old_region_ofsx != panel->runtime.region_ofsx) { + panel->runtime_flag |= PANEL_ANIM_ALIGN; + } + } +} + +void UI_panel_end(Panel *panel, int width, int height) +{ + /* Store the size of the buttons layout in the panel. The actual panel size + * (including sub-panels) is calculated in #UI_panels_end. */ + panel->blocksizex = width; + panel->blocksizey = height; +} + +static void ui_offset_panel_block(uiBlock *block) +{ + const uiStyle *style = UI_style_get_dpi(); + + /* Compute bounds and offset. */ + ui_block_bounds_calc(block); + + const int ofsy = block->panel->sizey - style->panelspace; + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->rect.ymin += ofsy; + but->rect.ymax += ofsy; + } + + block->rect.xmax = block->panel->sizex; + block->rect.ymax = block->panel->sizey; + block->rect.xmin = block->rect.ymin = 0.0; +} + +void ui_panel_tag_search_filter_match(Panel *panel) +{ + panel->runtime_flag |= PANEL_SEARCH_FILTER_MATCH; +} + +static void panel_matches_search_filter_recursive(const Panel *panel, bool *filter_matches) +{ + *filter_matches |= panel->runtime_flag & PANEL_SEARCH_FILTER_MATCH; + + /* If the panel has no match we need to make sure that its children are too. */ + if (!*filter_matches) { + LISTBASE_FOREACH (const Panel *, child_panel, &panel->children) { + panel_matches_search_filter_recursive(child_panel, filter_matches); + } + } +} + +/** + * Find whether a panel or any of its sub-panels contain a property that matches the search filter, + * depending on the search process running in #UI_block_apply_search_filter earlier. + */ +bool UI_panel_matches_search_filter(const Panel *panel) +{ + bool search_filter_matches = false; + panel_matches_search_filter_recursive(panel, &search_filter_matches); + return search_filter_matches; +} + +/** + * Set the flag telling the panel to use its search result status for its expansion. + */ +static void panel_set_expansion_from_search_filter_recursive(const bContext *C, + Panel *panel, + const bool use_search_closed) +{ + /* This has to run on inactive panels that may not have a type, + * but we can prevent running on header-less panels in some cases. */ + if (panel->type == NULL || !(panel->type->flag & PANEL_TYPE_NO_HEADER)) { + SET_FLAG_FROM_TEST(panel->runtime_flag, use_search_closed, PANEL_USE_CLOSED_FROM_SEARCH); + } + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + /* Don't check if the sub-panel is active, otherwise the + * expansion won't be reset when the parent is closed. */ + panel_set_expansion_from_search_filter_recursive(C, child_panel, use_search_closed); + } +} + +/** + * Set the flag telling every panel to override its expansion with its search result status. + */ +static void region_panels_set_expansion_from_search_filter(const bContext *C, + ARegion *region, + const bool use_search_closed) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + /* Don't check if the panel is active, otherwise the expansion won't + * be correct when switching back to tab after exiting search. */ + panel_set_expansion_from_search_filter_recursive(C, panel, use_search_closed); + } + set_panels_list_data_expand_flag(C, region); +} + +/** + * Hide buttons in invisible layouts, which are created because buttons must be + * added for all panels in order to search, even panels that will end up closed. + */ +static void panel_remove_invisible_layouts_recursive(Panel *panel, const Panel *parent_panel) +{ + uiBlock *block = panel->runtime.block; + BLI_assert(block != NULL); + BLI_assert(block->active); + if (parent_panel != NULL && UI_panel_is_closed(parent_panel)) { + /* The parent panel is closed, so this panel can be completely removed. */ + UI_block_set_search_only(block, true); + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + but->flag |= UI_HIDDEN; + } + } + else if (UI_panel_is_closed(panel)) { + /* If sub-panels have no search results but the parent panel does, then the parent panel open + * and the sub-panels will close. In that case there must be a way to hide the buttons in the + * panel but keep the header buttons. */ + LISTBASE_FOREACH (uiButtonGroup *, button_group, &block->button_groups) { + if (button_group->flag & UI_BUTTON_GROUP_PANEL_HEADER) { + continue; + } + LISTBASE_FOREACH (LinkData *, link, &button_group->buttons) { + uiBut *but = (uiBut *)link->data; + but->flag |= UI_HIDDEN; + } + } + } + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + if (child_panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(child_panel->runtime.block != NULL); + panel_remove_invisible_layouts_recursive(child_panel, panel); + } + } +} + +static void region_panels_remove_invisible_layouts(ARegion *region) +{ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(panel->runtime.block != NULL); + panel_remove_invisible_layouts_recursive(panel, NULL); + } + } +} + +/** + * Get the panel's expansion state, taking into account + * expansion set from property search if it applies. + */ +bool UI_panel_is_closed(const Panel *panel) +{ + /* Header-less panels can never be closed, otherwise they could disappear. */ + if (panel->type && panel->type->flag & PANEL_TYPE_NO_HEADER) { + return false; + } + + if (panel->runtime_flag & PANEL_USE_CLOSED_FROM_SEARCH) { + return !UI_panel_matches_search_filter(panel); + } + + return panel->flag & PNL_CLOSED; +} + +bool UI_panel_is_active(const Panel *panel) +{ + return panel->runtime_flag & PANEL_ACTIVE; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drawing + * \{ */ + +/** + * Draw panels, selected (panels currently being dragged) on top. + */ +void UI_panels_draw(const bContext *C, ARegion *region) +{ + /* Draw in reverse order, because #uiBlocks are added in reverse order + * and we need child panels to draw on top. */ + LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel && !UI_panel_is_dragging(block->panel) && + !UI_block_is_search_only(block)) { + UI_block_draw(C, block); + } + } + + LISTBASE_FOREACH_BACKWARD (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel && UI_panel_is_dragging(block->panel) && + !UI_block_is_search_only(block)) { + UI_block_draw(C, block); + } + } +} + +#define PNL_ICON UI_UNIT_X /* Could be UI_UNIT_Y too. */ + +/* For button layout next to label. */ +void UI_panel_label_offset(const uiBlock *block, int *r_x, int *r_y) +{ + Panel *panel = block->panel; + const bool is_subpanel = (panel->type && panel->type->parent); + + *r_x = UI_UNIT_X * 1.0f; + *r_y = UI_UNIT_Y * 1.5f; + + if (is_subpanel) { + *r_x += (0.7f * UI_UNIT_X); + } +} + +static void panel_title_color_get(const Panel *panel, + const bool show_background, + const bool region_search_filter_active, + uchar r_color[4]) +{ + if (!show_background) { + /* Use menu colors for floating panels. */ + bTheme *btheme = UI_GetTheme(); + const uiWidgetColors *wcol = &btheme->tui.wcol_menu_back; + copy_v4_v4_uchar(r_color, (const uchar *)wcol->text); + return; + } + + const bool search_match = UI_panel_matches_search_filter(panel); + + UI_GetThemeColor4ubv(TH_TITLE, r_color); + if (region_search_filter_active && !search_match) { + r_color[0] *= 0.5; + r_color[1] *= 0.5; + r_color[2] *= 0.5; + } +} + +static void panel_draw_highlight_border(const Panel *panel, + const rcti *rect, + const rcti *header_rect) +{ + const bool draw_box_style = panel->type->flag & PANEL_TYPE_DRAW_BOX; + const bool is_subpanel = panel->type->parent != NULL; + if (is_subpanel) { + return; + } + + float radius; + if (draw_box_style) { + /* Use the theme for box widgets. */ + const uiWidgetColors *box_wcol = &UI_GetTheme()->tui.wcol_box; + UI_draw_roundbox_corner_set(UI_CNR_ALL); + radius = box_wcol->roundness * U.widget_unit; + } + else { + UI_draw_roundbox_corner_set(UI_CNR_NONE); + radius = 0.0f; + } + + float color[4]; + UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color); + UI_draw_roundbox_4fv( + &(const rctf){ + .xmin = rect->xmin, + .xmax = rect->xmax, + .ymin = UI_panel_is_closed(panel) ? header_rect->ymin : rect->ymin, + .ymax = header_rect->ymax, + }, + false, + radius, + color); +} + +static void panel_draw_aligned_widgets(const uiStyle *style, + const Panel *panel, + const rcti *header_rect, + const float aspect, + const bool show_pin, + const bool show_background, + const bool region_search_filter_active) +{ + const bool is_subpanel = panel->type->parent != NULL; + const uiFontStyle *fontstyle = (is_subpanel) ? &style->widgetlabel : &style->paneltitle; + + const int header_height = BLI_rcti_size_y(header_rect); + const int scaled_unit = round_fl_to_int(UI_UNIT_X / aspect); + + /* Offset triangle and text to the right for subpanels. */ + const rcti widget_rect = { + .xmin = header_rect->xmin + (is_subpanel ? scaled_unit * 0.7f : 0), + .xmax = header_rect->xmax, + .ymin = header_rect->ymin, + .ymax = header_rect->ymax, + }; + + uchar title_color[4]; + panel_title_color_get(panel, show_background, region_search_filter_active, title_color); + title_color[3] = 255; + + /* Draw collapse icon. */ + { + rctf collapse_rect = { + .xmin = widget_rect.xmin, + .xmax = widget_rect.xmin + header_height, + .ymin = widget_rect.ymin, + .ymax = widget_rect.ymax, + }; + BLI_rctf_scale(&collapse_rect, 0.25f); + + float triangle_color[4]; + rgba_uchar_to_float(triangle_color, title_color); + + ui_draw_anti_tria_rect(&collapse_rect, UI_panel_is_closed(panel) ? 'h' : 'v', triangle_color); + } + + /* Draw text label. */ + if (panel->drawname[0] != '\0') { + /* + 0.001f to avoid flirting with float inaccuracy .*/ + const rcti title_rect = { + .xmin = widget_rect.xmin + (panel->labelofs / aspect) + scaled_unit * 1.1f, + .xmax = widget_rect.xmax, + .ymin = widget_rect.ymin - 2.0f / aspect, + .ymax = widget_rect.ymax, + }; + UI_fontstyle_draw(fontstyle, + &title_rect, + panel->drawname, + title_color, + &(struct uiFontStyleDraw_Params){ + .align = UI_STYLE_TEXT_LEFT, + }); + } + + /* Draw the pin icon. */ + if (show_pin && (panel->flag & PNL_PIN)) { + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw_ex(widget_rect.xmax - scaled_unit * 2.2f, + widget_rect.ymin + 5.0f / aspect, + ICON_PINNED, + aspect * U.inv_dpi_fac, + 1.0f, + 0.0f, + title_color, + false); + GPU_blend(GPU_BLEND_NONE); + } + + /* Draw drag widget. */ + if (!is_subpanel && show_background) { + const int drag_widget_size = header_height * 0.7f; + GPU_matrix_push(); + /* The magic numbers here center the widget vertically and offset it to the left. + * Currently this depends on the height of the header, although it could be independent. */ + GPU_matrix_translate_2f(widget_rect.xmax - scaled_unit * 1.15, + widget_rect.ymin + (header_height - drag_widget_size) * 0.5f); + + const int col_tint = 84; + float color_high[4], color_dark[4]; + UI_GetThemeColorShade4fv(TH_PANEL_HEADER, col_tint, color_high); + UI_GetThemeColorShade4fv(TH_PANEL_BACK, -col_tint, color_dark); + + GPUBatch *batch = GPU_batch_preset_panel_drag_widget( + U.pixelsize, color_high, color_dark, drag_widget_size); + GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_FLAT_COLOR); + GPU_batch_draw(batch); + GPU_matrix_pop(); + } +} + +static void panel_draw_aligned_backdrop(const Panel *panel, + const rcti *rect, + const rcti *header_rect) +{ + const bool draw_box_style = panel->type->flag & PANEL_TYPE_DRAW_BOX; + const bool is_subpanel = panel->type->parent != NULL; + const bool is_open = !UI_panel_is_closed(panel); + + if (is_subpanel && !is_open) { + return; + } + + const uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + + /* Draw with an opaque box backdrop for box style panels. */ + if (draw_box_style) { + /* Use the theme for box widgets. */ + const uiWidgetColors *box_wcol = &UI_GetTheme()->tui.wcol_box; + + if (is_subpanel) { + /* Use rounded bottom corners for the last subpanel. */ + if (panel->next == NULL) { + UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT); + float color[4]; + UI_GetThemeColor4fv(TH_PANEL_SUB_BACK, color); + /* Change the width a little bit to line up with sides. */ + UI_draw_roundbox_aa( + &(const rctf){ + .xmin = rect->xmin + U.pixelsize, + .xmax = rect->xmax - U.pixelsize, + .ymin = rect->ymin + U.pixelsize, + .ymax = rect->ymax, + }, + true, + box_wcol->roundness * U.widget_unit, + color); + } + else { + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformThemeColor(TH_PANEL_SUB_BACK); + immRectf(pos, rect->xmin + U.pixelsize, rect->ymin, rect->xmax - U.pixelsize, rect->ymax); + immUnbindProgram(); + } + } + else { + /* Expand the top a tiny bit to give header buttons equal size above and below. */ + rcti box_rect = { + .xmin = rect->xmin, + .xmax = rect->xmax, + .ymin = is_open ? rect->ymin : header_rect->ymin, + .ymax = header_rect->ymax + U.pixelsize, + }; + ui_draw_box_opaque(&box_rect, UI_CNR_ALL); + + /* Mimic the border between aligned box widgets for the bottom of the header. */ + if (is_open) { + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + + /* Top line. */ + immUniformColor4ubv(box_wcol->outline); + immRectf(pos, rect->xmin, header_rect->ymin - U.pixelsize, rect->xmax, header_rect->ymin); + + /* Bottom "shadow" line. */ + immUniformThemeColor(TH_WIDGET_EMBOSS); + immRectf(pos, + rect->xmin, + header_rect->ymin - U.pixelsize, + rect->xmax, + header_rect->ymin - U.pixelsize - 1); + + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); + } + } + } + else { + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + GPU_blend(GPU_BLEND_ALPHA); + + /* Panel backdrop. */ + if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) { + immUniformThemeColor(is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK); + immRectf(pos, rect->xmin, rect->ymin, rect->xmax, rect->ymax); + } + + /* Panel header backdrops for non sub-panels. */ + if (!is_subpanel) { + immUniformThemeColor(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER); + immRectf(pos, rect->xmin, header_rect->ymin, rect->xmax, header_rect->ymax); + } + + GPU_blend(GPU_BLEND_NONE); + immUnbindProgram(); + } +} + +/** + * Draw a panel integrated in buttons-window, tool/property lists etc. + */ +void ui_draw_aligned_panel(const uiStyle *style, + const uiBlock *block, + const rcti *rect, + const bool show_pin, + const bool show_background, + const bool region_search_filter_active) +{ + const Panel *panel = block->panel; + + /* Add 0.001f to prevent flicker from float inaccuracy. */ + const rcti header_rect = { + rect->xmin, + rect->xmax, + rect->ymax, + rect->ymax + floor(PNL_HEADER / block->aspect + 0.001f), + }; + + if (show_background) { + panel_draw_aligned_backdrop(panel, rect, &header_rect); + } + + /* Draw the widgets and text in the panel header. */ + if (!(panel->type->flag & PANEL_TYPE_NO_HEADER)) { + panel_draw_aligned_widgets(style, + panel, + &header_rect, + block->aspect, + show_pin, + show_background, + region_search_filter_active); + } + + if (panel_custom_data_active_get(panel)) { + panel_draw_highlight_border(panel, rect, &header_rect); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Category Drawing (Tabs) + * \{ */ + +#define TABS_PADDING_BETWEEN_FACTOR 4.0f +#define TABS_PADDING_TEXT_FACTOR 6.0f + +/** + * Draw vertical tabs on the left side of the region, one tab per category. + */ +void UI_panel_category_draw_all(ARegion *region, const char *category_id_active) +{ + // #define USE_FLAT_INACTIVE + const bool is_left = RGN_ALIGN_ENUM_FROM_MASK(region->alignment != RGN_ALIGN_RIGHT); + View2D *v2d = ®ion->v2d; + const uiStyle *style = UI_style_get(); + const uiFontStyle *fstyle = &style->widget; + const int fontid = fstyle->uifont_id; + short fstyle_points = fstyle->points; + const float aspect = ((uiBlock *)region->uiblocks.first)->aspect; + const float zoom = 1.0f / aspect; + const int px = U.pixelsize; + const int category_tabs_width = round_fl_to_int(UI_PANEL_CATEGORY_MARGIN_WIDTH * zoom); + const float dpi_fac = UI_DPI_FAC; + /* Padding of tabs around text. */ + const int tab_v_pad_text = round_fl_to_int(TABS_PADDING_TEXT_FACTOR * dpi_fac * zoom) + 2 * px; + /* Padding between tabs. */ + const int tab_v_pad = round_fl_to_int(TABS_PADDING_BETWEEN_FACTOR * dpi_fac * zoom); + bTheme *btheme = UI_GetTheme(); + const float tab_curve_radius = btheme->tui.wcol_tab.roundness * U.widget_unit * zoom; + const int roundboxtype = is_left ? (UI_CNR_TOP_LEFT | UI_CNR_BOTTOM_LEFT) : + (UI_CNR_TOP_RIGHT | UI_CNR_BOTTOM_RIGHT); + bool is_alpha; + bool do_scaletabs = false; +#ifdef USE_FLAT_INACTIVE + bool is_active_prev = false; +#endif + float scaletabs = 1.0f; + /* Same for all tabs. */ + /* Intentionally don't scale by 'px'. */ + const int rct_xmin = is_left ? v2d->mask.xmin + 3 : (v2d->mask.xmax - category_tabs_width); + const int rct_xmax = is_left ? v2d->mask.xmin + category_tabs_width : (v2d->mask.xmax - 3); + const int text_v_ofs = (rct_xmax - rct_xmin) * 0.3f; + + int y_ofs = tab_v_pad; + + /* Primary theme colors. */ + uchar theme_col_back[4]; + uchar theme_col_text[3]; + uchar theme_col_text_hi[3]; + + /* Tab colors. */ + uchar theme_col_tab_bg[4]; + float theme_col_tab_active[4]; + float theme_col_tab_inactive[4]; + float theme_col_tab_outline[4]; + + UI_GetThemeColor4ubv(TH_BACK, theme_col_back); + UI_GetThemeColor3ubv(TH_TEXT, theme_col_text); + UI_GetThemeColor3ubv(TH_TEXT_HI, theme_col_text_hi); + + UI_GetThemeColor4ubv(TH_TAB_BACK, theme_col_tab_bg); + UI_GetThemeColor4fv(TH_TAB_ACTIVE, theme_col_tab_active); + UI_GetThemeColor4fv(TH_TAB_INACTIVE, theme_col_tab_inactive); + UI_GetThemeColor4fv(TH_TAB_OUTLINE, theme_col_tab_outline); + + is_alpha = (region->overlap && (theme_col_back[3] != 255)); + + if (fstyle->kerning == 1) { + BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT); + } + + BLF_enable(fontid, BLF_ROTATION); + BLF_rotation(fontid, M_PI_2); + // UI_fontstyle_set(&style->widget); + ui_fontscale(&fstyle_points, aspect / (U.pixelsize * 1.1f)); + BLF_size(fontid, fstyle_points, U.dpi); + + /* Check the region type supports categories to avoid an assert + * for showing 3D view panels in the properties space. */ + if ((1 << region->regiontype) & RGN_TYPE_HAS_CATEGORY_MASK) { + BLI_assert(UI_panel_category_is_visible(region)); + } + + /* Calculate tab rectangle and check if we need to scale down. */ + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + rcti *rct = &pc_dyn->rect; + const char *category_id = pc_dyn->idname; + const char *category_id_draw = IFACE_(category_id); + const int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); + + rct->xmin = rct_xmin; + rct->xmax = rct_xmax; + + rct->ymin = v2d->mask.ymax - (y_ofs + category_width + (tab_v_pad_text * 2)); + rct->ymax = v2d->mask.ymax - (y_ofs); + + y_ofs += category_width + tab_v_pad + (tab_v_pad_text * 2); + } + + if (y_ofs > BLI_rcti_size_y(&v2d->mask)) { + scaletabs = (float)BLI_rcti_size_y(&v2d->mask) / (float)y_ofs; + + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + rcti *rct = &pc_dyn->rect; + rct->ymin = ((rct->ymin - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; + rct->ymax = ((rct->ymax - v2d->mask.ymax) * scaletabs) + v2d->mask.ymax; + } + + do_scaletabs = true; + } + + /* Begin drawing. */ + GPU_line_smooth(true); + + uint pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + /* Draw the background. */ + if (is_alpha) { + GPU_blend(GPU_BLEND_ALPHA); + immUniformColor4ubv(theme_col_tab_bg); + } + else { + immUniformColor3ubv(theme_col_tab_bg); + } + + if (is_left) { + immRecti( + pos, v2d->mask.xmin, v2d->mask.ymin, v2d->mask.xmin + category_tabs_width, v2d->mask.ymax); + } + else { + immRecti( + pos, v2d->mask.xmax - category_tabs_width, v2d->mask.ymin, v2d->mask.xmax, v2d->mask.ymax); + } + + if (is_alpha) { + GPU_blend(GPU_BLEND_NONE); + } + + immUnbindProgram(); + + LISTBASE_FOREACH (PanelCategoryDyn *, pc_dyn, ®ion->panels_category) { + const rcti *rct = &pc_dyn->rect; + const char *category_id = pc_dyn->idname; + const char *category_id_draw = IFACE_(category_id); + const int category_width = BLI_rcti_size_y(rct) - (tab_v_pad_text * 2); + size_t category_draw_len = BLF_DRAW_STR_DUMMY_MAX; +#if 0 + int category_width = BLF_width(fontid, category_id_draw, BLF_DRAW_STR_DUMMY_MAX); +#endif + + const bool is_active = STREQ(category_id, category_id_active); + + GPU_blend(GPU_BLEND_ALPHA); + +#ifdef USE_FLAT_INACTIVE + /* Draw line between inactive tabs. */ + if (is_active == false && is_active_prev == false && pc_dyn->prev) { + pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + immUniformColor3fvAlpha(theme_col_tab_outline, 0.3f); + immRecti(pos, + is_left ? v2d->mask.xmin + (category_tabs_width / 5) : + v2d->mask.xmax - (category_tabs_width / 5), + rct->ymax + px, + is_left ? (v2d->mask.xmin + category_tabs_width) - (category_tabs_width / 5) : + (v2d->mask.xmax - category_tabs_width) + (category_tabs_width / 5), + rct->ymax + (px * 3)); + immUnbindProgram(); + } + + is_active_prev = is_active; + + if (is_active) +#endif + { + /* Draw filled rectangle and outline for tab. */ + UI_draw_roundbox_corner_set(roundboxtype); + { + const rctf box_rect{ + rct->xmin, + rct->xmax, + rct->ymin, + rct->ymax, + }; + UI_draw_roundbox_4fv(&box_rect, + true, + tab_curve_radius, + is_active ? theme_col_tab_active : theme_col_tab_inactive); + } + + { + const rctf box_rect{ + rct->xmin, + rct->xmax, + rct->ymin, + rct->ymax, + }; + UI_draw_roundbox_4fv(&box_rect, false, tab_curve_radius, theme_col_tab_outline); + } + + /* Disguise the outline on one side to join the tab to the panel. */ + pos = GPU_vertformat_attr_add( + immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT); + immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); + + immUniformColor4fv(is_active ? theme_col_tab_active : theme_col_tab_inactive); + immRecti(pos, + is_left ? rct->xmax - px : rct->xmin, + rct->ymin + px, + is_left ? rct->xmax : rct->xmin + px, + rct->ymax - px); + immUnbindProgram(); + } + + /* Tab titles. */ + + if (do_scaletabs) { + category_draw_len = BLF_width_to_strlen( + fontid, category_id_draw, category_draw_len, category_width, NULL); + } + + BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f); + BLF_color3ubv(fontid, theme_col_text); + BLF_draw(fontid, category_id_draw, category_draw_len); + + GPU_blend(GPU_BLEND_NONE); + + /* Not essential, but allows events to be handled right up to the region edge (T38171). */ + if (is_left) { + pc_dyn->rect.xmin = v2d->mask.xmin; + } + else { + pc_dyn->rect.xmax = v2d->mask.xmax; + } + } + + GPU_line_smooth(false); + + BLF_disable(fontid, BLF_ROTATION); + + if (fstyle->kerning == 1) { + BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT); + } +} + +#undef TABS_PADDING_BETWEEN_FACTOR +#undef TABS_PADDING_TEXT_FACTOR + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Alignment + * \{ */ + +static int get_panel_size_y(const Panel *panel) +{ + if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { + return panel->sizey; + } + + return PNL_HEADER + panel->sizey; +} + +static int get_panel_real_size_y(const Panel *panel) +{ + const int sizey = UI_panel_is_closed(panel) ? 0 : panel->sizey; + + if (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER)) { + return sizey; + } + + return PNL_HEADER + sizey; +} + +int UI_panel_size_y(const Panel *panel) +{ + return get_panel_real_size_y(panel); +} + +/** + * This function is needed because #uiBlock and Panel itself don't + * change #Panel.sizey or location when closed. + */ +static int get_panel_real_ofsy(Panel *panel) +{ + if (UI_panel_is_closed(panel)) { + return panel->ofsy + panel->sizey; + } + return panel->ofsy; +} + +bool UI_panel_is_dragging(const Panel *panel) +{ + return panel->runtime_flag & PANEL_IS_DRAG_DROP; +} + +/** + * \note about sorting: + * The #Panel.sortorder has a lower value for new panels being added. + * however, that only works to insert a single panel, when more new panels get + * added the coordinates of existing panels and the previously stored to-be-inserted + * panels do not match for sorting. + */ + +static int find_highest_panel(const void *a, const void *b) +{ + const Panel *panel_a = ((PanelSort *)a)->panel; + const Panel *panel_b = ((PanelSort *)b)->panel; + + /* Stick uppermost header-less panels to the top of the region - + * prevent them from being sorted (multiple header-less panels have to be sorted though). */ + if (panel_a->type->flag & PANEL_TYPE_NO_HEADER && panel_b->type->flag & PANEL_TYPE_NO_HEADER) { + /* Pass the no-header checks and check for `ofsy` and #Panel.sortorder below. */ + } + else if (panel_a->type->flag & PANEL_TYPE_NO_HEADER) { + return -1; + } + else if (panel_b->type->flag & PANEL_TYPE_NO_HEADER) { + return 1; + } + + if (panel_a->ofsy + panel_a->sizey < panel_b->ofsy + panel_b->sizey) { + return 1; + } + if (panel_a->ofsy + panel_a->sizey > panel_b->ofsy + panel_b->sizey) { + return -1; + } + if (panel_a->sortorder > panel_b->sortorder) { + return 1; + } + if (panel_a->sortorder < panel_b->sortorder) { + return -1; + } + + return 0; +} + +static int compare_panel(const void *a, const void *b) +{ + const Panel *panel_a = ((PanelSort *)a)->panel; + const Panel *panel_b = ((PanelSort *)b)->panel; + + if (panel_a->sortorder > panel_b->sortorder) { + return 1; + } + if (panel_a->sortorder < panel_b->sortorder) { + return -1; + } + + return 0; +} + +static void align_sub_panels(Panel *panel) +{ + /* Position sub panels. */ + int ofsy = panel->ofsy + panel->sizey - panel->blocksizey; + + LISTBASE_FOREACH (Panel *, pachild, &panel->children) { + if (pachild->runtime_flag & PANEL_ACTIVE) { + pachild->ofsx = panel->ofsx; + pachild->ofsy = ofsy - get_panel_size_y(pachild); + ofsy -= get_panel_real_size_y(pachild); + + if (pachild->children.first) { + align_sub_panels(pachild); + } + } + } +} + +/** + * Calculate the position and order of panels as they are opened, closed, and dragged. + */ +static bool uiAlignPanelStep(ARegion *region, const float factor, const bool drag) +{ + /* Count active panels. */ + int active_panels_len = 0; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + /* These panels should have types since they are currently displayed to the user. */ + BLI_assert(panel->type != NULL); + active_panels_len++; + } + } + if (active_panels_len == 0) { + return false; + } + + /* Sort panels. */ + PanelSort *panel_sort = (PanelSort *)MEM_mallocN(sizeof(PanelSort) * active_panels_len, + __func__); + { + PanelSort *ps = panel_sort; + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + ps->panel = panel; + ps++; + } + } + } + + if (drag) { + /* While dragging, sort based on location and update #Panel.sortorder. */ + qsort(panel_sort, active_panels_len, sizeof(PanelSort), find_highest_panel); + for (int i = 0; i < active_panels_len; i++) { + panel_sort[i].panel->sortorder = i; + } + } + else { + /* Otherwise use #Panel.sortorder. */ + qsort(panel_sort, active_panels_len, sizeof(PanelSort), compare_panel); + } + + /* X offset. */ + const int region_offset_x = panel_region_offset_x_get(region); + for (int i = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + const bool use_box = ps->panel->type->flag & PANEL_TYPE_DRAW_BOX; + ps->panel->runtime.region_ofsx = region_offset_x; + ps->new_offset_x = region_offset_x + ((use_box) ? UI_PANEL_BOX_STYLE_MARGIN : 0); + } + + /* Y offset. */ + for (int i = 0, y = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + y -= get_panel_real_size_y(ps->panel); + + const bool use_box = ps->panel->type->flag & PANEL_TYPE_DRAW_BOX; + if (use_box) { + y -= UI_PANEL_BOX_STYLE_MARGIN; + } + ps->new_offset_y = y; + /* The header still draws offset by the size of closed panels, so apply the offset here. */ + if (UI_panel_is_closed(ps->panel)) { + panel_sort[i].new_offset_y -= ps->panel->sizey; + } + } + + /* Interpolate based on the input factor. */ + bool changed = false; + for (int i = 0; i < active_panels_len; i++) { + PanelSort *ps = &panel_sort[i]; + if (ps->panel->flag & PNL_SELECT) { + continue; + } + + if (ps->new_offset_x != ps->panel->ofsx) { + const float x = interpf((float)ps->new_offset_x, (float)ps->panel->ofsx, factor); + ps->panel->ofsx = round_fl_to_int(x); + changed = true; + } + if (ps->new_offset_y != ps->panel->ofsy) { + const float y = interpf((float)ps->new_offset_y, (float)ps->panel->ofsy, factor); + ps->panel->ofsy = round_fl_to_int(y); + changed = true; + } + } + + /* Set locations for tabbed and sub panels. */ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + if (panel->children.first) { + align_sub_panels(panel); + } + } + } + + MEM_freeN(panel_sort); + + return changed; +} + +static void ui_panels_size(ARegion *region, int *r_x, int *r_y) +{ + int sizex = 0; + int sizey = 0; + + /* Compute size taken up by panels, for setting in view2d. */ + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + const int pa_sizex = panel->ofsx + panel->sizex; + const int pa_sizey = get_panel_real_ofsy(panel); + + sizex = max_ii(sizex, pa_sizex); + sizey = min_ii(sizey, pa_sizey); + } + } + + if (sizex == 0) { + sizex = UI_PANEL_WIDTH; + } + if (sizey == 0) { + sizey = -UI_PANEL_WIDTH; + } + + *r_x = sizex; + *r_y = sizey; +} + +static void ui_do_animate(bContext *C, Panel *panel) +{ + uiHandlePanelData *data = (uiHandlePanelData *)panel->activedata; + ARegion *region = CTX_wm_region(C); + + float fac = (PIL_check_seconds_timer() - data->starttime) / ANIMATION_TIME; + fac = min_ff(sqrtf(fac), 1.0f); + + if (uiAlignPanelStep(region, fac, false)) { + ED_region_tag_redraw(region); + } + else { + if (UI_panel_is_dragging(panel)) { + /* Note: doing this in #panel_activate_state would require + * removing `const` for context in many other places. */ + reorder_instanced_panel_list(C, region, panel); + } + + panel_activate_state(C, panel, PANEL_STATE_EXIT); + } +} + +static void panels_layout_begin_clear_flags(ListBase *lb) +{ + LISTBASE_FOREACH (Panel *, panel, lb) { + /* Flags to copy over to the next layout pass. */ + const short flag_copy = PANEL_USE_CLOSED_FROM_SEARCH | PANEL_IS_DRAG_DROP; + + const bool was_active = panel->runtime_flag & PANEL_ACTIVE; + const bool was_closed = UI_panel_is_closed(panel); + panel->runtime_flag &= flag_copy; + SET_FLAG_FROM_TEST(panel->runtime_flag, was_active, PANEL_WAS_ACTIVE); + SET_FLAG_FROM_TEST(panel->runtime_flag, was_closed, PANEL_WAS_CLOSED); + + panels_layout_begin_clear_flags(&panel->children); + } +} + +void UI_panels_begin(const bContext *UNUSED(C), ARegion *region) +{ + /* Set all panels as inactive, so that at the end we know which ones were used. Also + * clear other flags so we know later that their values were set for the current redraw. */ + panels_layout_begin_clear_flags(®ion->panels); +} + +void UI_panels_end(const bContext *C, ARegion *region, int *r_x, int *r_y) +{ + ScrArea *area = CTX_wm_area(C); + + region_panels_set_expansion_from_list_data(C, region); + + const bool region_search_filter_active = region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE; + + if (properties_space_needs_realign(area, region)) { + region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); + } + else if (region->flag & RGN_FLAG_SEARCH_FILTER_UPDATE) { + region_panels_set_expansion_from_search_filter(C, region, region_search_filter_active); + } + + if (region->flag & RGN_FLAG_SEARCH_FILTER_ACTIVE) { + /* Clean up the extra panels and buttons created for searching. */ + region_panels_remove_invisible_layouts(region); + } + + LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { + if (panel->runtime_flag & PANEL_ACTIVE) { + BLI_assert(panel->runtime.block != NULL); + panel_calculate_size_recursive(region, panel); + } + } + + /* Offset contents. */ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (block->active && block->panel) { + ui_offset_panel_block(block); + } + } + + /* Re-align, possibly with animation. */ + Panel *panel; + if (panels_need_realign(area, region, &panel)) { + if (panel) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + else { + uiAlignPanelStep(region, 1.0, false); + } + } + + /* Compute size taken up by panels. */ + ui_panels_size(region, r_x, r_y); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Panel Dragging + * \{ */ + +#define DRAG_REGION_PAD (PNL_HEADER * 0.5) +static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel) +{ + uiHandlePanelData *data = (uiHandlePanelData *)panel->activedata; + ARegion *region = CTX_wm_region(C); + + /* Keep the drag position in the region with a small pad to keep the panel visible. */ + const int y = clamp_i(event->y, region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD); + + float dy = (float)(y - data->starty); + + /* Adjust for region zoom. */ + dy *= BLI_rctf_size_y(®ion->v2d.cur) / (float)BLI_rcti_size_y(®ion->winrct); + + /* Add the movement of the view due to edge scrolling while dragging. */ + dy += ((float)region->v2d.cur.ymin - data->start_cur_ymin); + + panel->ofsy = data->startofsy + round_fl_to_int(dy); + + uiAlignPanelStep(region, 0.2f, true); + + ED_region_tag_redraw(region); +} +#undef DRAG_REGION_PAD + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Region Level Panel Interaction + * \{ */ + +static uiPanelMouseState ui_panel_mouse_state_get(const uiBlock *block, + const Panel *panel, + const int mx, + const int my) +{ + if (!IN_RANGE((float)mx, block->rect.xmin, block->rect.xmax)) { + return PANEL_MOUSE_OUTSIDE; + } + + if (IN_RANGE((float)my, block->rect.ymax, block->rect.ymax + PNL_HEADER)) { + return PANEL_MOUSE_INSIDE_HEADER; + } + + if (!UI_panel_is_closed(panel)) { + if (IN_RANGE((float)my, block->rect.ymin, block->rect.ymax + PNL_HEADER)) { + return PANEL_MOUSE_INSIDE_CONTENT; + } + } + + return PANEL_MOUSE_OUTSIDE; +} + +typedef struct uiPanelDragCollapseHandle { + bool was_first_open; + int xy_init[2]; +} uiPanelDragCollapseHandle; + +static void ui_panel_drag_collapse_handler_remove(bContext *UNUSED(C), void *userdata) +{ + uiPanelDragCollapseHandle *dragcol_data = (uiPanelDragCollapseHandle *)userdata; + MEM_freeN(dragcol_data); +} + +static void ui_panel_drag_collapse(const bContext *C, + const uiPanelDragCollapseHandle *dragcol_data, + const int xy_dst[2]) +{ + ARegion *region = CTX_wm_region(C); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float xy_a_block[2] = {UNPACK2(dragcol_data->xy_init)}; + float xy_b_block[2] = {UNPACK2(xy_dst)}; + Panel *panel = block->panel; + + if (panel == NULL || (panel->type && (panel->type->flag & PANEL_TYPE_NO_HEADER))) { + continue; + } + const int oldflag = panel->flag; + + /* Lock axis. */ + xy_b_block[0] = dragcol_data->xy_init[0]; + + /* Use cursor coords in block space. */ + ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]); + ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]); + + /* Set up `rect` to match header size. */ + rctf rect = block->rect; + rect.ymin = rect.ymax; + rect.ymax = rect.ymin + PNL_HEADER; + + /* Touch all panels between last mouse coordinate and the current one. */ + if (BLI_rctf_isect_segment(&rect, xy_a_block, xy_b_block)) { + /* Force panel to open or close. */ + panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; + SET_FLAG_FROM_TEST(panel->flag, dragcol_data->was_first_open, PNL_CLOSED); + + /* If panel->flag has changed this means a panel was opened/closed here. */ + if (panel->flag != oldflag) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + } + } + /* Update the instanced panel data expand flags with the changes made here. */ + set_panels_list_data_expand_flag(C, region); +} + +/** + * Panel drag-collapse (modal handler). + * Clicking and dragging over panels toggles their collapse state based on the panel + * that was first dragged over. If it was open all affected panels including the initial + * one are closed and vice versa. + */ +static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, void *userdata) +{ + wmWindow *win = CTX_wm_window(C); + uiPanelDragCollapseHandle *dragcol_data = (uiPanelDragCollapseHandle *)userdata; + short retval = WM_UI_HANDLER_CONTINUE; + + switch (event->type) { + case MOUSEMOVE: + ui_panel_drag_collapse(C, dragcol_data, &event->x); + + retval = WM_UI_HANDLER_BREAK; + break; + case LEFTMOUSE: + if (event->val == KM_RELEASE) { + /* Done! */ + WM_event_remove_ui_handler(&win->modalhandlers, + ui_panel_drag_collapse_handler, + ui_panel_drag_collapse_handler_remove, + dragcol_data, + true); + ui_panel_drag_collapse_handler_remove(C, dragcol_data); + } + /* Don't let any left-mouse event fall through! */ + retval = WM_UI_HANDLER_BREAK; + break; + } + + return retval; +} + +static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open) +{ + wmWindow *win = CTX_wm_window(C); + const wmEvent *event = win->eventstate; + uiPanelDragCollapseHandle *dragcol_data = (uiPanelDragCollapseHandle *)MEM_mallocN( + sizeof(*dragcol_data), __func__); + + dragcol_data->was_first_open = was_open; + copy_v2_v2_int(dragcol_data->xy_init, &event->x); + + WM_event_add_ui_handler(C, + &win->modalhandlers, + ui_panel_drag_collapse_handler, + ui_panel_drag_collapse_handler_remove, + dragcol_data, + 0); +} + +/** + * Supposing the block has a panel and isn't a menu, handle opening, closing, pinning, etc. + * Code currently assumes layout style for location of widgets + * + * \param mx: The mouse x coordinate, in panel space. + */ +static void ui_handle_panel_header(const bContext *C, + const uiBlock *block, + const int mx, + const int event_type, + const short ctrl, + const short shift) +{ + Panel *panel = block->panel; + ARegion *region = CTX_wm_region(C); + + BLI_assert(panel->type != NULL); + BLI_assert(!(panel->type->flag & PANEL_TYPE_NO_HEADER)); + + const bool is_subpanel = (panel->type->parent != NULL); + const bool use_pin = UI_panel_category_is_visible(region) && !is_subpanel; + const bool show_pin = use_pin && (panel->flag & PNL_PIN); + const bool show_drag = !is_subpanel; + + /* Handle panel pinning. */ + if (use_pin && ELEM(event_type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE) && shift) { + panel->flag ^= PNL_PIN; + ED_region_tag_redraw(region); + return; + } + + float expansion_area_xmax = block->rect.xmax; + if (show_drag) { + expansion_area_xmax -= (PNL_ICON * 1.5f); + } + if (show_pin) { + expansion_area_xmax -= PNL_ICON; + } + + /* Collapse and expand panels. */ + if (ELEM(event_type, EVT_RETKEY, EVT_PADENTER, EVT_AKEY) || mx < expansion_area_xmax) { + if (ctrl && !is_subpanel) { + /* For parent panels, collapse all other panels or toggle children. */ + if (UI_panel_is_closed(panel) || BLI_listbase_is_empty(&panel->children)) { + panels_collapse_all(region, panel); + + /* Reset the view - we don't want to display a view without content. */ + UI_view2d_offset(®ion->v2d, 0.0f, 1.0f); + } + else { + /* If a panel has sub-panels and it's open, toggle the expansion + * of the sub-panels (based on the expansion of the first sub-panel). */ + Panel *first_child = (Panel *)panel->children.first; + BLI_assert(first_child != NULL); + panel_set_flag_recursive(panel, PNL_CLOSED, !UI_panel_is_closed(first_child)); + panel->flag |= PNL_CLOSED; + } + } + + SET_FLAG_FROM_TEST(panel->flag, !UI_panel_is_closed(panel), PNL_CLOSED); + + if (event_type == LEFTMOUSE) { + ui_panel_drag_collapse_handler_add(C, UI_panel_is_closed(panel)); + } + + /* Set panel custom data (modifier) active when expanding subpanels, but not top-level + * panels to allow collapsing and expanding without setting the active element. */ + if (is_subpanel) { + panel_custom_data_active_set(panel); + } + + set_panels_list_data_expand_flag(C, region); + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + return; + } + + /* Handle panel dragging. For now don't allow dragging in floating regions. */ + if (show_drag && !(region->alignment == RGN_ALIGN_FLOAT)) { + const float drag_area_xmin = block->rect.xmax - (PNL_ICON * 1.5f); + const float drag_area_xmax = block->rect.xmax; + if (IN_RANGE(mx, drag_area_xmin, drag_area_xmax)) { + panel_activate_state(C, panel, PANEL_STATE_DRAG); + return; + } + } + + /* Handle panel unpinning. */ + if (show_pin) { + const float pin_area_xmin = expansion_area_xmax; + const float pin_area_xmax = pin_area_xmin + PNL_ICON; + if (IN_RANGE(mx, pin_area_xmin, pin_area_xmax)) { + panel->flag ^= PNL_PIN; + ED_region_tag_redraw(region); + return; + } + } +} + +bool UI_panel_category_is_visible(const ARegion *region) +{ + /* Check for more than one category. */ + return region->panels_category.first && + region->panels_category.first != region->panels_category.last; +} + +PanelCategoryDyn *UI_panel_category_find(const ARegion *region, const char *idname) +{ + return (PanelCategoryDyn *)BLI_findstring( + ®ion->panels_category, idname, offsetof(PanelCategoryDyn, idname)); +} + +PanelCategoryStack *UI_panel_category_active_find(ARegion *region, const char *idname) +{ + return (PanelCategoryStack *)BLI_findstring( + ®ion->panels_category_active, idname, offsetof(PanelCategoryStack, idname)); +} + +static void ui_panel_category_active_set(ARegion *region, const char *idname, bool fallback) +{ + ListBase *lb = ®ion->panels_category_active; + PanelCategoryStack *pc_act = UI_panel_category_active_find(region, idname); + + if (pc_act) { + BLI_remlink(lb, pc_act); + } + else { + pc_act = (PanelCategoryStack *)MEM_callocN(sizeof(PanelCategoryStack), __func__); + BLI_strncpy(pc_act->idname, idname, sizeof(pc_act->idname)); + } + + if (fallback) { + /* For fall-backs, add at the end so explicitly chosen categories have priority. */ + BLI_addtail(lb, pc_act); + } + else { + BLI_addhead(lb, pc_act); + } + + /* Validate all active panels. We could do this on load, they are harmless - + * but we should remove them somewhere. + * (Add-ons could define panels and gather cruft over time). */ + { + PanelCategoryStack *pc_act_next; + /* intentionally skip first */ + pc_act_next = pc_act->next; + while ((pc_act = pc_act_next)) { + pc_act_next = pc_act->next; + if (!BLI_findstring( + ®ion->type->paneltypes, pc_act->idname, offsetof(PanelType, category))) { + BLI_remlink(lb, pc_act); + MEM_freeN(pc_act); + } + } + } +} + +void UI_panel_category_active_set(ARegion *region, const char *idname) +{ + ui_panel_category_active_set(region, idname, false); +} + +void UI_panel_category_active_set_default(ARegion *region, const char *idname) +{ + if (!UI_panel_category_active_find(region, idname)) { + ui_panel_category_active_set(region, idname, true); + } +} + +const char *UI_panel_category_active_get(ARegion *region, bool set_fallback) +{ + LISTBASE_FOREACH (PanelCategoryStack *, pc_act, ®ion->panels_category_active) { + if (UI_panel_category_find(region, pc_act->idname)) { + return pc_act->idname; + } + } + + if (set_fallback) { + PanelCategoryDyn *pc_dyn = (PanelCategoryDyn *)region->panels_category.first; + if (pc_dyn) { + ui_panel_category_active_set(region, pc_dyn->idname, true); + return pc_dyn->idname; + } + } + + return NULL; +} + +static PanelCategoryDyn *panel_categories_find_mouse_over(ARegion *region, const wmEvent *event) +{ + LISTBASE_FOREACH (PanelCategoryDyn *, ptd, ®ion->panels_category) { + if (BLI_rcti_isect_pt(&ptd->rect, event->mval[0], event->mval[1])) { + return ptd; + } + } + + return NULL; +} + +void UI_panel_category_add(ARegion *region, const char *name) +{ + PanelCategoryDyn *pc_dyn = (PanelCategoryDyn *)MEM_callocN(sizeof(*pc_dyn), __func__); + BLI_addtail(®ion->panels_category, pc_dyn); + + BLI_strncpy(pc_dyn->idname, name, sizeof(pc_dyn->idname)); + + /* 'pc_dyn->rect' must be set on draw. */ +} + +void UI_panel_category_clear_all(ARegion *region) +{ + BLI_freelistN(®ion->panels_category); +} + +static int ui_handle_panel_category_cycling(const wmEvent *event, + ARegion *region, + const uiBut *active_but) +{ + const bool is_mousewheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE); + const bool inside_tabregion = + ((RGN_ALIGN_ENUM_FROM_MASK(region->alignment) != RGN_ALIGN_RIGHT) ? + (event->mval[0] < ((PanelCategoryDyn *)region->panels_category.first)->rect.xmax) : + (event->mval[0] > ((PanelCategoryDyn *)region->panels_category.first)->rect.xmin)); + + /* If mouse is inside non-tab region, ctrl key is required. */ + if (is_mousewheel && !event->ctrl && !inside_tabregion) { + return WM_UI_HANDLER_CONTINUE; + } + + if (active_but && ui_but_supports_cycling(active_but)) { + /* Skip - exception to make cycling buttons using ctrl+mousewheel work in tabbed regions. */ + } + else { + const char *category = UI_panel_category_active_get(region, false); + if (LIKELY(category)) { + PanelCategoryDyn *pc_dyn = UI_panel_category_find(region, category); + if (LIKELY(pc_dyn)) { + if (is_mousewheel) { + /* We can probably get rid of this and only allow Ctrl-Tabbing. */ + pc_dyn = (event->type == WHEELDOWNMOUSE) ? pc_dyn->next : pc_dyn->prev; + } + else { + const bool backwards = event->shift; + pc_dyn = backwards ? pc_dyn->prev : pc_dyn->next; + if (!pc_dyn) { + /* Proper cyclic behavior, back to first/last category (only used for ctrl+tab). */ + pc_dyn = backwards ? (PanelCategoryDyn *)region->panels_category.last : + (PanelCategoryDyn *)region->panels_category.first; + } + } + + if (pc_dyn) { + /* Intentionally don't reset scroll in this case, + * allowing for quick browsing between tabs. */ + UI_panel_category_active_set(region, pc_dyn->idname); + ED_region_tag_redraw(region); + } + } + } + return WM_UI_HANDLER_BREAK; + } + + return WM_UI_HANDLER_CONTINUE; +} + +/** + * Handle region panel events like opening and closing panels, changing categories, etc. + * + * \note Could become a modal key-map. + */ +int ui_handler_panel_region(bContext *C, + const wmEvent *event, + ARegion *region, + const uiBut *active_but) +{ + /* Mouse-move events are handled by separate handlers for dragging and drag collapsing. */ + if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) { + return WM_UI_HANDLER_CONTINUE; + } + + /* We only use KM_PRESS events in this function, so it's simpler to return early. */ + if (event->val != KM_PRESS) { + return WM_UI_HANDLER_CONTINUE; + } + + /* Scroll-bars can overlap panels now, they have handling priority. */ + if (UI_view2d_mouse_in_scrollers(region, ®ion->v2d, event->x, event->y)) { + return WM_UI_HANDLER_CONTINUE; + } + + int retval = WM_UI_HANDLER_CONTINUE; + + /* Handle category tabs. */ + if (UI_panel_category_is_visible(region)) { + if (event->type == LEFTMOUSE) { + PanelCategoryDyn *pc_dyn = panel_categories_find_mouse_over(region, event); + if (pc_dyn) { + UI_panel_category_active_set(region, pc_dyn->idname); + ED_region_tag_redraw(region); + + /* Reset scroll to the top (T38348). */ + UI_view2d_offset(®ion->v2d, -1.0f, 1.0f); + + retval = WM_UI_HANDLER_BREAK; + } + } + else if ((event->type == EVT_TABKEY && event->ctrl) || + ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE)) { + /* Cycle tabs. */ + retval = ui_handle_panel_category_cycling(event, region, active_but); + } + } + + if (retval == WM_UI_HANDLER_BREAK) { + return retval; + } + + const bool region_has_active_button = (ui_region_find_active_but(region) != NULL); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + Panel *panel = block->panel; + if (panel == NULL || panel->type == NULL) { + continue; + } + /* We can't expand or collapse panels without headers, they would disappear. */ + if (panel->type->flag & PANEL_TYPE_NO_HEADER) { + continue; + } + + int mx = event->x; + int my = event->y; + ui_window_to_block(region, block, &mx, &my); + + const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); + + if (mouse_state != PANEL_MOUSE_OUTSIDE) { + /* Mark panels that have been interacted with so their expansion + * doesn't reset when property search finishes. */ + SET_FLAG_FROM_TEST(panel->flag, UI_panel_is_closed(panel), PNL_CLOSED); + panel->runtime_flag &= ~PANEL_USE_CLOSED_FROM_SEARCH; + + /* The panel collapse / expand key "A" is special as it takes priority over + * active button handling. */ + if (event->type == EVT_AKEY && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey)) { + retval = WM_UI_HANDLER_BREAK; + ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift); + break; + } + } + + /* Don't do any other panel handling with an active button. */ + if (region_has_active_button) { + continue; + } + + if (mouse_state == PANEL_MOUSE_INSIDE_HEADER) { + /* All mouse clicks inside panel headers should return in break. */ + if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER, LEFTMOUSE)) { + retval = WM_UI_HANDLER_BREAK; + ui_handle_panel_header(C, block, mx, event->type, event->ctrl, event->shift); + } + else if (event->type == RIGHTMOUSE) { + retval = WM_UI_HANDLER_BREAK; + ui_popup_context_menu_for_panel(C, region, block->panel); + } + break; + } + } + + return retval; +} + +static void ui_panel_custom_data_set_recursive(Panel *panel, PointerRNA *custom_data) +{ + panel->runtime.custom_data_ptr = custom_data; + + LISTBASE_FOREACH (Panel *, child_panel, &panel->children) { + ui_panel_custom_data_set_recursive(child_panel, custom_data); + } +} + +void UI_panel_custom_data_set(Panel *panel, PointerRNA *custom_data) +{ + BLI_assert(panel->type != NULL); + + /* Free the old custom data, which should be shared among all of the panel's sub-panels. */ + if (panel->runtime.custom_data_ptr != NULL) { + MEM_freeN(panel->runtime.custom_data_ptr); + } + + ui_panel_custom_data_set_recursive(panel, custom_data); +} + +PointerRNA *UI_panel_custom_data_get(const Panel *panel) +{ + return panel->runtime.custom_data_ptr; +} + +PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + Panel *panel = block->panel; + if (panel == NULL) { + continue; + } + + int mx = event->x; + int my = event->y; + ui_window_to_block(region, block, &mx, &my); + const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my); + if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) { + return UI_panel_custom_data_get(panel); + } + } + + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Window Level Modal Panel Interaction + * \{ */ + +/* Note, this is modal handler and should not swallow events for animation. */ +static int ui_handler_panel(bContext *C, const wmEvent *event, void *userdata) +{ + Panel *panel = (Panel *)userdata; + uiHandlePanelData *data = (uiHandlePanelData *)panel->activedata; + + /* Verify if we can stop. */ + if (event->type == LEFTMOUSE && event->val == KM_RELEASE) { + panel_activate_state(C, panel, PANEL_STATE_ANIMATION); + } + else if (event->type == MOUSEMOVE) { + if (data->state == PANEL_STATE_DRAG) { + ui_do_drag(C, event, panel); + } + } + else if (event->type == TIMER && event->customdata == data->animtimer) { + if (data->state == PANEL_STATE_ANIMATION) { + ui_do_animate(C, panel); + } + else if (data->state == PANEL_STATE_DRAG) { + ui_do_drag(C, event, panel); + } + } + + data = (uiHandlePanelData *)panel->activedata; + + if (data && data->state == PANEL_STATE_ANIMATION) { + return WM_UI_HANDLER_CONTINUE; + } + return WM_UI_HANDLER_BREAK; +} + +static void ui_handler_remove_panel(bContext *C, void *userdata) +{ + Panel *panel = (Panel *)userdata; + + panel_activate_state(C, panel, PANEL_STATE_EXIT); +} + +static void panel_handle_data_ensure(const bContext *C, + wmWindow *win, + const ARegion *region, + Panel *panel, + const uiHandlePanelState state) +{ + if (panel->activedata == NULL) { + panel->activedata = MEM_callocN(sizeof(uiHandlePanelData), __func__); + WM_event_add_ui_handler( + C, &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, 0); + } + + uiHandlePanelData *data = (uiHandlePanelData *)panel->activedata; + + data->animtimer = WM_event_add_timer(CTX_wm_manager(C), win, TIMER, ANIMATION_INTERVAL); + + data->state = state; + data->startx = win->eventstate->x; + data->starty = win->eventstate->y; + data->startofsx = panel->ofsx; + data->startofsy = panel->ofsy; + data->start_cur_xmin = region->v2d.cur.xmin; + data->start_cur_ymin = region->v2d.cur.ymin; + data->starttime = PIL_check_seconds_timer(); +} + +/** + * \note "select" and "drag drop" flags: First, the panel is "picked up" and both flags are set. + * Then when the mouse releases and the panel starts animating to its aligned position, PNL_SELECT + * is unset. When the animation finishes, PANEL_IS_DRAG_DROP is cleared. + */ +static void panel_activate_state(const bContext *C, Panel *panel, const uiHandlePanelState state) +{ + uiHandlePanelData *data = (uiHandlePanelData *)panel->activedata; + wmWindow *win = CTX_wm_window(C); + ARegion *region = CTX_wm_region(C); + + if (data != NULL && data->state == state) { + return; + } + + if (state == PANEL_STATE_DRAG) { + panel_custom_data_active_set(panel); + + panel_set_flag_recursive(panel, PNL_SELECT, true); + panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, true); + + panel_handle_data_ensure(C, win, region, panel, state); + + /* Initiate edge panning during drags for scrolling beyond the initial region view. */ + wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true); + ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true); + } + else if (state == PANEL_STATE_ANIMATION) { + panel_set_flag_recursive(panel, PNL_SELECT, false); + + panel_handle_data_ensure(C, win, region, panel, state); + } + else if (state == PANEL_STATE_EXIT) { + panel_set_runtime_flag_recursive(panel, PANEL_IS_DRAG_DROP, false); + + BLI_assert(data != NULL); + + if (data->animtimer) { + WM_event_remove_timer(CTX_wm_manager(C), win, data->animtimer); + data->animtimer = NULL; + } + + MEM_freeN(data); + panel->activedata = NULL; + + WM_event_remove_ui_handler( + &win->modalhandlers, ui_handler_panel, ui_handler_remove_panel, panel, false); + } + + ED_region_tag_redraw(region); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c deleted file mode 100644 index aa10d092f5e..00000000000 --- a/source/blender/editors/interface/interface_query.c +++ /dev/null @@ -1,704 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - * - * Utilities to inspect the interface, extract information. - */ - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_rect.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); -} - -/** - * Can we mouse over the button or is it hidden/disabled/layout. - * \note ctrl is kind of a hack currently, - * so that non-embossed UI_BTYPE_TEXT button behaves as a label when ctrl is not pressed. - */ -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) && (but->emboss == UI_EMBOSS_NONE) && !labeledit) { - return false; - } - if ((but->type == UI_BTYPE_LISTROW) && labeledit) { - return false; - } - - return true; -} - -/* file selectors are exempt from utf-8 checks */ -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, int x, int y) -{ - uiBlock *block = but->block; - if (!ui_region_contains_point_px(region, x, y)) { - return false; - } - - float mx = x, my = y; - 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->x, y = event->y; - - 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); -} - -/* x and y are only used in case event is NULL... */ -uiBut *ui_but_find_mouse_over_ex(const ARegion *region, - const int x, - const int y, - const bool labeledit) -{ - uiBut *butover = NULL; - - if (!ui_region_contains_point_px(region, x, y)) { - return NULL; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float mx = x, my = y; - ui_window_to_block_fl(region, block, &mx, &my); - - LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { - 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->x, event->y, event->ctrl != 0); -} - -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(ARegion *region, int x, int y) -{ - if (!ui_region_contains_point_px(region, x, y)) { - return NULL; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - float mx = x, my = y; - 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(ARegion *region, const wmEvent *event) -{ - return ui_list_find_mouse_over_ex(region, event->x, event->y); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \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_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 - * \{ */ - -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 x, - const int y, - bool only_clip) -{ - if (!ui_region_contains_point_px(region, x, y)) { - return NULL; - } - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - if (only_clip) { - if ((block->flag & UI_BLOCK_CLIP_EVENTS) == 0) { - continue; - } - } - float mx = x, my = y; - 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->x, event->y, only_clip); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Region (#ARegion) State - * \{ */ - -uiBut *ui_region_find_active_but(ARegion *region) -{ - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->active) { - 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, int x, int y) -{ - rcti winrct; - ui_region_winrct_get_no_margin(region, &winrct); - if (!BLI_rcti_isect_pt(&winrct, x, y)) { - 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 = x, my = y; - - ui_window_to_region(region, &mx, &my); - if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) || - UI_view2d_mouse_in_scrollers(region, ®ion->v2d, x, y)) { - 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 - * \{ */ - -/** Check if the cursor is over any popups. */ -ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y) -{ - LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { - rcti winrct; - - ui_region_winrct_get_no_margin(region, &winrct); - - if (BLI_rcti_isect_pt(&winrct, x, y)) { - 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->x, event->y); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \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..f3aeae2464f --- /dev/null +++ b/source/blender/editors/interface/interface_query.cc @@ -0,0 +1,702 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * Utilities to inspect the interface, extract information. + */ + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_rect.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); +} + +/** + * Can we mouse over the button or is it hidden/disabled/layout. + * \note ctrl is kind of a hack currently, + * so that non-embossed UI_BTYPE_TEXT button behaves as a label when ctrl is not pressed. + */ +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) && (but->emboss == UI_EMBOSS_NONE) && !labeledit) { + return false; + } + if ((but->type == UI_BTYPE_LISTROW) && labeledit) { + return false; + } + + return true; +} + +/* file selectors are exempt from utf-8 checks */ +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((RadialDirection)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, int x, int y) +{ + uiBlock *block = but->block; + if (!ui_region_contains_point_px(region, x, y)) { + return false; + } + + float mx = x, my = y; + 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->x, y = event->y; + + 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); +} + +/* x and y are only used in case event is NULL... */ +uiBut *ui_but_find_mouse_over_ex(const ARegion *region, + const int x, + const int y, + const bool labeledit) +{ + uiBut *butover = NULL; + + if (!ui_region_contains_point_px(region, x, y)) { + return NULL; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float mx = x, my = y; + ui_window_to_block_fl(region, block, &mx, &my); + + LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) { + 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->x, event->y, event->ctrl != 0); +} + +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(ARegion *region, int x, int y) +{ + if (!ui_region_contains_point_px(region, x, y)) { + return NULL; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + float mx = x, my = y; + 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(ARegion *region, const wmEvent *event) +{ + return ui_list_find_mouse_over_ex(region, event->x, event->y); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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 = (uiBut *)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_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 + * \{ */ + +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 = (const uiBut *)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 = (const uiBut *)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 x, + const int y, + bool only_clip) +{ + if (!ui_region_contains_point_px(region, x, y)) { + return NULL; + } + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + if (only_clip) { + if ((block->flag & UI_BLOCK_CLIP_EVENTS) == 0) { + continue; + } + } + float mx = x, my = y; + 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->x, event->y, only_clip); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Region (#ARegion) State + * \{ */ + +uiBut *ui_region_find_active_but(ARegion *region) +{ + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->active) { + 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, int x, int y) +{ + rcti winrct; + ui_region_winrct_get_no_margin(region, &winrct); + if (!BLI_rcti_isect_pt(&winrct, x, y)) { + 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 = x, my = y; + + ui_window_to_region(region, &mx, &my); + if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) || + UI_view2d_mouse_in_scrollers(region, ®ion->v2d, x, y)) { + 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 + * \{ */ + +/** Check if the cursor is over any popups. */ +ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y) +{ + LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) { + rcti winrct; + + ui_region_winrct_get_no_margin(region, &winrct); + + if (BLI_rcti_isect_pt(&winrct, x, y)) { + 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->x, event->y); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \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_region_color_picker.c b/source/blender/editors/interface/interface_region_color_picker.c deleted file mode 100644 index e68705e4321..00000000000 --- a/source/blender/editors/interface/interface_region_color_picker.c +++ /dev/null @@ -1,929 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 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; - } -} - -/* Returns true if the button is for a color with gamma baked in, - * or if it's a color picker for such a button. */ -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; -} - -/* for picker, while editing hsv */ -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..e7195ca4643 --- /dev/null +++ b/source/blender/editors/interface/interface_region_color_picker.cc @@ -0,0 +1,930 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 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; + } +} + +/* Returns true if the button is for a color with gamma baked in, + * or if it's a color picker for such a button. */ +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; +} + +/* for picker, while editing hsv */ +void ui_but_hsv_set(uiBut *but) +{ + float rgb_perceptual[3]; + ColorPicker *cpicker = (ColorPicker *)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, (ColorPicker *)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 = (ColorPicker *)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 = (ColorPicker *)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 = (ColorPicker *)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 = (uiBut *)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, 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 = (eButGradientType)(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, (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 == NULL) { + uiPopupBlockHandle *popup = block->handle; + ColorPicker *cpicker = (ColorPicker *)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 = (uiBut *)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 = (ColorPicker *)MEM_callocN(sizeof(ColorPicker), "color_picker"); + 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 b50daa0df21..00000000000 --- a/source/blender/editors/interface/interface_region_hud.c +++ /dev/null @@ -1,402 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 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..d7b3130e4ec --- /dev/null +++ b/source/blender/editors/interface/interface_region_hud.cc @@ -0,0 +1,405 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 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) { + HudRegionData *hrd = (HudRegionData *)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 = (PanelType *)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) +{ + HudRegionData *hrd = (HudRegionData *)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) { + rcti rect{ + 0, + region->winx, + 0, + region->winy, + }; + ui_draw_menu_back(NULL, NULL, &rect); + ED_region_panels_draw(C, region); + } +} + +ARegionType *ED_area_type_hud(int space_type) +{ + ARegionType *art = (ARegionType *)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 = (ARegion *)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; + } + + { + HudRegionData *hrd = (HudRegionData *)region->regiondata; + if (hrd == NULL) { + hrd = (HudRegionData *)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){ + 0.0f, + region->winx, + 0.0f, + 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){ + 0.0f, + region->winx, + 0.0f, + 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_popup.c b/source/blender/editors/interface/interface_region_popup.c deleted file mode 100644 index 8135f5a203e..00000000000 --- a/source/blender/editors/interface/interface_region_popup.c +++ /dev/null @@ -1,858 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 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 - * \{ */ - -/** - * Translate any popup regions (so we can drag them). - */ -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) && (dir1 == UI_DIR_LEFT || dir1 == UI_DIR_RIGHT)) { - dir2 = UI_DIR_DOWN; - } - if ((dir2 == 0) && (dir1 == UI_DIR_UP || dir1 == 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 pulldowns... */ - 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); - } -} - -/** - * Called for creating new popups and refreshing existing ones. - */ -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; -#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, ®ion->uiblocks); - } - - /* 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; -#endif - - return block; -} - -uiPopupBlockHandle *ui_popup_block_create(bContext *C, - ARegion *butregion, - uiBut *but, - uiBlockCreateFunc create_func, - uiBlockHandleCreateFunc handle_create_func, - void *arg, - void (*arg_free)(void *arg)) -{ - 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->x); - - /* 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..dcfbd538f89 --- /dev/null +++ b/source/blender/editors/interface/interface_region_popup.cc @@ -0,0 +1,858 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 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 + * \{ */ + +/** + * Translate any popup regions (so we can drag them). + */ +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) && (dir1 == UI_DIR_LEFT || dir1 == UI_DIR_RIGHT)) { + dir2 = UI_DIR_DOWN; + } + if ((dir2 == 0) && (dir1 == UI_DIR_UP || dir1 == 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 pulldowns... */ + 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 = (uiSafetyRct *)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); + } +} + +/** + * Called for creating new popups and refreshing existing ones. + */ +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 = (uiBlock *)region->uiblocks.first; + uiBlock *block; + + handle->refresh = (block_old != NULL); + + BLI_assert(!handle->refresh || handle->can_refresh); + +#ifdef DEBUG + wmEvent *event_back = window->eventstate; +#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 = (uiSafetyRct *)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, ®ion->uiblocks); + } + + /* 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; +#endif + + return block; +} + +uiPopupBlockHandle *ui_popup_block_create(bContext *C, + ARegion *butregion, + uiBut *but, + uiBlockCreateFunc create_func, + uiBlockHandleCreateFunc handle_create_func, + void *arg, + void (*arg_free)(void *arg)) +{ + 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 = (uiPopupBlockHandle *)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->x); + + /* 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_search.c b/source/blender/editors/interface/interface_region_search.c deleted file mode 100644 index 987cde61f97..00000000000 --- a/source/blender/editors/interface/interface_region_search.c +++ /dev/null @@ -1,1074 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2008 Blender Foundation. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - * - * Search Box Region & Interaction - */ - -#include -#include -#include - -#include "DNA_ID.h" -#include "MEM_guardedalloc.h" - -#include "DNA_userdef_types.h" - -#include "BLI_math.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_interface_icons.h" -#include "UI_view2d.h" - -#include "BLT_translation.h" - -#include "ED_screen.h" - -#include "GPU_state.h" -#include "interface_intern.h" -#include "interface_regions_intern.h" - -#define MENU_BORDER (int)(0.3f * U.widget_unit) - -/* -------------------------------------------------------------------- */ -/** \name Search Box Creation - * \{ */ - -struct uiSearchItems { - int maxitem, totitem, maxstrlen; - - int offset, offset_i; /* offset for inserting in array */ - int more; /* flag indicating there are more items */ - - char **names; - void **pointers; - int *icons; - int *states; - uint8_t *name_prefix_offsets; - - /** Is there any item with an icon? */ - bool has_icon; - - AutoComplete *autocpl; - void *active; -}; - -typedef struct uiSearchboxData { - rcti bbox; - uiFontStyle fstyle; - uiSearchItems items; - /** index in items array */ - int active; - /** when menu opened with enough space for this */ - bool noback; - /** draw thumbnail previews, rather than list */ - bool preview; - /** Use the #UI_SEP_CHAR char for splitting shortcuts (good for operators, bad for data). */ - bool use_sep; - int prv_rows, prv_cols; - /** - * Show the active icon and text after the last instance of this string. - * Used so we can show leading text to menu items less prominently (not related to 'use_sep'). - */ - const char *sep_string; -} uiSearchboxData; - -#define SEARCH_ITEMS 10 - -/** - * Public function exported for functions that use #UI_BTYPE_SEARCH_MENU. - * - * \param items: Stores the items. - * \param name: Text to display for the item. - * \param poin: Opaque pointer (for use by the caller). - * \param iconid: The icon, #ICON_NONE for no icon. - * \param state: The buttons state flag, compatible with #uiBut.flag, - * typically #UI_BUT_DISABLED / #UI_BUT_INACTIVE. - * \return false if there is nothing to add. - */ -bool UI_search_item_add(uiSearchItems *items, - const char *name, - void *poin, - int iconid, - int state, - const uint8_t name_prefix_offset) -{ - /* hijack for autocomplete */ - if (items->autocpl) { - UI_autocomplete_update_name(items->autocpl, name); - return true; - } - - if (iconid) { - items->has_icon = true; - } - - /* hijack for finding active item */ - if (items->active) { - if (poin == items->active) { - items->offset_i = items->totitem; - } - items->totitem++; - return true; - } - - if (items->totitem >= items->maxitem) { - items->more = 1; - return false; - } - - /* skip first items in list */ - if (items->offset_i > 0) { - items->offset_i--; - return true; - } - - if (items->names) { - BLI_strncpy(items->names[items->totitem], name, items->maxstrlen); - } - if (items->pointers) { - items->pointers[items->totitem] = poin; - } - if (items->icons) { - items->icons[items->totitem] = iconid; - } - - if (name_prefix_offset != 0) { - /* Lazy initialize, as this isn't used often. */ - if (items->name_prefix_offsets == NULL) { - items->name_prefix_offsets = MEM_callocN( - items->maxitem * sizeof(*items->name_prefix_offsets), "search name prefix offsets"); - } - items->name_prefix_offsets[items->totitem] = name_prefix_offset; - } - - /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set - * which will cause problems, add others as needed. */ - BLI_assert( - (state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)) == 0); - if (items->states) { - items->states[items->totitem] = state; - } - - items->totitem++; - - return true; -} - -int UI_searchbox_size_y(void) -{ - return SEARCH_ITEMS * UI_UNIT_Y + 2 * UI_POPUP_MENU_TOP; -} - -int UI_searchbox_size_x(void) -{ - return 12 * UI_UNIT_X; -} - -int UI_search_items_find_index(uiSearchItems *items, const char *name) -{ - if (items->name_prefix_offsets != NULL) { - for (int i = 0; i < items->totitem; i++) { - if (STREQ(name, items->names[i] + items->name_prefix_offsets[i])) { - return i; - } - } - } - else { - for (int i = 0; i < items->totitem; i++) { - if (STREQ(name, items->names[i])) { - return i; - } - } - } - return -1; -} - -/* region is the search box itself */ -static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step) -{ - uiSearchboxData *data = region->regiondata; - - /* apply step */ - data->active += step; - - if (data->items.totitem == 0) { - data->active = -1; - } - else if (data->active >= data->items.totitem) { - if (data->items.more) { - data->items.offset++; - data->active = data->items.totitem - 1; - ui_searchbox_update(C, region, but, false); - } - else { - data->active = data->items.totitem - 1; - } - } - else if (data->active < 0) { - if (data->items.offset) { - data->items.offset--; - data->active = 0; - ui_searchbox_update(C, region, but, false); - } - else { - /* only let users step into an 'unset' state for unlink buttons */ - data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0; - } - } - - ED_region_tag_redraw(region); -} - -static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr) -{ - /* thumbnail preview */ - if (data->preview) { - const int butw = (BLI_rcti_size_x(&data->bbox) - 2 * MENU_BORDER) / data->prv_cols; - const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_BORDER) / data->prv_rows; - int row, col; - - *r_rect = data->bbox; - - col = itemnr % data->prv_cols; - row = itemnr / data->prv_cols; - - r_rect->xmin += MENU_BORDER + (col * butw); - r_rect->xmax = r_rect->xmin + butw; - - r_rect->ymax -= MENU_BORDER + (row * buth); - r_rect->ymin = r_rect->ymax - buth; - } - /* list view */ - else { - const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * UI_POPUP_MENU_TOP) / SEARCH_ITEMS; - - *r_rect = data->bbox; - r_rect->xmin = data->bbox.xmin + 3.0f; - r_rect->xmax = data->bbox.xmax - 3.0f; - - r_rect->ymax = data->bbox.ymax - UI_POPUP_MENU_TOP - itemnr * buth; - r_rect->ymin = r_rect->ymax - buth; - } -} - -int ui_searchbox_find_index(ARegion *region, const char *name) -{ - uiSearchboxData *data = region->regiondata; - return UI_search_items_find_index(&data->items, name); -} - -/* x and y in screen-coords. */ -bool ui_searchbox_inside(ARegion *region, int x, int y) -{ - uiSearchboxData *data = region->regiondata; - - return BLI_rcti_isect_pt(&data->bbox, x - region->winrct.xmin, y - region->winrct.ymin); -} - -/* string validated to be of correct length (but->hardmax) */ -bool ui_searchbox_apply(uiBut *but, ARegion *region) -{ - uiSearchboxData *data = region->regiondata; - uiButSearch *search_but = (uiButSearch *)but; - - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - search_but->item_active = NULL; - - if (data->active != -1) { - const char *name = data->items.names[data->active] + - /* Never include the prefix in the button. */ - (data->items.name_prefix_offsets ? - data->items.name_prefix_offsets[data->active] : - 0); - - const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL; - - BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen); - - search_but->item_active = data->items.pointers[data->active]; - - return true; - } - if (but->flag & UI_BUT_VALUE_CLEAR) { - /* It is valid for _VALUE_CLEAR flavor to have no active element - * (it's a valid way to unlink). */ - but->editstr[0] = '\0'; - - return true; - } - return false; -} - -static struct ARegion *wm_searchbox_tooltip_init(struct bContext *C, - struct ARegion *region, - int *UNUSED(r_pass), - double *UNUSED(pass_delay), - bool *r_exit_on_event) -{ - *r_exit_on_event = true; - - LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - if (but->type != UI_BTYPE_SEARCH_MENU) { - continue; - } - - uiButSearch *search_but = (uiButSearch *)but; - if (!search_but->item_tooltip_fn) { - continue; - } - - ARegion *searchbox_region = UI_region_searchbox_region_get(region); - uiSearchboxData *data = searchbox_region->regiondata; - - BLI_assert(data->items.pointers[data->active] == search_but->item_active); - - rcti rect; - ui_searchbox_butrect(&rect, data, data->active); - - return search_but->item_tooltip_fn( - C, region, &rect, search_but->arg, search_but->item_active); - } - } - return NULL; -} - -bool ui_searchbox_event( - bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event) -{ - uiSearchboxData *data = region->regiondata; - uiButSearch *search_but = (uiButSearch *)but; - int type = event->type, val = event->val; - bool handled = false; - bool tooltip_timer_started = false; - - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - if (type == MOUSEPAN) { - ui_pan_to_scroll(event, &type, &val); - } - - switch (type) { - case WHEELUPMOUSE: - case EVT_UPARROWKEY: - ui_searchbox_select(C, region, but, -1); - handled = true; - break; - case WHEELDOWNMOUSE: - case EVT_DOWNARROWKEY: - ui_searchbox_select(C, region, but, 1); - handled = true; - break; - case RIGHTMOUSE: - if (val) { - if (search_but->item_context_menu_fn) { - if (data->active != -1) { - /* Check the cursor is over the active element - * (a little confusing if this isn't the case, although it does work). */ - rcti rect; - ui_searchbox_butrect(&rect, data, data->active); - if (BLI_rcti_isect_pt( - &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { - - void *active = data->items.pointers[data->active]; - if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) { - handled = true; - } - } - } - } - } - break; - case MOUSEMOVE: { - bool is_inside = false; - - if (BLI_rcti_isect_pt(®ion->winrct, event->x, event->y)) { - rcti rect; - int a; - - for (a = 0; a < data->items.totitem; a++) { - ui_searchbox_butrect(&rect, data, a); - if (BLI_rcti_isect_pt( - &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { - is_inside = true; - if (data->active != a) { - data->active = a; - ui_searchbox_select(C, region, but, 0); - handled = true; - break; - } - } - } - } - - if (U.flag & USER_TOOLTIPS) { - if (is_inside) { - if (data->active != -1) { - ScrArea *area = CTX_wm_area(C); - search_but->item_active = data->items.pointers[data->active]; - WM_tooltip_timer_init(C, CTX_wm_window(C), area, butregion, wm_searchbox_tooltip_init); - tooltip_timer_started = true; - } - } - } - - break; - } - } - - if (handled && (tooltip_timer_started == false)) { - wmWindow *win = CTX_wm_window(C); - WM_tooltip_clear(C, win); - } - - return handled; -} - -/** Wrap #uiButSearchUpdateFn callback. */ -static void ui_searchbox_update_fn(bContext *C, - uiButSearch *search_but, - const char *str, - uiSearchItems *items) -{ - /* While the button is in text editing mode (searchbox open), remove tooltips on every update. */ - if (search_but->but.editstr) { - wmWindow *win = CTX_wm_window(C); - WM_tooltip_clear(C, win); - } - const bool is_first_search = !search_but->but.changed; - search_but->items_update_fn(C, search_but->arg, str, items, is_first_search); -} - -/* region is the search box itself */ -void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset) -{ - uiButSearch *search_but = (uiButSearch *)but; - uiSearchboxData *data = region->regiondata; - - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - /* reset vars */ - data->items.totitem = 0; - data->items.more = 0; - if (reset == false) { - data->items.offset_i = data->items.offset; - } - else { - data->items.offset_i = data->items.offset = 0; - data->active = -1; - - /* handle active */ - if (search_but->items_update_fn && search_but->item_active) { - data->items.active = search_but->item_active; - ui_searchbox_update_fn(C, search_but, but->editstr, &data->items); - data->items.active = NULL; - - /* found active item, calculate real offset by centering it */ - if (data->items.totitem) { - /* first case, begin of list */ - if (data->items.offset_i < data->items.maxitem) { - data->active = data->items.offset_i; - data->items.offset_i = 0; - } - else { - /* second case, end of list */ - if (data->items.totitem - data->items.offset_i <= data->items.maxitem) { - data->active = data->items.offset_i - data->items.totitem + data->items.maxitem; - data->items.offset_i = data->items.totitem - data->items.maxitem; - } - else { - /* center active item */ - data->items.offset_i -= data->items.maxitem / 2; - data->active = data->items.maxitem / 2; - } - } - } - data->items.offset = data->items.offset_i; - data->items.totitem = 0; - } - } - - /* callback */ - if (search_but->items_update_fn) { - ui_searchbox_update_fn(C, search_but, but->editstr, &data->items); - } - - /* handle case where editstr is equal to one of items */ - if (reset && data->active == -1) { - int a; - - for (a = 0; a < data->items.totitem; a++) { - const char *name = data->items.names[a] + - /* Never include the prefix in the button. */ - (data->items.name_prefix_offsets ? data->items.name_prefix_offsets[a] : - 0); - const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL; - if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) { - data->active = a; - break; - } - } - if (data->items.totitem == 1 && but->editstr[0]) { - data->active = 0; - } - } - - /* validate selected item */ - ui_searchbox_select(C, region, but, 0); - - ED_region_tag_redraw(region); -} - -int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *str) -{ - uiButSearch *search_but = (uiButSearch *)but; - uiSearchboxData *data = region->regiondata; - int match = AUTOCOMPLETE_NO_MATCH; - - BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); - - if (str[0]) { - data->items.autocpl = UI_autocomplete_begin(str, ui_but_string_get_max_length(but)); - - ui_searchbox_update_fn(C, search_but, but->editstr, &data->items); - - match = UI_autocomplete_end(data->items.autocpl, str); - data->items.autocpl = NULL; - } - - return match; -} - -static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) -{ - uiSearchboxData *data = region->regiondata; - - /* pixel space */ - wmOrtho2_region_pixelspace(region); - - if (data->noback == false) { - ui_draw_widget_menu_back(&data->bbox, true); - } - - /* draw text */ - if (data->items.totitem) { - rcti rect; - int a; - - if (data->preview) { - /* draw items */ - for (a = 0; a < data->items.totitem; a++) { - const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; - - /* ensure icon is up-to-date */ - ui_icon_ensure_deferred(C, data->items.icons[a], data->preview); - - ui_searchbox_butrect(&rect, data, a); - - /* widget itself */ - ui_draw_preview_item( - &data->fstyle, &rect, data->items.names[a], data->items.icons[a], state); - } - - /* indicate more */ - if (data->items.more) { - ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN); - GPU_blend(GPU_BLEND_NONE); - } - if (data->items.offset) { - ui_searchbox_butrect(&rect, data, 0); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP); - GPU_blend(GPU_BLEND_NONE); - } - } - else { - const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0; - /* draw items */ - for (a = 0; a < data->items.totitem; a++) { - const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; - char *name = data->items.names[a]; - int icon = data->items.icons[a]; - char *name_sep_test = NULL; - - uiMenuItemSeparatorType separator_type = UI_MENU_ITEM_SEPARATOR_NONE; - if (data->use_sep) { - separator_type = UI_MENU_ITEM_SEPARATOR_SHORTCUT; - } - /* Only set for displaying additional hint (e.g. library name of a linked data-block). */ - else if (state & UI_BUT_HAS_SEP_CHAR) { - separator_type = UI_MENU_ITEM_SEPARATOR_HINT; - } - - ui_searchbox_butrect(&rect, data, a); - - /* widget itself */ - if ((search_sep_len == 0) || - !(name_sep_test = strstr(data->items.names[a], data->sep_string))) { - if (!icon && data->items.has_icon) { - /* If there is any icon item, make sure all items line up. */ - icon = ICON_BLANK1; - } - - /* Simple menu item. */ - ui_draw_menu_item(&data->fstyle, &rect, name, icon, state, separator_type, NULL); - } - else { - /* Split menu item, faded text before the separator. */ - char *name_sep = NULL; - do { - name_sep = name_sep_test; - name_sep_test = strstr(name_sep + search_sep_len, data->sep_string); - } while (name_sep_test != NULL); - - name_sep += search_sep_len; - const char name_sep_prev = *name_sep; - *name_sep = '\0'; - int name_width = 0; - ui_draw_menu_item(&data->fstyle, - &rect, - name, - 0, - state | UI_BUT_INACTIVE, - UI_MENU_ITEM_SEPARATOR_NONE, - &name_width); - *name_sep = name_sep_prev; - rect.xmin += name_width; - rect.xmin += UI_UNIT_X / 4; - - if (icon == ICON_BLANK1) { - icon = ICON_NONE; - rect.xmin -= UI_DPI_ICON_SIZE / 4; - } - - /* The previous menu item draws the active selection. */ - ui_draw_menu_item(&data->fstyle, &rect, name_sep, icon, state, separator_type, NULL); - } - } - /* indicate more */ - if (data->items.more) { - ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN); - GPU_blend(GPU_BLEND_NONE); - } - if (data->items.offset) { - ui_searchbox_butrect(&rect, data, 0); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP); - GPU_blend(GPU_BLEND_NONE); - } - } - } -} - -static void ui_searchbox_region_free_cb(ARegion *region) -{ - uiSearchboxData *data = region->regiondata; - int a; - - /* free search data */ - for (a = 0; a < data->items.maxitem; a++) { - MEM_freeN(data->items.names[a]); - } - MEM_freeN(data->items.names); - MEM_freeN(data->items.pointers); - MEM_freeN(data->items.icons); - MEM_freeN(data->items.states); - - if (data->items.name_prefix_offsets != NULL) { - MEM_freeN(data->items.name_prefix_offsets); - } - - MEM_freeN(data); - region->regiondata = NULL; -} - -ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiButSearch *search_but) -{ - wmWindow *win = CTX_wm_window(C); - const uiStyle *style = UI_style_get(); - uiBut *but = &search_but->but; - const float aspect = but->block->aspect; - const int margin = UI_POPUP_MARGIN; - - /* create area region */ - ARegion *region = ui_region_temp_add(CTX_wm_screen(C)); - - static ARegionType type; - memset(&type, 0, sizeof(ARegionType)); - type.draw = ui_searchbox_region_draw_cb; - type.free = ui_searchbox_region_free_cb; - type.regionid = RGN_TYPE_TEMPORARY; - region->type = &type; - - /* create searchbox data */ - uiSearchboxData *data = MEM_callocN(sizeof(uiSearchboxData), "uiSearchboxData"); - - /* set font, get bb */ - data->fstyle = style->widget; /* copy struct */ - ui_fontscale(&data->fstyle.points, aspect); - UI_fontstyle_set(&data->fstyle); - - region->regiondata = data; - - /* special case, hardcoded feature, not draw backdrop when called from menus, - * assume for design that popup already added it */ - if (but->block->flag & UI_BLOCK_SEARCH_MENU) { - data->noback = true; - } - - if (but->a1 > 0 && but->a2 > 0) { - data->preview = true; - data->prv_rows = but->a1; - data->prv_cols = but->a2; - } - - /* Only show key shortcuts when needed (checking RNA prop pointer is useless here, a lot of - * buttons are about data without having that pointer defined, let's rather try with optype!). - * One can also enforce that behavior by setting - * UI_BUT_HAS_SHORTCUT drawflag of search button. */ - if (but->optype != NULL || (but->drawflag & UI_BUT_HAS_SHORTCUT) != 0) { - data->use_sep = true; - } - data->sep_string = search_but->item_sep_string; - - /* compute position */ - if (but->block->flag & UI_BLOCK_SEARCH_MENU) { - const int search_but_h = BLI_rctf_size_y(&but->rect) + 10; - /* this case is search menu inside other menu */ - /* we copy region size */ - - region->winrct = butregion->winrct; - - /* widget rect, in region coords */ - data->bbox.xmin = margin; - data->bbox.xmax = BLI_rcti_size_x(®ion->winrct) - margin; - data->bbox.ymin = margin; - data->bbox.ymax = BLI_rcti_size_y(®ion->winrct) - margin; - - /* check if button is lower half */ - if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) { - data->bbox.ymin += search_but_h; - } - else { - data->bbox.ymax -= search_but_h; - } - } - else { - const int searchbox_width = UI_searchbox_size_x(); - - rctf rect_fl; - rect_fl.xmin = but->rect.xmin - 5; /* align text with button */ - rect_fl.xmax = but->rect.xmax + 5; /* symmetrical */ - rect_fl.ymax = but->rect.ymin; - rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y(); - - const int ofsx = (but->block->panel) ? but->block->panel->ofsx : 0; - const int ofsy = (but->block->panel) ? but->block->panel->ofsy : 0; - - BLI_rctf_translate(&rect_fl, ofsx, ofsy); - - /* minimal width */ - if (BLI_rctf_size_x(&rect_fl) < searchbox_width) { - rect_fl.xmax = rect_fl.xmin + searchbox_width; - } - - /* copy to int, gets projected if possible too */ - rcti rect_i; - BLI_rcti_rctf_copy(&rect_i, &rect_fl); - - if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) { - UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i); - } - - BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin); - - int winx = WM_window_pixels_x(win); - // winy = WM_window_pixels_y(win); /* UNUSED */ - // wm_window_get_size(win, &winx, &winy); - - if (rect_i.xmax > winx) { - /* super size */ - if (rect_i.xmax > winx + rect_i.xmin) { - rect_i.xmax = winx; - rect_i.xmin = 0; - } - else { - rect_i.xmin -= rect_i.xmax - winx; - rect_i.xmax = winx; - } - } - - if (rect_i.ymin < 0) { - int newy1 = but->rect.ymax + ofsy; - - if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) { - newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1); - } - - newy1 += butregion->winrct.ymin; - - rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1; - rect_i.ymin = newy1; - } - - /* widget rect, in region coords */ - data->bbox.xmin = margin; - data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin; - data->bbox.ymin = margin; - data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin; - - /* region bigger for shadow */ - region->winrct.xmin = rect_i.xmin - margin; - region->winrct.xmax = rect_i.xmax + margin; - region->winrct.ymin = rect_i.ymin - margin; - region->winrct.ymax = rect_i.ymax; - } - - /* adds subwindow */ - ED_region_floating_init(region); - - /* notify change and redraw */ - ED_region_tag_redraw(region); - - /* prepare search data */ - if (data->preview) { - data->items.maxitem = data->prv_rows * data->prv_cols; - } - else { - data->items.maxitem = SEARCH_ITEMS; - } - data->items.maxstrlen = but->hardmax; - data->items.totitem = 0; - data->items.names = MEM_callocN(data->items.maxitem * sizeof(void *), "search names"); - data->items.pointers = MEM_callocN(data->items.maxitem * sizeof(void *), "search pointers"); - data->items.icons = MEM_callocN(data->items.maxitem * sizeof(int), "search icons"); - data->items.states = MEM_callocN(data->items.maxitem * sizeof(int), "search flags"); - data->items.name_prefix_offsets = NULL; /* Lazy initialized as needed. */ - for (int i = 0; i < data->items.maxitem; i++) { - data->items.names[i] = MEM_callocN(but->hardmax + 1, "search pointers"); - } - - return region; -} - -/** - * Similar to Python's `str.title` except... - * - * - we know words are upper case and ascii only. - * - '_' are replaces by spaces. - */ -static void str_tolower_titlecaps_ascii(char *str, const size_t len) -{ - size_t i; - bool prev_delim = true; - - for (i = 0; (i < len) && str[i]; i++) { - if (str[i] >= 'A' && str[i] <= 'Z') { - if (prev_delim == false) { - str[i] += 'a' - 'A'; - } - } - else if (str[i] == '_') { - str[i] = ' '; - } - - prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9'); - } -} - -static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARegion *region) -{ - uiSearchboxData *data = region->regiondata; - - /* pixel space */ - wmOrtho2_region_pixelspace(region); - - if (data->noback == false) { - ui_draw_widget_menu_back(&data->bbox, true); - } - - /* draw text */ - if (data->items.totitem) { - rcti rect; - int a; - - /* draw items */ - for (a = 0; a < data->items.totitem; a++) { - rcti rect_pre, rect_post; - ui_searchbox_butrect(&rect, data, a); - - rect_pre = rect; - rect_post = rect; - - rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4); - - /* widget itself */ - /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */ - { - const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; - - wmOperatorType *ot = data->items.pointers[a]; - char text_pre[128]; - char *text_pre_p = strstr(ot->idname, "_OT_"); - if (text_pre_p == NULL) { - text_pre[0] = '\0'; - } - else { - int text_pre_len; - text_pre_p += 1; - text_pre_len = BLI_strncpy_rlen( - text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname)); - text_pre[text_pre_len] = ':'; - text_pre[text_pre_len + 1] = '\0'; - str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre)); - } - - rect_pre.xmax += 4; /* sneaky, avoid showing ugly margin */ - ui_draw_menu_item(&data->fstyle, - &rect_pre, - CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, text_pre), - data->items.icons[a], - state, - UI_MENU_ITEM_SEPARATOR_NONE, - NULL); - ui_draw_menu_item(&data->fstyle, - &rect_post, - data->items.names[a], - 0, - state, - data->use_sep ? UI_MENU_ITEM_SEPARATOR_SHORTCUT : - UI_MENU_ITEM_SEPARATOR_NONE, - NULL); - } - } - /* indicate more */ - if (data->items.more) { - ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN); - GPU_blend(GPU_BLEND_NONE); - } - if (data->items.offset) { - ui_searchbox_butrect(&rect, data, 0); - GPU_blend(GPU_BLEND_ALPHA); - UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP); - GPU_blend(GPU_BLEND_NONE); - } - } -} - -ARegion *ui_searchbox_create_operator(bContext *C, ARegion *butregion, uiButSearch *search_but) -{ - UI_but_drawflag_enable(&search_but->but, UI_BUT_HAS_SHORTCUT); - ARegion *region = ui_searchbox_create_generic(C, butregion, search_but); - - region->type->draw = ui_searchbox_region_draw_cb__operator; - - return region; -} - -void ui_searchbox_free(bContext *C, ARegion *region) -{ - ui_region_temp_remove(C, CTX_wm_screen(C), region); -} - -static void ui_searchbox_region_draw_cb__menu(const bContext *UNUSED(C), ARegion *UNUSED(region)) -{ - /* Currently unused. */ -} - -ARegion *ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *search_but) -{ - UI_but_drawflag_enable(&search_but->but, UI_BUT_HAS_SHORTCUT); - ARegion *region = ui_searchbox_create_generic(C, butregion, search_but); - - if (false) { - region->type->draw = ui_searchbox_region_draw_cb__menu; - } - - return region; -} - -/* sets red alert if button holds a string it can't find */ -/* XXX weak: search_func adds all partial matches... */ -void ui_but_search_refresh(uiButSearch *search_but) -{ - uiBut *but = &search_but->but; - uiSearchItems *items; - int x1; - - /* possibly very large lists (such as ID datablocks) only - * only validate string RNA buts (not pointers) */ - if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) { - return; - } - - items = MEM_callocN(sizeof(uiSearchItems), "search items"); - - /* setup search struct */ - items->maxitem = 10; - items->maxstrlen = 256; - items->names = MEM_callocN(items->maxitem * sizeof(void *), "search names"); - for (x1 = 0; x1 < items->maxitem; x1++) { - items->names[x1] = MEM_callocN(but->hardmax + 1, "search names"); - } - - ui_searchbox_update_fn(but->block->evil_C, search_but, but->drawstr, items); - - if (!search_but->results_are_suggestions) { - /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */ - if (items->totitem == 0) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - else if (items->more == 0) { - if (UI_search_items_find_index(items, but->drawstr) == -1) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - } - } - - for (x1 = 0; x1 < items->maxitem; x1++) { - MEM_freeN(items->names[x1]); - } - MEM_freeN(items->names); - MEM_freeN(items); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_region_search.cc b/source/blender/editors/interface/interface_region_search.cc new file mode 100644 index 00000000000..28343f81836 --- /dev/null +++ b/source/blender/editors/interface/interface_region_search.cc @@ -0,0 +1,1076 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + * + * Search Box Region & Interaction + */ + +#include +#include +#include + +#include "DNA_ID.h" +#include "MEM_guardedalloc.h" + +#include "DNA_userdef_types.h" + +#include "BLI_math.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_interface_icons.h" +#include "UI_view2d.h" + +#include "BLT_translation.h" + +#include "ED_screen.h" + +#include "GPU_state.h" +#include "interface_intern.h" +#include "interface_regions_intern.h" + +#define MENU_BORDER (int)(0.3f * U.widget_unit) + +/* -------------------------------------------------------------------- */ +/** \name Search Box Creation + * \{ */ + +struct uiSearchItems { + int maxitem, totitem, maxstrlen; + + int offset, offset_i; /* offset for inserting in array */ + int more; /* flag indicating there are more items */ + + char **names; + void **pointers; + int *icons; + int *states; + uint8_t *name_prefix_offsets; + + /** Is there any item with an icon? */ + bool has_icon; + + AutoComplete *autocpl; + void *active; +}; + +typedef struct uiSearchboxData { + rcti bbox; + uiFontStyle fstyle; + uiSearchItems items; + /** index in items array */ + int active; + /** when menu opened with enough space for this */ + bool noback; + /** draw thumbnail previews, rather than list */ + bool preview; + /** Use the #UI_SEP_CHAR char for splitting shortcuts (good for operators, bad for data). */ + bool use_sep; + int prv_rows, prv_cols; + /** + * Show the active icon and text after the last instance of this string. + * Used so we can show leading text to menu items less prominently (not related to 'use_sep'). + */ + const char *sep_string; +} uiSearchboxData; + +#define SEARCH_ITEMS 10 + +/** + * Public function exported for functions that use #UI_BTYPE_SEARCH_MENU. + * + * \param items: Stores the items. + * \param name: Text to display for the item. + * \param poin: Opaque pointer (for use by the caller). + * \param iconid: The icon, #ICON_NONE for no icon. + * \param state: The buttons state flag, compatible with #uiBut.flag, + * typically #UI_BUT_DISABLED / #UI_BUT_INACTIVE. + * \return false if there is nothing to add. + */ +bool UI_search_item_add(uiSearchItems *items, + const char *name, + void *poin, + int iconid, + int state, + const uint8_t name_prefix_offset) +{ + /* hijack for autocomplete */ + if (items->autocpl) { + UI_autocomplete_update_name(items->autocpl, name); + return true; + } + + if (iconid) { + items->has_icon = true; + } + + /* hijack for finding active item */ + if (items->active) { + if (poin == items->active) { + items->offset_i = items->totitem; + } + items->totitem++; + return true; + } + + if (items->totitem >= items->maxitem) { + items->more = 1; + return false; + } + + /* skip first items in list */ + if (items->offset_i > 0) { + items->offset_i--; + return true; + } + + if (items->names) { + BLI_strncpy(items->names[items->totitem], name, items->maxstrlen); + } + if (items->pointers) { + items->pointers[items->totitem] = poin; + } + if (items->icons) { + items->icons[items->totitem] = iconid; + } + + if (name_prefix_offset != 0) { + /* Lazy initialize, as this isn't used often. */ + if (items->name_prefix_offsets == NULL) { + items->name_prefix_offsets = (uint8_t *)MEM_callocN( + items->maxitem * sizeof(*items->name_prefix_offsets), "search name prefix offsets"); + } + items->name_prefix_offsets[items->totitem] = name_prefix_offset; + } + + /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set + * which will cause problems, add others as needed. */ + BLI_assert( + (state & ~(UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)) == 0); + if (items->states) { + items->states[items->totitem] = state; + } + + items->totitem++; + + return true; +} + +int UI_searchbox_size_y(void) +{ + return SEARCH_ITEMS * UI_UNIT_Y + 2 * UI_POPUP_MENU_TOP; +} + +int UI_searchbox_size_x(void) +{ + return 12 * UI_UNIT_X; +} + +int UI_search_items_find_index(uiSearchItems *items, const char *name) +{ + if (items->name_prefix_offsets != NULL) { + for (int i = 0; i < items->totitem; i++) { + if (STREQ(name, items->names[i] + items->name_prefix_offsets[i])) { + return i; + } + } + } + else { + for (int i = 0; i < items->totitem; i++) { + if (STREQ(name, items->names[i])) { + return i; + } + } + } + return -1; +} + +/* region is the search box itself */ +static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + + /* apply step */ + data->active += step; + + if (data->items.totitem == 0) { + data->active = -1; + } + else if (data->active >= data->items.totitem) { + if (data->items.more) { + data->items.offset++; + data->active = data->items.totitem - 1; + ui_searchbox_update(C, region, but, false); + } + else { + data->active = data->items.totitem - 1; + } + } + else if (data->active < 0) { + if (data->items.offset) { + data->items.offset--; + data->active = 0; + ui_searchbox_update(C, region, but, false); + } + else { + /* only let users step into an 'unset' state for unlink buttons */ + data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0; + } + } + + ED_region_tag_redraw(region); +} + +static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr) +{ + /* thumbnail preview */ + if (data->preview) { + const int butw = (BLI_rcti_size_x(&data->bbox) - 2 * MENU_BORDER) / data->prv_cols; + const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_BORDER) / data->prv_rows; + int row, col; + + *r_rect = data->bbox; + + col = itemnr % data->prv_cols; + row = itemnr / data->prv_cols; + + r_rect->xmin += MENU_BORDER + (col * butw); + r_rect->xmax = r_rect->xmin + butw; + + r_rect->ymax -= MENU_BORDER + (row * buth); + r_rect->ymin = r_rect->ymax - buth; + } + /* list view */ + else { + const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * UI_POPUP_MENU_TOP) / SEARCH_ITEMS; + + *r_rect = data->bbox; + r_rect->xmin = data->bbox.xmin + 3.0f; + r_rect->xmax = data->bbox.xmax - 3.0f; + + r_rect->ymax = data->bbox.ymax - UI_POPUP_MENU_TOP - itemnr * buth; + r_rect->ymin = r_rect->ymax - buth; + } +} + +int ui_searchbox_find_index(ARegion *region, const char *name) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + return UI_search_items_find_index(&data->items, name); +} + +/* x and y in screen-coords. */ +bool ui_searchbox_inside(ARegion *region, int x, int y) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + + return BLI_rcti_isect_pt(&data->bbox, x - region->winrct.xmin, y - region->winrct.ymin); +} + +/* string validated to be of correct length (but->hardmax) */ +bool ui_searchbox_apply(uiBut *but, ARegion *region) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + uiButSearch *search_but = (uiButSearch *)but; + + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + search_but->item_active = NULL; + + if (data->active != -1) { + const char *name = data->items.names[data->active] + + /* Never include the prefix in the button. */ + (data->items.name_prefix_offsets ? + data->items.name_prefix_offsets[data->active] : + 0); + + const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL; + + BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen); + + search_but->item_active = data->items.pointers[data->active]; + + return true; + } + if (but->flag & UI_BUT_VALUE_CLEAR) { + /* It is valid for _VALUE_CLEAR flavor to have no active element + * (it's a valid way to unlink). */ + but->editstr[0] = '\0'; + + return true; + } + return false; +} + +static struct ARegion *wm_searchbox_tooltip_init(struct bContext *C, + struct ARegion *region, + int *UNUSED(r_pass), + double *UNUSED(pass_delay), + bool *r_exit_on_event) +{ + *r_exit_on_event = true; + + LISTBASE_FOREACH (uiBlock *, block, ®ion->uiblocks) { + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + if (but->type != UI_BTYPE_SEARCH_MENU) { + continue; + } + + uiButSearch *search_but = (uiButSearch *)but; + if (!search_but->item_tooltip_fn) { + continue; + } + + ARegion *searchbox_region = UI_region_searchbox_region_get(region); + uiSearchboxData *data = (uiSearchboxData *)searchbox_region->regiondata; + + BLI_assert(data->items.pointers[data->active] == search_but->item_active); + + rcti rect; + ui_searchbox_butrect(&rect, data, data->active); + + return search_but->item_tooltip_fn( + C, region, &rect, search_but->arg, search_but->item_active); + } + } + return NULL; +} + +bool ui_searchbox_event( + bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + uiButSearch *search_but = (uiButSearch *)but; + int type = event->type, val = event->val; + bool handled = false; + bool tooltip_timer_started = false; + + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + if (type == MOUSEPAN) { + ui_pan_to_scroll(event, &type, &val); + } + + switch (type) { + case WHEELUPMOUSE: + case EVT_UPARROWKEY: + ui_searchbox_select(C, region, but, -1); + handled = true; + break; + case WHEELDOWNMOUSE: + case EVT_DOWNARROWKEY: + ui_searchbox_select(C, region, but, 1); + handled = true; + break; + case RIGHTMOUSE: + if (val) { + if (search_but->item_context_menu_fn) { + if (data->active != -1) { + /* Check the cursor is over the active element + * (a little confusing if this isn't the case, although it does work). */ + rcti rect; + ui_searchbox_butrect(&rect, data, data->active); + if (BLI_rcti_isect_pt( + &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { + + void *active = data->items.pointers[data->active]; + if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) { + handled = true; + } + } + } + } + } + break; + case MOUSEMOVE: { + bool is_inside = false; + + if (BLI_rcti_isect_pt(®ion->winrct, event->x, event->y)) { + rcti rect; + int a; + + for (a = 0; a < data->items.totitem; a++) { + ui_searchbox_butrect(&rect, data, a); + if (BLI_rcti_isect_pt( + &rect, event->x - region->winrct.xmin, event->y - region->winrct.ymin)) { + is_inside = true; + if (data->active != a) { + data->active = a; + ui_searchbox_select(C, region, but, 0); + handled = true; + break; + } + } + } + } + + if (U.flag & USER_TOOLTIPS) { + if (is_inside) { + if (data->active != -1) { + ScrArea *area = CTX_wm_area(C); + search_but->item_active = data->items.pointers[data->active]; + WM_tooltip_timer_init(C, CTX_wm_window(C), area, butregion, wm_searchbox_tooltip_init); + tooltip_timer_started = true; + } + } + } + + break; + } + } + + if (handled && (tooltip_timer_started == false)) { + wmWindow *win = CTX_wm_window(C); + WM_tooltip_clear(C, win); + } + + return handled; +} + +/** Wrap #uiButSearchUpdateFn callback. */ +static void ui_searchbox_update_fn(bContext *C, + uiButSearch *search_but, + const char *str, + uiSearchItems *items) +{ + /* While the button is in text editing mode (searchbox open), remove tooltips on every update. */ + if (search_but->but.editstr) { + wmWindow *win = CTX_wm_window(C); + WM_tooltip_clear(C, win); + } + const bool is_first_search = !search_but->but.changed; + search_but->items_update_fn(C, search_but->arg, str, items, is_first_search); +} + +/* region is the search box itself */ +void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset) +{ + uiButSearch *search_but = (uiButSearch *)but; + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + /* reset vars */ + data->items.totitem = 0; + data->items.more = 0; + if (reset == false) { + data->items.offset_i = data->items.offset; + } + else { + data->items.offset_i = data->items.offset = 0; + data->active = -1; + + /* handle active */ + if (search_but->items_update_fn && search_but->item_active) { + data->items.active = search_but->item_active; + ui_searchbox_update_fn(C, search_but, but->editstr, &data->items); + data->items.active = NULL; + + /* found active item, calculate real offset by centering it */ + if (data->items.totitem) { + /* first case, begin of list */ + if (data->items.offset_i < data->items.maxitem) { + data->active = data->items.offset_i; + data->items.offset_i = 0; + } + else { + /* second case, end of list */ + if (data->items.totitem - data->items.offset_i <= data->items.maxitem) { + data->active = data->items.offset_i - data->items.totitem + data->items.maxitem; + data->items.offset_i = data->items.totitem - data->items.maxitem; + } + else { + /* center active item */ + data->items.offset_i -= data->items.maxitem / 2; + data->active = data->items.maxitem / 2; + } + } + } + data->items.offset = data->items.offset_i; + data->items.totitem = 0; + } + } + + /* callback */ + if (search_but->items_update_fn) { + ui_searchbox_update_fn(C, search_but, but->editstr, &data->items); + } + + /* handle case where editstr is equal to one of items */ + if (reset && data->active == -1) { + int a; + + for (a = 0; a < data->items.totitem; a++) { + const char *name = data->items.names[a] + + /* Never include the prefix in the button. */ + (data->items.name_prefix_offsets ? data->items.name_prefix_offsets[a] : + 0); + const char *name_sep = data->use_sep ? strrchr(name, UI_SEP_CHAR) : NULL; + if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) { + data->active = a; + break; + } + } + if (data->items.totitem == 1 && but->editstr[0]) { + data->active = 0; + } + } + + /* validate selected item */ + ui_searchbox_select(C, region, but, 0); + + ED_region_tag_redraw(region); +} + +int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *str) +{ + uiButSearch *search_but = (uiButSearch *)but; + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + int match = AUTOCOMPLETE_NO_MATCH; + + BLI_assert(but->type == UI_BTYPE_SEARCH_MENU); + + if (str[0]) { + data->items.autocpl = UI_autocomplete_begin(str, ui_but_string_get_max_length(but)); + + ui_searchbox_update_fn(C, search_but, but->editstr, &data->items); + + match = UI_autocomplete_end(data->items.autocpl, str); + data->items.autocpl = NULL; + } + + return match; +} + +static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + + /* pixel space */ + wmOrtho2_region_pixelspace(region); + + if (data->noback == false) { + ui_draw_widget_menu_back(&data->bbox, true); + } + + /* draw text */ + if (data->items.totitem) { + rcti rect; + int a; + + if (data->preview) { + /* draw items */ + for (a = 0; a < data->items.totitem; a++) { + const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; + + /* ensure icon is up-to-date */ + ui_icon_ensure_deferred(C, data->items.icons[a], data->preview); + + ui_searchbox_butrect(&rect, data, a); + + /* widget itself */ + ui_draw_preview_item( + &data->fstyle, &rect, data->items.names[a], data->items.icons[a], state); + } + + /* indicate more */ + if (data->items.more) { + ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN); + GPU_blend(GPU_BLEND_NONE); + } + if (data->items.offset) { + ui_searchbox_butrect(&rect, data, 0); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP); + GPU_blend(GPU_BLEND_NONE); + } + } + else { + const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0; + /* draw items */ + for (a = 0; a < data->items.totitem; a++) { + const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; + char *name = data->items.names[a]; + int icon = data->items.icons[a]; + char *name_sep_test = NULL; + + uiMenuItemSeparatorType separator_type = UI_MENU_ITEM_SEPARATOR_NONE; + if (data->use_sep) { + separator_type = UI_MENU_ITEM_SEPARATOR_SHORTCUT; + } + /* Only set for displaying additional hint (e.g. library name of a linked data-block). */ + else if (state & UI_BUT_HAS_SEP_CHAR) { + separator_type = UI_MENU_ITEM_SEPARATOR_HINT; + } + + ui_searchbox_butrect(&rect, data, a); + + /* widget itself */ + if ((search_sep_len == 0) || + !(name_sep_test = strstr(data->items.names[a], data->sep_string))) { + if (!icon && data->items.has_icon) { + /* If there is any icon item, make sure all items line up. */ + icon = ICON_BLANK1; + } + + /* Simple menu item. */ + ui_draw_menu_item(&data->fstyle, &rect, name, icon, state, separator_type, NULL); + } + else { + /* Split menu item, faded text before the separator. */ + char *name_sep = NULL; + do { + name_sep = name_sep_test; + name_sep_test = strstr(name_sep + search_sep_len, data->sep_string); + } while (name_sep_test != NULL); + + name_sep += search_sep_len; + const char name_sep_prev = *name_sep; + *name_sep = '\0'; + int name_width = 0; + ui_draw_menu_item(&data->fstyle, + &rect, + name, + 0, + state | UI_BUT_INACTIVE, + UI_MENU_ITEM_SEPARATOR_NONE, + &name_width); + *name_sep = name_sep_prev; + rect.xmin += name_width; + rect.xmin += UI_UNIT_X / 4; + + if (icon == ICON_BLANK1) { + icon = ICON_NONE; + rect.xmin -= UI_DPI_ICON_SIZE / 4; + } + + /* The previous menu item draws the active selection. */ + ui_draw_menu_item(&data->fstyle, &rect, name_sep, icon, state, separator_type, NULL); + } + } + /* indicate more */ + if (data->items.more) { + ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN); + GPU_blend(GPU_BLEND_NONE); + } + if (data->items.offset) { + ui_searchbox_butrect(&rect, data, 0); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP); + GPU_blend(GPU_BLEND_NONE); + } + } + } +} + +static void ui_searchbox_region_free_cb(ARegion *region) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + int a; + + /* free search data */ + for (a = 0; a < data->items.maxitem; a++) { + MEM_freeN(data->items.names[a]); + } + MEM_freeN(data->items.names); + MEM_freeN(data->items.pointers); + MEM_freeN(data->items.icons); + MEM_freeN(data->items.states); + + if (data->items.name_prefix_offsets != NULL) { + MEM_freeN(data->items.name_prefix_offsets); + } + + MEM_freeN(data); + region->regiondata = NULL; +} + +ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiButSearch *search_but) +{ + wmWindow *win = CTX_wm_window(C); + const uiStyle *style = UI_style_get(); + uiBut *but = &search_but->but; + const float aspect = but->block->aspect; + const int margin = UI_POPUP_MARGIN; + + /* create area region */ + ARegion *region = ui_region_temp_add(CTX_wm_screen(C)); + + static ARegionType type; + memset(&type, 0, sizeof(ARegionType)); + type.draw = ui_searchbox_region_draw_cb; + type.free = ui_searchbox_region_free_cb; + type.regionid = RGN_TYPE_TEMPORARY; + region->type = &type; + + /* create searchbox data */ + uiSearchboxData *data = (uiSearchboxData *)MEM_callocN(sizeof(uiSearchboxData), + "uiSearchboxData"); + + /* set font, get bb */ + data->fstyle = style->widget; /* copy struct */ + ui_fontscale(&data->fstyle.points, aspect); + UI_fontstyle_set(&data->fstyle); + + region->regiondata = data; + + /* special case, hardcoded feature, not draw backdrop when called from menus, + * assume for design that popup already added it */ + if (but->block->flag & UI_BLOCK_SEARCH_MENU) { + data->noback = true; + } + + if (but->a1 > 0 && but->a2 > 0) { + data->preview = true; + data->prv_rows = but->a1; + data->prv_cols = but->a2; + } + + /* Only show key shortcuts when needed (checking RNA prop pointer is useless here, a lot of + * buttons are about data without having that pointer defined, let's rather try with optype!). + * One can also enforce that behavior by setting + * UI_BUT_HAS_SHORTCUT drawflag of search button. */ + if (but->optype != NULL || (but->drawflag & UI_BUT_HAS_SHORTCUT) != 0) { + data->use_sep = true; + } + data->sep_string = search_but->item_sep_string; + + /* compute position */ + if (but->block->flag & UI_BLOCK_SEARCH_MENU) { + const int search_but_h = BLI_rctf_size_y(&but->rect) + 10; + /* this case is search menu inside other menu */ + /* we copy region size */ + + region->winrct = butregion->winrct; + + /* widget rect, in region coords */ + data->bbox.xmin = margin; + data->bbox.xmax = BLI_rcti_size_x(®ion->winrct) - margin; + data->bbox.ymin = margin; + data->bbox.ymax = BLI_rcti_size_y(®ion->winrct) - margin; + + /* check if button is lower half */ + if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) { + data->bbox.ymin += search_but_h; + } + else { + data->bbox.ymax -= search_but_h; + } + } + else { + const int searchbox_width = UI_searchbox_size_x(); + + rctf rect_fl; + rect_fl.xmin = but->rect.xmin - 5; /* align text with button */ + rect_fl.xmax = but->rect.xmax + 5; /* symmetrical */ + rect_fl.ymax = but->rect.ymin; + rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y(); + + const int ofsx = (but->block->panel) ? but->block->panel->ofsx : 0; + const int ofsy = (but->block->panel) ? but->block->panel->ofsy : 0; + + BLI_rctf_translate(&rect_fl, ofsx, ofsy); + + /* minimal width */ + if (BLI_rctf_size_x(&rect_fl) < searchbox_width) { + rect_fl.xmax = rect_fl.xmin + searchbox_width; + } + + /* copy to int, gets projected if possible too */ + rcti rect_i; + BLI_rcti_rctf_copy(&rect_i, &rect_fl); + + if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) { + UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i); + } + + BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin); + + int winx = WM_window_pixels_x(win); + // winy = WM_window_pixels_y(win); /* UNUSED */ + // wm_window_get_size(win, &winx, &winy); + + if (rect_i.xmax > winx) { + /* super size */ + if (rect_i.xmax > winx + rect_i.xmin) { + rect_i.xmax = winx; + rect_i.xmin = 0; + } + else { + rect_i.xmin -= rect_i.xmax - winx; + rect_i.xmax = winx; + } + } + + if (rect_i.ymin < 0) { + int newy1 = but->rect.ymax + ofsy; + + if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) { + newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1); + } + + newy1 += butregion->winrct.ymin; + + rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1; + rect_i.ymin = newy1; + } + + /* widget rect, in region coords */ + data->bbox.xmin = margin; + data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin; + data->bbox.ymin = margin; + data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin; + + /* region bigger for shadow */ + region->winrct.xmin = rect_i.xmin - margin; + region->winrct.xmax = rect_i.xmax + margin; + region->winrct.ymin = rect_i.ymin - margin; + region->winrct.ymax = rect_i.ymax; + } + + /* adds subwindow */ + ED_region_floating_init(region); + + /* notify change and redraw */ + ED_region_tag_redraw(region); + + /* prepare search data */ + if (data->preview) { + data->items.maxitem = data->prv_rows * data->prv_cols; + } + else { + data->items.maxitem = SEARCH_ITEMS; + } + data->items.maxstrlen = but->hardmax; + data->items.totitem = 0; + data->items.names = (char **)MEM_callocN(data->items.maxitem * sizeof(void *), "search names"); + data->items.pointers = (void **)MEM_callocN(data->items.maxitem * sizeof(void *), + "search pointers"); + data->items.icons = (int *)MEM_callocN(data->items.maxitem * sizeof(int), "search icons"); + data->items.states = (int *)MEM_callocN(data->items.maxitem * sizeof(int), "search flags"); + data->items.name_prefix_offsets = NULL; /* Lazy initialized as needed. */ + for (int i = 0; i < data->items.maxitem; i++) { + data->items.names[i] = (char *)MEM_callocN(but->hardmax + 1, "search pointers"); + } + + return region; +} + +/** + * Similar to Python's `str.title` except... + * + * - we know words are upper case and ascii only. + * - '_' are replaces by spaces. + */ +static void str_tolower_titlecaps_ascii(char *str, const size_t len) +{ + size_t i; + bool prev_delim = true; + + for (i = 0; (i < len) && str[i]; i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + if (prev_delim == false) { + str[i] += 'a' - 'A'; + } + } + else if (str[i] == '_') { + str[i] = ' '; + } + + prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9'); + } +} + +static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARegion *region) +{ + uiSearchboxData *data = (uiSearchboxData *)region->regiondata; + + /* pixel space */ + wmOrtho2_region_pixelspace(region); + + if (data->noback == false) { + ui_draw_widget_menu_back(&data->bbox, true); + } + + /* draw text */ + if (data->items.totitem) { + rcti rect; + int a; + + /* draw items */ + for (a = 0; a < data->items.totitem; a++) { + rcti rect_pre, rect_post; + ui_searchbox_butrect(&rect, data, a); + + rect_pre = rect; + rect_post = rect; + + rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4); + + /* widget itself */ + /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */ + { + const int state = ((a == data->active) ? UI_ACTIVE : 0) | data->items.states[a]; + + wmOperatorType *ot = (wmOperatorType *)data->items.pointers[a]; + char text_pre[128]; + char *text_pre_p = const_cast(strstr(ot->idname, "_OT_")); + if (text_pre_p == NULL) { + text_pre[0] = '\0'; + } + else { + int text_pre_len; + text_pre_p += 1; + text_pre_len = BLI_strncpy_rlen( + text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname)); + text_pre[text_pre_len] = ':'; + text_pre[text_pre_len + 1] = '\0'; + str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre)); + } + + rect_pre.xmax += 4; /* sneaky, avoid showing ugly margin */ + ui_draw_menu_item(&data->fstyle, + &rect_pre, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, text_pre), + data->items.icons[a], + state, + UI_MENU_ITEM_SEPARATOR_NONE, + NULL); + ui_draw_menu_item(&data->fstyle, + &rect_post, + data->items.names[a], + 0, + state, + data->use_sep ? UI_MENU_ITEM_SEPARATOR_SHORTCUT : + UI_MENU_ITEM_SEPARATOR_NONE, + NULL); + } + } + /* indicate more */ + if (data->items.more) { + ui_searchbox_butrect(&rect, data, data->items.maxitem - 1); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymin - 9, ICON_TRIA_DOWN); + GPU_blend(GPU_BLEND_NONE); + } + if (data->items.offset) { + ui_searchbox_butrect(&rect, data, 0); + GPU_blend(GPU_BLEND_ALPHA); + UI_icon_draw((BLI_rcti_size_x(&rect)) / 2, rect.ymax - 7, ICON_TRIA_UP); + GPU_blend(GPU_BLEND_NONE); + } + } +} + +ARegion *ui_searchbox_create_operator(bContext *C, ARegion *butregion, uiButSearch *search_but) +{ + UI_but_drawflag_enable(&search_but->but, UI_BUT_HAS_SHORTCUT); + ARegion *region = ui_searchbox_create_generic(C, butregion, search_but); + + region->type->draw = ui_searchbox_region_draw_cb__operator; + + return region; +} + +void ui_searchbox_free(bContext *C, ARegion *region) +{ + ui_region_temp_remove(C, CTX_wm_screen(C), region); +} + +static void ui_searchbox_region_draw_cb__menu(const bContext *UNUSED(C), ARegion *UNUSED(region)) +{ + /* Currently unused. */ +} + +ARegion *ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *search_but) +{ + UI_but_drawflag_enable(&search_but->but, UI_BUT_HAS_SHORTCUT); + ARegion *region = ui_searchbox_create_generic(C, butregion, search_but); + + if (false) { + region->type->draw = ui_searchbox_region_draw_cb__menu; + } + + return region; +} + +/* sets red alert if button holds a string it can't find */ +/* XXX weak: search_func adds all partial matches... */ +void ui_but_search_refresh(uiButSearch *search_but) +{ + uiBut *but = &search_but->but; + uiSearchItems *items; + int x1; + + /* possibly very large lists (such as ID datablocks) only + * only validate string RNA buts (not pointers) */ + if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) { + return; + } + + items = (uiSearchItems *)MEM_callocN(sizeof(uiSearchItems), "search items"); + + /* setup search struct */ + items->maxitem = 10; + items->maxstrlen = 256; + items->names = (char **)MEM_callocN(items->maxitem * sizeof(void *), "search names"); + for (x1 = 0; x1 < items->maxitem; x1++) { + items->names[x1] = (char *)MEM_callocN(but->hardmax + 1, "search names"); + } + + ui_searchbox_update_fn((bContext *)but->block->evil_C, search_but, but->drawstr, items); + + if (!search_but->results_are_suggestions) { + /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */ + if (items->totitem == 0) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + else if (items->more == 0) { + if (UI_search_items_find_index(items, but->drawstr) == -1) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + } + } + + for (x1 = 0; x1 < items->maxitem; x1++) { + MEM_freeN(items->names[x1]); + } + MEM_freeN(items->names); + MEM_freeN(items); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_regions.c b/source/blender/editors/interface/interface_regions.c deleted file mode 100644 index 51b4e82d679..00000000000 --- a/source/blender/editors/interface/interface_regions.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 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..9934421c7d2 --- /dev/null +++ b/source/blender/editors/interface/interface_regions.cc @@ -0,0 +1,66 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 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 = (ARegion *)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_style.c b/source/blender/editors/interface/interface_style.c deleted file mode 100644 index ad0c523a594..00000000000 --- a/source/blender/editors/interface/interface_style.c +++ /dev/null @@ -1,577 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 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" -#ifdef WITH_INTERNATIONAL -# include "BLT_translation.h" -#endif - -#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.kerning = 1; - 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.kerning = 1; - 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.kerning = 1; - 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.kerning = 1; - 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 uchar col[4], - const struct uiFontStyleDraw_Params *fs_params, - size_t len, - 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->kerning == 1) { - font_flag |= BLF_KERNING_DEFAULT; - } - 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 boundbox top */ - yofs = BLI_rcti_size_y(rect) - BLF_height_max(fs->uifont_id); - } - else { - /* draw from boundbox 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, len))); - } - else if (fs_params->align == UI_STYLE_TEXT_RIGHT) { - xofs = BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, 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, 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 uchar col[4], - const struct uiFontStyleDraw_Params *fs_params) -{ - int xofs, yofs; - - UI_fontstyle_draw_ex(fs, rect, str, col, fs_params, BLF_DRAW_STR_DUMMY_MAX, &xofs, &yofs, NULL); -} - -/* drawn same as above, but at 90 degree angle */ -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); - } - - if (fs->kerning == 1) { - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - - 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); - } - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } -} - -/** - * Similar to #UI_fontstyle_draw - * but ignore alignment, shadow & no clipping rect. - * - * For drawing on-screen labels. - */ -void UI_fontstyle_draw_simple( - const uiFontStyle *fs, float x, float y, const char *str, const uchar col[4]) -{ - if (fs->kerning == 1) { - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - - 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); - - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } -} - -/** - * Same as #UI_fontstyle_draw but draw a colored backdrop. - */ -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]) -{ - if (fs->kerning == 1) { - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - - 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; - - /* backdrop */ - const float color[4] = {col_bg[0], col_bg[1], col_bg[2], 0.5f}; - - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_aa( - &(const rctf){ - .xmin = x - margin, - .xmax = x + width + margin, - .ymin = (y + decent) - margin, - .ymax = (y + decent) + height + margin, - }, - true, - margin, - color); - } - - 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); - - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } -} - -/* ************** helpers ************************ */ -/* XXX: read a style configure */ -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 -} - -/* for drawing, scaled with DPI setting */ -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) -{ - int width; - - if (fs->kerning == 1) { - /* for BLF_width */ - BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - - UI_fontstyle_set(fs); - width = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); - - if (fs->kerning == 1) { - BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); - } - - return width; -} - -int UI_fontstyle_height_max(const uiFontStyle *fs) -{ - UI_fontstyle_set(fs); - return BLF_height_max(fs->uifont_id); -} - -/* ************** init exit ************************ */ - -/* called on each startup.blend read */ -/* reading without uifont will create one */ -void uiStyleInit(void) -{ - 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->filename, U.font_path_ui, sizeof(font_first->filename)); - font_first->uifont_id = UIFONT_CUSTOM1; - } - else { - BLI_strncpy(font_first->filename, "default", sizeof(font_first->filename)); - 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->filename); - 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__); - } - } - else { - /* ? just for speed to initialize? - * Yes, this build the glyph cache and create - * the texture. - */ - BLF_size(font->blf_id, 11 * U.pixelsize, U.dpi); - BLF_size(font->blf_id, 12 * U.pixelsize, U.dpi); - BLF_size(font->blf_id, 14 * U.pixelsize, U.dpi); - } - } - - if (style == NULL) { - ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); - } - - /* 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); - } - - BLF_size(blf_mono_font, 12 * U.pixelsize, 72); - - /* 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); - } - - BLF_size(blf_mono_font_render, 12 * U.pixelsize, 72); -} - -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..b3aca8f9ea2 --- /dev/null +++ b/source/blender/editors/interface/interface_style.cc @@ -0,0 +1,577 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 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" +#ifdef WITH_INTERNATIONAL +# include "BLT_translation.h" +#endif + +#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 = (uiStyle *)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.kerning = 1; + 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.kerning = 1; + 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.kerning = 1; + 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.kerning = 1; + 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 = (uiFont *)U.uifonts.first; + + for (; font; font = font->next) { + if (font->uifont_id == id) { + return font; + } + } + return (uiFont *)U.uifonts.first; +} + +/* *************** draw ************************ */ + +void UI_fontstyle_draw_ex(const uiFontStyle *fs, + const rcti *rect, + const char *str, + const uchar col[4], + const struct uiFontStyleDraw_Params *fs_params, + size_t len, + 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->kerning == 1) { + font_flag |= BLF_KERNING_DEFAULT; + } + 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 boundbox top */ + yofs = BLI_rcti_size_y(rect) - BLF_height_max(fs->uifont_id); + } + else { + /* draw from boundbox 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, len))); + } + else if (fs_params->align == UI_STYLE_TEXT_RIGHT) { + xofs = BLI_rcti_size_x(rect) - BLF_width(fs->uifont_id, 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, 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 uchar col[4], + const struct uiFontStyleDraw_Params *fs_params) +{ + int xofs, yofs; + + UI_fontstyle_draw_ex(fs, rect, str, col, fs_params, BLF_DRAW_STR_DUMMY_MAX, &xofs, &yofs, NULL); +} + +/* drawn same as above, but at 90 degree angle */ +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); + } + + if (fs->kerning == 1) { + BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); + } + + 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); + } + if (fs->kerning == 1) { + BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); + } +} + +/** + * Similar to #UI_fontstyle_draw + * but ignore alignment, shadow & no clipping rect. + * + * For drawing on-screen labels. + */ +void UI_fontstyle_draw_simple( + const uiFontStyle *fs, float x, float y, const char *str, const uchar col[4]) +{ + if (fs->kerning == 1) { + BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); + } + + 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); + + if (fs->kerning == 1) { + BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); + } +} + +/** + * Same as #UI_fontstyle_draw but draw a colored backdrop. + */ +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]) +{ + if (fs->kerning == 1) { + BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); + } + + 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; + + /* backdrop */ + const float color[4] = {col_bg[0], col_bg[1], col_bg[2], 0.5f}; + + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_aa( + &(const rctf){ + .xmin = x - margin, + .xmax = x + width + margin, + .ymin = (y + decent) - margin, + .ymax = (y + decent) + height + margin, + }, + true, + margin, + color); + } + + 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); + + if (fs->kerning == 1) { + BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); + } +} + +/* ************** helpers ************************ */ +/* XXX: read a style configure */ +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 (uiStyle *)U.uistyles.first; +#endif +} + +/* for drawing, scaled with DPI setting */ +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) +{ + int width; + + if (fs->kerning == 1) { + /* for BLF_width */ + BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT); + } + + UI_fontstyle_set(fs); + width = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); + + if (fs->kerning == 1) { + BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT); + } + + return width; +} + +int UI_fontstyle_height_max(const uiFontStyle *fs) +{ + UI_fontstyle_set(fs); + return BLF_height_max(fs->uifont_id); +} + +/* ************** init exit ************************ */ + +/* called on each startup.blend read */ +/* reading without uifont will create one */ +void uiStyleInit() +{ + uiStyle *style = (uiStyle *)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 = (uiFont *)U.uifonts.first; + + /* default builtin */ + if (font_first == NULL) { + font_first = (uiFont *)MEM_callocN(sizeof(uiFont), "ui font"); + BLI_addtail(&U.uifonts, font_first); + } + + if (U.font_path_ui[0]) { + BLI_strncpy(font_first->filename, U.font_path_ui, sizeof(font_first->filename)); + font_first->uifont_id = UIFONT_CUSTOM1; + } + else { + BLI_strncpy(font_first->filename, "default", sizeof(font_first->filename)); + 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->filename); + 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__); + } + } + else { + /* ? just for speed to initialize? + * Yes, this build the glyph cache and create + * the texture. + */ + BLF_size(font->blf_id, 11 * U.pixelsize, U.dpi); + BLF_size(font->blf_id, 12 * U.pixelsize, U.dpi); + BLF_size(font->blf_id, 14 * U.pixelsize, U.dpi); + } + } + + if (style == NULL) { + ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT); + } + + /* 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); + } + + BLF_size(blf_mono_font, 12 * U.pixelsize, 72); + + /* 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); + } + + BLF_size(blf_mono_font_render, 12 * U.pixelsize, 72); +} + +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_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c deleted file mode 100644 index 91ad6619889..00000000000 --- a/source/blender/editors/interface/interface_template_search_menu.c +++ /dev/null @@ -1,1177 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - * - * Search available menu items via the user interface & key-maps. - * Accessed via the #WM_OT_search_menu operator. - */ - -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_action_types.h" -#include "DNA_gpencil_modifier_types.h" -#include "DNA_node_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_shader_fx_types.h" -#include "DNA_texture_types.h" - -#include "BLI_alloca.h" -#include "BLI_dynstr.h" -#include "BLI_ghash.h" -#include "BLI_linklist.h" -#include "BLI_listbase.h" -#include "BLI_math_matrix.h" -#include "BLI_memarena.h" -#include "BLI_string.h" -#include "BLI_string_search.h" -#include "BLI_string_utils.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "BKE_context.h" -#include "BKE_global.h" -#include "BKE_screen.h" - -#include "ED_screen.h" - -#include "RNA_access.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_interface.h" -#include "interface_intern.h" - -/* For key-map item access. */ -#include "wm_event_system.h" - -/* -------------------------------------------------------------------- */ -/** \name Menu Search Template Implementation - * \{ */ - -/* Unicode arrow. */ -#define MENU_SEP "\xe2\x96\xb6" - -/** - * Use when #menu_items_from_ui_create is called with `include_all_areas`. - * so we can run the menu item in the area it was extracted from. - */ -struct MenuSearch_Context { - /** - * Index into `Area.ui_type` #EnumPropertyItem or the top-bar when -1. - * Needed to get the display-name to use as a prefix for each menu item. - */ - int space_type_ui_index; - - ScrArea *area; - ARegion *region; -}; - -struct MenuSearch_Parent { - struct MenuSearch_Parent *parent; - MenuType *parent_mt; - const char *drawstr; - - /** Set while writing menu items only. */ - struct MenuSearch_Parent *temp_child; -}; - -struct MenuSearch_Item { - struct MenuSearch_Item *next, *prev; - const char *drawstr; - const char *drawwstr_full; - /** Support a single level sub-menu nesting (for operator buttons that expand). */ - const char *drawstr_submenu; - int icon; - int state; - - struct MenuSearch_Parent *menu_parent; - MenuType *mt; - - enum { - MENU_SEARCH_TYPE_OP = 1, - MENU_SEARCH_TYPE_RNA = 2, - } type; - - union { - /** Operator menu item. */ - struct { - wmOperatorType *type; - PointerRNA *opptr; - short opcontext; - bContextStore *context; - } op; - - /** Property (only for check-box/boolean). */ - struct { - PointerRNA ptr; - PropertyRNA *prop; - int index; - /** Only for enum buttons. */ - int enum_value; - } rna; - }; - - /** Set when we need each menu item to be able to set its own context. may be NULL. */ - struct MenuSearch_Context *wm_context; -}; - -struct MenuSearch_Data { - /** MenuSearch_Item */ - ListBase items; - /** Use for all small allocations. */ - MemArena *memarena; - - /** Use for context menu, to fake a button to create a context menu. */ - struct { - uiBut but; - uiBlock block; - } context_menu_data; -}; - -static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v) -{ - const struct MenuSearch_Item *menu_item_a = menu_item_a_v; - const struct MenuSearch_Item *menu_item_b = menu_item_b_v; - return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full); -} - -static const char *strdup_memarena(MemArena *memarena, const char *str) -{ - const uint str_size = strlen(str) + 1; - char *str_dst = BLI_memarena_alloc(memarena, str_size); - memcpy(str_dst, str, str_size); - return str_dst; -} - -static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str) -{ - const uint str_size = BLI_dynstr_get_len(dyn_str) + 1; - char *str_dst = BLI_memarena_alloc(memarena, str_size); - BLI_dynstr_get_cstring_ex(dyn_str, str_dst); - return str_dst; -} - -static bool menu_items_from_ui_create_item_from_button(struct MenuSearch_Data *data, - MemArena *memarena, - struct MenuType *mt, - const char *drawstr_submenu, - uiBut *but, - struct MenuSearch_Context *wm_context) -{ - struct MenuSearch_Item *item = NULL; - - /* Use override if the name is empty, this can happen with popovers. */ - const char *drawstr_override = NULL; - const char *drawstr_sep = (but->flag & UI_BUT_HAS_SEP_CHAR) ? - strrchr(but->drawstr, UI_SEP_CHAR) : - NULL; - const bool drawstr_is_empty = (drawstr_sep == but->drawstr) || (but->drawstr[0] == '\0'); - - if (but->optype != NULL) { - if (drawstr_is_empty) { - drawstr_override = WM_operatortype_name(but->optype, but->opptr); - } - - item = BLI_memarena_calloc(memarena, sizeof(*item)); - item->type = MENU_SEARCH_TYPE_OP; - - item->op.type = but->optype; - item->op.opcontext = but->opcontext; - item->op.context = but->context; - item->op.opptr = but->opptr; - but->opptr = NULL; - } - else if (but->rnaprop != NULL) { - const int prop_type = RNA_property_type(but->rnaprop); - - if (drawstr_is_empty) { - if (prop_type == PROP_ENUM) { - const int value_enum = (int)but->hardmax; - EnumPropertyItem enum_item; - if (RNA_property_enum_item_from_value_gettexted( - but->block->evil_C, &but->rnapoin, but->rnaprop, value_enum, &enum_item)) { - drawstr_override = enum_item.name; - } - else { - /* Should never happen. */ - drawstr_override = "Unknown"; - } - } - else { - drawstr_override = RNA_property_ui_name(but->rnaprop); - } - } - - if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) { - /* Note that these buttons are not prevented, - * but aren't typically used in menus. */ - printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n", - but->drawstr, - mt->idname, - prop_type); - } - else { - item = BLI_memarena_calloc(memarena, sizeof(*item)); - item->type = MENU_SEARCH_TYPE_RNA; - - item->rna.ptr = but->rnapoin; - item->rna.prop = but->rnaprop; - item->rna.index = but->rnaindex; - - if (prop_type == PROP_ENUM) { - item->rna.enum_value = (int)but->hardmax; - } - } - } - - if (item != NULL) { - /* Handle shared settings. */ - if (drawstr_override != NULL) { - const char *drawstr_suffix = drawstr_sep ? drawstr_sep : ""; - char *drawstr_alloc = BLI_string_joinN("(", drawstr_override, ")", drawstr_suffix); - item->drawstr = strdup_memarena(memarena, drawstr_alloc); - MEM_freeN(drawstr_alloc); - } - else { - item->drawstr = strdup_memarena(memarena, but->drawstr); - } - - item->icon = ui_but_icon(but); - item->state = (but->flag & - (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)); - item->mt = mt; - item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL; - - item->wm_context = wm_context; - - BLI_addtail(&data->items, item); - return true; - } - - return false; -} - -/** - * Populate a fake button from a menu item (use for context menu). - */ -static bool menu_items_to_ui_button(struct MenuSearch_Item *item, uiBut *but) -{ - bool changed = false; - switch (item->type) { - case MENU_SEARCH_TYPE_OP: { - but->optype = item->op.type; - but->opcontext = item->op.opcontext; - but->context = item->op.context; - but->opptr = item->op.opptr; - changed = true; - break; - } - case MENU_SEARCH_TYPE_RNA: { - const int prop_type = RNA_property_type(item->rna.prop); - - but->rnapoin = item->rna.ptr; - but->rnaprop = item->rna.prop; - but->rnaindex = item->rna.index; - - if (prop_type == PROP_ENUM) { - but->hardmax = item->rna.enum_value; - } - changed = true; - break; - } - } - - if (changed) { - STRNCPY(but->drawstr, item->drawstr); - char *drawstr_sep = (item->state & UI_BUT_HAS_SEP_CHAR) ? strrchr(but->drawstr, UI_SEP_CHAR) : - NULL; - if (drawstr_sep) { - *drawstr_sep = '\0'; - } - - but->icon = item->icon; - but->str = but->strdata; - } - - return changed; -} - -/** - * Populate \a menu_stack with menus from inspecting active key-maps for this context. - */ -static void menu_types_add_from_keymap_items(bContext *C, - wmWindow *win, - ScrArea *area, - ARegion *region, - LinkNode **menuid_stack_p, - GHash *menu_to_kmi, - GSet *menu_tagged) -{ - wmWindowManager *wm = CTX_wm_manager(C); - ListBase *handlers[] = { - region ? ®ion->handlers : NULL, - area ? &area->handlers : NULL, - &win->handlers, - }; - - for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) { - if (handlers[handler_index] == NULL) { - continue; - } - LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) { - /* During this loop, UI handlers for nested menus can tag multiple handlers free. */ - if (handler_base->flag & WM_HANDLER_DO_FREE) { - continue; - } - if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) { - continue; - } - - if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) { - wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; - wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); - if (keymap && WM_keymap_poll(C, keymap)) { - LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { - if (kmi->flag & KMI_INACTIVE) { - continue; - } - if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) { - char menu_idname[MAX_NAME]; - RNA_string_get(kmi->ptr, "name", menu_idname); - MenuType *mt = WM_menutype_find(menu_idname, false); - - if (mt && BLI_gset_add(menu_tagged, mt)) { - /* Unlikely, but possible this will be included twice. */ - BLI_linklist_prepend(menuid_stack_p, mt); - - void **kmi_p; - if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) { - *kmi_p = kmi; - } - } - } - } - } - } - } - } -} - -/** - * Display all operators (last). Developer-only convenience feature. - */ -static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *data) -{ - /* Add to temporary list so we can sort them separately. */ - ListBase operator_items = {NULL, NULL}; - - MemArena *memarena = data->memarena; - GHashIterator iter; - for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); - BLI_ghashIterator_step(&iter)) { - wmOperatorType *ot = BLI_ghashIterator_getValue(&iter); - - if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { - continue; - } - - if (WM_operator_poll((bContext *)C, ot)) { - const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); - - struct MenuSearch_Item *item = NULL; - item = BLI_memarena_calloc(memarena, sizeof(*item)); - item->type = MENU_SEARCH_TYPE_OP; - - item->op.type = ot; - item->op.opcontext = WM_OP_INVOKE_DEFAULT; - item->op.context = NULL; - - char idname_as_py[OP_MAX_TYPENAME]; - char uiname[256]; - WM_operator_py_idname(idname_as_py, ot->idname); - - SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name); - - item->drawwstr_full = strdup_memarena(memarena, uiname); - item->drawstr = ot_ui_name; - - item->wm_context = NULL; - - BLI_addtail(&operator_items, item); - } - } - - BLI_listbase_sort(&operator_items, menu_item_sort_by_drawstr_full); - - BLI_movelisttolist(&data->items, &operator_items); -} - -/** - * Create #MenuSearch_Data by inspecting the current context, this uses two methods: - * - * - Look up predefined editor-menus. - * - Look up key-map items which call menus. - */ -static struct MenuSearch_Data *menu_items_from_ui_create( - bContext *C, wmWindow *win, ScrArea *area_init, ARegion *region_init, bool include_all_areas) -{ - MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); - /** Map (#MenuType to #MenuSearch_Parent) */ - GHash *menu_parent_map = BLI_ghash_ptr_new(__func__); - GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__); - const uiStyle *style = UI_style_get_dpi(); - - /* Convert into non-ui structure. */ - struct MenuSearch_Data *data = MEM_callocN(sizeof(*data), __func__); - - DynStr *dyn_str = BLI_dynstr_new_memarena(); - - /* Use a stack of menus to handle and discover new menus in passes. */ - LinkNode *menu_stack = NULL; - - /* Tag menu types not to add, either because they have already been added - * or they have been blacklisted. - * Set of #MenuType. */ - GSet *menu_tagged = BLI_gset_ptr_new(__func__); - /** Map (#MenuType -> #wmKeyMapItem). */ - GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__); - - /* Blacklist menus we don't want to show. */ - { - const char *idname_array[] = { - /* While we could include this, it's just showing filenames to load. */ - "TOPBAR_MT_file_open_recent", - }; - for (int i = 0; i < ARRAY_SIZE(idname_array); i++) { - MenuType *mt = WM_menutype_find(idname_array[i], false); - if (mt != NULL) { - BLI_gset_add(menu_tagged, mt); - } - } - } - - { - /* Exclude context menus because: - * - The menu items are available elsewhere (and will show up multiple times). - * - Menu items depend on exact context, making search results unpredictable - * (exact number of items selected for example). See design doc T74158. - * There is one exception, - * as the outliner only exposes functionality via the context menu. */ - GHashIterator iter; - - for (WM_menutype_iter(&iter); (!BLI_ghashIterator_done(&iter)); - (BLI_ghashIterator_step(&iter))) { - MenuType *mt = BLI_ghashIterator_getValue(&iter); - if (BLI_str_endswith(mt->idname, "_context_menu")) { - BLI_gset_add(menu_tagged, mt); - } - } - const char *idname_array[] = { - /* Add back some context menus. */ - "OUTLINER_MT_context_menu", - }; - for (int i = 0; i < ARRAY_SIZE(idname_array); i++) { - MenuType *mt = WM_menutype_find(idname_array[i], false); - if (mt != NULL) { - BLI_gset_remove(menu_tagged, mt, NULL); - } - } - } - - /* Collect contexts, one for each 'ui_type'. */ - struct MenuSearch_Context *wm_contexts = NULL; - - const EnumPropertyItem *space_type_ui_items = NULL; - int space_type_ui_items_len = 0; - bool space_type_ui_items_free = false; - - /* Text used as prefix for top-bar menu items. */ - const char *global_menu_prefix = NULL; - - if (include_all_areas) { - bScreen *screen = WM_window_get_active_screen(win); - - /* First create arrays for ui_type. */ - PropertyRNA *prop_ui_type = NULL; - { - /* This must be a valid pointer, with only it's type checked. */ - ScrArea area_dummy = { - /* Anything besides #SPACE_EMPTY is fine, - * as this value is only included in the enum when set. */ - .spacetype = SPACE_TOPBAR, - }; - PointerRNA ptr; - RNA_pointer_create(&screen->id, &RNA_Area, &area_dummy, &ptr); - prop_ui_type = RNA_struct_find_property(&ptr, "ui_type"); - RNA_property_enum_items(C, - &ptr, - prop_ui_type, - &space_type_ui_items, - &space_type_ui_items_len, - &space_type_ui_items_free); - - wm_contexts = BLI_memarena_calloc(memarena, sizeof(*wm_contexts) * space_type_ui_items_len); - for (int i = 0; i < space_type_ui_items_len; i++) { - wm_contexts[i].space_type_ui_index = -1; - } - } - - LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { - ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); - if (region != NULL) { - PointerRNA ptr; - RNA_pointer_create(&screen->id, &RNA_Area, area, &ptr); - const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type); - - const int space_type_ui_index = RNA_enum_from_value(space_type_ui_items, space_type_ui); - if (space_type_ui_index == -1) { - continue; - } - - if (wm_contexts[space_type_ui_index].space_type_ui_index != -1) { - ScrArea *area_best = wm_contexts[space_type_ui_index].area; - const uint value_best = (uint)area_best->winx * (uint)area_best->winy; - const uint value_test = (uint)area->winx * (uint)area->winy; - if (value_best > value_test) { - continue; - } - } - - wm_contexts[space_type_ui_index].space_type_ui_index = space_type_ui_index; - wm_contexts[space_type_ui_index].area = area; - wm_contexts[space_type_ui_index].region = region; - } - } - - global_menu_prefix = CTX_IFACE_(RNA_property_translation_context(prop_ui_type), "Top Bar"); - } - - GHashIterator iter; - - for (int space_type_ui_index = -1; space_type_ui_index < space_type_ui_items_len; - space_type_ui_index += 1) { - - ScrArea *area = NULL; - ARegion *region = NULL; - struct MenuSearch_Context *wm_context = NULL; - - if (include_all_areas) { - if (space_type_ui_index == -1) { - /* First run without any context, to populate the top-bar without. */ - wm_context = NULL; - area = NULL; - region = NULL; - } - else { - wm_context = &wm_contexts[space_type_ui_index]; - if (wm_context->space_type_ui_index == -1) { - continue; - } - - area = wm_context->area; - region = wm_context->region; - - CTX_wm_area_set(C, area); - CTX_wm_region_set(C, region); - } - } - else { - area = area_init; - region = region_init; - } - - /* Populate menus from the editors, - * note that we could create a fake header, draw the header and extract the menus - * from the buttons, however this is quite involved and can be avoided as by convention - * each space-type has a single root-menu that headers use. */ - { - const char *idname_array[2] = {NULL}; - int idname_array_len = 0; - - /* Use negative for global (no area) context, populate the top-bar. */ - if (space_type_ui_index == -1) { - idname_array[idname_array_len++] = "TOPBAR_MT_editor_menus"; - } - -#define SPACE_MENU_MAP(space_type, menu_id) \ - case space_type: \ - idname_array[idname_array_len++] = menu_id; \ - break -#define SPACE_MENU_NOP(space_type) \ - case space_type: \ - break - - if (area != NULL) { - SpaceLink *sl = area->spacedata.first; - switch ((eSpace_Type)area->spacetype) { - SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus"); - SPACE_MENU_NOP(SPACE_PROPERTIES); - SPACE_MENU_MAP(SPACE_FILE, "FILEBROWSER_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_ACTION, - (((const SpaceAction *)sl)->mode == SACTCONT_TIMELINE) ? - "TIME_MT_editor_menus" : - "DOPESHEET_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus"); - SPACE_MENU_MAP(SPACE_CLIP, - (((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ? - "CLIP_MT_tracking_editor_menus" : - "CLIP_MT_masking_editor_menus"); - SPACE_MENU_NOP(SPACE_EMPTY); - SPACE_MENU_NOP(SPACE_SCRIPT); - SPACE_MENU_NOP(SPACE_STATUSBAR); - SPACE_MENU_NOP(SPACE_TOPBAR); - SPACE_MENU_NOP(SPACE_SPREADSHEET); - } - } - for (int i = 0; i < idname_array_len; i++) { - MenuType *mt = WM_menutype_find(idname_array[i], false); - if (mt != NULL) { - /* Check if this exists because of 'include_all_areas'. */ - if (BLI_gset_add(menu_tagged, mt)) { - BLI_linklist_prepend(&menu_stack, mt); - } - } - } - } -#undef SPACE_MENU_MAP -#undef SPACE_MENU_NOP - - bool has_keymap_menu_items = false; - - while (menu_stack != NULL) { - MenuType *mt = BLI_linklist_pop(&menu_stack); - if (!WM_menutype_poll(C, mt)) { - continue; - } - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); - uiLayout *layout = UI_block_layout( - block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - - UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); - - uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN); - UI_menutype_draw(C, mt, layout); - - UI_block_end(C, block); - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - MenuType *mt_from_but = NULL; - /* Support menu titles with dynamic from initial labels - * (used by edit-mesh context menu). */ - if (but->type == UI_BTYPE_LABEL) { - - /* Check if the label is the title. */ - uiBut *but_test = but->prev; - while (but_test && but_test->type == UI_BTYPE_SEPR) { - but_test = but_test->prev; - } - - if (but_test == NULL) { - BLI_ghash_insert( - menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr)); - } - } - else if (menu_items_from_ui_create_item_from_button( - data, memarena, mt, NULL, but, wm_context)) { - /* pass */ - } - else if ((mt_from_but = UI_but_menutype_get(but))) { - - if (BLI_gset_add(menu_tagged, mt_from_but)) { - BLI_linklist_prepend(&menu_stack, mt_from_but); - } - - if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) { - struct MenuSearch_Parent *menu_parent = BLI_memarena_calloc(memarena, - sizeof(*menu_parent)); - /* Use brackets for menu key shortcuts, - * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)". - * This is needed so we don't right align sub-menu contents - * we only want to do that for the last menu item, not the path that leads to it. - */ - const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ? - strrchr(but->drawstr, UI_SEP_CHAR) : - NULL; - bool drawstr_is_empty = false; - if (drawstr_sep != NULL) { - BLI_assert(BLI_dynstr_get_len(dyn_str) == 0); - /* Detect empty string, fallback to menu name. */ - const char *drawstr = but->drawstr; - int drawstr_len = drawstr_sep - but->drawstr; - if (UNLIKELY(drawstr_len == 0)) { - drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label); - drawstr_len = strlen(drawstr); - if (drawstr[0] == '\0') { - drawstr_is_empty = true; - } - } - BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len); - BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1); - menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str); - BLI_dynstr_clear(dyn_str); - } - else { - const char *drawstr = but->drawstr; - if (UNLIKELY(drawstr[0] == '\0')) { - drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label); - if (drawstr[0] == '\0') { - drawstr_is_empty = true; - } - } - menu_parent->drawstr = strdup_memarena(memarena, drawstr); - } - menu_parent->parent_mt = mt; - BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent); - - if (drawstr_is_empty) { - printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname); - } - } - } - else if (but->menu_create_func != NULL) { - /* A non 'MenuType' menu button. */ - - /* Only expand one level deep, this is mainly for expanding operator menus. */ - const char *drawstr_submenu = but->drawstr; - - /* +1 to avoid overlap with the current 'block'. */ - uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS); - uiLayout *sub_layout = UI_block_layout( - sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); - - UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); - - uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN); - - but->menu_create_func(C, sub_layout, but->poin); - - UI_block_end(C, sub_block); - - LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) { - menu_items_from_ui_create_item_from_button( - data, memarena, mt, drawstr_submenu, sub_but, wm_context); - } - - if (region) { - BLI_remlink(®ion->uiblocks, sub_block); - } - UI_block_free(NULL, sub_block); - } - } - if (region) { - BLI_remlink(®ion->uiblocks, block); - } - UI_block_free(NULL, block); - - /* Add key-map items as a second pass, - * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */ - if ((menu_stack == NULL) && (has_keymap_menu_items == false)) { - has_keymap_menu_items = true; - menu_types_add_from_keymap_items( - C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged); - } - } - } - - LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) { - item->menu_parent = BLI_ghash_lookup(menu_parent_map, item->mt); - } - - GHASH_ITER (iter, menu_parent_map) { - struct MenuSearch_Parent *menu_parent = BLI_ghashIterator_getValue(&iter); - menu_parent->parent = BLI_ghash_lookup(menu_parent_map, menu_parent->parent_mt); - } - - /* NOTE: currently this builds the full path for each menu item, - * that could be moved into the parent menu. */ - - /* Set names as full paths. */ - LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) { - BLI_assert(BLI_dynstr_get_len(dyn_str) == 0); - - if (include_all_areas) { - BLI_dynstr_appendf(dyn_str, - "%s: ", - (item->wm_context != NULL) ? - space_type_ui_items[item->wm_context->space_type_ui_index].name : - global_menu_prefix); - } - - if (item->menu_parent != NULL) { - struct MenuSearch_Parent *menu_parent = item->menu_parent; - menu_parent->temp_child = NULL; - while (menu_parent && menu_parent->parent) { - menu_parent->parent->temp_child = menu_parent; - menu_parent = menu_parent->parent; - } - while (menu_parent) { - BLI_dynstr_append(dyn_str, menu_parent->drawstr); - BLI_dynstr_append(dyn_str, " " MENU_SEP " "); - menu_parent = menu_parent->temp_child; - } - } - else { - const char *drawstr = BLI_ghash_lookup(menu_display_name_map, item->mt); - if (drawstr == NULL) { - drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label); - } - BLI_dynstr_append(dyn_str, drawstr); - - wmKeyMapItem *kmi = BLI_ghash_lookup(menu_to_kmi, item->mt); - if (kmi != NULL) { - char kmi_str[128]; - WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str)); - BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str); - } - - BLI_dynstr_append(dyn_str, " " MENU_SEP " "); - } - - /* Optional nested menu. */ - if (item->drawstr_submenu != NULL) { - BLI_dynstr_append(dyn_str, item->drawstr_submenu); - BLI_dynstr_append(dyn_str, " " MENU_SEP " "); - } - - BLI_dynstr_append(dyn_str, item->drawstr); - - item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str); - BLI_dynstr_clear(dyn_str); - } - BLI_dynstr_free(dyn_str); - - /* Finally sort menu items. - * - * Note: we might want to keep the in-menu order, for now sort all. */ - BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full); - - BLI_ghash_free(menu_parent_map, NULL, NULL); - BLI_ghash_free(menu_display_name_map, NULL, NULL); - - BLI_ghash_free(menu_to_kmi, NULL, NULL); - - BLI_gset_free(menu_tagged, NULL); - - data->memarena = memarena; - - if (include_all_areas) { - CTX_wm_area_set(C, area_init); - CTX_wm_region_set(C, region_init); - - if (space_type_ui_items_free) { - MEM_freeN((void *)space_type_ui_items); - } - } - - /* Include all operators for developers, - * since it can be handy to have a quick way to access any operator, - * including operators being developed which haven't yet been added into the interface. - * - * These are added after all menu items so developers still get normal behavior by default, - * unless searching for something that isn't already in a menu (or scroll down). - * - * Keep this behind a developer only check: - * - Many operators need options to be set to give useful results, see: T74157. - * - User who really prefer to list all operators can use #WM_OT_search_operator. - */ - if (U.flag & USER_DEVELOPER_UI) { - menu_items_from_all_operators(C, data); - } - - return data; -} - -static void menu_search_arg_free_fn(void *data_v) -{ - struct MenuSearch_Data *data = data_v; - LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) { - switch (item->type) { - case MENU_SEARCH_TYPE_OP: { - if (item->op.opptr != NULL) { - WM_operator_properties_free(item->op.opptr); - MEM_freeN(item->op.opptr); - } - } - case MENU_SEARCH_TYPE_RNA: { - break; - } - } - } - - BLI_memarena_free(data->memarena); - - MEM_freeN(data); -} - -static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) -{ - struct MenuSearch_Item *item = arg2; - if (item == NULL) { - return; - } - if (item->state & UI_BUT_DISABLED) { - return; - } - - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - if (item->wm_context != NULL) { - CTX_wm_area_set(C, item->wm_context->area); - CTX_wm_region_set(C, item->wm_context->region); - } - - switch (item->type) { - case MENU_SEARCH_TYPE_OP: { - CTX_store_set(C, item->op.context); - WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr); - CTX_store_set(C, NULL); - break; - } - case MENU_SEARCH_TYPE_RNA: { - PointerRNA *ptr = &item->rna.ptr; - PropertyRNA *prop = item->rna.prop; - const int index = item->rna.index; - const int prop_type = RNA_property_type(prop); - bool changed = false; - - if (prop_type == PROP_BOOLEAN) { - const bool is_array = RNA_property_array_check(prop); - if (is_array) { - const bool value = RNA_property_boolean_get_index(ptr, prop, index); - RNA_property_boolean_set_index(ptr, prop, index, !value); - } - else { - const bool value = RNA_property_boolean_get(ptr, prop); - RNA_property_boolean_set(ptr, prop, !value); - } - changed = true; - } - else if (prop_type == PROP_ENUM) { - RNA_property_enum_set(ptr, prop, item->rna.enum_value); - changed = true; - } - - if (changed) { - RNA_property_update(C, ptr, prop); - } - break; - } - } - - if (item->wm_context != NULL) { - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); - } -} - -static void menu_search_update_fn(const bContext *UNUSED(C), - void *arg, - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - struct MenuSearch_Data *data = arg; - - StringSearch *search = BLI_string_search_new(); - - LISTBASE_FOREACH (struct MenuSearch_Item *, item, &data->items) { - BLI_string_search_add(search, item->drawwstr_full, item); - } - - struct MenuSearch_Item **filtered_items; - const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items); - - for (int i = 0; i < filtered_amount; i++) { - struct MenuSearch_Item *item = filtered_items[i]; - if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state, 0)) { - break; - } - } - - MEM_freeN(filtered_items); - BLI_string_search_free(search); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Context Menu - * - * This uses a fake button to create a context menu, - * if this ever causes hard to solve bugs we may need to create - * a separate context menu just for the search, however this is fairly involved. - * \{ */ - -static bool ui_search_menu_create_context_menu(struct bContext *C, - void *arg, - void *active, - const struct wmEvent *UNUSED(event)) -{ - struct MenuSearch_Data *data = arg; - struct MenuSearch_Item *item = active; - bool has_menu = false; - - memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data)); - uiBut *but = &data->context_menu_data.but; - uiBlock *block = &data->context_menu_data.block; - - but->block = block; - - if (menu_items_to_ui_button(item, but)) { - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - if (item->wm_context != NULL) { - CTX_wm_area_set(C, item->wm_context->area); - CTX_wm_region_set(C, item->wm_context->region); - } - - if (ui_popup_context_menu_for_button(C, but)) { - has_menu = true; - } - - if (item->wm_context != NULL) { - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); - } - } - - return has_menu; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Tooltip - * \{ */ - -static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C, - struct ARegion *region, - const rcti *UNUSED(item_rect), - void *arg, - void *active) -{ - struct MenuSearch_Data *data = arg; - struct MenuSearch_Item *item = active; - - memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data)); - uiBut *but = &data->context_menu_data.but; - uiBlock *block = &data->context_menu_data.block; - unit_m4(block->winmat); - block->aspect = 1; - - but->block = block; - - /* Place the fake button at the cursor so the tool-tip is places properly. */ - float tip_init[2]; - const wmEvent *event = CTX_wm_window(C)->eventstate; - tip_init[0] = event->x; - tip_init[1] = event->y - (UI_UNIT_Y / 2); - ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]); - - but->rect.xmin = tip_init[0]; - but->rect.xmax = tip_init[0]; - but->rect.ymin = tip_init[1]; - but->rect.ymax = tip_init[1]; - - if (menu_items_to_ui_button(item, but)) { - ScrArea *area_prev = CTX_wm_area(C); - ARegion *region_prev = CTX_wm_region(C); - - if (item->wm_context != NULL) { - CTX_wm_area_set(C, item->wm_context->area); - CTX_wm_region_set(C, item->wm_context->region); - } - - ARegion *region_tip = UI_tooltip_create_from_button(C, region, but, false); - - if (item->wm_context != NULL) { - CTX_wm_area_set(C, area_prev); - CTX_wm_region_set(C, region_prev); - } - return region_tip; - } - - return NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Menu Search Template Public API - * \{ */ - -void UI_but_func_menu_search(uiBut *but) -{ - bContext *C = but->block->evil_C; - wmWindow *win = CTX_wm_window(C); - ScrArea *area = CTX_wm_area(C); - ARegion *region = CTX_wm_region(C); - /* When run from top-bar scan all areas in the current window. */ - const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)); - struct MenuSearch_Data *data = menu_items_from_ui_create( - C, win, area, region, include_all_areas); - UI_but_func_search_set(but, - /* Generic callback. */ - ui_searchbox_create_menu, - menu_search_update_fn, - data, - false, - menu_search_arg_free_fn, - menu_search_exec_fn, - NULL); - - UI_but_func_search_set_context_menu(but, ui_search_menu_create_context_menu); - UI_but_func_search_set_tooltip(but, ui_search_menu_create_tooltip); - UI_but_func_search_set_sep_string(but, MENU_SEP); -} - -void uiTemplateMenuSearch(uiLayout *layout) -{ - uiBlock *block; - uiBut *but; - static char search[256] = ""; - - block = uiLayoutGetBlock(layout); - UI_block_layout_set_current(block, layout); - - but = uiDefSearchBut( - block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, ""); - UI_but_func_menu_search(but); -} - -#undef MENU_SEP - -/** \} */ diff --git a/source/blender/editors/interface/interface_template_search_menu.cc b/source/blender/editors/interface/interface_template_search_menu.cc new file mode 100644 index 00000000000..fc7d705a150 --- /dev/null +++ b/source/blender/editors/interface/interface_template_search_menu.cc @@ -0,0 +1,1181 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * Search available menu items via the user interface & key-maps. + * Accessed via the #WM_OT_search_menu operator. + */ + +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_action_types.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_node_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_shader_fx_types.h" +#include "DNA_texture_types.h" + +#include "BLI_alloca.h" +#include "BLI_dynstr.h" +#include "BLI_ghash.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" +#include "BLI_memarena.h" +#include "BLI_string.h" +#include "BLI_string_search.h" +#include "BLI_string_utils.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_screen.h" + +#include "ED_screen.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +/* For key-map item access. */ +#include "wm_event_system.h" + +/* -------------------------------------------------------------------- */ +/** \name Menu Search Template Implementation + * \{ */ + +/* Unicode arrow. */ +#define MENU_SEP "\xe2\x96\xb6" + +/** + * Use when #menu_items_from_ui_create is called with `include_all_areas`. + * so we can run the menu item in the area it was extracted from. + */ +struct MenuSearch_Context { + /** + * Index into `Area.ui_type` #EnumPropertyItem or the top-bar when -1. + * Needed to get the display-name to use as a prefix for each menu item. + */ + int space_type_ui_index; + + ScrArea *area; + ARegion *region; +}; + +struct MenuSearch_Parent { + MenuSearch_Parent *parent; + MenuType *parent_mt; + const char *drawstr; + + /** Set while writing menu items only. */ + MenuSearch_Parent *temp_child; +}; + +struct MenuSearch_Item { + MenuSearch_Item *next, *prev; + const char *drawstr; + const char *drawwstr_full; + /** Support a single level sub-menu nesting (for operator buttons that expand). */ + const char *drawstr_submenu; + int icon; + int state; + + MenuSearch_Parent *menu_parent; + MenuType *mt; + + enum { + MENU_SEARCH_TYPE_OP = 1, + MENU_SEARCH_TYPE_RNA = 2, + } type; + + union { + /** Operator menu item. */ + struct { + wmOperatorType *type; + PointerRNA *opptr; + short opcontext; + bContextStore *context; + } op; + + /** Property (only for check-box/boolean). */ + struct { + PointerRNA ptr; + PropertyRNA *prop; + int index; + /** Only for enum buttons. */ + int enum_value; + } rna; + }; + + /** Set when we need each menu item to be able to set its own context. may be NULL. */ + MenuSearch_Context *wm_context; +}; + +struct MenuSearch_Data { + /** MenuSearch_Item */ + ListBase items; + /** Use for all small allocations. */ + MemArena *memarena; + + /** Use for context menu, to fake a button to create a context menu. */ + struct { + uiBut but; + uiBlock block; + } context_menu_data; +}; + +static int menu_item_sort_by_drawstr_full(const void *menu_item_a_v, const void *menu_item_b_v) +{ + const MenuSearch_Item *menu_item_a = (MenuSearch_Item *)menu_item_a_v; + const MenuSearch_Item *menu_item_b = (MenuSearch_Item *)menu_item_b_v; + return strcmp(menu_item_a->drawwstr_full, menu_item_b->drawwstr_full); +} + +static const char *strdup_memarena(MemArena *memarena, const char *str) +{ + const uint str_size = strlen(str) + 1; + char *str_dst = (char *)BLI_memarena_alloc(memarena, str_size); + memcpy(str_dst, str, str_size); + return str_dst; +} + +static const char *strdup_memarena_from_dynstr(MemArena *memarena, DynStr *dyn_str) +{ + const uint str_size = BLI_dynstr_get_len(dyn_str) + 1; + char *str_dst = (char *)BLI_memarena_alloc(memarena, str_size); + BLI_dynstr_get_cstring_ex(dyn_str, str_dst); + return str_dst; +} + +static bool menu_items_from_ui_create_item_from_button(MenuSearch_Data *data, + MemArena *memarena, + struct MenuType *mt, + const char *drawstr_submenu, + uiBut *but, + MenuSearch_Context *wm_context) +{ + MenuSearch_Item *item = NULL; + + /* Use override if the name is empty, this can happen with popovers. */ + const char *drawstr_override = NULL; + const char *drawstr_sep = (but->flag & UI_BUT_HAS_SEP_CHAR) ? + strrchr(but->drawstr, UI_SEP_CHAR) : + NULL; + const bool drawstr_is_empty = (drawstr_sep == but->drawstr) || (but->drawstr[0] == '\0'); + + if (but->optype != NULL) { + if (drawstr_is_empty) { + drawstr_override = WM_operatortype_name(but->optype, but->opptr); + } + + item = (MenuSearch_Item *)BLI_memarena_calloc(memarena, sizeof(*item)); + item->type = MenuSearch_Item::MENU_SEARCH_TYPE_OP; + + item->op.type = but->optype; + item->op.opcontext = but->opcontext; + item->op.context = but->context; + item->op.opptr = but->opptr; + but->opptr = NULL; + } + else if (but->rnaprop != NULL) { + const int prop_type = RNA_property_type(but->rnaprop); + + if (drawstr_is_empty) { + if (prop_type == PROP_ENUM) { + const int value_enum = (int)but->hardmax; + EnumPropertyItem enum_item; + if (RNA_property_enum_item_from_value_gettexted((bContext *)but->block->evil_C, + &but->rnapoin, + but->rnaprop, + value_enum, + &enum_item)) { + drawstr_override = enum_item.name; + } + else { + /* Should never happen. */ + drawstr_override = "Unknown"; + } + } + else { + drawstr_override = RNA_property_ui_name(but->rnaprop); + } + } + + if (!ELEM(prop_type, PROP_BOOLEAN, PROP_ENUM)) { + /* Note that these buttons are not prevented, + * but aren't typically used in menus. */ + printf("Button '%s' in menu '%s' is a menu item with unsupported RNA type %d\n", + but->drawstr, + mt->idname, + prop_type); + } + else { + MenuSearch_Item *item = (MenuSearch_Item *)BLI_memarena_calloc(memarena, sizeof(*item)); + item->type = MenuSearch_Item::MENU_SEARCH_TYPE_RNA; + + item->rna.ptr = but->rnapoin; + item->rna.prop = but->rnaprop; + item->rna.index = but->rnaindex; + + if (prop_type == PROP_ENUM) { + item->rna.enum_value = (int)but->hardmax; + } + } + } + + if (item != NULL) { + /* Handle shared settings. */ + if (drawstr_override != NULL) { + const char *drawstr_suffix = drawstr_sep ? drawstr_sep : ""; + char *drawstr_alloc = BLI_string_joinN("(", drawstr_override, ")", drawstr_suffix); + item->drawstr = strdup_memarena(memarena, drawstr_alloc); + MEM_freeN(drawstr_alloc); + } + else { + item->drawstr = strdup_memarena(memarena, but->drawstr); + } + + item->icon = ui_but_icon(but); + item->state = (but->flag & + (UI_BUT_DISABLED | UI_BUT_INACTIVE | UI_BUT_REDALERT | UI_BUT_HAS_SEP_CHAR)); + item->mt = mt; + item->drawstr_submenu = drawstr_submenu ? strdup_memarena(memarena, drawstr_submenu) : NULL; + + item->wm_context = wm_context; + + BLI_addtail(&data->items, item); + return true; + } + + return false; +} + +/** + * Populate a fake button from a menu item (use for context menu). + */ +static bool menu_items_to_ui_button(MenuSearch_Item *item, uiBut *but) +{ + bool changed = false; + switch (item->type) { + case MenuSearch_Item::MENU_SEARCH_TYPE_OP: { + but->optype = item->op.type; + but->opcontext = item->op.opcontext; + but->context = item->op.context; + but->opptr = item->op.opptr; + changed = true; + break; + } + case MenuSearch_Item::MENU_SEARCH_TYPE_RNA: { + const int prop_type = RNA_property_type(item->rna.prop); + + but->rnapoin = item->rna.ptr; + but->rnaprop = item->rna.prop; + but->rnaindex = item->rna.index; + + if (prop_type == PROP_ENUM) { + but->hardmax = item->rna.enum_value; + } + changed = true; + break; + } + } + + if (changed) { + STRNCPY(but->drawstr, item->drawstr); + char *drawstr_sep = (item->state & UI_BUT_HAS_SEP_CHAR) ? strrchr(but->drawstr, UI_SEP_CHAR) : + NULL; + if (drawstr_sep) { + *drawstr_sep = '\0'; + } + + but->icon = (BIFIconID)item->icon; + but->str = but->strdata; + } + + return changed; +} + +/** + * Populate \a menu_stack with menus from inspecting active key-maps for this context. + */ +static void menu_types_add_from_keymap_items(bContext *C, + wmWindow *win, + ScrArea *area, + ARegion *region, + LinkNode **menuid_stack_p, + GHash *menu_to_kmi, + GSet *menu_tagged) +{ + wmWindowManager *wm = CTX_wm_manager(C); + ListBase *handlers[] = { + region ? ®ion->handlers : NULL, + area ? &area->handlers : NULL, + &win->handlers, + }; + + for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) { + if (handlers[handler_index] == NULL) { + continue; + } + LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers[handler_index]) { + /* During this loop, UI handlers for nested menus can tag multiple handlers free. */ + if (handler_base->flag & WM_HANDLER_DO_FREE) { + continue; + } + if (handler_base->type != WM_HANDLER_TYPE_KEYMAP) { + continue; + } + + if (handler_base->poll == NULL || handler_base->poll(region, win->eventstate)) { + wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base; + wmKeyMap *keymap = WM_event_get_keymap_from_handler(wm, handler); + if (keymap && WM_keymap_poll(C, keymap)) { + LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) { + if (kmi->flag & KMI_INACTIVE) { + continue; + } + if (STR_ELEM(kmi->idname, "WM_OT_call_menu", "WM_OT_call_menu_pie")) { + char menu_idname[MAX_NAME]; + RNA_string_get(kmi->ptr, "name", menu_idname); + MenuType *mt = WM_menutype_find(menu_idname, false); + + if (mt && BLI_gset_add(menu_tagged, mt)) { + /* Unlikely, but possible this will be included twice. */ + BLI_linklist_prepend(menuid_stack_p, mt); + + void **kmi_p; + if (!BLI_ghash_ensure_p(menu_to_kmi, mt, &kmi_p)) { + *kmi_p = kmi; + } + } + } + } + } + } + } + } +} + +/** + * Display all operators (last). Developer-only convenience feature. + */ +static void menu_items_from_all_operators(bContext *C, MenuSearch_Data *data) +{ + /* Add to temporary list so we can sort them separately. */ + ListBase operator_items = {NULL, NULL}; + + MemArena *memarena = data->memarena; + GHashIterator iter; + for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); + BLI_ghashIterator_step(&iter)) { + wmOperatorType *ot = (wmOperatorType *)BLI_ghashIterator_getValue(&iter); + + if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { + continue; + } + + if (WM_operator_poll((bContext *)C, ot)) { + const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); + + MenuSearch_Item *item = (MenuSearch_Item *)BLI_memarena_calloc(memarena, sizeof(*item)); + item->type = MenuSearch_Item::MENU_SEARCH_TYPE_OP; + + item->op.type = ot; + item->op.opcontext = WM_OP_INVOKE_DEFAULT; + item->op.context = NULL; + + char idname_as_py[OP_MAX_TYPENAME]; + char uiname[256]; + WM_operator_py_idname(idname_as_py, ot->idname); + + SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name); + + item->drawwstr_full = strdup_memarena(memarena, uiname); + item->drawstr = ot_ui_name; + + item->wm_context = NULL; + + BLI_addtail(&operator_items, item); + } + } + + BLI_listbase_sort(&operator_items, menu_item_sort_by_drawstr_full); + + BLI_movelisttolist(&data->items, &operator_items); +} + +/** + * Create #MenuSearch_Data by inspecting the current context, this uses two methods: + * + * - Look up predefined editor-menus. + * - Look up key-map items which call menus. + */ +static MenuSearch_Data *menu_items_from_ui_create( + bContext *C, wmWindow *win, ScrArea *area_init, ARegion *region_init, bool include_all_areas) +{ + MemArena *memarena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__); + /** Map (#MenuType to #MenuSearch_Parent) */ + GHash *menu_parent_map = BLI_ghash_ptr_new(__func__); + GHash *menu_display_name_map = BLI_ghash_ptr_new(__func__); + const uiStyle *style = UI_style_get_dpi(); + + /* Convert into non-ui structure. */ + MenuSearch_Data *data = (MenuSearch_Data *)MEM_callocN(sizeof(*data), __func__); + + DynStr *dyn_str = BLI_dynstr_new_memarena(); + + /* Use a stack of menus to handle and discover new menus in passes. */ + LinkNode *menu_stack = NULL; + + /* Tag menu types not to add, either because they have already been added + * or they have been blacklisted. + * Set of #MenuType. */ + GSet *menu_tagged = BLI_gset_ptr_new(__func__); + /** Map (#MenuType -> #wmKeyMapItem). */ + GHash *menu_to_kmi = BLI_ghash_ptr_new(__func__); + + /* Blacklist menus we don't want to show. */ + { + const char *idname_array[] = { + /* While we could include this, it's just showing filenames to load. */ + "TOPBAR_MT_file_open_recent", + }; + for (int i = 0; i < ARRAY_SIZE(idname_array); i++) { + MenuType *mt = WM_menutype_find(idname_array[i], false); + if (mt != NULL) { + BLI_gset_add(menu_tagged, mt); + } + } + } + + { + /* Exclude context menus because: + * - The menu items are available elsewhere (and will show up multiple times). + * - Menu items depend on exact context, making search results unpredictable + * (exact number of items selected for example). See design doc T74158. + * There is one exception, + * as the outliner only exposes functionality via the context menu. */ + GHashIterator iter; + + for (WM_menutype_iter(&iter); (!BLI_ghashIterator_done(&iter)); + (BLI_ghashIterator_step(&iter))) { + MenuType *mt = (MenuType *)BLI_ghashIterator_getValue(&iter); + if (BLI_str_endswith(mt->idname, "_context_menu")) { + BLI_gset_add(menu_tagged, mt); + } + } + const char *idname_array[] = { + /* Add back some context menus. */ + "OUTLINER_MT_context_menu", + }; + for (int i = 0; i < ARRAY_SIZE(idname_array); i++) { + MenuType *mt = WM_menutype_find(idname_array[i], false); + if (mt != NULL) { + BLI_gset_remove(menu_tagged, mt, NULL); + } + } + } + + /* Collect contexts, one for each 'ui_type'. */ + MenuSearch_Context *wm_contexts = NULL; + + const EnumPropertyItem *space_type_ui_items = NULL; + int space_type_ui_items_len = 0; + bool space_type_ui_items_free = false; + + /* Text used as prefix for top-bar menu items. */ + const char *global_menu_prefix = NULL; + + if (include_all_areas) { + bScreen *screen = WM_window_get_active_screen(win); + + /* First create arrays for ui_type. */ + PropertyRNA *prop_ui_type = NULL; + { + /* This must be a valid pointer, with only it's type checked. */ + ScrArea area_dummy = { + /* Anything besides #SPACE_EMPTY is fine, + * as this value is only included in the enum when set. */ + .spacetype = SPACE_TOPBAR, + }; + PointerRNA ptr; + RNA_pointer_create(&screen->id, &RNA_Area, &area_dummy, &ptr); + prop_ui_type = RNA_struct_find_property(&ptr, "ui_type"); + RNA_property_enum_items(C, + &ptr, + prop_ui_type, + &space_type_ui_items, + &space_type_ui_items_len, + &space_type_ui_items_free); + + wm_contexts = (MenuSearch_Context *)BLI_memarena_calloc( + memarena, sizeof(*wm_contexts) * space_type_ui_items_len); + for (int i = 0; i < space_type_ui_items_len; i++) { + wm_contexts[i].space_type_ui_index = -1; + } + } + + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); + if (region != NULL) { + PointerRNA ptr; + RNA_pointer_create(&screen->id, &RNA_Area, area, &ptr); + const int space_type_ui = RNA_property_enum_get(&ptr, prop_ui_type); + + const int space_type_ui_index = RNA_enum_from_value(space_type_ui_items, space_type_ui); + if (space_type_ui_index == -1) { + continue; + } + + if (wm_contexts[space_type_ui_index].space_type_ui_index != -1) { + ScrArea *area_best = wm_contexts[space_type_ui_index].area; + const uint value_best = (uint)area_best->winx * (uint)area_best->winy; + const uint value_test = (uint)area->winx * (uint)area->winy; + if (value_best > value_test) { + continue; + } + } + + wm_contexts[space_type_ui_index].space_type_ui_index = space_type_ui_index; + wm_contexts[space_type_ui_index].area = area; + wm_contexts[space_type_ui_index].region = region; + } + } + + global_menu_prefix = CTX_IFACE_(RNA_property_translation_context(prop_ui_type), "Top Bar"); + } + + GHashIterator iter; + + for (int space_type_ui_index = -1; space_type_ui_index < space_type_ui_items_len; + space_type_ui_index += 1) { + + ScrArea *area = NULL; + ARegion *region = NULL; + MenuSearch_Context *wm_context = NULL; + + if (include_all_areas) { + if (space_type_ui_index == -1) { + /* First run without any context, to populate the top-bar without. */ + wm_context = NULL; + area = NULL; + region = NULL; + } + else { + wm_context = &wm_contexts[space_type_ui_index]; + if (wm_context->space_type_ui_index == -1) { + continue; + } + + area = wm_context->area; + region = wm_context->region; + + CTX_wm_area_set(C, area); + CTX_wm_region_set(C, region); + } + } + else { + area = area_init; + region = region_init; + } + + /* Populate menus from the editors, + * note that we could create a fake header, draw the header and extract the menus + * from the buttons, however this is quite involved and can be avoided as by convention + * each space-type has a single root-menu that headers use. */ + { + const char *idname_array[2] = {NULL}; + int idname_array_len = 0; + + /* Use negative for global (no area) context, populate the top-bar. */ + if (space_type_ui_index == -1) { + idname_array[idname_array_len++] = "TOPBAR_MT_editor_menus"; + } + +#define SPACE_MENU_MAP(space_type, menu_id) \ + case space_type: \ + idname_array[idname_array_len++] = menu_id; \ + break +#define SPACE_MENU_NOP(space_type) \ + case space_type: \ + break + + if (area != NULL) { + SpaceLink *sl = (SpaceLink *)area->spacedata.first; + switch ((eSpace_Type)area->spacetype) { + SPACE_MENU_MAP(SPACE_VIEW3D, "VIEW3D_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_GRAPH, "GRAPH_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_OUTLINER, "OUTLINER_MT_editor_menus"); + SPACE_MENU_NOP(SPACE_PROPERTIES); + SPACE_MENU_MAP(SPACE_FILE, "FILEBROWSER_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_IMAGE, "IMAGE_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_INFO, "INFO_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_SEQ, "SEQUENCER_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_TEXT, "TEXT_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_ACTION, + (((const SpaceAction *)sl)->mode == SACTCONT_TIMELINE) ? + "TIME_MT_editor_menus" : + "DOPESHEET_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_NLA, "NLA_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus"); + SPACE_MENU_MAP(SPACE_CLIP, + (((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ? + "CLIP_MT_tracking_editor_menus" : + "CLIP_MT_masking_editor_menus"); + SPACE_MENU_NOP(SPACE_EMPTY); + SPACE_MENU_NOP(SPACE_SCRIPT); + SPACE_MENU_NOP(SPACE_STATUSBAR); + SPACE_MENU_NOP(SPACE_TOPBAR); + SPACE_MENU_NOP(SPACE_SPREADSHEET); + } + } + for (int i = 0; i < idname_array_len; i++) { + MenuType *mt = WM_menutype_find(idname_array[i], false); + if (mt != NULL) { + /* Check if this exists because of 'include_all_areas'. */ + if (BLI_gset_add(menu_tagged, mt)) { + BLI_linklist_prepend(&menu_stack, mt); + } + } + } + } +#undef SPACE_MENU_MAP +#undef SPACE_MENU_NOP + + bool has_keymap_menu_items = false; + + while (menu_stack != NULL) { + MenuType *mt = (MenuType *)BLI_linklist_pop(&menu_stack); + if (!WM_menutype_poll(C, mt)) { + continue; + } + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + uiLayout *layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); + + UI_block_flag_enable(block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); + + uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_REGION_WIN); + UI_menutype_draw(C, mt, layout); + + UI_block_end(C, block); + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + MenuType *mt_from_but = NULL; + /* Support menu titles with dynamic from initial labels + * (used by edit-mesh context menu). */ + if (but->type == UI_BTYPE_LABEL) { + + /* Check if the label is the title. */ + uiBut *but_test = but->prev; + while (but_test && but_test->type == UI_BTYPE_SEPR) { + but_test = but_test->prev; + } + + if (but_test == NULL) { + BLI_ghash_insert( + menu_display_name_map, mt, (void *)strdup_memarena(memarena, but->drawstr)); + } + } + else if (menu_items_from_ui_create_item_from_button( + data, memarena, mt, NULL, but, wm_context)) { + /* pass */ + } + else if ((mt_from_but = UI_but_menutype_get(but))) { + + if (BLI_gset_add(menu_tagged, mt_from_but)) { + BLI_linklist_prepend(&menu_stack, mt_from_but); + } + + if (!BLI_ghash_haskey(menu_parent_map, mt_from_but)) { + MenuSearch_Parent *menu_parent = (MenuSearch_Parent *)BLI_memarena_calloc( + memarena, sizeof(*menu_parent)); + /* Use brackets for menu key shortcuts, + * converting "Text|Some-Shortcut" to "Text (Some-Shortcut)". + * This is needed so we don't right align sub-menu contents + * we only want to do that for the last menu item, not the path that leads to it. + */ + const char *drawstr_sep = but->flag & UI_BUT_HAS_SEP_CHAR ? + strrchr(but->drawstr, UI_SEP_CHAR) : + NULL; + bool drawstr_is_empty = false; + if (drawstr_sep != NULL) { + BLI_assert(BLI_dynstr_get_len(dyn_str) == 0); + /* Detect empty string, fallback to menu name. */ + const char *drawstr = but->drawstr; + int drawstr_len = drawstr_sep - but->drawstr; + if (UNLIKELY(drawstr_len == 0)) { + drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label); + drawstr_len = strlen(drawstr); + if (drawstr[0] == '\0') { + drawstr_is_empty = true; + } + } + BLI_dynstr_nappend(dyn_str, drawstr, drawstr_len); + BLI_dynstr_appendf(dyn_str, " (%s)", drawstr_sep + 1); + menu_parent->drawstr = strdup_memarena_from_dynstr(memarena, dyn_str); + BLI_dynstr_clear(dyn_str); + } + else { + const char *drawstr = but->drawstr; + if (UNLIKELY(drawstr[0] == '\0')) { + drawstr = CTX_IFACE_(mt_from_but->translation_context, mt_from_but->label); + if (drawstr[0] == '\0') { + drawstr_is_empty = true; + } + } + menu_parent->drawstr = strdup_memarena(memarena, drawstr); + } + menu_parent->parent_mt = mt; + BLI_ghash_insert(menu_parent_map, mt_from_but, menu_parent); + + if (drawstr_is_empty) { + printf("Warning: '%s' menu has empty 'bl_label'.\n", mt_from_but->idname); + } + } + } + else if (but->menu_create_func != NULL) { + /* A non 'MenuType' menu button. */ + + /* Only expand one level deep, this is mainly for expanding operator menus. */ + const char *drawstr_submenu = but->drawstr; + + /* +1 to avoid overlap with the current 'block'. */ + uiBlock *sub_block = UI_block_begin(C, region, __func__ + 1, UI_EMBOSS); + uiLayout *sub_layout = UI_block_layout( + sub_block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style); + + UI_block_flag_enable(sub_block, UI_BLOCK_SHOW_SHORTCUT_ALWAYS); + + uiLayoutSetOperatorContext(sub_layout, WM_OP_INVOKE_REGION_WIN); + + but->menu_create_func(C, sub_layout, but->poin); + + UI_block_end(C, sub_block); + + LISTBASE_FOREACH (uiBut *, sub_but, &sub_block->buttons) { + menu_items_from_ui_create_item_from_button( + data, memarena, mt, drawstr_submenu, sub_but, wm_context); + } + + if (region) { + BLI_remlink(®ion->uiblocks, sub_block); + } + UI_block_free(NULL, sub_block); + } + } + if (region) { + BLI_remlink(®ion->uiblocks, block); + } + UI_block_free(NULL, block); + + /* Add key-map items as a second pass, + * so all menus are accessed from the header & top-bar before key shortcuts are expanded. */ + if ((menu_stack == NULL) && (has_keymap_menu_items == false)) { + has_keymap_menu_items = true; + menu_types_add_from_keymap_items( + C, win, area, region, &menu_stack, menu_to_kmi, menu_tagged); + } + } + } + + LISTBASE_FOREACH (MenuSearch_Item *, item, &data->items) { + item->menu_parent = (MenuSearch_Parent *)BLI_ghash_lookup(menu_parent_map, item->mt); + } + + GHASH_ITER (iter, menu_parent_map) { + MenuSearch_Parent *menu_parent = (MenuSearch_Parent *)BLI_ghashIterator_getValue(&iter); + menu_parent->parent = (MenuSearch_Parent *)BLI_ghash_lookup(menu_parent_map, + menu_parent->parent_mt); + } + + /* NOTE: currently this builds the full path for each menu item, + * that could be moved into the parent menu. */ + + /* Set names as full paths. */ + LISTBASE_FOREACH (MenuSearch_Item *, item, &data->items) { + BLI_assert(BLI_dynstr_get_len(dyn_str) == 0); + + if (include_all_areas) { + BLI_dynstr_appendf(dyn_str, + "%s: ", + (item->wm_context != NULL) ? + space_type_ui_items[item->wm_context->space_type_ui_index].name : + global_menu_prefix); + } + + if (item->menu_parent != NULL) { + MenuSearch_Parent *menu_parent = item->menu_parent; + menu_parent->temp_child = NULL; + while (menu_parent && menu_parent->parent) { + menu_parent->parent->temp_child = menu_parent; + menu_parent = menu_parent->parent; + } + while (menu_parent) { + BLI_dynstr_append(dyn_str, menu_parent->drawstr); + BLI_dynstr_append(dyn_str, " " MENU_SEP " "); + menu_parent = menu_parent->temp_child; + } + } + else { + const char *drawstr = (const char *)BLI_ghash_lookup(menu_display_name_map, item->mt); + if (drawstr == NULL) { + drawstr = CTX_IFACE_(item->mt->translation_context, item->mt->label); + } + BLI_dynstr_append(dyn_str, drawstr); + + wmKeyMapItem *kmi = (wmKeyMapItem *)BLI_ghash_lookup(menu_to_kmi, item->mt); + if (kmi != NULL) { + char kmi_str[128]; + WM_keymap_item_to_string(kmi, false, kmi_str, sizeof(kmi_str)); + BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str); + } + + BLI_dynstr_append(dyn_str, " " MENU_SEP " "); + } + + /* Optional nested menu. */ + if (item->drawstr_submenu != NULL) { + BLI_dynstr_append(dyn_str, item->drawstr_submenu); + BLI_dynstr_append(dyn_str, " " MENU_SEP " "); + } + + BLI_dynstr_append(dyn_str, item->drawstr); + + item->drawwstr_full = strdup_memarena_from_dynstr(memarena, dyn_str); + BLI_dynstr_clear(dyn_str); + } + BLI_dynstr_free(dyn_str); + + /* Finally sort menu items. + * + * Note: we might want to keep the in-menu order, for now sort all. */ + BLI_listbase_sort(&data->items, menu_item_sort_by_drawstr_full); + + BLI_ghash_free(menu_parent_map, NULL, NULL); + BLI_ghash_free(menu_display_name_map, NULL, NULL); + + BLI_ghash_free(menu_to_kmi, NULL, NULL); + + BLI_gset_free(menu_tagged, NULL); + + data->memarena = memarena; + + if (include_all_areas) { + CTX_wm_area_set(C, area_init); + CTX_wm_region_set(C, region_init); + + if (space_type_ui_items_free) { + MEM_freeN((void *)space_type_ui_items); + } + } + + /* Include all operators for developers, + * since it can be handy to have a quick way to access any operator, + * including operators being developed which haven't yet been added into the interface. + * + * These are added after all menu items so developers still get normal behavior by default, + * unless searching for something that isn't already in a menu (or scroll down). + * + * Keep this behind a developer only check: + * - Many operators need options to be set to give useful results, see: T74157. + * - User who really prefer to list all operators can use #WM_OT_search_operator. + */ + if (U.flag & USER_DEVELOPER_UI) { + menu_items_from_all_operators(C, data); + } + + return data; +} + +static void menu_search_arg_free_fn(void *data_v) +{ + MenuSearch_Data *data = (MenuSearch_Data *)data_v; + LISTBASE_FOREACH (MenuSearch_Item *, item, &data->items) { + switch (item->type) { + case MenuSearch_Item::MENU_SEARCH_TYPE_OP: { + if (item->op.opptr != NULL) { + WM_operator_properties_free(item->op.opptr); + MEM_freeN(item->op.opptr); + } + } + case MenuSearch_Item::MENU_SEARCH_TYPE_RNA: { + break; + } + } + } + + BLI_memarena_free(data->memarena); + + MEM_freeN(data); +} + +static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) +{ + MenuSearch_Item *item = (MenuSearch_Item *)arg2; + + if (item == NULL) { + return; + } + if (item->state & UI_BUT_DISABLED) { + return; + } + + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + if (item->wm_context != NULL) { + CTX_wm_area_set(C, item->wm_context->area); + CTX_wm_region_set(C, item->wm_context->region); + } + + switch (item->type) { + case MenuSearch_Item::MENU_SEARCH_TYPE_OP: { + CTX_store_set(C, item->op.context); + WM_operator_name_call_ptr(C, item->op.type, item->op.opcontext, item->op.opptr); + CTX_store_set(C, NULL); + break; + } + case MenuSearch_Item::MENU_SEARCH_TYPE_RNA: { + PointerRNA *ptr = &item->rna.ptr; + PropertyRNA *prop = item->rna.prop; + const int index = item->rna.index; + const int prop_type = RNA_property_type(prop); + bool changed = false; + + if (prop_type == PROP_BOOLEAN) { + const bool is_array = RNA_property_array_check(prop); + if (is_array) { + const bool value = RNA_property_boolean_get_index(ptr, prop, index); + RNA_property_boolean_set_index(ptr, prop, index, !value); + } + else { + const bool value = RNA_property_boolean_get(ptr, prop); + RNA_property_boolean_set(ptr, prop, !value); + } + changed = true; + } + else if (prop_type == PROP_ENUM) { + RNA_property_enum_set(ptr, prop, item->rna.enum_value); + changed = true; + } + + if (changed) { + RNA_property_update(C, ptr, prop); + } + break; + } + } + + if (item->wm_context != NULL) { + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); + } +} + +static void menu_search_update_fn(const bContext *UNUSED(C), + void *arg, + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + MenuSearch_Data *data = (MenuSearch_Data *)arg; + + StringSearch *search = BLI_string_search_new(); + + LISTBASE_FOREACH (MenuSearch_Item *, item, &data->items) { + BLI_string_search_add(search, item->drawwstr_full, item); + } + + MenuSearch_Item **filtered_items; + const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_items); + + for (int i = 0; i < filtered_amount; i++) { + MenuSearch_Item *item = filtered_items[i]; + if (!UI_search_item_add(items, item->drawwstr_full, item, item->icon, item->state, 0)) { + break; + } + } + + MEM_freeN(filtered_items); + BLI_string_search_free(search); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Context Menu + * + * This uses a fake button to create a context menu, + * if this ever causes hard to solve bugs we may need to create + * a separate context menu just for the search, however this is fairly involved. + * \{ */ + +static bool ui_search_menu_create_context_menu(struct bContext *C, + void *arg, + void *active, + const struct wmEvent *UNUSED(event)) +{ + MenuSearch_Data *data = (MenuSearch_Data *)arg; + MenuSearch_Item *item = (MenuSearch_Item *)active; + bool has_menu = false; + + memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data)); + uiBut *but = &data->context_menu_data.but; + uiBlock *block = &data->context_menu_data.block; + + but->block = block; + + if (menu_items_to_ui_button(item, but)) { + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + if (item->wm_context != NULL) { + CTX_wm_area_set(C, item->wm_context->area); + CTX_wm_region_set(C, item->wm_context->region); + } + + if (ui_popup_context_menu_for_button(C, but)) { + has_menu = true; + } + + if (item->wm_context != NULL) { + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); + } + } + + return has_menu; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Tooltip + * \{ */ + +static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C, + struct ARegion *region, + const rcti *UNUSED(item_rect), + void *arg, + void *active) +{ + MenuSearch_Data *data = (MenuSearch_Data *)arg; + MenuSearch_Item *item = (MenuSearch_Item *)active; + + memset(&data->context_menu_data, 0x0, sizeof(data->context_menu_data)); + uiBut *but = &data->context_menu_data.but; + uiBlock *block = &data->context_menu_data.block; + unit_m4(block->winmat); + block->aspect = 1; + + but->block = block; + + /* Place the fake button at the cursor so the tool-tip is places properly. */ + float tip_init[2]; + const wmEvent *event = CTX_wm_window(C)->eventstate; + tip_init[0] = event->x; + tip_init[1] = event->y - (UI_UNIT_Y / 2); + ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]); + + but->rect.xmin = tip_init[0]; + but->rect.xmax = tip_init[0]; + but->rect.ymin = tip_init[1]; + but->rect.ymax = tip_init[1]; + + if (menu_items_to_ui_button(item, but)) { + ScrArea *area_prev = CTX_wm_area(C); + ARegion *region_prev = CTX_wm_region(C); + + if (item->wm_context != NULL) { + CTX_wm_area_set(C, item->wm_context->area); + CTX_wm_region_set(C, item->wm_context->region); + } + + ARegion *region_tip = UI_tooltip_create_from_button(C, region, but, false); + + if (item->wm_context != NULL) { + CTX_wm_area_set(C, area_prev); + CTX_wm_region_set(C, region_prev); + } + return region_tip; + } + + return NULL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Menu Search Template Public API + * \{ */ + +void UI_but_func_menu_search(uiBut *but) +{ + bContext *C = (bContext *)but->block->evil_C; + wmWindow *win = CTX_wm_window(C); + ScrArea *area = CTX_wm_area(C); + ARegion *region = CTX_wm_region(C); + /* When run from top-bar scan all areas in the current window. */ + const bool include_all_areas = (area && (area->spacetype == SPACE_TOPBAR)); + MenuSearch_Data *data = menu_items_from_ui_create(C, win, area, region, include_all_areas); + UI_but_func_search_set(but, + /* Generic callback. */ + ui_searchbox_create_menu, + menu_search_update_fn, + data, + false, + menu_search_arg_free_fn, + menu_search_exec_fn, + NULL); + + UI_but_func_search_set_context_menu(but, ui_search_menu_create_context_menu); + UI_but_func_search_set_tooltip(but, ui_search_menu_create_tooltip); + UI_but_func_search_set_sep_string(but, MENU_SEP); +} + +void uiTemplateMenuSearch(uiLayout *layout) +{ + uiBlock *block; + uiBut *but; + static char search[256] = ""; + + block = uiLayoutGetBlock(layout); + UI_block_layout_set_current(block, layout); + + but = uiDefSearchBut( + block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, ""); + UI_but_func_menu_search(but); +} + +#undef MENU_SEP + +/** \} */ diff --git a/source/blender/editors/interface/interface_template_search_operator.c b/source/blender/editors/interface/interface_template_search_operator.c deleted file mode 100644 index 2b765a1a2f5..00000000000 --- a/source/blender/editors/interface/interface_template_search_operator.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - * - * Search available operators by scanning all and checking their poll function. - * accessed via the #WM_OT_search_operator operator. - */ - -#include - -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_texture_types.h" - -#include "BLI_alloca.h" -#include "BLI_ghash.h" -#include "BLI_string.h" -#include "BLI_utildefines.h" - -#include "BLT_translation.h" - -#include "BKE_context.h" -#include "BKE_global.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "UI_interface.h" -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Operator Search Template Implementation - * \{ */ - -static void operator_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) -{ - wmOperatorType *ot = arg2; - - if (ot) { - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL); - } -} - -static void operator_search_update_fn(const bContext *C, - void *UNUSED(arg), - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - GHashIterator iter; - - /* Prepare BLI_string_all_words_matched. */ - const size_t str_len = strlen(str); - const int words_max = BLI_string_max_possible_word_count(str_len); - int(*words)[2] = BLI_array_alloca(words, words_max); - const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max); - - for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); - BLI_ghashIterator_step(&iter)) { - wmOperatorType *ot = BLI_ghashIterator_getValue(&iter); - const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); - - if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { - continue; - } - - if (BLI_string_all_words_matched(ot_ui_name, str, words, words_len)) { - if (WM_operator_poll((bContext *)C, ot)) { - char name[256]; - const int len = strlen(ot_ui_name); - - /* display name for menu, can hold hotkey */ - BLI_strncpy(name, ot_ui_name, sizeof(name)); - - /* check for hotkey */ - if (len < sizeof(name) - 6) { - if (WM_key_event_operator_string(C, - ot->idname, - WM_OP_EXEC_DEFAULT, - NULL, - true, - &name[len + 1], - sizeof(name) - len - 1)) { - name[len] = UI_SEP_CHAR; - } - } - - if (!UI_search_item_add(items, name, ot, ICON_NONE, 0, 0)) { - break; - } - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Operator Search Template API - * \{ */ - -void UI_but_func_operator_search(uiBut *but) -{ - UI_but_func_search_set(but, - ui_searchbox_create_operator, - operator_search_update_fn, - NULL, - false, - NULL, - operator_search_exec_fn, - NULL); -} - -void uiTemplateOperatorSearch(uiLayout *layout) -{ - uiBlock *block; - uiBut *but; - static char search[256] = ""; - - block = uiLayoutGetBlock(layout); - UI_block_layout_set_current(block, layout); - - but = uiDefSearchBut( - block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, ""); - UI_but_func_operator_search(but); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_template_search_operator.cc b/source/blender/editors/interface/interface_template_search_operator.cc new file mode 100644 index 00000000000..b4320252219 --- /dev/null +++ b/source/blender/editors/interface/interface_template_search_operator.cc @@ -0,0 +1,143 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + * + * Search available operators by scanning all and checking their poll function. + * accessed via the #WM_OT_search_operator operator. + */ + +#include + +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_texture_types.h" + +#include "BLI_alloca.h" +#include "BLI_ghash.h" +#include "BLI_string.h" +#include "BLI_utildefines.h" + +#include "BLT_translation.h" + +#include "BKE_context.h" +#include "BKE_global.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "UI_interface.h" +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Operator Search Template Implementation + * \{ */ + +static void operator_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2) +{ + wmOperatorType *ot = (wmOperatorType *)arg2; + + if (ot) { + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, NULL); + } +} + +static void operator_search_update_fn(const bContext *C, + void *UNUSED(arg), + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + GHashIterator iter; + + /* Prepare BLI_string_all_words_matched. */ + const size_t str_len = strlen(str); + const int words_max = BLI_string_max_possible_word_count(str_len); + int(*words)[2] = BLI_array_alloca(words, words_max); + const int words_len = BLI_string_find_split_words(str, str_len, ' ', words, words_max); + + for (WM_operatortype_iter(&iter); !BLI_ghashIterator_done(&iter); + BLI_ghashIterator_step(&iter)) { + wmOperatorType *ot = (wmOperatorType *)BLI_ghashIterator_getValue(&iter); + const char *ot_ui_name = CTX_IFACE_(ot->translation_context, ot->name); + + if ((ot->flag & OPTYPE_INTERNAL) && (G.debug & G_DEBUG_WM) == 0) { + continue; + } + + if (BLI_string_all_words_matched(ot_ui_name, str, words, words_len)) { + if (WM_operator_poll((bContext *)C, ot)) { + char name[256]; + const int len = strlen(ot_ui_name); + + /* display name for menu, can hold hotkey */ + BLI_strncpy(name, ot_ui_name, sizeof(name)); + + /* check for hotkey */ + if (len < sizeof(name) - 6) { + if (WM_key_event_operator_string(C, + ot->idname, + WM_OP_EXEC_DEFAULT, + NULL, + true, + &name[len + 1], + sizeof(name) - len - 1)) { + name[len] = UI_SEP_CHAR; + } + } + + if (!UI_search_item_add(items, name, ot, ICON_NONE, 0, 0)) { + break; + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator Search Template API + * \{ */ + +void UI_but_func_operator_search(uiBut *but) +{ + UI_but_func_search_set(but, + ui_searchbox_create_operator, + operator_search_update_fn, + NULL, + false, + NULL, + operator_search_exec_fn, + NULL); +} + +void uiTemplateOperatorSearch(uiLayout *layout) +{ + uiBlock *block; + uiBut *but; + static char search[256] = ""; + + block = uiLayoutGetBlock(layout); + UI_block_layout_set_current(block, layout); + + but = uiDefSearchBut( + block, search, 0, ICON_VIEWZOOM, sizeof(search), 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, 0, 0, ""); + UI_but_func_operator_search(but); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c deleted file mode 100644 index 6ca0f196280..00000000000 --- a/source/blender/editors/interface/interface_templates.c +++ /dev/null @@ -1,7352 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_brush_types.h" -#include "DNA_cachefile_types.h" -#include "DNA_constraint_types.h" -#include "DNA_curveprofile_types.h" -#include "DNA_gpencil_modifier_types.h" -#include "DNA_node_types.h" -#include "DNA_object_force_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_shader_fx_types.h" -#include "DNA_texture_types.h" - -#include "BLI_alloca.h" -#include "BLI_fnmatch.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_path_util.h" -#include "BLI_rect.h" -#include "BLI_string.h" -#include "BLI_string_search.h" -#include "BLI_timecode.h" -#include "BLI_utildefines.h" - -#include "BLF_api.h" -#include "BLT_translation.h" - -#include "BKE_action.h" -#include "BKE_colorband.h" -#include "BKE_colortools.h" -#include "BKE_constraint.h" -#include "BKE_context.h" -#include "BKE_curveprofile.h" -#include "BKE_global.h" -#include "BKE_gpencil_modifier.h" -#include "BKE_idprop.h" -#include "BKE_idtype.h" -#include "BKE_layer.h" -#include "BKE_lib_id.h" -#include "BKE_lib_override.h" -#include "BKE_linestyle.h" -#include "BKE_main.h" -#include "BKE_modifier.h" -#include "BKE_object.h" -#include "BKE_packedFile.h" -#include "BKE_particle.h" -#include "BKE_report.h" -#include "BKE_scene.h" -#include "BKE_screen.h" -#include "BKE_shader_fx.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_build.h" - -#include "ED_fileselect.h" -#include "ED_object.h" -#include "ED_render.h" -#include "ED_screen.h" -#include "ED_undo.h" - -#include "RNA_access.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "BLO_readfile.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" -#include "UI_view2d.h" -#include "interface_intern.h" - -#include "PIL_time.h" - -/* we may want to make this optional, disable for now. */ -// #define USE_OP_RESET_BUT - -/* defines for templateID/TemplateSearch */ -#define TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH (UI_UNIT_X * 6) -#define TEMPLATE_SEARCH_TEXTBUT_HEIGHT UI_UNIT_Y - -void UI_template_fix_linking(void) -{ -} - -/* -------------------------------------------------------------------- */ -/** \name Header Template - * \{ */ - -void uiTemplateHeader(uiLayout *layout, bContext *C) -{ - uiBlock *block = uiLayoutAbsoluteBlock(layout); - ED_area_header_switchbutton(C, block, 0); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Search Menu Helpers - * \{ */ - -static int template_search_textbut_width(PointerRNA *ptr, PropertyRNA *name_prop) -{ - char str[UI_MAX_DRAW_STR]; - int buf_len = 0; - - BLI_assert(RNA_property_type(name_prop) == PROP_STRING); - - const char *name = RNA_property_string_get_alloc(ptr, name_prop, str, sizeof(str), &buf_len); - - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - const int margin = UI_UNIT_X * 0.75f; - const int estimated_width = UI_fontstyle_string_width(fstyle, name) + margin; - - if (name != str) { - MEM_freeN((void *)name); - } - - /* Clamp to some min/max width. */ - return CLAMPIS( - estimated_width, TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH, TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH * 3); -} - -static int template_search_textbut_height(void) -{ - return TEMPLATE_SEARCH_TEXTBUT_HEIGHT; -} - -/** - * Add a block button for the search menu for templateID and templateSearch. - */ -static void template_add_button_search_menu(const bContext *C, - uiLayout *layout, - uiBlock *block, - PointerRNA *ptr, - PropertyRNA *prop, - uiBlockCreateFunc block_func, - void *block_argN, - const char *const tip, - const bool use_previews, - const bool editable, - const bool live_icon) -{ - const PointerRNA active_ptr = RNA_property_pointer_get(ptr, prop); - ID *id = (active_ptr.data && RNA_struct_is_ID(active_ptr.type)) ? active_ptr.data : NULL; - const ID *idfrom = ptr->owner_id; - const StructRNA *type = active_ptr.type ? active_ptr.type : RNA_property_pointer_type(ptr, prop); - uiBut *but; - - if (use_previews) { - ARegion *region = CTX_wm_region(C); - /* Ugly tool header exception. */ - const bool use_big_size = (region->regiontype != RGN_TYPE_TOOL_HEADER); - /* Ugly exception for screens here, - * drawing their preview in icon size looks ugly/useless */ - const bool use_preview_icon = use_big_size || (id && (GS(id->name) != ID_SCR)); - const short width = UI_UNIT_X * (use_big_size ? 6 : 1.6f); - const short height = UI_UNIT_Y * (use_big_size ? 6 : 1); - uiLayout *col = NULL; - - if (use_big_size) { - /* Assume column layout here. To be more correct, we should check if the layout passed to - * template_id is a column one, but this should work well in practice. */ - col = uiLayoutColumn(layout, true); - } - - but = uiDefBlockButN(block, block_func, block_argN, "", 0, 0, width, height, tip); - if (use_preview_icon) { - const int icon = id ? ui_id_icon_get(C, id, use_big_size) : RNA_struct_ui_icon(type); - ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); - } - else { - ui_def_but_icon(but, RNA_struct_ui_icon(type), UI_HAS_ICON); - UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); - } - - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - if (use_big_size) { - uiLayoutRow(col ? col : layout, true); - } - } - else { - but = uiDefBlockButN(block, block_func, block_argN, "", 0, 0, UI_UNIT_X * 1.6, UI_UNIT_Y, tip); - - if (live_icon) { - const int icon = id ? ui_id_icon_get(C, id, false) : RNA_struct_ui_icon(type); - ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); - } - else { - ui_def_but_icon(but, RNA_struct_ui_icon(type), UI_HAS_ICON); - } - if (id) { - /* default dragging of icon for id browse buttons */ - UI_but_drag_set_id(but, id); - } - UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); - - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } -} - -static uiBlock *template_common_search_menu(const bContext *C, - ARegion *region, - uiButSearchUpdateFn search_update_fn, - void *search_arg, - uiButHandleFunc search_exec_fn, - void *active_item, - uiButSearchTooltipFn item_tooltip_fn, - const int preview_rows, - const int preview_cols, - float scale) -{ - static char search[256]; - wmWindow *win = CTX_wm_window(C); - uiBut *but; - - /* clear initial search string, then all items show */ - search[0] = 0; - - uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); - UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_SEARCH_MENU); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - - /* preview thumbnails */ - if (preview_rows > 0 && preview_cols > 0) { - const int w = 4 * U.widget_unit * preview_cols * scale; - const int h = 5 * U.widget_unit * preview_rows * scale; - - /* fake button, it holds space for search items */ - uiDefBut(block, UI_BTYPE_LABEL, 0, "", 10, 26, w, h, NULL, 0, 0, 0, 0, NULL); - - but = uiDefSearchBut(block, - search, - 0, - ICON_VIEWZOOM, - sizeof(search), - 10, - 0, - w, - UI_UNIT_Y, - preview_rows, - preview_cols, - ""); - } - /* list view */ - else { - const int searchbox_width = UI_searchbox_size_x(); - const int searchbox_height = UI_searchbox_size_y(); - - /* fake button, it holds space for search items */ - uiDefBut(block, - UI_BTYPE_LABEL, - 0, - "", - 10, - 15, - searchbox_width, - searchbox_height, - NULL, - 0, - 0, - 0, - 0, - NULL); - but = uiDefSearchBut(block, - search, - 0, - ICON_VIEWZOOM, - sizeof(search), - 10, - 0, - searchbox_width, - UI_UNIT_Y - 1, - 0, - 0, - ""); - } - UI_but_func_search_set(but, - ui_searchbox_create_generic, - search_update_fn, - search_arg, - false, - NULL, - search_exec_fn, - active_item); - UI_but_func_search_set_tooltip(but, item_tooltip_fn); - - UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); - UI_block_direction_set(block, UI_DIR_DOWN); - - /* give search-field focus */ - UI_but_focus_on_enter_event(win, but); - /* this type of search menu requires undo */ - but->flag |= UI_BUT_UNDO; - - return block; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Search Callbacks - * \{ */ - -typedef struct TemplateID { - PointerRNA ptr; - PropertyRNA *prop; - - ListBase *idlb; - short idcode; - short filter; - int prv_rows, prv_cols; - bool preview; - float scale; -} TemplateID; - -/* Search browse menu, assign */ -static void template_ID_set_property_exec_fn(bContext *C, void *arg_template, void *item) -{ - TemplateID *template_ui = (TemplateID *)arg_template; - - /* ID */ - if (item) { - PointerRNA idptr; - - RNA_id_pointer_create(item, &idptr); - RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); - RNA_property_update(C, &template_ui->ptr, template_ui->prop); - } -} - -static bool id_search_allows_id(TemplateID *template_ui, const int flag, ID *id, const char *query) -{ - ID *id_from = template_ui->ptr.owner_id; - - /* Do self check. */ - if ((flag & PROP_ID_SELF_CHECK) && id == id_from) { - return false; - } - - /* Use filter. */ - if (RNA_property_type(template_ui->prop) == PROP_POINTER) { - PointerRNA ptr; - RNA_id_pointer_create(id, &ptr); - if (RNA_property_pointer_poll(&template_ui->ptr, template_ui->prop, &ptr) == 0) { - return false; - } - } - - /* Hide dot prefixed data-blocks, but only if filter does not force them visible. */ - if (U.uiflag & USER_HIDE_DOT) { - if ((id->name[2] == '.') && (query[0] != '.')) { - return false; - } - } - - return true; -} - -static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchItems *items, ID *id) -{ - /* +1 is needed because BKE_id_ui_prefix used 3 letter prefix - * followed by ID_NAME-2 characters from id->name - */ - char name_ui[MAX_ID_FULL_NAME_UI]; - int iconid = ui_id_icon_get(C, id, template_ui->preview); - const bool use_lib_prefix = template_ui->preview || iconid; - const bool has_sep_char = (id->lib != NULL); - - /* When using previews, the library hint (linked, overridden, missing) is added with a - * character prefix, otherwise we can use a icon. */ - int name_prefix_offset; - BKE_id_full_name_ui_prefix_get(name_ui, id, use_lib_prefix, UI_SEP_CHAR, &name_prefix_offset); - if (!use_lib_prefix) { - iconid = UI_icon_from_library(id); - } - - if (!UI_search_item_add(items, - name_ui, - id, - iconid, - has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0, - name_prefix_offset)) { - return false; - } - - return true; -} - -/* ID Search browse menu, do the search */ -static void id_search_cb(const bContext *C, - void *arg_template, - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - TemplateID *template_ui = (TemplateID *)arg_template; - ListBase *lb = template_ui->idlb; - const int flag = RNA_property_flag(template_ui->prop); - - StringSearch *search = BLI_string_search_new(); - - /* ID listbase */ - LISTBASE_FOREACH (ID *, id, lb) { - if (id_search_allows_id(template_ui, flag, id, str)) { - BLI_string_search_add(search, id->name + 2, id); - } - } - - ID **filtered_ids; - const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_ids); - - for (int i = 0; i < filtered_amount; i++) { - if (!id_search_add(C, template_ui, items, filtered_ids[i])) { - break; - } - } - - MEM_freeN(filtered_ids); - BLI_string_search_free(search); -} - -/** - * Use id tags for filtering. - */ -static void id_search_cb_tagged(const bContext *C, - void *arg_template, - const char *str, - uiSearchItems *items) -{ - TemplateID *template_ui = (TemplateID *)arg_template; - ListBase *lb = template_ui->idlb; - const int flag = RNA_property_flag(template_ui->prop); - - StringSearch *search = BLI_string_search_new(); - - /* ID listbase */ - LISTBASE_FOREACH (ID *, id, lb) { - if (id->tag & LIB_TAG_DOIT) { - if (id_search_allows_id(template_ui, flag, id, str)) { - BLI_string_search_add(search, id->name + 2, id); - } - id->tag &= ~LIB_TAG_DOIT; - } - } - - ID **filtered_ids; - const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_ids); - - for (int i = 0; i < filtered_amount; i++) { - if (!id_search_add(C, template_ui, items, filtered_ids[i])) { - break; - } - } - - MEM_freeN(filtered_ids); - BLI_string_search_free(search); -} - -/** - * A version of 'id_search_cb' that lists scene objects. - */ -static void id_search_cb_objects_from_scene(const bContext *C, - void *arg_template, - const char *str, - uiSearchItems *items, - const bool UNUSED(is_first)) -{ - TemplateID *template_ui = (TemplateID *)arg_template; - ListBase *lb = template_ui->idlb; - Scene *scene = NULL; - ID *id_from = template_ui->ptr.owner_id; - - if (id_from && GS(id_from->name) == ID_SCE) { - scene = (Scene *)id_from; - } - else { - scene = CTX_data_scene(C); - } - - BKE_main_id_flag_listbase(lb, LIB_TAG_DOIT, false); - - FOREACH_SCENE_OBJECT_BEGIN (scene, ob_iter) { - ob_iter->id.tag |= LIB_TAG_DOIT; - } - FOREACH_SCENE_OBJECT_END; - id_search_cb_tagged(C, arg_template, str, items); -} - -static ARegion *template_ID_search_menu_item_tooltip( - bContext *C, ARegion *region, const rcti *item_rect, void *arg, void *active) -{ - TemplateID *template_ui = arg; - ID *active_id = active; - StructRNA *type = RNA_property_pointer_type(&template_ui->ptr, template_ui->prop); - - uiSearchItemTooltipData tooltip_data = {0}; - - tooltip_data.name = active_id->name + 2; - BLI_snprintf(tooltip_data.description, - sizeof(tooltip_data.description), - TIP_("Choose %s data-block to be assigned to this user"), - RNA_struct_ui_name(type)); - if (ID_IS_LINKED(active_id)) { - BLI_snprintf(tooltip_data.hint, - sizeof(tooltip_data.hint), - TIP_("Source library: %s\n%s"), - active_id->lib->id.name + 2, - active_id->lib->filepath); - } - - return UI_tooltip_create_from_search_item_generic(C, region, item_rect, &tooltip_data); -} - -/* ID Search browse menu, open */ -static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem) -{ - static TemplateID template_ui; - PointerRNA active_item_ptr; - void (*id_search_update_fn)( - const bContext *, void *, const char *, uiSearchItems *, const bool) = id_search_cb; - - /* arg_litem is malloced, can be freed by parent button */ - template_ui = *((TemplateID *)arg_litem); - active_item_ptr = RNA_property_pointer_get(&template_ui.ptr, template_ui.prop); - - if (template_ui.filter) { - /* Currently only used for objects. */ - if (template_ui.idcode == ID_OB) { - if (template_ui.filter == UI_TEMPLATE_ID_FILTER_AVAILABLE) { - id_search_update_fn = id_search_cb_objects_from_scene; - } - } - } - - return template_common_search_menu(C, - region, - id_search_update_fn, - &template_ui, - template_ID_set_property_exec_fn, - active_item_ptr.data, - template_ID_search_menu_item_tooltip, - template_ui.prv_rows, - template_ui.prv_cols, - template_ui.scale); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ID Template - * \{ */ - -/* This is for browsing and editing the ID-blocks used */ - -/* for new/open operators */ -void UI_context_active_but_prop_get_templateID(bContext *C, - PointerRNA *r_ptr, - PropertyRNA **r_prop) -{ - uiBut *but = UI_context_active_but_get(C); - - memset(r_ptr, 0, sizeof(*r_ptr)); - *r_prop = NULL; - - if (but && but->func_argN) { - TemplateID *template_ui = but->func_argN; - *r_ptr = template_ui->ptr; - *r_prop = template_ui->prop; - } -} - -static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) -{ - TemplateID *template_ui = (TemplateID *)arg_litem; - PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); - ID *id = idptr.data; - const int event = POINTER_AS_INT(arg_event); - const char *undo_push_label = NULL; - - switch (event) { - case UI_ID_BROWSE: - case UI_ID_PIN: - RNA_warning("warning, id event %d shouldn't come here", event); - break; - case UI_ID_OPEN: - case UI_ID_ADD_NEW: - /* these call UI_context_active_but_prop_get_templateID */ - break; - case UI_ID_DELETE: - memset(&idptr, 0, sizeof(idptr)); - RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); - RNA_property_update(C, &template_ui->ptr, template_ui->prop); - - if (id && CTX_wm_window(C)->eventstate->shift) { - /* only way to force-remove data (on save) */ - id_us_clear_real(id); - id_fake_user_clear(id); - id->us = 0; - undo_push_label = "Delete Data-Block"; - } - - break; - case UI_ID_FAKE_USER: - if (id) { - if (id->flag & LIB_FAKEUSER) { - id_us_plus(id); - } - else { - id_us_min(id); - } - undo_push_label = "Fake User"; - } - else { - return; - } - break; - case UI_ID_LOCAL: - if (id) { - Main *bmain = CTX_data_main(C); - if (CTX_wm_window(C)->eventstate->shift) { - if (ID_IS_OVERRIDABLE_LIBRARY(id)) { - /* Only remap that specific ID usage to overriding local data-block. */ - ID *override_id = BKE_lib_override_library_create_from_id(bmain, id, false); - if (override_id != NULL) { - BKE_main_id_clear_newpoins(bmain); - - if (GS(override_id->name) == ID_OB) { - Scene *scene = CTX_data_scene(C); - if (!BKE_collection_has_object_recursive(scene->master_collection, - (Object *)override_id)) { - BKE_collection_object_add_from( - bmain, scene, (Object *)id, (Object *)override_id); - } - } - - /* Assign new pointer, takes care of updates/notifiers */ - RNA_id_pointer_create(override_id, &idptr); - } - undo_push_label = "Make Library Override"; - } - } - else { - if (BKE_lib_id_make_local(bmain, id, false, 0)) { - BKE_main_id_clear_newpoins(bmain); - - /* reassign to get get proper updates/notifiers */ - idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); - undo_push_label = "Make Local"; - } - } - if (undo_push_label != NULL) { - RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); - RNA_property_update(C, &template_ui->ptr, template_ui->prop); - } - } - break; - case UI_ID_OVERRIDE: - if (id && ID_IS_OVERRIDE_LIBRARY(id)) { - BKE_lib_override_library_free(&id->override_library, true); - /* reassign to get get proper updates/notifiers */ - idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); - RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); - RNA_property_update(C, &template_ui->ptr, template_ui->prop); - undo_push_label = "Override Data-Block"; - } - break; - case UI_ID_ALONE: - if (id) { - const bool do_scene_obj = ((GS(id->name) == ID_OB) && - (template_ui->ptr.type == &RNA_LayerObjects)); - - /* make copy */ - if (do_scene_obj) { - Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); - ED_object_single_user(bmain, scene, (struct Object *)id); - WM_event_add_notifier(C, NC_WINDOW, NULL); - DEG_relations_tag_update(bmain); - } - else { - Main *bmain = CTX_data_main(C); - id_single_user(C, id, &template_ui->ptr, template_ui->prop); - DEG_relations_tag_update(bmain); - } - undo_push_label = "Make Single User"; - } - break; -#if 0 - case UI_ID_AUTO_NAME: - break; -#endif - } - - if (undo_push_label != NULL) { - ED_undo_push(C, undo_push_label); - } -} - -static const char *template_id_browse_tip(const StructRNA *type) -{ - if (type) { - switch ((ID_Type)RNA_type_to_ID_code(type)) { - case ID_SCE: - return N_("Browse Scene to be linked"); - case ID_OB: - return N_("Browse Object to be linked"); - case ID_ME: - return N_("Browse Mesh Data to be linked"); - case ID_CU: - return N_("Browse Curve Data to be linked"); - case ID_MB: - return N_("Browse Metaball Data to be linked"); - case ID_MA: - return N_("Browse Material to be linked"); - case ID_TE: - return N_("Browse Texture to be linked"); - case ID_IM: - return N_("Browse Image to be linked"); - case ID_LS: - return N_("Browse Line Style Data to be linked"); - case ID_LT: - return N_("Browse Lattice Data to be linked"); - case ID_LA: - return N_("Browse Light Data to be linked"); - case ID_CA: - return N_("Browse Camera Data to be linked"); - case ID_WO: - return N_("Browse World Settings to be linked"); - case ID_SCR: - return N_("Choose Screen layout"); - case ID_TXT: - return N_("Browse Text to be linked"); - case ID_SPK: - return N_("Browse Speaker Data to be linked"); - case ID_SO: - return N_("Browse Sound to be linked"); - case ID_AR: - return N_("Browse Armature data to be linked"); - case ID_AC: - return N_("Browse Action to be linked"); - case ID_NT: - return N_("Browse Node Tree to be linked"); - case ID_BR: - return N_("Browse Brush to be linked"); - case ID_PA: - return N_("Browse Particle Settings to be linked"); - case ID_GD: - return N_("Browse Grease Pencil Data to be linked"); - case ID_MC: - return N_("Browse Movie Clip to be linked"); - case ID_MSK: - return N_("Browse Mask to be linked"); - case ID_PAL: - return N_("Browse Palette Data to be linked"); - case ID_PC: - return N_("Browse Paint Curve Data to be linked"); - case ID_CF: - return N_("Browse Cache Files to be linked"); - case ID_WS: - return N_("Browse Workspace to be linked"); - case ID_LP: - return N_("Browse LightProbe to be linked"); - case ID_HA: - return N_("Browse Hair Data to be linked"); - case ID_PT: - return N_("Browse Point Cloud Data to be linked"); - case ID_VO: - return N_("Browse Volume Data to be linked"); - case ID_SIM: - return N_("Browse Simulation to be linked"); - - /* Use generic text. */ - case ID_LI: - case ID_IP: - case ID_KE: - case ID_VF: - case ID_GR: - case ID_WM: - break; - } - } - return N_("Browse ID data to be linked"); -} - -/** - * \return a type-based i18n context, needed e.g. by "New" button. - * In most languages, this adjective takes different form based on gender of type name... - */ -#ifdef WITH_INTERNATIONAL -static const char *template_id_context(StructRNA *type) -{ - if (type) { - return BKE_idtype_idcode_to_translation_context(RNA_type_to_ID_code(type)); - } - return BLT_I18NCONTEXT_DEFAULT; -} -#else -# define template_id_context(type) 0 -#endif - -static uiBut *template_id_def_new_but(uiBlock *block, - const ID *id, - const TemplateID *template_ui, - StructRNA *type, - const char *const newop, - const bool editable, - const bool id_open, - const bool use_tab_but, - int but_height) -{ - ID *idfrom = template_ui->ptr.owner_id; - uiBut *but; - const int w = id ? UI_UNIT_X : id_open ? UI_UNIT_X * 3 : UI_UNIT_X * 6; - const int but_type = use_tab_but ? UI_BTYPE_TAB : UI_BTYPE_BUT; - - /* i18n markup, does nothing! */ - BLT_I18N_MSGID_MULTI_CTXT("New", - BLT_I18NCONTEXT_DEFAULT, - BLT_I18NCONTEXT_ID_SCENE, - BLT_I18NCONTEXT_ID_OBJECT, - BLT_I18NCONTEXT_ID_MESH, - BLT_I18NCONTEXT_ID_CURVE, - BLT_I18NCONTEXT_ID_METABALL, - BLT_I18NCONTEXT_ID_MATERIAL, - BLT_I18NCONTEXT_ID_TEXTURE, - BLT_I18NCONTEXT_ID_IMAGE, - BLT_I18NCONTEXT_ID_LATTICE, - BLT_I18NCONTEXT_ID_LIGHT, - BLT_I18NCONTEXT_ID_CAMERA, - BLT_I18NCONTEXT_ID_WORLD, - BLT_I18NCONTEXT_ID_SCREEN, - BLT_I18NCONTEXT_ID_TEXT, ); - BLT_I18N_MSGID_MULTI_CTXT("New", - BLT_I18NCONTEXT_ID_SPEAKER, - BLT_I18NCONTEXT_ID_SOUND, - BLT_I18NCONTEXT_ID_ARMATURE, - BLT_I18NCONTEXT_ID_ACTION, - BLT_I18NCONTEXT_ID_NODETREE, - BLT_I18NCONTEXT_ID_BRUSH, - BLT_I18NCONTEXT_ID_PARTICLESETTINGS, - BLT_I18NCONTEXT_ID_GPENCIL, - BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, - BLT_I18NCONTEXT_ID_WORKSPACE, - BLT_I18NCONTEXT_ID_LIGHTPROBE, - BLT_I18NCONTEXT_ID_HAIR, - BLT_I18NCONTEXT_ID_POINTCLOUD, - BLT_I18NCONTEXT_ID_VOLUME, - BLT_I18NCONTEXT_ID_SIMULATION, ); - /* Note: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters, - * check the definition to see if a new call must be added when the limit - * is exceeded. */ - - if (newop) { - but = uiDefIconTextButO(block, - but_type, - newop, - WM_OP_INVOKE_DEFAULT, - (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD, - (id) ? "" : CTX_IFACE_(template_id_context(type), "New"), - 0, - 0, - w, - but_height, - NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); - } - else { - but = uiDefIconTextBut(block, - but_type, - 0, - (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD, - (id) ? "" : CTX_IFACE_(template_id_context(type), "New"), - 0, - 0, - w, - but_height, - NULL, - 0, - 0, - 0, - 0, - NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); - } - - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - -#ifndef WITH_INTERNATIONAL - UNUSED_VARS(type); -#endif - - return but; -} - -static void template_ID(const bContext *C, - uiLayout *layout, - TemplateID *template_ui, - StructRNA *type, - int flag, - const char *newop, - const char *openop, - const char *unlinkop, - const char *text, - const bool live_icon, - const bool hide_buttons) -{ - uiBut *but; - const bool editable = RNA_property_editable(&template_ui->ptr, template_ui->prop); - const bool use_previews = template_ui->preview = (flag & UI_ID_PREVIEWS) != 0; - - PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); - ID *id = idptr.data; - ID *idfrom = template_ui->ptr.owner_id; - // lb = template_ui->idlb; - - /* Allow operators to take the ID from context. */ - uiLayoutSetContextPointer(layout, "id", &idptr); - - uiBlock *block = uiLayoutGetBlock(layout); - UI_block_align_begin(block); - - if (idptr.type) { - type = idptr.type; - } - - if (text) { - /* Add label respecting the separated layout property split state. */ - uiItemL_respect_property_split(layout, text, ICON_NONE); - } - - if (flag & UI_ID_BROWSE) { - template_add_button_search_menu(C, - layout, - block, - &template_ui->ptr, - template_ui->prop, - id_search_menu, - MEM_dupallocN(template_ui), - TIP_(template_id_browse_tip(type)), - use_previews, - editable, - live_icon); - } - - /* text button with name */ - if (id) { - char name[UI_MAX_NAME_STR]; - const bool user_alert = (id->us <= 0); - - const int width = template_search_textbut_width(&idptr, - RNA_struct_find_property(&idptr, "name")); - const int height = template_search_textbut_height(); - - // text_idbutton(id, name); - name[0] = '\0'; - but = uiDefButR(block, - UI_BTYPE_TEXT, - 0, - name, - 0, - 0, - width, - height, - &idptr, - "name", - -1, - 0, - 0, - -1, - -1, - RNA_struct_ui_description(type)); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_RENAME)); - if (user_alert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - if (id->lib) { - if (id->tag & LIB_TAG_INDIRECT) { - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_INDIRECT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Indirect library data-block, cannot change")); - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - else { - const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) || - (idfrom && idfrom->lib)); - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_DIRECT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Direct linked library data-block, click to make local, " - "Shift + Click to create a library override")); - if (disabled) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - else { - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_LOCAL)); - } - } - } - else if (ID_IS_OVERRIDE_LIBRARY(id)) { - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_OVERRIDE, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Library override of linked data-block, click to make fully local")); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE)); - } - - if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) { - char numstr[32]; - short numstr_len; - - numstr_len = BLI_snprintf(numstr, sizeof(numstr), "%d", ID_REAL_USERS(id)); - - but = uiDefBut( - block, - UI_BTYPE_BUT, - 0, - numstr, - 0, - 0, - numstr_len * 0.2f * UI_UNIT_X + UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Display number of users of this data (click to make a single-user copy)")); - but->flag |= UI_BUT_UNDO; - - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ALONE)); - if ((!BKE_id_copy_is_allowed(id)) || (idfrom && idfrom->lib) || (!editable) || - /* object in editmode - don't change data */ - (idfrom && GS(idfrom->name) == ID_OB && (((Object *)idfrom)->mode & OB_MODE_EDIT))) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } - - if (user_alert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - if (id->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS)) && - (hide_buttons == false)) { - uiDefIconButR(block, - UI_BTYPE_ICON_TOGGLE, - 0, - ICON_FAKE_USER_OFF, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - &idptr, - "use_fake_user", - -1, - 0, - 0, - -1, - -1, - NULL); - } - } - - if ((flag & UI_ID_ADD_NEW) && (hide_buttons == false)) { - template_id_def_new_but( - block, id, template_ui, type, newop, editable, flag & UI_ID_OPEN, false, UI_UNIT_X); - } - - /* Due to space limit in UI - skip the "open" icon for packed data, and allow to unpack. - * Only for images, sound and fonts */ - if (id && BKE_packedfile_id_check(id)) { - but = uiDefIconButO(block, - UI_BTYPE_BUT, - "FILE_OT_unpack_item", - WM_OP_INVOKE_REGION_WIN, - ICON_PACKAGE, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - TIP_("Packed File, click to unpack")); - UI_but_operator_ptr_get(but); - - RNA_string_set(but->opptr, "id_name", id->name + 2); - RNA_int_set(but->opptr, "id_type", GS(id->name)); - } - else if (flag & UI_ID_OPEN) { - const int w = id ? UI_UNIT_X : (flag & UI_ID_ADD_NEW) ? UI_UNIT_X * 3 : UI_UNIT_X * 6; - - if (openop) { - but = uiDefIconTextButO(block, - UI_BTYPE_BUT, - openop, - WM_OP_INVOKE_DEFAULT, - ICON_FILEBROWSER, - (id) ? "" : IFACE_("Open"), - 0, - 0, - w, - UI_UNIT_Y, - NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); - } - else { - but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_FILEBROWSER, - (id) ? "" : IFACE_("Open"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); - } - - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } - - /* delete button */ - /* don't use RNA_property_is_unlink here */ - if (id && (flag & UI_ID_DELETE) && (hide_buttons == false)) { - /* allow unlink if 'unlinkop' is passed, even when 'PROP_NEVER_UNLINK' is set */ - but = NULL; - - if (unlinkop) { - but = uiDefIconButO(block, - UI_BTYPE_BUT, - unlinkop, - WM_OP_INVOKE_DEFAULT, - ICON_X, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - /* so we can access the template from operators, font unlinking needs this */ - UI_but_funcN_set(but, NULL, MEM_dupallocN(template_ui), NULL); - } - else { - if ((RNA_property_flag(template_ui->prop) & PROP_NEVER_UNLINK) == 0) { - but = uiDefIconBut( - block, - UI_BTYPE_BUT, - 0, - ICON_X, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Unlink data-block " - "(Shift + Click to set users to zero, data will then not be saved)")); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_DELETE)); - - if (RNA_property_flag(template_ui->prop) & PROP_NEVER_NULL) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } - } - - if (but) { - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } - } - - if (template_ui->idcode == ID_TE) { - uiTemplateTextureShow(layout, C, &template_ui->ptr, template_ui->prop); - } - UI_block_align_end(block); -} - -ID *UI_context_active_but_get_tab_ID(bContext *C) -{ - uiBut *but = UI_context_active_but_get(C); - - if (but && but->type == UI_BTYPE_TAB) { - return but->custom_data; - } - return NULL; -} - -static void template_ID_tabs(const bContext *C, - uiLayout *layout, - TemplateID *template, - StructRNA *type, - int flag, - const char *newop, - const char *menu) -{ - const ARegion *region = CTX_wm_region(C); - const PointerRNA active_ptr = RNA_property_pointer_get(&template->ptr, template->prop); - MenuType *mt = menu ? WM_menutype_find(menu, false) : NULL; - - const int but_align = ui_but_align_opposite_to_area_align_get(region); - const int but_height = UI_UNIT_Y * 1.1; - - uiBlock *block = uiLayoutGetBlock(layout); - const uiStyle *style = UI_style_get_dpi(); - - ListBase ordered; - BKE_id_ordered_list(&ordered, template->idlb); - - LISTBASE_FOREACH (LinkData *, link, &ordered) { - ID *id = link->data; - const int name_width = UI_fontstyle_string_width(&style->widget, id->name + 2); - const int but_width = name_width + UI_UNIT_X; - - uiButTab *tab = (uiButTab *)uiDefButR_prop(block, - UI_BTYPE_TAB, - 0, - id->name + 2, - 0, - 0, - but_width, - but_height, - &template->ptr, - template->prop, - 0, - 0.0f, - sizeof(id->name) - 2, - 0.0f, - 0.0f, - ""); - UI_but_funcN_set(&tab->but, template_ID_set_property_exec_fn, MEM_dupallocN(template), id); - tab->but.custom_data = (void *)id; - tab->but.dragpoin = id; - tab->menu = mt; - - UI_but_drawflag_enable(&tab->but, but_align); - } - - BLI_freelistN(&ordered); - - if (flag & UI_ID_ADD_NEW) { - const bool editable = RNA_property_editable(&template->ptr, template->prop); - uiBut *but; - - if (active_ptr.type) { - type = active_ptr.type; - } - - but = template_id_def_new_but(block, - active_ptr.data, - template, - type, - newop, - editable, - flag & UI_ID_OPEN, - true, - but_height); - UI_but_drawflag_enable(but, but_align); - } -} - -static void ui_template_id(uiLayout *layout, - const bContext *C, - PointerRNA *ptr, - const char *propname, - const char *newop, - const char *openop, - const char *unlinkop, - /* Only respected by tabs (use_tabs). */ - const char *menu, - const char *text, - int flag, - int prv_rows, - int prv_cols, - int filter, - bool use_tabs, - float scale, - const bool live_icon, - const bool hide_buttons) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_POINTER) { - RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - TemplateID *template_ui = MEM_callocN(sizeof(TemplateID), "TemplateID"); - template_ui->ptr = *ptr; - template_ui->prop = prop; - template_ui->prv_rows = prv_rows; - template_ui->prv_cols = prv_cols; - template_ui->scale = scale; - - if ((flag & UI_ID_PIN) == 0) { - template_ui->filter = filter; - } - else { - template_ui->filter = 0; - } - - if (newop) { - flag |= UI_ID_ADD_NEW; - } - if (openop) { - flag |= UI_ID_OPEN; - } - - StructRNA *type = RNA_property_pointer_type(ptr, prop); - short idcode = RNA_type_to_ID_code(type); - template_ui->idcode = idcode; - template_ui->idlb = which_libbase(CTX_data_main(C), idcode); - - /* create UI elements for this template - * - template_ID makes a copy of the template data and assigns it to the relevant buttons - */ - if (template_ui->idlb) { - if (use_tabs) { - layout = uiLayoutRow(layout, true); - template_ID_tabs(C, layout, template_ui, type, flag, newop, menu); - } - else { - layout = uiLayoutRow(layout, true); - template_ID(C, - layout, - template_ui, - type, - flag, - newop, - openop, - unlinkop, - text, - live_icon, - hide_buttons); - } - } - - MEM_freeN(template_ui); -} - -void uiTemplateID(uiLayout *layout, - const bContext *C, - PointerRNA *ptr, - const char *propname, - const char *newop, - const char *openop, - const char *unlinkop, - int filter, - const bool live_icon, - const char *text) -{ - ui_template_id(layout, - C, - ptr, - propname, - newop, - openop, - unlinkop, - NULL, - text, - UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE, - 0, - 0, - filter, - false, - 1.0f, - live_icon, - false); -} - -void uiTemplateIDBrowse(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - const char *propname, - const char *newop, - const char *openop, - const char *unlinkop, - int filter, - const char *text) -{ - ui_template_id(layout, - C, - ptr, - propname, - newop, - openop, - unlinkop, - NULL, - text, - UI_ID_BROWSE | UI_ID_RENAME, - 0, - 0, - filter, - false, - 1.0f, - false, - false); -} - -void uiTemplateIDPreview(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - const char *propname, - const char *newop, - const char *openop, - const char *unlinkop, - int rows, - int cols, - int filter, - const bool hide_buttons) -{ - ui_template_id(layout, - C, - ptr, - propname, - newop, - openop, - unlinkop, - NULL, - NULL, - UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE | UI_ID_PREVIEWS, - rows, - cols, - filter, - false, - 1.0f, - false, - hide_buttons); -} - -void uiTemplateGpencilColorPreview(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - const char *propname, - int rows, - int cols, - float scale, - int filter) -{ - ui_template_id(layout, - C, - ptr, - propname, - NULL, - NULL, - NULL, - NULL, - NULL, - UI_ID_BROWSE | UI_ID_PREVIEWS | UI_ID_DELETE, - rows, - cols, - filter, - false, - scale < 0.5f ? 0.5f : scale, - false, - false); -} - -/** - * Version of #uiTemplateID using tabs. - */ -void uiTemplateIDTabs(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - const char *propname, - const char *newop, - const char *menu, - int filter) -{ - ui_template_id(layout, - C, - ptr, - propname, - newop, - NULL, - NULL, - menu, - NULL, - UI_ID_BROWSE | UI_ID_RENAME, - 0, - 0, - filter, - true, - 1.0f, - false, - false); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ID Chooser Template - * \{ */ - -/** - * This is for selecting the type of ID-block to use, - * and then from the relevant type choosing the block to use. - * - * \param propname: property identifier for property that ID-pointer gets stored to. - * \param proptypename: property identifier for property - * used to determine the type of ID-pointer that can be used. - */ -void uiTemplateAnyID(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - const char *proptypename, - const char *text) -{ - /* get properties... */ - PropertyRNA *propID = RNA_struct_find_property(ptr, propname); - PropertyRNA *propType = RNA_struct_find_property(ptr, proptypename); - - if (!propID || RNA_property_type(propID) != PROP_POINTER) { - RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - if (!propType || RNA_property_type(propType) != PROP_ENUM) { - RNA_warning( - "pointer-type property not found: %s.%s", RNA_struct_identifier(ptr->type), proptypename); - return; - } - - /* Start drawing UI Elements using standard defines */ - - /* NOTE: split amount here needs to be synced with normal labels */ - uiLayout *split = uiLayoutSplit(layout, 0.33f, false); - - /* FIRST PART ................................................ */ - uiLayout *row = uiLayoutRow(split, false); - - /* Label - either use the provided text, or will become "ID-Block:" */ - if (text) { - if (text[0]) { - uiItemL(row, text, ICON_NONE); - } - } - else { - uiItemL(row, IFACE_("ID-Block:"), ICON_NONE); - } - - /* SECOND PART ................................................ */ - row = uiLayoutRow(split, true); - - /* ID-Type Selector - just have a menu of icons */ - - /* HACK: special group just for the enum, - * otherwise we get ugly layout with text included too... */ - uiLayout *sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); - - uiItemFullR(sub, ptr, propType, 0, 0, UI_ITEM_R_ICON_ONLY, "", ICON_NONE); - - /* ID-Block Selector - just use pointer widget... */ - - /* HACK: special group to counteract the effects of the previous enum, - * which now pushes everything too far right. */ - sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND); - - uiItemFullR(sub, ptr, propID, 0, 0, 0, "", ICON_NONE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Search Template - * \{ */ - -typedef struct TemplateSearch { - uiRNACollectionSearch search_data; - - bool use_previews; - int preview_rows, preview_cols; -} TemplateSearch; - -static void template_search_exec_fn(bContext *C, void *arg_template, void *item) -{ - TemplateSearch *template_search = arg_template; - uiRNACollectionSearch *coll_search = &template_search->search_data; - StructRNA *type = RNA_property_pointer_type(&coll_search->target_ptr, coll_search->target_prop); - PointerRNA item_ptr; - - RNA_pointer_create(NULL, type, item, &item_ptr); - RNA_property_pointer_set(&coll_search->target_ptr, coll_search->target_prop, item_ptr, NULL); - RNA_property_update(C, &coll_search->target_ptr, coll_search->target_prop); -} - -static uiBlock *template_search_menu(bContext *C, ARegion *region, void *arg_template) -{ - static TemplateSearch template_search; - - /* arg_template is malloced, can be freed by parent button */ - template_search = *((TemplateSearch *)arg_template); - PointerRNA active_ptr = RNA_property_pointer_get(&template_search.search_data.target_ptr, - template_search.search_data.target_prop); - - return template_common_search_menu(C, - region, - ui_rna_collection_search_update_fn, - &template_search, - template_search_exec_fn, - active_ptr.data, - NULL, - template_search.preview_rows, - template_search.preview_cols, - 1.0f); -} - -static void template_search_add_button_searchmenu(const bContext *C, - uiLayout *layout, - uiBlock *block, - TemplateSearch *template_search, - const bool editable, - const bool live_icon) -{ - const char *ui_description = RNA_property_ui_description( - template_search->search_data.target_prop); - - template_add_button_search_menu(C, - layout, - block, - &template_search->search_data.target_ptr, - template_search->search_data.target_prop, - template_search_menu, - MEM_dupallocN(template_search), - ui_description, - template_search->use_previews, - editable, - live_icon); -} - -static void template_search_add_button_name(uiBlock *block, - PointerRNA *active_ptr, - const StructRNA *type) -{ - PropertyRNA *name_prop = RNA_struct_name_property(type); - const int width = template_search_textbut_width(active_ptr, name_prop); - const int height = template_search_textbut_height(); - uiDefAutoButR(block, active_ptr, name_prop, 0, "", ICON_NONE, 0, 0, width, height); -} - -static void template_search_add_button_operator(uiBlock *block, - const char *const operator_name, - const int opcontext, - const int icon, - const bool editable) -{ - if (!operator_name) { - return; - } - - uiBut *but = uiDefIconButO( - block, UI_BTYPE_BUT, operator_name, opcontext, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL); - - if (!editable) { - UI_but_drawflag_enable(but, UI_BUT_DISABLED); - } -} - -static void template_search_buttons(const bContext *C, - uiLayout *layout, - TemplateSearch *template_search, - const char *newop, - const char *unlinkop) -{ - uiBlock *block = uiLayoutGetBlock(layout); - uiRNACollectionSearch *search_data = &template_search->search_data; - StructRNA *type = RNA_property_pointer_type(&search_data->target_ptr, search_data->target_prop); - const bool editable = RNA_property_editable(&search_data->target_ptr, search_data->target_prop); - PointerRNA active_ptr = RNA_property_pointer_get(&search_data->target_ptr, - search_data->target_prop); - - if (active_ptr.type) { - /* can only get correct type when there is an active item */ - type = active_ptr.type; - } - - uiLayoutRow(layout, true); - UI_block_align_begin(block); - - template_search_add_button_searchmenu(C, layout, block, template_search, editable, false); - template_search_add_button_name(block, &active_ptr, type); - template_search_add_button_operator( - block, newop, WM_OP_INVOKE_DEFAULT, ICON_DUPLICATE, editable); - template_search_add_button_operator(block, unlinkop, WM_OP_INVOKE_REGION_WIN, ICON_X, editable); - - UI_block_align_end(block); -} - -static PropertyRNA *template_search_get_searchprop(PointerRNA *targetptr, - PropertyRNA *targetprop, - PointerRNA *searchptr, - const char *const searchpropname) -{ - PropertyRNA *searchprop; - - if (searchptr && !searchptr->data) { - searchptr = NULL; - } - - if (!searchptr && !searchpropname) { - /* both NULL means we don't use a custom rna collection to search in */ - } - else if (!searchptr && searchpropname) { - RNA_warning("searchpropname defined (%s) but searchptr is missing", searchpropname); - } - else if (searchptr && !searchpropname) { - RNA_warning("searchptr defined (%s) but searchpropname is missing", - RNA_struct_identifier(searchptr->type)); - } - else if (!(searchprop = RNA_struct_find_property(searchptr, searchpropname))) { - RNA_warning("search collection property not found: %s.%s", - RNA_struct_identifier(searchptr->type), - searchpropname); - } - else if (RNA_property_type(searchprop) != PROP_COLLECTION) { - RNA_warning("search collection property is not a collection type: %s.%s", - RNA_struct_identifier(searchptr->type), - searchpropname); - } - /* check if searchprop has same type as targetprop */ - else if (RNA_property_pointer_type(searchptr, searchprop) != - RNA_property_pointer_type(targetptr, targetprop)) { - RNA_warning("search collection items from %s.%s are not of type %s", - RNA_struct_identifier(searchptr->type), - searchpropname, - RNA_struct_identifier(RNA_property_pointer_type(targetptr, targetprop))); - } - else { - return searchprop; - } - - return NULL; -} - -static TemplateSearch *template_search_setup(PointerRNA *ptr, - const char *const propname, - PointerRNA *searchptr, - const char *const searchpropname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_POINTER) { - RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return NULL; - } - PropertyRNA *searchprop = template_search_get_searchprop(ptr, prop, searchptr, searchpropname); - - TemplateSearch *template_search = MEM_callocN(sizeof(*template_search), __func__); - template_search->search_data.target_ptr = *ptr; - template_search->search_data.target_prop = prop; - template_search->search_data.search_ptr = *searchptr; - template_search->search_data.search_prop = searchprop; - - return template_search; -} - -/** - * Search menu to pick an item from a collection. - * A version of uiTemplateID that works for non-ID types. - */ -void uiTemplateSearch(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - const char *propname, - PointerRNA *searchptr, - const char *searchpropname, - const char *newop, - const char *unlinkop) -{ - TemplateSearch *template_search = template_search_setup( - ptr, propname, searchptr, searchpropname); - if (template_search != NULL) { - template_search_buttons(C, layout, template_search, newop, unlinkop); - MEM_freeN(template_search); - } -} - -void uiTemplateSearchPreview(uiLayout *layout, - bContext *C, - PointerRNA *ptr, - const char *propname, - PointerRNA *searchptr, - const char *searchpropname, - const char *newop, - const char *unlinkop, - const int rows, - const int cols) -{ - TemplateSearch *template_search = template_search_setup( - ptr, propname, searchptr, searchpropname); - - if (template_search != NULL) { - template_search->use_previews = true; - template_search->preview_rows = rows; - template_search->preview_cols = cols; - - template_search_buttons(C, layout, template_search, newop, unlinkop); - - MEM_freeN(template_search); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name RNA Path Builder Template - * \{ */ - -/* ---------- */ - -/** - * This is creating/editing RNA-Paths - * - * - ptr: struct which holds the path property - * - propname: property identifier for property that path gets stored to - * - root_ptr: struct that path gets built from - */ -void uiTemplatePathBuilder(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - PointerRNA *UNUSED(root_ptr), - const char *text) -{ - /* check that properties are valid */ - PropertyRNA *propPath = RNA_struct_find_property(ptr, propname); - if (!propPath || RNA_property_type(propPath) != PROP_STRING) { - RNA_warning("path property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - /* Start drawing UI Elements using standard defines */ - uiLayout *row = uiLayoutRow(layout, true); - - /* Path (existing string) Widget */ - uiItemR(row, ptr, propname, 0, text, ICON_RNA); - - /* TODO: attach something to this to make allow - * searching of nested properties to 'build' the path */ -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Modifiers Template - * - * Template for building the panel layout for the active object's modifiers. - * \{ */ - -static void modifier_panel_id(void *md_link, char *r_name) -{ - ModifierData *md = (ModifierData *)md_link; - BKE_modifier_type_panel_id(md->type, r_name); -} - -void uiTemplateModifiers(uiLayout *UNUSED(layout), bContext *C) -{ - ARegion *region = CTX_wm_region(C); - - Object *ob = ED_object_active_context(C); - ListBase *modifiers = &ob->modifiers; - - const bool panels_match = UI_panel_list_matches_data(region, modifiers, modifier_panel_id); - - if (!panels_match) { - UI_panels_free_instanced(C, region); - ModifierData *md = modifiers->first; - for (int i = 0; md; i++, md = md->next) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - if (mti->panelRegister == NULL) { - continue; - } - - char panel_idname[MAX_NAME]; - modifier_panel_id(md, panel_idname); - - /* Create custom data RNA pointer. */ - PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_Modifier, md, md_ptr); - - UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); - } - } - else { - /* Assuming there's only one group of instanced panels, update the custom data pointers. */ - Panel *panel = region->panels.first; - LISTBASE_FOREACH (ModifierData *, md, modifiers) { - const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); - if (mti->panelRegister == NULL) { - continue; - } - - /* Move to the next instanced panel corresponding to the next modifier. */ - while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { - panel = panel->next; - BLI_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ - } - - PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_Modifier, md, md_ptr); - UI_panel_custom_data_set(panel, md_ptr); - - panel = panel->next; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Constraints Template - * - * Template for building the panel layout for the active object or bone's constraints. - * \{ */ - -/** For building the panel UI for constraints. */ -#define CONSTRAINT_TYPE_PANEL_PREFIX "OBJECT_PT_" -#define CONSTRAINT_BONE_TYPE_PANEL_PREFIX "BONE_PT_" - -/** - * Check if the panel's ID starts with 'BONE', meaning it is a bone constraint. - */ -static bool constraint_panel_is_bone(Panel *panel) -{ - return (panel->panelname[0] == 'B') && (panel->panelname[1] == 'O') && - (panel->panelname[2] == 'N') && (panel->panelname[3] == 'E'); -} - -/** - * Move a constraint to the index it's moved to after a drag and drop. - */ -static void constraint_reorder(bContext *C, Panel *panel, int new_index) -{ - const bool constraint_from_bone = constraint_panel_is_bone(panel); - - PointerRNA *con_ptr = UI_panel_custom_data_get(panel); - bConstraint *con = (bConstraint *)con_ptr->data; - - PointerRNA props_ptr; - wmOperatorType *ot = WM_operatortype_find("CONSTRAINT_OT_move_to_index", false); - WM_operator_properties_create_ptr(&props_ptr, ot); - RNA_string_set(&props_ptr, "constraint", con->name); - RNA_int_set(&props_ptr, "index", new_index); - /* Set owner to #EDIT_CONSTRAINT_OWNER_OBJECT or #EDIT_CONSTRAINT_OWNER_BONE. */ - RNA_enum_set(&props_ptr, "owner", constraint_from_bone ? 1 : 0); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr); - WM_operator_properties_free(&props_ptr); -} - -/** - * Get the expand flag from the active constraint to use for the panel. - */ -static short get_constraint_expand_flag(const bContext *UNUSED(C), Panel *panel) -{ - PointerRNA *con_ptr = UI_panel_custom_data_get(panel); - bConstraint *con = (bConstraint *)con_ptr->data; - - return con->ui_expand_flag; -} - -/** - * Save the expand flag for the panel and sub-panels to the constraint. - */ -static void set_constraint_expand_flag(const bContext *UNUSED(C), Panel *panel, short expand_flag) -{ - PointerRNA *con_ptr = UI_panel_custom_data_get(panel); - bConstraint *con = (bConstraint *)con_ptr->data; - con->ui_expand_flag = expand_flag; -} - -/** - * Function with void * argument for #uiListPanelIDFromDataFunc. - * - * \note Constraint panel types are assumed to be named with the struct name field - * concatenated to the defined prefix. - */ -static void object_constraint_panel_id(void *md_link, char *r_name) -{ - bConstraint *con = (bConstraint *)md_link; - const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_from_type(con->type); - - /* Cannot get TypeInfo for invalid/legacy constraints. */ - if (cti == NULL) { - return; - } - - strcpy(r_name, CONSTRAINT_TYPE_PANEL_PREFIX); - strcat(r_name, cti->structName); -} - -static void bone_constraint_panel_id(void *md_link, char *r_name) -{ - bConstraint *con = (bConstraint *)md_link; - const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_from_type(con->type); - - /* Cannot get TypeInfo for invalid/legacy constraints. */ - if (cti == NULL) { - return; - } - - strcpy(r_name, CONSTRAINT_BONE_TYPE_PANEL_PREFIX); - strcat(r_name, cti->structName); -} - -/** - * Check if the constraint panels don't match the data and rebuild the panels if so. - */ -void uiTemplateConstraints(uiLayout *UNUSED(layout), bContext *C, bool use_bone_constraints) -{ - ARegion *region = CTX_wm_region(C); - - Object *ob = ED_object_active_context(C); - ListBase *constraints = {NULL}; - if (use_bone_constraints) { - constraints = ED_object_pose_constraint_list(C); - } - else if (ob != NULL) { - constraints = &ob->constraints; - } - - /* Switch between the bone panel ID function and the object panel ID function. */ - uiListPanelIDFromDataFunc panel_id_func = use_bone_constraints ? bone_constraint_panel_id : - object_constraint_panel_id; - - const bool panels_match = UI_panel_list_matches_data(region, constraints, panel_id_func); - - if (!panels_match) { - UI_panels_free_instanced(C, region); - bConstraint *con = (constraints == NULL) ? NULL : constraints->first; - for (int i = 0; con; i++, con = con->next) { - /* Don't show invalid/legacy constraints. */ - if (con->type == CONSTRAINT_TYPE_NULL) { - continue; - } - /* Don't show temporary constraints (AutoIK and target-less IK constraints). */ - if (con->type == CONSTRAINT_TYPE_KINEMATIC) { - bKinematicConstraint *data = con->data; - if (data->flag & CONSTRAINT_IK_TEMP) { - continue; - } - } - - char panel_idname[MAX_NAME]; - panel_id_func(con, panel_idname); - - /* Create custom data RNA pointer. */ - PointerRNA *con_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_Constraint, con, con_ptr); - - Panel *new_panel = UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, con_ptr); - - if (new_panel) { - /* Set the list panel functionality function pointers since we don't do it with python. */ - new_panel->type->set_list_data_expand_flag = set_constraint_expand_flag; - new_panel->type->get_list_data_expand_flag = get_constraint_expand_flag; - new_panel->type->reorder = constraint_reorder; - } - } - } - else { - /* Assuming there's only one group of instanced panels, update the custom data pointers. */ - Panel *panel = region->panels.first; - LISTBASE_FOREACH (bConstraint *, con, constraints) { - /* Don't show invalid/legacy constraints. */ - if (con->type == CONSTRAINT_TYPE_NULL) { - continue; - } - /* Don't show temporary constraints (AutoIK and target-less IK constraints). */ - if (con->type == CONSTRAINT_TYPE_KINEMATIC) { - bKinematicConstraint *data = con->data; - if (data->flag & CONSTRAINT_IK_TEMP) { - continue; - } - } - - /* Move to the next instanced panel corresponding to the next constraint. */ - while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { - panel = panel->next; - BLI_assert(panel != NULL); /* There shouldn't be fewer panels than constraint panels. */ - } - - PointerRNA *con_ptr = MEM_mallocN(sizeof(PointerRNA), "constraint panel customdata"); - RNA_pointer_create(&ob->id, &RNA_Constraint, con, con_ptr); - UI_panel_custom_data_set(panel, con_ptr); - - panel = panel->next; - } - } -} - -#undef CONSTRAINT_TYPE_PANEL_PREFIX -#undef CONSTRAINT_BONE_TYPE_PANEL_PREFIX - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Grease Pencil Modifiers Template - * \{ */ - -/** - * Function with void * argument for #uiListPanelIDFromDataFunc. - */ -static void gpencil_modifier_panel_id(void *md_link, char *r_name) -{ - ModifierData *md = (ModifierData *)md_link; - BKE_gpencil_modifierType_panel_id(md->type, r_name); -} - -void uiTemplateGpencilModifiers(uiLayout *UNUSED(layout), bContext *C) -{ - ARegion *region = CTX_wm_region(C); - Object *ob = ED_object_active_context(C); - ListBase *modifiers = &ob->greasepencil_modifiers; - - const bool panels_match = UI_panel_list_matches_data( - region, modifiers, gpencil_modifier_panel_id); - - if (!panels_match) { - UI_panels_free_instanced(C, region); - GpencilModifierData *md = modifiers->first; - for (int i = 0; md; i++, md = md->next) { - const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type); - if (mti->panelRegister == NULL) { - continue; - } - - char panel_idname[MAX_NAME]; - gpencil_modifier_panel_id(md, panel_idname); - - /* Create custom data RNA pointer. */ - PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_GpencilModifier, md, md_ptr); - - UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); - } - } - else { - /* Assuming there's only one group of instanced panels, update the custom data pointers. */ - Panel *panel = region->panels.first; - LISTBASE_FOREACH (ModifierData *, md, modifiers) { - const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type); - if (mti->panelRegister == NULL) { - continue; - } - - /* Move to the next instanced panel corresponding to the next modifier. */ - while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { - panel = panel->next; - BLI_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ - } - - PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_GpencilModifier, md, md_ptr); - UI_panel_custom_data_set(panel, md_ptr); - - panel = panel->next; - } - } -} - -/** \} */ - -/** \} */ - -#define ERROR_LIBDATA_MESSAGE TIP_("Can't edit external library data") - -/* -------------------------------------------------------------------- */ -/** \name ShaderFx Template - * - * Template for building the panel layout for the active object's grease pencil shader - * effects. - * \{ */ - -/** - * Function with void * argument for #uiListPanelIDFromDataFunc. - */ -static void shaderfx_panel_id(void *fx_v, char *r_idname) -{ - ShaderFxData *fx = (ShaderFxData *)fx_v; - BKE_shaderfxType_panel_id(fx->type, r_idname); -} - -/** - * Check if the shader effect panels don't match the data and rebuild the panels if so. - */ -void uiTemplateShaderFx(uiLayout *UNUSED(layout), bContext *C) -{ - ARegion *region = CTX_wm_region(C); - Object *ob = ED_object_active_context(C); - ListBase *shaderfx = &ob->shader_fx; - - const bool panels_match = UI_panel_list_matches_data(region, shaderfx, shaderfx_panel_id); - - if (!panels_match) { - UI_panels_free_instanced(C, region); - ShaderFxData *fx = shaderfx->first; - for (int i = 0; fx; i++, fx = fx->next) { - char panel_idname[MAX_NAME]; - shaderfx_panel_id(fx, panel_idname); - - /* Create custom data RNA pointer. */ - PointerRNA *fx_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_ShaderFx, fx, fx_ptr); - - UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, fx_ptr); - } - } - else { - /* Assuming there's only one group of instanced panels, update the custom data pointers. */ - Panel *panel = region->panels.first; - LISTBASE_FOREACH (ShaderFxData *, fx, shaderfx) { - const ShaderFxTypeInfo *fxi = BKE_shaderfx_get_info(fx->type); - if (fxi->panelRegister == NULL) { - continue; - } - - /* Move to the next instanced panel corresponding to the next modifier. */ - while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { - panel = panel->next; - BLI_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ - } - - PointerRNA *fx_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); - RNA_pointer_create(&ob->id, &RNA_ShaderFx, fx, fx_ptr); - UI_panel_custom_data_set(panel, fx_ptr); - - panel = panel->next; - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Operator Property Buttons Template - * \{ */ - -typedef struct uiTemplateOperatorPropertyPollParam { - const bContext *C; - wmOperator *op; - short flag; -} uiTemplateOperatorPropertyPollParam; - -#ifdef USE_OP_RESET_BUT -static void ui_layout_operator_buts__reset_cb(bContext *UNUSED(C), - void *op_pt, - void *UNUSED(arg_dummy2)) -{ - WM_operator_properties_reset((wmOperator *)op_pt); -} -#endif - -static bool ui_layout_operator_buts_poll_property(struct PointerRNA *UNUSED(ptr), - struct PropertyRNA *prop, - void *user_data) -{ - uiTemplateOperatorPropertyPollParam *params = user_data; - - if ((params->flag & UI_TEMPLATE_OP_PROPS_HIDE_ADVANCED) && - (RNA_property_tags(prop) & OP_PROP_TAG_ADVANCED)) { - return false; - } - return params->op->type->poll_property(params->C, params->op, prop); -} - -static eAutoPropButsReturn template_operator_property_buts_draw_single( - const bContext *C, - wmOperator *op, - uiLayout *layout, - const eButLabelAlign label_align, - int layout_flags) -{ - uiBlock *block = uiLayoutGetBlock(layout); - eAutoPropButsReturn return_info = 0; - - if (!op->properties) { - const IDPropertyTemplate val = {0}; - op->properties = IDP_New(IDP_GROUP, &val, "wmOperatorProperties"); - } - - /* poll() on this operator may still fail, - * at the moment there is no nice feedback when this happens just fails silently. */ - if (!WM_operator_repeat_check(C, op)) { - UI_block_lock_set(block, true, "Operator can't' redo"); - return return_info; - } - - /* useful for macros where only one of the steps can't be re-done */ - UI_block_lock_clear(block); - - if (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_TITLE) { - uiItemL(layout, WM_operatortype_name(op->type, op->ptr), ICON_NONE); - } - - /* menu */ - if (op->type->flag & OPTYPE_PRESET) { - /* XXX, no simple way to get WM_MT_operator_presets.bl_label - * from python! Label remains the same always! */ - PointerRNA op_ptr; - uiLayout *row; - - block->ui_operator = op; - - row = uiLayoutRow(layout, true); - uiItemM(row, "WM_MT_operator_presets", NULL, ICON_NONE); - - wmOperatorType *ot = WM_operatortype_find("WM_OT_operator_preset_add", false); - uiItemFullO_ptr(row, ot, "", ICON_ADD, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); - RNA_string_set(&op_ptr, "operator", op->type->idname); - - uiItemFullO_ptr(row, ot, "", ICON_REMOVE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); - RNA_string_set(&op_ptr, "operator", op->type->idname); - RNA_boolean_set(&op_ptr, "remove_active", true); - } - - if (op->type->ui) { - op->layout = layout; - op->type->ui((bContext *)C, op); - op->layout = NULL; - - /* UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too. We could - * allow ot.ui callback to return this, but not needed right now. */ - } - else { - wmWindowManager *wm = CTX_wm_manager(C); - uiTemplateOperatorPropertyPollParam user_data = {.C = C, .op = op, .flag = layout_flags}; - const bool use_prop_split = (layout_flags & UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT) == 0; - - PointerRNA ptr; - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - uiLayoutSetPropSep(layout, use_prop_split); - uiLayoutSetPropDecorate(layout, false); - - /* main draw call */ - return_info = uiDefAutoButsRNA( - layout, - &ptr, - op->type->poll_property ? ui_layout_operator_buts_poll_property : NULL, - op->type->poll_property ? &user_data : NULL, - op->type->prop, - label_align, - (layout_flags & UI_TEMPLATE_OP_PROPS_COMPACT)); - - if ((return_info & UI_PROP_BUTS_NONE_ADDED) && - (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_EMPTY)) { - uiItemL(layout, IFACE_("No Properties"), ICON_NONE); - } - } - -#ifdef USE_OP_RESET_BUT - /* its possible that reset can do nothing if all have PROP_SKIP_SAVE enabled - * but this is not so important if this button is drawn in those cases - * (which isn't all that likely anyway) - campbell */ - if (op->properties->len) { - uiBut *but; - uiLayout *col; /* needed to avoid alignment errors with previous buttons */ - - col = uiLayoutColumn(layout, false); - block = uiLayoutGetBlock(col); - but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_FILE_REFRESH, - IFACE_("Reset"), - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Reset operator defaults")); - UI_but_func_set(but, ui_layout_operator_buts__reset_cb, op, NULL); - } -#endif - - /* set various special settings for buttons */ - - /* Only do this if we're not refreshing an existing UI. */ - if (block->oldblock == NULL) { - const bool is_popup = (block->flag & UI_BLOCK_KEEP_OPEN) != 0; - - LISTBASE_FOREACH (uiBut *, but, &block->buttons) { - /* no undo for buttons for operator redo panels */ - UI_but_flag_disable(but, UI_BUT_UNDO); - - /* only for popups, see T36109. */ - - /* if button is operator's default property, and a text-field, enable focus for it - * - this is used for allowing operators with popups to rename stuff with fewer clicks - */ - if (is_popup) { - if ((but->rnaprop == op->type->prop) && (but->type == UI_BTYPE_TEXT)) { - UI_but_focus_on_enter_event(CTX_wm_window(C), but); - } - } - } - } - - return return_info; -} - -static void template_operator_property_buts_draw_recursive(const bContext *C, - wmOperator *op, - uiLayout *layout, - const eButLabelAlign label_align, - int layout_flags, - bool *r_has_advanced) -{ - if (op->type->flag & OPTYPE_MACRO) { - LISTBASE_FOREACH (wmOperator *, macro_op, &op->macro) { - template_operator_property_buts_draw_recursive( - C, macro_op, layout, label_align, layout_flags, r_has_advanced); - } - } - else { - /* Might want to make label_align adjustable somehow. */ - eAutoPropButsReturn return_info = template_operator_property_buts_draw_single( - C, op, layout, label_align, layout_flags); - if (return_info & UI_PROP_BUTS_ANY_FAILED_CHECK) { - if (r_has_advanced) { - *r_has_advanced = true; - } - } - } -} - -static bool ui_layout_operator_properties_only_booleans(const bContext *C, - wmWindowManager *wm, - wmOperator *op, - int layout_flags) -{ - if (op->type->flag & OPTYPE_MACRO) { - LISTBASE_FOREACH (wmOperator *, macro_op, &op->macro) { - if (!ui_layout_operator_properties_only_booleans(C, wm, macro_op, layout_flags)) { - return false; - } - } - } - else { - uiTemplateOperatorPropertyPollParam user_data = {.C = C, .op = op, .flag = layout_flags}; - PointerRNA ptr; - - RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); - - RNA_STRUCT_BEGIN (&ptr, prop) { - if (RNA_property_flag(prop) & PROP_HIDDEN) { - continue; - } - if (op->type->poll_property && - !ui_layout_operator_buts_poll_property(&ptr, prop, &user_data)) { - continue; - } - if (RNA_property_type(prop) != PROP_BOOLEAN) { - return false; - } - } - RNA_STRUCT_END; - } - - return true; -} - -/** - * Draw Operator property buttons for redoing execution with different settings. - * This function does not initialize the layout, - * functions can be called on the layout before and after. - */ -void uiTemplateOperatorPropertyButs( - const bContext *C, uiLayout *layout, wmOperator *op, eButLabelAlign label_align, short flag) -{ - wmWindowManager *wm = CTX_wm_manager(C); - - /* If there are only checkbox items, don't use split layout by default. It looks weird if the - * checkboxes only use half the width. */ - if (ui_layout_operator_properties_only_booleans(C, wm, op, flag)) { - flag |= UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT; - } - - template_operator_property_buts_draw_recursive(C, op, layout, label_align, flag, NULL); -} - -void uiTemplateOperatorRedoProperties(uiLayout *layout, const bContext *C) -{ - wmOperator *op = WM_operator_last_redo(C); - uiBlock *block = uiLayoutGetBlock(layout); - - if (op == NULL) { - return; - } - - /* Disable for now, doesn't fit well in popover. */ -#if 0 - /* Repeat button with operator name as text. */ - uiItemFullO(layout, - "SCREEN_OT_repeat_last", - WM_operatortype_name(op->type, op->ptr), - ICON_NONE, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - NULL); -#endif - - if (WM_operator_repeat_check(C, op)) { - int layout_flags = 0; - if (block->panel == NULL) { - layout_flags = UI_TEMPLATE_OP_PROPS_SHOW_TITLE; - } -#if 0 - bool has_advanced = false; -#endif - - UI_block_func_handle_set(block, ED_undo_operator_repeat_cb_evt, op); - template_operator_property_buts_draw_recursive( - C, op, layout, UI_BUT_LABEL_ALIGN_NONE, layout_flags, NULL /* &has_advanced */); - /* Warning! this leaves the handle function for any other users of this block. */ - -#if 0 - if (has_advanced) { - uiItemO(layout, IFACE_("More..."), ICON_NONE, "SCREEN_OT_redo_last"); - } -#endif - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Constraint Header Template - * \{ */ - -#define ERROR_LIBDATA_MESSAGE TIP_("Can't edit external library data") - -static void constraint_active_func(bContext *UNUSED(C), void *ob_v, void *con_v) -{ - ED_object_constraint_active_set(ob_v, con_v); -} - -static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *con) -{ - bPoseChannel *pchan = BKE_pose_channel_active(ob); - short proxy_protected, xco = 0, yco = 0; - // int rb_col; // UNUSED - - /* determine whether constraint is proxy protected or not */ - if (BKE_constraints_proxylocked_owner(ob, pchan)) { - proxy_protected = (con->flag & CONSTRAINT_PROXY_LOCAL) == 0; - } - else { - proxy_protected = 0; - } - - /* unless button has own callback, it adds this callback to button */ - uiBlock *block = uiLayoutGetBlock(layout); - UI_block_func_set(block, constraint_active_func, ob, con); - - PointerRNA ptr; - RNA_pointer_create(&ob->id, &RNA_Constraint, con, &ptr); - - uiLayoutSetContextPointer(layout, "constraint", &ptr); - - /* Constraint type icon. */ - uiLayout *sub = uiLayoutRow(layout, false); - uiLayoutSetEmboss(sub, false); - uiLayoutSetRedAlert(sub, (con->flag & CONSTRAINT_DISABLE)); - uiItemL(sub, "", RNA_struct_ui_icon(ptr.type)); - - UI_block_emboss_set(block, UI_EMBOSS); - - if (proxy_protected == 0) { - uiItemR(layout, &ptr, "name", 0, "", ICON_NONE); - } - else { - uiItemL(layout, con->name, ICON_NONE); - } - - /* proxy-protected constraints cannot be edited, so hide up/down + close buttons */ - if (proxy_protected) { - UI_block_emboss_set(block, UI_EMBOSS_NONE); - - /* draw a ghost icon (for proxy) and also a lock beside it, - * to show that constraint is "proxy locked" */ - uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_GHOST_ENABLED, - xco + 12.2f * UI_UNIT_X, - yco, - 0.95f * UI_UNIT_X, - 0.95f * UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Proxy Protected")); - uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LOCKED, - xco + 13.1f * UI_UNIT_X, - yco, - 0.95f * UI_UNIT_X, - 0.95f * UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Proxy Protected")); - - UI_block_emboss_set(block, UI_EMBOSS); - } - else { - /* enabled */ - UI_block_emboss_set(block, UI_EMBOSS_NONE_OR_STATUS); - uiItemR(layout, &ptr, "mute", 0, "", 0); - UI_block_emboss_set(block, UI_EMBOSS); - - uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); - - /* Close 'button' - emboss calls here disable drawing of 'button' behind X */ - UI_block_emboss_set(block, UI_EMBOSS_NONE); - uiItemO(layout, "", ICON_X, "CONSTRAINT_OT_delete"); - UI_block_emboss_set(block, UI_EMBOSS); - - /* Some extra padding at the end, so the 'x' icon isn't too close to drag button. */ - uiItemS(layout); - } - - /* Set but-locks for protected settings (magic numbers are used here!) */ - if (proxy_protected) { - UI_block_lock_set(block, true, TIP_("Cannot edit Proxy-Protected Constraint")); - } - - /* clear any locks set up for proxies/lib-linking */ - UI_block_lock_clear(block); -} - -void uiTemplateConstraintHeader(uiLayout *layout, PointerRNA *ptr) -{ - /* verify we have valid data */ - if (!RNA_struct_is_a(ptr->type, &RNA_Constraint)) { - RNA_warning("Expected constraint on object"); - return; - } - - Object *ob = (Object *)ptr->owner_id; - bConstraint *con = ptr->data; - - if (!ob || !(GS(ob->id.name) == ID_OB)) { - RNA_warning("Expected constraint on object"); - return; - } - - UI_block_lock_set(uiLayoutGetBlock(layout), (ob && ID_IS_LINKED(ob)), ERROR_LIBDATA_MESSAGE); - - draw_constraint_header(layout, ob, con); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Preview Template - * \{ */ - -#include "DNA_light_types.h" -#include "DNA_material_types.h" -#include "DNA_world_types.h" - -#define B_MATPRV 1 - -static void do_preview_buttons(bContext *C, void *arg, int event) -{ - switch (event) { - case B_MATPRV: - WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_PREVIEW, arg); - break; - } -} - -void uiTemplatePreview(uiLayout *layout, - bContext *C, - ID *id, - bool show_buttons, - ID *parent, - MTex *slot, - const char *preview_id) -{ - Material *ma = NULL; - Tex *tex = (Tex *)id; - short *pr_texture = NULL; - PointerRNA material_ptr; - PointerRNA texture_ptr; - - char _preview_id[UI_MAX_NAME_STR]; - - if (id && !ELEM(GS(id->name), ID_MA, ID_TE, ID_WO, ID_LA, ID_LS)) { - RNA_warning("Expected ID of type material, texture, light, world or line style"); - return; - } - - /* decide what to render */ - ID *pid = id; - ID *pparent = NULL; - - if (id && (GS(id->name) == ID_TE)) { - if (parent && (GS(parent->name) == ID_MA)) { - pr_texture = &((Material *)parent)->pr_texture; - } - else if (parent && (GS(parent->name) == ID_WO)) { - pr_texture = &((World *)parent)->pr_texture; - } - else if (parent && (GS(parent->name) == ID_LA)) { - pr_texture = &((Light *)parent)->pr_texture; - } - else if (parent && (GS(parent->name) == ID_LS)) { - pr_texture = &((FreestyleLineStyle *)parent)->pr_texture; - } - - if (pr_texture) { - if (*pr_texture == TEX_PR_OTHER) { - pid = parent; - } - else if (*pr_texture == TEX_PR_BOTH) { - pparent = parent; - } - } - } - - if (!preview_id || (preview_id[0] == '\0')) { - /* If no identifier given, generate one from ID type. */ - BLI_snprintf( - _preview_id, UI_MAX_NAME_STR, "uiPreview_%s", BKE_idtype_idcode_to_name(GS(id->name))); - preview_id = _preview_id; - } - - /* Find or add the uiPreview to the current Region. */ - ARegion *region = CTX_wm_region(C); - uiPreview *ui_preview = BLI_findstring( - ®ion->ui_previews, preview_id, offsetof(uiPreview, preview_id)); - - if (!ui_preview) { - ui_preview = MEM_callocN(sizeof(uiPreview), "uiPreview"); - BLI_strncpy(ui_preview->preview_id, preview_id, sizeof(ui_preview->preview_id)); - ui_preview->height = (short)(UI_UNIT_Y * 7.6f); - BLI_addtail(®ion->ui_previews, ui_preview); - } - - if (ui_preview->height < UI_UNIT_Y) { - ui_preview->height = UI_UNIT_Y; - } - else if (ui_preview->height > UI_UNIT_Y * 50) { /* Rather high upper limit, yet not insane! */ - ui_preview->height = UI_UNIT_Y * 50; - } - - /* layout */ - uiBlock *block = uiLayoutGetBlock(layout); - uiLayout *row = uiLayoutRow(layout, false); - uiLayout *col = uiLayoutColumn(row, false); - uiLayoutSetKeepAspect(col, true); - - /* add preview */ - uiDefBut(block, - UI_BTYPE_EXTRA, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - ui_preview->height, - pid, - 0.0, - 0.0, - 0, - 0, - ""); - UI_but_func_drawextra_set(block, ED_preview_draw, pparent, slot); - UI_block_func_handle_set(block, do_preview_buttons, NULL); - - uiDefIconButS(block, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10, - (short)(UI_UNIT_Y * 0.3f), - &ui_preview->height, - UI_UNIT_Y, - UI_UNIT_Y * 50.0f, - 0.0f, - 0.0f, - ""); - - /* add buttons */ - if (pid && show_buttons) { - if (GS(pid->name) == ID_MA || (pparent && GS(pparent->name) == ID_MA)) { - if (GS(pid->name) == ID_MA) { - ma = (Material *)pid; - } - else { - ma = (Material *)pparent; - } - - /* Create RNA Pointer */ - RNA_pointer_create(&ma->id, &RNA_Material, ma, &material_ptr); - - col = uiLayoutColumn(row, true); - uiLayoutSetScaleX(col, 1.5); - uiItemR(col, &material_ptr, "preview_render_type", UI_ITEM_R_EXPAND, "", ICON_NONE); - - /* EEVEE preview file has baked lighting so use_preview_world has no effect, - * just hide the option until this feature is supported. */ - if (!BKE_scene_uses_blender_eevee(CTX_data_scene(C))) { - uiItemS(col); - uiItemR(col, &material_ptr, "use_preview_world", 0, "", ICON_WORLD); - } - } - - if (pr_texture) { - /* Create RNA Pointer */ - RNA_pointer_create(id, &RNA_Texture, tex, &texture_ptr); - - uiLayoutRow(layout, true); - uiDefButS(block, - UI_BTYPE_ROW, - B_MATPRV, - IFACE_("Texture"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - pr_texture, - 10, - TEX_PR_TEXTURE, - 0, - 0, - ""); - if (GS(parent->name) == ID_MA) { - uiDefButS(block, - UI_BTYPE_ROW, - B_MATPRV, - IFACE_("Material"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - pr_texture, - 10, - TEX_PR_OTHER, - 0, - 0, - ""); - } - else if (GS(parent->name) == ID_LA) { - uiDefButS(block, - UI_BTYPE_ROW, - B_MATPRV, - CTX_IFACE_(BLT_I18NCONTEXT_ID_LIGHT, "Light"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - pr_texture, - 10, - TEX_PR_OTHER, - 0, - 0, - ""); - } - else if (GS(parent->name) == ID_WO) { - uiDefButS(block, - UI_BTYPE_ROW, - B_MATPRV, - IFACE_("World"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - pr_texture, - 10, - TEX_PR_OTHER, - 0, - 0, - ""); - } - else if (GS(parent->name) == ID_LS) { - uiDefButS(block, - UI_BTYPE_ROW, - B_MATPRV, - IFACE_("Line Style"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - pr_texture, - 10, - TEX_PR_OTHER, - 0, - 0, - ""); - } - uiDefButS(block, - UI_BTYPE_ROW, - B_MATPRV, - IFACE_("Both"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - pr_texture, - 10, - TEX_PR_BOTH, - 0, - 0, - ""); - - /* Alpha button for texture preview */ - if (*pr_texture != TEX_PR_OTHER) { - row = uiLayoutRow(layout, false); - uiItemR(row, &texture_ptr, "use_preview_alpha", 0, NULL, ICON_NONE); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ColorRamp Template - * \{ */ - -typedef struct RNAUpdateCb { - PointerRNA ptr; - PropertyRNA *prop; -} RNAUpdateCb; - -static void rna_update_cb(bContext *C, void *arg_cb, void *UNUSED(arg)) -{ - RNAUpdateCb *cb = (RNAUpdateCb *)arg_cb; - - /* we call update here on the pointer property, this way the - * owner of the curve mapping can still define its own update - * and notifier, even if the CurveMapping struct is shared. */ - RNA_property_update(C, &cb->ptr, cb->prop); -} - -enum { - CB_FUNC_FLIP, - CB_FUNC_DISTRIBUTE_LR, - CB_FUNC_DISTRIBUTE_EVENLY, - CB_FUNC_RESET, -}; - -static void colorband_flip_cb(bContext *C, ColorBand *coba) -{ - CBData data_tmp[MAXCOLORBAND]; - - for (int a = 0; a < coba->tot; a++) { - data_tmp[a] = coba->data[coba->tot - (a + 1)]; - } - for (int a = 0; a < coba->tot; a++) { - data_tmp[a].pos = 1.0f - data_tmp[a].pos; - coba->data[a] = data_tmp[a]; - } - - /* may as well flip the cur*/ - coba->cur = coba->tot - (coba->cur + 1); - - ED_undo_push(C, "Flip Color Ramp"); -} - -static void colorband_distribute_cb(bContext *C, ColorBand *coba, bool evenly) -{ - if (coba->tot > 1) { - const int tot = evenly ? coba->tot - 1 : coba->tot; - const float gap = 1.0f / tot; - float pos = 0.0f; - for (int a = 0; a < coba->tot; a++) { - coba->data[a].pos = pos; - pos += gap; - } - ED_undo_push(C, evenly ? "Distribute Stops Evenly" : "Distribute Stops from Left"); - } -} - -static void colorband_tools_dofunc(bContext *C, void *coba_v, int event) -{ - ColorBand *coba = coba_v; - - switch (event) { - case CB_FUNC_FLIP: - colorband_flip_cb(C, coba); - break; - case CB_FUNC_DISTRIBUTE_LR: - colorband_distribute_cb(C, coba, false); - break; - case CB_FUNC_DISTRIBUTE_EVENLY: - colorband_distribute_cb(C, coba, true); - break; - case CB_FUNC_RESET: - BKE_colorband_init(coba, true); - ED_undo_push(C, "Reset Color Ramp"); - break; - } - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static uiBlock *colorband_tools_func(bContext *C, ARegion *region, void *coba_v) -{ - const uiStyle *style = UI_style_get_dpi(); - ColorBand *coba = coba_v; - short yco = 0; - const short menuwidth = 10 * UI_UNIT_X; - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS_PULLDOWN); - UI_block_func_butmenu_set(block, colorband_tools_dofunc, coba); - - uiLayout *layout = UI_block_layout(block, - UI_LAYOUT_VERTICAL, - UI_LAYOUT_MENU, - 0, - 0, - UI_MENU_WIDTH_MIN, - 0, - UI_MENU_PADDING, - style); - UI_block_layout_set_current(block, layout); - { - PointerRNA coba_ptr; - RNA_pointer_create(NULL, &RNA_ColorRamp, coba, &coba_ptr); - uiLayoutSetContextPointer(layout, "color_ramp", &coba_ptr); - } - - /* We could move these to operators, - * although this isn't important unless we want to assign key shortcuts to them. */ - { - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Flip Color Ramp"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - CB_FUNC_FLIP, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Distribute Stops from Left"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - CB_FUNC_DISTRIBUTE_LR, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Distribute Stops Evenly"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - CB_FUNC_DISTRIBUTE_EVENLY, - ""); - - uiItemO(layout, IFACE_("Eyedropper"), ICON_EYEDROPPER, "UI_OT_eyedropper_colorramp"); - - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Reset Color Ramp"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - CB_FUNC_RESET, - ""); - } - - UI_block_direction_set(block, UI_DIR_DOWN); - UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X); - - return block; -} - -static void colorband_add_cb(bContext *C, void *cb_v, void *coba_v) -{ - ColorBand *coba = coba_v; - float pos = 0.5f; - - if (coba->tot > 1) { - if (coba->cur > 0) { - pos = (coba->data[coba->cur - 1].pos + coba->data[coba->cur].pos) * 0.5f; - } - else { - pos = (coba->data[coba->cur + 1].pos + coba->data[coba->cur].pos) * 0.5f; - } - } - - if (BKE_colorband_element_add(coba, pos)) { - rna_update_cb(C, cb_v, NULL); - ED_undo_push(C, "Add Color Ramp Stop"); - } -} - -static void colorband_del_cb(bContext *C, void *cb_v, void *coba_v) -{ - ColorBand *coba = coba_v; - - if (BKE_colorband_element_remove(coba, coba->cur)) { - ED_undo_push(C, "Delete Color Ramp Stop"); - rna_update_cb(C, cb_v, NULL); - } -} - -static void colorband_update_cb(bContext *UNUSED(C), void *bt_v, void *coba_v) -{ - uiBut *bt = bt_v; - ColorBand *coba = coba_v; - - /* Sneaky update here, we need to sort the color-band points to be in order, - * however the RNA pointer then is wrong, so we update it */ - BKE_colorband_update_sort(coba); - bt->rnapoin.data = coba->data + coba->cur; -} - -static void colorband_buttons_layout(uiLayout *layout, - uiBlock *block, - ColorBand *coba, - const rctf *butr, - RNAUpdateCb *cb, - int expand) -{ - uiBut *bt; - const float unit = BLI_rctf_size_x(butr) / 14.0f; - const float xs = butr->xmin; - const float ys = butr->ymin; - - PointerRNA ptr; - RNA_pointer_create(cb->ptr.owner_id, &RNA_ColorRamp, coba, &ptr); - - uiLayout *split = uiLayoutSplit(layout, 0.4f, false); - - UI_block_emboss_set(block, UI_EMBOSS_NONE); - UI_block_align_begin(block); - uiLayout *row = uiLayoutRow(split, false); - - bt = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_ADD, - "", - 0, - 0, - 2.0f * unit, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Add a new color stop to the color ramp")); - UI_but_funcN_set(bt, colorband_add_cb, MEM_dupallocN(cb), coba); - - bt = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_REMOVE, - "", - xs + 2.0f * unit, - ys + UI_UNIT_Y, - 2.0f * unit, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Delete the active position")); - UI_but_funcN_set(bt, colorband_del_cb, MEM_dupallocN(cb), coba); - - bt = uiDefIconBlockBut(block, - colorband_tools_func, - coba, - 0, - ICON_DOWNARROW_HLT, - xs + 4.0f * unit, - ys + UI_UNIT_Y, - 2.0f * unit, - UI_UNIT_Y, - TIP_("Tools")); - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), coba); - - UI_block_align_end(block); - UI_block_emboss_set(block, UI_EMBOSS); - - row = uiLayoutRow(split, false); - - UI_block_align_begin(block); - uiItemR(row, &ptr, "color_mode", 0, "", ICON_NONE); - if (ELEM(coba->color_mode, COLBAND_BLEND_HSV, COLBAND_BLEND_HSL)) { - uiItemR(row, &ptr, "hue_interpolation", 0, "", ICON_NONE); - } - else { /* COLBAND_BLEND_RGB */ - uiItemR(row, &ptr, "interpolation", 0, "", ICON_NONE); - } - UI_block_align_end(block); - - row = uiLayoutRow(layout, false); - - bt = uiDefBut(block, - UI_BTYPE_COLORBAND, - 0, - "", - xs, - ys, - BLI_rctf_size_x(butr), - UI_UNIT_Y, - coba, - 0, - 0, - 0, - 0, - ""); - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - - row = uiLayoutRow(layout, false); - - if (coba->tot) { - CBData *cbd = coba->data + coba->cur; - - RNA_pointer_create(cb->ptr.owner_id, &RNA_ColorRampElement, cbd, &ptr); - - if (!expand) { - split = uiLayoutSplit(layout, 0.3f, false); - - row = uiLayoutRow(split, false); - bt = uiDefButS(block, - UI_BTYPE_NUM, - 0, - "", - 0, - 0, - 5.0f * UI_UNIT_X, - UI_UNIT_Y, - &coba->cur, - 0.0, - (float)(MAX2(0, coba->tot - 1)), - 0, - 0, - TIP_("Choose active color stop")); - UI_but_number_step_size_set(bt, 1); - - row = uiLayoutRow(split, false); - uiItemR(row, &ptr, "position", 0, IFACE_("Pos"), ICON_NONE); - bt = block->buttons.last; - UI_but_func_set(bt, colorband_update_cb, bt, coba); - - row = uiLayoutRow(layout, false); - uiItemR(row, &ptr, "color", 0, "", ICON_NONE); - bt = block->buttons.last; - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - } - else { - split = uiLayoutSplit(layout, 0.5f, false); - uiLayout *subsplit = uiLayoutSplit(split, 0.35f, false); - - row = uiLayoutRow(subsplit, false); - bt = uiDefButS(block, - UI_BTYPE_NUM, - 0, - "", - 0, - 0, - 5.0f * UI_UNIT_X, - UI_UNIT_Y, - &coba->cur, - 0.0, - (float)(MAX2(0, coba->tot - 1)), - 0, - 0, - TIP_("Choose active color stop")); - UI_but_number_step_size_set(bt, 1); - - row = uiLayoutRow(subsplit, false); - uiItemR(row, &ptr, "position", UI_ITEM_R_SLIDER, IFACE_("Pos"), ICON_NONE); - bt = block->buttons.last; - UI_but_func_set(bt, colorband_update_cb, bt, coba); - - row = uiLayoutRow(split, false); - uiItemR(row, &ptr, "color", 0, "", ICON_NONE); - bt = block->buttons.last; - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - } - } -} - -void uiTemplateColorRamp(uiLayout *layout, PointerRNA *ptr, const char *propname, bool expand) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_POINTER) { - return; - } - - const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_ColorRamp)) { - return; - } - - RNAUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); - cb->ptr = *ptr; - cb->prop = prop; - - rctf rect; - rect.xmin = 0; - rect.xmax = 10.0f * UI_UNIT_X; - rect.ymin = 0; - rect.ymax = 19.5f * UI_UNIT_X; - - uiBlock *block = uiLayoutAbsoluteBlock(layout); - - ID *id = cptr.owner_id; - UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); - - colorband_buttons_layout(layout, block, cptr.data, &rect, cb, expand); - - UI_block_lock_clear(block); - - MEM_freeN(cb); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Icon Template - * \{ */ - -/** - * \param icon_scale: Scale of the icon, 1x == button height. - */ -void uiTemplateIcon(uiLayout *layout, int icon_value, float icon_scale) -{ - uiBlock *block = uiLayoutAbsoluteBlock(layout); - uiBut *but = uiDefIconBut(block, - UI_BTYPE_LABEL, - 0, - ICON_X, - 0, - 0, - UI_UNIT_X * icon_scale, - UI_UNIT_Y * icon_scale, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - ""); - ui_def_but_icon(but, icon_value, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Icon viewer Template - * \{ */ - -typedef struct IconViewMenuArgs { - PointerRNA ptr; - PropertyRNA *prop; - bool show_labels; - float icon_scale; -} IconViewMenuArgs; - -/* ID Search browse menu, open */ -static uiBlock *ui_icon_view_menu_cb(bContext *C, ARegion *region, void *arg_litem) -{ - static IconViewMenuArgs args; - - /* arg_litem is malloced, can be freed by parent button */ - args = *((IconViewMenuArgs *)arg_litem); - const int w = UI_UNIT_X * (args.icon_scale); - const int h = UI_UNIT_X * (args.icon_scale + args.show_labels); - - uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS_PULLDOWN); - UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NO_FLIP); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - - bool free; - const EnumPropertyItem *item; - RNA_property_enum_items(C, &args.ptr, args.prop, &item, NULL, &free); - - for (int a = 0; item[a].identifier; a++) { - const int x = (a % 8) * w; - const int y = -(a / 8) * h; - - const int icon = item[a].icon; - const int value = item[a].value; - uiBut *but; - if (args.show_labels) { - but = uiDefIconTextButR_prop(block, - UI_BTYPE_ROW, - 0, - icon, - item[a].name, - x, - y, - w, - h, - &args.ptr, - args.prop, - -1, - 0, - value, - -1, - -1, - NULL); - } - else { - but = uiDefIconButR_prop(block, - UI_BTYPE_ROW, - 0, - icon, - x, - y, - w, - h, - &args.ptr, - args.prop, - -1, - 0, - value, - -1, - -1, - NULL); - } - ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); - } - - UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); - UI_block_direction_set(block, UI_DIR_DOWN); - - if (free) { - MEM_freeN((void *)item); - } - - return block; -} - -/** - * \param icon_scale: Scale of the icon, 1x == button height. - */ -void uiTemplateIconView(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - bool show_labels, - float icon_scale, - float icon_scale_popup) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_ENUM) { - RNA_warning( - "property of type Enum not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiBlock *block = uiLayoutAbsoluteBlock(layout); - - int tot_items; - bool free_items; - const EnumPropertyItem *items; - RNA_property_enum_items(block->evil_C, ptr, prop, &items, &tot_items, &free_items); - const int value = RNA_property_enum_get(ptr, prop); - int icon = ICON_NONE; - RNA_enum_icon_from_value(items, value, &icon); - - uiBut *but; - if (RNA_property_editable(ptr, prop)) { - IconViewMenuArgs *cb_args = MEM_callocN(sizeof(IconViewMenuArgs), __func__); - cb_args->ptr = *ptr; - cb_args->prop = prop; - cb_args->show_labels = show_labels; - cb_args->icon_scale = icon_scale_popup; - - but = uiDefBlockButN(block, - ui_icon_view_menu_cb, - cb_args, - "", - 0, - 0, - UI_UNIT_X * icon_scale, - UI_UNIT_Y * icon_scale, - ""); - } - else { - but = uiDefIconBut(block, - UI_BTYPE_LABEL, - 0, - ICON_X, - 0, - 0, - UI_UNIT_X * icon_scale, - UI_UNIT_Y * icon_scale, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - ""); - } - - ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); - - if (free_items) { - MEM_freeN((void *)items); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Histogram Template - * \{ */ - -void uiTemplateHistogram(uiLayout *layout, PointerRNA *ptr, const char *propname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_POINTER) { - return; - } - - const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Histogram)) { - return; - } - Histogram *hist = (Histogram *)cptr.data; - - if (hist->height < UI_UNIT_Y) { - hist->height = UI_UNIT_Y; - } - else if (hist->height > UI_UNIT_Y * 20) { - hist->height = UI_UNIT_Y * 20; - } - - uiLayout *col = uiLayoutColumn(layout, true); - uiBlock *block = uiLayoutGetBlock(col); - - uiDefBut( - block, UI_BTYPE_HISTOGRAM, 0, "", 0, 0, UI_UNIT_X * 10, hist->height, hist, 0, 0, 0, 0, ""); - - /* Resize grip. */ - uiDefIconButI(block, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10, - (short)(UI_UNIT_Y * 0.3f), - &hist->height, - UI_UNIT_Y, - UI_UNIT_Y * 20.0f, - 0.0f, - 0.0f, - ""); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Waveform Template - * \{ */ - -void uiTemplateWaveform(uiLayout *layout, PointerRNA *ptr, const char *propname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_POINTER) { - return; - } - - const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Scopes)) { - return; - } - Scopes *scopes = (Scopes *)cptr.data; - - uiLayout *col = uiLayoutColumn(layout, true); - uiBlock *block = uiLayoutGetBlock(col); - - if (scopes->wavefrm_height < UI_UNIT_Y) { - scopes->wavefrm_height = UI_UNIT_Y; - } - else if (scopes->wavefrm_height > UI_UNIT_Y * 20) { - scopes->wavefrm_height = UI_UNIT_Y * 20; - } - - uiDefBut(block, - UI_BTYPE_WAVEFORM, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - scopes->wavefrm_height, - scopes, - 0, - 0, - 0, - 0, - ""); - - /* Resize grip. */ - uiDefIconButI(block, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10, - (short)(UI_UNIT_Y * 0.3f), - &scopes->wavefrm_height, - UI_UNIT_Y, - UI_UNIT_Y * 20.0f, - 0.0f, - 0.0f, - ""); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Vector-Scope Template - * \{ */ - -void uiTemplateVectorscope(uiLayout *layout, PointerRNA *ptr, const char *propname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop || RNA_property_type(prop) != PROP_POINTER) { - return; - } - - const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Scopes)) { - return; - } - Scopes *scopes = (Scopes *)cptr.data; - - if (scopes->vecscope_height < UI_UNIT_Y) { - scopes->vecscope_height = UI_UNIT_Y; - } - else if (scopes->vecscope_height > UI_UNIT_Y * 20) { - scopes->vecscope_height = UI_UNIT_Y * 20; - } - - uiLayout *col = uiLayoutColumn(layout, true); - uiBlock *block = uiLayoutGetBlock(col); - - uiDefBut(block, - UI_BTYPE_VECTORSCOPE, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - scopes->vecscope_height, - scopes, - 0, - 0, - 0, - 0, - ""); - - /* Resize grip. */ - uiDefIconButI(block, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10, - (short)(UI_UNIT_Y * 0.3f), - &scopes->vecscope_height, - UI_UNIT_Y, - UI_UNIT_Y * 20.0f, - 0.0f, - 0.0f, - ""); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name CurveMapping Template - * \{ */ - -static void curvemap_buttons_zoom_in(bContext *C, void *cumap_v, void *UNUSED(arg)) -{ - CurveMapping *cumap = cumap_v; - - /* we allow 20 times zoom */ - if (BLI_rctf_size_x(&cumap->curr) > 0.04f * BLI_rctf_size_x(&cumap->clipr)) { - const float dx = 0.1154f * BLI_rctf_size_x(&cumap->curr); - cumap->curr.xmin += dx; - cumap->curr.xmax -= dx; - const float dy = 0.1154f * BLI_rctf_size_y(&cumap->curr); - cumap->curr.ymin += dy; - cumap->curr.ymax -= dy; - } - - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static void curvemap_buttons_zoom_out(bContext *C, void *cumap_v, void *UNUSED(unused)) -{ - CurveMapping *cumap = cumap_v; - float d, d1; - - /* we allow 20 times zoom, but don't view outside clip */ - if (BLI_rctf_size_x(&cumap->curr) < 20.0f * BLI_rctf_size_x(&cumap->clipr)) { - d = d1 = 0.15f * BLI_rctf_size_x(&cumap->curr); - - if (cumap->flag & CUMA_DO_CLIP) { - if (cumap->curr.xmin - d < cumap->clipr.xmin) { - d1 = cumap->curr.xmin - cumap->clipr.xmin; - } - } - cumap->curr.xmin -= d1; - - d1 = d; - if (cumap->flag & CUMA_DO_CLIP) { - if (cumap->curr.xmax + d > cumap->clipr.xmax) { - d1 = -cumap->curr.xmax + cumap->clipr.xmax; - } - } - cumap->curr.xmax += d1; - - d = d1 = 0.15f * BLI_rctf_size_y(&cumap->curr); - - if (cumap->flag & CUMA_DO_CLIP) { - if (cumap->curr.ymin - d < cumap->clipr.ymin) { - d1 = cumap->curr.ymin - cumap->clipr.ymin; - } - } - cumap->curr.ymin -= d1; - - d1 = d; - if (cumap->flag & CUMA_DO_CLIP) { - if (cumap->curr.ymax + d > cumap->clipr.ymax) { - d1 = -cumap->curr.ymax + cumap->clipr.ymax; - } - } - cumap->curr.ymax += d1; - } - - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static void curvemap_buttons_setclip(bContext *UNUSED(C), void *cumap_v, void *UNUSED(arg)) -{ - CurveMapping *cumap = cumap_v; - - BKE_curvemapping_changed(cumap, false); -} - -static void curvemap_buttons_delete(bContext *C, void *cb_v, void *cumap_v) -{ - CurveMapping *cumap = cumap_v; - - BKE_curvemap_remove(cumap->cm + cumap->cur, SELECT); - BKE_curvemapping_changed(cumap, false); - - rna_update_cb(C, cb_v, NULL); -} - -/* NOTE: this is a block-menu, needs 0 events, otherwise the menu closes */ -static uiBlock *curvemap_clipping_func(bContext *C, ARegion *region, void *cumap_v) -{ - CurveMapping *cumap = cumap_v; - uiBut *bt; - const float width = 8 * UI_UNIT_X; - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); - UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_MOVEMOUSE_QUIT); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - - bt = uiDefButBitI(block, - UI_BTYPE_CHECKBOX, - CUMA_DO_CLIP, - 1, - IFACE_("Use Clipping"), - 0, - 5 * UI_UNIT_Y, - width, - UI_UNIT_Y, - &cumap->flag, - 0.0, - 0.0, - 10, - 0, - ""); - UI_but_func_set(bt, curvemap_buttons_setclip, cumap, NULL); - - UI_block_align_begin(block); - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - IFACE_("Min X:"), - 0, - 4 * UI_UNIT_Y, - width, - UI_UNIT_Y, - &cumap->clipr.xmin, - -100.0, - cumap->clipr.xmax, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 10); - UI_but_number_precision_set(bt, 2); - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - IFACE_("Min Y:"), - 0, - 3 * UI_UNIT_Y, - width, - UI_UNIT_Y, - &cumap->clipr.ymin, - -100.0, - cumap->clipr.ymax, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 10); - UI_but_number_precision_set(bt, 2); - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - IFACE_("Max X:"), - 0, - 2 * UI_UNIT_Y, - width, - UI_UNIT_Y, - &cumap->clipr.xmax, - cumap->clipr.xmin, - 100.0, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 10); - UI_but_number_precision_set(bt, 2); - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - IFACE_("Max Y:"), - 0, - UI_UNIT_Y, - width, - UI_UNIT_Y, - &cumap->clipr.ymax, - cumap->clipr.ymin, - 100.0, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 10); - UI_but_number_precision_set(bt, 2); - - UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); - UI_block_direction_set(block, UI_DIR_DOWN); - - return block; -} - -/* only for BKE_curvemap_tools_dofunc */ -enum { - UICURVE_FUNC_RESET_NEG, - UICURVE_FUNC_RESET_POS, - UICURVE_FUNC_RESET_VIEW, - UICURVE_FUNC_HANDLE_VECTOR, - UICURVE_FUNC_HANDLE_AUTO, - UICURVE_FUNC_HANDLE_AUTO_ANIM, - UICURVE_FUNC_EXTEND_HOZ, - UICURVE_FUNC_EXTEND_EXP, -}; - -static void curvemap_tools_dofunc(bContext *C, void *cumap_v, int event) -{ - CurveMapping *cumap = cumap_v; - CurveMap *cuma = cumap->cm + cumap->cur; - - switch (event) { - case UICURVE_FUNC_RESET_NEG: - case UICURVE_FUNC_RESET_POS: /* reset */ - BKE_curvemap_reset(cuma, - &cumap->clipr, - cumap->preset, - (event == UICURVE_FUNC_RESET_NEG) ? CURVEMAP_SLOPE_NEGATIVE : - CURVEMAP_SLOPE_POSITIVE); - BKE_curvemapping_changed(cumap, false); - break; - case UICURVE_FUNC_RESET_VIEW: - BKE_curvemapping_reset_view(cumap); - break; - case UICURVE_FUNC_HANDLE_VECTOR: /* set vector */ - BKE_curvemap_handle_set(cuma, HD_VECT); - BKE_curvemapping_changed(cumap, false); - break; - case UICURVE_FUNC_HANDLE_AUTO: /* set auto */ - BKE_curvemap_handle_set(cuma, HD_AUTO); - BKE_curvemapping_changed(cumap, false); - break; - case UICURVE_FUNC_HANDLE_AUTO_ANIM: /* set auto-clamped */ - BKE_curvemap_handle_set(cuma, HD_AUTO_ANIM); - BKE_curvemapping_changed(cumap, false); - break; - case UICURVE_FUNC_EXTEND_HOZ: /* extend horiz */ - cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; - BKE_curvemapping_changed(cumap, false); - break; - case UICURVE_FUNC_EXTEND_EXP: /* extend extrapolate */ - cumap->flag |= CUMA_EXTEND_EXTRAPOLATE; - BKE_curvemapping_changed(cumap, false); - break; - } - ED_undo_push(C, "CurveMap tools"); - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static uiBlock *curvemap_tools_func( - bContext *C, ARegion *region, CurveMapping *cumap, bool show_extend, int reset_mode) -{ - short yco = 0; - const short menuwidth = 10 * UI_UNIT_X; - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); - UI_block_func_butmenu_set(block, curvemap_tools_dofunc, cumap); - - { - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Reset View"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UICURVE_FUNC_RESET_VIEW, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Vector Handle"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UICURVE_FUNC_HANDLE_VECTOR, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Auto Handle"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UICURVE_FUNC_HANDLE_AUTO, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Auto Clamped Handle"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UICURVE_FUNC_HANDLE_AUTO_ANIM, - ""); - } - - if (show_extend) { - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Extend Horizontal"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UICURVE_FUNC_EXTEND_HOZ, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Extend Extrapolated"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UICURVE_FUNC_EXTEND_EXP, - ""); - } - - { - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Reset Curve"), - 0, - yco -= UI_UNIT_Y, - menuwidth, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - reset_mode, - ""); - } - - UI_block_direction_set(block, UI_DIR_DOWN); - UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X); - - return block; -} - -static uiBlock *curvemap_tools_posslope_func(bContext *C, ARegion *region, void *cumap_v) -{ - return curvemap_tools_func(C, region, cumap_v, true, UICURVE_FUNC_RESET_POS); -} - -static uiBlock *curvemap_tools_negslope_func(bContext *C, ARegion *region, void *cumap_v) -{ - return curvemap_tools_func(C, region, cumap_v, true, UICURVE_FUNC_RESET_NEG); -} - -static uiBlock *curvemap_brush_tools_func(bContext *C, ARegion *region, void *cumap_v) -{ - return curvemap_tools_func(C, region, cumap_v, false, UICURVE_FUNC_RESET_NEG); -} - -static uiBlock *curvemap_brush_tools_negslope_func(bContext *C, ARegion *region, void *cumap_v) -{ - return curvemap_tools_func(C, region, cumap_v, false, UICURVE_FUNC_RESET_POS); -} - -static void curvemap_buttons_redraw(bContext *C, void *UNUSED(arg1), void *UNUSED(arg2)) -{ - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static void curvemap_buttons_update(bContext *C, void *arg1_v, void *cumap_v) -{ - CurveMapping *cumap = cumap_v; - BKE_curvemapping_changed(cumap, true); - rna_update_cb(C, arg1_v, NULL); -} - -static void curvemap_buttons_reset(bContext *C, void *cb_v, void *cumap_v) -{ - CurveMapping *cumap = cumap_v; - cumap->preset = CURVE_PRESET_LINE; - for (int a = 0; a < CM_TOT; a++) { - BKE_curvemap_reset(cumap->cm + a, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_POSITIVE); - } - - cumap->black[0] = cumap->black[1] = cumap->black[2] = 0.0f; - cumap->white[0] = cumap->white[1] = cumap->white[2] = 1.0f; - BKE_curvemapping_set_black_white(cumap, NULL, NULL); - - BKE_curvemapping_changed(cumap, false); - - rna_update_cb(C, cb_v, NULL); -} - -/** - * \note Still unsure how this call evolves. - * - * \param labeltype: Used for defining which curve-channels to show. - */ -static void curvemap_buttons_layout(uiLayout *layout, - PointerRNA *ptr, - char labeltype, - bool levels, - bool brush, - bool neg_slope, - bool tone, - RNAUpdateCb *cb) -{ - CurveMapping *cumap = ptr->data; - CurveMap *cm = &cumap->cm[cumap->cur]; - uiBut *bt; - const float dx = UI_UNIT_X; - int bg = -1; - - uiBlock *block = uiLayoutGetBlock(layout); - - if (tone) { - uiLayout *split = uiLayoutSplit(layout, 0.0f, false); - uiItemR(uiLayoutRow(split, false), ptr, "tone", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - } - - /* curve chooser */ - uiLayout *row = uiLayoutRow(layout, false); - - if (labeltype == 'v') { - /* vector */ - uiLayout *sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); - - if (cumap->cm[0].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "X", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[1].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "Y", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[2].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "Z", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - } - else if (labeltype == 'c') { - /* color */ - uiLayout *sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); - - if (cumap->cm[3].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "C", 0, 0, dx, dx, &cumap->cur, 0.0, 3.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[0].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "R", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[1].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "G", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[2].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "B", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - } - else if (labeltype == 'h') { - /* HSV */ - uiLayout *sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); - - if (cumap->cm[0].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "H", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[1].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "S", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - if (cumap->cm[2].curve) { - bt = uiDefButI( - block, UI_BTYPE_ROW, 0, "V", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); - UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); - } - } - else { - uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT); - } - - if (labeltype == 'h') { - bg = UI_GRAD_H; - } - - /* operation buttons */ - uiLayoutRow(row, true); - - UI_block_emboss_set(block, UI_EMBOSS_NONE); - - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_ZOOM_IN, - 0, - 0, - dx, - dx, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Zoom in")); - UI_but_func_set(bt, curvemap_buttons_zoom_in, cumap, NULL); - - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_ZOOM_OUT, - 0, - 0, - dx, - dx, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Zoom out")); - UI_but_func_set(bt, curvemap_buttons_zoom_out, cumap, NULL); - - if (brush && neg_slope) { - bt = uiDefIconBlockBut(block, - curvemap_brush_tools_negslope_func, - cumap, - 0, - ICON_DOWNARROW_HLT, - 0, - 0, - dx, - dx, - TIP_("Tools")); - } - else if (brush) { - bt = uiDefIconBlockBut(block, - curvemap_brush_tools_func, - cumap, - 0, - ICON_DOWNARROW_HLT, - 0, - 0, - dx, - dx, - TIP_("Tools")); - } - else if (neg_slope) { - bt = uiDefIconBlockBut(block, - curvemap_tools_negslope_func, - cumap, - 0, - ICON_DOWNARROW_HLT, - 0, - 0, - dx, - dx, - TIP_("Tools")); - } - else { - bt = uiDefIconBlockBut(block, - curvemap_tools_posslope_func, - cumap, - 0, - ICON_DOWNARROW_HLT, - 0, - 0, - dx, - dx, - TIP_("Tools")); - } - - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - - const int icon = (cumap->flag & CUMA_DO_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT; - bt = uiDefIconBlockBut( - block, curvemap_clipping_func, cumap, 0, icon, 0, 0, dx, dx, TIP_("Clipping Options")); - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_X, - 0, - 0, - dx, - dx, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Delete points")); - UI_but_funcN_set(bt, curvemap_buttons_delete, MEM_dupallocN(cb), cumap); - - UI_block_emboss_set(block, UI_EMBOSS); - - UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL); - - /* curve itself */ - const int size = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X); - row = uiLayoutRow(layout, false); - uiButCurveMapping *curve_but = (uiButCurveMapping *)uiDefBut( - block, UI_BTYPE_CURVE, 0, "", 0, 0, size, 8.0f * UI_UNIT_X, cumap, 0.0f, 1.0f, 0, 0, ""); - curve_but->gradient_type = bg; - - /* sliders for selected point */ - CurveMapPoint *cmp = NULL; - for (int i = 0; i < cm->totpoint; i++) { - if (cm->curve[i].flag & CUMA_SELECT) { - cmp = &cm->curve[i]; - break; - } - } - - if (cmp) { - rctf bounds; - if (cumap->flag & CUMA_DO_CLIP) { - bounds = cumap->clipr; - } - else { - bounds.xmin = bounds.ymin = -1000.0; - bounds.xmax = bounds.ymax = 1000.0; - } - - uiLayoutRow(layout, true); - UI_block_funcN_set(block, curvemap_buttons_update, MEM_dupallocN(cb), cumap); - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - "X", - 0, - 2 * UI_UNIT_Y, - UI_UNIT_X * 10, - UI_UNIT_Y, - &cmp->x, - bounds.xmin, - bounds.xmax, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 1); - UI_but_number_precision_set(bt, 5); - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - "Y", - 0, - 1 * UI_UNIT_Y, - UI_UNIT_X * 10, - UI_UNIT_Y, - &cmp->y, - bounds.ymin, - bounds.ymax, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 1); - UI_but_number_precision_set(bt, 5); - } - - /* black/white levels */ - if (levels) { - uiLayout *split = uiLayoutSplit(layout, 0.0f, false); - uiItemR(uiLayoutColumn(split, false), ptr, "black_level", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - uiItemR(uiLayoutColumn(split, false), ptr, "white_level", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - - uiLayoutRow(layout, false); - bt = uiDefBut(block, - UI_BTYPE_BUT, - 0, - IFACE_("Reset"), - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0, - 0, - TIP_("Reset Black/White point and curves")); - UI_but_funcN_set(bt, curvemap_buttons_reset, MEM_dupallocN(cb), cumap); - } - - UI_block_funcN_set(block, NULL, NULL, NULL); -} - -void uiTemplateCurveMapping(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - int type, - bool levels, - bool brush, - bool neg_slope, - bool tone) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - uiBlock *block = uiLayoutGetBlock(layout); - - if (!prop) { - RNA_warning("curve property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - if (RNA_property_type(prop) != PROP_POINTER) { - RNA_warning("curve is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_CurveMapping)) { - return; - } - - RNAUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); - cb->ptr = *ptr; - cb->prop = prop; - - ID *id = cptr.owner_id; - UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); - - curvemap_buttons_layout(layout, &cptr, type, levels, brush, neg_slope, tone, cb); - - UI_block_lock_clear(block); - - MEM_freeN(cb); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Curve Profile Template - * \{ */ - -static void CurveProfile_presets_dofunc(bContext *C, void *profile_v, int event) -{ - CurveProfile *profile = profile_v; - - profile->preset = event; - BKE_curveprofile_reset(profile); - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - - ED_undo_push(C, "CurveProfile tools"); - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static uiBlock *CurveProfile_presets_func(bContext *C, ARegion *region, CurveProfile *profile) -{ - short yco = 0; - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); - UI_block_func_butmenu_set(block, CurveProfile_presets_dofunc, profile); - - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Default"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - PROF_PRESET_LINE, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Support Loops"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - PROF_PRESET_SUPPORTS, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Cornice Molding"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - PROF_PRESET_CORNICE, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Crown Molding"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - PROF_PRESET_CROWN, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Steps"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - PROF_PRESET_STEPS, - ""); - - UI_block_direction_set(block, UI_DIR_DOWN); - UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X)); - - return block; -} - -static uiBlock *CurveProfile_buttons_presets(bContext *C, ARegion *region, void *profile_v) -{ - return CurveProfile_presets_func(C, region, (CurveProfile *)profile_v); -} - -/* Only for CurveProfile tools block */ -enum { - UIPROFILE_FUNC_RESET, - UIPROFILE_FUNC_RESET_VIEW, -}; - -static void CurveProfile_tools_dofunc(bContext *C, void *profile_v, int event) -{ - CurveProfile *profile = profile_v; - - switch (event) { - case UIPROFILE_FUNC_RESET: /* reset */ - BKE_curveprofile_reset(profile); - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - break; - case UIPROFILE_FUNC_RESET_VIEW: /* reset view to clipping rect */ - BKE_curveprofile_reset_view(profile); - break; - } - ED_undo_push(C, "CurveProfile tools"); - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static uiBlock *CurveProfile_tools_func(bContext *C, ARegion *region, CurveProfile *profile) -{ - short yco = 0; - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); - UI_block_func_butmenu_set(block, CurveProfile_tools_dofunc, profile); - - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Reset View"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UIPROFILE_FUNC_RESET_VIEW, - ""); - uiDefIconTextBut(block, - UI_BTYPE_BUT_MENU, - 1, - ICON_BLANK1, - IFACE_("Reset Curve"), - 0, - yco -= UI_UNIT_Y, - 0, - UI_UNIT_Y, - NULL, - 0.0, - 0.0, - 0, - UIPROFILE_FUNC_RESET, - ""); - - UI_block_direction_set(block, UI_DIR_DOWN); - UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X)); - - return block; -} - -static uiBlock *CurveProfile_buttons_tools(bContext *C, ARegion *region, void *profile_v) -{ - return CurveProfile_tools_func(C, region, (CurveProfile *)profile_v); -} - -static void CurveProfile_buttons_zoom_in(bContext *C, void *profile_v, void *UNUSED(arg)) -{ - CurveProfile *profile = profile_v; - - /* Allow a 20x zoom. */ - if (BLI_rctf_size_x(&profile->view_rect) > 0.04f * BLI_rctf_size_x(&profile->clip_rect)) { - const float dx = 0.1154f * BLI_rctf_size_x(&profile->view_rect); - profile->view_rect.xmin += dx; - profile->view_rect.xmax -= dx; - const float dy = 0.1154f * BLI_rctf_size_y(&profile->view_rect); - profile->view_rect.ymin += dy; - profile->view_rect.ymax -= dy; - } - - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static void CurveProfile_buttons_zoom_out(bContext *C, void *profile_v, void *UNUSED(arg)) -{ - CurveProfile *profile = profile_v; - - /* Allow 20 times zoom, but don't view outside clip */ - if (BLI_rctf_size_x(&profile->view_rect) < 20.0f * BLI_rctf_size_x(&profile->clip_rect)) { - float d = 0.15f * BLI_rctf_size_x(&profile->view_rect); - float d1 = d; - - if (profile->flag & PROF_USE_CLIP) { - if (profile->view_rect.xmin - d < profile->clip_rect.xmin) { - d1 = profile->view_rect.xmin - profile->clip_rect.xmin; - } - } - profile->view_rect.xmin -= d1; - - d1 = d; - if (profile->flag & PROF_USE_CLIP) { - if (profile->view_rect.xmax + d > profile->clip_rect.xmax) { - d1 = -profile->view_rect.xmax + profile->clip_rect.xmax; - } - } - profile->view_rect.xmax += d1; - - d = d1 = 0.15f * BLI_rctf_size_y(&profile->view_rect); - - if (profile->flag & PROF_USE_CLIP) { - if (profile->view_rect.ymin - d < profile->clip_rect.ymin) { - d1 = profile->view_rect.ymin - profile->clip_rect.ymin; - } - } - profile->view_rect.ymin -= d1; - - d1 = d; - if (profile->flag & PROF_USE_CLIP) { - if (profile->view_rect.ymax + d > profile->clip_rect.ymax) { - d1 = -profile->view_rect.ymax + profile->clip_rect.ymax; - } - } - profile->view_rect.ymax += d1; - } - - ED_region_tag_redraw(CTX_wm_region(C)); -} - -static void CurveProfile_clipping_toggle(bContext *C, void *cb_v, void *profile_v) -{ - CurveProfile *profile = profile_v; - - profile->flag ^= PROF_USE_CLIP; - - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - rna_update_cb(C, cb_v, NULL); -} - -static void CurveProfile_buttons_reverse(bContext *C, void *cb_v, void *profile_v) -{ - CurveProfile *profile = profile_v; - - BKE_curveprofile_reverse(profile); - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - rna_update_cb(C, cb_v, NULL); -} - -static void CurveProfile_buttons_delete(bContext *C, void *cb_v, void *profile_v) -{ - CurveProfile *profile = profile_v; - - BKE_curveprofile_remove_by_flag(profile, SELECT); - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - - rna_update_cb(C, cb_v, NULL); -} - -static void CurveProfile_buttons_update(bContext *C, void *arg1_v, void *profile_v) -{ - CurveProfile *profile = profile_v; - BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP); - rna_update_cb(C, arg1_v, NULL); -} - -static void CurveProfile_buttons_reset(bContext *C, void *arg1_v, void *profile_v) -{ - CurveProfile *profile = profile_v; - BKE_curveprofile_reset(profile); - BKE_curveprofile_update(profile, PROF_UPDATE_NONE); - rna_update_cb(C, arg1_v, NULL); -} - -static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUpdateCb *cb) -{ - CurveProfile *profile = ptr->data; - uiBut *bt; - - uiBlock *block = uiLayoutGetBlock(layout); - - UI_block_emboss_set(block, UI_EMBOSS); - - uiLayoutSetPropSep(layout, false); - - /* Preset selector */ - /* There is probably potential to use simpler "uiItemR" functions here, but automatic updating - * after a preset is selected would be more complicated. */ - uiLayout *row = uiLayoutRow(layout, true); - bt = uiDefBlockBut( - block, CurveProfile_buttons_presets, profile, "Preset", 0, 0, UI_UNIT_X, UI_UNIT_X, ""); - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - - /* Show a "re-apply" preset button when it has been changed from the preset. */ - if (profile->flag & PROF_DIRTY_PRESET) { - /* Only for dynamic presets. */ - if (ELEM(profile->preset, PROF_PRESET_STEPS, PROF_PRESET_SUPPORTS)) { - bt = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_NONE, - "Apply Preset", - 0, - 0, - UI_UNIT_X, - UI_UNIT_X, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - "Reapply and update the preset, removing changes"); - UI_but_funcN_set(bt, CurveProfile_buttons_reset, MEM_dupallocN(cb), profile); - } - } - - row = uiLayoutRow(layout, false); - - /* (Left aligned) */ - uiLayout *sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); - - /* Zoom in */ - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_ZOOM_IN, - 0, - 0, - UI_UNIT_X, - UI_UNIT_X, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Zoom in")); - UI_but_func_set(bt, CurveProfile_buttons_zoom_in, profile, NULL); - - /* Zoom out */ - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_ZOOM_OUT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_X, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Zoom out")); - UI_but_func_set(bt, CurveProfile_buttons_zoom_out, profile, NULL); - - /* (Right aligned) */ - sub = uiLayoutRow(row, true); - uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT); - - /* Reset view, reset curve */ - bt = uiDefIconBlockBut( - block, CurveProfile_buttons_tools, profile, 0, 0, 0, 0, UI_UNIT_X, UI_UNIT_X, TIP_("Tools")); - UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); - - /* Flip path */ - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_ARROW_LEFTRIGHT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_X, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Reverse Path")); - UI_but_funcN_set(bt, CurveProfile_buttons_reverse, MEM_dupallocN(cb), profile); - - /* Clipping toggle */ - const int icon = (profile->flag & PROF_USE_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT; - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - icon, - 0, - 0, - UI_UNIT_X, - UI_UNIT_X, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Toggle Profile Clipping")); - UI_but_funcN_set(bt, CurveProfile_clipping_toggle, MEM_dupallocN(cb), profile); - - UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL); - - /* The path itself */ - int path_width = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X); - path_width = min_ii(path_width, (int)(16.0f * UI_UNIT_X)); - const int path_height = path_width; - uiLayoutRow(layout, false); - uiDefBut(block, - UI_BTYPE_CURVEPROFILE, - 0, - "", - 0, - 0, - (short)path_width, - (short)path_height, - profile, - 0.0f, - 1.0f, - -1, - 0, - ""); - - /* Position sliders for (first) selected point */ - int i; - float *selection_x, *selection_y; - bool point_last_or_first = false; - CurveProfilePoint *point = NULL; - for (i = 0; i < profile->path_len; i++) { - if (profile->path[i].flag & PROF_SELECT) { - point = &profile->path[i]; - selection_x = &point->x; - selection_y = &point->y; - break; - } - if (profile->path[i].flag & PROF_H1_SELECT) { - point = &profile->path[i]; - selection_x = &point->h1_loc[0]; - selection_y = &point->h1_loc[1]; - } - else if (profile->path[i].flag & PROF_H2_SELECT) { - point = &profile->path[i]; - selection_x = &point->h2_loc[0]; - selection_y = &point->h2_loc[1]; - } - } - if (ELEM(i, 0, profile->path_len - 1)) { - point_last_or_first = true; - } - - /* Selected point data */ - rctf bounds; - if (point) { - if (profile->flag & PROF_USE_CLIP) { - bounds = profile->clip_rect; - } - else { - bounds.xmin = bounds.ymin = -1000.0; - bounds.xmax = bounds.ymax = 1000.0; - } - - row = uiLayoutRow(layout, true); - - PointerRNA point_ptr; - RNA_pointer_create(ptr->owner_id, &RNA_CurveProfilePoint, point, &point_ptr); - PropertyRNA *prop_handle_type = RNA_struct_find_property(&point_ptr, "handle_type_1"); - uiItemFullR(row, - &point_ptr, - prop_handle_type, - RNA_NO_INDEX, - 0, - UI_ITEM_R_EXPAND | UI_ITEM_R_ICON_ONLY, - "", - ICON_NONE); - - /* Position */ - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - "X:", - 0, - 2 * UI_UNIT_Y, - UI_UNIT_X * 10, - UI_UNIT_Y, - selection_x, - bounds.xmin, - bounds.xmax, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 1); - UI_but_number_precision_set(bt, 5); - UI_but_funcN_set(bt, CurveProfile_buttons_update, MEM_dupallocN(cb), profile); - if (point_last_or_first) { - UI_but_flag_enable(bt, UI_BUT_DISABLED); - } - bt = uiDefButF(block, - UI_BTYPE_NUM, - 0, - "Y:", - 0, - 1 * UI_UNIT_Y, - UI_UNIT_X * 10, - UI_UNIT_Y, - selection_y, - bounds.ymin, - bounds.ymax, - 0, - 0, - ""); - UI_but_number_step_size_set(bt, 1); - UI_but_number_precision_set(bt, 5); - UI_but_funcN_set(bt, CurveProfile_buttons_update, MEM_dupallocN(cb), profile); - if (point_last_or_first) { - UI_but_flag_enable(bt, UI_BUT_DISABLED); - } - - /* Delete points */ - bt = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_X, - 0, - 0, - UI_UNIT_X, - UI_UNIT_X, - NULL, - 0.0, - 0.0, - 0.0, - 0.0, - TIP_("Delete points")); - UI_but_funcN_set(bt, CurveProfile_buttons_delete, MEM_dupallocN(cb), profile); - if (point_last_or_first) { - UI_but_flag_enable(bt, UI_BUT_DISABLED); - } - } - - uiItemR(layout, ptr, "use_sample_straight_edges", 0, NULL, ICON_NONE); - uiItemR(layout, ptr, "use_sample_even_lengths", 0, NULL, ICON_NONE); - - UI_block_funcN_set(block, NULL, NULL, NULL); -} - -/** - * Template for a path creation widget intended for custom bevel profiles. - * This section is quite similar to #uiTemplateCurveMapping, but with reduced complexity. - */ -void uiTemplateCurveProfile(uiLayout *layout, PointerRNA *ptr, const char *propname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - uiBlock *block = uiLayoutGetBlock(layout); - - if (!prop) { - RNA_warning( - "Curve Profile property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - if (RNA_property_type(prop) != PROP_POINTER) { - RNA_warning( - "Curve Profile is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_CurveProfile)) { - return; - } - - /* Share update functionality with the CurveMapping widget template. */ - RNAUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); - cb->ptr = *ptr; - cb->prop = prop; - - ID *id = cptr.owner_id; - UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); - - CurveProfile_buttons_layout(layout, &cptr, cb); - - UI_block_lock_clear(block); - - MEM_freeN(cb); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name ColorPicker Template - * \{ */ - -#define WHEEL_SIZE (5 * U.widget_unit) - -/* This template now follows User Preference for type - name is not correct anymore... */ -void uiTemplateColorPicker(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - bool value_slider, - bool lock, - bool lock_luminosity, - bool cubic) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - uiBlock *block = uiLayoutGetBlock(layout); - ColorPicker *cpicker = ui_block_colorpicker_create(block); - - if (!prop) { - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - float softmin, softmax, step, precision; - RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); - - uiLayout *col = uiLayoutColumn(layout, true); - uiLayout *row = uiLayoutRow(col, true); - - uiBut *but = NULL; - uiButHSVCube *hsv_but; - switch (U.color_picker_type) { - case USER_CP_SQUARE_SV: - case USER_CP_SQUARE_HS: - case USER_CP_SQUARE_HV: - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - 0, - 0, - WHEEL_SIZE, - WHEEL_SIZE, - ptr, - prop, - -1, - 0.0, - 0.0, - 0, - 0, - ""); - switch (U.color_picker_type) { - case USER_CP_SQUARE_SV: - hsv_but->gradient_type = UI_GRAD_SV; - break; - case USER_CP_SQUARE_HS: - hsv_but->gradient_type = UI_GRAD_HS; - break; - case USER_CP_SQUARE_HV: - hsv_but->gradient_type = UI_GRAD_HV; - break; - } - but = &hsv_but->but; - break; - - /* user default */ - case USER_CP_CIRCLE_HSV: - case USER_CP_CIRCLE_HSL: - default: - but = uiDefButR_prop(block, - UI_BTYPE_HSVCIRCLE, - 0, - "", - 0, - 0, - WHEEL_SIZE, - WHEEL_SIZE, - ptr, - prop, - -1, - 0.0, - 0.0, - 0, - 0, - ""); - break; - } - - but->custom_data = cpicker; - - cpicker->use_color_lock = lock; - cpicker->use_color_cubic = cubic; - cpicker->use_luminosity_lock = lock_luminosity; - - if (lock_luminosity) { - float color[4]; /* in case of alpha */ - RNA_property_float_get_array(ptr, prop, color); - but->a2 = len_v3(color); - cpicker->luminosity_lock_value = len_v3(color); - } - - if (value_slider) { - switch (U.color_picker_type) { - case USER_CP_CIRCLE_HSL: - uiItemS(row); - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - WHEEL_SIZE + 6, - 0, - 14 * UI_DPI_FAC, - WHEEL_SIZE, - ptr, - prop, - -1, - softmin, - softmax, - 0, - 0, - ""); - hsv_but->gradient_type = UI_GRAD_L_ALT; - break; - case USER_CP_SQUARE_SV: - uiItemS(col); - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - 0, - 4, - WHEEL_SIZE, - 18 * UI_DPI_FAC, - ptr, - prop, - -1, - softmin, - softmax, - 0, - 0, - ""); - hsv_but->gradient_type = UI_GRAD_SV + 3; - break; - case USER_CP_SQUARE_HS: - uiItemS(col); - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - 0, - 4, - WHEEL_SIZE, - 18 * UI_DPI_FAC, - ptr, - prop, - -1, - softmin, - softmax, - 0, - 0, - ""); - hsv_but->gradient_type = UI_GRAD_HS + 3; - break; - case USER_CP_SQUARE_HV: - uiItemS(col); - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - 0, - 4, - WHEEL_SIZE, - 18 * UI_DPI_FAC, - ptr, - prop, - -1, - softmin, - softmax, - 0, - 0, - ""); - hsv_but->gradient_type = UI_GRAD_HV + 3; - break; - - /* user default */ - case USER_CP_CIRCLE_HSV: - default: - uiItemS(row); - hsv_but = (uiButHSVCube *)uiDefButR_prop(block, - UI_BTYPE_HSVCUBE, - 0, - "", - WHEEL_SIZE + 6, - 0, - 14 * UI_DPI_FAC, - WHEEL_SIZE, - ptr, - prop, - -1, - softmin, - softmax, - 0, - 0, - ""); - hsv_but->gradient_type = UI_GRAD_V_ALT; - break; - } - - hsv_but->but.custom_data = cpicker; - } -} - -static void ui_template_palette_menu(bContext *UNUSED(C), uiLayout *layout, void *UNUSED(but_p)) -{ - uiLayout *row; - - uiItemL(layout, IFACE_("Sort By:"), ICON_NONE); - row = uiLayoutRow(layout, false); - uiItemEnumO_value(row, IFACE_("Hue"), ICON_NONE, "PALETTE_OT_sort", "type", 1); - row = uiLayoutRow(layout, false); - uiItemEnumO_value(row, IFACE_("Saturation"), ICON_NONE, "PALETTE_OT_sort", "type", 2); - row = uiLayoutRow(layout, false); - uiItemEnumO_value(row, IFACE_("Value"), ICON_NONE, "PALETTE_OT_sort", "type", 3); - row = uiLayoutRow(layout, false); - uiItemEnumO_value(row, IFACE_("Luminance"), ICON_NONE, "PALETTE_OT_sort", "type", 4); -} - -void uiTemplatePalette(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - bool UNUSED(colors)) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - uiBut *but = NULL; - - const int cols_per_row = MAX2(uiLayoutGetWidth(layout) / UI_UNIT_X, 1); - - if (!prop) { - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); - if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Palette)) { - return; - } - - uiBlock *block = uiLayoutGetBlock(layout); - - Palette *palette = cptr.data; - - uiLayout *col = uiLayoutColumn(layout, true); - uiLayoutRow(col, true); - uiDefIconButO(block, - UI_BTYPE_BUT, - "PALETTE_OT_color_add", - WM_OP_INVOKE_DEFAULT, - ICON_ADD, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - uiDefIconButO(block, - UI_BTYPE_BUT, - "PALETTE_OT_color_delete", - WM_OP_INVOKE_DEFAULT, - ICON_REMOVE, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - if (palette->colors.first != NULL) { - but = uiDefIconButO(block, - UI_BTYPE_BUT, - "PALETTE_OT_color_move", - WM_OP_INVOKE_DEFAULT, - ICON_TRIA_UP, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - UI_but_operator_ptr_get(but); - RNA_enum_set(but->opptr, "type", -1); - - but = uiDefIconButO(block, - UI_BTYPE_BUT, - "PALETTE_OT_color_move", - WM_OP_INVOKE_DEFAULT, - ICON_TRIA_DOWN, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - UI_but_operator_ptr_get(but); - RNA_enum_set(but->opptr, "type", 1); - - /* Menu. */ - uiDefIconMenuBut( - block, ui_template_palette_menu, NULL, ICON_SORTSIZE, 0, 0, UI_UNIT_X, UI_UNIT_Y, ""); - } - - col = uiLayoutColumn(layout, true); - uiLayoutRow(col, true); - - int row_cols = 0, col_id = 0; - LISTBASE_FOREACH (PaletteColor *, color, &palette->colors) { - if (row_cols >= cols_per_row) { - uiLayoutRow(col, true); - row_cols = 0; - } - - PointerRNA color_ptr; - RNA_pointer_create(&palette->id, &RNA_PaletteColor, color, &color_ptr); - uiButColor *color_but = (uiButColor *)uiDefButR(block, - UI_BTYPE_COLOR, - 0, - "", - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - &color_ptr, - "color", - -1, - 0.0, - 1.0, - 0.0, - 0.0, - ""); - color_but->is_pallete_color = true; - color_but->palette_color_index = col_id; - row_cols++; - col_id++; - } -} - -void uiTemplateCryptoPicker(uiLayout *layout, PointerRNA *ptr, const char *propname, int icon) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop) { - RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - uiBlock *block = uiLayoutGetBlock(layout); - - uiBut *but = uiDefIconTextButO(block, - UI_BTYPE_BUT, - "UI_OT_eyedropper_color", - WM_OP_INVOKE_DEFAULT, - icon, - "", - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - RNA_property_ui_description(prop)); - but->rnapoin = *ptr; - but->rnaprop = prop; - but->rnaindex = -1; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Layer Buttons Template - * \{ */ - -static void handle_layer_buttons(bContext *C, void *arg1, void *arg2) -{ - uiBut *but = arg1; - const int cur = POINTER_AS_INT(arg2); - wmWindow *win = CTX_wm_window(C); - const int shift = win->eventstate->shift; - - if (!shift) { - const int tot = RNA_property_array_length(&but->rnapoin, but->rnaprop); - - /* Normally clicking only selects one layer */ - RNA_property_boolean_set_index(&but->rnapoin, but->rnaprop, cur, true); - for (int i = 0; i < tot; i++) { - if (i != cur) { - RNA_property_boolean_set_index(&but->rnapoin, but->rnaprop, i, false); - } - } - } - - /* view3d layer change should update depsgraph (invisible object changed maybe) */ - /* see view3d_header.c */ -} - -/** - * \todo for now, grouping of layers is determined by dividing up the length of - * the array of layer bitflags - */ -void uiTemplateLayers(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - PointerRNA *used_ptr, - const char *used_propname, - int active_layer) -{ - const int cols_per_group = 5; - - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - if (!prop) { - RNA_warning("layers property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); - return; - } - - /* the number of layers determines the way we group them - * - we want 2 rows only (for now) - * - The number of columns (cols) is the total number of buttons per row the 'remainder' - * is added to this, as it will be ok to have first row slightly wider if need be. - * - For now, only split into groups if group will have at least 5 items. - */ - const int layers = RNA_property_array_length(ptr, prop); - const int cols = (layers / 2) + (layers % 2); - const int groups = ((cols / 2) < cols_per_group) ? (1) : (cols / cols_per_group); - - PropertyRNA *used_prop = NULL; - if (used_ptr && used_propname) { - used_prop = RNA_struct_find_property(used_ptr, used_propname); - if (!used_prop) { - RNA_warning("used layers property not found: %s.%s", - RNA_struct_identifier(ptr->type), - used_propname); - return; - } - - if (RNA_property_array_length(used_ptr, used_prop) < layers) { - used_prop = NULL; - } - } - - /* layers are laid out going across rows, with the columns being divided into groups */ - - for (int group = 0; group < groups; group++) { - uiLayout *uCol = uiLayoutColumn(layout, true); - - for (int row = 0; row < 2; row++) { - uiLayout *uRow = uiLayoutRow(uCol, true); - uiBlock *block = uiLayoutGetBlock(uRow); - int layer = groups * cols_per_group * row + cols_per_group * group; - - /* add layers as toggle buts */ - for (int col = 0; (col < cols_per_group) && (layer < layers); col++, layer++) { - int icon = 0; - const int butlay = 1 << layer; - - if (active_layer & butlay) { - icon = ICON_LAYER_ACTIVE; - } - else if (used_prop && RNA_property_boolean_get_index(used_ptr, used_prop, layer)) { - icon = ICON_LAYER_USED; - } - - uiBut *but = uiDefAutoButR( - block, ptr, prop, layer, "", icon, 0, 0, UI_UNIT_X / 2, UI_UNIT_Y / 2); - UI_but_func_set(but, handle_layer_buttons, but, POINTER_FROM_INT(layer)); - but->type = UI_BTYPE_TOGGLE; - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name List Template - * \{ */ - -static void uilist_draw_item_default(struct uiList *ui_list, - struct bContext *UNUSED(C), - struct uiLayout *layout, - struct PointerRNA *UNUSED(dataptr), - struct PointerRNA *itemptr, - int icon, - struct PointerRNA *UNUSED(active_dataptr), - const char *UNUSED(active_propname), - int UNUSED(index), - int UNUSED(flt_flag)) -{ - PropertyRNA *nameprop = RNA_struct_name_property(itemptr->type); - - /* Simplest one! */ - switch (ui_list->layout_type) { - case UILST_LAYOUT_GRID: - uiItemL(layout, "", icon); - break; - case UILST_LAYOUT_DEFAULT: - case UILST_LAYOUT_COMPACT: - default: - if (nameprop) { - uiItemFullR(layout, itemptr, nameprop, RNA_NO_INDEX, 0, UI_ITEM_R_NO_BG, "", icon); - } - else { - uiItemL(layout, "", icon); - } - break; - } -} - -static void uilist_draw_filter_default(struct uiList *ui_list, - struct bContext *UNUSED(C), - struct uiLayout *layout) -{ - PointerRNA listptr; - RNA_pointer_create(NULL, &RNA_UIList, ui_list, &listptr); - - uiLayout *row = uiLayoutRow(layout, false); - - uiLayout *subrow = uiLayoutRow(row, true); - uiItemR(subrow, &listptr, "filter_name", 0, "", ICON_NONE); - uiItemR(subrow, - &listptr, - "use_filter_invert", - UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, - "", - ICON_ARROW_LEFTRIGHT); - - if ((ui_list->filter_sort_flag & UILST_FLT_SORT_LOCK) == 0) { - subrow = uiLayoutRow(row, true); - uiItemR(subrow, - &listptr, - "use_filter_sort_alpha", - UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, - "", - ICON_NONE); - uiItemR(subrow, - &listptr, - "use_filter_sort_reverse", - UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, - "", - (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) ? ICON_SORT_DESC : ICON_SORT_ASC); - } -} - -typedef struct { - char name[MAX_IDPROP_NAME]; - int org_idx; -} StringCmp; - -static int cmpstringp(const void *p1, const void *p2) -{ - /* Case-insensitive comparison. */ - return BLI_strcasecmp(((StringCmp *)p1)->name, ((StringCmp *)p2)->name); -} - -static void uilist_filter_items_default(struct uiList *ui_list, - struct bContext *UNUSED(C), - struct PointerRNA *dataptr, - const char *propname) -{ - uiListDyn *dyn_data = ui_list->dyn_data; - PropertyRNA *prop = RNA_struct_find_property(dataptr, propname); - - const char *filter_raw = ui_list->filter_byname; - char *filter = (char *)filter_raw, filter_buff[32], *filter_dyn = NULL; - const bool filter_exclude = (ui_list->filter_flag & UILST_FLT_EXCLUDE) != 0; - const bool order_by_name = (ui_list->filter_sort_flag & UILST_FLT_SORT_MASK) == - UILST_FLT_SORT_ALPHA; - const int len = RNA_property_collection_length(dataptr, prop); - - dyn_data->items_shown = dyn_data->items_len = len; - - if (len && (order_by_name || filter_raw[0])) { - StringCmp *names = NULL; - int order_idx = 0, i = 0; - - if (order_by_name) { - names = MEM_callocN(sizeof(StringCmp) * len, "StringCmp"); - } - if (filter_raw[0]) { - const size_t slen = strlen(filter_raw); - - dyn_data->items_filter_flags = MEM_callocN(sizeof(int) * len, "items_filter_flags"); - dyn_data->items_shown = 0; - - /* Implicitly add heading/trailing wildcards if needed. */ - if (slen + 3 <= sizeof(filter_buff)) { - filter = filter_buff; - } - else { - filter = filter_dyn = MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn"); - } - BLI_strncpy_ensure_pad(filter, filter_raw, '*', slen + 3); - } - - RNA_PROP_BEGIN (dataptr, itemptr, prop) { - bool do_order = false; - - char *namebuf = RNA_struct_name_get_alloc(&itemptr, NULL, 0, NULL); - const char *name = namebuf ? namebuf : ""; - - if (filter[0]) { - /* Case-insensitive! */ - if (fnmatch(filter, name, FNM_CASEFOLD) == 0) { - dyn_data->items_filter_flags[i] = UILST_FLT_ITEM; - if (!filter_exclude) { - dyn_data->items_shown++; - do_order = order_by_name; - } - // printf("%s: '%s' matches '%s'\n", __func__, name, filter); - } - else if (filter_exclude) { - dyn_data->items_shown++; - do_order = order_by_name; - } - } - else { - do_order = order_by_name; - } - - if (do_order) { - names[order_idx].org_idx = order_idx; - BLI_strncpy(names[order_idx++].name, name, MAX_IDPROP_NAME); - } - - /* free name */ - if (namebuf) { - MEM_freeN(namebuf); - } - i++; - } - RNA_PROP_END; - - if (order_by_name) { - int new_idx; - /* note: order_idx equals either to ui_list->items_len if no filtering done, - * or to ui_list->items_shown if filter is enabled, - * or to (ui_list->items_len - ui_list->items_shown) if filtered items are excluded. - * This way, we only sort items we actually intend to draw! - */ - qsort(names, order_idx, sizeof(StringCmp), cmpstringp); - - dyn_data->items_filter_neworder = MEM_mallocN(sizeof(int) * order_idx, - "items_filter_neworder"); - for (new_idx = 0; new_idx < order_idx; new_idx++) { - dyn_data->items_filter_neworder[names[new_idx].org_idx] = new_idx; - } - } - - if (filter_dyn) { - MEM_freeN(filter_dyn); - } - if (names) { - MEM_freeN(names); - } - } -} - -typedef struct { - PointerRNA item; - int org_idx; - int flt_flag; -} _uilist_item; - -typedef struct { - int visual_items; /* Visual number of items (i.e. number of items we have room to display). */ - int start_idx; /* Index of first item to display. */ - int end_idx; /* Index of last item to display + 1. */ -} uiListLayoutdata; - -static void uilist_prepare(uiList *ui_list, - int len, - int activei, - int rows, - int maxrows, - int columns, - uiListLayoutdata *layoutdata) -{ - uiListDyn *dyn_data = ui_list->dyn_data; - const bool use_auto_size = (ui_list->list_grip < (rows - UI_LIST_AUTO_SIZE_THRESHOLD)); - - /* default rows */ - if (rows <= 0) { - rows = 5; - } - dyn_data->visual_height_min = rows; - if (maxrows < rows) { - maxrows = max_ii(rows, 5); - } - if (columns <= 0) { - columns = 9; - } - - int activei_row; - if (columns > 1) { - dyn_data->height = (int)ceil((double)len / (double)columns); - activei_row = (int)floor((double)activei / (double)columns); - } - else { - dyn_data->height = len; - activei_row = activei; - } - - if (!use_auto_size) { - /* No auto-size, yet we clamp at min size! */ - maxrows = rows = max_ii(ui_list->list_grip, rows); - } - else if ((rows != maxrows) && (dyn_data->height > rows)) { - /* Expand size if needed and possible. */ - rows = min_ii(dyn_data->height, maxrows); - } - - /* If list length changes or list is tagged to check this, - * and active is out of view, scroll to it .*/ - if (ui_list->list_last_len != len || ui_list->flag & UILST_SCROLL_TO_ACTIVE_ITEM) { - if (activei_row < ui_list->list_scroll) { - ui_list->list_scroll = activei_row; - } - else if (activei_row >= ui_list->list_scroll + rows) { - ui_list->list_scroll = activei_row - rows + 1; - } - ui_list->flag &= ~UILST_SCROLL_TO_ACTIVE_ITEM; - } - - const int max_scroll = max_ii(0, dyn_data->height - rows); - CLAMP(ui_list->list_scroll, 0, max_scroll); - ui_list->list_last_len = len; - dyn_data->visual_height = rows; - layoutdata->visual_items = rows * columns; - layoutdata->start_idx = ui_list->list_scroll * columns; - layoutdata->end_idx = min_ii(layoutdata->start_idx + rows * columns, len); -} - -static void uilist_resize_update_cb(bContext *C, void *arg1, void *UNUSED(arg2)) -{ - uiList *ui_list = arg1; - uiListDyn *dyn_data = ui_list->dyn_data; - - /* This way we get diff in number of additional items to show (positive) or hide (negative). */ - const int diff = round_fl_to_int((float)(dyn_data->resize - dyn_data->resize_prev) / - (float)UI_UNIT_Y); - - if (diff != 0) { - ui_list->list_grip += diff; - dyn_data->resize_prev += diff * UI_UNIT_Y; - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; - } - - /* In case uilist is in popup, we need special refreshing */ - ED_region_tag_refresh_ui(CTX_wm_menu(C)); -} - -static void *uilist_item_use_dynamic_tooltip(PointerRNA *itemptr, const char *propname) -{ - if (propname && propname[0] && itemptr && itemptr->data) { - PropertyRNA *prop = RNA_struct_find_property(itemptr, propname); - - if (prop && (RNA_property_type(prop) == PROP_STRING)) { - return RNA_property_string_get_alloc(itemptr, prop, NULL, 0, NULL); - } - } - return NULL; -} - -static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const char *tip) -{ - char *dyn_tooltip = argN; - return BLI_sprintfN("%s - %s", tip, dyn_tooltip); -} - -void uiTemplateList(uiLayout *layout, - bContext *C, - const char *listtype_name, - const char *list_id, - PointerRNA *dataptr, - const char *propname, - PointerRNA *active_dataptr, - const char *active_propname, - const char *item_dyntip_propname, - int rows, - int maxrows, - int layout_type, - int columns, - bool sort_reverse, - bool sort_lock) -{ - PropertyRNA *prop = NULL, *activeprop; - _uilist_item *items_ptr = NULL; - uiLayout *glob = NULL, *box, *row, *col, *subrow, *sub, *overlap; - uiBut *but; - - uiListLayoutdata layoutdata; - char ui_list_id[UI_MAX_NAME_STR]; - char numstr[32]; - int rnaicon = ICON_NONE, icon = ICON_NONE; - int i = 0, activei = 0; - int len = 0; - - /* validate arguments */ - /* Forbid default UI_UL_DEFAULT_CLASS_NAME list class without a custom list_id! */ - if (STREQ(UI_UL_DEFAULT_CLASS_NAME, listtype_name) && !(list_id && list_id[0])) { - RNA_warning("template_list using default '%s' UIList class must provide a custom list_id", - UI_UL_DEFAULT_CLASS_NAME); - return; - } - - uiBlock *block = uiLayoutGetBlock(layout); - - if (!active_dataptr->data) { - RNA_warning("No active data"); - return; - } - - if (dataptr->data) { - prop = RNA_struct_find_property(dataptr, propname); - if (!prop) { - RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname); - return; - } - } - - activeprop = RNA_struct_find_property(active_dataptr, active_propname); - if (!activeprop) { - RNA_warning( - "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname); - return; - } - - if (prop) { - const PropertyType type = RNA_property_type(prop); - if (type != PROP_COLLECTION) { - RNA_warning("Expected a collection data property"); - return; - } - } - - const PropertyType activetype = RNA_property_type(activeprop); - if (activetype != PROP_INT) { - RNA_warning("Expected an integer active data property"); - return; - } - - /* get icon */ - if (dataptr->data && prop) { - StructRNA *ptype = RNA_property_pointer_type(dataptr, prop); - rnaicon = RNA_struct_ui_icon(ptype); - } - - /* get active data */ - activei = RNA_property_int_get(active_dataptr, activeprop); - - /* Find the uiList type. */ - uiListType *ui_list_type = WM_uilisttype_find(listtype_name, false); - - if (ui_list_type == NULL) { - RNA_warning("List type %s not found", listtype_name); - return; - } - - uiListDrawItemFunc draw_item = ui_list_type->draw_item ? ui_list_type->draw_item : - uilist_draw_item_default; - uiListDrawFilterFunc draw_filter = ui_list_type->draw_filter ? ui_list_type->draw_filter : - uilist_draw_filter_default; - uiListFilterItemsFunc filter_items = ui_list_type->filter_items ? ui_list_type->filter_items : - uilist_filter_items_default; - - /* Find or add the uiList to the current Region. */ - /* We tag the list id with the list type... */ - BLI_snprintf( - ui_list_id, sizeof(ui_list_id), "%s_%s", ui_list_type->idname, list_id ? list_id : ""); - - /* Allows to work in popups. */ - ARegion *region = CTX_wm_menu(C); - if (region == NULL) { - region = CTX_wm_region(C); - } - uiList *ui_list = BLI_findstring(®ion->ui_lists, ui_list_id, offsetof(uiList, list_id)); - - if (!ui_list) { - ui_list = MEM_callocN(sizeof(uiList), "uiList"); - BLI_strncpy(ui_list->list_id, ui_list_id, sizeof(ui_list->list_id)); - BLI_addtail(®ion->ui_lists, ui_list); - ui_list->list_grip = -UI_LIST_AUTO_SIZE_THRESHOLD; /* Force auto size by default. */ - if (sort_reverse) { - ui_list->filter_sort_flag |= UILST_FLT_SORT_REVERSE; - } - if (sort_lock) { - ui_list->filter_sort_flag |= UILST_FLT_SORT_LOCK; - } - } - - if (!ui_list->dyn_data) { - ui_list->dyn_data = MEM_callocN(sizeof(uiListDyn), "uiList.dyn_data"); - } - uiListDyn *dyn_data = ui_list->dyn_data; - - /* Because we can't actually pass type across save&load... */ - ui_list->type = ui_list_type; - ui_list->layout_type = layout_type; - - /* Reset filtering data. */ - MEM_SAFE_FREE(dyn_data->items_filter_flags); - MEM_SAFE_FREE(dyn_data->items_filter_neworder); - dyn_data->items_len = dyn_data->items_shown = -1; - - /* When active item changed since last draw, scroll to it. */ - if (activei != ui_list->list_last_activei) { - ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; - ui_list->list_last_activei = activei; - } - - /* Filter list items! (not for compact layout, though) */ - if (dataptr->data && prop) { - const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE; - const bool order_reverse = (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0; - int items_shown, idx = 0; -#if 0 - int prev_ii = -1, prev_i; -#endif - - if (layout_type == UILST_LAYOUT_COMPACT) { - dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length(dataptr, prop); - } - else { - // printf("%s: filtering...\n", __func__); - filter_items(ui_list, C, dataptr, propname); - // printf("%s: filtering done.\n", __func__); - } - - items_shown = dyn_data->items_shown; - if (items_shown >= 0) { - bool activei_mapping_pending = true; - items_ptr = MEM_mallocN(sizeof(_uilist_item) * items_shown, __func__); - // printf("%s: items shown: %d.\n", __func__, items_shown); - RNA_PROP_BEGIN (dataptr, itemptr, prop) { - if (!dyn_data->items_filter_flags || - ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) { - int ii; - if (dyn_data->items_filter_neworder) { - ii = dyn_data->items_filter_neworder[idx++]; - ii = order_reverse ? items_shown - ii - 1 : ii; - } - else { - ii = order_reverse ? items_shown - ++idx : idx++; - } - // printf("%s: ii: %d\n", __func__, ii); - items_ptr[ii].item = itemptr; - items_ptr[ii].org_idx = i; - items_ptr[ii].flt_flag = dyn_data->items_filter_flags ? dyn_data->items_filter_flags[i] : - 0; - - if (activei_mapping_pending && activei == i) { - activei = ii; - /* So that we do not map again activei! */ - activei_mapping_pending = false; - } -#if 0 /* For now, do not alter active element, even if it will be hidden... */ - else if (activei < i) { - /* We do not want an active but invisible item! - * Only exception is when all items are filtered out... - */ - if (prev_ii >= 0) { - activei = prev_ii; - RNA_property_int_set(active_dataptr, activeprop, prev_i); - } - else { - activei = ii; - RNA_property_int_set(active_dataptr, activeprop, i); - } - } - prev_i = i; - prev_ii = ii; -#endif - } - i++; - } - RNA_PROP_END; - - if (activei_mapping_pending) { - /* No active item found, set to 'invalid' -1 value... */ - activei = -1; - } - } - if (dyn_data->items_shown >= 0) { - len = dyn_data->items_shown; - } - else { - len = dyn_data->items_len; - } - } - - switch (layout_type) { - case UILST_LAYOUT_DEFAULT: - /* layout */ - box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop); - glob = uiLayoutColumn(box, true); - row = uiLayoutRow(glob, false); - col = uiLayoutColumn(row, true); - - /* init numbers */ - uilist_prepare(ui_list, len, activei, rows, maxrows, 1, &layoutdata); - - if (dataptr->data && prop) { - /* create list items */ - for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) { - PointerRNA *itemptr = &items_ptr[i].item; - void *dyntip_data; - const int org_i = items_ptr[i].org_idx; - const int flt_flag = items_ptr[i].flt_flag; - uiBlock *subblock = uiLayoutGetBlock(col); - - overlap = uiLayoutOverlap(col); - - UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); - - /* list item behind label & other buttons */ - sub = uiLayoutRow(overlap, false); - - but = uiDefButR_prop(subblock, - UI_BTYPE_LISTROW, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - active_dataptr, - activeprop, - 0, - 0, - org_i, - 0, - 0, - TIP_("Double click to rename")); - if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr, item_dyntip_propname))) { - UI_but_func_tooltip_set(but, uilist_item_tooltip_func, dyntip_data); - } - - sub = uiLayoutRow(overlap, false); - - icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); - if (icon == ICON_DOT) { - icon = ICON_NONE; - } - draw_item(ui_list, - C, - sub, - dataptr, - itemptr, - icon, - active_dataptr, - active_propname, - org_i, - flt_flag); - - /* Items should be able to set context pointers for the layout. But the list-row button - * swallows events, so it needs the context storage too for handlers to see it. */ - but->context = uiLayoutGetContextStore(sub); - - /* If we are "drawing" active item, set all labels as active. */ - if (i == activei) { - ui_layout_list_set_labels_active(sub); - } - - UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); - } - } - - /* add dummy buttons to fill space */ - for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) { - uiItemL(col, "", ICON_NONE); - } - - /* add scrollbar */ - if (len > layoutdata.visual_items) { - col = uiLayoutColumn(row, false); - uiDefButI(block, - UI_BTYPE_SCROLL, - 0, - "", - 0, - 0, - V2D_SCROLL_WIDTH, - UI_UNIT_Y * dyn_data->visual_height, - &ui_list->list_scroll, - 0, - dyn_data->height - dyn_data->visual_height, - dyn_data->visual_height, - 0, - ""); - } - break; - case UILST_LAYOUT_COMPACT: - row = uiLayoutRow(layout, true); - - if ((dataptr->data && prop) && (dyn_data->items_shown > 0) && (activei >= 0) && - (activei < dyn_data->items_shown)) { - PointerRNA *itemptr = &items_ptr[activei].item; - const int org_i = items_ptr[activei].org_idx; - - icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); - if (icon == ICON_DOT) { - icon = ICON_NONE; - } - draw_item( - ui_list, C, row, dataptr, itemptr, icon, active_dataptr, active_propname, org_i, 0); - } - /* if list is empty, add in dummy button */ - else { - uiItemL(row, "", ICON_NONE); - } - - /* next/prev button */ - BLI_snprintf(numstr, sizeof(numstr), "%d :", dyn_data->items_shown); - but = uiDefIconTextButR_prop(block, - UI_BTYPE_NUM, - 0, - 0, - numstr, - 0, - 0, - UI_UNIT_X * 5, - UI_UNIT_Y, - active_dataptr, - activeprop, - 0, - 0, - 0, - 0, - 0, - ""); - if (dyn_data->items_shown == 0) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - break; - case UILST_LAYOUT_GRID: - box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop); - glob = uiLayoutColumn(box, true); - row = uiLayoutRow(glob, false); - col = uiLayoutColumn(row, true); - subrow = NULL; /* Quite gcc warning! */ - - uilist_prepare(ui_list, len, activei, rows, maxrows, columns, &layoutdata); - - if (dataptr->data && prop) { - /* create list items */ - for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) { - PointerRNA *itemptr = &items_ptr[i].item; - const int org_i = items_ptr[i].org_idx; - const int flt_flag = items_ptr[i].flt_flag; - - /* create button */ - if (!(i % columns)) { - subrow = uiLayoutRow(col, false); - } - - uiBlock *subblock = uiLayoutGetBlock(subrow); - overlap = uiLayoutOverlap(subrow); - - UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); - - /* list item behind label & other buttons */ - sub = uiLayoutRow(overlap, false); - - but = uiDefButR_prop(subblock, - UI_BTYPE_LISTROW, - 0, - "", - 0, - 0, - UI_UNIT_X * 10, - UI_UNIT_Y, - active_dataptr, - activeprop, - 0, - 0, - org_i, - 0, - 0, - NULL); - UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP); - - sub = uiLayoutRow(overlap, false); - - icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); - draw_item(ui_list, - C, - sub, - dataptr, - itemptr, - icon, - active_dataptr, - active_propname, - org_i, - flt_flag); - - /* If we are "drawing" active item, set all labels as active. */ - if (i == activei) { - ui_layout_list_set_labels_active(sub); - } - - UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); - } - } - - /* add dummy buttons to fill space */ - for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) { - if (!(i % columns)) { - subrow = uiLayoutRow(col, false); - } - uiItemL(subrow, "", ICON_NONE); - } - - /* add scrollbar */ - if (len > layoutdata.visual_items) { - /* col = */ uiLayoutColumn(row, false); - uiDefButI(block, - UI_BTYPE_SCROLL, - 0, - "", - 0, - 0, - V2D_SCROLL_WIDTH, - UI_UNIT_Y * dyn_data->visual_height, - &ui_list->list_scroll, - 0, - dyn_data->height - dyn_data->visual_height, - dyn_data->visual_height, - 0, - ""); - } - break; - } - - if (glob) { - /* About #UI_BTYPE_GRIP drag-resize: - * We can't directly use results from a grip button, since we have a - * rather complex behavior here (sizing by discrete steps and, overall, auto-size feature). - * Since we *never* know whether we are grip-resizing or not - * (because there is no callback for when a button enters/leaves its "edit mode"), - * we use the fact that grip-controlled value (dyn_data->resize) is completely handled - * by the grip during the grab resize, so settings its value here has no effect at all. - * - * It is only meaningful when we are not resizing, - * in which case this gives us the correct "init drag" value. - * Note we cannot affect `dyn_data->resize_prev here`, - * since this value is not controlled by the grip! - */ - dyn_data->resize = dyn_data->resize_prev + - (dyn_data->visual_height - ui_list->list_grip) * UI_UNIT_Y; - - row = uiLayoutRow(glob, true); - uiBlock *subblock = uiLayoutGetBlock(row); - UI_block_emboss_set(subblock, UI_EMBOSS_NONE); - - if (ui_list->filter_flag & UILST_FLT_SHOW) { - but = uiDefIconButBitI(subblock, - UI_BTYPE_TOGGLE, - UILST_FLT_SHOW, - 0, - ICON_DISCLOSURE_TRI_DOWN, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y * 0.5f, - &(ui_list->filter_flag), - 0, - 0, - 0, - 0, - TIP_("Hide filtering options")); - UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ - - but = uiDefIconButI(subblock, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10.0f, - UI_UNIT_Y * 0.5f, - &dyn_data->resize, - 0.0, - 0.0, - 0, - 0, - ""); - UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL); - - UI_block_emboss_set(subblock, UI_EMBOSS); - - col = uiLayoutColumn(glob, false); - subblock = uiLayoutGetBlock(col); - uiDefBut(subblock, - UI_BTYPE_SEPR, - 0, - "", - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y * 0.05f, - NULL, - 0.0, - 0.0, - 0, - 0, - ""); - - draw_filter(ui_list, C, col); - } - else { - but = uiDefIconButBitI(subblock, - UI_BTYPE_TOGGLE, - UILST_FLT_SHOW, - 0, - ICON_DISCLOSURE_TRI_RIGHT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y * 0.5f, - &(ui_list->filter_flag), - 0, - 0, - 0, - 0, - TIP_("Show filtering options")); - UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ - - but = uiDefIconButI(subblock, - UI_BTYPE_GRIP, - 0, - ICON_GRIP, - 0, - 0, - UI_UNIT_X * 10.0f, - UI_UNIT_Y * 0.5f, - &dyn_data->resize, - 0.0, - 0.0, - 0, - 0, - ""); - UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL); - - UI_block_emboss_set(subblock, UI_EMBOSS); - } - } - - if (items_ptr) { - MEM_freeN(items_ptr); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Running Jobs Template - * \{ */ - -#define B_STOPRENDER 1 -#define B_STOPCAST 2 -#define B_STOPANIM 3 -#define B_STOPCOMPO 4 -#define B_STOPSEQ 5 -#define B_STOPCLIP 6 -#define B_STOPFILE 7 -#define B_STOPOTHER 8 - -static void do_running_jobs(bContext *C, void *UNUSED(arg), int event) -{ - switch (event) { - case B_STOPRENDER: - G.is_break = true; - break; - case B_STOPCAST: - WM_jobs_stop(CTX_wm_manager(C), CTX_wm_screen(C), NULL); - break; - case B_STOPANIM: - WM_operator_name_call(C, "SCREEN_OT_animation_play", WM_OP_INVOKE_SCREEN, NULL); - break; - case B_STOPCOMPO: - WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); - break; - case B_STOPSEQ: - WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); - break; - case B_STOPCLIP: - WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); - break; - case B_STOPFILE: - WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); - break; - case B_STOPOTHER: - G.is_break = true; - break; - } -} - -struct ProgressTooltip_Store { - wmWindowManager *wm; - void *owner; -}; - -static char *progress_tooltip_func(bContext *UNUSED(C), void *argN, const char *UNUSED(tip)) -{ - struct ProgressTooltip_Store *arg = argN; - wmWindowManager *wm = arg->wm; - void *owner = arg->owner; - - const float progress = WM_jobs_progress(wm, owner); - - /* create tooltip text and associate it with the job */ - char elapsed_str[32]; - char remaining_str[32] = "Unknown"; - const double elapsed = PIL_check_seconds_timer() - WM_jobs_starttime(wm, owner); - BLI_timecode_string_from_time_simple(elapsed_str, sizeof(elapsed_str), elapsed); - - if (progress) { - const double remaining = (elapsed / (double)progress) - elapsed; - BLI_timecode_string_from_time_simple(remaining_str, sizeof(remaining_str), remaining); - } - - return BLI_sprintfN( - "Time Remaining: %s\n" - "Time Elapsed: %s", - remaining_str, - elapsed_str); -} - -void uiTemplateRunningJobs(uiLayout *layout, bContext *C) -{ - Main *bmain = CTX_data_main(C); - wmWindowManager *wm = CTX_wm_manager(C); - ScrArea *area = CTX_wm_area(C); - void *owner = NULL; - int handle_event, icon = 0; - - uiBlock *block = uiLayoutGetBlock(layout); - UI_block_layout_set_current(block, layout); - - UI_block_func_handle_set(block, do_running_jobs, NULL); - - /* another scene can be rendering too, for example via compositor */ - LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY)) { - handle_event = B_STOPOTHER; - icon = ICON_NONE; - owner = scene; - } - else { - continue; - } - - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_BUILD_PROXY)) { - handle_event = B_STOPSEQ; - icon = ICON_SEQUENCE; - owner = scene; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_BUILD_PREVIEW)) { - handle_event = B_STOPSEQ; - icon = ICON_SEQUENCE; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) { - handle_event = B_STOPCLIP; - icon = ICON_TRACKER; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_PREFETCH)) { - handle_event = B_STOPCLIP; - icon = ICON_TRACKER; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_TRACK_MARKERS)) { - handle_event = B_STOPCLIP; - icon = ICON_TRACKER; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_SOLVE_CAMERA)) { - handle_event = B_STOPCLIP; - icon = ICON_TRACKER; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_FILESEL_READDIR)) { - handle_event = B_STOPFILE; - icon = ICON_FILEBROWSER; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_RENDER)) { - handle_event = B_STOPRENDER; - icon = ICON_SCENE; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_COMPOSITE)) { - handle_event = B_STOPCOMPO; - icon = ICON_RENDERLAYERS; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_BAKE_TEXTURE) || - WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_BAKE)) { - /* Skip bake jobs in compositor to avoid compo header displaying - * progress bar which is not being updated (bake jobs only need - * to update NC_IMAGE context. - */ - if (area->spacetype != SPACE_NODE) { - handle_event = B_STOPOTHER; - icon = ICON_IMAGE; - break; - } - continue; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_DPAINT_BAKE)) { - handle_event = B_STOPOTHER; - icon = ICON_MOD_DYNAMICPAINT; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_POINTCACHE)) { - handle_event = B_STOPOTHER; - icon = ICON_PHYSICS; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_SIM_FLUID)) { - handle_event = B_STOPOTHER; - icon = ICON_MOD_FLUIDSIM; - break; - } - if (WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_SIM_OCEAN)) { - handle_event = B_STOPOTHER; - icon = ICON_MOD_OCEAN; - break; - } - } - - if (owner) { - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; - const bool active = !(G.is_break || WM_jobs_is_stopped(wm, owner)); - - uiLayout *row = uiLayoutRow(layout, false); - block = uiLayoutGetBlock(row); - - /* get percentage done and set it as the UI text */ - const float progress = WM_jobs_progress(wm, owner); - char text[8]; - BLI_snprintf(text, 8, "%d%%", (int)(progress * 100)); - - const char *name = active ? WM_jobs_name(wm, owner) : "Canceling..."; - - /* job name and icon */ - const int textwidth = UI_fontstyle_string_width(fstyle, name); - uiDefIconTextBut(block, - UI_BTYPE_LABEL, - 0, - icon, - name, - 0, - 0, - textwidth + UI_UNIT_X * 1.5f, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0.0f, - 0.0f, - ""); - - /* stick progress bar and cancel button together */ - row = uiLayoutRow(layout, true); - uiLayoutSetActive(row, active); - block = uiLayoutGetBlock(row); - - { - struct ProgressTooltip_Store *tip_arg = MEM_mallocN(sizeof(*tip_arg), __func__); - tip_arg->wm = wm; - tip_arg->owner = owner; - uiButProgressbar *but_progress = (uiButProgressbar *)uiDefIconTextBut(block, - UI_BTYPE_PROGRESS_BAR, - 0, - 0, - text, - UI_UNIT_X, - 0, - UI_UNIT_X * 6.0f, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0.0f, - 0, - NULL); - - but_progress->progress = progress; - UI_but_func_tooltip_set(&but_progress->but, progress_tooltip_func, tip_arg); - } - - if (!wm->is_interface_locked) { - uiDefIconTextBut(block, - UI_BTYPE_BUT, - handle_event, - ICON_PANEL_CLOSE, - "", - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0, - 0, - TIP_("Stop this job")); - } - } - - if (ED_screen_animation_no_scrub(wm)) { - uiDefIconTextBut(block, - UI_BTYPE_BUT, - B_STOPANIM, - ICON_CANCEL, - IFACE_("Anim Player"), - 0, - 0, - UI_UNIT_X * 5.0f, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0, - 0, - TIP_("Stop animation playback")); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Reports for Last Operator Template - * \{ */ - -void uiTemplateReportsBanner(uiLayout *layout, bContext *C) -{ - ReportList *reports = CTX_wm_reports(C); - Report *report = BKE_reports_last_displayable(reports); - const uiStyle *style = UI_style_get(); - - uiBut *but; - - /* if the report display has timed out, don't show */ - if (!reports->reporttimer) { - return; - } - - ReportTimerInfo *rti = (ReportTimerInfo *)reports->reporttimer->customdata; - - if (!rti || rti->widthfac == 0.0f || !report) { - return; - } - - uiLayout *ui_abs = uiLayoutAbsolute(layout, false); - uiBlock *block = uiLayoutGetBlock(ui_abs); - - UI_fontstyle_set(&style->widgetlabel); - int width = BLF_width(style->widgetlabel.uifont_id, report->message, report->len); - width = min_ii((int)(rti->widthfac * width), width); - width = max_ii(width, 10 * UI_DPI_FAC); - - UI_block_align_begin(block); - - /* Background for icon. */ - but = uiDefBut(block, - UI_BTYPE_ROUNDBOX, - 0, - "", - 0, - 0, - UI_UNIT_X + (6 * UI_DPI_FAC), - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0, - 0, - ""); - /* UI_BTYPE_ROUNDBOX's bg color is set in but->col. */ - UI_GetThemeColorType4ubv(UI_icon_colorid_from_report_type(report->type), SPACE_INFO, but->col); - - /* Background for the rest of the message. */ - but = uiDefBut(block, - UI_BTYPE_ROUNDBOX, - 0, - "", - UI_UNIT_X + (6 * UI_DPI_FAC), - 0, - UI_UNIT_X + width, - UI_UNIT_Y, - NULL, - 0.0f, - 0.0f, - 0, - 0, - ""); - - /* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */ - UI_GetThemeColorType4ubv(UI_icon_colorid_from_report_type(report->type), SPACE_INFO, but->col); - but->col[3] = 64; - - UI_block_align_end(block); - UI_block_emboss_set(block, UI_EMBOSS_NONE); - - /* The report icon itself. */ - but = uiDefIconButO(block, - UI_BTYPE_BUT, - "SCREEN_OT_info_log_show", - WM_OP_INVOKE_REGION_WIN, - UI_icon_from_report_type(report->type), - (3 * UI_DPI_FAC), - 0, - UI_UNIT_X, - UI_UNIT_Y, - TIP_("Click to see the remaining reports in text block: 'Recent Reports'")); - UI_GetThemeColorType4ubv(UI_text_colorid_from_report_type(report->type), SPACE_INFO, but->col); - but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */ - - /* The report message. */ - but = uiDefButO(block, - UI_BTYPE_BUT, - "SCREEN_OT_info_log_show", - WM_OP_INVOKE_REGION_WIN, - report->message, - UI_UNIT_X, - 0, - width + UI_UNIT_X, - UI_UNIT_Y, - "Show in Info Log"); -} - -void uiTemplateInputStatus(uiLayout *layout, struct bContext *C) -{ - wmWindow *win = CTX_wm_window(C); - WorkSpace *workspace = CTX_wm_workspace(C); - - /* Workspace status text has priority. */ - if (workspace->status_text) { - uiItemL(layout, workspace->status_text, ICON_NONE); - return; - } - - if (WM_window_modal_keymap_status_draw(C, win, layout)) { - return; - } - - /* Otherwise should cursor keymap status. */ - for (int i = 0; i < 3; i++) { - uiLayout *box = uiLayoutRow(layout, false); - uiLayout *col = uiLayoutColumn(box, false); - uiLayout *row = uiLayoutRow(col, true); - uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_LEFT); - - const char *msg = WM_window_cursor_keymap_status_get(win, i, 0); - const char *msg_drag = WM_window_cursor_keymap_status_get(win, i, 1); - - if (msg || (msg_drag == NULL)) { - uiItemL(row, msg ? msg : "", (ICON_MOUSE_LMB + i)); - } - - if (msg_drag) { - uiItemL(row, msg_drag, (ICON_MOUSE_LMB_DRAG + i)); - } - - /* Use trick with empty string to keep icons in same position. */ - row = uiLayoutRow(col, false); - uiItemL(row, " ", ICON_NONE); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Keymap Template - * \{ */ - -static void keymap_item_modified(bContext *UNUSED(C), void *kmi_p, void *UNUSED(unused)) -{ - wmKeyMapItem *kmi = (wmKeyMapItem *)kmi_p; - WM_keyconfig_update_tag(NULL, kmi); -} - -static void template_keymap_item_properties(uiLayout *layout, const char *title, PointerRNA *ptr) -{ - uiItemS(layout); - - if (title) { - uiItemL(layout, title, ICON_NONE); - } - - uiLayout *flow = uiLayoutColumnFlow(layout, 2, false); - - RNA_STRUCT_BEGIN_SKIP_RNA_TYPE (ptr, prop) { - const bool is_set = RNA_property_is_set(ptr, prop); - uiBut *but; - - /* recurse for nested properties */ - if (RNA_property_type(prop) == PROP_POINTER) { - PointerRNA propptr = RNA_property_pointer_get(ptr, prop); - - if (propptr.data && RNA_struct_is_a(propptr.type, &RNA_OperatorProperties)) { - const char *name = RNA_property_ui_name(prop); - template_keymap_item_properties(layout, name, &propptr); - continue; - } - } - - uiLayout *box = uiLayoutBox(flow); - uiLayoutSetActive(box, is_set); - uiLayout *row = uiLayoutRow(box, false); - - /* property value */ - uiItemFullR(row, ptr, prop, -1, 0, 0, NULL, ICON_NONE); - - if (is_set) { - /* unset operator */ - uiBlock *block = uiLayoutGetBlock(row); - UI_block_emboss_set(block, UI_EMBOSS_NONE); - but = uiDefIconButO(block, - UI_BTYPE_BUT, - "UI_OT_unset_property_button", - WM_OP_EXEC_DEFAULT, - ICON_X, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - but->rnapoin = *ptr; - but->rnaprop = prop; - UI_block_emboss_set(block, UI_EMBOSS); - } - } - RNA_STRUCT_END; -} - -void uiTemplateKeymapItemProperties(uiLayout *layout, PointerRNA *ptr) -{ - PointerRNA propptr = RNA_pointer_get(ptr, "properties"); - - if (propptr.data) { - uiBut *but = uiLayoutGetBlock(layout)->buttons.last; - - WM_operator_properties_sanitize(&propptr, false); - template_keymap_item_properties(layout, NULL, &propptr); - - /* attach callbacks to compensate for missing properties update, - * we don't know which keymap (item) is being modified there */ - for (; but; but = but->next) { - /* operator buttons may store props for use (file selector, T36492) */ - if (but->rnaprop) { - UI_but_func_set(but, keymap_item_modified, ptr->data, NULL); - - /* Otherwise the keymap will be re-generated which we're trying to edit, - * see: T47685 */ - UI_but_flag_enable(but, UI_BUT_UPDATE_DELAY); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Event Icon Template - * \{ */ - -bool uiTemplateEventFromKeymapItem(struct uiLayout *layout, - const char *text, - const struct wmKeyMapItem *kmi, - bool text_fallback) -{ - bool ok = false; - - int icon_mod[4]; -#ifdef WITH_HEADLESS - int icon = 0; -#else - const int icon = UI_icon_from_keymap_item(kmi, icon_mod); -#endif - if (icon != 0) { - for (int j = 0; j < ARRAY_SIZE(icon_mod) && icon_mod[j]; j++) { - uiItemL(layout, "", icon_mod[j]); - } - uiItemL(layout, text, icon); - ok = true; - } - else if (text_fallback) { - const char *event_text = WM_key_event_string(kmi->type, true); - uiItemL(layout, event_text, ICON_NONE); - uiItemL(layout, text, ICON_NONE); - ok = true; - } - return ok; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Color Management Template - * \{ */ - -void uiTemplateColorspaceSettings(uiLayout *layout, PointerRNA *ptr, const char *propname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop) { - printf( - "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname); - return; - } - - PointerRNA colorspace_settings_ptr = RNA_property_pointer_get(ptr, prop); - - uiItemR(layout, &colorspace_settings_ptr, "name", 0, IFACE_("Color Space"), ICON_NONE); -} - -void uiTemplateColormanagedViewSettings(uiLayout *layout, - bContext *UNUSED(C), - PointerRNA *ptr, - const char *propname) -{ - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop) { - printf( - "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname); - return; - } - - PointerRNA view_transform_ptr = RNA_property_pointer_get(ptr, prop); - ColorManagedViewSettings *view_settings = view_transform_ptr.data; - - uiLayout *col = uiLayoutColumn(layout, false); - - uiLayout *row = uiLayoutRow(col, false); - uiItemR(row, &view_transform_ptr, "view_transform", 0, IFACE_("View"), ICON_NONE); - - col = uiLayoutColumn(layout, false); - uiItemR(col, &view_transform_ptr, "exposure", 0, NULL, ICON_NONE); - uiItemR(col, &view_transform_ptr, "gamma", 0, NULL, ICON_NONE); - - uiItemR(col, &view_transform_ptr, "look", 0, IFACE_("Look"), ICON_NONE); - - col = uiLayoutColumn(layout, false); - uiItemR(col, &view_transform_ptr, "use_curve_mapping", 0, NULL, ICON_NONE); - if (view_settings->flag & COLORMANAGE_VIEW_USE_CURVES) { - uiTemplateCurveMapping( - col, &view_transform_ptr, "curve_mapping", 'c', true, false, false, false); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Component Menu - * \{ */ - -typedef struct ComponentMenuArgs { - PointerRNA ptr; - char propname[64]; /* XXX arbitrary */ -} ComponentMenuArgs; -/* NOTE: this is a block-menu, needs 0 events, otherwise the menu closes */ -static uiBlock *component_menu(bContext *C, ARegion *region, void *args_v) -{ - ComponentMenuArgs *args = (ComponentMenuArgs *)args_v; - - uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); - UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN); - - uiLayout *layout = uiLayoutColumn(UI_block_layout(block, - UI_LAYOUT_VERTICAL, - UI_LAYOUT_PANEL, - 0, - 0, - UI_UNIT_X * 6, - UI_UNIT_Y, - 0, - UI_style_get()), - 0); - - uiItemR(layout, &args->ptr, args->propname, UI_ITEM_R_EXPAND, "", ICON_NONE); - - UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); - UI_block_direction_set(block, UI_DIR_DOWN); - - return block; -} -void uiTemplateComponentMenu(uiLayout *layout, - PointerRNA *ptr, - const char *propname, - const char *name) -{ - ComponentMenuArgs *args = MEM_callocN(sizeof(ComponentMenuArgs), "component menu template args"); - - args->ptr = *ptr; - BLI_strncpy(args->propname, propname, sizeof(args->propname)); - - uiBlock *block = uiLayoutGetBlock(layout); - UI_block_align_begin(block); - - uiBut *but = uiDefBlockButN( - block, component_menu, args, name, 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, ""); - /* set rna directly, uiDefBlockButN doesn't do this */ - but->rnapoin = *ptr; - but->rnaprop = RNA_struct_find_property(ptr, propname); - but->rnaindex = 0; - - UI_block_align_end(block); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Node Socket Icon Template - * \{ */ - -void uiTemplateNodeSocket(uiLayout *layout, bContext *UNUSED(C), float color[4]) -{ - uiBlock *block = uiLayoutGetBlock(layout); - UI_block_align_begin(block); - - /* XXX using explicit socket colors is not quite ideal. - * Eventually it should be possible to use theme colors for this purpose, - * but this requires a better design for extendable color palettes in user prefs. - */ - uiBut *but = uiDefBut( - block, UI_BTYPE_NODE_SOCKET, 0, "", 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL, 0, 0, 0, 0, ""); - rgba_float_to_uchar(but->col, color); - - UI_block_align_end(block); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Cache File Template - * \{ */ - -void uiTemplateCacheFile(uiLayout *layout, - const bContext *C, - PointerRNA *ptr, - const char *propname) -{ - if (!ptr->data) { - return; - } - - PropertyRNA *prop = RNA_struct_find_property(ptr, propname); - - if (!prop) { - printf( - "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname); - return; - } - - if (RNA_property_type(prop) != PROP_POINTER) { - printf("%s: expected pointer property for %s.%s\n", - __func__, - RNA_struct_identifier(ptr->type), - propname); - return; - } - - PointerRNA fileptr = RNA_property_pointer_get(ptr, prop); - CacheFile *file = fileptr.data; - - uiLayoutSetContextPointer(layout, "edit_cachefile", &fileptr); - - uiTemplateID(layout, - C, - ptr, - propname, - NULL, - "CACHEFILE_OT_open", - NULL, - UI_TEMPLATE_ID_FILTER_ALL, - false, - NULL); - - if (!file) { - return; - } - - SpaceProperties *sbuts = CTX_wm_space_properties(C); - - uiLayout *row, *sub, *subsub; - - uiLayoutSetPropSep(layout, true); - - row = uiLayoutRow(layout, true); - uiItemR(row, &fileptr, "filepath", 0, NULL, ICON_NONE); - sub = uiLayoutRow(row, true); - uiItemO(sub, "", ICON_FILE_REFRESH, "cachefile.reload"); - - row = uiLayoutRow(layout, false); - uiItemR(row, &fileptr, "is_sequence", 0, NULL, ICON_NONE); - - row = uiLayoutRowWithHeading(layout, true, IFACE_("Override Frame")); - sub = uiLayoutRow(row, true); - uiLayoutSetPropDecorate(sub, false); - uiItemR(sub, &fileptr, "override_frame", 0, "", ICON_NONE); - subsub = uiLayoutRow(sub, true); - uiLayoutSetActive(subsub, RNA_boolean_get(&fileptr, "override_frame")); - uiItemR(subsub, &fileptr, "frame", 0, "", ICON_NONE); - uiItemDecoratorR(row, &fileptr, "frame", 0); - - row = uiLayoutRow(layout, false); - uiItemR(row, &fileptr, "frame_offset", 0, NULL, ICON_NONE); - uiLayoutSetActive(row, !RNA_boolean_get(&fileptr, "is_sequence")); - - if (sbuts->mainb == BCONTEXT_CONSTRAINT) { - row = uiLayoutRow(layout, false); - uiItemR(row, &fileptr, "scale", 0, IFACE_("Manual Scale"), ICON_NONE); - } - - uiItemR(layout, &fileptr, "velocity_name", 0, NULL, ICON_NONE); - uiItemR(layout, &fileptr, "velocity_unit", 0, NULL, ICON_NONE); - - /* TODO: unused for now, so no need to expose. */ -#if 0 - row = uiLayoutRow(layout, false); - uiItemR(row, &fileptr, "forward_axis", 0, "Forward Axis", ICON_NONE); - - row = uiLayoutRow(layout, false); - uiItemR(row, &fileptr, "up_axis", 0, "Up Axis", ICON_NONE); -#endif -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Recent Files Template - * \{ */ - -int uiTemplateRecentFiles(uiLayout *layout, int rows) -{ - int i; - LISTBASE_FOREACH_INDEX (RecentFile *, recent, &G.recent_files, i) { - if (i >= rows) { - break; - } - - const char *filename = BLI_path_basename(recent->filepath); - PointerRNA ptr; - uiItemFullO(layout, - "WM_OT_open_mainfile", - filename, - BLO_has_bfile_extension(filename) ? ICON_FILE_BLEND : ICON_FILE_BACKUP, - NULL, - WM_OP_INVOKE_DEFAULT, - 0, - &ptr); - RNA_string_set(&ptr, "filepath", recent->filepath); - RNA_boolean_set(&ptr, "display_file_selector", false); - } - - return i; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name FileSelectParams Path Button Template - * \{ */ - -void uiTemplateFileSelectPath(uiLayout *layout, bContext *C, FileSelectParams *params) -{ - bScreen *screen = CTX_wm_screen(C); - SpaceFile *sfile = CTX_wm_space_file(C); - - ED_file_path_button(screen, sfile, params, uiLayoutGetBlock(layout)); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_templates.cc b/source/blender/editors/interface/interface_templates.cc new file mode 100644 index 00000000000..6ca0f196280 --- /dev/null +++ b/source/blender/editors/interface/interface_templates.cc @@ -0,0 +1,7352 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_brush_types.h" +#include "DNA_cachefile_types.h" +#include "DNA_constraint_types.h" +#include "DNA_curveprofile_types.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_node_types.h" +#include "DNA_object_force_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_shader_fx_types.h" +#include "DNA_texture_types.h" + +#include "BLI_alloca.h" +#include "BLI_fnmatch.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_path_util.h" +#include "BLI_rect.h" +#include "BLI_string.h" +#include "BLI_string_search.h" +#include "BLI_timecode.h" +#include "BLI_utildefines.h" + +#include "BLF_api.h" +#include "BLT_translation.h" + +#include "BKE_action.h" +#include "BKE_colorband.h" +#include "BKE_colortools.h" +#include "BKE_constraint.h" +#include "BKE_context.h" +#include "BKE_curveprofile.h" +#include "BKE_global.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_idprop.h" +#include "BKE_idtype.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_lib_override.h" +#include "BKE_linestyle.h" +#include "BKE_main.h" +#include "BKE_modifier.h" +#include "BKE_object.h" +#include "BKE_packedFile.h" +#include "BKE_particle.h" +#include "BKE_report.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_shader_fx.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" + +#include "ED_fileselect.h" +#include "ED_object.h" +#include "ED_render.h" +#include "ED_screen.h" +#include "ED_undo.h" + +#include "RNA_access.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "BLO_readfile.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" +#include "UI_view2d.h" +#include "interface_intern.h" + +#include "PIL_time.h" + +/* we may want to make this optional, disable for now. */ +// #define USE_OP_RESET_BUT + +/* defines for templateID/TemplateSearch */ +#define TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH (UI_UNIT_X * 6) +#define TEMPLATE_SEARCH_TEXTBUT_HEIGHT UI_UNIT_Y + +void UI_template_fix_linking(void) +{ +} + +/* -------------------------------------------------------------------- */ +/** \name Header Template + * \{ */ + +void uiTemplateHeader(uiLayout *layout, bContext *C) +{ + uiBlock *block = uiLayoutAbsoluteBlock(layout); + ED_area_header_switchbutton(C, block, 0); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Search Menu Helpers + * \{ */ + +static int template_search_textbut_width(PointerRNA *ptr, PropertyRNA *name_prop) +{ + char str[UI_MAX_DRAW_STR]; + int buf_len = 0; + + BLI_assert(RNA_property_type(name_prop) == PROP_STRING); + + const char *name = RNA_property_string_get_alloc(ptr, name_prop, str, sizeof(str), &buf_len); + + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + const int margin = UI_UNIT_X * 0.75f; + const int estimated_width = UI_fontstyle_string_width(fstyle, name) + margin; + + if (name != str) { + MEM_freeN((void *)name); + } + + /* Clamp to some min/max width. */ + return CLAMPIS( + estimated_width, TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH, TEMPLATE_SEARCH_TEXTBUT_MIN_WIDTH * 3); +} + +static int template_search_textbut_height(void) +{ + return TEMPLATE_SEARCH_TEXTBUT_HEIGHT; +} + +/** + * Add a block button for the search menu for templateID and templateSearch. + */ +static void template_add_button_search_menu(const bContext *C, + uiLayout *layout, + uiBlock *block, + PointerRNA *ptr, + PropertyRNA *prop, + uiBlockCreateFunc block_func, + void *block_argN, + const char *const tip, + const bool use_previews, + const bool editable, + const bool live_icon) +{ + const PointerRNA active_ptr = RNA_property_pointer_get(ptr, prop); + ID *id = (active_ptr.data && RNA_struct_is_ID(active_ptr.type)) ? active_ptr.data : NULL; + const ID *idfrom = ptr->owner_id; + const StructRNA *type = active_ptr.type ? active_ptr.type : RNA_property_pointer_type(ptr, prop); + uiBut *but; + + if (use_previews) { + ARegion *region = CTX_wm_region(C); + /* Ugly tool header exception. */ + const bool use_big_size = (region->regiontype != RGN_TYPE_TOOL_HEADER); + /* Ugly exception for screens here, + * drawing their preview in icon size looks ugly/useless */ + const bool use_preview_icon = use_big_size || (id && (GS(id->name) != ID_SCR)); + const short width = UI_UNIT_X * (use_big_size ? 6 : 1.6f); + const short height = UI_UNIT_Y * (use_big_size ? 6 : 1); + uiLayout *col = NULL; + + if (use_big_size) { + /* Assume column layout here. To be more correct, we should check if the layout passed to + * template_id is a column one, but this should work well in practice. */ + col = uiLayoutColumn(layout, true); + } + + but = uiDefBlockButN(block, block_func, block_argN, "", 0, 0, width, height, tip); + if (use_preview_icon) { + const int icon = id ? ui_id_icon_get(C, id, use_big_size) : RNA_struct_ui_icon(type); + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + } + else { + ui_def_but_icon(but, RNA_struct_ui_icon(type), UI_HAS_ICON); + UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); + } + + if ((idfrom && idfrom->lib) || !editable) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + if (use_big_size) { + uiLayoutRow(col ? col : layout, true); + } + } + else { + but = uiDefBlockButN(block, block_func, block_argN, "", 0, 0, UI_UNIT_X * 1.6, UI_UNIT_Y, tip); + + if (live_icon) { + const int icon = id ? ui_id_icon_get(C, id, false) : RNA_struct_ui_icon(type); + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + } + else { + ui_def_but_icon(but, RNA_struct_ui_icon(type), UI_HAS_ICON); + } + if (id) { + /* default dragging of icon for id browse buttons */ + UI_but_drag_set_id(but, id); + } + UI_but_drawflag_enable(but, UI_BUT_ICON_LEFT); + + if ((idfrom && idfrom->lib) || !editable) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } +} + +static uiBlock *template_common_search_menu(const bContext *C, + ARegion *region, + uiButSearchUpdateFn search_update_fn, + void *search_arg, + uiButHandleFunc search_exec_fn, + void *active_item, + uiButSearchTooltipFn item_tooltip_fn, + const int preview_rows, + const int preview_cols, + float scale) +{ + static char search[256]; + wmWindow *win = CTX_wm_window(C); + uiBut *but; + + /* clear initial search string, then all items show */ + search[0] = 0; + + uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS); + UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_SEARCH_MENU); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + /* preview thumbnails */ + if (preview_rows > 0 && preview_cols > 0) { + const int w = 4 * U.widget_unit * preview_cols * scale; + const int h = 5 * U.widget_unit * preview_rows * scale; + + /* fake button, it holds space for search items */ + uiDefBut(block, UI_BTYPE_LABEL, 0, "", 10, 26, w, h, NULL, 0, 0, 0, 0, NULL); + + but = uiDefSearchBut(block, + search, + 0, + ICON_VIEWZOOM, + sizeof(search), + 10, + 0, + w, + UI_UNIT_Y, + preview_rows, + preview_cols, + ""); + } + /* list view */ + else { + const int searchbox_width = UI_searchbox_size_x(); + const int searchbox_height = UI_searchbox_size_y(); + + /* fake button, it holds space for search items */ + uiDefBut(block, + UI_BTYPE_LABEL, + 0, + "", + 10, + 15, + searchbox_width, + searchbox_height, + NULL, + 0, + 0, + 0, + 0, + NULL); + but = uiDefSearchBut(block, + search, + 0, + ICON_VIEWZOOM, + sizeof(search), + 10, + 0, + searchbox_width, + UI_UNIT_Y - 1, + 0, + 0, + ""); + } + UI_but_func_search_set(but, + ui_searchbox_create_generic, + search_update_fn, + search_arg, + false, + NULL, + search_exec_fn, + active_item); + UI_but_func_search_set_tooltip(but, item_tooltip_fn); + + UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); + UI_block_direction_set(block, UI_DIR_DOWN); + + /* give search-field focus */ + UI_but_focus_on_enter_event(win, but); + /* this type of search menu requires undo */ + but->flag |= UI_BUT_UNDO; + + return block; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Search Callbacks + * \{ */ + +typedef struct TemplateID { + PointerRNA ptr; + PropertyRNA *prop; + + ListBase *idlb; + short idcode; + short filter; + int prv_rows, prv_cols; + bool preview; + float scale; +} TemplateID; + +/* Search browse menu, assign */ +static void template_ID_set_property_exec_fn(bContext *C, void *arg_template, void *item) +{ + TemplateID *template_ui = (TemplateID *)arg_template; + + /* ID */ + if (item) { + PointerRNA idptr; + + RNA_id_pointer_create(item, &idptr); + RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + } +} + +static bool id_search_allows_id(TemplateID *template_ui, const int flag, ID *id, const char *query) +{ + ID *id_from = template_ui->ptr.owner_id; + + /* Do self check. */ + if ((flag & PROP_ID_SELF_CHECK) && id == id_from) { + return false; + } + + /* Use filter. */ + if (RNA_property_type(template_ui->prop) == PROP_POINTER) { + PointerRNA ptr; + RNA_id_pointer_create(id, &ptr); + if (RNA_property_pointer_poll(&template_ui->ptr, template_ui->prop, &ptr) == 0) { + return false; + } + } + + /* Hide dot prefixed data-blocks, but only if filter does not force them visible. */ + if (U.uiflag & USER_HIDE_DOT) { + if ((id->name[2] == '.') && (query[0] != '.')) { + return false; + } + } + + return true; +} + +static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchItems *items, ID *id) +{ + /* +1 is needed because BKE_id_ui_prefix used 3 letter prefix + * followed by ID_NAME-2 characters from id->name + */ + char name_ui[MAX_ID_FULL_NAME_UI]; + int iconid = ui_id_icon_get(C, id, template_ui->preview); + const bool use_lib_prefix = template_ui->preview || iconid; + const bool has_sep_char = (id->lib != NULL); + + /* When using previews, the library hint (linked, overridden, missing) is added with a + * character prefix, otherwise we can use a icon. */ + int name_prefix_offset; + BKE_id_full_name_ui_prefix_get(name_ui, id, use_lib_prefix, UI_SEP_CHAR, &name_prefix_offset); + if (!use_lib_prefix) { + iconid = UI_icon_from_library(id); + } + + if (!UI_search_item_add(items, + name_ui, + id, + iconid, + has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0, + name_prefix_offset)) { + return false; + } + + return true; +} + +/* ID Search browse menu, do the search */ +static void id_search_cb(const bContext *C, + void *arg_template, + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + TemplateID *template_ui = (TemplateID *)arg_template; + ListBase *lb = template_ui->idlb; + const int flag = RNA_property_flag(template_ui->prop); + + StringSearch *search = BLI_string_search_new(); + + /* ID listbase */ + LISTBASE_FOREACH (ID *, id, lb) { + if (id_search_allows_id(template_ui, flag, id, str)) { + BLI_string_search_add(search, id->name + 2, id); + } + } + + ID **filtered_ids; + const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_ids); + + for (int i = 0; i < filtered_amount; i++) { + if (!id_search_add(C, template_ui, items, filtered_ids[i])) { + break; + } + } + + MEM_freeN(filtered_ids); + BLI_string_search_free(search); +} + +/** + * Use id tags for filtering. + */ +static void id_search_cb_tagged(const bContext *C, + void *arg_template, + const char *str, + uiSearchItems *items) +{ + TemplateID *template_ui = (TemplateID *)arg_template; + ListBase *lb = template_ui->idlb; + const int flag = RNA_property_flag(template_ui->prop); + + StringSearch *search = BLI_string_search_new(); + + /* ID listbase */ + LISTBASE_FOREACH (ID *, id, lb) { + if (id->tag & LIB_TAG_DOIT) { + if (id_search_allows_id(template_ui, flag, id, str)) { + BLI_string_search_add(search, id->name + 2, id); + } + id->tag &= ~LIB_TAG_DOIT; + } + } + + ID **filtered_ids; + const int filtered_amount = BLI_string_search_query(search, str, (void ***)&filtered_ids); + + for (int i = 0; i < filtered_amount; i++) { + if (!id_search_add(C, template_ui, items, filtered_ids[i])) { + break; + } + } + + MEM_freeN(filtered_ids); + BLI_string_search_free(search); +} + +/** + * A version of 'id_search_cb' that lists scene objects. + */ +static void id_search_cb_objects_from_scene(const bContext *C, + void *arg_template, + const char *str, + uiSearchItems *items, + const bool UNUSED(is_first)) +{ + TemplateID *template_ui = (TemplateID *)arg_template; + ListBase *lb = template_ui->idlb; + Scene *scene = NULL; + ID *id_from = template_ui->ptr.owner_id; + + if (id_from && GS(id_from->name) == ID_SCE) { + scene = (Scene *)id_from; + } + else { + scene = CTX_data_scene(C); + } + + BKE_main_id_flag_listbase(lb, LIB_TAG_DOIT, false); + + FOREACH_SCENE_OBJECT_BEGIN (scene, ob_iter) { + ob_iter->id.tag |= LIB_TAG_DOIT; + } + FOREACH_SCENE_OBJECT_END; + id_search_cb_tagged(C, arg_template, str, items); +} + +static ARegion *template_ID_search_menu_item_tooltip( + bContext *C, ARegion *region, const rcti *item_rect, void *arg, void *active) +{ + TemplateID *template_ui = arg; + ID *active_id = active; + StructRNA *type = RNA_property_pointer_type(&template_ui->ptr, template_ui->prop); + + uiSearchItemTooltipData tooltip_data = {0}; + + tooltip_data.name = active_id->name + 2; + BLI_snprintf(tooltip_data.description, + sizeof(tooltip_data.description), + TIP_("Choose %s data-block to be assigned to this user"), + RNA_struct_ui_name(type)); + if (ID_IS_LINKED(active_id)) { + BLI_snprintf(tooltip_data.hint, + sizeof(tooltip_data.hint), + TIP_("Source library: %s\n%s"), + active_id->lib->id.name + 2, + active_id->lib->filepath); + } + + return UI_tooltip_create_from_search_item_generic(C, region, item_rect, &tooltip_data); +} + +/* ID Search browse menu, open */ +static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem) +{ + static TemplateID template_ui; + PointerRNA active_item_ptr; + void (*id_search_update_fn)( + const bContext *, void *, const char *, uiSearchItems *, const bool) = id_search_cb; + + /* arg_litem is malloced, can be freed by parent button */ + template_ui = *((TemplateID *)arg_litem); + active_item_ptr = RNA_property_pointer_get(&template_ui.ptr, template_ui.prop); + + if (template_ui.filter) { + /* Currently only used for objects. */ + if (template_ui.idcode == ID_OB) { + if (template_ui.filter == UI_TEMPLATE_ID_FILTER_AVAILABLE) { + id_search_update_fn = id_search_cb_objects_from_scene; + } + } + } + + return template_common_search_menu(C, + region, + id_search_update_fn, + &template_ui, + template_ID_set_property_exec_fn, + active_item_ptr.data, + template_ID_search_menu_item_tooltip, + template_ui.prv_rows, + template_ui.prv_cols, + template_ui.scale); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ID Template + * \{ */ + +/* This is for browsing and editing the ID-blocks used */ + +/* for new/open operators */ +void UI_context_active_but_prop_get_templateID(bContext *C, + PointerRNA *r_ptr, + PropertyRNA **r_prop) +{ + uiBut *but = UI_context_active_but_get(C); + + memset(r_ptr, 0, sizeof(*r_ptr)); + *r_prop = NULL; + + if (but && but->func_argN) { + TemplateID *template_ui = but->func_argN; + *r_ptr = template_ui->ptr; + *r_prop = template_ui->prop; + } +} + +static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) +{ + TemplateID *template_ui = (TemplateID *)arg_litem; + PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + ID *id = idptr.data; + const int event = POINTER_AS_INT(arg_event); + const char *undo_push_label = NULL; + + switch (event) { + case UI_ID_BROWSE: + case UI_ID_PIN: + RNA_warning("warning, id event %d shouldn't come here", event); + break; + case UI_ID_OPEN: + case UI_ID_ADD_NEW: + /* these call UI_context_active_but_prop_get_templateID */ + break; + case UI_ID_DELETE: + memset(&idptr, 0, sizeof(idptr)); + RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + + if (id && CTX_wm_window(C)->eventstate->shift) { + /* only way to force-remove data (on save) */ + id_us_clear_real(id); + id_fake_user_clear(id); + id->us = 0; + undo_push_label = "Delete Data-Block"; + } + + break; + case UI_ID_FAKE_USER: + if (id) { + if (id->flag & LIB_FAKEUSER) { + id_us_plus(id); + } + else { + id_us_min(id); + } + undo_push_label = "Fake User"; + } + else { + return; + } + break; + case UI_ID_LOCAL: + if (id) { + Main *bmain = CTX_data_main(C); + if (CTX_wm_window(C)->eventstate->shift) { + if (ID_IS_OVERRIDABLE_LIBRARY(id)) { + /* Only remap that specific ID usage to overriding local data-block. */ + ID *override_id = BKE_lib_override_library_create_from_id(bmain, id, false); + if (override_id != NULL) { + BKE_main_id_clear_newpoins(bmain); + + if (GS(override_id->name) == ID_OB) { + Scene *scene = CTX_data_scene(C); + if (!BKE_collection_has_object_recursive(scene->master_collection, + (Object *)override_id)) { + BKE_collection_object_add_from( + bmain, scene, (Object *)id, (Object *)override_id); + } + } + + /* Assign new pointer, takes care of updates/notifiers */ + RNA_id_pointer_create(override_id, &idptr); + } + undo_push_label = "Make Library Override"; + } + } + else { + if (BKE_lib_id_make_local(bmain, id, false, 0)) { + BKE_main_id_clear_newpoins(bmain); + + /* reassign to get get proper updates/notifiers */ + idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + undo_push_label = "Make Local"; + } + } + if (undo_push_label != NULL) { + RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + } + } + break; + case UI_ID_OVERRIDE: + if (id && ID_IS_OVERRIDE_LIBRARY(id)) { + BKE_lib_override_library_free(&id->override_library, true); + /* reassign to get get proper updates/notifiers */ + idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + undo_push_label = "Override Data-Block"; + } + break; + case UI_ID_ALONE: + if (id) { + const bool do_scene_obj = ((GS(id->name) == ID_OB) && + (template_ui->ptr.type == &RNA_LayerObjects)); + + /* make copy */ + if (do_scene_obj) { + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + ED_object_single_user(bmain, scene, (struct Object *)id); + WM_event_add_notifier(C, NC_WINDOW, NULL); + DEG_relations_tag_update(bmain); + } + else { + Main *bmain = CTX_data_main(C); + id_single_user(C, id, &template_ui->ptr, template_ui->prop); + DEG_relations_tag_update(bmain); + } + undo_push_label = "Make Single User"; + } + break; +#if 0 + case UI_ID_AUTO_NAME: + break; +#endif + } + + if (undo_push_label != NULL) { + ED_undo_push(C, undo_push_label); + } +} + +static const char *template_id_browse_tip(const StructRNA *type) +{ + if (type) { + switch ((ID_Type)RNA_type_to_ID_code(type)) { + case ID_SCE: + return N_("Browse Scene to be linked"); + case ID_OB: + return N_("Browse Object to be linked"); + case ID_ME: + return N_("Browse Mesh Data to be linked"); + case ID_CU: + return N_("Browse Curve Data to be linked"); + case ID_MB: + return N_("Browse Metaball Data to be linked"); + case ID_MA: + return N_("Browse Material to be linked"); + case ID_TE: + return N_("Browse Texture to be linked"); + case ID_IM: + return N_("Browse Image to be linked"); + case ID_LS: + return N_("Browse Line Style Data to be linked"); + case ID_LT: + return N_("Browse Lattice Data to be linked"); + case ID_LA: + return N_("Browse Light Data to be linked"); + case ID_CA: + return N_("Browse Camera Data to be linked"); + case ID_WO: + return N_("Browse World Settings to be linked"); + case ID_SCR: + return N_("Choose Screen layout"); + case ID_TXT: + return N_("Browse Text to be linked"); + case ID_SPK: + return N_("Browse Speaker Data to be linked"); + case ID_SO: + return N_("Browse Sound to be linked"); + case ID_AR: + return N_("Browse Armature data to be linked"); + case ID_AC: + return N_("Browse Action to be linked"); + case ID_NT: + return N_("Browse Node Tree to be linked"); + case ID_BR: + return N_("Browse Brush to be linked"); + case ID_PA: + return N_("Browse Particle Settings to be linked"); + case ID_GD: + return N_("Browse Grease Pencil Data to be linked"); + case ID_MC: + return N_("Browse Movie Clip to be linked"); + case ID_MSK: + return N_("Browse Mask to be linked"); + case ID_PAL: + return N_("Browse Palette Data to be linked"); + case ID_PC: + return N_("Browse Paint Curve Data to be linked"); + case ID_CF: + return N_("Browse Cache Files to be linked"); + case ID_WS: + return N_("Browse Workspace to be linked"); + case ID_LP: + return N_("Browse LightProbe to be linked"); + case ID_HA: + return N_("Browse Hair Data to be linked"); + case ID_PT: + return N_("Browse Point Cloud Data to be linked"); + case ID_VO: + return N_("Browse Volume Data to be linked"); + case ID_SIM: + return N_("Browse Simulation to be linked"); + + /* Use generic text. */ + case ID_LI: + case ID_IP: + case ID_KE: + case ID_VF: + case ID_GR: + case ID_WM: + break; + } + } + return N_("Browse ID data to be linked"); +} + +/** + * \return a type-based i18n context, needed e.g. by "New" button. + * In most languages, this adjective takes different form based on gender of type name... + */ +#ifdef WITH_INTERNATIONAL +static const char *template_id_context(StructRNA *type) +{ + if (type) { + return BKE_idtype_idcode_to_translation_context(RNA_type_to_ID_code(type)); + } + return BLT_I18NCONTEXT_DEFAULT; +} +#else +# define template_id_context(type) 0 +#endif + +static uiBut *template_id_def_new_but(uiBlock *block, + const ID *id, + const TemplateID *template_ui, + StructRNA *type, + const char *const newop, + const bool editable, + const bool id_open, + const bool use_tab_but, + int but_height) +{ + ID *idfrom = template_ui->ptr.owner_id; + uiBut *but; + const int w = id ? UI_UNIT_X : id_open ? UI_UNIT_X * 3 : UI_UNIT_X * 6; + const int but_type = use_tab_but ? UI_BTYPE_TAB : UI_BTYPE_BUT; + + /* i18n markup, does nothing! */ + BLT_I18N_MSGID_MULTI_CTXT("New", + BLT_I18NCONTEXT_DEFAULT, + BLT_I18NCONTEXT_ID_SCENE, + BLT_I18NCONTEXT_ID_OBJECT, + BLT_I18NCONTEXT_ID_MESH, + BLT_I18NCONTEXT_ID_CURVE, + BLT_I18NCONTEXT_ID_METABALL, + BLT_I18NCONTEXT_ID_MATERIAL, + BLT_I18NCONTEXT_ID_TEXTURE, + BLT_I18NCONTEXT_ID_IMAGE, + BLT_I18NCONTEXT_ID_LATTICE, + BLT_I18NCONTEXT_ID_LIGHT, + BLT_I18NCONTEXT_ID_CAMERA, + BLT_I18NCONTEXT_ID_WORLD, + BLT_I18NCONTEXT_ID_SCREEN, + BLT_I18NCONTEXT_ID_TEXT, ); + BLT_I18N_MSGID_MULTI_CTXT("New", + BLT_I18NCONTEXT_ID_SPEAKER, + BLT_I18NCONTEXT_ID_SOUND, + BLT_I18NCONTEXT_ID_ARMATURE, + BLT_I18NCONTEXT_ID_ACTION, + BLT_I18NCONTEXT_ID_NODETREE, + BLT_I18NCONTEXT_ID_BRUSH, + BLT_I18NCONTEXT_ID_PARTICLESETTINGS, + BLT_I18NCONTEXT_ID_GPENCIL, + BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, + BLT_I18NCONTEXT_ID_WORKSPACE, + BLT_I18NCONTEXT_ID_LIGHTPROBE, + BLT_I18NCONTEXT_ID_HAIR, + BLT_I18NCONTEXT_ID_POINTCLOUD, + BLT_I18NCONTEXT_ID_VOLUME, + BLT_I18NCONTEXT_ID_SIMULATION, ); + /* Note: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters, + * check the definition to see if a new call must be added when the limit + * is exceeded. */ + + if (newop) { + but = uiDefIconTextButO(block, + but_type, + newop, + WM_OP_INVOKE_DEFAULT, + (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD, + (id) ? "" : CTX_IFACE_(template_id_context(type), "New"), + 0, + 0, + w, + but_height, + NULL); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); + } + else { + but = uiDefIconTextBut(block, + but_type, + 0, + (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD, + (id) ? "" : CTX_IFACE_(template_id_context(type), "New"), + 0, + 0, + w, + but_height, + NULL, + 0, + 0, + 0, + 0, + NULL); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); + } + + if ((idfrom && idfrom->lib) || !editable) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + +#ifndef WITH_INTERNATIONAL + UNUSED_VARS(type); +#endif + + return but; +} + +static void template_ID(const bContext *C, + uiLayout *layout, + TemplateID *template_ui, + StructRNA *type, + int flag, + const char *newop, + const char *openop, + const char *unlinkop, + const char *text, + const bool live_icon, + const bool hide_buttons) +{ + uiBut *but; + const bool editable = RNA_property_editable(&template_ui->ptr, template_ui->prop); + const bool use_previews = template_ui->preview = (flag & UI_ID_PREVIEWS) != 0; + + PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + ID *id = idptr.data; + ID *idfrom = template_ui->ptr.owner_id; + // lb = template_ui->idlb; + + /* Allow operators to take the ID from context. */ + uiLayoutSetContextPointer(layout, "id", &idptr); + + uiBlock *block = uiLayoutGetBlock(layout); + UI_block_align_begin(block); + + if (idptr.type) { + type = idptr.type; + } + + if (text) { + /* Add label respecting the separated layout property split state. */ + uiItemL_respect_property_split(layout, text, ICON_NONE); + } + + if (flag & UI_ID_BROWSE) { + template_add_button_search_menu(C, + layout, + block, + &template_ui->ptr, + template_ui->prop, + id_search_menu, + MEM_dupallocN(template_ui), + TIP_(template_id_browse_tip(type)), + use_previews, + editable, + live_icon); + } + + /* text button with name */ + if (id) { + char name[UI_MAX_NAME_STR]; + const bool user_alert = (id->us <= 0); + + const int width = template_search_textbut_width(&idptr, + RNA_struct_find_property(&idptr, "name")); + const int height = template_search_textbut_height(); + + // text_idbutton(id, name); + name[0] = '\0'; + but = uiDefButR(block, + UI_BTYPE_TEXT, + 0, + name, + 0, + 0, + width, + height, + &idptr, + "name", + -1, + 0, + 0, + -1, + -1, + RNA_struct_ui_description(type)); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_RENAME)); + if (user_alert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + if (id->lib) { + if (id->tag & LIB_TAG_INDIRECT) { + but = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_INDIRECT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Indirect library data-block, cannot change")); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + else { + const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) || + (idfrom && idfrom->lib)); + but = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_DIRECT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Direct linked library data-block, click to make local, " + "Shift + Click to create a library override")); + if (disabled) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + else { + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_LOCAL)); + } + } + } + else if (ID_IS_OVERRIDE_LIBRARY(id)) { + but = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LIBRARY_DATA_OVERRIDE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Library override of linked data-block, click to make fully local")); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE)); + } + + if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) { + char numstr[32]; + short numstr_len; + + numstr_len = BLI_snprintf(numstr, sizeof(numstr), "%d", ID_REAL_USERS(id)); + + but = uiDefBut( + block, + UI_BTYPE_BUT, + 0, + numstr, + 0, + 0, + numstr_len * 0.2f * UI_UNIT_X + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Display number of users of this data (click to make a single-user copy)")); + but->flag |= UI_BUT_UNDO; + + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ALONE)); + if ((!BKE_id_copy_is_allowed(id)) || (idfrom && idfrom->lib) || (!editable) || + /* object in editmode - don't change data */ + (idfrom && GS(idfrom->name) == ID_OB && (((Object *)idfrom)->mode & OB_MODE_EDIT))) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } + + if (user_alert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + if (id->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS)) && + (hide_buttons == false)) { + uiDefIconButR(block, + UI_BTYPE_ICON_TOGGLE, + 0, + ICON_FAKE_USER_OFF, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + &idptr, + "use_fake_user", + -1, + 0, + 0, + -1, + -1, + NULL); + } + } + + if ((flag & UI_ID_ADD_NEW) && (hide_buttons == false)) { + template_id_def_new_but( + block, id, template_ui, type, newop, editable, flag & UI_ID_OPEN, false, UI_UNIT_X); + } + + /* Due to space limit in UI - skip the "open" icon for packed data, and allow to unpack. + * Only for images, sound and fonts */ + if (id && BKE_packedfile_id_check(id)) { + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "FILE_OT_unpack_item", + WM_OP_INVOKE_REGION_WIN, + ICON_PACKAGE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + TIP_("Packed File, click to unpack")); + UI_but_operator_ptr_get(but); + + RNA_string_set(but->opptr, "id_name", id->name + 2); + RNA_int_set(but->opptr, "id_type", GS(id->name)); + } + else if (flag & UI_ID_OPEN) { + const int w = id ? UI_UNIT_X : (flag & UI_ID_ADD_NEW) ? UI_UNIT_X * 3 : UI_UNIT_X * 6; + + if (openop) { + but = uiDefIconTextButO(block, + UI_BTYPE_BUT, + openop, + WM_OP_INVOKE_DEFAULT, + ICON_FILEBROWSER, + (id) ? "" : IFACE_("Open"), + 0, + 0, + w, + UI_UNIT_Y, + NULL); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); + } + else { + but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_FILEBROWSER, + (id) ? "" : IFACE_("Open"), + 0, + 0, + w, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + NULL); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); + } + + if ((idfrom && idfrom->lib) || !editable) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } + + /* delete button */ + /* don't use RNA_property_is_unlink here */ + if (id && (flag & UI_ID_DELETE) && (hide_buttons == false)) { + /* allow unlink if 'unlinkop' is passed, even when 'PROP_NEVER_UNLINK' is set */ + but = NULL; + + if (unlinkop) { + but = uiDefIconButO(block, + UI_BTYPE_BUT, + unlinkop, + WM_OP_INVOKE_DEFAULT, + ICON_X, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + /* so we can access the template from operators, font unlinking needs this */ + UI_but_funcN_set(but, NULL, MEM_dupallocN(template_ui), NULL); + } + else { + if ((RNA_property_flag(template_ui->prop) & PROP_NEVER_UNLINK) == 0) { + but = uiDefIconBut( + block, + UI_BTYPE_BUT, + 0, + ICON_X, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Unlink data-block " + "(Shift + Click to set users to zero, data will then not be saved)")); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_DELETE)); + + if (RNA_property_flag(template_ui->prop) & PROP_NEVER_NULL) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } + } + + if (but) { + if ((idfrom && idfrom->lib) || !editable) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } + } + + if (template_ui->idcode == ID_TE) { + uiTemplateTextureShow(layout, C, &template_ui->ptr, template_ui->prop); + } + UI_block_align_end(block); +} + +ID *UI_context_active_but_get_tab_ID(bContext *C) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && but->type == UI_BTYPE_TAB) { + return but->custom_data; + } + return NULL; +} + +static void template_ID_tabs(const bContext *C, + uiLayout *layout, + TemplateID *template, + StructRNA *type, + int flag, + const char *newop, + const char *menu) +{ + const ARegion *region = CTX_wm_region(C); + const PointerRNA active_ptr = RNA_property_pointer_get(&template->ptr, template->prop); + MenuType *mt = menu ? WM_menutype_find(menu, false) : NULL; + + const int but_align = ui_but_align_opposite_to_area_align_get(region); + const int but_height = UI_UNIT_Y * 1.1; + + uiBlock *block = uiLayoutGetBlock(layout); + const uiStyle *style = UI_style_get_dpi(); + + ListBase ordered; + BKE_id_ordered_list(&ordered, template->idlb); + + LISTBASE_FOREACH (LinkData *, link, &ordered) { + ID *id = link->data; + const int name_width = UI_fontstyle_string_width(&style->widget, id->name + 2); + const int but_width = name_width + UI_UNIT_X; + + uiButTab *tab = (uiButTab *)uiDefButR_prop(block, + UI_BTYPE_TAB, + 0, + id->name + 2, + 0, + 0, + but_width, + but_height, + &template->ptr, + template->prop, + 0, + 0.0f, + sizeof(id->name) - 2, + 0.0f, + 0.0f, + ""); + UI_but_funcN_set(&tab->but, template_ID_set_property_exec_fn, MEM_dupallocN(template), id); + tab->but.custom_data = (void *)id; + tab->but.dragpoin = id; + tab->menu = mt; + + UI_but_drawflag_enable(&tab->but, but_align); + } + + BLI_freelistN(&ordered); + + if (flag & UI_ID_ADD_NEW) { + const bool editable = RNA_property_editable(&template->ptr, template->prop); + uiBut *but; + + if (active_ptr.type) { + type = active_ptr.type; + } + + but = template_id_def_new_but(block, + active_ptr.data, + template, + type, + newop, + editable, + flag & UI_ID_OPEN, + true, + but_height); + UI_but_drawflag_enable(but, but_align); + } +} + +static void ui_template_id(uiLayout *layout, + const bContext *C, + PointerRNA *ptr, + const char *propname, + const char *newop, + const char *openop, + const char *unlinkop, + /* Only respected by tabs (use_tabs). */ + const char *menu, + const char *text, + int flag, + int prv_rows, + int prv_cols, + int filter, + bool use_tabs, + float scale, + const bool live_icon, + const bool hide_buttons) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_POINTER) { + RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + TemplateID *template_ui = MEM_callocN(sizeof(TemplateID), "TemplateID"); + template_ui->ptr = *ptr; + template_ui->prop = prop; + template_ui->prv_rows = prv_rows; + template_ui->prv_cols = prv_cols; + template_ui->scale = scale; + + if ((flag & UI_ID_PIN) == 0) { + template_ui->filter = filter; + } + else { + template_ui->filter = 0; + } + + if (newop) { + flag |= UI_ID_ADD_NEW; + } + if (openop) { + flag |= UI_ID_OPEN; + } + + StructRNA *type = RNA_property_pointer_type(ptr, prop); + short idcode = RNA_type_to_ID_code(type); + template_ui->idcode = idcode; + template_ui->idlb = which_libbase(CTX_data_main(C), idcode); + + /* create UI elements for this template + * - template_ID makes a copy of the template data and assigns it to the relevant buttons + */ + if (template_ui->idlb) { + if (use_tabs) { + layout = uiLayoutRow(layout, true); + template_ID_tabs(C, layout, template_ui, type, flag, newop, menu); + } + else { + layout = uiLayoutRow(layout, true); + template_ID(C, + layout, + template_ui, + type, + flag, + newop, + openop, + unlinkop, + text, + live_icon, + hide_buttons); + } + } + + MEM_freeN(template_ui); +} + +void uiTemplateID(uiLayout *layout, + const bContext *C, + PointerRNA *ptr, + const char *propname, + const char *newop, + const char *openop, + const char *unlinkop, + int filter, + const bool live_icon, + const char *text) +{ + ui_template_id(layout, + C, + ptr, + propname, + newop, + openop, + unlinkop, + NULL, + text, + UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE, + 0, + 0, + filter, + false, + 1.0f, + live_icon, + false); +} + +void uiTemplateIDBrowse(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + const char *propname, + const char *newop, + const char *openop, + const char *unlinkop, + int filter, + const char *text) +{ + ui_template_id(layout, + C, + ptr, + propname, + newop, + openop, + unlinkop, + NULL, + text, + UI_ID_BROWSE | UI_ID_RENAME, + 0, + 0, + filter, + false, + 1.0f, + false, + false); +} + +void uiTemplateIDPreview(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + const char *propname, + const char *newop, + const char *openop, + const char *unlinkop, + int rows, + int cols, + int filter, + const bool hide_buttons) +{ + ui_template_id(layout, + C, + ptr, + propname, + newop, + openop, + unlinkop, + NULL, + NULL, + UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE | UI_ID_PREVIEWS, + rows, + cols, + filter, + false, + 1.0f, + false, + hide_buttons); +} + +void uiTemplateGpencilColorPreview(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + const char *propname, + int rows, + int cols, + float scale, + int filter) +{ + ui_template_id(layout, + C, + ptr, + propname, + NULL, + NULL, + NULL, + NULL, + NULL, + UI_ID_BROWSE | UI_ID_PREVIEWS | UI_ID_DELETE, + rows, + cols, + filter, + false, + scale < 0.5f ? 0.5f : scale, + false, + false); +} + +/** + * Version of #uiTemplateID using tabs. + */ +void uiTemplateIDTabs(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + const char *propname, + const char *newop, + const char *menu, + int filter) +{ + ui_template_id(layout, + C, + ptr, + propname, + newop, + NULL, + NULL, + menu, + NULL, + UI_ID_BROWSE | UI_ID_RENAME, + 0, + 0, + filter, + true, + 1.0f, + false, + false); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ID Chooser Template + * \{ */ + +/** + * This is for selecting the type of ID-block to use, + * and then from the relevant type choosing the block to use. + * + * \param propname: property identifier for property that ID-pointer gets stored to. + * \param proptypename: property identifier for property + * used to determine the type of ID-pointer that can be used. + */ +void uiTemplateAnyID(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + const char *proptypename, + const char *text) +{ + /* get properties... */ + PropertyRNA *propID = RNA_struct_find_property(ptr, propname); + PropertyRNA *propType = RNA_struct_find_property(ptr, proptypename); + + if (!propID || RNA_property_type(propID) != PROP_POINTER) { + RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + if (!propType || RNA_property_type(propType) != PROP_ENUM) { + RNA_warning( + "pointer-type property not found: %s.%s", RNA_struct_identifier(ptr->type), proptypename); + return; + } + + /* Start drawing UI Elements using standard defines */ + + /* NOTE: split amount here needs to be synced with normal labels */ + uiLayout *split = uiLayoutSplit(layout, 0.33f, false); + + /* FIRST PART ................................................ */ + uiLayout *row = uiLayoutRow(split, false); + + /* Label - either use the provided text, or will become "ID-Block:" */ + if (text) { + if (text[0]) { + uiItemL(row, text, ICON_NONE); + } + } + else { + uiItemL(row, IFACE_("ID-Block:"), ICON_NONE); + } + + /* SECOND PART ................................................ */ + row = uiLayoutRow(split, true); + + /* ID-Type Selector - just have a menu of icons */ + + /* HACK: special group just for the enum, + * otherwise we get ugly layout with text included too... */ + uiLayout *sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); + + uiItemFullR(sub, ptr, propType, 0, 0, UI_ITEM_R_ICON_ONLY, "", ICON_NONE); + + /* ID-Block Selector - just use pointer widget... */ + + /* HACK: special group to counteract the effects of the previous enum, + * which now pushes everything too far right. */ + sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND); + + uiItemFullR(sub, ptr, propID, 0, 0, 0, "", ICON_NONE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Search Template + * \{ */ + +typedef struct TemplateSearch { + uiRNACollectionSearch search_data; + + bool use_previews; + int preview_rows, preview_cols; +} TemplateSearch; + +static void template_search_exec_fn(bContext *C, void *arg_template, void *item) +{ + TemplateSearch *template_search = arg_template; + uiRNACollectionSearch *coll_search = &template_search->search_data; + StructRNA *type = RNA_property_pointer_type(&coll_search->target_ptr, coll_search->target_prop); + PointerRNA item_ptr; + + RNA_pointer_create(NULL, type, item, &item_ptr); + RNA_property_pointer_set(&coll_search->target_ptr, coll_search->target_prop, item_ptr, NULL); + RNA_property_update(C, &coll_search->target_ptr, coll_search->target_prop); +} + +static uiBlock *template_search_menu(bContext *C, ARegion *region, void *arg_template) +{ + static TemplateSearch template_search; + + /* arg_template is malloced, can be freed by parent button */ + template_search = *((TemplateSearch *)arg_template); + PointerRNA active_ptr = RNA_property_pointer_get(&template_search.search_data.target_ptr, + template_search.search_data.target_prop); + + return template_common_search_menu(C, + region, + ui_rna_collection_search_update_fn, + &template_search, + template_search_exec_fn, + active_ptr.data, + NULL, + template_search.preview_rows, + template_search.preview_cols, + 1.0f); +} + +static void template_search_add_button_searchmenu(const bContext *C, + uiLayout *layout, + uiBlock *block, + TemplateSearch *template_search, + const bool editable, + const bool live_icon) +{ + const char *ui_description = RNA_property_ui_description( + template_search->search_data.target_prop); + + template_add_button_search_menu(C, + layout, + block, + &template_search->search_data.target_ptr, + template_search->search_data.target_prop, + template_search_menu, + MEM_dupallocN(template_search), + ui_description, + template_search->use_previews, + editable, + live_icon); +} + +static void template_search_add_button_name(uiBlock *block, + PointerRNA *active_ptr, + const StructRNA *type) +{ + PropertyRNA *name_prop = RNA_struct_name_property(type); + const int width = template_search_textbut_width(active_ptr, name_prop); + const int height = template_search_textbut_height(); + uiDefAutoButR(block, active_ptr, name_prop, 0, "", ICON_NONE, 0, 0, width, height); +} + +static void template_search_add_button_operator(uiBlock *block, + const char *const operator_name, + const int opcontext, + const int icon, + const bool editable) +{ + if (!operator_name) { + return; + } + + uiBut *but = uiDefIconButO( + block, UI_BTYPE_BUT, operator_name, opcontext, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL); + + if (!editable) { + UI_but_drawflag_enable(but, UI_BUT_DISABLED); + } +} + +static void template_search_buttons(const bContext *C, + uiLayout *layout, + TemplateSearch *template_search, + const char *newop, + const char *unlinkop) +{ + uiBlock *block = uiLayoutGetBlock(layout); + uiRNACollectionSearch *search_data = &template_search->search_data; + StructRNA *type = RNA_property_pointer_type(&search_data->target_ptr, search_data->target_prop); + const bool editable = RNA_property_editable(&search_data->target_ptr, search_data->target_prop); + PointerRNA active_ptr = RNA_property_pointer_get(&search_data->target_ptr, + search_data->target_prop); + + if (active_ptr.type) { + /* can only get correct type when there is an active item */ + type = active_ptr.type; + } + + uiLayoutRow(layout, true); + UI_block_align_begin(block); + + template_search_add_button_searchmenu(C, layout, block, template_search, editable, false); + template_search_add_button_name(block, &active_ptr, type); + template_search_add_button_operator( + block, newop, WM_OP_INVOKE_DEFAULT, ICON_DUPLICATE, editable); + template_search_add_button_operator(block, unlinkop, WM_OP_INVOKE_REGION_WIN, ICON_X, editable); + + UI_block_align_end(block); +} + +static PropertyRNA *template_search_get_searchprop(PointerRNA *targetptr, + PropertyRNA *targetprop, + PointerRNA *searchptr, + const char *const searchpropname) +{ + PropertyRNA *searchprop; + + if (searchptr && !searchptr->data) { + searchptr = NULL; + } + + if (!searchptr && !searchpropname) { + /* both NULL means we don't use a custom rna collection to search in */ + } + else if (!searchptr && searchpropname) { + RNA_warning("searchpropname defined (%s) but searchptr is missing", searchpropname); + } + else if (searchptr && !searchpropname) { + RNA_warning("searchptr defined (%s) but searchpropname is missing", + RNA_struct_identifier(searchptr->type)); + } + else if (!(searchprop = RNA_struct_find_property(searchptr, searchpropname))) { + RNA_warning("search collection property not found: %s.%s", + RNA_struct_identifier(searchptr->type), + searchpropname); + } + else if (RNA_property_type(searchprop) != PROP_COLLECTION) { + RNA_warning("search collection property is not a collection type: %s.%s", + RNA_struct_identifier(searchptr->type), + searchpropname); + } + /* check if searchprop has same type as targetprop */ + else if (RNA_property_pointer_type(searchptr, searchprop) != + RNA_property_pointer_type(targetptr, targetprop)) { + RNA_warning("search collection items from %s.%s are not of type %s", + RNA_struct_identifier(searchptr->type), + searchpropname, + RNA_struct_identifier(RNA_property_pointer_type(targetptr, targetprop))); + } + else { + return searchprop; + } + + return NULL; +} + +static TemplateSearch *template_search_setup(PointerRNA *ptr, + const char *const propname, + PointerRNA *searchptr, + const char *const searchpropname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_POINTER) { + RNA_warning("pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return NULL; + } + PropertyRNA *searchprop = template_search_get_searchprop(ptr, prop, searchptr, searchpropname); + + TemplateSearch *template_search = MEM_callocN(sizeof(*template_search), __func__); + template_search->search_data.target_ptr = *ptr; + template_search->search_data.target_prop = prop; + template_search->search_data.search_ptr = *searchptr; + template_search->search_data.search_prop = searchprop; + + return template_search; +} + +/** + * Search menu to pick an item from a collection. + * A version of uiTemplateID that works for non-ID types. + */ +void uiTemplateSearch(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + const char *propname, + PointerRNA *searchptr, + const char *searchpropname, + const char *newop, + const char *unlinkop) +{ + TemplateSearch *template_search = template_search_setup( + ptr, propname, searchptr, searchpropname); + if (template_search != NULL) { + template_search_buttons(C, layout, template_search, newop, unlinkop); + MEM_freeN(template_search); + } +} + +void uiTemplateSearchPreview(uiLayout *layout, + bContext *C, + PointerRNA *ptr, + const char *propname, + PointerRNA *searchptr, + const char *searchpropname, + const char *newop, + const char *unlinkop, + const int rows, + const int cols) +{ + TemplateSearch *template_search = template_search_setup( + ptr, propname, searchptr, searchpropname); + + if (template_search != NULL) { + template_search->use_previews = true; + template_search->preview_rows = rows; + template_search->preview_cols = cols; + + template_search_buttons(C, layout, template_search, newop, unlinkop); + + MEM_freeN(template_search); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name RNA Path Builder Template + * \{ */ + +/* ---------- */ + +/** + * This is creating/editing RNA-Paths + * + * - ptr: struct which holds the path property + * - propname: property identifier for property that path gets stored to + * - root_ptr: struct that path gets built from + */ +void uiTemplatePathBuilder(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + PointerRNA *UNUSED(root_ptr), + const char *text) +{ + /* check that properties are valid */ + PropertyRNA *propPath = RNA_struct_find_property(ptr, propname); + if (!propPath || RNA_property_type(propPath) != PROP_STRING) { + RNA_warning("path property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + /* Start drawing UI Elements using standard defines */ + uiLayout *row = uiLayoutRow(layout, true); + + /* Path (existing string) Widget */ + uiItemR(row, ptr, propname, 0, text, ICON_RNA); + + /* TODO: attach something to this to make allow + * searching of nested properties to 'build' the path */ +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Modifiers Template + * + * Template for building the panel layout for the active object's modifiers. + * \{ */ + +static void modifier_panel_id(void *md_link, char *r_name) +{ + ModifierData *md = (ModifierData *)md_link; + BKE_modifier_type_panel_id(md->type, r_name); +} + +void uiTemplateModifiers(uiLayout *UNUSED(layout), bContext *C) +{ + ARegion *region = CTX_wm_region(C); + + Object *ob = ED_object_active_context(C); + ListBase *modifiers = &ob->modifiers; + + const bool panels_match = UI_panel_list_matches_data(region, modifiers, modifier_panel_id); + + if (!panels_match) { + UI_panels_free_instanced(C, region); + ModifierData *md = modifiers->first; + for (int i = 0; md; i++, md = md->next) { + const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + if (mti->panelRegister == NULL) { + continue; + } + + char panel_idname[MAX_NAME]; + modifier_panel_id(md, panel_idname); + + /* Create custom data RNA pointer. */ + PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_Modifier, md, md_ptr); + + UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel = region->panels.first; + LISTBASE_FOREACH (ModifierData *, md, modifiers) { + const ModifierTypeInfo *mti = BKE_modifier_get_info(md->type); + if (mti->panelRegister == NULL) { + continue; + } + + /* Move to the next instanced panel corresponding to the next modifier. */ + while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { + panel = panel->next; + BLI_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ + } + + PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_Modifier, md, md_ptr); + UI_panel_custom_data_set(panel, md_ptr); + + panel = panel->next; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Constraints Template + * + * Template for building the panel layout for the active object or bone's constraints. + * \{ */ + +/** For building the panel UI for constraints. */ +#define CONSTRAINT_TYPE_PANEL_PREFIX "OBJECT_PT_" +#define CONSTRAINT_BONE_TYPE_PANEL_PREFIX "BONE_PT_" + +/** + * Check if the panel's ID starts with 'BONE', meaning it is a bone constraint. + */ +static bool constraint_panel_is_bone(Panel *panel) +{ + return (panel->panelname[0] == 'B') && (panel->panelname[1] == 'O') && + (panel->panelname[2] == 'N') && (panel->panelname[3] == 'E'); +} + +/** + * Move a constraint to the index it's moved to after a drag and drop. + */ +static void constraint_reorder(bContext *C, Panel *panel, int new_index) +{ + const bool constraint_from_bone = constraint_panel_is_bone(panel); + + PointerRNA *con_ptr = UI_panel_custom_data_get(panel); + bConstraint *con = (bConstraint *)con_ptr->data; + + PointerRNA props_ptr; + wmOperatorType *ot = WM_operatortype_find("CONSTRAINT_OT_move_to_index", false); + WM_operator_properties_create_ptr(&props_ptr, ot); + RNA_string_set(&props_ptr, "constraint", con->name); + RNA_int_set(&props_ptr, "index", new_index); + /* Set owner to #EDIT_CONSTRAINT_OWNER_OBJECT or #EDIT_CONSTRAINT_OWNER_BONE. */ + RNA_enum_set(&props_ptr, "owner", constraint_from_bone ? 1 : 0); + WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr); + WM_operator_properties_free(&props_ptr); +} + +/** + * Get the expand flag from the active constraint to use for the panel. + */ +static short get_constraint_expand_flag(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *con_ptr = UI_panel_custom_data_get(panel); + bConstraint *con = (bConstraint *)con_ptr->data; + + return con->ui_expand_flag; +} + +/** + * Save the expand flag for the panel and sub-panels to the constraint. + */ +static void set_constraint_expand_flag(const bContext *UNUSED(C), Panel *panel, short expand_flag) +{ + PointerRNA *con_ptr = UI_panel_custom_data_get(panel); + bConstraint *con = (bConstraint *)con_ptr->data; + con->ui_expand_flag = expand_flag; +} + +/** + * Function with void * argument for #uiListPanelIDFromDataFunc. + * + * \note Constraint panel types are assumed to be named with the struct name field + * concatenated to the defined prefix. + */ +static void object_constraint_panel_id(void *md_link, char *r_name) +{ + bConstraint *con = (bConstraint *)md_link; + const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_from_type(con->type); + + /* Cannot get TypeInfo for invalid/legacy constraints. */ + if (cti == NULL) { + return; + } + + strcpy(r_name, CONSTRAINT_TYPE_PANEL_PREFIX); + strcat(r_name, cti->structName); +} + +static void bone_constraint_panel_id(void *md_link, char *r_name) +{ + bConstraint *con = (bConstraint *)md_link; + const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_from_type(con->type); + + /* Cannot get TypeInfo for invalid/legacy constraints. */ + if (cti == NULL) { + return; + } + + strcpy(r_name, CONSTRAINT_BONE_TYPE_PANEL_PREFIX); + strcat(r_name, cti->structName); +} + +/** + * Check if the constraint panels don't match the data and rebuild the panels if so. + */ +void uiTemplateConstraints(uiLayout *UNUSED(layout), bContext *C, bool use_bone_constraints) +{ + ARegion *region = CTX_wm_region(C); + + Object *ob = ED_object_active_context(C); + ListBase *constraints = {NULL}; + if (use_bone_constraints) { + constraints = ED_object_pose_constraint_list(C); + } + else if (ob != NULL) { + constraints = &ob->constraints; + } + + /* Switch between the bone panel ID function and the object panel ID function. */ + uiListPanelIDFromDataFunc panel_id_func = use_bone_constraints ? bone_constraint_panel_id : + object_constraint_panel_id; + + const bool panels_match = UI_panel_list_matches_data(region, constraints, panel_id_func); + + if (!panels_match) { + UI_panels_free_instanced(C, region); + bConstraint *con = (constraints == NULL) ? NULL : constraints->first; + for (int i = 0; con; i++, con = con->next) { + /* Don't show invalid/legacy constraints. */ + if (con->type == CONSTRAINT_TYPE_NULL) { + continue; + } + /* Don't show temporary constraints (AutoIK and target-less IK constraints). */ + if (con->type == CONSTRAINT_TYPE_KINEMATIC) { + bKinematicConstraint *data = con->data; + if (data->flag & CONSTRAINT_IK_TEMP) { + continue; + } + } + + char panel_idname[MAX_NAME]; + panel_id_func(con, panel_idname); + + /* Create custom data RNA pointer. */ + PointerRNA *con_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_Constraint, con, con_ptr); + + Panel *new_panel = UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, con_ptr); + + if (new_panel) { + /* Set the list panel functionality function pointers since we don't do it with python. */ + new_panel->type->set_list_data_expand_flag = set_constraint_expand_flag; + new_panel->type->get_list_data_expand_flag = get_constraint_expand_flag; + new_panel->type->reorder = constraint_reorder; + } + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel = region->panels.first; + LISTBASE_FOREACH (bConstraint *, con, constraints) { + /* Don't show invalid/legacy constraints. */ + if (con->type == CONSTRAINT_TYPE_NULL) { + continue; + } + /* Don't show temporary constraints (AutoIK and target-less IK constraints). */ + if (con->type == CONSTRAINT_TYPE_KINEMATIC) { + bKinematicConstraint *data = con->data; + if (data->flag & CONSTRAINT_IK_TEMP) { + continue; + } + } + + /* Move to the next instanced panel corresponding to the next constraint. */ + while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { + panel = panel->next; + BLI_assert(panel != NULL); /* There shouldn't be fewer panels than constraint panels. */ + } + + PointerRNA *con_ptr = MEM_mallocN(sizeof(PointerRNA), "constraint panel customdata"); + RNA_pointer_create(&ob->id, &RNA_Constraint, con, con_ptr); + UI_panel_custom_data_set(panel, con_ptr); + + panel = panel->next; + } + } +} + +#undef CONSTRAINT_TYPE_PANEL_PREFIX +#undef CONSTRAINT_BONE_TYPE_PANEL_PREFIX + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Grease Pencil Modifiers Template + * \{ */ + +/** + * Function with void * argument for #uiListPanelIDFromDataFunc. + */ +static void gpencil_modifier_panel_id(void *md_link, char *r_name) +{ + ModifierData *md = (ModifierData *)md_link; + BKE_gpencil_modifierType_panel_id(md->type, r_name); +} + +void uiTemplateGpencilModifiers(uiLayout *UNUSED(layout), bContext *C) +{ + ARegion *region = CTX_wm_region(C); + Object *ob = ED_object_active_context(C); + ListBase *modifiers = &ob->greasepencil_modifiers; + + const bool panels_match = UI_panel_list_matches_data( + region, modifiers, gpencil_modifier_panel_id); + + if (!panels_match) { + UI_panels_free_instanced(C, region); + GpencilModifierData *md = modifiers->first; + for (int i = 0; md; i++, md = md->next) { + const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type); + if (mti->panelRegister == NULL) { + continue; + } + + char panel_idname[MAX_NAME]; + gpencil_modifier_panel_id(md, panel_idname); + + /* Create custom data RNA pointer. */ + PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_GpencilModifier, md, md_ptr); + + UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, md_ptr); + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel = region->panels.first; + LISTBASE_FOREACH (ModifierData *, md, modifiers) { + const GpencilModifierTypeInfo *mti = BKE_gpencil_modifier_get_info(md->type); + if (mti->panelRegister == NULL) { + continue; + } + + /* Move to the next instanced panel corresponding to the next modifier. */ + while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { + panel = panel->next; + BLI_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ + } + + PointerRNA *md_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_GpencilModifier, md, md_ptr); + UI_panel_custom_data_set(panel, md_ptr); + + panel = panel->next; + } + } +} + +/** \} */ + +/** \} */ + +#define ERROR_LIBDATA_MESSAGE TIP_("Can't edit external library data") + +/* -------------------------------------------------------------------- */ +/** \name ShaderFx Template + * + * Template for building the panel layout for the active object's grease pencil shader + * effects. + * \{ */ + +/** + * Function with void * argument for #uiListPanelIDFromDataFunc. + */ +static void shaderfx_panel_id(void *fx_v, char *r_idname) +{ + ShaderFxData *fx = (ShaderFxData *)fx_v; + BKE_shaderfxType_panel_id(fx->type, r_idname); +} + +/** + * Check if the shader effect panels don't match the data and rebuild the panels if so. + */ +void uiTemplateShaderFx(uiLayout *UNUSED(layout), bContext *C) +{ + ARegion *region = CTX_wm_region(C); + Object *ob = ED_object_active_context(C); + ListBase *shaderfx = &ob->shader_fx; + + const bool panels_match = UI_panel_list_matches_data(region, shaderfx, shaderfx_panel_id); + + if (!panels_match) { + UI_panels_free_instanced(C, region); + ShaderFxData *fx = shaderfx->first; + for (int i = 0; fx; i++, fx = fx->next) { + char panel_idname[MAX_NAME]; + shaderfx_panel_id(fx, panel_idname); + + /* Create custom data RNA pointer. */ + PointerRNA *fx_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_ShaderFx, fx, fx_ptr); + + UI_panel_add_instanced(C, region, ®ion->panels, panel_idname, fx_ptr); + } + } + else { + /* Assuming there's only one group of instanced panels, update the custom data pointers. */ + Panel *panel = region->panels.first; + LISTBASE_FOREACH (ShaderFxData *, fx, shaderfx) { + const ShaderFxTypeInfo *fxi = BKE_shaderfx_get_info(fx->type); + if (fxi->panelRegister == NULL) { + continue; + } + + /* Move to the next instanced panel corresponding to the next modifier. */ + while ((panel->type == NULL) || !(panel->type->flag & PANEL_TYPE_INSTANCED)) { + panel = panel->next; + BLI_assert(panel != NULL); /* There shouldn't be fewer panels than modifiers with UIs. */ + } + + PointerRNA *fx_ptr = MEM_mallocN(sizeof(PointerRNA), "panel customdata"); + RNA_pointer_create(&ob->id, &RNA_ShaderFx, fx, fx_ptr); + UI_panel_custom_data_set(panel, fx_ptr); + + panel = panel->next; + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator Property Buttons Template + * \{ */ + +typedef struct uiTemplateOperatorPropertyPollParam { + const bContext *C; + wmOperator *op; + short flag; +} uiTemplateOperatorPropertyPollParam; + +#ifdef USE_OP_RESET_BUT +static void ui_layout_operator_buts__reset_cb(bContext *UNUSED(C), + void *op_pt, + void *UNUSED(arg_dummy2)) +{ + WM_operator_properties_reset((wmOperator *)op_pt); +} +#endif + +static bool ui_layout_operator_buts_poll_property(struct PointerRNA *UNUSED(ptr), + struct PropertyRNA *prop, + void *user_data) +{ + uiTemplateOperatorPropertyPollParam *params = user_data; + + if ((params->flag & UI_TEMPLATE_OP_PROPS_HIDE_ADVANCED) && + (RNA_property_tags(prop) & OP_PROP_TAG_ADVANCED)) { + return false; + } + return params->op->type->poll_property(params->C, params->op, prop); +} + +static eAutoPropButsReturn template_operator_property_buts_draw_single( + const bContext *C, + wmOperator *op, + uiLayout *layout, + const eButLabelAlign label_align, + int layout_flags) +{ + uiBlock *block = uiLayoutGetBlock(layout); + eAutoPropButsReturn return_info = 0; + + if (!op->properties) { + const IDPropertyTemplate val = {0}; + op->properties = IDP_New(IDP_GROUP, &val, "wmOperatorProperties"); + } + + /* poll() on this operator may still fail, + * at the moment there is no nice feedback when this happens just fails silently. */ + if (!WM_operator_repeat_check(C, op)) { + UI_block_lock_set(block, true, "Operator can't' redo"); + return return_info; + } + + /* useful for macros where only one of the steps can't be re-done */ + UI_block_lock_clear(block); + + if (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_TITLE) { + uiItemL(layout, WM_operatortype_name(op->type, op->ptr), ICON_NONE); + } + + /* menu */ + if (op->type->flag & OPTYPE_PRESET) { + /* XXX, no simple way to get WM_MT_operator_presets.bl_label + * from python! Label remains the same always! */ + PointerRNA op_ptr; + uiLayout *row; + + block->ui_operator = op; + + row = uiLayoutRow(layout, true); + uiItemM(row, "WM_MT_operator_presets", NULL, ICON_NONE); + + wmOperatorType *ot = WM_operatortype_find("WM_OT_operator_preset_add", false); + uiItemFullO_ptr(row, ot, "", ICON_ADD, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); + RNA_string_set(&op_ptr, "operator", op->type->idname); + + uiItemFullO_ptr(row, ot, "", ICON_REMOVE, NULL, WM_OP_INVOKE_DEFAULT, 0, &op_ptr); + RNA_string_set(&op_ptr, "operator", op->type->idname); + RNA_boolean_set(&op_ptr, "remove_active", true); + } + + if (op->type->ui) { + op->layout = layout; + op->type->ui((bContext *)C, op); + op->layout = NULL; + + /* UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too. We could + * allow ot.ui callback to return this, but not needed right now. */ + } + else { + wmWindowManager *wm = CTX_wm_manager(C); + uiTemplateOperatorPropertyPollParam user_data = {.C = C, .op = op, .flag = layout_flags}; + const bool use_prop_split = (layout_flags & UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT) == 0; + + PointerRNA ptr; + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + uiLayoutSetPropSep(layout, use_prop_split); + uiLayoutSetPropDecorate(layout, false); + + /* main draw call */ + return_info = uiDefAutoButsRNA( + layout, + &ptr, + op->type->poll_property ? ui_layout_operator_buts_poll_property : NULL, + op->type->poll_property ? &user_data : NULL, + op->type->prop, + label_align, + (layout_flags & UI_TEMPLATE_OP_PROPS_COMPACT)); + + if ((return_info & UI_PROP_BUTS_NONE_ADDED) && + (layout_flags & UI_TEMPLATE_OP_PROPS_SHOW_EMPTY)) { + uiItemL(layout, IFACE_("No Properties"), ICON_NONE); + } + } + +#ifdef USE_OP_RESET_BUT + /* its possible that reset can do nothing if all have PROP_SKIP_SAVE enabled + * but this is not so important if this button is drawn in those cases + * (which isn't all that likely anyway) - campbell */ + if (op->properties->len) { + uiBut *but; + uiLayout *col; /* needed to avoid alignment errors with previous buttons */ + + col = uiLayoutColumn(layout, false); + block = uiLayoutGetBlock(col); + but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_FILE_REFRESH, + IFACE_("Reset"), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Reset operator defaults")); + UI_but_func_set(but, ui_layout_operator_buts__reset_cb, op, NULL); + } +#endif + + /* set various special settings for buttons */ + + /* Only do this if we're not refreshing an existing UI. */ + if (block->oldblock == NULL) { + const bool is_popup = (block->flag & UI_BLOCK_KEEP_OPEN) != 0; + + LISTBASE_FOREACH (uiBut *, but, &block->buttons) { + /* no undo for buttons for operator redo panels */ + UI_but_flag_disable(but, UI_BUT_UNDO); + + /* only for popups, see T36109. */ + + /* if button is operator's default property, and a text-field, enable focus for it + * - this is used for allowing operators with popups to rename stuff with fewer clicks + */ + if (is_popup) { + if ((but->rnaprop == op->type->prop) && (but->type == UI_BTYPE_TEXT)) { + UI_but_focus_on_enter_event(CTX_wm_window(C), but); + } + } + } + } + + return return_info; +} + +static void template_operator_property_buts_draw_recursive(const bContext *C, + wmOperator *op, + uiLayout *layout, + const eButLabelAlign label_align, + int layout_flags, + bool *r_has_advanced) +{ + if (op->type->flag & OPTYPE_MACRO) { + LISTBASE_FOREACH (wmOperator *, macro_op, &op->macro) { + template_operator_property_buts_draw_recursive( + C, macro_op, layout, label_align, layout_flags, r_has_advanced); + } + } + else { + /* Might want to make label_align adjustable somehow. */ + eAutoPropButsReturn return_info = template_operator_property_buts_draw_single( + C, op, layout, label_align, layout_flags); + if (return_info & UI_PROP_BUTS_ANY_FAILED_CHECK) { + if (r_has_advanced) { + *r_has_advanced = true; + } + } + } +} + +static bool ui_layout_operator_properties_only_booleans(const bContext *C, + wmWindowManager *wm, + wmOperator *op, + int layout_flags) +{ + if (op->type->flag & OPTYPE_MACRO) { + LISTBASE_FOREACH (wmOperator *, macro_op, &op->macro) { + if (!ui_layout_operator_properties_only_booleans(C, wm, macro_op, layout_flags)) { + return false; + } + } + } + else { + uiTemplateOperatorPropertyPollParam user_data = {.C = C, .op = op, .flag = layout_flags}; + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + RNA_STRUCT_BEGIN (&ptr, prop) { + if (RNA_property_flag(prop) & PROP_HIDDEN) { + continue; + } + if (op->type->poll_property && + !ui_layout_operator_buts_poll_property(&ptr, prop, &user_data)) { + continue; + } + if (RNA_property_type(prop) != PROP_BOOLEAN) { + return false; + } + } + RNA_STRUCT_END; + } + + return true; +} + +/** + * Draw Operator property buttons for redoing execution with different settings. + * This function does not initialize the layout, + * functions can be called on the layout before and after. + */ +void uiTemplateOperatorPropertyButs( + const bContext *C, uiLayout *layout, wmOperator *op, eButLabelAlign label_align, short flag) +{ + wmWindowManager *wm = CTX_wm_manager(C); + + /* If there are only checkbox items, don't use split layout by default. It looks weird if the + * checkboxes only use half the width. */ + if (ui_layout_operator_properties_only_booleans(C, wm, op, flag)) { + flag |= UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT; + } + + template_operator_property_buts_draw_recursive(C, op, layout, label_align, flag, NULL); +} + +void uiTemplateOperatorRedoProperties(uiLayout *layout, const bContext *C) +{ + wmOperator *op = WM_operator_last_redo(C); + uiBlock *block = uiLayoutGetBlock(layout); + + if (op == NULL) { + return; + } + + /* Disable for now, doesn't fit well in popover. */ +#if 0 + /* Repeat button with operator name as text. */ + uiItemFullO(layout, + "SCREEN_OT_repeat_last", + WM_operatortype_name(op->type, op->ptr), + ICON_NONE, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + NULL); +#endif + + if (WM_operator_repeat_check(C, op)) { + int layout_flags = 0; + if (block->panel == NULL) { + layout_flags = UI_TEMPLATE_OP_PROPS_SHOW_TITLE; + } +#if 0 + bool has_advanced = false; +#endif + + UI_block_func_handle_set(block, ED_undo_operator_repeat_cb_evt, op); + template_operator_property_buts_draw_recursive( + C, op, layout, UI_BUT_LABEL_ALIGN_NONE, layout_flags, NULL /* &has_advanced */); + /* Warning! this leaves the handle function for any other users of this block. */ + +#if 0 + if (has_advanced) { + uiItemO(layout, IFACE_("More..."), ICON_NONE, "SCREEN_OT_redo_last"); + } +#endif + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Constraint Header Template + * \{ */ + +#define ERROR_LIBDATA_MESSAGE TIP_("Can't edit external library data") + +static void constraint_active_func(bContext *UNUSED(C), void *ob_v, void *con_v) +{ + ED_object_constraint_active_set(ob_v, con_v); +} + +static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *con) +{ + bPoseChannel *pchan = BKE_pose_channel_active(ob); + short proxy_protected, xco = 0, yco = 0; + // int rb_col; // UNUSED + + /* determine whether constraint is proxy protected or not */ + if (BKE_constraints_proxylocked_owner(ob, pchan)) { + proxy_protected = (con->flag & CONSTRAINT_PROXY_LOCAL) == 0; + } + else { + proxy_protected = 0; + } + + /* unless button has own callback, it adds this callback to button */ + uiBlock *block = uiLayoutGetBlock(layout); + UI_block_func_set(block, constraint_active_func, ob, con); + + PointerRNA ptr; + RNA_pointer_create(&ob->id, &RNA_Constraint, con, &ptr); + + uiLayoutSetContextPointer(layout, "constraint", &ptr); + + /* Constraint type icon. */ + uiLayout *sub = uiLayoutRow(layout, false); + uiLayoutSetEmboss(sub, false); + uiLayoutSetRedAlert(sub, (con->flag & CONSTRAINT_DISABLE)); + uiItemL(sub, "", RNA_struct_ui_icon(ptr.type)); + + UI_block_emboss_set(block, UI_EMBOSS); + + if (proxy_protected == 0) { + uiItemR(layout, &ptr, "name", 0, "", ICON_NONE); + } + else { + uiItemL(layout, con->name, ICON_NONE); + } + + /* proxy-protected constraints cannot be edited, so hide up/down + close buttons */ + if (proxy_protected) { + UI_block_emboss_set(block, UI_EMBOSS_NONE); + + /* draw a ghost icon (for proxy) and also a lock beside it, + * to show that constraint is "proxy locked" */ + uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_GHOST_ENABLED, + xco + 12.2f * UI_UNIT_X, + yco, + 0.95f * UI_UNIT_X, + 0.95f * UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Proxy Protected")); + uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_LOCKED, + xco + 13.1f * UI_UNIT_X, + yco, + 0.95f * UI_UNIT_X, + 0.95f * UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Proxy Protected")); + + UI_block_emboss_set(block, UI_EMBOSS); + } + else { + /* enabled */ + UI_block_emboss_set(block, UI_EMBOSS_NONE_OR_STATUS); + uiItemR(layout, &ptr, "mute", 0, "", 0); + UI_block_emboss_set(block, UI_EMBOSS); + + uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); + + /* Close 'button' - emboss calls here disable drawing of 'button' behind X */ + UI_block_emboss_set(block, UI_EMBOSS_NONE); + uiItemO(layout, "", ICON_X, "CONSTRAINT_OT_delete"); + UI_block_emboss_set(block, UI_EMBOSS); + + /* Some extra padding at the end, so the 'x' icon isn't too close to drag button. */ + uiItemS(layout); + } + + /* Set but-locks for protected settings (magic numbers are used here!) */ + if (proxy_protected) { + UI_block_lock_set(block, true, TIP_("Cannot edit Proxy-Protected Constraint")); + } + + /* clear any locks set up for proxies/lib-linking */ + UI_block_lock_clear(block); +} + +void uiTemplateConstraintHeader(uiLayout *layout, PointerRNA *ptr) +{ + /* verify we have valid data */ + if (!RNA_struct_is_a(ptr->type, &RNA_Constraint)) { + RNA_warning("Expected constraint on object"); + return; + } + + Object *ob = (Object *)ptr->owner_id; + bConstraint *con = ptr->data; + + if (!ob || !(GS(ob->id.name) == ID_OB)) { + RNA_warning("Expected constraint on object"); + return; + } + + UI_block_lock_set(uiLayoutGetBlock(layout), (ob && ID_IS_LINKED(ob)), ERROR_LIBDATA_MESSAGE); + + draw_constraint_header(layout, ob, con); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Preview Template + * \{ */ + +#include "DNA_light_types.h" +#include "DNA_material_types.h" +#include "DNA_world_types.h" + +#define B_MATPRV 1 + +static void do_preview_buttons(bContext *C, void *arg, int event) +{ + switch (event) { + case B_MATPRV: + WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_PREVIEW, arg); + break; + } +} + +void uiTemplatePreview(uiLayout *layout, + bContext *C, + ID *id, + bool show_buttons, + ID *parent, + MTex *slot, + const char *preview_id) +{ + Material *ma = NULL; + Tex *tex = (Tex *)id; + short *pr_texture = NULL; + PointerRNA material_ptr; + PointerRNA texture_ptr; + + char _preview_id[UI_MAX_NAME_STR]; + + if (id && !ELEM(GS(id->name), ID_MA, ID_TE, ID_WO, ID_LA, ID_LS)) { + RNA_warning("Expected ID of type material, texture, light, world or line style"); + return; + } + + /* decide what to render */ + ID *pid = id; + ID *pparent = NULL; + + if (id && (GS(id->name) == ID_TE)) { + if (parent && (GS(parent->name) == ID_MA)) { + pr_texture = &((Material *)parent)->pr_texture; + } + else if (parent && (GS(parent->name) == ID_WO)) { + pr_texture = &((World *)parent)->pr_texture; + } + else if (parent && (GS(parent->name) == ID_LA)) { + pr_texture = &((Light *)parent)->pr_texture; + } + else if (parent && (GS(parent->name) == ID_LS)) { + pr_texture = &((FreestyleLineStyle *)parent)->pr_texture; + } + + if (pr_texture) { + if (*pr_texture == TEX_PR_OTHER) { + pid = parent; + } + else if (*pr_texture == TEX_PR_BOTH) { + pparent = parent; + } + } + } + + if (!preview_id || (preview_id[0] == '\0')) { + /* If no identifier given, generate one from ID type. */ + BLI_snprintf( + _preview_id, UI_MAX_NAME_STR, "uiPreview_%s", BKE_idtype_idcode_to_name(GS(id->name))); + preview_id = _preview_id; + } + + /* Find or add the uiPreview to the current Region. */ + ARegion *region = CTX_wm_region(C); + uiPreview *ui_preview = BLI_findstring( + ®ion->ui_previews, preview_id, offsetof(uiPreview, preview_id)); + + if (!ui_preview) { + ui_preview = MEM_callocN(sizeof(uiPreview), "uiPreview"); + BLI_strncpy(ui_preview->preview_id, preview_id, sizeof(ui_preview->preview_id)); + ui_preview->height = (short)(UI_UNIT_Y * 7.6f); + BLI_addtail(®ion->ui_previews, ui_preview); + } + + if (ui_preview->height < UI_UNIT_Y) { + ui_preview->height = UI_UNIT_Y; + } + else if (ui_preview->height > UI_UNIT_Y * 50) { /* Rather high upper limit, yet not insane! */ + ui_preview->height = UI_UNIT_Y * 50; + } + + /* layout */ + uiBlock *block = uiLayoutGetBlock(layout); + uiLayout *row = uiLayoutRow(layout, false); + uiLayout *col = uiLayoutColumn(row, false); + uiLayoutSetKeepAspect(col, true); + + /* add preview */ + uiDefBut(block, + UI_BTYPE_EXTRA, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + ui_preview->height, + pid, + 0.0, + 0.0, + 0, + 0, + ""); + UI_but_func_drawextra_set(block, ED_preview_draw, pparent, slot); + UI_block_func_handle_set(block, do_preview_buttons, NULL); + + uiDefIconButS(block, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10, + (short)(UI_UNIT_Y * 0.3f), + &ui_preview->height, + UI_UNIT_Y, + UI_UNIT_Y * 50.0f, + 0.0f, + 0.0f, + ""); + + /* add buttons */ + if (pid && show_buttons) { + if (GS(pid->name) == ID_MA || (pparent && GS(pparent->name) == ID_MA)) { + if (GS(pid->name) == ID_MA) { + ma = (Material *)pid; + } + else { + ma = (Material *)pparent; + } + + /* Create RNA Pointer */ + RNA_pointer_create(&ma->id, &RNA_Material, ma, &material_ptr); + + col = uiLayoutColumn(row, true); + uiLayoutSetScaleX(col, 1.5); + uiItemR(col, &material_ptr, "preview_render_type", UI_ITEM_R_EXPAND, "", ICON_NONE); + + /* EEVEE preview file has baked lighting so use_preview_world has no effect, + * just hide the option until this feature is supported. */ + if (!BKE_scene_uses_blender_eevee(CTX_data_scene(C))) { + uiItemS(col); + uiItemR(col, &material_ptr, "use_preview_world", 0, "", ICON_WORLD); + } + } + + if (pr_texture) { + /* Create RNA Pointer */ + RNA_pointer_create(id, &RNA_Texture, tex, &texture_ptr); + + uiLayoutRow(layout, true); + uiDefButS(block, + UI_BTYPE_ROW, + B_MATPRV, + IFACE_("Texture"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + pr_texture, + 10, + TEX_PR_TEXTURE, + 0, + 0, + ""); + if (GS(parent->name) == ID_MA) { + uiDefButS(block, + UI_BTYPE_ROW, + B_MATPRV, + IFACE_("Material"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + pr_texture, + 10, + TEX_PR_OTHER, + 0, + 0, + ""); + } + else if (GS(parent->name) == ID_LA) { + uiDefButS(block, + UI_BTYPE_ROW, + B_MATPRV, + CTX_IFACE_(BLT_I18NCONTEXT_ID_LIGHT, "Light"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + pr_texture, + 10, + TEX_PR_OTHER, + 0, + 0, + ""); + } + else if (GS(parent->name) == ID_WO) { + uiDefButS(block, + UI_BTYPE_ROW, + B_MATPRV, + IFACE_("World"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + pr_texture, + 10, + TEX_PR_OTHER, + 0, + 0, + ""); + } + else if (GS(parent->name) == ID_LS) { + uiDefButS(block, + UI_BTYPE_ROW, + B_MATPRV, + IFACE_("Line Style"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + pr_texture, + 10, + TEX_PR_OTHER, + 0, + 0, + ""); + } + uiDefButS(block, + UI_BTYPE_ROW, + B_MATPRV, + IFACE_("Both"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + pr_texture, + 10, + TEX_PR_BOTH, + 0, + 0, + ""); + + /* Alpha button for texture preview */ + if (*pr_texture != TEX_PR_OTHER) { + row = uiLayoutRow(layout, false); + uiItemR(row, &texture_ptr, "use_preview_alpha", 0, NULL, ICON_NONE); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ColorRamp Template + * \{ */ + +typedef struct RNAUpdateCb { + PointerRNA ptr; + PropertyRNA *prop; +} RNAUpdateCb; + +static void rna_update_cb(bContext *C, void *arg_cb, void *UNUSED(arg)) +{ + RNAUpdateCb *cb = (RNAUpdateCb *)arg_cb; + + /* we call update here on the pointer property, this way the + * owner of the curve mapping can still define its own update + * and notifier, even if the CurveMapping struct is shared. */ + RNA_property_update(C, &cb->ptr, cb->prop); +} + +enum { + CB_FUNC_FLIP, + CB_FUNC_DISTRIBUTE_LR, + CB_FUNC_DISTRIBUTE_EVENLY, + CB_FUNC_RESET, +}; + +static void colorband_flip_cb(bContext *C, ColorBand *coba) +{ + CBData data_tmp[MAXCOLORBAND]; + + for (int a = 0; a < coba->tot; a++) { + data_tmp[a] = coba->data[coba->tot - (a + 1)]; + } + for (int a = 0; a < coba->tot; a++) { + data_tmp[a].pos = 1.0f - data_tmp[a].pos; + coba->data[a] = data_tmp[a]; + } + + /* may as well flip the cur*/ + coba->cur = coba->tot - (coba->cur + 1); + + ED_undo_push(C, "Flip Color Ramp"); +} + +static void colorband_distribute_cb(bContext *C, ColorBand *coba, bool evenly) +{ + if (coba->tot > 1) { + const int tot = evenly ? coba->tot - 1 : coba->tot; + const float gap = 1.0f / tot; + float pos = 0.0f; + for (int a = 0; a < coba->tot; a++) { + coba->data[a].pos = pos; + pos += gap; + } + ED_undo_push(C, evenly ? "Distribute Stops Evenly" : "Distribute Stops from Left"); + } +} + +static void colorband_tools_dofunc(bContext *C, void *coba_v, int event) +{ + ColorBand *coba = coba_v; + + switch (event) { + case CB_FUNC_FLIP: + colorband_flip_cb(C, coba); + break; + case CB_FUNC_DISTRIBUTE_LR: + colorband_distribute_cb(C, coba, false); + break; + case CB_FUNC_DISTRIBUTE_EVENLY: + colorband_distribute_cb(C, coba, true); + break; + case CB_FUNC_RESET: + BKE_colorband_init(coba, true); + ED_undo_push(C, "Reset Color Ramp"); + break; + } + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *colorband_tools_func(bContext *C, ARegion *region, void *coba_v) +{ + const uiStyle *style = UI_style_get_dpi(); + ColorBand *coba = coba_v; + short yco = 0; + const short menuwidth = 10 * UI_UNIT_X; + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS_PULLDOWN); + UI_block_func_butmenu_set(block, colorband_tools_dofunc, coba); + + uiLayout *layout = UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_MENU, + 0, + 0, + UI_MENU_WIDTH_MIN, + 0, + UI_MENU_PADDING, + style); + UI_block_layout_set_current(block, layout); + { + PointerRNA coba_ptr; + RNA_pointer_create(NULL, &RNA_ColorRamp, coba, &coba_ptr); + uiLayoutSetContextPointer(layout, "color_ramp", &coba_ptr); + } + + /* We could move these to operators, + * although this isn't important unless we want to assign key shortcuts to them. */ + { + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Flip Color Ramp"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + CB_FUNC_FLIP, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Distribute Stops from Left"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + CB_FUNC_DISTRIBUTE_LR, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Distribute Stops Evenly"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + CB_FUNC_DISTRIBUTE_EVENLY, + ""); + + uiItemO(layout, IFACE_("Eyedropper"), ICON_EYEDROPPER, "UI_OT_eyedropper_colorramp"); + + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset Color Ramp"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + CB_FUNC_RESET, + ""); + } + + UI_block_direction_set(block, UI_DIR_DOWN); + UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X); + + return block; +} + +static void colorband_add_cb(bContext *C, void *cb_v, void *coba_v) +{ + ColorBand *coba = coba_v; + float pos = 0.5f; + + if (coba->tot > 1) { + if (coba->cur > 0) { + pos = (coba->data[coba->cur - 1].pos + coba->data[coba->cur].pos) * 0.5f; + } + else { + pos = (coba->data[coba->cur + 1].pos + coba->data[coba->cur].pos) * 0.5f; + } + } + + if (BKE_colorband_element_add(coba, pos)) { + rna_update_cb(C, cb_v, NULL); + ED_undo_push(C, "Add Color Ramp Stop"); + } +} + +static void colorband_del_cb(bContext *C, void *cb_v, void *coba_v) +{ + ColorBand *coba = coba_v; + + if (BKE_colorband_element_remove(coba, coba->cur)) { + ED_undo_push(C, "Delete Color Ramp Stop"); + rna_update_cb(C, cb_v, NULL); + } +} + +static void colorband_update_cb(bContext *UNUSED(C), void *bt_v, void *coba_v) +{ + uiBut *bt = bt_v; + ColorBand *coba = coba_v; + + /* Sneaky update here, we need to sort the color-band points to be in order, + * however the RNA pointer then is wrong, so we update it */ + BKE_colorband_update_sort(coba); + bt->rnapoin.data = coba->data + coba->cur; +} + +static void colorband_buttons_layout(uiLayout *layout, + uiBlock *block, + ColorBand *coba, + const rctf *butr, + RNAUpdateCb *cb, + int expand) +{ + uiBut *bt; + const float unit = BLI_rctf_size_x(butr) / 14.0f; + const float xs = butr->xmin; + const float ys = butr->ymin; + + PointerRNA ptr; + RNA_pointer_create(cb->ptr.owner_id, &RNA_ColorRamp, coba, &ptr); + + uiLayout *split = uiLayoutSplit(layout, 0.4f, false); + + UI_block_emboss_set(block, UI_EMBOSS_NONE); + UI_block_align_begin(block); + uiLayout *row = uiLayoutRow(split, false); + + bt = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_ADD, + "", + 0, + 0, + 2.0f * unit, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Add a new color stop to the color ramp")); + UI_but_funcN_set(bt, colorband_add_cb, MEM_dupallocN(cb), coba); + + bt = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_REMOVE, + "", + xs + 2.0f * unit, + ys + UI_UNIT_Y, + 2.0f * unit, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Delete the active position")); + UI_but_funcN_set(bt, colorband_del_cb, MEM_dupallocN(cb), coba); + + bt = uiDefIconBlockBut(block, + colorband_tools_func, + coba, + 0, + ICON_DOWNARROW_HLT, + xs + 4.0f * unit, + ys + UI_UNIT_Y, + 2.0f * unit, + UI_UNIT_Y, + TIP_("Tools")); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), coba); + + UI_block_align_end(block); + UI_block_emboss_set(block, UI_EMBOSS); + + row = uiLayoutRow(split, false); + + UI_block_align_begin(block); + uiItemR(row, &ptr, "color_mode", 0, "", ICON_NONE); + if (ELEM(coba->color_mode, COLBAND_BLEND_HSV, COLBAND_BLEND_HSL)) { + uiItemR(row, &ptr, "hue_interpolation", 0, "", ICON_NONE); + } + else { /* COLBAND_BLEND_RGB */ + uiItemR(row, &ptr, "interpolation", 0, "", ICON_NONE); + } + UI_block_align_end(block); + + row = uiLayoutRow(layout, false); + + bt = uiDefBut(block, + UI_BTYPE_COLORBAND, + 0, + "", + xs, + ys, + BLI_rctf_size_x(butr), + UI_UNIT_Y, + coba, + 0, + 0, + 0, + 0, + ""); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + row = uiLayoutRow(layout, false); + + if (coba->tot) { + CBData *cbd = coba->data + coba->cur; + + RNA_pointer_create(cb->ptr.owner_id, &RNA_ColorRampElement, cbd, &ptr); + + if (!expand) { + split = uiLayoutSplit(layout, 0.3f, false); + + row = uiLayoutRow(split, false); + bt = uiDefButS(block, + UI_BTYPE_NUM, + 0, + "", + 0, + 0, + 5.0f * UI_UNIT_X, + UI_UNIT_Y, + &coba->cur, + 0.0, + (float)(MAX2(0, coba->tot - 1)), + 0, + 0, + TIP_("Choose active color stop")); + UI_but_number_step_size_set(bt, 1); + + row = uiLayoutRow(split, false); + uiItemR(row, &ptr, "position", 0, IFACE_("Pos"), ICON_NONE); + bt = block->buttons.last; + UI_but_func_set(bt, colorband_update_cb, bt, coba); + + row = uiLayoutRow(layout, false); + uiItemR(row, &ptr, "color", 0, "", ICON_NONE); + bt = block->buttons.last; + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + } + else { + split = uiLayoutSplit(layout, 0.5f, false); + uiLayout *subsplit = uiLayoutSplit(split, 0.35f, false); + + row = uiLayoutRow(subsplit, false); + bt = uiDefButS(block, + UI_BTYPE_NUM, + 0, + "", + 0, + 0, + 5.0f * UI_UNIT_X, + UI_UNIT_Y, + &coba->cur, + 0.0, + (float)(MAX2(0, coba->tot - 1)), + 0, + 0, + TIP_("Choose active color stop")); + UI_but_number_step_size_set(bt, 1); + + row = uiLayoutRow(subsplit, false); + uiItemR(row, &ptr, "position", UI_ITEM_R_SLIDER, IFACE_("Pos"), ICON_NONE); + bt = block->buttons.last; + UI_but_func_set(bt, colorband_update_cb, bt, coba); + + row = uiLayoutRow(split, false); + uiItemR(row, &ptr, "color", 0, "", ICON_NONE); + bt = block->buttons.last; + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + } + } +} + +void uiTemplateColorRamp(uiLayout *layout, PointerRNA *ptr, const char *propname, bool expand) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_POINTER) { + return; + } + + const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_ColorRamp)) { + return; + } + + RNAUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); + cb->ptr = *ptr; + cb->prop = prop; + + rctf rect; + rect.xmin = 0; + rect.xmax = 10.0f * UI_UNIT_X; + rect.ymin = 0; + rect.ymax = 19.5f * UI_UNIT_X; + + uiBlock *block = uiLayoutAbsoluteBlock(layout); + + ID *id = cptr.owner_id; + UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); + + colorband_buttons_layout(layout, block, cptr.data, &rect, cb, expand); + + UI_block_lock_clear(block); + + MEM_freeN(cb); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Icon Template + * \{ */ + +/** + * \param icon_scale: Scale of the icon, 1x == button height. + */ +void uiTemplateIcon(uiLayout *layout, int icon_value, float icon_scale) +{ + uiBlock *block = uiLayoutAbsoluteBlock(layout); + uiBut *but = uiDefIconBut(block, + UI_BTYPE_LABEL, + 0, + ICON_X, + 0, + 0, + UI_UNIT_X * icon_scale, + UI_UNIT_Y * icon_scale, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + ""); + ui_def_but_icon(but, icon_value, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Icon viewer Template + * \{ */ + +typedef struct IconViewMenuArgs { + PointerRNA ptr; + PropertyRNA *prop; + bool show_labels; + float icon_scale; +} IconViewMenuArgs; + +/* ID Search browse menu, open */ +static uiBlock *ui_icon_view_menu_cb(bContext *C, ARegion *region, void *arg_litem) +{ + static IconViewMenuArgs args; + + /* arg_litem is malloced, can be freed by parent button */ + args = *((IconViewMenuArgs *)arg_litem); + const int w = UI_UNIT_X * (args.icon_scale); + const int h = UI_UNIT_X * (args.icon_scale + args.show_labels); + + uiBlock *block = UI_block_begin(C, region, "_popup", UI_EMBOSS_PULLDOWN); + UI_block_flag_enable(block, UI_BLOCK_LOOP | UI_BLOCK_NO_FLIP); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + bool free; + const EnumPropertyItem *item; + RNA_property_enum_items(C, &args.ptr, args.prop, &item, NULL, &free); + + for (int a = 0; item[a].identifier; a++) { + const int x = (a % 8) * w; + const int y = -(a / 8) * h; + + const int icon = item[a].icon; + const int value = item[a].value; + uiBut *but; + if (args.show_labels) { + but = uiDefIconTextButR_prop(block, + UI_BTYPE_ROW, + 0, + icon, + item[a].name, + x, + y, + w, + h, + &args.ptr, + args.prop, + -1, + 0, + value, + -1, + -1, + NULL); + } + else { + but = uiDefIconButR_prop(block, + UI_BTYPE_ROW, + 0, + icon, + x, + y, + w, + h, + &args.ptr, + args.prop, + -1, + 0, + value, + -1, + -1, + NULL); + } + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + } + + UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); + UI_block_direction_set(block, UI_DIR_DOWN); + + if (free) { + MEM_freeN((void *)item); + } + + return block; +} + +/** + * \param icon_scale: Scale of the icon, 1x == button height. + */ +void uiTemplateIconView(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + bool show_labels, + float icon_scale, + float icon_scale_popup) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_ENUM) { + RNA_warning( + "property of type Enum not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiBlock *block = uiLayoutAbsoluteBlock(layout); + + int tot_items; + bool free_items; + const EnumPropertyItem *items; + RNA_property_enum_items(block->evil_C, ptr, prop, &items, &tot_items, &free_items); + const int value = RNA_property_enum_get(ptr, prop); + int icon = ICON_NONE; + RNA_enum_icon_from_value(items, value, &icon); + + uiBut *but; + if (RNA_property_editable(ptr, prop)) { + IconViewMenuArgs *cb_args = MEM_callocN(sizeof(IconViewMenuArgs), __func__); + cb_args->ptr = *ptr; + cb_args->prop = prop; + cb_args->show_labels = show_labels; + cb_args->icon_scale = icon_scale_popup; + + but = uiDefBlockButN(block, + ui_icon_view_menu_cb, + cb_args, + "", + 0, + 0, + UI_UNIT_X * icon_scale, + UI_UNIT_Y * icon_scale, + ""); + } + else { + but = uiDefIconBut(block, + UI_BTYPE_LABEL, + 0, + ICON_X, + 0, + 0, + UI_UNIT_X * icon_scale, + UI_UNIT_Y * icon_scale, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + ""); + } + + ui_def_but_icon(but, icon, UI_HAS_ICON | UI_BUT_ICON_PREVIEW); + + if (free_items) { + MEM_freeN((void *)items); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Histogram Template + * \{ */ + +void uiTemplateHistogram(uiLayout *layout, PointerRNA *ptr, const char *propname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_POINTER) { + return; + } + + const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Histogram)) { + return; + } + Histogram *hist = (Histogram *)cptr.data; + + if (hist->height < UI_UNIT_Y) { + hist->height = UI_UNIT_Y; + } + else if (hist->height > UI_UNIT_Y * 20) { + hist->height = UI_UNIT_Y * 20; + } + + uiLayout *col = uiLayoutColumn(layout, true); + uiBlock *block = uiLayoutGetBlock(col); + + uiDefBut( + block, UI_BTYPE_HISTOGRAM, 0, "", 0, 0, UI_UNIT_X * 10, hist->height, hist, 0, 0, 0, 0, ""); + + /* Resize grip. */ + uiDefIconButI(block, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10, + (short)(UI_UNIT_Y * 0.3f), + &hist->height, + UI_UNIT_Y, + UI_UNIT_Y * 20.0f, + 0.0f, + 0.0f, + ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Waveform Template + * \{ */ + +void uiTemplateWaveform(uiLayout *layout, PointerRNA *ptr, const char *propname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_POINTER) { + return; + } + + const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Scopes)) { + return; + } + Scopes *scopes = (Scopes *)cptr.data; + + uiLayout *col = uiLayoutColumn(layout, true); + uiBlock *block = uiLayoutGetBlock(col); + + if (scopes->wavefrm_height < UI_UNIT_Y) { + scopes->wavefrm_height = UI_UNIT_Y; + } + else if (scopes->wavefrm_height > UI_UNIT_Y * 20) { + scopes->wavefrm_height = UI_UNIT_Y * 20; + } + + uiDefBut(block, + UI_BTYPE_WAVEFORM, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + scopes->wavefrm_height, + scopes, + 0, + 0, + 0, + 0, + ""); + + /* Resize grip. */ + uiDefIconButI(block, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10, + (short)(UI_UNIT_Y * 0.3f), + &scopes->wavefrm_height, + UI_UNIT_Y, + UI_UNIT_Y * 20.0f, + 0.0f, + 0.0f, + ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Vector-Scope Template + * \{ */ + +void uiTemplateVectorscope(uiLayout *layout, PointerRNA *ptr, const char *propname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop || RNA_property_type(prop) != PROP_POINTER) { + return; + } + + const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Scopes)) { + return; + } + Scopes *scopes = (Scopes *)cptr.data; + + if (scopes->vecscope_height < UI_UNIT_Y) { + scopes->vecscope_height = UI_UNIT_Y; + } + else if (scopes->vecscope_height > UI_UNIT_Y * 20) { + scopes->vecscope_height = UI_UNIT_Y * 20; + } + + uiLayout *col = uiLayoutColumn(layout, true); + uiBlock *block = uiLayoutGetBlock(col); + + uiDefBut(block, + UI_BTYPE_VECTORSCOPE, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + scopes->vecscope_height, + scopes, + 0, + 0, + 0, + 0, + ""); + + /* Resize grip. */ + uiDefIconButI(block, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10, + (short)(UI_UNIT_Y * 0.3f), + &scopes->vecscope_height, + UI_UNIT_Y, + UI_UNIT_Y * 20.0f, + 0.0f, + 0.0f, + ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name CurveMapping Template + * \{ */ + +static void curvemap_buttons_zoom_in(bContext *C, void *cumap_v, void *UNUSED(arg)) +{ + CurveMapping *cumap = cumap_v; + + /* we allow 20 times zoom */ + if (BLI_rctf_size_x(&cumap->curr) > 0.04f * BLI_rctf_size_x(&cumap->clipr)) { + const float dx = 0.1154f * BLI_rctf_size_x(&cumap->curr); + cumap->curr.xmin += dx; + cumap->curr.xmax -= dx; + const float dy = 0.1154f * BLI_rctf_size_y(&cumap->curr); + cumap->curr.ymin += dy; + cumap->curr.ymax -= dy; + } + + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void curvemap_buttons_zoom_out(bContext *C, void *cumap_v, void *UNUSED(unused)) +{ + CurveMapping *cumap = cumap_v; + float d, d1; + + /* we allow 20 times zoom, but don't view outside clip */ + if (BLI_rctf_size_x(&cumap->curr) < 20.0f * BLI_rctf_size_x(&cumap->clipr)) { + d = d1 = 0.15f * BLI_rctf_size_x(&cumap->curr); + + if (cumap->flag & CUMA_DO_CLIP) { + if (cumap->curr.xmin - d < cumap->clipr.xmin) { + d1 = cumap->curr.xmin - cumap->clipr.xmin; + } + } + cumap->curr.xmin -= d1; + + d1 = d; + if (cumap->flag & CUMA_DO_CLIP) { + if (cumap->curr.xmax + d > cumap->clipr.xmax) { + d1 = -cumap->curr.xmax + cumap->clipr.xmax; + } + } + cumap->curr.xmax += d1; + + d = d1 = 0.15f * BLI_rctf_size_y(&cumap->curr); + + if (cumap->flag & CUMA_DO_CLIP) { + if (cumap->curr.ymin - d < cumap->clipr.ymin) { + d1 = cumap->curr.ymin - cumap->clipr.ymin; + } + } + cumap->curr.ymin -= d1; + + d1 = d; + if (cumap->flag & CUMA_DO_CLIP) { + if (cumap->curr.ymax + d > cumap->clipr.ymax) { + d1 = -cumap->curr.ymax + cumap->clipr.ymax; + } + } + cumap->curr.ymax += d1; + } + + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void curvemap_buttons_setclip(bContext *UNUSED(C), void *cumap_v, void *UNUSED(arg)) +{ + CurveMapping *cumap = cumap_v; + + BKE_curvemapping_changed(cumap, false); +} + +static void curvemap_buttons_delete(bContext *C, void *cb_v, void *cumap_v) +{ + CurveMapping *cumap = cumap_v; + + BKE_curvemap_remove(cumap->cm + cumap->cur, SELECT); + BKE_curvemapping_changed(cumap, false); + + rna_update_cb(C, cb_v, NULL); +} + +/* NOTE: this is a block-menu, needs 0 events, otherwise the menu closes */ +static uiBlock *curvemap_clipping_func(bContext *C, ARegion *region, void *cumap_v) +{ + CurveMapping *cumap = cumap_v; + uiBut *bt; + const float width = 8 * UI_UNIT_X; + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_MOVEMOUSE_QUIT); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + + bt = uiDefButBitI(block, + UI_BTYPE_CHECKBOX, + CUMA_DO_CLIP, + 1, + IFACE_("Use Clipping"), + 0, + 5 * UI_UNIT_Y, + width, + UI_UNIT_Y, + &cumap->flag, + 0.0, + 0.0, + 10, + 0, + ""); + UI_but_func_set(bt, curvemap_buttons_setclip, cumap, NULL); + + UI_block_align_begin(block); + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + IFACE_("Min X:"), + 0, + 4 * UI_UNIT_Y, + width, + UI_UNIT_Y, + &cumap->clipr.xmin, + -100.0, + cumap->clipr.xmax, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 10); + UI_but_number_precision_set(bt, 2); + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + IFACE_("Min Y:"), + 0, + 3 * UI_UNIT_Y, + width, + UI_UNIT_Y, + &cumap->clipr.ymin, + -100.0, + cumap->clipr.ymax, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 10); + UI_but_number_precision_set(bt, 2); + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + IFACE_("Max X:"), + 0, + 2 * UI_UNIT_Y, + width, + UI_UNIT_Y, + &cumap->clipr.xmax, + cumap->clipr.xmin, + 100.0, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 10); + UI_but_number_precision_set(bt, 2); + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + IFACE_("Max Y:"), + 0, + UI_UNIT_Y, + width, + UI_UNIT_Y, + &cumap->clipr.ymax, + cumap->clipr.ymin, + 100.0, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 10); + UI_but_number_precision_set(bt, 2); + + UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); + UI_block_direction_set(block, UI_DIR_DOWN); + + return block; +} + +/* only for BKE_curvemap_tools_dofunc */ +enum { + UICURVE_FUNC_RESET_NEG, + UICURVE_FUNC_RESET_POS, + UICURVE_FUNC_RESET_VIEW, + UICURVE_FUNC_HANDLE_VECTOR, + UICURVE_FUNC_HANDLE_AUTO, + UICURVE_FUNC_HANDLE_AUTO_ANIM, + UICURVE_FUNC_EXTEND_HOZ, + UICURVE_FUNC_EXTEND_EXP, +}; + +static void curvemap_tools_dofunc(bContext *C, void *cumap_v, int event) +{ + CurveMapping *cumap = cumap_v; + CurveMap *cuma = cumap->cm + cumap->cur; + + switch (event) { + case UICURVE_FUNC_RESET_NEG: + case UICURVE_FUNC_RESET_POS: /* reset */ + BKE_curvemap_reset(cuma, + &cumap->clipr, + cumap->preset, + (event == UICURVE_FUNC_RESET_NEG) ? CURVEMAP_SLOPE_NEGATIVE : + CURVEMAP_SLOPE_POSITIVE); + BKE_curvemapping_changed(cumap, false); + break; + case UICURVE_FUNC_RESET_VIEW: + BKE_curvemapping_reset_view(cumap); + break; + case UICURVE_FUNC_HANDLE_VECTOR: /* set vector */ + BKE_curvemap_handle_set(cuma, HD_VECT); + BKE_curvemapping_changed(cumap, false); + break; + case UICURVE_FUNC_HANDLE_AUTO: /* set auto */ + BKE_curvemap_handle_set(cuma, HD_AUTO); + BKE_curvemapping_changed(cumap, false); + break; + case UICURVE_FUNC_HANDLE_AUTO_ANIM: /* set auto-clamped */ + BKE_curvemap_handle_set(cuma, HD_AUTO_ANIM); + BKE_curvemapping_changed(cumap, false); + break; + case UICURVE_FUNC_EXTEND_HOZ: /* extend horiz */ + cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE; + BKE_curvemapping_changed(cumap, false); + break; + case UICURVE_FUNC_EXTEND_EXP: /* extend extrapolate */ + cumap->flag |= CUMA_EXTEND_EXTRAPOLATE; + BKE_curvemapping_changed(cumap, false); + break; + } + ED_undo_push(C, "CurveMap tools"); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *curvemap_tools_func( + bContext *C, ARegion *region, CurveMapping *cumap, bool show_extend, int reset_mode) +{ + short yco = 0; + const short menuwidth = 10 * UI_UNIT_X; + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + UI_block_func_butmenu_set(block, curvemap_tools_dofunc, cumap); + + { + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset View"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UICURVE_FUNC_RESET_VIEW, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Vector Handle"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UICURVE_FUNC_HANDLE_VECTOR, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Auto Handle"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UICURVE_FUNC_HANDLE_AUTO, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Auto Clamped Handle"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UICURVE_FUNC_HANDLE_AUTO_ANIM, + ""); + } + + if (show_extend) { + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Extend Horizontal"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UICURVE_FUNC_EXTEND_HOZ, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Extend Extrapolated"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UICURVE_FUNC_EXTEND_EXP, + ""); + } + + { + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset Curve"), + 0, + yco -= UI_UNIT_Y, + menuwidth, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + reset_mode, + ""); + } + + UI_block_direction_set(block, UI_DIR_DOWN); + UI_block_bounds_set_text(block, 3.0f * UI_UNIT_X); + + return block; +} + +static uiBlock *curvemap_tools_posslope_func(bContext *C, ARegion *region, void *cumap_v) +{ + return curvemap_tools_func(C, region, cumap_v, true, UICURVE_FUNC_RESET_POS); +} + +static uiBlock *curvemap_tools_negslope_func(bContext *C, ARegion *region, void *cumap_v) +{ + return curvemap_tools_func(C, region, cumap_v, true, UICURVE_FUNC_RESET_NEG); +} + +static uiBlock *curvemap_brush_tools_func(bContext *C, ARegion *region, void *cumap_v) +{ + return curvemap_tools_func(C, region, cumap_v, false, UICURVE_FUNC_RESET_NEG); +} + +static uiBlock *curvemap_brush_tools_negslope_func(bContext *C, ARegion *region, void *cumap_v) +{ + return curvemap_tools_func(C, region, cumap_v, false, UICURVE_FUNC_RESET_POS); +} + +static void curvemap_buttons_redraw(bContext *C, void *UNUSED(arg1), void *UNUSED(arg2)) +{ + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void curvemap_buttons_update(bContext *C, void *arg1_v, void *cumap_v) +{ + CurveMapping *cumap = cumap_v; + BKE_curvemapping_changed(cumap, true); + rna_update_cb(C, arg1_v, NULL); +} + +static void curvemap_buttons_reset(bContext *C, void *cb_v, void *cumap_v) +{ + CurveMapping *cumap = cumap_v; + cumap->preset = CURVE_PRESET_LINE; + for (int a = 0; a < CM_TOT; a++) { + BKE_curvemap_reset(cumap->cm + a, &cumap->clipr, cumap->preset, CURVEMAP_SLOPE_POSITIVE); + } + + cumap->black[0] = cumap->black[1] = cumap->black[2] = 0.0f; + cumap->white[0] = cumap->white[1] = cumap->white[2] = 1.0f; + BKE_curvemapping_set_black_white(cumap, NULL, NULL); + + BKE_curvemapping_changed(cumap, false); + + rna_update_cb(C, cb_v, NULL); +} + +/** + * \note Still unsure how this call evolves. + * + * \param labeltype: Used for defining which curve-channels to show. + */ +static void curvemap_buttons_layout(uiLayout *layout, + PointerRNA *ptr, + char labeltype, + bool levels, + bool brush, + bool neg_slope, + bool tone, + RNAUpdateCb *cb) +{ + CurveMapping *cumap = ptr->data; + CurveMap *cm = &cumap->cm[cumap->cur]; + uiBut *bt; + const float dx = UI_UNIT_X; + int bg = -1; + + uiBlock *block = uiLayoutGetBlock(layout); + + if (tone) { + uiLayout *split = uiLayoutSplit(layout, 0.0f, false); + uiItemR(uiLayoutRow(split, false), ptr, "tone", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + } + + /* curve chooser */ + uiLayout *row = uiLayoutRow(layout, false); + + if (labeltype == 'v') { + /* vector */ + uiLayout *sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); + + if (cumap->cm[0].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "X", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[1].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "Y", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[2].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "Z", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + } + else if (labeltype == 'c') { + /* color */ + uiLayout *sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); + + if (cumap->cm[3].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "C", 0, 0, dx, dx, &cumap->cur, 0.0, 3.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[0].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "R", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[1].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "G", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[2].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "B", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + } + else if (labeltype == 'h') { + /* HSV */ + uiLayout *sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); + + if (cumap->cm[0].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "H", 0, 0, dx, dx, &cumap->cur, 0.0, 0.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[1].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "S", 0, 0, dx, dx, &cumap->cur, 0.0, 1.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + if (cumap->cm[2].curve) { + bt = uiDefButI( + block, UI_BTYPE_ROW, 0, "V", 0, 0, dx, dx, &cumap->cur, 0.0, 2.0, 0.0, 0.0, ""); + UI_but_func_set(bt, curvemap_buttons_redraw, NULL, NULL); + } + } + else { + uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_RIGHT); + } + + if (labeltype == 'h') { + bg = UI_GRAD_H; + } + + /* operation buttons */ + uiLayoutRow(row, true); + + UI_block_emboss_set(block, UI_EMBOSS_NONE); + + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ZOOM_IN, + 0, + 0, + dx, + dx, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Zoom in")); + UI_but_func_set(bt, curvemap_buttons_zoom_in, cumap, NULL); + + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ZOOM_OUT, + 0, + 0, + dx, + dx, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Zoom out")); + UI_but_func_set(bt, curvemap_buttons_zoom_out, cumap, NULL); + + if (brush && neg_slope) { + bt = uiDefIconBlockBut(block, + curvemap_brush_tools_negslope_func, + cumap, + 0, + ICON_DOWNARROW_HLT, + 0, + 0, + dx, + dx, + TIP_("Tools")); + } + else if (brush) { + bt = uiDefIconBlockBut(block, + curvemap_brush_tools_func, + cumap, + 0, + ICON_DOWNARROW_HLT, + 0, + 0, + dx, + dx, + TIP_("Tools")); + } + else if (neg_slope) { + bt = uiDefIconBlockBut(block, + curvemap_tools_negslope_func, + cumap, + 0, + ICON_DOWNARROW_HLT, + 0, + 0, + dx, + dx, + TIP_("Tools")); + } + else { + bt = uiDefIconBlockBut(block, + curvemap_tools_posslope_func, + cumap, + 0, + ICON_DOWNARROW_HLT, + 0, + 0, + dx, + dx, + TIP_("Tools")); + } + + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + const int icon = (cumap->flag & CUMA_DO_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT; + bt = uiDefIconBlockBut( + block, curvemap_clipping_func, cumap, 0, icon, 0, 0, dx, dx, TIP_("Clipping Options")); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_X, + 0, + 0, + dx, + dx, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Delete points")); + UI_but_funcN_set(bt, curvemap_buttons_delete, MEM_dupallocN(cb), cumap); + + UI_block_emboss_set(block, UI_EMBOSS); + + UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL); + + /* curve itself */ + const int size = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X); + row = uiLayoutRow(layout, false); + uiButCurveMapping *curve_but = (uiButCurveMapping *)uiDefBut( + block, UI_BTYPE_CURVE, 0, "", 0, 0, size, 8.0f * UI_UNIT_X, cumap, 0.0f, 1.0f, 0, 0, ""); + curve_but->gradient_type = bg; + + /* sliders for selected point */ + CurveMapPoint *cmp = NULL; + for (int i = 0; i < cm->totpoint; i++) { + if (cm->curve[i].flag & CUMA_SELECT) { + cmp = &cm->curve[i]; + break; + } + } + + if (cmp) { + rctf bounds; + if (cumap->flag & CUMA_DO_CLIP) { + bounds = cumap->clipr; + } + else { + bounds.xmin = bounds.ymin = -1000.0; + bounds.xmax = bounds.ymax = 1000.0; + } + + uiLayoutRow(layout, true); + UI_block_funcN_set(block, curvemap_buttons_update, MEM_dupallocN(cb), cumap); + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + "X", + 0, + 2 * UI_UNIT_Y, + UI_UNIT_X * 10, + UI_UNIT_Y, + &cmp->x, + bounds.xmin, + bounds.xmax, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 1); + UI_but_number_precision_set(bt, 5); + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + "Y", + 0, + 1 * UI_UNIT_Y, + UI_UNIT_X * 10, + UI_UNIT_Y, + &cmp->y, + bounds.ymin, + bounds.ymax, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 1); + UI_but_number_precision_set(bt, 5); + } + + /* black/white levels */ + if (levels) { + uiLayout *split = uiLayoutSplit(layout, 0.0f, false); + uiItemR(uiLayoutColumn(split, false), ptr, "black_level", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + uiItemR(uiLayoutColumn(split, false), ptr, "white_level", UI_ITEM_R_EXPAND, NULL, ICON_NONE); + + uiLayoutRow(layout, false); + bt = uiDefBut(block, + UI_BTYPE_BUT, + 0, + IFACE_("Reset"), + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0, + 0, + TIP_("Reset Black/White point and curves")); + UI_but_funcN_set(bt, curvemap_buttons_reset, MEM_dupallocN(cb), cumap); + } + + UI_block_funcN_set(block, NULL, NULL, NULL); +} + +void uiTemplateCurveMapping(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + int type, + bool levels, + bool brush, + bool neg_slope, + bool tone) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + uiBlock *block = uiLayoutGetBlock(layout); + + if (!prop) { + RNA_warning("curve property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + if (RNA_property_type(prop) != PROP_POINTER) { + RNA_warning("curve is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_CurveMapping)) { + return; + } + + RNAUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); + cb->ptr = *ptr; + cb->prop = prop; + + ID *id = cptr.owner_id; + UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); + + curvemap_buttons_layout(layout, &cptr, type, levels, brush, neg_slope, tone, cb); + + UI_block_lock_clear(block); + + MEM_freeN(cb); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Curve Profile Template + * \{ */ + +static void CurveProfile_presets_dofunc(bContext *C, void *profile_v, int event) +{ + CurveProfile *profile = profile_v; + + profile->preset = event; + BKE_curveprofile_reset(profile); + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + + ED_undo_push(C, "CurveProfile tools"); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *CurveProfile_presets_func(bContext *C, ARegion *region, CurveProfile *profile) +{ + short yco = 0; + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + UI_block_func_butmenu_set(block, CurveProfile_presets_dofunc, profile); + + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Default"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_LINE, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Support Loops"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_SUPPORTS, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Cornice Molding"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_CORNICE, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Crown Molding"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_CROWN, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Steps"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + PROF_PRESET_STEPS, + ""); + + UI_block_direction_set(block, UI_DIR_DOWN); + UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X)); + + return block; +} + +static uiBlock *CurveProfile_buttons_presets(bContext *C, ARegion *region, void *profile_v) +{ + return CurveProfile_presets_func(C, region, (CurveProfile *)profile_v); +} + +/* Only for CurveProfile tools block */ +enum { + UIPROFILE_FUNC_RESET, + UIPROFILE_FUNC_RESET_VIEW, +}; + +static void CurveProfile_tools_dofunc(bContext *C, void *profile_v, int event) +{ + CurveProfile *profile = profile_v; + + switch (event) { + case UIPROFILE_FUNC_RESET: /* reset */ + BKE_curveprofile_reset(profile); + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + break; + case UIPROFILE_FUNC_RESET_VIEW: /* reset view to clipping rect */ + BKE_curveprofile_reset_view(profile); + break; + } + ED_undo_push(C, "CurveProfile tools"); + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static uiBlock *CurveProfile_tools_func(bContext *C, ARegion *region, CurveProfile *profile) +{ + short yco = 0; + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + UI_block_func_butmenu_set(block, CurveProfile_tools_dofunc, profile); + + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset View"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UIPROFILE_FUNC_RESET_VIEW, + ""); + uiDefIconTextBut(block, + UI_BTYPE_BUT_MENU, + 1, + ICON_BLANK1, + IFACE_("Reset Curve"), + 0, + yco -= UI_UNIT_Y, + 0, + UI_UNIT_Y, + NULL, + 0.0, + 0.0, + 0, + UIPROFILE_FUNC_RESET, + ""); + + UI_block_direction_set(block, UI_DIR_DOWN); + UI_block_bounds_set_text(block, (int)(3.0f * UI_UNIT_X)); + + return block; +} + +static uiBlock *CurveProfile_buttons_tools(bContext *C, ARegion *region, void *profile_v) +{ + return CurveProfile_tools_func(C, region, (CurveProfile *)profile_v); +} + +static void CurveProfile_buttons_zoom_in(bContext *C, void *profile_v, void *UNUSED(arg)) +{ + CurveProfile *profile = profile_v; + + /* Allow a 20x zoom. */ + if (BLI_rctf_size_x(&profile->view_rect) > 0.04f * BLI_rctf_size_x(&profile->clip_rect)) { + const float dx = 0.1154f * BLI_rctf_size_x(&profile->view_rect); + profile->view_rect.xmin += dx; + profile->view_rect.xmax -= dx; + const float dy = 0.1154f * BLI_rctf_size_y(&profile->view_rect); + profile->view_rect.ymin += dy; + profile->view_rect.ymax -= dy; + } + + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void CurveProfile_buttons_zoom_out(bContext *C, void *profile_v, void *UNUSED(arg)) +{ + CurveProfile *profile = profile_v; + + /* Allow 20 times zoom, but don't view outside clip */ + if (BLI_rctf_size_x(&profile->view_rect) < 20.0f * BLI_rctf_size_x(&profile->clip_rect)) { + float d = 0.15f * BLI_rctf_size_x(&profile->view_rect); + float d1 = d; + + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.xmin - d < profile->clip_rect.xmin) { + d1 = profile->view_rect.xmin - profile->clip_rect.xmin; + } + } + profile->view_rect.xmin -= d1; + + d1 = d; + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.xmax + d > profile->clip_rect.xmax) { + d1 = -profile->view_rect.xmax + profile->clip_rect.xmax; + } + } + profile->view_rect.xmax += d1; + + d = d1 = 0.15f * BLI_rctf_size_y(&profile->view_rect); + + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.ymin - d < profile->clip_rect.ymin) { + d1 = profile->view_rect.ymin - profile->clip_rect.ymin; + } + } + profile->view_rect.ymin -= d1; + + d1 = d; + if (profile->flag & PROF_USE_CLIP) { + if (profile->view_rect.ymax + d > profile->clip_rect.ymax) { + d1 = -profile->view_rect.ymax + profile->clip_rect.ymax; + } + } + profile->view_rect.ymax += d1; + } + + ED_region_tag_redraw(CTX_wm_region(C)); +} + +static void CurveProfile_clipping_toggle(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + profile->flag ^= PROF_USE_CLIP; + + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_reverse(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + BKE_curveprofile_reverse(profile); + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_delete(bContext *C, void *cb_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + + BKE_curveprofile_remove_by_flag(profile, SELECT); + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + + rna_update_cb(C, cb_v, NULL); +} + +static void CurveProfile_buttons_update(bContext *C, void *arg1_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + BKE_curveprofile_update(profile, PROF_UPDATE_REMOVE_DOUBLES | PROF_UPDATE_CLIP); + rna_update_cb(C, arg1_v, NULL); +} + +static void CurveProfile_buttons_reset(bContext *C, void *arg1_v, void *profile_v) +{ + CurveProfile *profile = profile_v; + BKE_curveprofile_reset(profile); + BKE_curveprofile_update(profile, PROF_UPDATE_NONE); + rna_update_cb(C, arg1_v, NULL); +} + +static void CurveProfile_buttons_layout(uiLayout *layout, PointerRNA *ptr, RNAUpdateCb *cb) +{ + CurveProfile *profile = ptr->data; + uiBut *bt; + + uiBlock *block = uiLayoutGetBlock(layout); + + UI_block_emboss_set(block, UI_EMBOSS); + + uiLayoutSetPropSep(layout, false); + + /* Preset selector */ + /* There is probably potential to use simpler "uiItemR" functions here, but automatic updating + * after a preset is selected would be more complicated. */ + uiLayout *row = uiLayoutRow(layout, true); + bt = uiDefBlockBut( + block, CurveProfile_buttons_presets, profile, "Preset", 0, 0, UI_UNIT_X, UI_UNIT_X, ""); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + /* Show a "re-apply" preset button when it has been changed from the preset. */ + if (profile->flag & PROF_DIRTY_PRESET) { + /* Only for dynamic presets. */ + if (ELEM(profile->preset, PROF_PRESET_STEPS, PROF_PRESET_SUPPORTS)) { + bt = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_NONE, + "Apply Preset", + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + "Reapply and update the preset, removing changes"); + UI_but_funcN_set(bt, CurveProfile_buttons_reset, MEM_dupallocN(cb), profile); + } + } + + row = uiLayoutRow(layout, false); + + /* (Left aligned) */ + uiLayout *sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT); + + /* Zoom in */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ZOOM_IN, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Zoom in")); + UI_but_func_set(bt, CurveProfile_buttons_zoom_in, profile, NULL); + + /* Zoom out */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ZOOM_OUT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Zoom out")); + UI_but_func_set(bt, CurveProfile_buttons_zoom_out, profile, NULL); + + /* (Right aligned) */ + sub = uiLayoutRow(row, true); + uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_RIGHT); + + /* Reset view, reset curve */ + bt = uiDefIconBlockBut( + block, CurveProfile_buttons_tools, profile, 0, 0, 0, 0, UI_UNIT_X, UI_UNIT_X, TIP_("Tools")); + UI_but_funcN_set(bt, rna_update_cb, MEM_dupallocN(cb), NULL); + + /* Flip path */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_ARROW_LEFTRIGHT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Reverse Path")); + UI_but_funcN_set(bt, CurveProfile_buttons_reverse, MEM_dupallocN(cb), profile); + + /* Clipping toggle */ + const int icon = (profile->flag & PROF_USE_CLIP) ? ICON_CLIPUV_HLT : ICON_CLIPUV_DEHLT; + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + icon, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Toggle Profile Clipping")); + UI_but_funcN_set(bt, CurveProfile_clipping_toggle, MEM_dupallocN(cb), profile); + + UI_block_funcN_set(block, rna_update_cb, MEM_dupallocN(cb), NULL); + + /* The path itself */ + int path_width = max_ii(uiLayoutGetWidth(layout), UI_UNIT_X); + path_width = min_ii(path_width, (int)(16.0f * UI_UNIT_X)); + const int path_height = path_width; + uiLayoutRow(layout, false); + uiDefBut(block, + UI_BTYPE_CURVEPROFILE, + 0, + "", + 0, + 0, + (short)path_width, + (short)path_height, + profile, + 0.0f, + 1.0f, + -1, + 0, + ""); + + /* Position sliders for (first) selected point */ + int i; + float *selection_x, *selection_y; + bool point_last_or_first = false; + CurveProfilePoint *point = NULL; + for (i = 0; i < profile->path_len; i++) { + if (profile->path[i].flag & PROF_SELECT) { + point = &profile->path[i]; + selection_x = &point->x; + selection_y = &point->y; + break; + } + if (profile->path[i].flag & PROF_H1_SELECT) { + point = &profile->path[i]; + selection_x = &point->h1_loc[0]; + selection_y = &point->h1_loc[1]; + } + else if (profile->path[i].flag & PROF_H2_SELECT) { + point = &profile->path[i]; + selection_x = &point->h2_loc[0]; + selection_y = &point->h2_loc[1]; + } + } + if (ELEM(i, 0, profile->path_len - 1)) { + point_last_or_first = true; + } + + /* Selected point data */ + rctf bounds; + if (point) { + if (profile->flag & PROF_USE_CLIP) { + bounds = profile->clip_rect; + } + else { + bounds.xmin = bounds.ymin = -1000.0; + bounds.xmax = bounds.ymax = 1000.0; + } + + row = uiLayoutRow(layout, true); + + PointerRNA point_ptr; + RNA_pointer_create(ptr->owner_id, &RNA_CurveProfilePoint, point, &point_ptr); + PropertyRNA *prop_handle_type = RNA_struct_find_property(&point_ptr, "handle_type_1"); + uiItemFullR(row, + &point_ptr, + prop_handle_type, + RNA_NO_INDEX, + 0, + UI_ITEM_R_EXPAND | UI_ITEM_R_ICON_ONLY, + "", + ICON_NONE); + + /* Position */ + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + "X:", + 0, + 2 * UI_UNIT_Y, + UI_UNIT_X * 10, + UI_UNIT_Y, + selection_x, + bounds.xmin, + bounds.xmax, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 1); + UI_but_number_precision_set(bt, 5); + UI_but_funcN_set(bt, CurveProfile_buttons_update, MEM_dupallocN(cb), profile); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + bt = uiDefButF(block, + UI_BTYPE_NUM, + 0, + "Y:", + 0, + 1 * UI_UNIT_Y, + UI_UNIT_X * 10, + UI_UNIT_Y, + selection_y, + bounds.ymin, + bounds.ymax, + 0, + 0, + ""); + UI_but_number_step_size_set(bt, 1); + UI_but_number_precision_set(bt, 5); + UI_but_funcN_set(bt, CurveProfile_buttons_update, MEM_dupallocN(cb), profile); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + + /* Delete points */ + bt = uiDefIconBut(block, + UI_BTYPE_BUT, + 0, + ICON_X, + 0, + 0, + UI_UNIT_X, + UI_UNIT_X, + NULL, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Delete points")); + UI_but_funcN_set(bt, CurveProfile_buttons_delete, MEM_dupallocN(cb), profile); + if (point_last_or_first) { + UI_but_flag_enable(bt, UI_BUT_DISABLED); + } + } + + uiItemR(layout, ptr, "use_sample_straight_edges", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "use_sample_even_lengths", 0, NULL, ICON_NONE); + + UI_block_funcN_set(block, NULL, NULL, NULL); +} + +/** + * Template for a path creation widget intended for custom bevel profiles. + * This section is quite similar to #uiTemplateCurveMapping, but with reduced complexity. + */ +void uiTemplateCurveProfile(uiLayout *layout, PointerRNA *ptr, const char *propname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + uiBlock *block = uiLayoutGetBlock(layout); + + if (!prop) { + RNA_warning( + "Curve Profile property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + if (RNA_property_type(prop) != PROP_POINTER) { + RNA_warning( + "Curve Profile is not a pointer: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_CurveProfile)) { + return; + } + + /* Share update functionality with the CurveMapping widget template. */ + RNAUpdateCb *cb = MEM_callocN(sizeof(RNAUpdateCb), "RNAUpdateCb"); + cb->ptr = *ptr; + cb->prop = prop; + + ID *id = cptr.owner_id; + UI_block_lock_set(block, (id && ID_IS_LINKED(id)), ERROR_LIBDATA_MESSAGE); + + CurveProfile_buttons_layout(layout, &cptr, cb); + + UI_block_lock_clear(block); + + MEM_freeN(cb); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name ColorPicker Template + * \{ */ + +#define WHEEL_SIZE (5 * U.widget_unit) + +/* This template now follows User Preference for type - name is not correct anymore... */ +void uiTemplateColorPicker(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + bool value_slider, + bool lock, + bool lock_luminosity, + bool cubic) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + uiBlock *block = uiLayoutGetBlock(layout); + ColorPicker *cpicker = ui_block_colorpicker_create(block); + + if (!prop) { + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + float softmin, softmax, step, precision; + RNA_property_float_ui_range(ptr, prop, &softmin, &softmax, &step, &precision); + + uiLayout *col = uiLayoutColumn(layout, true); + uiLayout *row = uiLayoutRow(col, true); + + uiBut *but = NULL; + uiButHSVCube *hsv_but; + switch (U.color_picker_type) { + case USER_CP_SQUARE_SV: + case USER_CP_SQUARE_HS: + case USER_CP_SQUARE_HV: + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + 0, + 0, + WHEEL_SIZE, + WHEEL_SIZE, + ptr, + prop, + -1, + 0.0, + 0.0, + 0, + 0, + ""); + switch (U.color_picker_type) { + case USER_CP_SQUARE_SV: + hsv_but->gradient_type = UI_GRAD_SV; + break; + case USER_CP_SQUARE_HS: + hsv_but->gradient_type = UI_GRAD_HS; + break; + case USER_CP_SQUARE_HV: + hsv_but->gradient_type = UI_GRAD_HV; + break; + } + but = &hsv_but->but; + break; + + /* user default */ + case USER_CP_CIRCLE_HSV: + case USER_CP_CIRCLE_HSL: + default: + but = uiDefButR_prop(block, + UI_BTYPE_HSVCIRCLE, + 0, + "", + 0, + 0, + WHEEL_SIZE, + WHEEL_SIZE, + ptr, + prop, + -1, + 0.0, + 0.0, + 0, + 0, + ""); + break; + } + + but->custom_data = cpicker; + + cpicker->use_color_lock = lock; + cpicker->use_color_cubic = cubic; + cpicker->use_luminosity_lock = lock_luminosity; + + if (lock_luminosity) { + float color[4]; /* in case of alpha */ + RNA_property_float_get_array(ptr, prop, color); + but->a2 = len_v3(color); + cpicker->luminosity_lock_value = len_v3(color); + } + + if (value_slider) { + switch (U.color_picker_type) { + case USER_CP_CIRCLE_HSL: + uiItemS(row); + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + WHEEL_SIZE + 6, + 0, + 14 * UI_DPI_FAC, + WHEEL_SIZE, + ptr, + prop, + -1, + softmin, + softmax, + 0, + 0, + ""); + hsv_but->gradient_type = UI_GRAD_L_ALT; + break; + case USER_CP_SQUARE_SV: + uiItemS(col); + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + 0, + 4, + WHEEL_SIZE, + 18 * UI_DPI_FAC, + ptr, + prop, + -1, + softmin, + softmax, + 0, + 0, + ""); + hsv_but->gradient_type = UI_GRAD_SV + 3; + break; + case USER_CP_SQUARE_HS: + uiItemS(col); + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + 0, + 4, + WHEEL_SIZE, + 18 * UI_DPI_FAC, + ptr, + prop, + -1, + softmin, + softmax, + 0, + 0, + ""); + hsv_but->gradient_type = UI_GRAD_HS + 3; + break; + case USER_CP_SQUARE_HV: + uiItemS(col); + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + 0, + 4, + WHEEL_SIZE, + 18 * UI_DPI_FAC, + ptr, + prop, + -1, + softmin, + softmax, + 0, + 0, + ""); + hsv_but->gradient_type = UI_GRAD_HV + 3; + break; + + /* user default */ + case USER_CP_CIRCLE_HSV: + default: + uiItemS(row); + hsv_but = (uiButHSVCube *)uiDefButR_prop(block, + UI_BTYPE_HSVCUBE, + 0, + "", + WHEEL_SIZE + 6, + 0, + 14 * UI_DPI_FAC, + WHEEL_SIZE, + ptr, + prop, + -1, + softmin, + softmax, + 0, + 0, + ""); + hsv_but->gradient_type = UI_GRAD_V_ALT; + break; + } + + hsv_but->but.custom_data = cpicker; + } +} + +static void ui_template_palette_menu(bContext *UNUSED(C), uiLayout *layout, void *UNUSED(but_p)) +{ + uiLayout *row; + + uiItemL(layout, IFACE_("Sort By:"), ICON_NONE); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Hue"), ICON_NONE, "PALETTE_OT_sort", "type", 1); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Saturation"), ICON_NONE, "PALETTE_OT_sort", "type", 2); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Value"), ICON_NONE, "PALETTE_OT_sort", "type", 3); + row = uiLayoutRow(layout, false); + uiItemEnumO_value(row, IFACE_("Luminance"), ICON_NONE, "PALETTE_OT_sort", "type", 4); +} + +void uiTemplatePalette(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + bool UNUSED(colors)) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + uiBut *but = NULL; + + const int cols_per_row = MAX2(uiLayoutGetWidth(layout) / UI_UNIT_X, 1); + + if (!prop) { + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + const PointerRNA cptr = RNA_property_pointer_get(ptr, prop); + if (!cptr.data || !RNA_struct_is_a(cptr.type, &RNA_Palette)) { + return; + } + + uiBlock *block = uiLayoutGetBlock(layout); + + Palette *palette = cptr.data; + + uiLayout *col = uiLayoutColumn(layout, true); + uiLayoutRow(col, true); + uiDefIconButO(block, + UI_BTYPE_BUT, + "PALETTE_OT_color_add", + WM_OP_INVOKE_DEFAULT, + ICON_ADD, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + uiDefIconButO(block, + UI_BTYPE_BUT, + "PALETTE_OT_color_delete", + WM_OP_INVOKE_DEFAULT, + ICON_REMOVE, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + if (palette->colors.first != NULL) { + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "PALETTE_OT_color_move", + WM_OP_INVOKE_DEFAULT, + ICON_TRIA_UP, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + UI_but_operator_ptr_get(but); + RNA_enum_set(but->opptr, "type", -1); + + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "PALETTE_OT_color_move", + WM_OP_INVOKE_DEFAULT, + ICON_TRIA_DOWN, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + UI_but_operator_ptr_get(but); + RNA_enum_set(but->opptr, "type", 1); + + /* Menu. */ + uiDefIconMenuBut( + block, ui_template_palette_menu, NULL, ICON_SORTSIZE, 0, 0, UI_UNIT_X, UI_UNIT_Y, ""); + } + + col = uiLayoutColumn(layout, true); + uiLayoutRow(col, true); + + int row_cols = 0, col_id = 0; + LISTBASE_FOREACH (PaletteColor *, color, &palette->colors) { + if (row_cols >= cols_per_row) { + uiLayoutRow(col, true); + row_cols = 0; + } + + PointerRNA color_ptr; + RNA_pointer_create(&palette->id, &RNA_PaletteColor, color, &color_ptr); + uiButColor *color_but = (uiButColor *)uiDefButR(block, + UI_BTYPE_COLOR, + 0, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + &color_ptr, + "color", + -1, + 0.0, + 1.0, + 0.0, + 0.0, + ""); + color_but->is_pallete_color = true; + color_but->palette_color_index = col_id; + row_cols++; + col_id++; + } +} + +void uiTemplateCryptoPicker(uiLayout *layout, PointerRNA *ptr, const char *propname, int icon) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop) { + RNA_warning("property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + uiBlock *block = uiLayoutGetBlock(layout); + + uiBut *but = uiDefIconTextButO(block, + UI_BTYPE_BUT, + "UI_OT_eyedropper_color", + WM_OP_INVOKE_DEFAULT, + icon, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + RNA_property_ui_description(prop)); + but->rnapoin = *ptr; + but->rnaprop = prop; + but->rnaindex = -1; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Layer Buttons Template + * \{ */ + +static void handle_layer_buttons(bContext *C, void *arg1, void *arg2) +{ + uiBut *but = arg1; + const int cur = POINTER_AS_INT(arg2); + wmWindow *win = CTX_wm_window(C); + const int shift = win->eventstate->shift; + + if (!shift) { + const int tot = RNA_property_array_length(&but->rnapoin, but->rnaprop); + + /* Normally clicking only selects one layer */ + RNA_property_boolean_set_index(&but->rnapoin, but->rnaprop, cur, true); + for (int i = 0; i < tot; i++) { + if (i != cur) { + RNA_property_boolean_set_index(&but->rnapoin, but->rnaprop, i, false); + } + } + } + + /* view3d layer change should update depsgraph (invisible object changed maybe) */ + /* see view3d_header.c */ +} + +/** + * \todo for now, grouping of layers is determined by dividing up the length of + * the array of layer bitflags + */ +void uiTemplateLayers(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + PointerRNA *used_ptr, + const char *used_propname, + int active_layer) +{ + const int cols_per_group = 5; + + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + if (!prop) { + RNA_warning("layers property not found: %s.%s", RNA_struct_identifier(ptr->type), propname); + return; + } + + /* the number of layers determines the way we group them + * - we want 2 rows only (for now) + * - The number of columns (cols) is the total number of buttons per row the 'remainder' + * is added to this, as it will be ok to have first row slightly wider if need be. + * - For now, only split into groups if group will have at least 5 items. + */ + const int layers = RNA_property_array_length(ptr, prop); + const int cols = (layers / 2) + (layers % 2); + const int groups = ((cols / 2) < cols_per_group) ? (1) : (cols / cols_per_group); + + PropertyRNA *used_prop = NULL; + if (used_ptr && used_propname) { + used_prop = RNA_struct_find_property(used_ptr, used_propname); + if (!used_prop) { + RNA_warning("used layers property not found: %s.%s", + RNA_struct_identifier(ptr->type), + used_propname); + return; + } + + if (RNA_property_array_length(used_ptr, used_prop) < layers) { + used_prop = NULL; + } + } + + /* layers are laid out going across rows, with the columns being divided into groups */ + + for (int group = 0; group < groups; group++) { + uiLayout *uCol = uiLayoutColumn(layout, true); + + for (int row = 0; row < 2; row++) { + uiLayout *uRow = uiLayoutRow(uCol, true); + uiBlock *block = uiLayoutGetBlock(uRow); + int layer = groups * cols_per_group * row + cols_per_group * group; + + /* add layers as toggle buts */ + for (int col = 0; (col < cols_per_group) && (layer < layers); col++, layer++) { + int icon = 0; + const int butlay = 1 << layer; + + if (active_layer & butlay) { + icon = ICON_LAYER_ACTIVE; + } + else if (used_prop && RNA_property_boolean_get_index(used_ptr, used_prop, layer)) { + icon = ICON_LAYER_USED; + } + + uiBut *but = uiDefAutoButR( + block, ptr, prop, layer, "", icon, 0, 0, UI_UNIT_X / 2, UI_UNIT_Y / 2); + UI_but_func_set(but, handle_layer_buttons, but, POINTER_FROM_INT(layer)); + but->type = UI_BTYPE_TOGGLE; + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name List Template + * \{ */ + +static void uilist_draw_item_default(struct uiList *ui_list, + struct bContext *UNUSED(C), + struct uiLayout *layout, + struct PointerRNA *UNUSED(dataptr), + struct PointerRNA *itemptr, + int icon, + struct PointerRNA *UNUSED(active_dataptr), + const char *UNUSED(active_propname), + int UNUSED(index), + int UNUSED(flt_flag)) +{ + PropertyRNA *nameprop = RNA_struct_name_property(itemptr->type); + + /* Simplest one! */ + switch (ui_list->layout_type) { + case UILST_LAYOUT_GRID: + uiItemL(layout, "", icon); + break; + case UILST_LAYOUT_DEFAULT: + case UILST_LAYOUT_COMPACT: + default: + if (nameprop) { + uiItemFullR(layout, itemptr, nameprop, RNA_NO_INDEX, 0, UI_ITEM_R_NO_BG, "", icon); + } + else { + uiItemL(layout, "", icon); + } + break; + } +} + +static void uilist_draw_filter_default(struct uiList *ui_list, + struct bContext *UNUSED(C), + struct uiLayout *layout) +{ + PointerRNA listptr; + RNA_pointer_create(NULL, &RNA_UIList, ui_list, &listptr); + + uiLayout *row = uiLayoutRow(layout, false); + + uiLayout *subrow = uiLayoutRow(row, true); + uiItemR(subrow, &listptr, "filter_name", 0, "", ICON_NONE); + uiItemR(subrow, + &listptr, + "use_filter_invert", + UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, + "", + ICON_ARROW_LEFTRIGHT); + + if ((ui_list->filter_sort_flag & UILST_FLT_SORT_LOCK) == 0) { + subrow = uiLayoutRow(row, true); + uiItemR(subrow, + &listptr, + "use_filter_sort_alpha", + UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, + "", + ICON_NONE); + uiItemR(subrow, + &listptr, + "use_filter_sort_reverse", + UI_ITEM_R_TOGGLE | UI_ITEM_R_ICON_ONLY, + "", + (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) ? ICON_SORT_DESC : ICON_SORT_ASC); + } +} + +typedef struct { + char name[MAX_IDPROP_NAME]; + int org_idx; +} StringCmp; + +static int cmpstringp(const void *p1, const void *p2) +{ + /* Case-insensitive comparison. */ + return BLI_strcasecmp(((StringCmp *)p1)->name, ((StringCmp *)p2)->name); +} + +static void uilist_filter_items_default(struct uiList *ui_list, + struct bContext *UNUSED(C), + struct PointerRNA *dataptr, + const char *propname) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + PropertyRNA *prop = RNA_struct_find_property(dataptr, propname); + + const char *filter_raw = ui_list->filter_byname; + char *filter = (char *)filter_raw, filter_buff[32], *filter_dyn = NULL; + const bool filter_exclude = (ui_list->filter_flag & UILST_FLT_EXCLUDE) != 0; + const bool order_by_name = (ui_list->filter_sort_flag & UILST_FLT_SORT_MASK) == + UILST_FLT_SORT_ALPHA; + const int len = RNA_property_collection_length(dataptr, prop); + + dyn_data->items_shown = dyn_data->items_len = len; + + if (len && (order_by_name || filter_raw[0])) { + StringCmp *names = NULL; + int order_idx = 0, i = 0; + + if (order_by_name) { + names = MEM_callocN(sizeof(StringCmp) * len, "StringCmp"); + } + if (filter_raw[0]) { + const size_t slen = strlen(filter_raw); + + dyn_data->items_filter_flags = MEM_callocN(sizeof(int) * len, "items_filter_flags"); + dyn_data->items_shown = 0; + + /* Implicitly add heading/trailing wildcards if needed. */ + if (slen + 3 <= sizeof(filter_buff)) { + filter = filter_buff; + } + else { + filter = filter_dyn = MEM_mallocN((slen + 3) * sizeof(char), "filter_dyn"); + } + BLI_strncpy_ensure_pad(filter, filter_raw, '*', slen + 3); + } + + RNA_PROP_BEGIN (dataptr, itemptr, prop) { + bool do_order = false; + + char *namebuf = RNA_struct_name_get_alloc(&itemptr, NULL, 0, NULL); + const char *name = namebuf ? namebuf : ""; + + if (filter[0]) { + /* Case-insensitive! */ + if (fnmatch(filter, name, FNM_CASEFOLD) == 0) { + dyn_data->items_filter_flags[i] = UILST_FLT_ITEM; + if (!filter_exclude) { + dyn_data->items_shown++; + do_order = order_by_name; + } + // printf("%s: '%s' matches '%s'\n", __func__, name, filter); + } + else if (filter_exclude) { + dyn_data->items_shown++; + do_order = order_by_name; + } + } + else { + do_order = order_by_name; + } + + if (do_order) { + names[order_idx].org_idx = order_idx; + BLI_strncpy(names[order_idx++].name, name, MAX_IDPROP_NAME); + } + + /* free name */ + if (namebuf) { + MEM_freeN(namebuf); + } + i++; + } + RNA_PROP_END; + + if (order_by_name) { + int new_idx; + /* note: order_idx equals either to ui_list->items_len if no filtering done, + * or to ui_list->items_shown if filter is enabled, + * or to (ui_list->items_len - ui_list->items_shown) if filtered items are excluded. + * This way, we only sort items we actually intend to draw! + */ + qsort(names, order_idx, sizeof(StringCmp), cmpstringp); + + dyn_data->items_filter_neworder = MEM_mallocN(sizeof(int) * order_idx, + "items_filter_neworder"); + for (new_idx = 0; new_idx < order_idx; new_idx++) { + dyn_data->items_filter_neworder[names[new_idx].org_idx] = new_idx; + } + } + + if (filter_dyn) { + MEM_freeN(filter_dyn); + } + if (names) { + MEM_freeN(names); + } + } +} + +typedef struct { + PointerRNA item; + int org_idx; + int flt_flag; +} _uilist_item; + +typedef struct { + int visual_items; /* Visual number of items (i.e. number of items we have room to display). */ + int start_idx; /* Index of first item to display. */ + int end_idx; /* Index of last item to display + 1. */ +} uiListLayoutdata; + +static void uilist_prepare(uiList *ui_list, + int len, + int activei, + int rows, + int maxrows, + int columns, + uiListLayoutdata *layoutdata) +{ + uiListDyn *dyn_data = ui_list->dyn_data; + const bool use_auto_size = (ui_list->list_grip < (rows - UI_LIST_AUTO_SIZE_THRESHOLD)); + + /* default rows */ + if (rows <= 0) { + rows = 5; + } + dyn_data->visual_height_min = rows; + if (maxrows < rows) { + maxrows = max_ii(rows, 5); + } + if (columns <= 0) { + columns = 9; + } + + int activei_row; + if (columns > 1) { + dyn_data->height = (int)ceil((double)len / (double)columns); + activei_row = (int)floor((double)activei / (double)columns); + } + else { + dyn_data->height = len; + activei_row = activei; + } + + if (!use_auto_size) { + /* No auto-size, yet we clamp at min size! */ + maxrows = rows = max_ii(ui_list->list_grip, rows); + } + else if ((rows != maxrows) && (dyn_data->height > rows)) { + /* Expand size if needed and possible. */ + rows = min_ii(dyn_data->height, maxrows); + } + + /* If list length changes or list is tagged to check this, + * and active is out of view, scroll to it .*/ + if (ui_list->list_last_len != len || ui_list->flag & UILST_SCROLL_TO_ACTIVE_ITEM) { + if (activei_row < ui_list->list_scroll) { + ui_list->list_scroll = activei_row; + } + else if (activei_row >= ui_list->list_scroll + rows) { + ui_list->list_scroll = activei_row - rows + 1; + } + ui_list->flag &= ~UILST_SCROLL_TO_ACTIVE_ITEM; + } + + const int max_scroll = max_ii(0, dyn_data->height - rows); + CLAMP(ui_list->list_scroll, 0, max_scroll); + ui_list->list_last_len = len; + dyn_data->visual_height = rows; + layoutdata->visual_items = rows * columns; + layoutdata->start_idx = ui_list->list_scroll * columns; + layoutdata->end_idx = min_ii(layoutdata->start_idx + rows * columns, len); +} + +static void uilist_resize_update_cb(bContext *C, void *arg1, void *UNUSED(arg2)) +{ + uiList *ui_list = arg1; + uiListDyn *dyn_data = ui_list->dyn_data; + + /* This way we get diff in number of additional items to show (positive) or hide (negative). */ + const int diff = round_fl_to_int((float)(dyn_data->resize - dyn_data->resize_prev) / + (float)UI_UNIT_Y); + + if (diff != 0) { + ui_list->list_grip += diff; + dyn_data->resize_prev += diff * UI_UNIT_Y; + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + } + + /* In case uilist is in popup, we need special refreshing */ + ED_region_tag_refresh_ui(CTX_wm_menu(C)); +} + +static void *uilist_item_use_dynamic_tooltip(PointerRNA *itemptr, const char *propname) +{ + if (propname && propname[0] && itemptr && itemptr->data) { + PropertyRNA *prop = RNA_struct_find_property(itemptr, propname); + + if (prop && (RNA_property_type(prop) == PROP_STRING)) { + return RNA_property_string_get_alloc(itemptr, prop, NULL, 0, NULL); + } + } + return NULL; +} + +static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const char *tip) +{ + char *dyn_tooltip = argN; + return BLI_sprintfN("%s - %s", tip, dyn_tooltip); +} + +void uiTemplateList(uiLayout *layout, + bContext *C, + const char *listtype_name, + const char *list_id, + PointerRNA *dataptr, + const char *propname, + PointerRNA *active_dataptr, + const char *active_propname, + const char *item_dyntip_propname, + int rows, + int maxrows, + int layout_type, + int columns, + bool sort_reverse, + bool sort_lock) +{ + PropertyRNA *prop = NULL, *activeprop; + _uilist_item *items_ptr = NULL; + uiLayout *glob = NULL, *box, *row, *col, *subrow, *sub, *overlap; + uiBut *but; + + uiListLayoutdata layoutdata; + char ui_list_id[UI_MAX_NAME_STR]; + char numstr[32]; + int rnaicon = ICON_NONE, icon = ICON_NONE; + int i = 0, activei = 0; + int len = 0; + + /* validate arguments */ + /* Forbid default UI_UL_DEFAULT_CLASS_NAME list class without a custom list_id! */ + if (STREQ(UI_UL_DEFAULT_CLASS_NAME, listtype_name) && !(list_id && list_id[0])) { + RNA_warning("template_list using default '%s' UIList class must provide a custom list_id", + UI_UL_DEFAULT_CLASS_NAME); + return; + } + + uiBlock *block = uiLayoutGetBlock(layout); + + if (!active_dataptr->data) { + RNA_warning("No active data"); + return; + } + + if (dataptr->data) { + prop = RNA_struct_find_property(dataptr, propname); + if (!prop) { + RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname); + return; + } + } + + activeprop = RNA_struct_find_property(active_dataptr, active_propname); + if (!activeprop) { + RNA_warning( + "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname); + return; + } + + if (prop) { + const PropertyType type = RNA_property_type(prop); + if (type != PROP_COLLECTION) { + RNA_warning("Expected a collection data property"); + return; + } + } + + const PropertyType activetype = RNA_property_type(activeprop); + if (activetype != PROP_INT) { + RNA_warning("Expected an integer active data property"); + return; + } + + /* get icon */ + if (dataptr->data && prop) { + StructRNA *ptype = RNA_property_pointer_type(dataptr, prop); + rnaicon = RNA_struct_ui_icon(ptype); + } + + /* get active data */ + activei = RNA_property_int_get(active_dataptr, activeprop); + + /* Find the uiList type. */ + uiListType *ui_list_type = WM_uilisttype_find(listtype_name, false); + + if (ui_list_type == NULL) { + RNA_warning("List type %s not found", listtype_name); + return; + } + + uiListDrawItemFunc draw_item = ui_list_type->draw_item ? ui_list_type->draw_item : + uilist_draw_item_default; + uiListDrawFilterFunc draw_filter = ui_list_type->draw_filter ? ui_list_type->draw_filter : + uilist_draw_filter_default; + uiListFilterItemsFunc filter_items = ui_list_type->filter_items ? ui_list_type->filter_items : + uilist_filter_items_default; + + /* Find or add the uiList to the current Region. */ + /* We tag the list id with the list type... */ + BLI_snprintf( + ui_list_id, sizeof(ui_list_id), "%s_%s", ui_list_type->idname, list_id ? list_id : ""); + + /* Allows to work in popups. */ + ARegion *region = CTX_wm_menu(C); + if (region == NULL) { + region = CTX_wm_region(C); + } + uiList *ui_list = BLI_findstring(®ion->ui_lists, ui_list_id, offsetof(uiList, list_id)); + + if (!ui_list) { + ui_list = MEM_callocN(sizeof(uiList), "uiList"); + BLI_strncpy(ui_list->list_id, ui_list_id, sizeof(ui_list->list_id)); + BLI_addtail(®ion->ui_lists, ui_list); + ui_list->list_grip = -UI_LIST_AUTO_SIZE_THRESHOLD; /* Force auto size by default. */ + if (sort_reverse) { + ui_list->filter_sort_flag |= UILST_FLT_SORT_REVERSE; + } + if (sort_lock) { + ui_list->filter_sort_flag |= UILST_FLT_SORT_LOCK; + } + } + + if (!ui_list->dyn_data) { + ui_list->dyn_data = MEM_callocN(sizeof(uiListDyn), "uiList.dyn_data"); + } + uiListDyn *dyn_data = ui_list->dyn_data; + + /* Because we can't actually pass type across save&load... */ + ui_list->type = ui_list_type; + ui_list->layout_type = layout_type; + + /* Reset filtering data. */ + MEM_SAFE_FREE(dyn_data->items_filter_flags); + MEM_SAFE_FREE(dyn_data->items_filter_neworder); + dyn_data->items_len = dyn_data->items_shown = -1; + + /* When active item changed since last draw, scroll to it. */ + if (activei != ui_list->list_last_activei) { + ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM; + ui_list->list_last_activei = activei; + } + + /* Filter list items! (not for compact layout, though) */ + if (dataptr->data && prop) { + const int filter_exclude = ui_list->filter_flag & UILST_FLT_EXCLUDE; + const bool order_reverse = (ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0; + int items_shown, idx = 0; +#if 0 + int prev_ii = -1, prev_i; +#endif + + if (layout_type == UILST_LAYOUT_COMPACT) { + dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length(dataptr, prop); + } + else { + // printf("%s: filtering...\n", __func__); + filter_items(ui_list, C, dataptr, propname); + // printf("%s: filtering done.\n", __func__); + } + + items_shown = dyn_data->items_shown; + if (items_shown >= 0) { + bool activei_mapping_pending = true; + items_ptr = MEM_mallocN(sizeof(_uilist_item) * items_shown, __func__); + // printf("%s: items shown: %d.\n", __func__, items_shown); + RNA_PROP_BEGIN (dataptr, itemptr, prop) { + if (!dyn_data->items_filter_flags || + ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) { + int ii; + if (dyn_data->items_filter_neworder) { + ii = dyn_data->items_filter_neworder[idx++]; + ii = order_reverse ? items_shown - ii - 1 : ii; + } + else { + ii = order_reverse ? items_shown - ++idx : idx++; + } + // printf("%s: ii: %d\n", __func__, ii); + items_ptr[ii].item = itemptr; + items_ptr[ii].org_idx = i; + items_ptr[ii].flt_flag = dyn_data->items_filter_flags ? dyn_data->items_filter_flags[i] : + 0; + + if (activei_mapping_pending && activei == i) { + activei = ii; + /* So that we do not map again activei! */ + activei_mapping_pending = false; + } +#if 0 /* For now, do not alter active element, even if it will be hidden... */ + else if (activei < i) { + /* We do not want an active but invisible item! + * Only exception is when all items are filtered out... + */ + if (prev_ii >= 0) { + activei = prev_ii; + RNA_property_int_set(active_dataptr, activeprop, prev_i); + } + else { + activei = ii; + RNA_property_int_set(active_dataptr, activeprop, i); + } + } + prev_i = i; + prev_ii = ii; +#endif + } + i++; + } + RNA_PROP_END; + + if (activei_mapping_pending) { + /* No active item found, set to 'invalid' -1 value... */ + activei = -1; + } + } + if (dyn_data->items_shown >= 0) { + len = dyn_data->items_shown; + } + else { + len = dyn_data->items_len; + } + } + + switch (layout_type) { + case UILST_LAYOUT_DEFAULT: + /* layout */ + box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop); + glob = uiLayoutColumn(box, true); + row = uiLayoutRow(glob, false); + col = uiLayoutColumn(row, true); + + /* init numbers */ + uilist_prepare(ui_list, len, activei, rows, maxrows, 1, &layoutdata); + + if (dataptr->data && prop) { + /* create list items */ + for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) { + PointerRNA *itemptr = &items_ptr[i].item; + void *dyntip_data; + const int org_i = items_ptr[i].org_idx; + const int flt_flag = items_ptr[i].flt_flag; + uiBlock *subblock = uiLayoutGetBlock(col); + + overlap = uiLayoutOverlap(col); + + UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); + + /* list item behind label & other buttons */ + sub = uiLayoutRow(overlap, false); + + but = uiDefButR_prop(subblock, + UI_BTYPE_LISTROW, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + active_dataptr, + activeprop, + 0, + 0, + org_i, + 0, + 0, + TIP_("Double click to rename")); + if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr, item_dyntip_propname))) { + UI_but_func_tooltip_set(but, uilist_item_tooltip_func, dyntip_data); + } + + sub = uiLayoutRow(overlap, false); + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + if (icon == ICON_DOT) { + icon = ICON_NONE; + } + draw_item(ui_list, + C, + sub, + dataptr, + itemptr, + icon, + active_dataptr, + active_propname, + org_i, + flt_flag); + + /* Items should be able to set context pointers for the layout. But the list-row button + * swallows events, so it needs the context storage too for handlers to see it. */ + but->context = uiLayoutGetContextStore(sub); + + /* If we are "drawing" active item, set all labels as active. */ + if (i == activei) { + ui_layout_list_set_labels_active(sub); + } + + UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); + } + } + + /* add dummy buttons to fill space */ + for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) { + uiItemL(col, "", ICON_NONE); + } + + /* add scrollbar */ + if (len > layoutdata.visual_items) { + col = uiLayoutColumn(row, false); + uiDefButI(block, + UI_BTYPE_SCROLL, + 0, + "", + 0, + 0, + V2D_SCROLL_WIDTH, + UI_UNIT_Y * dyn_data->visual_height, + &ui_list->list_scroll, + 0, + dyn_data->height - dyn_data->visual_height, + dyn_data->visual_height, + 0, + ""); + } + break; + case UILST_LAYOUT_COMPACT: + row = uiLayoutRow(layout, true); + + if ((dataptr->data && prop) && (dyn_data->items_shown > 0) && (activei >= 0) && + (activei < dyn_data->items_shown)) { + PointerRNA *itemptr = &items_ptr[activei].item; + const int org_i = items_ptr[activei].org_idx; + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + if (icon == ICON_DOT) { + icon = ICON_NONE; + } + draw_item( + ui_list, C, row, dataptr, itemptr, icon, active_dataptr, active_propname, org_i, 0); + } + /* if list is empty, add in dummy button */ + else { + uiItemL(row, "", ICON_NONE); + } + + /* next/prev button */ + BLI_snprintf(numstr, sizeof(numstr), "%d :", dyn_data->items_shown); + but = uiDefIconTextButR_prop(block, + UI_BTYPE_NUM, + 0, + 0, + numstr, + 0, + 0, + UI_UNIT_X * 5, + UI_UNIT_Y, + active_dataptr, + activeprop, + 0, + 0, + 0, + 0, + 0, + ""); + if (dyn_data->items_shown == 0) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + break; + case UILST_LAYOUT_GRID: + box = uiLayoutListBox(layout, ui_list, active_dataptr, activeprop); + glob = uiLayoutColumn(box, true); + row = uiLayoutRow(glob, false); + col = uiLayoutColumn(row, true); + subrow = NULL; /* Quite gcc warning! */ + + uilist_prepare(ui_list, len, activei, rows, maxrows, columns, &layoutdata); + + if (dataptr->data && prop) { + /* create list items */ + for (i = layoutdata.start_idx; i < layoutdata.end_idx; i++) { + PointerRNA *itemptr = &items_ptr[i].item; + const int org_i = items_ptr[i].org_idx; + const int flt_flag = items_ptr[i].flt_flag; + + /* create button */ + if (!(i % columns)) { + subrow = uiLayoutRow(col, false); + } + + uiBlock *subblock = uiLayoutGetBlock(subrow); + overlap = uiLayoutOverlap(subrow); + + UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM); + + /* list item behind label & other buttons */ + sub = uiLayoutRow(overlap, false); + + but = uiDefButR_prop(subblock, + UI_BTYPE_LISTROW, + 0, + "", + 0, + 0, + UI_UNIT_X * 10, + UI_UNIT_Y, + active_dataptr, + activeprop, + 0, + 0, + org_i, + 0, + 0, + NULL); + UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP); + + sub = uiLayoutRow(overlap, false); + + icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false); + draw_item(ui_list, + C, + sub, + dataptr, + itemptr, + icon, + active_dataptr, + active_propname, + org_i, + flt_flag); + + /* If we are "drawing" active item, set all labels as active. */ + if (i == activei) { + ui_layout_list_set_labels_active(sub); + } + + UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM); + } + } + + /* add dummy buttons to fill space */ + for (; i < layoutdata.start_idx + layoutdata.visual_items; i++) { + if (!(i % columns)) { + subrow = uiLayoutRow(col, false); + } + uiItemL(subrow, "", ICON_NONE); + } + + /* add scrollbar */ + if (len > layoutdata.visual_items) { + /* col = */ uiLayoutColumn(row, false); + uiDefButI(block, + UI_BTYPE_SCROLL, + 0, + "", + 0, + 0, + V2D_SCROLL_WIDTH, + UI_UNIT_Y * dyn_data->visual_height, + &ui_list->list_scroll, + 0, + dyn_data->height - dyn_data->visual_height, + dyn_data->visual_height, + 0, + ""); + } + break; + } + + if (glob) { + /* About #UI_BTYPE_GRIP drag-resize: + * We can't directly use results from a grip button, since we have a + * rather complex behavior here (sizing by discrete steps and, overall, auto-size feature). + * Since we *never* know whether we are grip-resizing or not + * (because there is no callback for when a button enters/leaves its "edit mode"), + * we use the fact that grip-controlled value (dyn_data->resize) is completely handled + * by the grip during the grab resize, so settings its value here has no effect at all. + * + * It is only meaningful when we are not resizing, + * in which case this gives us the correct "init drag" value. + * Note we cannot affect `dyn_data->resize_prev here`, + * since this value is not controlled by the grip! + */ + dyn_data->resize = dyn_data->resize_prev + + (dyn_data->visual_height - ui_list->list_grip) * UI_UNIT_Y; + + row = uiLayoutRow(glob, true); + uiBlock *subblock = uiLayoutGetBlock(row); + UI_block_emboss_set(subblock, UI_EMBOSS_NONE); + + if (ui_list->filter_flag & UILST_FLT_SHOW) { + but = uiDefIconButBitI(subblock, + UI_BTYPE_TOGGLE, + UILST_FLT_SHOW, + 0, + ICON_DISCLOSURE_TRI_DOWN, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y * 0.5f, + &(ui_list->filter_flag), + 0, + 0, + 0, + 0, + TIP_("Hide filtering options")); + UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ + + but = uiDefIconButI(subblock, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10.0f, + UI_UNIT_Y * 0.5f, + &dyn_data->resize, + 0.0, + 0.0, + 0, + 0, + ""); + UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL); + + UI_block_emboss_set(subblock, UI_EMBOSS); + + col = uiLayoutColumn(glob, false); + subblock = uiLayoutGetBlock(col); + uiDefBut(subblock, + UI_BTYPE_SEPR, + 0, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y * 0.05f, + NULL, + 0.0, + 0.0, + 0, + 0, + ""); + + draw_filter(ui_list, C, col); + } + else { + but = uiDefIconButBitI(subblock, + UI_BTYPE_TOGGLE, + UILST_FLT_SHOW, + 0, + ICON_DISCLOSURE_TRI_RIGHT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y * 0.5f, + &(ui_list->filter_flag), + 0, + 0, + 0, + 0, + TIP_("Show filtering options")); + UI_but_flag_disable(but, UI_BUT_UNDO); /* skip undo on screen buttons */ + + but = uiDefIconButI(subblock, + UI_BTYPE_GRIP, + 0, + ICON_GRIP, + 0, + 0, + UI_UNIT_X * 10.0f, + UI_UNIT_Y * 0.5f, + &dyn_data->resize, + 0.0, + 0.0, + 0, + 0, + ""); + UI_but_func_set(but, uilist_resize_update_cb, ui_list, NULL); + + UI_block_emboss_set(subblock, UI_EMBOSS); + } + } + + if (items_ptr) { + MEM_freeN(items_ptr); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Running Jobs Template + * \{ */ + +#define B_STOPRENDER 1 +#define B_STOPCAST 2 +#define B_STOPANIM 3 +#define B_STOPCOMPO 4 +#define B_STOPSEQ 5 +#define B_STOPCLIP 6 +#define B_STOPFILE 7 +#define B_STOPOTHER 8 + +static void do_running_jobs(bContext *C, void *UNUSED(arg), int event) +{ + switch (event) { + case B_STOPRENDER: + G.is_break = true; + break; + case B_STOPCAST: + WM_jobs_stop(CTX_wm_manager(C), CTX_wm_screen(C), NULL); + break; + case B_STOPANIM: + WM_operator_name_call(C, "SCREEN_OT_animation_play", WM_OP_INVOKE_SCREEN, NULL); + break; + case B_STOPCOMPO: + WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); + break; + case B_STOPSEQ: + WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); + break; + case B_STOPCLIP: + WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); + break; + case B_STOPFILE: + WM_jobs_stop(CTX_wm_manager(C), CTX_data_scene(C), NULL); + break; + case B_STOPOTHER: + G.is_break = true; + break; + } +} + +struct ProgressTooltip_Store { + wmWindowManager *wm; + void *owner; +}; + +static char *progress_tooltip_func(bContext *UNUSED(C), void *argN, const char *UNUSED(tip)) +{ + struct ProgressTooltip_Store *arg = argN; + wmWindowManager *wm = arg->wm; + void *owner = arg->owner; + + const float progress = WM_jobs_progress(wm, owner); + + /* create tooltip text and associate it with the job */ + char elapsed_str[32]; + char remaining_str[32] = "Unknown"; + const double elapsed = PIL_check_seconds_timer() - WM_jobs_starttime(wm, owner); + BLI_timecode_string_from_time_simple(elapsed_str, sizeof(elapsed_str), elapsed); + + if (progress) { + const double remaining = (elapsed / (double)progress) - elapsed; + BLI_timecode_string_from_time_simple(remaining_str, sizeof(remaining_str), remaining); + } + + return BLI_sprintfN( + "Time Remaining: %s\n" + "Time Elapsed: %s", + remaining_str, + elapsed_str); +} + +void uiTemplateRunningJobs(uiLayout *layout, bContext *C) +{ + Main *bmain = CTX_data_main(C); + wmWindowManager *wm = CTX_wm_manager(C); + ScrArea *area = CTX_wm_area(C); + void *owner = NULL; + int handle_event, icon = 0; + + uiBlock *block = uiLayoutGetBlock(layout); + UI_block_layout_set_current(block, layout); + + UI_block_func_handle_set(block, do_running_jobs, NULL); + + /* another scene can be rendering too, for example via compositor */ + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY)) { + handle_event = B_STOPOTHER; + icon = ICON_NONE; + owner = scene; + } + else { + continue; + } + + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_BUILD_PROXY)) { + handle_event = B_STOPSEQ; + icon = ICON_SEQUENCE; + owner = scene; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_BUILD_PREVIEW)) { + handle_event = B_STOPSEQ; + icon = ICON_SEQUENCE; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_BUILD_PROXY)) { + handle_event = B_STOPCLIP; + icon = ICON_TRACKER; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_PREFETCH)) { + handle_event = B_STOPCLIP; + icon = ICON_TRACKER; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_TRACK_MARKERS)) { + handle_event = B_STOPCLIP; + icon = ICON_TRACKER; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_CLIP_SOLVE_CAMERA)) { + handle_event = B_STOPCLIP; + icon = ICON_TRACKER; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_FILESEL_READDIR)) { + handle_event = B_STOPFILE; + icon = ICON_FILEBROWSER; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_RENDER)) { + handle_event = B_STOPRENDER; + icon = ICON_SCENE; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_COMPOSITE)) { + handle_event = B_STOPCOMPO; + icon = ICON_RENDERLAYERS; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_BAKE_TEXTURE) || + WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_BAKE)) { + /* Skip bake jobs in compositor to avoid compo header displaying + * progress bar which is not being updated (bake jobs only need + * to update NC_IMAGE context. + */ + if (area->spacetype != SPACE_NODE) { + handle_event = B_STOPOTHER; + icon = ICON_IMAGE; + break; + } + continue; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_DPAINT_BAKE)) { + handle_event = B_STOPOTHER; + icon = ICON_MOD_DYNAMICPAINT; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_POINTCACHE)) { + handle_event = B_STOPOTHER; + icon = ICON_PHYSICS; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_SIM_FLUID)) { + handle_event = B_STOPOTHER; + icon = ICON_MOD_FLUIDSIM; + break; + } + if (WM_jobs_test(wm, scene, WM_JOB_TYPE_OBJECT_SIM_OCEAN)) { + handle_event = B_STOPOTHER; + icon = ICON_MOD_OCEAN; + break; + } + } + + if (owner) { + const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + const bool active = !(G.is_break || WM_jobs_is_stopped(wm, owner)); + + uiLayout *row = uiLayoutRow(layout, false); + block = uiLayoutGetBlock(row); + + /* get percentage done and set it as the UI text */ + const float progress = WM_jobs_progress(wm, owner); + char text[8]; + BLI_snprintf(text, 8, "%d%%", (int)(progress * 100)); + + const char *name = active ? WM_jobs_name(wm, owner) : "Canceling..."; + + /* job name and icon */ + const int textwidth = UI_fontstyle_string_width(fstyle, name); + uiDefIconTextBut(block, + UI_BTYPE_LABEL, + 0, + icon, + name, + 0, + 0, + textwidth + UI_UNIT_X * 1.5f, + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + ""); + + /* stick progress bar and cancel button together */ + row = uiLayoutRow(layout, true); + uiLayoutSetActive(row, active); + block = uiLayoutGetBlock(row); + + { + struct ProgressTooltip_Store *tip_arg = MEM_mallocN(sizeof(*tip_arg), __func__); + tip_arg->wm = wm; + tip_arg->owner = owner; + uiButProgressbar *but_progress = (uiButProgressbar *)uiDefIconTextBut(block, + UI_BTYPE_PROGRESS_BAR, + 0, + 0, + text, + UI_UNIT_X, + 0, + UI_UNIT_X * 6.0f, + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0.0f, + 0, + NULL); + + but_progress->progress = progress; + UI_but_func_tooltip_set(&but_progress->but, progress_tooltip_func, tip_arg); + } + + if (!wm->is_interface_locked) { + uiDefIconTextBut(block, + UI_BTYPE_BUT, + handle_event, + ICON_PANEL_CLOSE, + "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0, + 0, + TIP_("Stop this job")); + } + } + + if (ED_screen_animation_no_scrub(wm)) { + uiDefIconTextBut(block, + UI_BTYPE_BUT, + B_STOPANIM, + ICON_CANCEL, + IFACE_("Anim Player"), + 0, + 0, + UI_UNIT_X * 5.0f, + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0, + 0, + TIP_("Stop animation playback")); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reports for Last Operator Template + * \{ */ + +void uiTemplateReportsBanner(uiLayout *layout, bContext *C) +{ + ReportList *reports = CTX_wm_reports(C); + Report *report = BKE_reports_last_displayable(reports); + const uiStyle *style = UI_style_get(); + + uiBut *but; + + /* if the report display has timed out, don't show */ + if (!reports->reporttimer) { + return; + } + + ReportTimerInfo *rti = (ReportTimerInfo *)reports->reporttimer->customdata; + + if (!rti || rti->widthfac == 0.0f || !report) { + return; + } + + uiLayout *ui_abs = uiLayoutAbsolute(layout, false); + uiBlock *block = uiLayoutGetBlock(ui_abs); + + UI_fontstyle_set(&style->widgetlabel); + int width = BLF_width(style->widgetlabel.uifont_id, report->message, report->len); + width = min_ii((int)(rti->widthfac * width), width); + width = max_ii(width, 10 * UI_DPI_FAC); + + UI_block_align_begin(block); + + /* Background for icon. */ + but = uiDefBut(block, + UI_BTYPE_ROUNDBOX, + 0, + "", + 0, + 0, + UI_UNIT_X + (6 * UI_DPI_FAC), + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0, + 0, + ""); + /* UI_BTYPE_ROUNDBOX's bg color is set in but->col. */ + UI_GetThemeColorType4ubv(UI_icon_colorid_from_report_type(report->type), SPACE_INFO, but->col); + + /* Background for the rest of the message. */ + but = uiDefBut(block, + UI_BTYPE_ROUNDBOX, + 0, + "", + UI_UNIT_X + (6 * UI_DPI_FAC), + 0, + UI_UNIT_X + width, + UI_UNIT_Y, + NULL, + 0.0f, + 0.0f, + 0, + 0, + ""); + + /* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */ + UI_GetThemeColorType4ubv(UI_icon_colorid_from_report_type(report->type), SPACE_INFO, but->col); + but->col[3] = 64; + + UI_block_align_end(block); + UI_block_emboss_set(block, UI_EMBOSS_NONE); + + /* The report icon itself. */ + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "SCREEN_OT_info_log_show", + WM_OP_INVOKE_REGION_WIN, + UI_icon_from_report_type(report->type), + (3 * UI_DPI_FAC), + 0, + UI_UNIT_X, + UI_UNIT_Y, + TIP_("Click to see the remaining reports in text block: 'Recent Reports'")); + UI_GetThemeColorType4ubv(UI_text_colorid_from_report_type(report->type), SPACE_INFO, but->col); + but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */ + + /* The report message. */ + but = uiDefButO(block, + UI_BTYPE_BUT, + "SCREEN_OT_info_log_show", + WM_OP_INVOKE_REGION_WIN, + report->message, + UI_UNIT_X, + 0, + width + UI_UNIT_X, + UI_UNIT_Y, + "Show in Info Log"); +} + +void uiTemplateInputStatus(uiLayout *layout, struct bContext *C) +{ + wmWindow *win = CTX_wm_window(C); + WorkSpace *workspace = CTX_wm_workspace(C); + + /* Workspace status text has priority. */ + if (workspace->status_text) { + uiItemL(layout, workspace->status_text, ICON_NONE); + return; + } + + if (WM_window_modal_keymap_status_draw(C, win, layout)) { + return; + } + + /* Otherwise should cursor keymap status. */ + for (int i = 0; i < 3; i++) { + uiLayout *box = uiLayoutRow(layout, false); + uiLayout *col = uiLayoutColumn(box, false); + uiLayout *row = uiLayoutRow(col, true); + uiLayoutSetAlignment(row, UI_LAYOUT_ALIGN_LEFT); + + const char *msg = WM_window_cursor_keymap_status_get(win, i, 0); + const char *msg_drag = WM_window_cursor_keymap_status_get(win, i, 1); + + if (msg || (msg_drag == NULL)) { + uiItemL(row, msg ? msg : "", (ICON_MOUSE_LMB + i)); + } + + if (msg_drag) { + uiItemL(row, msg_drag, (ICON_MOUSE_LMB_DRAG + i)); + } + + /* Use trick with empty string to keep icons in same position. */ + row = uiLayoutRow(col, false); + uiItemL(row, " ", ICON_NONE); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Keymap Template + * \{ */ + +static void keymap_item_modified(bContext *UNUSED(C), void *kmi_p, void *UNUSED(unused)) +{ + wmKeyMapItem *kmi = (wmKeyMapItem *)kmi_p; + WM_keyconfig_update_tag(NULL, kmi); +} + +static void template_keymap_item_properties(uiLayout *layout, const char *title, PointerRNA *ptr) +{ + uiItemS(layout); + + if (title) { + uiItemL(layout, title, ICON_NONE); + } + + uiLayout *flow = uiLayoutColumnFlow(layout, 2, false); + + RNA_STRUCT_BEGIN_SKIP_RNA_TYPE (ptr, prop) { + const bool is_set = RNA_property_is_set(ptr, prop); + uiBut *but; + + /* recurse for nested properties */ + if (RNA_property_type(prop) == PROP_POINTER) { + PointerRNA propptr = RNA_property_pointer_get(ptr, prop); + + if (propptr.data && RNA_struct_is_a(propptr.type, &RNA_OperatorProperties)) { + const char *name = RNA_property_ui_name(prop); + template_keymap_item_properties(layout, name, &propptr); + continue; + } + } + + uiLayout *box = uiLayoutBox(flow); + uiLayoutSetActive(box, is_set); + uiLayout *row = uiLayoutRow(box, false); + + /* property value */ + uiItemFullR(row, ptr, prop, -1, 0, 0, NULL, ICON_NONE); + + if (is_set) { + /* unset operator */ + uiBlock *block = uiLayoutGetBlock(row); + UI_block_emboss_set(block, UI_EMBOSS_NONE); + but = uiDefIconButO(block, + UI_BTYPE_BUT, + "UI_OT_unset_property_button", + WM_OP_EXEC_DEFAULT, + ICON_X, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + but->rnapoin = *ptr; + but->rnaprop = prop; + UI_block_emboss_set(block, UI_EMBOSS); + } + } + RNA_STRUCT_END; +} + +void uiTemplateKeymapItemProperties(uiLayout *layout, PointerRNA *ptr) +{ + PointerRNA propptr = RNA_pointer_get(ptr, "properties"); + + if (propptr.data) { + uiBut *but = uiLayoutGetBlock(layout)->buttons.last; + + WM_operator_properties_sanitize(&propptr, false); + template_keymap_item_properties(layout, NULL, &propptr); + + /* attach callbacks to compensate for missing properties update, + * we don't know which keymap (item) is being modified there */ + for (; but; but = but->next) { + /* operator buttons may store props for use (file selector, T36492) */ + if (but->rnaprop) { + UI_but_func_set(but, keymap_item_modified, ptr->data, NULL); + + /* Otherwise the keymap will be re-generated which we're trying to edit, + * see: T47685 */ + UI_but_flag_enable(but, UI_BUT_UPDATE_DELAY); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Event Icon Template + * \{ */ + +bool uiTemplateEventFromKeymapItem(struct uiLayout *layout, + const char *text, + const struct wmKeyMapItem *kmi, + bool text_fallback) +{ + bool ok = false; + + int icon_mod[4]; +#ifdef WITH_HEADLESS + int icon = 0; +#else + const int icon = UI_icon_from_keymap_item(kmi, icon_mod); +#endif + if (icon != 0) { + for (int j = 0; j < ARRAY_SIZE(icon_mod) && icon_mod[j]; j++) { + uiItemL(layout, "", icon_mod[j]); + } + uiItemL(layout, text, icon); + ok = true; + } + else if (text_fallback) { + const char *event_text = WM_key_event_string(kmi->type, true); + uiItemL(layout, event_text, ICON_NONE); + uiItemL(layout, text, ICON_NONE); + ok = true; + } + return ok; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Color Management Template + * \{ */ + +void uiTemplateColorspaceSettings(uiLayout *layout, PointerRNA *ptr, const char *propname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop) { + printf( + "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname); + return; + } + + PointerRNA colorspace_settings_ptr = RNA_property_pointer_get(ptr, prop); + + uiItemR(layout, &colorspace_settings_ptr, "name", 0, IFACE_("Color Space"), ICON_NONE); +} + +void uiTemplateColormanagedViewSettings(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr, + const char *propname) +{ + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop) { + printf( + "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname); + return; + } + + PointerRNA view_transform_ptr = RNA_property_pointer_get(ptr, prop); + ColorManagedViewSettings *view_settings = view_transform_ptr.data; + + uiLayout *col = uiLayoutColumn(layout, false); + + uiLayout *row = uiLayoutRow(col, false); + uiItemR(row, &view_transform_ptr, "view_transform", 0, IFACE_("View"), ICON_NONE); + + col = uiLayoutColumn(layout, false); + uiItemR(col, &view_transform_ptr, "exposure", 0, NULL, ICON_NONE); + uiItemR(col, &view_transform_ptr, "gamma", 0, NULL, ICON_NONE); + + uiItemR(col, &view_transform_ptr, "look", 0, IFACE_("Look"), ICON_NONE); + + col = uiLayoutColumn(layout, false); + uiItemR(col, &view_transform_ptr, "use_curve_mapping", 0, NULL, ICON_NONE); + if (view_settings->flag & COLORMANAGE_VIEW_USE_CURVES) { + uiTemplateCurveMapping( + col, &view_transform_ptr, "curve_mapping", 'c', true, false, false, false); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Component Menu + * \{ */ + +typedef struct ComponentMenuArgs { + PointerRNA ptr; + char propname[64]; /* XXX arbitrary */ +} ComponentMenuArgs; +/* NOTE: this is a block-menu, needs 0 events, otherwise the menu closes */ +static uiBlock *component_menu(bContext *C, ARegion *region, void *args_v) +{ + ComponentMenuArgs *args = (ComponentMenuArgs *)args_v; + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN); + + uiLayout *layout = uiLayoutColumn(UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_PANEL, + 0, + 0, + UI_UNIT_X * 6, + UI_UNIT_Y, + 0, + UI_style_get()), + 0); + + uiItemR(layout, &args->ptr, args->propname, UI_ITEM_R_EXPAND, "", ICON_NONE); + + UI_block_bounds_set_normal(block, 0.3f * U.widget_unit); + UI_block_direction_set(block, UI_DIR_DOWN); + + return block; +} +void uiTemplateComponentMenu(uiLayout *layout, + PointerRNA *ptr, + const char *propname, + const char *name) +{ + ComponentMenuArgs *args = MEM_callocN(sizeof(ComponentMenuArgs), "component menu template args"); + + args->ptr = *ptr; + BLI_strncpy(args->propname, propname, sizeof(args->propname)); + + uiBlock *block = uiLayoutGetBlock(layout); + UI_block_align_begin(block); + + uiBut *but = uiDefBlockButN( + block, component_menu, args, name, 0, 0, UI_UNIT_X * 6, UI_UNIT_Y, ""); + /* set rna directly, uiDefBlockButN doesn't do this */ + but->rnapoin = *ptr; + but->rnaprop = RNA_struct_find_property(ptr, propname); + but->rnaindex = 0; + + UI_block_align_end(block); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Node Socket Icon Template + * \{ */ + +void uiTemplateNodeSocket(uiLayout *layout, bContext *UNUSED(C), float color[4]) +{ + uiBlock *block = uiLayoutGetBlock(layout); + UI_block_align_begin(block); + + /* XXX using explicit socket colors is not quite ideal. + * Eventually it should be possible to use theme colors for this purpose, + * but this requires a better design for extendable color palettes in user prefs. + */ + uiBut *but = uiDefBut( + block, UI_BTYPE_NODE_SOCKET, 0, "", 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL, 0, 0, 0, 0, ""); + rgba_float_to_uchar(but->col, color); + + UI_block_align_end(block); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Cache File Template + * \{ */ + +void uiTemplateCacheFile(uiLayout *layout, + const bContext *C, + PointerRNA *ptr, + const char *propname) +{ + if (!ptr->data) { + return; + } + + PropertyRNA *prop = RNA_struct_find_property(ptr, propname); + + if (!prop) { + printf( + "%s: property not found: %s.%s\n", __func__, RNA_struct_identifier(ptr->type), propname); + return; + } + + if (RNA_property_type(prop) != PROP_POINTER) { + printf("%s: expected pointer property for %s.%s\n", + __func__, + RNA_struct_identifier(ptr->type), + propname); + return; + } + + PointerRNA fileptr = RNA_property_pointer_get(ptr, prop); + CacheFile *file = fileptr.data; + + uiLayoutSetContextPointer(layout, "edit_cachefile", &fileptr); + + uiTemplateID(layout, + C, + ptr, + propname, + NULL, + "CACHEFILE_OT_open", + NULL, + UI_TEMPLATE_ID_FILTER_ALL, + false, + NULL); + + if (!file) { + return; + } + + SpaceProperties *sbuts = CTX_wm_space_properties(C); + + uiLayout *row, *sub, *subsub; + + uiLayoutSetPropSep(layout, true); + + row = uiLayoutRow(layout, true); + uiItemR(row, &fileptr, "filepath", 0, NULL, ICON_NONE); + sub = uiLayoutRow(row, true); + uiItemO(sub, "", ICON_FILE_REFRESH, "cachefile.reload"); + + row = uiLayoutRow(layout, false); + uiItemR(row, &fileptr, "is_sequence", 0, NULL, ICON_NONE); + + row = uiLayoutRowWithHeading(layout, true, IFACE_("Override Frame")); + sub = uiLayoutRow(row, true); + uiLayoutSetPropDecorate(sub, false); + uiItemR(sub, &fileptr, "override_frame", 0, "", ICON_NONE); + subsub = uiLayoutRow(sub, true); + uiLayoutSetActive(subsub, RNA_boolean_get(&fileptr, "override_frame")); + uiItemR(subsub, &fileptr, "frame", 0, "", ICON_NONE); + uiItemDecoratorR(row, &fileptr, "frame", 0); + + row = uiLayoutRow(layout, false); + uiItemR(row, &fileptr, "frame_offset", 0, NULL, ICON_NONE); + uiLayoutSetActive(row, !RNA_boolean_get(&fileptr, "is_sequence")); + + if (sbuts->mainb == BCONTEXT_CONSTRAINT) { + row = uiLayoutRow(layout, false); + uiItemR(row, &fileptr, "scale", 0, IFACE_("Manual Scale"), ICON_NONE); + } + + uiItemR(layout, &fileptr, "velocity_name", 0, NULL, ICON_NONE); + uiItemR(layout, &fileptr, "velocity_unit", 0, NULL, ICON_NONE); + + /* TODO: unused for now, so no need to expose. */ +#if 0 + row = uiLayoutRow(layout, false); + uiItemR(row, &fileptr, "forward_axis", 0, "Forward Axis", ICON_NONE); + + row = uiLayoutRow(layout, false); + uiItemR(row, &fileptr, "up_axis", 0, "Up Axis", ICON_NONE); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Recent Files Template + * \{ */ + +int uiTemplateRecentFiles(uiLayout *layout, int rows) +{ + int i; + LISTBASE_FOREACH_INDEX (RecentFile *, recent, &G.recent_files, i) { + if (i >= rows) { + break; + } + + const char *filename = BLI_path_basename(recent->filepath); + PointerRNA ptr; + uiItemFullO(layout, + "WM_OT_open_mainfile", + filename, + BLO_has_bfile_extension(filename) ? ICON_FILE_BLEND : ICON_FILE_BACKUP, + NULL, + WM_OP_INVOKE_DEFAULT, + 0, + &ptr); + RNA_string_set(&ptr, "filepath", recent->filepath); + RNA_boolean_set(&ptr, "display_file_selector", false); + } + + return i; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name FileSelectParams Path Button Template + * \{ */ + +void uiTemplateFileSelectPath(uiLayout *layout, bContext *C, FileSelectParams *params) +{ + bScreen *screen = CTX_wm_screen(C); + SpaceFile *sfile = CTX_wm_space_file(C); + + ED_file_path_button(screen, sfile, params, uiLayoutGetBlock(layout)); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_undo.c b/source/blender/editors/interface/interface_undo.c deleted file mode 100644 index 304d2254a81..00000000000 --- a/source/blender/editors/interface/interface_undo.c +++ /dev/null @@ -1,137 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * Copyright (C) 2020 Blender Foundation. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - * - * Undo stack to use for UI widgets that manage their own editing state. - */ - -#include - -#include "BLI_listbase.h" - -#include "DNA_listBase.h" - -#include "MEM_guardedalloc.h" - -#include "interface_intern.h" - -/* -------------------------------------------------------------------- */ -/** \name Text Field Undo Stack - * \{ */ - -typedef struct uiUndoStack_Text_State { - struct uiUndoStack_Text_State *next, *prev; - int cursor_index; - char text[0]; -} uiUndoStack_Text_State; - -typedef struct uiUndoStack_Text { - ListBase states; - uiUndoStack_Text_State *current; -} uiUndoStack_Text; - -static const char *ui_textedit_undo_impl(uiUndoStack_Text *stack, int *r_cursor_index) -{ - /* Don't undo if no data has been pushed yet. */ - if (stack->current == NULL) { - return NULL; - } - - /* Travel backwards in the stack and copy information to the caller. */ - if (stack->current->prev != NULL) { - stack->current = stack->current->prev; - - *r_cursor_index = stack->current->cursor_index; - return stack->current->text; - } - return NULL; -} - -static const char *ui_textedit_redo_impl(uiUndoStack_Text *stack, int *r_cursor_index) -{ - /* Don't redo if no data has been pushed yet. */ - if (stack->current == NULL) { - return NULL; - } - - /* Only redo if new data has not been entered since the last undo. */ - if (stack->current->next) { - stack->current = stack->current->next; - - *r_cursor_index = stack->current->cursor_index; - return stack->current->text; - } - return NULL; -} - -const char *ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index) -{ - BLI_assert(ELEM(direction, -1, 1)); - if (direction < 0) { - return ui_textedit_undo_impl(stack, r_cursor_index); - } - return ui_textedit_redo_impl(stack, r_cursor_index); -} - -/** - * Push the information in the arguments to a new state in the undo stack. - * - * \note Currently the total length of the undo stack is not limited. - */ -void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index) -{ - /* Clear all redo actions from the current state. */ - if (stack->current != NULL) { - while (stack->current->next) { - uiUndoStack_Text_State *state = stack->current->next; - BLI_remlink(&stack->states, state); - MEM_freeN(state); - } - } - - /* Create the new state */ - const int text_size = strlen(text) + 1; - stack->current = MEM_mallocN(sizeof(uiUndoStack_Text_State) + text_size, __func__); - stack->current->cursor_index = cursor_index; - memcpy(stack->current->text, text, text_size); - BLI_addtail(&stack->states, stack->current); -} -/** - * Start the undo stack. - * - * \note The current state should be pushed immediately after calling this. - */ -uiUndoStack_Text *ui_textedit_undo_stack_create(void) -{ - uiUndoStack_Text *stack = MEM_mallocN(sizeof(uiUndoStack_Text), __func__); - stack->current = NULL; - BLI_listbase_clear(&stack->states); - - return stack; -} - -void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack) -{ - BLI_freelistN(&stack->states); - MEM_freeN(stack); -} - -/** \} */ diff --git a/source/blender/editors/interface/interface_undo.cc b/source/blender/editors/interface/interface_undo.cc new file mode 100644 index 00000000000..22cdee0b755 --- /dev/null +++ b/source/blender/editors/interface/interface_undo.cc @@ -0,0 +1,138 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Copyright (C) 2020 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + * + * Undo stack to use for UI widgets that manage their own editing state. + */ + +#include + +#include "BLI_listbase.h" + +#include "DNA_listBase.h" + +#include "MEM_guardedalloc.h" + +#include "interface_intern.h" + +/* -------------------------------------------------------------------- */ +/** \name Text Field Undo Stack + * \{ */ + +typedef struct uiUndoStack_Text_State { + struct uiUndoStack_Text_State *next, *prev; + int cursor_index; + char text[0]; +} uiUndoStack_Text_State; + +typedef struct uiUndoStack_Text { + ListBase states; + uiUndoStack_Text_State *current; +} uiUndoStack_Text; + +static const char *ui_textedit_undo_impl(uiUndoStack_Text *stack, int *r_cursor_index) +{ + /* Don't undo if no data has been pushed yet. */ + if (stack->current == NULL) { + return NULL; + } + + /* Travel backwards in the stack and copy information to the caller. */ + if (stack->current->prev != NULL) { + stack->current = stack->current->prev; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return NULL; +} + +static const char *ui_textedit_redo_impl(uiUndoStack_Text *stack, int *r_cursor_index) +{ + /* Don't redo if no data has been pushed yet. */ + if (stack->current == NULL) { + return NULL; + } + + /* Only redo if new data has not been entered since the last undo. */ + if (stack->current->next) { + stack->current = stack->current->next; + + *r_cursor_index = stack->current->cursor_index; + return stack->current->text; + } + return NULL; +} + +const char *ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index) +{ + BLI_assert(ELEM(direction, -1, 1)); + if (direction < 0) { + return ui_textedit_undo_impl(stack, r_cursor_index); + } + return ui_textedit_redo_impl(stack, r_cursor_index); +} + +/** + * Push the information in the arguments to a new state in the undo stack. + * + * \note Currently the total length of the undo stack is not limited. + */ +void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index) +{ + /* Clear all redo actions from the current state. */ + if (stack->current != NULL) { + while (stack->current->next) { + uiUndoStack_Text_State *state = stack->current->next; + BLI_remlink(&stack->states, state); + MEM_freeN(state); + } + } + + /* Create the new state */ + const int text_size = strlen(text) + 1; + stack->current = (uiUndoStack_Text_State *)MEM_mallocN( + sizeof(uiUndoStack_Text_State) + text_size, __func__); + stack->current->cursor_index = cursor_index; + memcpy(stack->current->text, text, text_size); + BLI_addtail(&stack->states, stack->current); +} +/** + * Start the undo stack. + * + * \note The current state should be pushed immediately after calling this. + */ +uiUndoStack_Text *ui_textedit_undo_stack_create() +{ + uiUndoStack_Text *stack = (uiUndoStack_Text *)MEM_mallocN(sizeof(uiUndoStack_Text), __func__); + stack->current = NULL; + BLI_listbase_clear(&stack->states); + + return stack; +} + +void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack) +{ + BLI_freelistN(&stack->states); + MEM_freeN(stack); +} + +/** \} */ diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c deleted file mode 100644 index 6ad1de68a1f..00000000000 --- a/source/blender/editors/interface/interface_utils.c +++ /dev/null @@ -1,944 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2009 Blender Foundation. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include -#include - -#include "DNA_object_types.h" -#include "DNA_screen_types.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_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 "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); - 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; -} - -/** - * \a check_prop callback filters functions to avoid drawing certain properties, - * in cases where PROP_HIDDEN flag can't be used for a property. - * - * \param prop_activate_init: Property to activate on initial popup (#UI_BUT_ACTIVATE_ON_INIT). - */ -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->lib != NULL); - } - } - 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); - } - 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); -} - -/***************************** ID Utilities *******************************/ -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; -} - -/* see: report_type_str */ -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 **************************************/ - -/** - * Returns the best "UI" precision for given floating value, - * so that e.g. 10.000001 rather gets drawn as '10'... - */ -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 si 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; -} - -/* -------------------------------------------------------------------- */ -/** \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; -}; - -/** - * Create a new button store, the caller must manage and run #UI_butstore_free - */ -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); -} - -/** - * Update the pointer for a registered button. - */ -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; -} - -/** - * NULL all pointers, don't free since the owner needs to be able to inspect. - */ -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; - } - } -} - -/** - * Map freed buttons from the old block and update pointers. - */ -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..b350269af38 --- /dev/null +++ b/source/blender/editors/interface/interface_utils.cc @@ -0,0 +1,945 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2009 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include +#include + +#include "DNA_object_types.h" +#include "DNA_screen_types.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_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 "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); + 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; +} + +/** + * \a check_prop callback filters functions to avoid drawing certain properties, + * in cases where PROP_HIDDEN flag can't be used for a property. + * + * \param prop_activate_init: Property to activate on initial popup (#UI_BUT_ACTIVATE_ON_INIT). + */ +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 = (eAutoPropButsReturn)(int(return_info) | int(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 = (eAutoPropButsReturn)(int(return_info) | int(~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((ID *)cis->data); + /* No need to re-allocate, string should be shorter than before (lib status prefix is + * removed). */ + BKE_id_full_name_ui_prefix_get( + name_buf, (ID *)cis->data, false, UI_SEP_CHAR, &name_prefix_offset); + BLI_assert(strlen(name_buf) <= MEM_allocN_len(cis->name)); + strcpy(cis->name, name_buf); + } + + return UI_search_item_add(items, + cis->name, + cis->data, + cis->iconid, + cis->has_sep_char ? UI_BUT_HAS_SEP_CHAR : 0, + name_prefix_offset); +} + +void ui_rna_collection_search_update_fn(const struct bContext *C, + void *arg, + const char *str, + uiSearchItems *items, + const bool is_first) +{ + uiRNACollectionSearch *data = (uiRNACollectionSearch *)arg; + const int flag = RNA_property_flag(data->target_prop); + ListBase *items_list = (ListBase *)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, (ID *)itemptr.data, false); + if (!ELEM(iconid, 0, ICON_BLANK1)) { + has_id_icon = true; + } + + if (requires_exact_data_name) { + name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), NULL); + } + else { + const ID *id = (const ID *)itemptr.data; + BKE_id_full_name_ui_prefix_get( + name_buf, (const ID *)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->lib != NULL); + } + } + else { + name = RNA_struct_name_get_alloc(&itemptr, name_buf, sizeof(name_buf), NULL); + } + + if (name) { + CollItemSearch *cis = (CollItemSearch *)MEM_callocN(sizeof(CollItemSearch), __func__); + cis->data = itemptr.data; + cis->name = BLI_strdup(name); + cis->index = item_index; + cis->iconid = iconid; + cis->is_id = is_id; + cis->name_prefix_offset = name_prefix_offset; + cis->has_sep_char = has_sep_char; + if (!skip_filter) { + BLI_string_search_add(search, name, cis); + } + 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); +} + +/***************************** ID Utilities *******************************/ +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((const ID *)ob->data); + } + + /* otherwise get it through RNA, creating the pointer + * will set the right type, also with subclassing */ + PointerRNA ptr; + RNA_id_pointer_create((ID *)id, &ptr); + + return (ptr.type) ? RNA_struct_ui_icon(ptr.type) : ICON_NONE; +} + +/* see: report_type_str */ +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 **************************************/ + +/** + * Returns the best "UI" precision for given floating value, + * so that e.g. 10.000001 rather gets drawn as '10'... + */ +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 si 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; +} + +/* -------------------------------------------------------------------- */ +/** \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; +}; + +/** + * Create a new button store, the caller must manage and run #UI_butstore_free + */ +uiButStore *UI_butstore_create(uiBlock *block) +{ + uiButStore *bs_handle = (uiButStore *)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 = (uiButStoreElem *)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); +} + +/** + * Update the pointer for a registered button. + */ +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; +} + +/** + * NULL all pointers, don't free since the owner needs to be able to inspect. + */ +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; + } + } +} + +/** + * Map freed buttons from the old block and update pointers. + */ +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/resources.c b/source/blender/editors/interface/resources.c deleted file mode 100644 index afac254f542..00000000000 --- a/source/blender/editors/interface/resources.c +++ /dev/null @@ -1,1513 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. - * All rights reserved. - */ - -/** \file - * \ingroup edinterface - */ - -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_screen_types.h" -#include "DNA_space_types.h" -#include "DNA_userdef_types.h" - -#include "BLI_blenlib.h" -#include "BLI_math.h" -#include "BLI_utildefines.h" - -#include "BKE_addon.h" -#include "BKE_appdir.h" -#include "BKE_main.h" -#include "BKE_mesh_runtime.h" - -#include "BLO_readfile.h" /* for UserDef version patching. */ - -#include "BLF_api.h" - -#include "ED_screen.h" - -#include "UI_interface.h" -#include "UI_interface_icons.h" - -#include "GPU_framebuffer.h" -#include "interface_intern.h" - -/* global for themes */ -typedef void (*VectorDrawFunc)(int x, int y, int w, int h, float alpha); - -/* be sure to keep 'bThemeState' in sync */ -static struct bThemeState g_theme_state = { - NULL, - SPACE_VIEW3D, - RGN_TYPE_WINDOW, -}; - -#define theme_active g_theme_state.theme -#define theme_spacetype g_theme_state.spacetype -#define theme_regionid g_theme_state.regionid - -void ui_resources_init(void) -{ - UI_icons_init(); -} - -void ui_resources_free(void) -{ - UI_icons_free(); -} - -/* ******************************************************** */ -/* THEMES */ -/* ******************************************************** */ - -const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) -{ - ThemeSpace *ts = NULL; - static uchar error[4] = {240, 0, 240, 255}; - static uchar alert[4] = {240, 60, 60, 255}; - static uchar headerdesel[4] = {0, 0, 0, 255}; - static uchar back[4] = {0, 0, 0, 255}; - static uchar setting = 0; - const uchar *cp = error; - - /* ensure we're not getting a color after running BKE_blender_userdef_free */ - BLI_assert(BLI_findindex(&U.themes, theme_active) != -1); - BLI_assert(colorid != TH_UNDEFINED); - - if (btheme) { - - /* first check for ui buttons theme */ - if (colorid < TH_THEMEUI) { - - switch (colorid) { - - case TH_REDALERT: - cp = alert; - break; - } - } - else { - - switch (spacetype) { - case SPACE_PROPERTIES: - ts = &btheme->space_properties; - break; - case SPACE_VIEW3D: - ts = &btheme->space_view3d; - break; - case SPACE_GRAPH: - ts = &btheme->space_graph; - break; - case SPACE_FILE: - ts = &btheme->space_file; - break; - case SPACE_NLA: - ts = &btheme->space_nla; - break; - case SPACE_ACTION: - ts = &btheme->space_action; - break; - case SPACE_SEQ: - ts = &btheme->space_sequencer; - break; - case SPACE_IMAGE: - ts = &btheme->space_image; - break; - case SPACE_TEXT: - ts = &btheme->space_text; - break; - case SPACE_OUTLINER: - ts = &btheme->space_outliner; - break; - case SPACE_INFO: - ts = &btheme->space_info; - break; - case SPACE_USERPREF: - ts = &btheme->space_preferences; - break; - case SPACE_CONSOLE: - ts = &btheme->space_console; - break; - case SPACE_NODE: - ts = &btheme->space_node; - break; - case SPACE_CLIP: - ts = &btheme->space_clip; - break; - case SPACE_TOPBAR: - ts = &btheme->space_topbar; - break; - case SPACE_STATUSBAR: - ts = &btheme->space_statusbar; - break; - case SPACE_SPREADSHEET: - ts = &btheme->space_spreadsheet; - break; - default: - ts = &btheme->space_view3d; - break; - } - - switch (colorid) { - case TH_BACK: - if (ELEM(theme_regionid, RGN_TYPE_WINDOW, RGN_TYPE_PREVIEW)) { - cp = ts->back; - } - else if (theme_regionid == RGN_TYPE_CHANNELS) { - cp = ts->list; - } - else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { - cp = ts->header; - } - else if (theme_regionid == RGN_TYPE_NAV_BAR) { - cp = ts->navigation_bar; - } - else if (theme_regionid == RGN_TYPE_EXECUTE) { - cp = ts->execution_buts; - } - else { - cp = ts->button; - } - - copy_v4_v4_uchar(back, cp); - if (!ED_region_is_overlap(spacetype, theme_regionid)) { - back[3] = 255; - } - cp = back; - break; - case TH_BACK_GRAD: - cp = ts->back_grad; - break; - - case TH_BACKGROUND_TYPE: - cp = &setting; - setting = ts->background_type; - break; - case TH_TEXT: - if (theme_regionid == RGN_TYPE_WINDOW) { - cp = ts->text; - } - else if (theme_regionid == RGN_TYPE_CHANNELS) { - cp = ts->list_text; - } - else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { - cp = ts->header_text; - } - else { - cp = ts->button_text; - } - break; - case TH_TEXT_HI: - if (theme_regionid == RGN_TYPE_WINDOW) { - cp = ts->text_hi; - } - else if (theme_regionid == RGN_TYPE_CHANNELS) { - cp = ts->list_text_hi; - } - else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { - cp = ts->header_text_hi; - } - else { - cp = ts->button_text_hi; - } - break; - case TH_TITLE: - if (theme_regionid == RGN_TYPE_WINDOW) { - cp = ts->title; - } - else if (theme_regionid == RGN_TYPE_CHANNELS) { - cp = ts->list_title; - } - else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { - cp = ts->header_title; - } - else { - cp = ts->button_title; - } - break; - - case TH_HEADER: - cp = ts->header; - break; - case TH_HEADERDESEL: - /* We calculate a dynamic builtin header deselect color, also for pull-downs. */ - cp = ts->header; - headerdesel[0] = cp[0] > 10 ? cp[0] - 10 : 0; - headerdesel[1] = cp[1] > 10 ? cp[1] - 10 : 0; - headerdesel[2] = cp[2] > 10 ? cp[2] - 10 : 0; - headerdesel[3] = cp[3]; - cp = headerdesel; - break; - case TH_HEADER_TEXT: - cp = ts->header_text; - break; - case TH_HEADER_TEXT_HI: - cp = ts->header_text_hi; - break; - - case TH_PANEL_HEADER: - cp = ts->panelcolors.header; - break; - case TH_PANEL_BACK: - cp = ts->panelcolors.back; - break; - case TH_PANEL_SUB_BACK: - cp = ts->panelcolors.sub_back; - break; - - case TH_BUTBACK: - cp = ts->button; - break; - case TH_BUTBACK_TEXT: - cp = ts->button_text; - break; - case TH_BUTBACK_TEXT_HI: - cp = ts->button_text_hi; - break; - - case TH_TAB_ACTIVE: - cp = ts->tab_active; - break; - case TH_TAB_INACTIVE: - cp = ts->tab_inactive; - break; - case TH_TAB_BACK: - cp = ts->tab_back; - break; - case TH_TAB_OUTLINE: - cp = ts->tab_outline; - break; - - case TH_SHADE1: - cp = ts->shade1; - break; - case TH_SHADE2: - cp = ts->shade2; - break; - case TH_HILITE: - cp = ts->hilite; - break; - - case TH_GRID: - cp = ts->grid; - break; - case TH_TIME_SCRUB_BACKGROUND: - cp = ts->time_scrub_background; - break; - case TH_TIME_MARKER_LINE: - cp = ts->time_marker_line; - break; - case TH_TIME_MARKER_LINE_SELECTED: - cp = ts->time_marker_line_selected; - break; - case TH_VIEW_OVERLAY: - cp = ts->view_overlay; - break; - case TH_WIRE: - cp = ts->wire; - break; - case TH_WIRE_INNER: - cp = ts->syntaxr; - break; - case TH_WIRE_EDIT: - cp = ts->wire_edit; - break; - case TH_LIGHT: - cp = ts->lamp; - break; - case TH_SPEAKER: - cp = ts->speaker; - break; - case TH_CAMERA: - cp = ts->camera; - break; - case TH_EMPTY: - cp = ts->empty; - break; - case TH_SELECT: - cp = ts->select; - break; - case TH_ACTIVE: - cp = ts->active; - break; - case TH_GROUP: - cp = ts->group; - break; - case TH_GROUP_ACTIVE: - cp = ts->group_active; - break; - case TH_TRANSFORM: - cp = ts->transform; - break; - case TH_VERTEX: - cp = ts->vertex; - break; - case TH_VERTEX_SELECT: - cp = ts->vertex_select; - break; - case TH_VERTEX_ACTIVE: - cp = ts->vertex_active; - break; - case TH_VERTEX_BEVEL: - cp = ts->vertex_bevel; - break; - case TH_VERTEX_UNREFERENCED: - cp = ts->vertex_unreferenced; - break; - case TH_VERTEX_SIZE: - cp = &ts->vertex_size; - break; - case TH_OUTLINE_WIDTH: - cp = &ts->outline_width; - break; - case TH_OBCENTER_DIA: - cp = &ts->obcenter_dia; - break; - case TH_EDGE: - cp = ts->edge; - break; - case TH_EDGE_SELECT: - cp = ts->edge_select; - break; - case TH_EDGE_SEAM: - cp = ts->edge_seam; - break; - case TH_EDGE_SHARP: - cp = ts->edge_sharp; - break; - case TH_EDGE_CREASE: - cp = ts->edge_crease; - break; - case TH_EDGE_BEVEL: - cp = ts->edge_bevel; - break; - case TH_EDITMESH_ACTIVE: - cp = ts->editmesh_active; - break; - case TH_EDGE_FACESEL: - cp = ts->edge_facesel; - break; - case TH_FACE: - cp = ts->face; - break; - case TH_FACE_SELECT: - cp = ts->face_select; - break; - case TH_FACE_BACK: - cp = ts->face_back; - break; - case TH_FACE_FRONT: - cp = ts->face_front; - break; - case TH_FACE_DOT: - cp = ts->face_dot; - break; - case TH_FACEDOT_SIZE: - cp = &ts->facedot_size; - break; - case TH_DRAWEXTRA_EDGELEN: - cp = ts->extra_edge_len; - break; - case TH_DRAWEXTRA_EDGEANG: - cp = ts->extra_edge_angle; - break; - case TH_DRAWEXTRA_FACEAREA: - cp = ts->extra_face_area; - break; - case TH_DRAWEXTRA_FACEANG: - cp = ts->extra_face_angle; - break; - case TH_NORMAL: - cp = ts->normal; - break; - case TH_VNORMAL: - cp = ts->vertex_normal; - break; - case TH_LNORMAL: - cp = ts->loop_normal; - break; - case TH_BONE_SOLID: - cp = ts->bone_solid; - break; - case TH_BONE_POSE: - cp = ts->bone_pose; - break; - case TH_BONE_POSE_ACTIVE: - cp = ts->bone_pose_active; - break; - case TH_BONE_LOCKED_WEIGHT: - cp = ts->bone_locked_weight; - break; - case TH_STRIP: - cp = ts->strip; - break; - case TH_STRIP_SELECT: - cp = ts->strip_select; - break; - case TH_KEYTYPE_KEYFRAME: - cp = ts->keytype_keyframe; - break; - case TH_KEYTYPE_KEYFRAME_SELECT: - cp = ts->keytype_keyframe_select; - break; - case TH_KEYTYPE_EXTREME: - cp = ts->keytype_extreme; - break; - case TH_KEYTYPE_EXTREME_SELECT: - cp = ts->keytype_extreme_select; - break; - case TH_KEYTYPE_BREAKDOWN: - cp = ts->keytype_breakdown; - break; - case TH_KEYTYPE_BREAKDOWN_SELECT: - cp = ts->keytype_breakdown_select; - break; - case TH_KEYTYPE_JITTER: - cp = ts->keytype_jitter; - break; - case TH_KEYTYPE_JITTER_SELECT: - cp = ts->keytype_jitter_select; - break; - case TH_KEYTYPE_MOVEHOLD: - cp = ts->keytype_movehold; - break; - case TH_KEYTYPE_MOVEHOLD_SELECT: - cp = ts->keytype_movehold_select; - break; - case TH_KEYBORDER: - cp = ts->keyborder; - break; - case TH_KEYBORDER_SELECT: - cp = ts->keyborder_select; - break; - case TH_CFRAME: - cp = ts->cframe; - break; - case TH_TIME_KEYFRAME: - cp = ts->time_keyframe; - break; - case TH_TIME_GP_KEYFRAME: - cp = ts->time_gp_keyframe; - break; - case TH_NURB_ULINE: - cp = ts->nurb_uline; - break; - case TH_NURB_VLINE: - cp = ts->nurb_vline; - break; - case TH_NURB_SEL_ULINE: - cp = ts->nurb_sel_uline; - break; - case TH_NURB_SEL_VLINE: - cp = ts->nurb_sel_vline; - break; - case TH_ACTIVE_SPLINE: - cp = ts->act_spline; - break; - case TH_ACTIVE_VERT: - cp = ts->lastsel_point; - break; - case TH_HANDLE_FREE: - cp = ts->handle_free; - break; - case TH_HANDLE_AUTO: - cp = ts->handle_auto; - break; - case TH_HANDLE_AUTOCLAMP: - cp = ts->handle_auto_clamped; - break; - case TH_HANDLE_VECT: - cp = ts->handle_vect; - break; - case TH_HANDLE_ALIGN: - cp = ts->handle_align; - break; - case TH_HANDLE_SEL_FREE: - cp = ts->handle_sel_free; - break; - case TH_HANDLE_SEL_AUTO: - cp = ts->handle_sel_auto; - break; - case TH_HANDLE_SEL_AUTOCLAMP: - cp = ts->handle_sel_auto_clamped; - break; - case TH_HANDLE_SEL_VECT: - cp = ts->handle_sel_vect; - break; - case TH_HANDLE_SEL_ALIGN: - cp = ts->handle_sel_align; - break; - case TH_FREESTYLE_EDGE_MARK: - cp = ts->freestyle_edge_mark; - break; - case TH_FREESTYLE_FACE_MARK: - cp = ts->freestyle_face_mark; - break; - - case TH_SYNTAX_B: - cp = ts->syntaxb; - break; - case TH_SYNTAX_V: - cp = ts->syntaxv; - break; - case TH_SYNTAX_C: - cp = ts->syntaxc; - break; - case TH_SYNTAX_L: - cp = ts->syntaxl; - break; - case TH_SYNTAX_D: - cp = ts->syntaxd; - break; - case TH_SYNTAX_R: - cp = ts->syntaxr; - break; - case TH_SYNTAX_N: - cp = ts->syntaxn; - break; - case TH_SYNTAX_S: - cp = ts->syntaxs; - break; - case TH_LINENUMBERS: - cp = ts->line_numbers; - break; - - case TH_NODE: - cp = ts->syntaxl; - break; - case TH_NODE_INPUT: - cp = ts->syntaxn; - break; - case TH_NODE_OUTPUT: - cp = ts->nodeclass_output; - break; - case TH_NODE_COLOR: - cp = ts->syntaxb; - break; - case TH_NODE_FILTER: - cp = ts->nodeclass_filter; - break; - case TH_NODE_VECTOR: - cp = ts->nodeclass_vector; - break; - case TH_NODE_TEXTURE: - cp = ts->nodeclass_texture; - break; - case TH_NODE_PATTERN: - cp = ts->nodeclass_pattern; - break; - case TH_NODE_SCRIPT: - cp = ts->nodeclass_script; - break; - case TH_NODE_LAYOUT: - cp = ts->nodeclass_layout; - break; - case TH_NODE_GEOMETRY: - cp = ts->nodeclass_geometry; - break; - case TH_NODE_ATTRIBUTE: - cp = ts->nodeclass_attribute; - break; - case TH_NODE_SHADER: - cp = ts->nodeclass_shader; - break; - case TH_NODE_CONVERTOR: - cp = ts->syntaxv; - break; - case TH_NODE_GROUP: - cp = ts->syntaxc; - break; - case TH_NODE_INTERFACE: - cp = ts->console_output; - break; - case TH_NODE_FRAME: - cp = ts->movie; - break; - case TH_NODE_MATTE: - cp = ts->syntaxs; - break; - case TH_NODE_DISTORT: - cp = ts->syntaxd; - break; - case TH_NODE_CURVING: - cp = &ts->noodle_curving; - break; - case TH_NODE_GRID_LEVELS: - cp = &ts->grid_levels; - break; - - case TH_SEQ_MOVIE: - cp = ts->movie; - break; - case TH_SEQ_MOVIECLIP: - cp = ts->movieclip; - break; - case TH_SEQ_MASK: - cp = ts->mask; - break; - case TH_SEQ_IMAGE: - cp = ts->image; - break; - case TH_SEQ_SCENE: - cp = ts->scene; - break; - case TH_SEQ_AUDIO: - cp = ts->audio; - break; - case TH_SEQ_EFFECT: - cp = ts->effect; - break; - case TH_SEQ_META: - cp = ts->meta; - break; - case TH_SEQ_TEXT: - cp = ts->text_strip; - break; - case TH_SEQ_PREVIEW: - cp = ts->preview_back; - break; - case TH_SEQ_COLOR: - cp = ts->color_strip; - break; - case TH_SEQ_ACTIVE: - cp = ts->active_strip; - break; - case TH_SEQ_SELECTED: - cp = ts->selected_strip; - break; - - case TH_CONSOLE_OUTPUT: - cp = ts->console_output; - break; - case TH_CONSOLE_INPUT: - cp = ts->console_input; - break; - case TH_CONSOLE_INFO: - cp = ts->console_info; - break; - case TH_CONSOLE_ERROR: - cp = ts->console_error; - break; - case TH_CONSOLE_CURSOR: - cp = ts->console_cursor; - break; - case TH_CONSOLE_SELECT: - cp = ts->console_select; - break; - - case TH_HANDLE_VERTEX: - cp = ts->handle_vertex; - break; - case TH_HANDLE_VERTEX_SELECT: - cp = ts->handle_vertex_select; - break; - case TH_HANDLE_VERTEX_SIZE: - cp = &ts->handle_vertex_size; - break; - - case TH_GP_VERTEX: - cp = ts->gp_vertex; - break; - case TH_GP_VERTEX_SELECT: - cp = ts->gp_vertex_select; - break; - case TH_GP_VERTEX_SIZE: - cp = &ts->gp_vertex_size; - break; - - case TH_DOPESHEET_CHANNELOB: - cp = ts->ds_channel; - break; - case TH_DOPESHEET_CHANNELSUBOB: - cp = ts->ds_subchannel; - break; - case TH_DOPESHEET_IPOLINE: - cp = ts->ds_ipoline; - break; - - case TH_PREVIEW_BACK: - cp = ts->preview_back; - break; - - case TH_STITCH_PREVIEW_FACE: - cp = ts->preview_stitch_face; - break; - - case TH_STITCH_PREVIEW_EDGE: - cp = ts->preview_stitch_edge; - break; - - case TH_STITCH_PREVIEW_VERT: - cp = ts->preview_stitch_vert; - break; - - case TH_STITCH_PREVIEW_STITCHABLE: - cp = ts->preview_stitch_stitchable; - break; - - case TH_STITCH_PREVIEW_UNSTITCHABLE: - cp = ts->preview_stitch_unstitchable; - break; - case TH_STITCH_PREVIEW_ACTIVE: - cp = ts->preview_stitch_active; - break; - - case TH_PAINT_CURVE_HANDLE: - cp = ts->paint_curve_handle; - break; - case TH_PAINT_CURVE_PIVOT: - cp = ts->paint_curve_pivot; - break; - - case TH_METADATA_BG: - cp = ts->metadatabg; - break; - case TH_METADATA_TEXT: - cp = ts->metadatatext; - break; - - case TH_UV_SHADOW: - cp = ts->uv_shadow; - break; - - case TH_MARKER_OUTLINE: - cp = ts->marker_outline; - break; - case TH_MARKER: - cp = ts->marker; - break; - case TH_ACT_MARKER: - cp = ts->act_marker; - break; - case TH_SEL_MARKER: - cp = ts->sel_marker; - break; - case TH_BUNDLE_SOLID: - cp = ts->bundle_solid; - break; - case TH_DIS_MARKER: - cp = ts->dis_marker; - break; - case TH_PATH_BEFORE: - cp = ts->path_before; - break; - case TH_PATH_AFTER: - cp = ts->path_after; - break; - case TH_PATH_KEYFRAME_BEFORE: - cp = ts->path_keyframe_before; - break; - case TH_PATH_KEYFRAME_AFTER: - cp = ts->path_keyframe_after; - break; - case TH_CAMERA_PATH: - cp = ts->camera_path; - break; - case TH_LOCK_MARKER: - cp = ts->lock_marker; - break; - - case TH_MATCH: - cp = ts->match; - break; - - case TH_SELECT_HIGHLIGHT: - cp = ts->selected_highlight; - break; - - case TH_SELECT_ACTIVE: - cp = ts->active; - break; - - case TH_SELECTED_OBJECT: - cp = ts->selected_object; - break; - - case TH_ACTIVE_OBJECT: - cp = ts->active_object; - break; - - case TH_EDITED_OBJECT: - cp = ts->edited_object; - break; - - case TH_ROW_ALTERNATE: - cp = ts->row_alternate; - break; - - case TH_SKIN_ROOT: - cp = ts->skin_root; - break; - - case TH_ANIM_ACTIVE: - cp = ts->anim_active; - break; - case TH_ANIM_INACTIVE: - cp = ts->anim_non_active; - break; - case TH_ANIM_PREVIEW_RANGE: - cp = ts->anim_preview_range; - break; - - case TH_NLA_TWEAK: - cp = ts->nla_tweaking; - break; - case TH_NLA_TWEAK_DUPLI: - cp = ts->nla_tweakdupli; - break; - - case TH_NLA_TRACK: - cp = ts->nla_track; - break; - case TH_NLA_TRANSITION: - cp = ts->nla_transition; - break; - case TH_NLA_TRANSITION_SEL: - cp = ts->nla_transition_sel; - break; - case TH_NLA_META: - cp = ts->nla_meta; - break; - case TH_NLA_META_SEL: - cp = ts->nla_meta_sel; - break; - case TH_NLA_SOUND: - cp = ts->nla_sound; - break; - case TH_NLA_SOUND_SEL: - cp = ts->nla_sound_sel; - break; - - case TH_WIDGET_EMBOSS: - cp = btheme->tui.widget_emboss; - break; - - case TH_EDITOR_OUTLINE: - cp = btheme->tui.editor_outline; - break; - case TH_WIDGET_TEXT_CURSOR: - cp = btheme->tui.widget_text_cursor; - break; - - case TH_TRANSPARENT_CHECKER_PRIMARY: - cp = btheme->tui.transparent_checker_primary; - break; - case TH_TRANSPARENT_CHECKER_SECONDARY: - cp = btheme->tui.transparent_checker_secondary; - break; - case TH_TRANSPARENT_CHECKER_SIZE: - cp = &btheme->tui.transparent_checker_size; - break; - - case TH_AXIS_X: - cp = btheme->tui.xaxis; - break; - case TH_AXIS_Y: - cp = btheme->tui.yaxis; - break; - case TH_AXIS_Z: - cp = btheme->tui.zaxis; - break; - - case TH_GIZMO_HI: - cp = btheme->tui.gizmo_hi; - break; - case TH_GIZMO_PRIMARY: - cp = btheme->tui.gizmo_primary; - break; - case TH_GIZMO_SECONDARY: - cp = btheme->tui.gizmo_secondary; - break; - case TH_GIZMO_VIEW_ALIGN: - cp = btheme->tui.gizmo_view_align; - break; - case TH_GIZMO_A: - cp = btheme->tui.gizmo_a; - break; - case TH_GIZMO_B: - cp = btheme->tui.gizmo_b; - break; - - case TH_ICON_SCENE: - cp = btheme->tui.icon_scene; - break; - case TH_ICON_COLLECTION: - cp = btheme->tui.icon_collection; - break; - case TH_ICON_OBJECT: - cp = btheme->tui.icon_object; - break; - case TH_ICON_OBJECT_DATA: - cp = btheme->tui.icon_object_data; - break; - case TH_ICON_MODIFIER: - cp = btheme->tui.icon_modifier; - break; - case TH_ICON_SHADING: - cp = btheme->tui.icon_shading; - break; - case TH_ICON_FOLDER: - cp = btheme->tui.icon_folder; - break; - case TH_ICON_FUND: { - /* Development fund icon color is not part of theme. */ - static const uchar red[4] = {204, 48, 72, 255}; - cp = red; - break; - } - - case TH_SCROLL_TEXT: - cp = btheme->tui.wcol_scroll.text; - break; - - case TH_INFO_SELECTED: - cp = ts->info_selected; - break; - case TH_INFO_SELECTED_TEXT: - cp = ts->info_selected_text; - break; - case TH_INFO_ERROR: - cp = ts->info_error; - break; - case TH_INFO_ERROR_TEXT: - cp = ts->info_error_text; - break; - case TH_INFO_WARNING: - cp = ts->info_warning; - break; - case TH_INFO_WARNING_TEXT: - cp = ts->info_warning_text; - break; - case TH_INFO_INFO: - cp = ts->info_info; - break; - case TH_INFO_INFO_TEXT: - cp = ts->info_info_text; - break; - case TH_INFO_DEBUG: - cp = ts->info_debug; - break; - case TH_INFO_DEBUG_TEXT: - cp = ts->info_debug_text; - break; - case TH_INFO_PROPERTY: - cp = ts->info_property; - break; - case TH_INFO_PROPERTY_TEXT: - cp = ts->info_property_text; - break; - case TH_INFO_OPERATOR: - cp = ts->info_operator; - break; - case TH_INFO_OPERATOR_TEXT: - cp = ts->info_operator_text; - break; - case TH_V3D_CLIPPING_BORDER: - cp = ts->clipping_border_3d; - break; - } - } - } - - return (const uchar *)cp; -} - -/** - * Initialize default theme. - * - * \note When you add new colors, created & saved themes need initialized - * use function below, #init_userdef_do_versions. - */ -void UI_theme_init_default(void) -{ - /* we search for the theme with name Default */ - bTheme *btheme = BLI_findstring(&U.themes, "Default", offsetof(bTheme, name)); - if (btheme == NULL) { - btheme = MEM_callocN(sizeof(bTheme), __func__); - BLI_addtail(&U.themes, btheme); - } - - UI_SetTheme(0, 0); /* make sure the global used in this file is set */ - - const int active_theme_area = btheme->active_theme_area; - memcpy(btheme, &U_theme_default, sizeof(*btheme)); - btheme->active_theme_area = active_theme_area; -} - -void UI_style_init_default(void) -{ - BLI_freelistN(&U.uistyles); - /* gets automatically re-allocated */ - uiStyleInit(); -} - -void UI_SetTheme(int spacetype, int regionid) -{ - if (spacetype) { - /* later on, a local theme can be found too */ - theme_active = U.themes.first; - theme_spacetype = spacetype; - theme_regionid = regionid; - } - else if (regionid) { - /* popups */ - theme_active = U.themes.first; - theme_spacetype = SPACE_PROPERTIES; - theme_regionid = regionid; - } - else { - /* for safety, when theme was deleted */ - theme_active = U.themes.first; - theme_spacetype = SPACE_VIEW3D; - theme_regionid = RGN_TYPE_WINDOW; - } -} - -bTheme *UI_GetTheme(void) -{ - return U.themes.first; -} - -/** - * for the rare case we need to temp swap in a different theme (offscreen render) - */ -void UI_Theme_Store(struct bThemeState *theme_state) -{ - *theme_state = g_theme_state; -} -void UI_Theme_Restore(struct bThemeState *theme_state) -{ - g_theme_state = *theme_state; -} - -void UI_GetThemeColorShadeAlpha4ubv(int colorid, int coloffset, int alphaoffset, uchar col[4]) -{ - int r, g, b, a; - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - r = coloffset + (int)cp[0]; - CLAMP(r, 0, 255); - g = coloffset + (int)cp[1]; - CLAMP(g, 0, 255); - b = coloffset + (int)cp[2]; - CLAMP(b, 0, 255); - a = alphaoffset + (int)cp[3]; - CLAMP(a, 0, 255); - - col[0] = r; - col[1] = g; - col[2] = b; - col[3] = a; -} - -void UI_GetThemeColorBlend3ubv(int colorid1, int colorid2, float fac, uchar col[3]) -{ - const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); - const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); - - CLAMP(fac, 0.0f, 1.0f); - col[0] = floorf((1.0f - fac) * cp1[0] + fac * cp2[0]); - col[1] = floorf((1.0f - fac) * cp1[1] + fac * cp2[1]); - col[2] = floorf((1.0f - fac) * cp1[2] + fac * cp2[2]); -} - -void UI_GetThemeColorBlend3f(int colorid1, int colorid2, float fac, float r_col[3]) -{ - const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); - const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); - - CLAMP(fac, 0.0f, 1.0f); - r_col[0] = ((1.0f - fac) * cp1[0] + fac * cp2[0]) / 255.0f; - r_col[1] = ((1.0f - fac) * cp1[1] + fac * cp2[1]) / 255.0f; - r_col[2] = ((1.0f - fac) * cp1[2] + fac * cp2[2]) / 255.0f; -} - -void UI_GetThemeColorBlend4f(int colorid1, int colorid2, float fac, float r_col[4]) -{ - const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); - const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); - - CLAMP(fac, 0.0f, 1.0f); - r_col[0] = ((1.0f - fac) * cp1[0] + fac * cp2[0]) / 255.0f; - r_col[1] = ((1.0f - fac) * cp1[1] + fac * cp2[1]) / 255.0f; - r_col[2] = ((1.0f - fac) * cp1[2] + fac * cp2[2]) / 255.0f; - r_col[3] = ((1.0f - fac) * cp1[3] + fac * cp2[3]) / 255.0f; -} - -void UI_FontThemeColor(int fontid, int colorid) -{ - uchar color[4]; - UI_GetThemeColor4ubv(colorid, color); - BLF_color4ubv(fontid, color); -} - -/* get individual values, not scaled */ -float UI_GetThemeValuef(int colorid) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - return ((float)cp[0]); -} - -/* get individual values, not scaled */ -int UI_GetThemeValue(int colorid) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - return ((int)cp[0]); -} - -/* versions of the function above, which take a space-type */ -float UI_GetThemeValueTypef(int colorid, int spacetype) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); - return ((float)cp[0]); -} - -int UI_GetThemeValueType(int colorid, int spacetype) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); - return ((int)cp[0]); -} - -/* get the color, range 0.0-1.0 */ -void UI_GetThemeColor3fv(int colorid, float col[3]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - col[0] = ((float)cp[0]) / 255.0f; - col[1] = ((float)cp[1]) / 255.0f; - col[2] = ((float)cp[2]) / 255.0f; -} - -void UI_GetThemeColor4fv(int colorid, float col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - col[0] = ((float)cp[0]) / 255.0f; - col[1] = ((float)cp[1]) / 255.0f; - col[2] = ((float)cp[2]) / 255.0f; - col[3] = ((float)cp[3]) / 255.0f; -} - -void UI_GetThemeColorType4fv(int colorid, int spacetype, float col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); - col[0] = ((float)cp[0]) / 255.0f; - col[1] = ((float)cp[1]) / 255.0f; - col[2] = ((float)cp[2]) / 255.0f; - col[3] = ((float)cp[3]) / 255.0f; -} - -/* get the color, range 0.0-1.0, complete with shading offset */ -void UI_GetThemeColorShade3fv(int colorid, int offset, float col[3]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - int r, g, b; - - r = offset + (int)cp[0]; - CLAMP(r, 0, 255); - g = offset + (int)cp[1]; - CLAMP(g, 0, 255); - b = offset + (int)cp[2]; - CLAMP(b, 0, 255); - - col[0] = ((float)r) / 255.0f; - col[1] = ((float)g) / 255.0f; - col[2] = ((float)b) / 255.0f; -} - -void UI_GetThemeColorShade3ubv(int colorid, int offset, uchar col[3]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - int r, g, b; - - r = offset + (int)cp[0]; - CLAMP(r, 0, 255); - g = offset + (int)cp[1]; - CLAMP(g, 0, 255); - b = offset + (int)cp[2]; - CLAMP(b, 0, 255); - - col[0] = r; - col[1] = g; - col[2] = b; -} - -void UI_GetThemeColorBlendShade3ubv( - int colorid1, int colorid2, float fac, int offset, uchar col[3]) -{ - const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); - const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); - - CLAMP(fac, 0.0f, 1.0f); - - float blend[3]; - blend[0] = (offset + floorf((1.0f - fac) * cp1[0] + fac * cp2[0])) / 255.0f; - blend[1] = (offset + floorf((1.0f - fac) * cp1[1] + fac * cp2[1])) / 255.0f; - blend[2] = (offset + floorf((1.0f - fac) * cp1[2] + fac * cp2[2])) / 255.0f; - - unit_float_to_uchar_clamp_v3(col, blend); -} - -void UI_GetThemeColorShade4ubv(int colorid, int offset, uchar col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - int r, g, b; - - r = offset + (int)cp[0]; - CLAMP(r, 0, 255); - g = offset + (int)cp[1]; - CLAMP(g, 0, 255); - b = offset + (int)cp[2]; - CLAMP(b, 0, 255); - - col[0] = r; - col[1] = g; - col[2] = b; - col[3] = cp[3]; -} - -void UI_GetThemeColorShadeAlpha4fv(int colorid, int coloffset, int alphaoffset, float col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - int r, g, b, a; - - r = coloffset + (int)cp[0]; - CLAMP(r, 0, 255); - g = coloffset + (int)cp[1]; - CLAMP(g, 0, 255); - b = coloffset + (int)cp[2]; - CLAMP(b, 0, 255); - a = alphaoffset + (int)cp[3]; - CLAMP(a, 0, 255); - - col[0] = ((float)r) / 255.0f; - col[1] = ((float)g) / 255.0f; - col[2] = ((float)b) / 255.0f; - col[3] = ((float)a) / 255.0f; -} - -void UI_GetThemeColorBlendShade3fv(int colorid1, int colorid2, float fac, int offset, float col[3]) -{ - const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); - const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); - int r, g, b; - - CLAMP(fac, 0.0f, 1.0f); - - r = offset + floorf((1.0f - fac) * cp1[0] + fac * cp2[0]); - CLAMP(r, 0, 255); - g = offset + floorf((1.0f - fac) * cp1[1] + fac * cp2[1]); - CLAMP(g, 0, 255); - b = offset + floorf((1.0f - fac) * cp1[2] + fac * cp2[2]); - CLAMP(b, 0, 255); - - col[0] = ((float)r) / 255.0f; - col[1] = ((float)g) / 255.0f; - col[2] = ((float)b) / 255.0f; -} - -void UI_GetThemeColorBlendShade4fv(int colorid1, int colorid2, float fac, int offset, float col[4]) -{ - const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); - const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); - int r, g, b, a; - - CLAMP(fac, 0.0f, 1.0f); - - r = offset + floorf((1.0f - fac) * cp1[0] + fac * cp2[0]); - CLAMP(r, 0, 255); - g = offset + floorf((1.0f - fac) * cp1[1] + fac * cp2[1]); - CLAMP(g, 0, 255); - b = offset + floorf((1.0f - fac) * cp1[2] + fac * cp2[2]); - CLAMP(b, 0, 255); - a = offset + floorf((1.0f - fac) * cp1[3] + fac * cp2[3]); - CLAMP(a, 0, 255); - - col[0] = ((float)r) / 255.0f; - col[1] = ((float)g) / 255.0f; - col[2] = ((float)b) / 255.0f; - col[3] = ((float)a) / 255.0f; -} - -/* get the color, in char pointer */ -void UI_GetThemeColor3ubv(int colorid, uchar col[3]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - col[0] = cp[0]; - col[1] = cp[1]; - col[2] = cp[2]; -} - -/* get the color, range 0.0-1.0, complete with shading offset */ -void UI_GetThemeColorShade4fv(int colorid, int offset, float col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - int r, g, b, a; - - r = offset + (int)cp[0]; - CLAMP(r, 0, 255); - g = offset + (int)cp[1]; - CLAMP(g, 0, 255); - b = offset + (int)cp[2]; - CLAMP(b, 0, 255); - - a = (int)cp[3]; /* no shading offset... */ - CLAMP(a, 0, 255); - - col[0] = ((float)r) / 255.0f; - col[1] = ((float)g) / 255.0f; - col[2] = ((float)b) / 255.0f; - col[3] = ((float)a) / 255.0f; -} - -/* get the color, in char pointer */ -void UI_GetThemeColor4ubv(int colorid, uchar col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - col[0] = cp[0]; - col[1] = cp[1]; - col[2] = cp[2]; - col[3] = cp[3]; -} - -void UI_GetThemeColorType3fv(int colorid, int spacetype, float col[3]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); - col[0] = ((float)cp[0]) / 255.0f; - col[1] = ((float)cp[1]) / 255.0f; - col[2] = ((float)cp[2]) / 255.0f; -} - -void UI_GetThemeColorType3ubv(int colorid, int spacetype, uchar col[3]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); - col[0] = cp[0]; - col[1] = cp[1]; - col[2] = cp[2]; -} - -void UI_GetThemeColorType4ubv(int colorid, int spacetype, uchar col[4]) -{ - const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); - col[0] = cp[0]; - col[1] = cp[1]; - col[2] = cp[2]; - col[3] = cp[3]; -} - -bool UI_GetIconThemeColor4ubv(int colorid, uchar col[4]) -{ - if (colorid == 0) { - return false; - } - if (colorid == TH_ICON_FUND) { - /* Always color development fund icon. */ - } - else if (!((theme_spacetype == SPACE_OUTLINER && theme_regionid == RGN_TYPE_WINDOW) || - (theme_spacetype == SPACE_PROPERTIES && theme_regionid == RGN_TYPE_NAV_BAR) || - (theme_spacetype == SPACE_FILE && theme_regionid == RGN_TYPE_WINDOW))) { - /* Only colored icons in specific places, overall UI is intended - * to stay monochrome and out of the way except a few places where it - * is important to communicate different data types. */ - return false; - } - - const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); - col[0] = cp[0]; - col[1] = cp[1]; - col[2] = cp[2]; - col[3] = cp[3]; - - return true; -} - -void UI_GetColorPtrShade3ubv(const uchar cp[3], uchar col[3], int offset) -{ - int r, g, b; - - r = offset + (int)cp[0]; - g = offset + (int)cp[1]; - b = offset + (int)cp[2]; - - CLAMP(r, 0, 255); - CLAMP(g, 0, 255); - CLAMP(b, 0, 255); - - col[0] = r; - col[1] = g; - col[2] = b; -} - -/* get a 3 byte color, blended and shaded between two other char color pointers */ -void UI_GetColorPtrBlendShade3ubv( - const uchar cp1[3], const uchar cp2[3], uchar col[3], float fac, int offset) -{ - int r, g, b; - - CLAMP(fac, 0.0f, 1.0f); - r = offset + floor((1.0f - fac) * cp1[0] + fac * cp2[0]); - g = offset + floor((1.0f - fac) * cp1[1] + fac * cp2[1]); - b = offset + floor((1.0f - fac) * cp1[2] + fac * cp2[2]); - - CLAMP(r, 0, 255); - CLAMP(g, 0, 255); - CLAMP(b, 0, 255); - - col[0] = r; - col[1] = g; - col[2] = b; -} - -void UI_ThemeClearColor(int colorid) -{ - float col[3]; - - UI_GetThemeColor3fv(colorid, col); - GPU_clear_color(col[0], col[1], col[2], 1.0f); -} - -int UI_ThemeMenuShadowWidth(void) -{ - bTheme *btheme = UI_GetTheme(); - return (int)(btheme->tui.menu_shadow_width * UI_DPI_FAC); -} - -void UI_make_axis_color(const uchar src_col[3], uchar dst_col[3], const char axis) -{ - uchar col[3]; - - switch (axis) { - case 'X': - UI_GetThemeColor3ubv(TH_AXIS_X, col); - UI_GetColorPtrBlendShade3ubv(src_col, col, dst_col, 0.5f, -10); - break; - case 'Y': - UI_GetThemeColor3ubv(TH_AXIS_Y, col); - UI_GetColorPtrBlendShade3ubv(src_col, col, dst_col, 0.5f, -10); - break; - case 'Z': - UI_GetThemeColor3ubv(TH_AXIS_Z, col); - UI_GetColorPtrBlendShade3ubv(src_col, col, dst_col, 0.5f, -10); - break; - default: - BLI_assert(0); - break; - } -} diff --git a/source/blender/editors/interface/resources.cc b/source/blender/editors/interface/resources.cc new file mode 100644 index 00000000000..f610a765797 --- /dev/null +++ b/source/blender/editors/interface/resources.cc @@ -0,0 +1,1513 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. + * All rights reserved. + */ + +/** \file + * \ingroup edinterface + */ + +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_userdef_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "BKE_addon.h" +#include "BKE_appdir.h" +#include "BKE_main.h" +#include "BKE_mesh_runtime.h" + +#include "BLO_readfile.h" /* for UserDef version patching. */ + +#include "BLF_api.h" + +#include "ED_screen.h" + +#include "UI_interface.h" +#include "UI_interface_icons.h" + +#include "GPU_framebuffer.h" +#include "interface_intern.h" + +/* global for themes */ +typedef void (*VectorDrawFunc)(int x, int y, int w, int h, float alpha); + +/* be sure to keep 'bThemeState' in sync */ +static struct bThemeState g_theme_state = { + NULL, + SPACE_VIEW3D, + RGN_TYPE_WINDOW, +}; + +#define theme_active g_theme_state.theme +#define theme_spacetype g_theme_state.spacetype +#define theme_regionid g_theme_state.regionid + +void ui_resources_init(void) +{ + UI_icons_init(); +} + +void ui_resources_free(void) +{ + UI_icons_free(); +} + +/* ******************************************************** */ +/* THEMES */ +/* ******************************************************** */ + +const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) +{ + ThemeSpace *ts = NULL; + static uchar error[4] = {240, 0, 240, 255}; + static uchar alert[4] = {240, 60, 60, 255}; + static uchar headerdesel[4] = {0, 0, 0, 255}; + static uchar back[4] = {0, 0, 0, 255}; + static uchar setting = 0; + const uchar *cp = error; + + /* ensure we're not getting a color after running BKE_blender_userdef_free */ + BLI_assert(BLI_findindex(&U.themes, theme_active) != -1); + BLI_assert(colorid != TH_UNDEFINED); + + if (btheme) { + + /* first check for ui buttons theme */ + if (colorid < TH_THEMEUI) { + + switch (colorid) { + + case TH_REDALERT: + cp = alert; + break; + } + } + else { + + switch (spacetype) { + case SPACE_PROPERTIES: + ts = &btheme->space_properties; + break; + case SPACE_VIEW3D: + ts = &btheme->space_view3d; + break; + case SPACE_GRAPH: + ts = &btheme->space_graph; + break; + case SPACE_FILE: + ts = &btheme->space_file; + break; + case SPACE_NLA: + ts = &btheme->space_nla; + break; + case SPACE_ACTION: + ts = &btheme->space_action; + break; + case SPACE_SEQ: + ts = &btheme->space_sequencer; + break; + case SPACE_IMAGE: + ts = &btheme->space_image; + break; + case SPACE_TEXT: + ts = &btheme->space_text; + break; + case SPACE_OUTLINER: + ts = &btheme->space_outliner; + break; + case SPACE_INFO: + ts = &btheme->space_info; + break; + case SPACE_USERPREF: + ts = &btheme->space_preferences; + break; + case SPACE_CONSOLE: + ts = &btheme->space_console; + break; + case SPACE_NODE: + ts = &btheme->space_node; + break; + case SPACE_CLIP: + ts = &btheme->space_clip; + break; + case SPACE_TOPBAR: + ts = &btheme->space_topbar; + break; + case SPACE_STATUSBAR: + ts = &btheme->space_statusbar; + break; + case SPACE_SPREADSHEET: + ts = &btheme->space_spreadsheet; + break; + default: + ts = &btheme->space_view3d; + break; + } + + switch (colorid) { + case TH_BACK: + if (ELEM(theme_regionid, RGN_TYPE_WINDOW, RGN_TYPE_PREVIEW)) { + cp = ts->back; + } + else if (theme_regionid == RGN_TYPE_CHANNELS) { + cp = ts->list; + } + else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + cp = ts->header; + } + else if (theme_regionid == RGN_TYPE_NAV_BAR) { + cp = ts->navigation_bar; + } + else if (theme_regionid == RGN_TYPE_EXECUTE) { + cp = ts->execution_buts; + } + else { + cp = ts->button; + } + + copy_v4_v4_uchar(back, cp); + if (!ED_region_is_overlap(spacetype, theme_regionid)) { + back[3] = 255; + } + cp = back; + break; + case TH_BACK_GRAD: + cp = ts->back_grad; + break; + + case TH_BACKGROUND_TYPE: + cp = &setting; + setting = ts->background_type; + break; + case TH_TEXT: + if (theme_regionid == RGN_TYPE_WINDOW) { + cp = ts->text; + } + else if (theme_regionid == RGN_TYPE_CHANNELS) { + cp = ts->list_text; + } + else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + cp = ts->header_text; + } + else { + cp = ts->button_text; + } + break; + case TH_TEXT_HI: + if (theme_regionid == RGN_TYPE_WINDOW) { + cp = ts->text_hi; + } + else if (theme_regionid == RGN_TYPE_CHANNELS) { + cp = ts->list_text_hi; + } + else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + cp = ts->header_text_hi; + } + else { + cp = ts->button_text_hi; + } + break; + case TH_TITLE: + if (theme_regionid == RGN_TYPE_WINDOW) { + cp = ts->title; + } + else if (theme_regionid == RGN_TYPE_CHANNELS) { + cp = ts->list_title; + } + else if (ELEM(theme_regionid, RGN_TYPE_HEADER, RGN_TYPE_FOOTER)) { + cp = ts->header_title; + } + else { + cp = ts->button_title; + } + break; + + case TH_HEADER: + cp = ts->header; + break; + case TH_HEADERDESEL: + /* We calculate a dynamic builtin header deselect color, also for pull-downs. */ + cp = ts->header; + headerdesel[0] = cp[0] > 10 ? cp[0] - 10 : 0; + headerdesel[1] = cp[1] > 10 ? cp[1] - 10 : 0; + headerdesel[2] = cp[2] > 10 ? cp[2] - 10 : 0; + headerdesel[3] = cp[3]; + cp = headerdesel; + break; + case TH_HEADER_TEXT: + cp = ts->header_text; + break; + case TH_HEADER_TEXT_HI: + cp = ts->header_text_hi; + break; + + case TH_PANEL_HEADER: + cp = ts->panelcolors.header; + break; + case TH_PANEL_BACK: + cp = ts->panelcolors.back; + break; + case TH_PANEL_SUB_BACK: + cp = ts->panelcolors.sub_back; + break; + + case TH_BUTBACK: + cp = ts->button; + break; + case TH_BUTBACK_TEXT: + cp = ts->button_text; + break; + case TH_BUTBACK_TEXT_HI: + cp = ts->button_text_hi; + break; + + case TH_TAB_ACTIVE: + cp = ts->tab_active; + break; + case TH_TAB_INACTIVE: + cp = ts->tab_inactive; + break; + case TH_TAB_BACK: + cp = ts->tab_back; + break; + case TH_TAB_OUTLINE: + cp = ts->tab_outline; + break; + + case TH_SHADE1: + cp = ts->shade1; + break; + case TH_SHADE2: + cp = ts->shade2; + break; + case TH_HILITE: + cp = ts->hilite; + break; + + case TH_GRID: + cp = ts->grid; + break; + case TH_TIME_SCRUB_BACKGROUND: + cp = ts->time_scrub_background; + break; + case TH_TIME_MARKER_LINE: + cp = ts->time_marker_line; + break; + case TH_TIME_MARKER_LINE_SELECTED: + cp = ts->time_marker_line_selected; + break; + case TH_VIEW_OVERLAY: + cp = ts->view_overlay; + break; + case TH_WIRE: + cp = ts->wire; + break; + case TH_WIRE_INNER: + cp = ts->syntaxr; + break; + case TH_WIRE_EDIT: + cp = ts->wire_edit; + break; + case TH_LIGHT: + cp = ts->lamp; + break; + case TH_SPEAKER: + cp = ts->speaker; + break; + case TH_CAMERA: + cp = ts->camera; + break; + case TH_EMPTY: + cp = ts->empty; + break; + case TH_SELECT: + cp = ts->select; + break; + case TH_ACTIVE: + cp = ts->active; + break; + case TH_GROUP: + cp = ts->group; + break; + case TH_GROUP_ACTIVE: + cp = ts->group_active; + break; + case TH_TRANSFORM: + cp = ts->transform; + break; + case TH_VERTEX: + cp = ts->vertex; + break; + case TH_VERTEX_SELECT: + cp = ts->vertex_select; + break; + case TH_VERTEX_ACTIVE: + cp = ts->vertex_active; + break; + case TH_VERTEX_BEVEL: + cp = ts->vertex_bevel; + break; + case TH_VERTEX_UNREFERENCED: + cp = ts->vertex_unreferenced; + break; + case TH_VERTEX_SIZE: + cp = &ts->vertex_size; + break; + case TH_OUTLINE_WIDTH: + cp = &ts->outline_width; + break; + case TH_OBCENTER_DIA: + cp = &ts->obcenter_dia; + break; + case TH_EDGE: + cp = ts->edge; + break; + case TH_EDGE_SELECT: + cp = ts->edge_select; + break; + case TH_EDGE_SEAM: + cp = ts->edge_seam; + break; + case TH_EDGE_SHARP: + cp = ts->edge_sharp; + break; + case TH_EDGE_CREASE: + cp = ts->edge_crease; + break; + case TH_EDGE_BEVEL: + cp = ts->edge_bevel; + break; + case TH_EDITMESH_ACTIVE: + cp = ts->editmesh_active; + break; + case TH_EDGE_FACESEL: + cp = ts->edge_facesel; + break; + case TH_FACE: + cp = ts->face; + break; + case TH_FACE_SELECT: + cp = ts->face_select; + break; + case TH_FACE_BACK: + cp = ts->face_back; + break; + case TH_FACE_FRONT: + cp = ts->face_front; + break; + case TH_FACE_DOT: + cp = ts->face_dot; + break; + case TH_FACEDOT_SIZE: + cp = &ts->facedot_size; + break; + case TH_DRAWEXTRA_EDGELEN: + cp = ts->extra_edge_len; + break; + case TH_DRAWEXTRA_EDGEANG: + cp = ts->extra_edge_angle; + break; + case TH_DRAWEXTRA_FACEAREA: + cp = ts->extra_face_area; + break; + case TH_DRAWEXTRA_FACEANG: + cp = ts->extra_face_angle; + break; + case TH_NORMAL: + cp = ts->normal; + break; + case TH_VNORMAL: + cp = ts->vertex_normal; + break; + case TH_LNORMAL: + cp = ts->loop_normal; + break; + case TH_BONE_SOLID: + cp = ts->bone_solid; + break; + case TH_BONE_POSE: + cp = ts->bone_pose; + break; + case TH_BONE_POSE_ACTIVE: + cp = ts->bone_pose_active; + break; + case TH_BONE_LOCKED_WEIGHT: + cp = ts->bone_locked_weight; + break; + case TH_STRIP: + cp = ts->strip; + break; + case TH_STRIP_SELECT: + cp = ts->strip_select; + break; + case TH_KEYTYPE_KEYFRAME: + cp = ts->keytype_keyframe; + break; + case TH_KEYTYPE_KEYFRAME_SELECT: + cp = ts->keytype_keyframe_select; + break; + case TH_KEYTYPE_EXTREME: + cp = ts->keytype_extreme; + break; + case TH_KEYTYPE_EXTREME_SELECT: + cp = ts->keytype_extreme_select; + break; + case TH_KEYTYPE_BREAKDOWN: + cp = ts->keytype_breakdown; + break; + case TH_KEYTYPE_BREAKDOWN_SELECT: + cp = ts->keytype_breakdown_select; + break; + case TH_KEYTYPE_JITTER: + cp = ts->keytype_jitter; + break; + case TH_KEYTYPE_JITTER_SELECT: + cp = ts->keytype_jitter_select; + break; + case TH_KEYTYPE_MOVEHOLD: + cp = ts->keytype_movehold; + break; + case TH_KEYTYPE_MOVEHOLD_SELECT: + cp = ts->keytype_movehold_select; + break; + case TH_KEYBORDER: + cp = ts->keyborder; + break; + case TH_KEYBORDER_SELECT: + cp = ts->keyborder_select; + break; + case TH_CFRAME: + cp = ts->cframe; + break; + case TH_TIME_KEYFRAME: + cp = ts->time_keyframe; + break; + case TH_TIME_GP_KEYFRAME: + cp = ts->time_gp_keyframe; + break; + case TH_NURB_ULINE: + cp = ts->nurb_uline; + break; + case TH_NURB_VLINE: + cp = ts->nurb_vline; + break; + case TH_NURB_SEL_ULINE: + cp = ts->nurb_sel_uline; + break; + case TH_NURB_SEL_VLINE: + cp = ts->nurb_sel_vline; + break; + case TH_ACTIVE_SPLINE: + cp = ts->act_spline; + break; + case TH_ACTIVE_VERT: + cp = ts->lastsel_point; + break; + case TH_HANDLE_FREE: + cp = ts->handle_free; + break; + case TH_HANDLE_AUTO: + cp = ts->handle_auto; + break; + case TH_HANDLE_AUTOCLAMP: + cp = ts->handle_auto_clamped; + break; + case TH_HANDLE_VECT: + cp = ts->handle_vect; + break; + case TH_HANDLE_ALIGN: + cp = ts->handle_align; + break; + case TH_HANDLE_SEL_FREE: + cp = ts->handle_sel_free; + break; + case TH_HANDLE_SEL_AUTO: + cp = ts->handle_sel_auto; + break; + case TH_HANDLE_SEL_AUTOCLAMP: + cp = ts->handle_sel_auto_clamped; + break; + case TH_HANDLE_SEL_VECT: + cp = ts->handle_sel_vect; + break; + case TH_HANDLE_SEL_ALIGN: + cp = ts->handle_sel_align; + break; + case TH_FREESTYLE_EDGE_MARK: + cp = ts->freestyle_edge_mark; + break; + case TH_FREESTYLE_FACE_MARK: + cp = ts->freestyle_face_mark; + break; + + case TH_SYNTAX_B: + cp = ts->syntaxb; + break; + case TH_SYNTAX_V: + cp = ts->syntaxv; + break; + case TH_SYNTAX_C: + cp = ts->syntaxc; + break; + case TH_SYNTAX_L: + cp = ts->syntaxl; + break; + case TH_SYNTAX_D: + cp = ts->syntaxd; + break; + case TH_SYNTAX_R: + cp = ts->syntaxr; + break; + case TH_SYNTAX_N: + cp = ts->syntaxn; + break; + case TH_SYNTAX_S: + cp = ts->syntaxs; + break; + case TH_LINENUMBERS: + cp = ts->line_numbers; + break; + + case TH_NODE: + cp = ts->syntaxl; + break; + case TH_NODE_INPUT: + cp = ts->syntaxn; + break; + case TH_NODE_OUTPUT: + cp = ts->nodeclass_output; + break; + case TH_NODE_COLOR: + cp = ts->syntaxb; + break; + case TH_NODE_FILTER: + cp = ts->nodeclass_filter; + break; + case TH_NODE_VECTOR: + cp = ts->nodeclass_vector; + break; + case TH_NODE_TEXTURE: + cp = ts->nodeclass_texture; + break; + case TH_NODE_PATTERN: + cp = ts->nodeclass_pattern; + break; + case TH_NODE_SCRIPT: + cp = ts->nodeclass_script; + break; + case TH_NODE_LAYOUT: + cp = ts->nodeclass_layout; + break; + case TH_NODE_GEOMETRY: + cp = ts->nodeclass_geometry; + break; + case TH_NODE_ATTRIBUTE: + cp = ts->nodeclass_attribute; + break; + case TH_NODE_SHADER: + cp = ts->nodeclass_shader; + break; + case TH_NODE_CONVERTOR: + cp = ts->syntaxv; + break; + case TH_NODE_GROUP: + cp = ts->syntaxc; + break; + case TH_NODE_INTERFACE: + cp = ts->console_output; + break; + case TH_NODE_FRAME: + cp = ts->movie; + break; + case TH_NODE_MATTE: + cp = ts->syntaxs; + break; + case TH_NODE_DISTORT: + cp = ts->syntaxd; + break; + case TH_NODE_CURVING: + cp = &ts->noodle_curving; + break; + case TH_NODE_GRID_LEVELS: + cp = &ts->grid_levels; + break; + + case TH_SEQ_MOVIE: + cp = ts->movie; + break; + case TH_SEQ_MOVIECLIP: + cp = ts->movieclip; + break; + case TH_SEQ_MASK: + cp = ts->mask; + break; + case TH_SEQ_IMAGE: + cp = ts->image; + break; + case TH_SEQ_SCENE: + cp = ts->scene; + break; + case TH_SEQ_AUDIO: + cp = ts->audio; + break; + case TH_SEQ_EFFECT: + cp = ts->effect; + break; + case TH_SEQ_META: + cp = ts->meta; + break; + case TH_SEQ_TEXT: + cp = ts->text_strip; + break; + case TH_SEQ_PREVIEW: + cp = ts->preview_back; + break; + case TH_SEQ_COLOR: + cp = ts->color_strip; + break; + case TH_SEQ_ACTIVE: + cp = ts->active_strip; + break; + case TH_SEQ_SELECTED: + cp = ts->selected_strip; + break; + + case TH_CONSOLE_OUTPUT: + cp = ts->console_output; + break; + case TH_CONSOLE_INPUT: + cp = ts->console_input; + break; + case TH_CONSOLE_INFO: + cp = ts->console_info; + break; + case TH_CONSOLE_ERROR: + cp = ts->console_error; + break; + case TH_CONSOLE_CURSOR: + cp = ts->console_cursor; + break; + case TH_CONSOLE_SELECT: + cp = ts->console_select; + break; + + case TH_HANDLE_VERTEX: + cp = ts->handle_vertex; + break; + case TH_HANDLE_VERTEX_SELECT: + cp = ts->handle_vertex_select; + break; + case TH_HANDLE_VERTEX_SIZE: + cp = &ts->handle_vertex_size; + break; + + case TH_GP_VERTEX: + cp = ts->gp_vertex; + break; + case TH_GP_VERTEX_SELECT: + cp = ts->gp_vertex_select; + break; + case TH_GP_VERTEX_SIZE: + cp = &ts->gp_vertex_size; + break; + + case TH_DOPESHEET_CHANNELOB: + cp = ts->ds_channel; + break; + case TH_DOPESHEET_CHANNELSUBOB: + cp = ts->ds_subchannel; + break; + case TH_DOPESHEET_IPOLINE: + cp = ts->ds_ipoline; + break; + + case TH_PREVIEW_BACK: + cp = ts->preview_back; + break; + + case TH_STITCH_PREVIEW_FACE: + cp = ts->preview_stitch_face; + break; + + case TH_STITCH_PREVIEW_EDGE: + cp = ts->preview_stitch_edge; + break; + + case TH_STITCH_PREVIEW_VERT: + cp = ts->preview_stitch_vert; + break; + + case TH_STITCH_PREVIEW_STITCHABLE: + cp = ts->preview_stitch_stitchable; + break; + + case TH_STITCH_PREVIEW_UNSTITCHABLE: + cp = ts->preview_stitch_unstitchable; + break; + case TH_STITCH_PREVIEW_ACTIVE: + cp = ts->preview_stitch_active; + break; + + case TH_PAINT_CURVE_HANDLE: + cp = ts->paint_curve_handle; + break; + case TH_PAINT_CURVE_PIVOT: + cp = ts->paint_curve_pivot; + break; + + case TH_METADATA_BG: + cp = ts->metadatabg; + break; + case TH_METADATA_TEXT: + cp = ts->metadatatext; + break; + + case TH_UV_SHADOW: + cp = ts->uv_shadow; + break; + + case TH_MARKER_OUTLINE: + cp = ts->marker_outline; + break; + case TH_MARKER: + cp = ts->marker; + break; + case TH_ACT_MARKER: + cp = ts->act_marker; + break; + case TH_SEL_MARKER: + cp = ts->sel_marker; + break; + case TH_BUNDLE_SOLID: + cp = ts->bundle_solid; + break; + case TH_DIS_MARKER: + cp = ts->dis_marker; + break; + case TH_PATH_BEFORE: + cp = ts->path_before; + break; + case TH_PATH_AFTER: + cp = ts->path_after; + break; + case TH_PATH_KEYFRAME_BEFORE: + cp = ts->path_keyframe_before; + break; + case TH_PATH_KEYFRAME_AFTER: + cp = ts->path_keyframe_after; + break; + case TH_CAMERA_PATH: + cp = ts->camera_path; + break; + case TH_LOCK_MARKER: + cp = ts->lock_marker; + break; + + case TH_MATCH: + cp = ts->match; + break; + + case TH_SELECT_HIGHLIGHT: + cp = ts->selected_highlight; + break; + + case TH_SELECT_ACTIVE: + cp = ts->active; + break; + + case TH_SELECTED_OBJECT: + cp = ts->selected_object; + break; + + case TH_ACTIVE_OBJECT: + cp = ts->active_object; + break; + + case TH_EDITED_OBJECT: + cp = ts->edited_object; + break; + + case TH_ROW_ALTERNATE: + cp = ts->row_alternate; + break; + + case TH_SKIN_ROOT: + cp = ts->skin_root; + break; + + case TH_ANIM_ACTIVE: + cp = ts->anim_active; + break; + case TH_ANIM_INACTIVE: + cp = ts->anim_non_active; + break; + case TH_ANIM_PREVIEW_RANGE: + cp = ts->anim_preview_range; + break; + + case TH_NLA_TWEAK: + cp = ts->nla_tweaking; + break; + case TH_NLA_TWEAK_DUPLI: + cp = ts->nla_tweakdupli; + break; + + case TH_NLA_TRACK: + cp = ts->nla_track; + break; + case TH_NLA_TRANSITION: + cp = ts->nla_transition; + break; + case TH_NLA_TRANSITION_SEL: + cp = ts->nla_transition_sel; + break; + case TH_NLA_META: + cp = ts->nla_meta; + break; + case TH_NLA_META_SEL: + cp = ts->nla_meta_sel; + break; + case TH_NLA_SOUND: + cp = ts->nla_sound; + break; + case TH_NLA_SOUND_SEL: + cp = ts->nla_sound_sel; + break; + + case TH_WIDGET_EMBOSS: + cp = btheme->tui.widget_emboss; + break; + + case TH_EDITOR_OUTLINE: + cp = btheme->tui.editor_outline; + break; + case TH_WIDGET_TEXT_CURSOR: + cp = btheme->tui.widget_text_cursor; + break; + + case TH_TRANSPARENT_CHECKER_PRIMARY: + cp = btheme->tui.transparent_checker_primary; + break; + case TH_TRANSPARENT_CHECKER_SECONDARY: + cp = btheme->tui.transparent_checker_secondary; + break; + case TH_TRANSPARENT_CHECKER_SIZE: + cp = &btheme->tui.transparent_checker_size; + break; + + case TH_AXIS_X: + cp = btheme->tui.xaxis; + break; + case TH_AXIS_Y: + cp = btheme->tui.yaxis; + break; + case TH_AXIS_Z: + cp = btheme->tui.zaxis; + break; + + case TH_GIZMO_HI: + cp = btheme->tui.gizmo_hi; + break; + case TH_GIZMO_PRIMARY: + cp = btheme->tui.gizmo_primary; + break; + case TH_GIZMO_SECONDARY: + cp = btheme->tui.gizmo_secondary; + break; + case TH_GIZMO_VIEW_ALIGN: + cp = btheme->tui.gizmo_view_align; + break; + case TH_GIZMO_A: + cp = btheme->tui.gizmo_a; + break; + case TH_GIZMO_B: + cp = btheme->tui.gizmo_b; + break; + + case TH_ICON_SCENE: + cp = btheme->tui.icon_scene; + break; + case TH_ICON_COLLECTION: + cp = btheme->tui.icon_collection; + break; + case TH_ICON_OBJECT: + cp = btheme->tui.icon_object; + break; + case TH_ICON_OBJECT_DATA: + cp = btheme->tui.icon_object_data; + break; + case TH_ICON_MODIFIER: + cp = btheme->tui.icon_modifier; + break; + case TH_ICON_SHADING: + cp = btheme->tui.icon_shading; + break; + case TH_ICON_FOLDER: + cp = btheme->tui.icon_folder; + break; + case TH_ICON_FUND: { + /* Development fund icon color is not part of theme. */ + static const uchar red[4] = {204, 48, 72, 255}; + cp = red; + break; + } + + case TH_SCROLL_TEXT: + cp = btheme->tui.wcol_scroll.text; + break; + + case TH_INFO_SELECTED: + cp = ts->info_selected; + break; + case TH_INFO_SELECTED_TEXT: + cp = ts->info_selected_text; + break; + case TH_INFO_ERROR: + cp = ts->info_error; + break; + case TH_INFO_ERROR_TEXT: + cp = ts->info_error_text; + break; + case TH_INFO_WARNING: + cp = ts->info_warning; + break; + case TH_INFO_WARNING_TEXT: + cp = ts->info_warning_text; + break; + case TH_INFO_INFO: + cp = ts->info_info; + break; + case TH_INFO_INFO_TEXT: + cp = ts->info_info_text; + break; + case TH_INFO_DEBUG: + cp = ts->info_debug; + break; + case TH_INFO_DEBUG_TEXT: + cp = ts->info_debug_text; + break; + case TH_INFO_PROPERTY: + cp = ts->info_property; + break; + case TH_INFO_PROPERTY_TEXT: + cp = ts->info_property_text; + break; + case TH_INFO_OPERATOR: + cp = ts->info_operator; + break; + case TH_INFO_OPERATOR_TEXT: + cp = ts->info_operator_text; + break; + case TH_V3D_CLIPPING_BORDER: + cp = ts->clipping_border_3d; + break; + } + } + } + + return (const uchar *)cp; +} + +/** + * Initialize default theme. + * + * \note When you add new colors, created & saved themes need initialized + * use function below, #init_userdef_do_versions. + */ +void UI_theme_init_default(void) +{ + /* we search for the theme with name Default */ + bTheme *btheme = (bTheme *)BLI_findstring(&U.themes, "Default", offsetof(bTheme, name)); + if (btheme == NULL) { + btheme = (bTheme *)MEM_callocN(sizeof(bTheme), __func__); + BLI_addtail(&U.themes, btheme); + } + + UI_SetTheme(0, 0); /* make sure the global used in this file is set */ + + const int active_theme_area = btheme->active_theme_area; + memcpy(btheme, &U_theme_default, sizeof(*btheme)); + btheme->active_theme_area = active_theme_area; +} + +void UI_style_init_default(void) +{ + BLI_freelistN(&U.uistyles); + /* gets automatically re-allocated */ + uiStyleInit(); +} + +void UI_SetTheme(int spacetype, int regionid) +{ + if (spacetype) { + /* later on, a local theme can be found too */ + theme_active = (bTheme *)U.themes.first; + theme_spacetype = spacetype; + theme_regionid = regionid; + } + else if (regionid) { + /* popups */ + theme_active = (bTheme *)U.themes.first; + theme_spacetype = SPACE_PROPERTIES; + theme_regionid = regionid; + } + else { + /* for safety, when theme was deleted */ + theme_active = (bTheme *)U.themes.first; + theme_spacetype = SPACE_VIEW3D; + theme_regionid = RGN_TYPE_WINDOW; + } +} + +bTheme *UI_GetTheme() +{ + return (bTheme *)U.themes.first; +} + +/** + * for the rare case we need to temp swap in a different theme (offscreen render) + */ +void UI_Theme_Store(struct bThemeState *theme_state) +{ + *theme_state = g_theme_state; +} +void UI_Theme_Restore(struct bThemeState *theme_state) +{ + g_theme_state = *theme_state; +} + +void UI_GetThemeColorShadeAlpha4ubv(int colorid, int coloffset, int alphaoffset, uchar col[4]) +{ + int r, g, b, a; + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + r = coloffset + (int)cp[0]; + CLAMP(r, 0, 255); + g = coloffset + (int)cp[1]; + CLAMP(g, 0, 255); + b = coloffset + (int)cp[2]; + CLAMP(b, 0, 255); + a = alphaoffset + (int)cp[3]; + CLAMP(a, 0, 255); + + col[0] = r; + col[1] = g; + col[2] = b; + col[3] = a; +} + +void UI_GetThemeColorBlend3ubv(int colorid1, int colorid2, float fac, uchar col[3]) +{ + const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + CLAMP(fac, 0.0f, 1.0f); + col[0] = floorf((1.0f - fac) * cp1[0] + fac * cp2[0]); + col[1] = floorf((1.0f - fac) * cp1[1] + fac * cp2[1]); + col[2] = floorf((1.0f - fac) * cp1[2] + fac * cp2[2]); +} + +void UI_GetThemeColorBlend3f(int colorid1, int colorid2, float fac, float r_col[3]) +{ + const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + CLAMP(fac, 0.0f, 1.0f); + r_col[0] = ((1.0f - fac) * cp1[0] + fac * cp2[0]) / 255.0f; + r_col[1] = ((1.0f - fac) * cp1[1] + fac * cp2[1]) / 255.0f; + r_col[2] = ((1.0f - fac) * cp1[2] + fac * cp2[2]) / 255.0f; +} + +void UI_GetThemeColorBlend4f(int colorid1, int colorid2, float fac, float r_col[4]) +{ + const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + CLAMP(fac, 0.0f, 1.0f); + r_col[0] = ((1.0f - fac) * cp1[0] + fac * cp2[0]) / 255.0f; + r_col[1] = ((1.0f - fac) * cp1[1] + fac * cp2[1]) / 255.0f; + r_col[2] = ((1.0f - fac) * cp1[2] + fac * cp2[2]) / 255.0f; + r_col[3] = ((1.0f - fac) * cp1[3] + fac * cp2[3]) / 255.0f; +} + +void UI_FontThemeColor(int fontid, int colorid) +{ + uchar color[4]; + UI_GetThemeColor4ubv(colorid, color); + BLF_color4ubv(fontid, color); +} + +/* get individual values, not scaled */ +float UI_GetThemeValuef(int colorid) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + return ((float)cp[0]); +} + +/* get individual values, not scaled */ +int UI_GetThemeValue(int colorid) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + return ((int)cp[0]); +} + +/* versions of the function above, which take a space-type */ +float UI_GetThemeValueTypef(int colorid, int spacetype) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + return ((float)cp[0]); +} + +int UI_GetThemeValueType(int colorid, int spacetype) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + return ((int)cp[0]); +} + +/* get the color, range 0.0-1.0 */ +void UI_GetThemeColor3fv(int colorid, float col[3]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0] = ((float)cp[0]) / 255.0f; + col[1] = ((float)cp[1]) / 255.0f; + col[2] = ((float)cp[2]) / 255.0f; +} + +void UI_GetThemeColor4fv(int colorid, float col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0] = ((float)cp[0]) / 255.0f; + col[1] = ((float)cp[1]) / 255.0f; + col[2] = ((float)cp[2]) / 255.0f; + col[3] = ((float)cp[3]) / 255.0f; +} + +void UI_GetThemeColorType4fv(int colorid, int spacetype, float col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + col[0] = ((float)cp[0]) / 255.0f; + col[1] = ((float)cp[1]) / 255.0f; + col[2] = ((float)cp[2]) / 255.0f; + col[3] = ((float)cp[3]) / 255.0f; +} + +/* get the color, range 0.0-1.0, complete with shading offset */ +void UI_GetThemeColorShade3fv(int colorid, int offset, float col[3]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + int r, g, b; + + r = offset + (int)cp[0]; + CLAMP(r, 0, 255); + g = offset + (int)cp[1]; + CLAMP(g, 0, 255); + b = offset + (int)cp[2]; + CLAMP(b, 0, 255); + + col[0] = ((float)r) / 255.0f; + col[1] = ((float)g) / 255.0f; + col[2] = ((float)b) / 255.0f; +} + +void UI_GetThemeColorShade3ubv(int colorid, int offset, uchar col[3]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + int r, g, b; + + r = offset + (int)cp[0]; + CLAMP(r, 0, 255); + g = offset + (int)cp[1]; + CLAMP(g, 0, 255); + b = offset + (int)cp[2]; + CLAMP(b, 0, 255); + + col[0] = r; + col[1] = g; + col[2] = b; +} + +void UI_GetThemeColorBlendShade3ubv( + int colorid1, int colorid2, float fac, int offset, uchar col[3]) +{ + const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + + CLAMP(fac, 0.0f, 1.0f); + + float blend[3]; + blend[0] = (offset + floorf((1.0f - fac) * cp1[0] + fac * cp2[0])) / 255.0f; + blend[1] = (offset + floorf((1.0f - fac) * cp1[1] + fac * cp2[1])) / 255.0f; + blend[2] = (offset + floorf((1.0f - fac) * cp1[2] + fac * cp2[2])) / 255.0f; + + unit_float_to_uchar_clamp_v3(col, blend); +} + +void UI_GetThemeColorShade4ubv(int colorid, int offset, uchar col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + int r, g, b; + + r = offset + (int)cp[0]; + CLAMP(r, 0, 255); + g = offset + (int)cp[1]; + CLAMP(g, 0, 255); + b = offset + (int)cp[2]; + CLAMP(b, 0, 255); + + col[0] = r; + col[1] = g; + col[2] = b; + col[3] = cp[3]; +} + +void UI_GetThemeColorShadeAlpha4fv(int colorid, int coloffset, int alphaoffset, float col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + int r, g, b, a; + + r = coloffset + (int)cp[0]; + CLAMP(r, 0, 255); + g = coloffset + (int)cp[1]; + CLAMP(g, 0, 255); + b = coloffset + (int)cp[2]; + CLAMP(b, 0, 255); + a = alphaoffset + (int)cp[3]; + CLAMP(a, 0, 255); + + col[0] = ((float)r) / 255.0f; + col[1] = ((float)g) / 255.0f; + col[2] = ((float)b) / 255.0f; + col[3] = ((float)a) / 255.0f; +} + +void UI_GetThemeColorBlendShade3fv(int colorid1, int colorid2, float fac, int offset, float col[3]) +{ + const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + int r, g, b; + + CLAMP(fac, 0.0f, 1.0f); + + r = offset + floorf((1.0f - fac) * cp1[0] + fac * cp2[0]); + CLAMP(r, 0, 255); + g = offset + floorf((1.0f - fac) * cp1[1] + fac * cp2[1]); + CLAMP(g, 0, 255); + b = offset + floorf((1.0f - fac) * cp1[2] + fac * cp2[2]); + CLAMP(b, 0, 255); + + col[0] = ((float)r) / 255.0f; + col[1] = ((float)g) / 255.0f; + col[2] = ((float)b) / 255.0f; +} + +void UI_GetThemeColorBlendShade4fv(int colorid1, int colorid2, float fac, int offset, float col[4]) +{ + const uchar *cp1 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid1); + const uchar *cp2 = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid2); + int r, g, b, a; + + CLAMP(fac, 0.0f, 1.0f); + + r = offset + floorf((1.0f - fac) * cp1[0] + fac * cp2[0]); + CLAMP(r, 0, 255); + g = offset + floorf((1.0f - fac) * cp1[1] + fac * cp2[1]); + CLAMP(g, 0, 255); + b = offset + floorf((1.0f - fac) * cp1[2] + fac * cp2[2]); + CLAMP(b, 0, 255); + a = offset + floorf((1.0f - fac) * cp1[3] + fac * cp2[3]); + CLAMP(a, 0, 255); + + col[0] = ((float)r) / 255.0f; + col[1] = ((float)g) / 255.0f; + col[2] = ((float)b) / 255.0f; + col[3] = ((float)a) / 255.0f; +} + +/* get the color, in char pointer */ +void UI_GetThemeColor3ubv(int colorid, uchar col[3]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0] = cp[0]; + col[1] = cp[1]; + col[2] = cp[2]; +} + +/* get the color, range 0.0-1.0, complete with shading offset */ +void UI_GetThemeColorShade4fv(int colorid, int offset, float col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + int r, g, b, a; + + r = offset + (int)cp[0]; + CLAMP(r, 0, 255); + g = offset + (int)cp[1]; + CLAMP(g, 0, 255); + b = offset + (int)cp[2]; + CLAMP(b, 0, 255); + + a = (int)cp[3]; /* no shading offset... */ + CLAMP(a, 0, 255); + + col[0] = ((float)r) / 255.0f; + col[1] = ((float)g) / 255.0f; + col[2] = ((float)b) / 255.0f; + col[3] = ((float)a) / 255.0f; +} + +/* get the color, in char pointer */ +void UI_GetThemeColor4ubv(int colorid, uchar col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0] = cp[0]; + col[1] = cp[1]; + col[2] = cp[2]; + col[3] = cp[3]; +} + +void UI_GetThemeColorType3fv(int colorid, int spacetype, float col[3]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + col[0] = ((float)cp[0]) / 255.0f; + col[1] = ((float)cp[1]) / 255.0f; + col[2] = ((float)cp[2]) / 255.0f; +} + +void UI_GetThemeColorType3ubv(int colorid, int spacetype, uchar col[3]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + col[0] = cp[0]; + col[1] = cp[1]; + col[2] = cp[2]; +} + +void UI_GetThemeColorType4ubv(int colorid, int spacetype, uchar col[4]) +{ + const uchar *cp = UI_ThemeGetColorPtr(theme_active, spacetype, colorid); + col[0] = cp[0]; + col[1] = cp[1]; + col[2] = cp[2]; + col[3] = cp[3]; +} + +bool UI_GetIconThemeColor4ubv(int colorid, uchar col[4]) +{ + if (colorid == 0) { + return false; + } + if (colorid == TH_ICON_FUND) { + /* Always color development fund icon. */ + } + else if (!((theme_spacetype == SPACE_OUTLINER && theme_regionid == RGN_TYPE_WINDOW) || + (theme_spacetype == SPACE_PROPERTIES && theme_regionid == RGN_TYPE_NAV_BAR) || + (theme_spacetype == SPACE_FILE && theme_regionid == RGN_TYPE_WINDOW))) { + /* Only colored icons in specific places, overall UI is intended + * to stay monochrome and out of the way except a few places where it + * is important to communicate different data types. */ + return false; + } + + const uchar *cp = UI_ThemeGetColorPtr(theme_active, theme_spacetype, colorid); + col[0] = cp[0]; + col[1] = cp[1]; + col[2] = cp[2]; + col[3] = cp[3]; + + return true; +} + +void UI_GetColorPtrShade3ubv(const uchar cp[3], uchar col[3], int offset) +{ + int r, g, b; + + r = offset + (int)cp[0]; + g = offset + (int)cp[1]; + b = offset + (int)cp[2]; + + CLAMP(r, 0, 255); + CLAMP(g, 0, 255); + CLAMP(b, 0, 255); + + col[0] = r; + col[1] = g; + col[2] = b; +} + +/* get a 3 byte color, blended and shaded between two other char color pointers */ +void UI_GetColorPtrBlendShade3ubv( + const uchar cp1[3], const uchar cp2[3], uchar col[3], float fac, int offset) +{ + int r, g, b; + + CLAMP(fac, 0.0f, 1.0f); + r = offset + floor((1.0f - fac) * cp1[0] + fac * cp2[0]); + g = offset + floor((1.0f - fac) * cp1[1] + fac * cp2[1]); + b = offset + floor((1.0f - fac) * cp1[2] + fac * cp2[2]); + + CLAMP(r, 0, 255); + CLAMP(g, 0, 255); + CLAMP(b, 0, 255); + + col[0] = r; + col[1] = g; + col[2] = b; +} + +void UI_ThemeClearColor(int colorid) +{ + float col[3]; + + UI_GetThemeColor3fv(colorid, col); + GPU_clear_color(col[0], col[1], col[2], 1.0f); +} + +int UI_ThemeMenuShadowWidth(void) +{ + bTheme *btheme = UI_GetTheme(); + return (int)(btheme->tui.menu_shadow_width * UI_DPI_FAC); +} + +void UI_make_axis_color(const uchar src_col[3], uchar dst_col[3], const char axis) +{ + uchar col[3]; + + switch (axis) { + case 'X': + UI_GetThemeColor3ubv(TH_AXIS_X, col); + UI_GetColorPtrBlendShade3ubv(src_col, col, dst_col, 0.5f, -10); + break; + case 'Y': + UI_GetThemeColor3ubv(TH_AXIS_Y, col); + UI_GetColorPtrBlendShade3ubv(src_col, col, dst_col, 0.5f, -10); + break; + case 'Z': + UI_GetThemeColor3ubv(TH_AXIS_Z, col); + UI_GetColorPtrBlendShade3ubv(src_col, col, dst_col, 0.5f, -10); + break; + default: + BLI_assert(0); + break; + } +} -- cgit v1.2.3