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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/interface')
-rw-r--r--source/blender/editors/interface/CMakeLists.txt11
-rw-r--r--source/blender/editors/interface/interface.c396
-rw-r--r--source/blender/editors/interface/interface_align.c2
-rw-r--r--source/blender/editors/interface/interface_button_group.c2
-rw-r--r--source/blender/editors/interface/interface_context_menu.c115
-rw-r--r--source/blender/editors/interface/interface_context_path.cc85
-rw-r--r--source/blender/editors/interface/interface_draw.c174
-rw-r--r--source/blender/editors/interface/interface_dropboxes.cc95
-rw-r--r--source/blender/editors/interface/interface_eyedropper.c57
-rw-r--r--source/blender/editors/interface/interface_eyedropper_color.c48
-rw-r--r--source/blender/editors/interface/interface_eyedropper_colorband.c12
-rw-r--r--source/blender/editors/interface/interface_eyedropper_datablock.c48
-rw-r--r--source/blender/editors/interface/interface_eyedropper_depth.c17
-rw-r--r--source/blender/editors/interface/interface_eyedropper_driver.c5
-rw-r--r--source/blender/editors/interface/interface_eyedropper_gpencil_color.c4
-rw-r--r--source/blender/editors/interface/interface_eyedropper_intern.h9
-rw-r--r--source/blender/editors/interface/interface_handlers.c1150
-rw-r--r--source/blender/editors/interface/interface_icons.c206
-rw-r--r--source/blender/editors/interface/interface_intern.h135
-rw-r--r--source/blender/editors/interface/interface_layout.c254
-rw-r--r--source/blender/editors/interface/interface_ops.c348
-rw-r--r--source/blender/editors/interface/interface_panel.c221
-rw-r--r--source/blender/editors/interface/interface_query.c196
-rw-r--r--source/blender/editors/interface/interface_region_color_picker.c2
-rw-r--r--source/blender/editors/interface/interface_region_menu_pie.c10
-rw-r--r--source/blender/editors/interface/interface_region_menu_popup.c19
-rw-r--r--source/blender/editors/interface/interface_region_popover.c6
-rw-r--r--source/blender/editors/interface/interface_region_popup.c10
-rw-r--r--source/blender/editors/interface/interface_region_search.c78
-rw-r--r--source/blender/editors/interface/interface_region_tooltip.c115
-rw-r--r--source/blender/editors/interface/interface_style.c81
-rw-r--r--source/blender/editors/interface/interface_template_asset_view.cc292
-rw-r--r--source/blender/editors/interface/interface_template_attribute_search.cc126
-rw-r--r--source/blender/editors/interface/interface_template_list.cc1333
-rw-r--r--source/blender/editors/interface/interface_template_search_menu.c70
-rw-r--r--source/blender/editors/interface/interface_templates.c1109
-rw-r--r--source/blender/editors/interface/interface_utils.c102
-rw-r--r--source/blender/editors/interface/interface_view.cc186
-rw-r--r--source/blender/editors/interface/interface_widgets.c388
-rw-r--r--source/blender/editors/interface/resources.c23
-rw-r--r--source/blender/editors/interface/tree_view.cc790
-rw-r--r--source/blender/editors/interface/view2d.c217
-rw-r--r--source/blender/editors/interface/view2d_draw.c84
-rw-r--r--source/blender/editors/interface/view2d_edge_pan.c85
-rw-r--r--source/blender/editors/interface/view2d_gizmo_navigate.c15
-rw-r--r--source/blender/editors/interface/view2d_ops.c84
46 files changed, 6213 insertions, 2602 deletions
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt
index 5011a50ed73..84172c7efce 100644
--- a/source/blender/editors/interface/CMakeLists.txt
+++ b/source/blender/editors/interface/CMakeLists.txt
@@ -25,9 +25,11 @@ set(INC
../../depsgraph
../../draw
../../gpu
+ ../../functions
../../imbuf
../../makesdna
../../makesrna
+ ../../nodes
../../python
../../render
../../windowmanager
@@ -41,7 +43,9 @@ set(SRC
interface_anim.c
interface_button_group.c
interface_context_menu.c
+ interface_context_path.cc
interface_draw.c
+ interface_dropboxes.cc
interface_eyedropper.c
interface_eyedropper_color.c
interface_eyedropper_colorband.c
@@ -66,13 +70,18 @@ set(SRC
interface_region_tooltip.c
interface_regions.c
interface_style.c
+ interface_template_asset_view.cc
+ interface_template_list.cc
+ interface_template_attribute_search.cc
interface_template_search_menu.c
interface_template_search_operator.c
interface_templates.c
interface_undo.c
interface_utils.c
+ interface_view.cc
interface_widgets.c
resources.c
+ tree_view.cc
view2d.c
view2d_draw.c
view2d_edge_pan.c
@@ -102,7 +111,7 @@ if(WITH_PYTHON)
add_definitions(-DWITH_PYTHON)
endif()
-if(WIN32)
+if(WIN32 OR APPLE)
if(WITH_INPUT_IME)
add_definitions(-DWITH_INPUT_IME)
endif()
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index d2204c17e62..82ea218baba 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -57,6 +57,8 @@
#include "BKE_screen.h"
#include "BKE_unit.h"
+#include "ED_asset.h"
+
#include "GPU_matrix.h"
#include "GPU_state.h"
@@ -131,12 +133,10 @@ static bool ui_but_is_unit_radians(const uiBut *but)
/* ************* window matrix ************** */
-void ui_block_to_window_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y)
+void ui_block_to_region_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y)
{
const int getsizex = BLI_rcti_size_x(&region->winrct) + 1;
const int getsizey = BLI_rcti_size_y(&region->winrct) + 1;
- const int sx = region->winrct.xmin;
- const int sy = region->winrct.ymin;
float gx = *r_x;
float gy = *r_y;
@@ -146,14 +146,19 @@ void ui_block_to_window_fl(const ARegion *region, uiBlock *block, float *r_x, fl
gy += block->panel->ofsy;
}
- *r_x = ((float)sx) +
- ((float)getsizex) * (0.5f + 0.5f * (gx * block->winmat[0][0] + gy * block->winmat[1][0] +
+ *r_x = ((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] +
+ *r_y = ((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_fl(const ARegion *region, uiBlock *block, float *r_x, float *r_y)
+{
+ ui_block_to_region_fl(region, block, r_x, r_y);
+ *r_x += region->winrct.xmin;
+ *r_y += region->winrct.ymin;
+}
+
void ui_block_to_window(const ARegion *region, uiBlock *block, int *r_x, int *r_y)
{
float fx = *r_x;
@@ -165,6 +170,16 @@ void ui_block_to_window(const ARegion *region, uiBlock *block, int *r_x, int *r_
*r_y = (int)(fy + 0.5f);
}
+void ui_block_to_region_rctf(const ARegion *region,
+ uiBlock *block,
+ rctf *rct_dst,
+ const rctf *rct_src)
+{
+ *rct_dst = *rct_src;
+ ui_block_to_region_fl(region, block, &rct_dst->xmin, &rct_dst->ymin);
+ ui_block_to_region_fl(region, block, &rct_dst->xmax, &rct_dst->ymax);
+}
+
void ui_block_to_window_rctf(const ARegion *region,
uiBlock *block,
rctf *rct_dst,
@@ -249,6 +264,14 @@ void ui_window_to_region_rcti(const ARegion *region, rcti *rect_dst, const rcti
rect_dst->ymax = rct_src->ymax - region->winrct.ymin;
}
+void ui_window_to_region_rctf(const ARegion *region, rctf *rect_dst, const rctf *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;
@@ -476,7 +499,7 @@ void ui_block_bounds_calc(uiBlock *block)
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
+ /* 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);
@@ -587,7 +610,7 @@ void UI_block_bounds_set_normal(uiBlock *block, int addval)
block->bounds_type = UI_BLOCK_BOUNDS;
}
-/* used for pulldowns */
+/* Used for pull-downs. */
void UI_block_bounds_set_text(uiBlock *block, int addval)
{
block->bounds = addval;
@@ -719,6 +742,18 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
if (but->optype != oldbut->optype) {
return false;
}
+ if (but->dragtype != oldbut->dragtype) {
+ return false;
+ }
+
+ if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) {
+ uiButTreeRow *but_treerow = (uiButTreeRow *)but;
+ uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut;
+ if (!but_treerow->tree_item || !oldbut_treerow->tree_item ||
+ !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) {
+ return false;
+ }
+ }
return true;
}
@@ -811,7 +846,8 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
/* Move tooltip from new to old. */
SWAP(uiButToolTipFunc, oldbut->tip_func, but->tip_func);
- SWAP(void *, oldbut->tip_argN, but->tip_argN);
+ SWAP(void *, oldbut->tip_arg, but->tip_arg);
+ SWAP(uiFreeArgFunc, oldbut->tip_arg_free, but->tip_arg_free);
oldbut->flag = (oldbut->flag & ~flag_copy) | (but->flag & flag_copy);
oldbut->drawflag = (oldbut->drawflag & ~drawflag_copy) | (but->drawflag & drawflag_copy);
@@ -822,7 +858,7 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
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(uiFreeArgFunc, search_oldbut->arg_free_fn, search_but->arg_free_fn);
SWAP(void *, search_oldbut->arg, search_but->arg);
}
@@ -832,10 +868,21 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
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;
+ switch (oldbut->type) {
+ case UI_BTYPE_PROGRESS_BAR: {
+ uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut;
+ uiButProgressbar *progress_but = (uiButProgressbar *)but;
+ progress_oldbut->progress = progress_but->progress;
+ break;
+ }
+ case UI_BTYPE_TREEROW: {
+ uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut;
+ uiButTreeRow *treerow_newbut = (uiButTreeRow *)but;
+ SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item);
+ break;
+ }
+ default:
+ break;
}
/* move/copy string from the new button to the old */
@@ -861,7 +908,7 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
SWAP(void *, but->dragpoin, oldbut->dragpoin);
}
- /* note: if layout hasn't been applied yet, it uses old button pointers... */
+ /* NOTE: if layout hasn't been applied yet, it uses old button pointers... */
}
/**
@@ -921,7 +968,13 @@ static bool ui_but_update_from_old_block(const bContext *C,
found_active = true;
}
else {
- const int flag_copy = UI_BUT_DRAG_MULTI;
+ int flag_copy = UI_BUT_DRAG_MULTI;
+
+ /* Stupid special case: The active button may be inside (as in, overlapped on top) a tree-row
+ * button which we also want to keep highlighted then. */
+ if (but->type == UI_BTYPE_TREEROW) {
+ flag_copy |= UI_ACTIVE;
+ }
but->flag = (but->flag & ~flag_copy) | (oldbut->flag & flag_copy);
@@ -970,6 +1023,9 @@ bool UI_but_active_only_ex(
else if ((found == true) && (isactive == false)) {
if (remove_on_failure) {
BLI_remlink(&block->buttons, but);
+ if (but->layout) {
+ ui_layout_remove_but(but->layout, but);
+ }
ui_but_free(C, but);
}
return false;
@@ -1171,16 +1227,21 @@ void ui_but_add_shortcut(uiBut *but, const char *shortcut_str, const bool do_str
* \{ */
static bool ui_but_event_operator_string_from_operator(const bContext *C,
- uiBut *but,
+ wmOperatorCallParams *op_call_params,
char *buf,
const size_t buf_len)
{
- BLI_assert(but->optype != NULL);
+ BLI_assert(op_call_params->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)) {
+ IDProperty *prop = (op_call_params->opptr) ? op_call_params->opptr->data : NULL;
+
+ if (WM_key_event_operator_string(C,
+ op_call_params->optype->idname,
+ op_call_params->opcontext,
+ prop,
+ true,
+ buf,
+ buf_len)) {
found = true;
}
return found;
@@ -1265,7 +1326,12 @@ static bool ui_but_event_operator_string(const bContext *C,
bool found = false;
if (but->optype != NULL) {
- found = ui_but_event_operator_string_from_operator(C, but, buf, buf_len);
+ found = ui_but_event_operator_string_from_operator(
+ C,
+ &(wmOperatorCallParams){
+ .optype = but->optype, .opptr = but->opptr, .opcontext = but->opcontext},
+ buf,
+ buf_len);
}
else if (UI_but_menutype_get(but) != NULL) {
found = ui_but_event_operator_string_from_menu(C, but, buf, buf_len);
@@ -1277,6 +1343,20 @@ static bool ui_but_event_operator_string(const bContext *C,
return found;
}
+static bool ui_but_extra_icon_event_operator_string(const bContext *C,
+ uiButExtraOpIcon *extra_icon,
+ char *buf,
+ const size_t buf_len)
+{
+ wmOperatorType *extra_icon_optype = UI_but_extra_operator_icon_optype_get(extra_icon);
+
+ if (extra_icon_optype) {
+ return ui_but_event_operator_string_from_operator(C, extra_icon->optype_params, buf, buf_len);
+ }
+
+ return false;
+}
+
static bool ui_but_event_property_operator_string(const bContext *C,
uiBut *but,
char *buf,
@@ -1315,6 +1395,7 @@ static bool ui_but_event_property_operator_string(const bContext *C,
const char *prop_enum_value_id = "value";
PointerRNA *ptr = &but->rnapoin;
PropertyRNA *prop = but->rnaprop;
+ int prop_index = but->rnaindex;
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) &&
@@ -1339,28 +1420,15 @@ static bool ui_but_event_property_operator_string(const bContext *C,
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;
+ /* This version is only for finding hotkeys for properties.
+ * These are set set via a data-path which is appended to the context,
+ * manipulated using operators (see #ctx_toggle_opnames). */
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)) {
+ 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'. */
@@ -1368,58 +1436,19 @@ static bool ui_but_event_property_operator_string(const bContext *C,
prop_id = "type";
prop_enum_value >>= 16;
prop = RNA_struct_find_property(ptr, prop_id);
+ prop_index = -1;
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);
}
+ char *data_path = WM_context_path_resolve_property_full(C, ptr, prop, prop_index);
+
/* We have a data-path! */
bool found = false;
if (data_path || (prop_enum_value_ok && prop_enum_value_id)) {
@@ -1609,7 +1638,7 @@ typedef enum PredefinedExtraOpIconType {
static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but,
wmOperatorType *optype,
- short opcontext,
+ wmOperatorCallContext opcontext,
int icon)
{
uiButExtraOpIcon *extra_op_icon = MEM_mallocN(sizeof(*extra_op_icon), __func__);
@@ -1624,6 +1653,7 @@ static PointerRNA *ui_but_extra_operator_icon_add_ptr(uiBut *but,
extra_op_icon->optype_params->optype);
extra_op_icon->optype_params->opcontext = opcontext;
extra_op_icon->highlighted = false;
+ extra_op_icon->disabled = false;
BLI_addtail(&but->extra_op_icons, extra_op_icon);
@@ -1648,7 +1678,7 @@ void ui_but_extra_operator_icons_free(uiBut *but)
PointerRNA *UI_but_extra_operator_icon_add(uiBut *but,
const char *opname,
- short opcontext,
+ wmOperatorCallContext opcontext,
int icon)
{
wmOperatorType *optype = WM_operatortype_find(opname, false);
@@ -1660,6 +1690,16 @@ PointerRNA *UI_but_extra_operator_icon_add(uiBut *but,
return NULL;
}
+wmOperatorType *UI_but_extra_operator_icon_optype_get(uiButExtraOpIcon *extra_icon)
+{
+ return extra_icon ? extra_icon->optype_params->optype : NULL;
+}
+
+PointerRNA *UI_but_extra_operator_icon_opptr_get(uiButExtraOpIcon *extra_icon)
+{
+ return extra_icon->optype_params->opptr;
+}
+
static bool ui_but_icon_extra_is_visible_text_clear(const uiBut *but)
{
BLI_assert(but->type == UI_BTYPE_TEXT);
@@ -1818,18 +1858,19 @@ static void ui_but_validate(const uiBut *but)
/**
* 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).
+ * 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 ui_but_context_poll_operator_ex(bContext *C,
+ const uiBut *but,
+ const wmOperatorCallParams *optype_params)
{
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);
+ result = WM_operator_poll_context(C, optype_params->optype, optype_params->opcontext);
if (but && but->context) {
CTX_store_set(C, NULL);
@@ -1838,6 +1879,13 @@ bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *
return result;
}
+bool ui_but_context_poll_operator(bContext *C, wmOperatorType *ot, const uiBut *but)
+{
+ const wmOperatorCallContext opcontext = but ? but->opcontext : WM_OP_INVOKE_DEFAULT;
+ return ui_but_context_poll_operator_ex(
+ C, but, &(wmOperatorCallParams){.optype = ot, .opcontext = opcontext});
+}
+
void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_xy[2])
{
wmWindow *window = CTX_wm_window(C);
@@ -1868,6 +1916,12 @@ void UI_block_end_ex(const bContext *C, uiBlock *block, const int xy[2], int r_x
}
}
+ LISTBASE_FOREACH (uiButExtraOpIcon *, op_icon, &but->extra_op_icons) {
+ if (!ui_but_context_poll_operator_ex((bContext *)C, but, op_icon->optype_params)) {
+ op_icon->disabled = true;
+ }
+ }
+
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);
@@ -1934,7 +1988,7 @@ 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);
+ UI_block_end_ex(C, block, window->eventstate->xy, NULL);
}
/* ************** BLOCK DRAWING FUNCTION ************* */
@@ -1944,8 +1998,8 @@ 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) */
+ /* For some reason scaling fonts goes too fast compared to widget size. */
+ /* XXX(ton): not true anymore? */
// aspect = sqrt(aspect);
pointsf /= aspect;
@@ -2179,6 +2233,15 @@ int ui_but_is_pushed_ex(uiBut *but, double *value)
}
}
break;
+ case UI_BTYPE_TREEROW: {
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but;
+
+ is_push = -1;
+ if (tree_row_but->tree_item) {
+ is_push = UI_tree_view_item_is_active(tree_row_but->tree_item);
+ }
+ break;
+ }
default:
is_push = -1;
break;
@@ -2431,7 +2494,7 @@ bool ui_but_is_rna_valid(uiBut *but)
*/
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)) ||
+ 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));
@@ -2504,7 +2567,7 @@ double ui_but_value_get(uiBut *but)
void ui_but_value_set(uiBut *but, double value)
{
- /* value is a hsv value: convert to rgb */
+ /* Value is a HSV value: convert to RGB. */
if (but->rnaprop) {
PropertyRNA *prop = but->rnaprop;
@@ -2628,7 +2691,7 @@ static double ui_get_but_scale_unit(uiBut *but, double value)
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 :| */
+ if (unit_type == PROP_UNIT_TIME) { /* WARNING: using evil_C :| */
Scene *scene = CTX_data_scene(but->block->evil_C);
return FRA2TIME(value);
}
@@ -3139,7 +3202,7 @@ bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
return true;
}
else if (str[0] == '#') {
- /* shortcut to create new driver expression (versus immediate Py-execution) */
+ /* Shortcut to create new driver expression (versus immediate Python-execution). */
return ui_but_anim_expression_create(but, str + 1);
}
else {
@@ -3222,7 +3285,7 @@ void ui_but_range_set_hard(uiBut *but)
}
}
-/* note: this could be split up into functions which handle arrays and not */
+/* 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 it's more than
@@ -3358,8 +3421,8 @@ static void ui_but_free(const bContext *C, uiBut *but)
MEM_freeN(but->func_argN);
}
- if (but->tip_argN) {
- MEM_freeN(but->tip_argN);
+ if (but->tip_arg_free) {
+ but->tip_arg_free(but->tip_arg);
}
if (but->hold_argN) {
@@ -3423,6 +3486,7 @@ void UI_block_free(const bContext *C, uiBlock *block)
BLI_freelistN(&block->color_pickers.list);
ui_block_free_button_groups(block);
+ ui_block_free_views(block);
MEM_freeN(block);
}
@@ -3439,6 +3503,15 @@ void UI_blocklist_update_window_matrix(const bContext *C, const ListBase *lb)
}
}
+void UI_blocklist_update_view_for_buttons(const bContext *C, const ListBase *lb)
+{
+ LISTBASE_FOREACH (uiBlock *, block, lb) {
+ if (block->active) {
+ ui_but_update_view_for_active(C, block);
+ }
+ }
+}
+
void UI_blocklist_draw(const bContext *C, const ListBase *lb)
{
LISTBASE_FOREACH (uiBlock *, block, lb) {
@@ -3541,7 +3614,7 @@ uiBlock *UI_block_begin(const bContext *C, ARegion *region, const char *name, eU
return block;
}
-char UI_block_emboss_get(uiBlock *block)
+eUIEmbossType UI_block_emboss_get(uiBlock *block)
{
return block->emboss;
}
@@ -3909,6 +3982,10 @@ static void ui_but_alloc_info(const eButType type,
alloc_size = sizeof(uiButDatasetRow);
alloc_str = "uiButDatasetRow";
break;
+ case UI_BTYPE_TREEROW:
+ alloc_size = sizeof(uiButTreeRow);
+ alloc_str = "uiButTreeRow";
+ break;
default:
alloc_size = sizeof(uiBut);
alloc_str = "uiBut";
@@ -3984,9 +4061,11 @@ uiBut *ui_but_change_type(uiBut *but, eButType new_type)
UNUSED_VARS_NDEBUG(found_layout);
ui_button_group_replace_but_ptr(uiLayoutGetBlock(but->layout), old_but_ptr, but);
}
+#ifdef WITH_PYTHON
if (UI_editsource_enable_check()) {
UI_editsource_but_replace(old_but_ptr, but);
}
+#endif
}
return but;
@@ -4105,8 +4184,8 @@ static uiBut *ui_def_but(uiBlock *block,
UI_BTYPE_BLOCK,
UI_BTYPE_BUT_MENU,
UI_BTYPE_SEARCH_MENU,
- UI_BTYPE_PROGRESS_BAR,
UI_BTYPE_DATASETROW,
+ UI_BTYPE_TREEROW,
UI_BTYPE_POPOVER)) {
but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT);
}
@@ -4246,7 +4325,7 @@ static void ui_def_but_rna__menu(bContext *UNUSED(C), uiLayout *layout, void *bu
uiItemS(layout);
}
- /* note, item_array[...] is reversed on access */
+ /* NOTE: `item_array[...]` is reversed on access. */
/* create items */
uiLayout *split = uiLayoutSplit(layout, 0.0f, false);
@@ -4371,7 +4450,7 @@ static void ui_def_but_rna__panel_type(bContext *C, uiLayout *layout, void *but_
}
else {
char msg[256];
- SNPRINTF(msg, "Missing Panel: %s", panel_type);
+ SNPRINTF(msg, TIP_("Missing Panel: %s"), panel_type);
uiItemL(layout, msg, ICON_NONE);
}
}
@@ -4400,7 +4479,7 @@ static void ui_def_but_rna__menu_type(bContext *C, uiLayout *layout, void *but_p
}
else {
char msg[256];
- SNPRINTF(msg, "Missing Menu: %s", menu_type);
+ SNPRINTF(msg, TIP_("Missing Menu: %s"), menu_type);
uiItemL(layout, msg, ICON_NONE);
}
}
@@ -4549,7 +4628,7 @@ static uiBut *ui_def_but_rna(uiBlock *block,
else if (proptype == PROP_STRING) {
min = 0;
max = RNA_property_string_maxlength(prop);
- /* note, 'max' may be zero (code for dynamically resized array) */
+ /* NOTE: 'max' may be zero (code for dynamically resized array). */
}
}
@@ -4663,7 +4742,7 @@ static uiBut *ui_def_but_rna_propname(uiBlock *block,
static uiBut *ui_def_but_operator_ptr(uiBlock *block,
int type,
wmOperatorType *ot,
- int opcontext,
+ wmOperatorCallContext opcontext,
const char *str,
int x,
int y,
@@ -5201,7 +5280,7 @@ uiBut *uiDefButR_prop(uiBlock *block,
uiBut *uiDefButO_ptr(uiBlock *block,
int type,
wmOperatorType *ot,
- int opcontext,
+ wmOperatorCallContext opcontext,
const char *str,
int x,
int y,
@@ -5216,7 +5295,7 @@ uiBut *uiDefButO_ptr(uiBlock *block,
uiBut *uiDefButO(uiBlock *block,
int type,
const char *opname,
- int opcontext,
+ wmOperatorCallContext opcontext,
const char *str,
int x,
int y,
@@ -5584,7 +5663,7 @@ uiBut *uiDefIconButR_prop(uiBlock *block,
uiBut *uiDefIconButO_ptr(uiBlock *block,
int type,
wmOperatorType *ot,
- int opcontext,
+ wmOperatorCallContext opcontext,
int icon,
int x,
int y,
@@ -5599,7 +5678,7 @@ uiBut *uiDefIconButO_ptr(uiBlock *block,
uiBut *uiDefIconButO(uiBlock *block,
int type,
const char *opname,
- int opcontext,
+ wmOperatorCallContext opcontext,
int icon,
int x,
int y,
@@ -5987,7 +6066,7 @@ uiBut *uiDefIconTextButR_prop(uiBlock *block,
uiBut *uiDefIconTextButO_ptr(uiBlock *block,
int type,
wmOperatorType *ot,
- int opcontext,
+ wmOperatorCallContext opcontext,
int icon,
const char *str,
int x,
@@ -6004,7 +6083,7 @@ uiBut *uiDefIconTextButO_ptr(uiBlock *block,
uiBut *uiDefIconTextButO(uiBlock *block,
int type,
const char *opname,
- int opcontext,
+ wmOperatorCallContext opcontext,
int icon,
const char *str,
int x,
@@ -6139,32 +6218,37 @@ int UI_but_return_value_get(uiBut *but)
void UI_but_drag_set_id(uiBut *but, ID *id)
{
but->dragtype = WM_DRAG_ID;
- if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) {
+ 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;
}
+/**
+ * \param asset: May be passed from a temporary variable, drag data only stores a copy of this.
+ */
void UI_but_drag_set_asset(uiBut *but,
- const char *name,
+ const AssetHandle *asset,
const char *path,
- int id_type,
+ struct AssetMetaData *metadata,
int import_type,
int icon,
struct ImBuf *imb,
float scale)
{
- wmDragAsset *asset_drag = MEM_mallocN(sizeof(*asset_drag), "wmDragAsset");
+ wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, metadata, path, import_type);
- BLI_strncpy(asset_drag->name, name, sizeof(asset_drag->name));
- asset_drag->path = path;
- asset_drag->id_type = id_type;
- asset_drag->import_type = import_type;
+ /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the
+ * #wmDropBox.
+ * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its
+ * copy callback.
+ * */
+ asset_drag->evil_C = but->block->evil_C;
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)) {
+ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) {
WM_drag_data_free(but->dragtype, but->dragpoin);
}
but->dragpoin = asset_drag;
@@ -6176,7 +6260,7 @@ void UI_but_drag_set_asset(uiBut *but,
void UI_but_drag_set_rna(uiBut *but, PointerRNA *ptr)
{
but->dragtype = WM_DRAG_RNA;
- if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) {
+ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) {
WM_drag_data_free(but->dragtype, but->dragpoin);
but->dragflag &= ~UI_BUT_DRAGPOIN_FREE;
}
@@ -6186,7 +6270,7 @@ void UI_but_drag_set_rna(uiBut *but, PointerRNA *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)) {
+ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) {
WM_drag_data_free(but->dragtype, but->dragpoin);
but->dragflag &= ~UI_BUT_DRAGPOIN_FREE;
}
@@ -6199,7 +6283,7 @@ void UI_but_drag_set_path(uiBut *but, const char *path, const bool use_free)
void UI_but_drag_set_name(uiBut *but, const char *name)
{
but->dragtype = WM_DRAG_NAME;
- if ((but->dragflag & UI_BUT_DRAGPOIN_FREE)) {
+ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) {
WM_drag_data_free(but->dragtype, but->dragpoin);
but->dragflag &= ~UI_BUT_DRAGPOIN_FREE;
}
@@ -6217,7 +6301,7 @@ void UI_but_drag_set_image(
{
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)) {
+ if (but->dragflag & UI_BUT_DRAGPOIN_FREE) {
WM_drag_data_free(but->dragtype, but->dragpoin);
but->dragflag &= ~UI_BUT_DRAGPOIN_FREE;
}
@@ -6334,13 +6418,14 @@ 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)
+void UI_but_func_tooltip_set(uiBut *but, uiButToolTipFunc func, void *arg, uiFreeArgFunc free_arg)
{
but->tip_func = func;
- if (but->tip_argN) {
- MEM_freeN(but->tip_argN);
+ if (but->tip_arg_free) {
+ but->tip_arg_free(but->tip_arg);
}
- but->tip_argN = argN;
+ but->tip_arg = arg;
+ but->tip_arg_free = free_arg;
}
void UI_but_func_pushed_state_set(uiBut *but, uiButPushedStateFunc func, const void *arg)
@@ -6630,7 +6715,7 @@ void UI_but_func_search_set(uiBut *but,
uiButSearchUpdateFn search_update_fn,
void *arg,
const bool free_arg,
- uiButSearchArgFreeFn search_arg_free_fn,
+ uiFreeArgFunc search_arg_free_fn,
uiButHandleFunc search_exec_fn,
void *active)
{
@@ -6755,7 +6840,7 @@ static void operator_enum_search_update_fn(const struct bContext *C,
for (int i = 0; i < filtered_amount; i++) {
const EnumPropertyItem *item = filtered_items[i];
- /* note: need to give the index rather than the
+ /* 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)) {
@@ -6841,6 +6926,15 @@ void UI_but_datasetrow_indentation_set(uiBut *but, int indentation)
BLI_assert(indentation >= 0);
}
+void UI_but_treerow_indentation_set(uiBut *but, int indentation)
+{
+ uiButTreeRow *but_row = (uiButTreeRow *)but;
+ BLI_assert(but->type == UI_BTYPE_TREEROW);
+
+ but_row->indentation = indentation;
+ BLI_assert(indentation >= 0);
+}
+
/**
* Adds a hint to the button which draws right aligned, grayed out and never clipped.
*/
@@ -6920,7 +7014,7 @@ void UI_but_focus_on_enter_event(wmWindow *win, uiBut *but)
event.val = KM_PRESS;
event.is_repeat = false;
event.customdata = but;
- event.customdatafree = false;
+ event.customdata_free = false;
wm_event_add(win, &event);
}
@@ -6965,7 +7059,7 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
}
else if (type == BUT_GET_TIP) {
if (but->tip_func) {
- tmp = but->tip_func(C, but->tip_argN, but->tip);
+ tmp = but->tip_func(C, but->tip_arg, but->tip);
}
else if (but->tip && but->tip[0]) {
tmp = BLI_strdup(but->tip);
@@ -7170,6 +7264,42 @@ void UI_but_string_info_get(bContext *C, uiBut *but, ...)
}
}
+void UI_but_extra_icon_string_info_get(struct bContext *C, uiButExtraOpIcon *extra_icon, ...)
+{
+ va_list args;
+ uiStringInfo *si;
+
+ wmOperatorType *optype = UI_but_extra_operator_icon_optype_get(extra_icon);
+ PointerRNA *opptr = UI_but_extra_operator_icon_opptr_get(extra_icon);
+
+ va_start(args, extra_icon);
+ while ((si = (uiStringInfo *)va_arg(args, void *))) {
+ char *tmp = NULL;
+
+ switch (si->type) {
+ case BUT_GET_LABEL:
+ tmp = BLI_strdup(WM_operatortype_name(optype, opptr));
+ break;
+ case BUT_GET_TIP:
+ tmp = WM_operatortype_description(C, optype, opptr);
+ break;
+ case BUT_GET_OP_KEYMAP: {
+ char buf[128];
+ if (ui_but_extra_icon_event_operator_string(C, extra_icon, buf, sizeof(buf))) {
+ tmp = BLI_strdup(buf);
+ }
+ }
+ /* Other types not supported. The caller should expect that outcome, no need to message or
+ * assert here. */
+ default:
+ break;
+ }
+
+ si->strinfo = tmp;
+ }
+ va_end(args);
+}
+
/* Program Init/Exit */
void UI_init(void)
diff --git a/source/blender/editors/interface/interface_align.c b/source/blender/editors/interface/interface_align.c
index dbfdfbf7950..3149675ac04 100644
--- a/source/blender/editors/interface/interface_align.c
+++ b/source/blender/editors/interface/interface_align.c
@@ -343,7 +343,7 @@ static int ui_block_align_butal_cmp(const void *a, const void *b)
* stupid UI code produces widgets which have the same TOP and LEFT positions...
* We do not care really,
* because this happens when UI is way too small to be usable anyway. */
- /* BLI_assert(0); */
+ // BLI_assert(0);
return 0;
}
diff --git a/source/blender/editors/interface/interface_button_group.c b/source/blender/editors/interface/interface_button_group.c
index 4e7da4ada33..7054498d469 100644
--- a/source/blender/editors/interface/interface_button_group.c
+++ b/source/blender/editors/interface/interface_button_group.c
@@ -57,7 +57,7 @@ void ui_button_group_add_but(uiBlock *block, uiBut *but)
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. */
+ * this list would mess with its `prev` and `next` pointers. */
LinkData *button_link = BLI_genericNodeN(but);
BLI_addtail(&current_button_group->buttons, button_link);
}
diff --git a/source/blender/editors/interface/interface_context_menu.c b/source/blender/editors/interface/interface_context_menu.c
index 775e3923edc..72e7203c6ea 100644
--- a/source/blender/editors/interface/interface_context_menu.c
+++ b/source/blender/editors/interface/interface_context_menu.c
@@ -70,26 +70,12 @@ static IDProperty *shortcut_property_from_rna(bContext *C, uiBut *but)
/* 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) {
+ char *final_data_path = WM_context_path_resolve_property_full(
+ C, &but->rnapoin, but->rnaprop, but->rnaindex);
+ if (final_data_path == 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__);
@@ -329,10 +315,24 @@ static void popup_add_shortcut_func(bContext *C, void *arg1, void *UNUSED(arg2))
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));
+ bool result = false;
+ if (but->optype) {
+ result = true;
+ }
+ else if (but->rnaprop) {
+ if (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) {
+ char *data_path = WM_context_path_resolve_full(C, &but->rnapoin);
+ if (data_path != NULL) {
+ MEM_freeN(data_path);
+ result = true;
+ }
+ }
+ }
+ else if (UI_but_menutype_get(but)) {
+ result = true;
+ }
+
+ return result;
}
static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *um)
@@ -343,21 +343,11 @@ static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *
&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);
- }
+ char *member_id_data_path = WM_context_path_resolve_full(C, &but->rnapoin);
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);
- }
+ MEM_freeN(member_id_data_path);
return umi;
}
@@ -373,13 +363,7 @@ 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';
- }
- }
+ ui_but_drawstr_without_sep_char(but, drawstr, sizeof(drawstr));
MenuType *mt = NULL;
if (but->optype) {
@@ -417,22 +401,12 @@ static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um)
&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);
- }
+ /* NOTE: 'member_id' may be a path. */
+ char *member_id_data_path = WM_context_path_resolve_full(C, &but->rnapoin);
const char *prop_id = RNA_property_identifier(but->rnaprop);
- /* Note, ignore 'drawstr', use property idname always. */
+ /* 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);
- }
+ MEM_freeN(member_id_data_path);
}
else if ((mt = UI_but_menutype_get(but))) {
ED_screen_user_menu_item_add_menu(&um->items, drawstr, mt);
@@ -494,7 +468,7 @@ static void ui_but_menu_add_path_operators(uiLayout *layout, PointerRNA *ptr, Pr
RNA_string_set(&props_ptr, "filepath", dir);
}
-bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
+bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *event)
{
/* ui_but_is_interactive() may let some buttons through that should not get a context menu - it
* doesn't make sense for them. */
@@ -560,7 +534,7 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
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. */
+ * and pointer data for Python access to the hovered UI element. */
uiLayoutSetContextFromBut(layout, but);
/* Keyframes */
@@ -951,8 +925,19 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
}
}
+ {
+ const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C);
+ uiButTreeRow *treerow_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, event->xy);
+ if (treerow_but) {
+ BLI_assert(treerow_but->but.type == UI_BTYPE_TREEROW);
+ UI_tree_view_item_context_menu_build(
+ C, treerow_but->tree_item, uiLayoutColumn(layout, false));
+ 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)) {
+ if (ED_asset_can_mark_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
@@ -1226,6 +1211,19 @@ bool ui_popup_context_menu_for_button(bContext *C, uiBut *but)
}
}
+ /* UI List item context menu. Scripts can add items to it, by default there's nothing shown. */
+ const ARegion *region = CTX_wm_menu(C) ? CTX_wm_menu(C) : CTX_wm_region(C);
+ const bool is_inside_listbox = ui_list_find_mouse_over(region, event) != NULL;
+ const bool is_inside_listrow = is_inside_listbox ?
+ ui_list_row_find_mouse_over(region, event->xy) != NULL :
+ false;
+ if (is_inside_listrow) {
+ MenuType *mt = WM_menutype_find("UI_MT_list_item_context_menu", true);
+ if (mt) {
+ UI_menutype_draw(C, mt, uiLayoutColumn(layout, false));
+ }
+ }
+
MenuType *mt = WM_menutype_find("WM_MT_button_context", true);
if (mt) {
UI_menutype_draw(C, mt, uiLayoutColumn(layout, false));
@@ -1259,6 +1257,9 @@ void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel)
if (panel->type->parent != NULL) {
return;
}
+ if (!UI_panel_can_be_pinned(panel)) {
+ return;
+ }
PointerRNA ptr;
RNA_pointer_create(&screen->id, &RNA_Panel, panel, &ptr);
diff --git a/source/blender/editors/interface/interface_context_path.cc b/source/blender/editors/interface/interface_context_path.cc
new file mode 100644
index 00000000000..b0f8d186afa
--- /dev/null
+++ b/source/blender/editors/interface/interface_context_path.cc
@@ -0,0 +1,85 @@
+/*
+ * 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) 2021 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edinterface
+ */
+
+#include "BLI_vector.hh"
+
+#include "BKE_screen.h"
+
+#include "RNA_access.h"
+
+#include "ED_screen.h"
+
+#include "UI_interface.h"
+#include "UI_interface.hh"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+
+namespace blender::ui {
+
+void context_path_add_generic(Vector<ContextPathItem> &path,
+ StructRNA &rna_type,
+ void *ptr,
+ const BIFIconID icon_override)
+{
+ /* Add the null check here to make calling functions less verbose. */
+ if (!ptr) {
+ return;
+ }
+
+ PointerRNA rna_ptr;
+ RNA_pointer_create(nullptr, &rna_type, ptr, &rna_ptr);
+ char name[128];
+ RNA_struct_name_get_alloc(&rna_ptr, name, sizeof(name), nullptr);
+
+ /* Use a blank icon by default to check whether to retrieve it automatically from the type. */
+ const BIFIconID icon = icon_override == ICON_NONE ?
+ static_cast<BIFIconID>(RNA_struct_ui_icon(rna_ptr.type)) :
+ icon_override;
+
+ path.append({name, static_cast<int>(icon)});
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Breadcrumb Template
+ * \{ */
+
+void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path)
+{
+ uiLayout *row = uiLayoutRow(&layout, true);
+ uiLayoutSetAlignment(&layout, UI_LAYOUT_ALIGN_LEFT);
+
+ for (const int i : context_path.index_range()) {
+ uiLayout *sub_row = uiLayoutRow(row, true);
+ uiLayoutSetAlignment(sub_row, UI_LAYOUT_ALIGN_LEFT);
+
+ if (i > 0) {
+ uiItemL(sub_row, "", ICON_RIGHTARROW_THIN);
+ }
+ uiItemL(sub_row, context_path[i].name.c_str(), context_path[i].icon);
+ }
+}
+
+} // namespace blender::ui
+
+/** \} */ \ No newline at end of file
diff --git a/source/blender/editors/interface/interface_draw.c b/source/blender/editors/interface/interface_draw.c
index 05b6fcdded1..e45a5fc61c6 100644
--- a/source/blender/editors/interface/interface_draw.c
+++ b/source/blender/editors/interface/interface_draw.c
@@ -106,14 +106,30 @@ void UI_draw_roundbox_4fv_ex(const rctf *rect,
.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,
+ .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,
};
@@ -162,35 +178,6 @@ void UI_draw_roundbox_4fv(const rctf *rect, bool filled, float rad, const float
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;
@@ -229,7 +216,7 @@ void ui_draw_but_TAB_outline(const rcti *rect,
{0.98, 0.805},
};
- /* mult */
+ /* Multiply. */
for (a = 0; a < 4; a++) {
mul_v2_fl(vec[a], rad);
}
@@ -592,7 +579,7 @@ static void waveform_draw_one(float *waveform, int nbr, const float col[3])
GPU_vertbuf_attr_fill(vbo, pos_id, waveform);
- /* TODO store the GPUBatch inside the scope */
+ /* 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);
@@ -1844,13 +1831,13 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
/* 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;
+ int tot_points = BKE_curveprofile_table_size(profile) + 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. */
+ for (uint i = 0; i < (uint)BKE_curveprofile_table_size(profile); i++) {
+ /* Only add the points from the table here. */
table_coords[i][0] = pts[i].x;
table_coords[i][1] = pts[i].y;
}
@@ -1887,44 +1874,50 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
}
/* 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);
+ if (tot_triangles > 0) {
+ uint(*tri_indices)[3] = MEM_mallocN(sizeof(*tri_indices) * tot_triangles, __func__);
+ 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);
+ /* 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);
}
- 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);
+ const int edges_len = tot_points - 1;
+ if (edges_len > 0) {
+ GPU_line_width(1.0f);
+ immUniformColor3ubvAlpha((const uchar *)wcol->item, 255);
+ GPU_line_smooth(true);
+ immBegin(GPU_PRIM_LINE_STRIP, tot_points);
+ for (int i = 0; i < tot_points; i++) {
+ fx = rect->xmin + zoomx * (table_coords[i][0] - offsx);
+ fy = rect->ymin + zoomy * (table_coords[i][1] - offsy);
+ immVertex2f(pos, fx, fy);
+ }
+ immEnd();
}
- immEnd();
- MEM_freeN(table_coords);
+
+ MEM_SAFE_FREE(table_coords);
/* Draw the handles for the selected control points. */
pts = profile->path;
- tot_points = (uint)profile->path_len;
+ const int path_len = tot_points = (uint)profile->path_len;
int selected_free_points = 0;
- for (uint i = 0; i < tot_points; i++) {
+ for (int i = 0; i < path_len; i++) {
if (point_draw_handles(&pts[i])) {
selected_free_points++;
}
@@ -1936,7 +1929,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
GPU_line_smooth(true);
immBegin(GPU_PRIM_LINES, selected_free_points * 4);
float ptx, pty;
- for (uint i = 0; i < tot_points; i++) {
+ for (int i = 0; i < path_len; i++) {
if (point_draw_handles(&pts[i])) {
ptx = rect->xmin + zoomx * (pts[i].x - offsx);
pty = rect->ymin + zoomy * (pts[i].y - offsy);
@@ -1980,16 +1973,18 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
/* 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);
+ if (path_len > 0) {
+ 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, path_len);
+ for (int i = 0; i < path_len; 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();
}
- immEnd();
/* Draw the handle points. */
if (selected_free_points > 0) {
@@ -1997,7 +1992,7 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
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++) {
+ for (int i = 0; i < path_len; 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);
@@ -2015,11 +2010,11 @@ void ui_draw_but_CURVEPROFILE(ARegion *region,
/* 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) {
+ const int segments_len = (uint)profile->segments_len;
+ if (segments_len > 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++) {
+ immBegin(GPU_PRIM_POINTS, segments_len);
+ for (int i = 0; i < segments_len; i++) {
fx = rect->xmin + zoomx * (pts[i].x - offsx);
fy = rect->ymin + zoomy * (pts[i].y - offsy);
immAttr4fv(col, color_sample);
@@ -2223,9 +2218,8 @@ void ui_draw_but_TRACKPREVIEW(ARegion *UNUSED(region),
/* ****************************************************** */
-/* TODO: high quality UI drop shadows using GLSL shader and single draw call
- * would replace / modify the following 3 functions - merwin
- */
+/* TODO(merwin): high quality UI drop shadows using GLSL shader and single draw call
+ * would replace / modify the following 3 functions. */
static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, uchar alpha)
{
@@ -2266,7 +2260,7 @@ static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize,
immVertex2fv(pos, v3);
/* corner shape */
- /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */
+ // 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);
@@ -2278,7 +2272,7 @@ static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize,
immVertex2fv(pos, v3);
/* bottom quad */
- /* immAttr4ub(color, 0, 0, 0, alpha); */ /* Not needed, done above in previous tri */
+ // 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);
@@ -2350,7 +2344,7 @@ void ui_draw_dropshadow(
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. */
+ /* TODO: we could just find a better fit function inside the shader instead of this. */
visibility = visibility * (1.0f - calpha);
calpha += dalpha;
}
diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc
new file mode 100644
index 00000000000..369efa7c52e
--- /dev/null
+++ b/source/blender/editors/interface/interface_dropboxes.cc
@@ -0,0 +1,95 @@
+/*
+ * 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 "BKE_context.h"
+
+#include "DNA_space_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+
+#include "UI_interface.h"
+
+static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
+{
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region,
+ event->xy);
+ if (!hovered_tree_item) {
+ return false;
+ }
+
+ if (drag->drop_state.free_disabled_info) {
+ MEM_SAFE_FREE(drag->drop_state.disabled_info);
+ }
+
+ drag->drop_state.free_disabled_info = false;
+ return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->drop_state.disabled_info);
+}
+
+static char *ui_tree_view_drop_tooltip(bContext *C,
+ wmDrag *drag,
+ const int xy[2],
+ wmDropBox *UNUSED(drop))
+{
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, xy);
+ if (!hovered_tree_item) {
+ return nullptr;
+ }
+
+ return UI_tree_view_item_drop_tooltip(hovered_tree_item, drag);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static bool ui_drop_name_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event))
+{
+ return UI_but_active_drop_name(C) && (drag->type == WM_DRAG_ID);
+}
+
+static void ui_drop_name_copy(wmDrag *drag, wmDropBox *drop)
+{
+ const ID *id = WM_drag_get_local_ID(drag, 0);
+ RNA_string_set(drop->ptr, "string", id->name + 2);
+}
+
+/* ---------------------------------------------------------------------- */
+
+void ED_dropboxes_ui()
+{
+ ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0);
+
+ WM_dropbox_add(lb,
+ "UI_OT_tree_view_drop",
+ ui_tree_view_drop_poll,
+ nullptr,
+ nullptr,
+ ui_tree_view_drop_tooltip);
+ WM_dropbox_add(lb,
+ "UI_OT_drop_name",
+ ui_drop_name_poll,
+ ui_drop_name_copy,
+ WM_drag_free_imported_drag_ID,
+ nullptr);
+}
diff --git a/source/blender/editors/interface/interface_eyedropper.c b/source/blender/editors/interface/interface_eyedropper.c
index b52bfc81b7a..395ecc77ef4 100644
--- a/source/blender/editors/interface/interface_eyedropper.c
+++ b/source/blender/editors/interface/interface_eyedropper.c
@@ -24,6 +24,8 @@
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
+#include "BLI_math_color.h"
+
#include "BKE_context.h"
#include "BKE_screen.h"
@@ -107,8 +109,13 @@ static void eyedropper_draw_cursor_text_ex(const int x, const int y, const char
{
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
- const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f};
- const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f};
+ /* Use the theme settings from tooltips. */
+ const bTheme *btheme = UI_GetTheme();
+ const uiWidgetColors *wcol = &btheme->tui.wcol_tooltip;
+
+ float col_fg[4], col_bg[4];
+ rgba_uchar_to_float(col_fg, wcol->text);
+ rgba_uchar_to_float(col_bg, wcol->inner);
UI_fontstyle_draw_simple_backdrop(fstyle, x, y + U.widget_unit, name, col_fg, col_bg);
}
@@ -119,30 +126,19 @@ void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const cha
return;
}
- const int x = window->eventstate->x;
- const int y = window->eventstate->y;
+ const int x = window->eventstate->xy[0];
+ const int y = window->eventstate->xy[1];
eyedropper_draw_cursor_text_ex(x, y, name);
}
-void eyedropper_draw_cursor_text_region(const struct bContext *C,
- const ARegion *region,
- const char *name)
+void eyedropper_draw_cursor_text_region(const int x, const int y, const char *name)
{
- wmWindow *win = CTX_wm_window(C);
- const int x = win->eventstate->x;
- const int y = win->eventstate->y;
-
- if ((name[0] == '\0') || (BLI_rcti_isect_pt(&region->winrct, x, y) == false)) {
+ if (name[0] == '\0') {
return;
}
- const int mval[2] = {
- x - region->winrct.xmin,
- y - region->winrct.ymin,
- };
-
- eyedropper_draw_cursor_text_ex(mval[0], mval[1], name);
+ eyedropper_draw_cursor_text_ex(x, y, name);
}
/**
@@ -157,8 +153,8 @@ void eyedropper_draw_cursor_text_region(const struct bContext *C,
uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event)
{
bScreen *screen = CTX_wm_screen(C);
- ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->x, event->y);
- const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->x, event->y);
+ ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, event->xy[0], event->xy[1]);
+ const ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_ANY, event->xy[0], event->xy[1]);
uiBut *but = ui_but_find_mouse_over(region, event);
@@ -168,4 +164,25 @@ uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *ev
return but;
}
+void datadropper_win_area_find(
+ const bContext *C, const int mval[2], int r_mval[2], wmWindow **r_win, ScrArea **r_area)
+{
+ bScreen *screen = CTX_wm_screen(C);
+
+ *r_win = CTX_wm_window(C);
+ *r_area = BKE_screen_find_area_xy(screen, -1, mval[0], mval[1]);
+ if (*r_area == NULL) {
+ wmWindowManager *wm = CTX_wm_manager(C);
+ *r_win = WM_window_find_under_cursor(wm, NULL, *r_win, mval, r_mval);
+ if (*r_win) {
+ screen = WM_window_get_active_screen(*r_win);
+ *r_area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, r_mval[0], r_mval[1]);
+ }
+ }
+ else if (mval != r_mval) {
+ r_mval[0] = mval[0];
+ r_mval[1] = mval[1];
+ }
+}
+
/** \} */
diff --git a/source/blender/editors/interface/interface_eyedropper_color.c b/source/blender/editors/interface/interface_eyedropper_color.c
index ba72cecc514..c3633e11f83 100644
--- a/source/blender/editors/interface/interface_eyedropper_color.c
+++ b/source/blender/editors/interface/interface_eyedropper_color.c
@@ -337,52 +337,41 @@ void eyedropper_color_sample_fl(bContext *C, int mx, int my, float r_col[3])
const char *display_device = CTX_data_scene(C)->display_settings.display_device;
struct ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device);
- wmWindow *win = CTX_wm_window(C);
- bScreen *screen = CTX_wm_screen(C);
- ScrArea *area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mx, my);
- if (area == NULL) {
- int mval[2] = {mx, my};
- if (WM_window_find_under_cursor(wm, NULL, win, mval, &win, mval)) {
- mx = mval[0];
- my = mval[1];
- screen = WM_window_get_active_screen(win);
- area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mx, my);
- }
- else {
- win = NULL;
- }
- }
+ wmWindow *win;
+ ScrArea *area;
+ int mval[2] = {mx, my};
+ datadropper_win_area_find(C, mval, mval, &win, &area);
if (area) {
if (area->spacetype == SPACE_IMAGE) {
- ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my);
+ ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval[0], mval[1]);
if (region) {
SpaceImage *sima = area->spacedata.first;
- int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin};
+ int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin};
- if (ED_space_image_color_sample(sima, region, mval, r_col, NULL)) {
+ if (ED_space_image_color_sample(sima, region, region_mval, r_col, NULL)) {
return;
}
}
}
else if (area->spacetype == SPACE_NODE) {
- ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my);
+ ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval[0], mval[1]);
if (region) {
SpaceNode *snode = area->spacedata.first;
- const int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin};
+ int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin};
- if (ED_space_node_color_sample(bmain, snode, region, mval, r_col)) {
+ if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) {
return;
}
}
}
else if (area->spacetype == SPACE_CLIP) {
- ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mx, my);
+ ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval[0], mval[1]);
if (region) {
SpaceClip *sc = area->spacedata.first;
- int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin};
+ int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin};
- if (ED_space_clip_color_sample(sc, region, mval, r_col)) {
+ if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) {
return;
}
}
@@ -391,7 +380,6 @@ void eyedropper_color_sample_fl(bContext *C, int mx, int my, float r_col[3])
if (win) {
/* Fallback to simple opengl picker. */
- const int mval[2] = {mx, my};
WM_window_pixel_sample_read(wm, win, mval, r_col);
IMB_colormanagement_display_to_scene_linear_v3(r_col, display);
}
@@ -493,7 +481,7 @@ static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
case EYE_MODAL_SAMPLE_CONFIRM: {
const bool is_undo = eye->is_undo;
if (eye->accum_tot == 0) {
- eyedropper_color_sample(C, eye, event->x, event->y);
+ eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]);
}
eyedropper_exit(C, op);
/* Could support finished & undo-skip. */
@@ -502,23 +490,23 @@ static int eyedropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
case EYE_MODAL_SAMPLE_BEGIN:
/* enable accum and make first sample */
eye->accum_start = true;
- eyedropper_color_sample(C, eye, event->x, event->y);
+ eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]);
break;
case EYE_MODAL_SAMPLE_RESET:
eye->accum_tot = 0;
zero_v3(eye->accum_col);
- eyedropper_color_sample(C, eye, event->x, event->y);
+ eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]);
break;
}
}
else if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
if (eye->accum_start) {
/* button is pressed so keep sampling */
- eyedropper_color_sample(C, eye, event->x, event->y);
+ eyedropper_color_sample(C, eye, event->xy[0], event->xy[1]);
}
if (eye->draw_handle_sample_text) {
- eyedropper_color_sample_text_update(C, eye, event->x, event->y);
+ eyedropper_color_sample_text_update(C, eye, event->xy[0], event->xy[1]);
ED_region_tag_redraw(CTX_wm_region(C));
}
}
diff --git a/source/blender/editors/interface/interface_eyedropper_colorband.c b/source/blender/editors/interface/interface_eyedropper_colorband.c
index d32eb415b19..22320282766 100644
--- a/source/blender/editors/interface/interface_eyedropper_colorband.c
+++ b/source/blender/editors/interface/interface_eyedropper_colorband.c
@@ -233,7 +233,7 @@ static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent
return OPERATOR_CANCELLED;
case EYE_MODAL_SAMPLE_CONFIRM: {
const bool is_undo = eye->is_undo;
- eyedropper_colorband_sample_segment(C, eye, event->x, event->y);
+ eyedropper_colorband_sample_segment(C, eye, event->xy[0], event->xy[1]);
eyedropper_colorband_apply(C, op);
eyedropper_colorband_exit(C, op);
/* Could support finished & undo-skip. */
@@ -242,10 +242,10 @@ static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent
case EYE_MODAL_SAMPLE_BEGIN:
/* enable accum and make first sample */
eye->sample_start = true;
- eyedropper_colorband_sample_point(C, eye, event->x, event->y);
+ eyedropper_colorband_sample_point(C, eye, event->xy[0], event->xy[1]);
eyedropper_colorband_apply(C, op);
- eye->last_x = event->x;
- eye->last_y = event->y;
+ eye->last_x = event->xy[0];
+ eye->last_y = event->xy[1];
break;
case EYE_MODAL_SAMPLE_RESET:
break;
@@ -253,7 +253,7 @@ static int eyedropper_colorband_modal(bContext *C, wmOperator *op, const wmEvent
}
else if (event->type == MOUSEMOVE) {
if (eye->sample_start) {
- eyedropper_colorband_sample_segment(C, eye, event->x, event->y);
+ eyedropper_colorband_sample_segment(C, eye, event->xy[0], event->xy[1]);
eyedropper_colorband_apply(C, op);
}
}
@@ -280,7 +280,7 @@ static int eyedropper_colorband_point_modal(bContext *C, wmOperator *op, const w
}
break;
case EYE_MODAL_POINT_SAMPLE:
- eyedropper_colorband_sample_point(C, eye, event->x, event->y);
+ eyedropper_colorband_sample_point(C, eye, event->xy[0], event->xy[1]);
eyedropper_colorband_apply(C, op);
if (eye->color_buffer_len == MAXCOLORBAND) {
eyedropper_colorband_exit(C, op);
diff --git a/source/blender/editors/interface/interface_eyedropper_datablock.c b/source/blender/editors/interface/interface_eyedropper_datablock.c
index 8c605598cbc..261aa997d06 100644
--- a/source/blender/editors/interface/interface_eyedropper_datablock.c
+++ b/source/blender/editors/interface/interface_eyedropper_datablock.c
@@ -71,13 +71,16 @@ typedef struct DataDropper {
ScrArea *cursor_area; /* Area under the cursor */
ARegionType *art;
void *draw_handle_pixel;
+ int name_pos[2];
char name[200];
} DataDropper;
-static void datadropper_draw_cb(const struct bContext *C, ARegion *region, void *arg)
+static void datadropper_draw_cb(const struct bContext *UNUSED(C),
+ ARegion *UNUSED(region),
+ void *arg)
{
DataDropper *ddr = arg;
- eyedropper_draw_cursor_text_region(C, region, ddr->name);
+ eyedropper_draw_cursor_text_region(UNPACK2(ddr->name_pos), ddr->name);
}
static int datadropper_init(bContext *C, wmOperator *op)
@@ -148,12 +151,10 @@ static void datadropper_exit(bContext *C, wmOperator *op)
/**
* \brief get the ID from the 3D view or outliner.
*/
-static void datadropper_id_sample_pt(bContext *C, DataDropper *ddr, int mx, int my, ID **r_id)
+static void datadropper_id_sample_pt(
+ bContext *C, wmWindow *win, ScrArea *area, DataDropper *ddr, int mx, int my, ID **r_id)
{
- /* we could use some clever */
- bScreen *screen = CTX_wm_screen(C);
- ScrArea *area = BKE_screen_find_area_xy(screen, -1, mx, my);
-
+ wmWindow *win_prev = CTX_wm_window(C);
ScrArea *area_prev = CTX_wm_area(C);
ARegion *region_prev = CTX_wm_region(C);
@@ -166,6 +167,7 @@ static void datadropper_id_sample_pt(bContext *C, DataDropper *ddr, int mx, int
const int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin};
Base *base;
+ CTX_wm_window_set(C, win);
CTX_wm_area_set(C, area);
CTX_wm_region_set(C, region);
@@ -202,11 +204,15 @@ static void datadropper_id_sample_pt(bContext *C, DataDropper *ddr, int mx, int
BLI_snprintf(ddr->name, sizeof(ddr->name), "%s: %s", ddr->idcode_name, id->name + 2);
*r_id = id;
}
+
+ ddr->name_pos[0] = mval[0];
+ ddr->name_pos[1] = mval[1];
}
}
}
}
+ CTX_wm_window_set(C, win_prev);
CTX_wm_area_set(C, area_prev);
CTX_wm_region_set(C, region_prev);
}
@@ -232,7 +238,13 @@ static bool datadropper_id_sample(bContext *C, DataDropper *ddr, int mx, int my)
{
ID *id = NULL;
- datadropper_id_sample_pt(C, ddr, mx, my, &id);
+ wmWindow *win;
+ ScrArea *area;
+
+ int mval[] = {mx, my};
+ datadropper_win_area_find(C, mval, mval, &win, &area);
+
+ datadropper_id_sample_pt(C, win, area, ddr, mval[0], mval[1], &id);
return datadropper_id_set(C, ddr, id);
}
@@ -244,14 +256,8 @@ static void datadropper_cancel(bContext *C, wmOperator *op)
}
/* To switch the draw callback when region under mouse event changes */
-static void datadropper_set_draw_callback_region(bContext *C,
- DataDropper *ddr,
- const int mx,
- const int my)
+static void datadropper_set_draw_callback_region(ScrArea *area, DataDropper *ddr)
{
- bScreen *screen = CTX_wm_screen(C);
- ScrArea *area = BKE_screen_find_area_xy(screen, -1, mx, my);
-
if (area) {
/* If spacetype changed */
if (area->spacetype != ddr->cursor_area->spacetype) {
@@ -286,7 +292,7 @@ static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_CANCELLED;
case EYE_MODAL_SAMPLE_CONFIRM: {
const bool is_undo = ddr->is_undo;
- const bool success = datadropper_id_sample(C, ddr, event->x, event->y);
+ const bool success = datadropper_id_sample(C, ddr, event->xy[0], event->xy[1]);
datadropper_exit(C, op);
if (success) {
/* Could support finished & undo-skip. */
@@ -300,10 +306,16 @@ static int datadropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
else if (event->type == MOUSEMOVE) {
ID *id = NULL;
+ wmWindow *win;
+ ScrArea *area;
+
+ int mval[] = {event->xy[0], event->xy[1]};
+ datadropper_win_area_find(C, mval, mval, &win, &area);
+
/* Set the region for eyedropper cursor text drawing */
- datadropper_set_draw_callback_region(C, ddr, event->x, event->y);
+ datadropper_set_draw_callback_region(area, ddr);
- datadropper_id_sample_pt(C, ddr, event->x, event->y, &id);
+ datadropper_id_sample_pt(C, win, area, ddr, mval[0], mval[1], &id);
}
return OPERATOR_RUNNING_MODAL;
diff --git a/source/blender/editors/interface/interface_eyedropper_depth.c b/source/blender/editors/interface/interface_eyedropper_depth.c
index a64fad8c333..4172c474f4a 100644
--- a/source/blender/editors/interface/interface_eyedropper_depth.c
+++ b/source/blender/editors/interface/interface_eyedropper_depth.c
@@ -72,13 +72,16 @@ typedef struct DepthDropper {
ARegionType *art;
void *draw_handle_pixel;
+ int name_pos[2];
char name[200];
} DepthDropper;
-static void depthdropper_draw_cb(const struct bContext *C, ARegion *region, void *arg)
+static void depthdropper_draw_cb(const struct bContext *UNUSED(C),
+ ARegion *UNUSED(region),
+ void *arg)
{
DepthDropper *ddr = arg;
- eyedropper_draw_cursor_text_region(C, region, ddr->name);
+ eyedropper_draw_cursor_text_region(UNPACK2(ddr->name_pos), ddr->name);
}
static int depthdropper_init(bContext *C, wmOperator *op)
@@ -172,6 +175,8 @@ static void depthdropper_depth_sample_pt(
/* weak, we could pass in some reference point */
const float *view_co = v3d->camera ? v3d->camera->obmat[3] : rv3d->viewinv[3];
const int mval[2] = {mx - region->winrct.xmin, my - region->winrct.ymin};
+ copy_v2_v2_int(ddr->name_pos, mval);
+
float co[3];
CTX_wm_area_set(C, area);
@@ -271,7 +276,7 @@ static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
case EYE_MODAL_SAMPLE_CONFIRM: {
const bool is_undo = ddr->is_undo;
if (ddr->accum_tot == 0) {
- depthdropper_depth_sample(C, ddr, event->x, event->y);
+ depthdropper_depth_sample(C, ddr, event->xy[0], event->xy[1]);
}
else {
depthdropper_depth_set_accum(C, ddr);
@@ -283,12 +288,12 @@ static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
case EYE_MODAL_SAMPLE_BEGIN:
/* enable accum and make first sample */
ddr->accum_start = true;
- depthdropper_depth_sample_accum(C, ddr, event->x, event->y);
+ depthdropper_depth_sample_accum(C, ddr, event->xy[0], event->xy[1]);
break;
case EYE_MODAL_SAMPLE_RESET:
ddr->accum_tot = 0;
ddr->accum_depth = 0.0f;
- depthdropper_depth_sample_accum(C, ddr, event->x, event->y);
+ depthdropper_depth_sample_accum(C, ddr, event->xy[0], event->xy[1]);
depthdropper_depth_set_accum(C, ddr);
break;
}
@@ -296,7 +301,7 @@ static int depthdropper_modal(bContext *C, wmOperator *op, const wmEvent *event)
else if (event->type == MOUSEMOVE) {
if (ddr->accum_start) {
/* button is pressed so keep sampling */
- depthdropper_depth_sample_accum(C, ddr, event->x, event->y);
+ depthdropper_depth_sample_accum(C, ddr, event->xy[0], event->xy[1]);
depthdropper_depth_set_accum(C, ddr);
}
}
diff --git a/source/blender/editors/interface/interface_eyedropper_driver.c b/source/blender/editors/interface/interface_eyedropper_driver.c
index 8762a4819d4..ccf0e727da8 100644
--- a/source/blender/editors/interface/interface_eyedropper_driver.c
+++ b/source/blender/editors/interface/interface_eyedropper_driver.c
@@ -84,10 +84,7 @@ static void driverdropper_exit(bContext *C, wmOperator *op)
{
WM_cursor_modal_restore(CTX_wm_window(C));
- if (op->customdata) {
- MEM_freeN(op->customdata);
- op->customdata = NULL;
- }
+ MEM_SAFE_FREE(op->customdata);
}
static void driverdropper_sample(bContext *C, wmOperator *op, const wmEvent *event)
diff --git a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c
index 417807afff1..d76ff84bcad 100644
--- a/source/blender/editors/interface/interface_eyedropper_gpencil_color.c
+++ b/source/blender/editors/interface/interface_eyedropper_gpencil_color.c
@@ -292,7 +292,7 @@ static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *
return OPERATOR_CANCELLED;
}
case EYE_MODAL_SAMPLE_CONFIRM: {
- eyedropper_gpencil_color_sample(C, eye, event->x, event->y);
+ eyedropper_gpencil_color_sample(C, eye, event->xy[0], event->xy[1]);
/* Create material. */
eyedropper_gpencil_color_set(C, event, eye);
@@ -309,7 +309,7 @@ static int eyedropper_gpencil_modal(bContext *C, wmOperator *op, const wmEvent *
}
case MOUSEMOVE:
case INBETWEEN_MOUSEMOVE: {
- eyedropper_gpencil_color_sample(C, eye, event->x, event->y);
+ eyedropper_gpencil_color_sample(C, eye, event->xy[0], event->xy[1]);
break;
}
default: {
diff --git a/source/blender/editors/interface/interface_eyedropper_intern.h b/source/blender/editors/interface/interface_eyedropper_intern.h
index 96a2c6ed111..f9f3fcfb5d1 100644
--- a/source/blender/editors/interface/interface_eyedropper_intern.h
+++ b/source/blender/editors/interface/interface_eyedropper_intern.h
@@ -24,10 +24,13 @@
/* interface_eyedropper.c */
void eyedropper_draw_cursor_text_window(const struct wmWindow *window, const char *name);
-void eyedropper_draw_cursor_text_region(const struct bContext *C,
- const struct ARegion *region,
- const char *name);
+void eyedropper_draw_cursor_text_region(const int x, const int y, const char *name);
uiBut *eyedropper_get_property_button_under_mouse(bContext *C, const wmEvent *event);
+void datadropper_win_area_find(const struct bContext *C,
+ const int mval[2],
+ int r_mval[2],
+ struct wmWindow **r_win,
+ struct ScrArea **r_area);
/* interface_eyedropper_color.c (expose for color-band picker) */
void eyedropper_color_sample_fl(bContext *C, int mx, int my, float r_col[3]);
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 542a226ee68..35e1526d079 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -35,10 +35,12 @@
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
+#include "BLI_array_utils.h"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_rect.h"
+#include "BLI_sort_utils.h"
#include "BLI_string.h"
#include "BLI_string_cursor_utf8.h"
#include "BLI_string_utf8.h"
@@ -170,6 +172,20 @@ static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but
static void ui_textedit_string_set(uiBut *but, struct uiHandleButtonData *data, const char *str);
static void button_tooltip_timer_reset(bContext *C, uiBut *but);
+static void ui_block_interaction_begin_ensure(bContext *C,
+ uiBlock *block,
+ struct uiHandleButtonData *data,
+ const bool is_click);
+static struct uiBlockInteraction_Handle *ui_block_interaction_begin(struct bContext *C,
+ uiBlock *block,
+ const bool is_click);
+static void ui_block_interaction_end(struct bContext *C,
+ uiBlockInteraction_CallbackData *callbacks,
+ struct uiBlockInteraction_Handle *interaction);
+static void ui_block_interaction_update(struct bContext *C,
+ uiBlockInteraction_CallbackData *callbacks,
+ struct uiBlockInteraction_Handle *interaction);
+
#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);
@@ -225,6 +241,19 @@ typedef enum uiMenuScrollType {
MENU_SCROLL_BOTTOM,
} uiMenuScrollType;
+typedef struct uiBlockInteraction_Handle {
+ struct uiBlockInteraction_Params params;
+ void *user_data;
+ /**
+ * This is shared between #uiHandleButtonData and #uiAfterFunc,
+ * the last user runs the end callback and frees the data.
+ *
+ * This is needed as the order of freeing changes depending on
+ * accepting/canceling the operation.
+ */
+ int user_count;
+} uiBlockInteraction_Handle;
+
#ifdef USE_ALLSELECT
/* Unfortunately there's no good way handle more generally:
@@ -273,7 +302,7 @@ static void ui_selectcontext_apply(bContext *C,
/**
* how far to drag before we check for gesture direction (in pixels),
- * note: half the height of a button is about right... */
+ * NOTE: half the height of a button is about right... */
# define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
/**
@@ -329,7 +358,7 @@ typedef struct uiHandleButtonMulti {
* here so we can tell if this is a vertical motion or not. */
float drag_dir[2];
- /* values copied direct from event->x,y
+ /* values copied direct from event->xy
* used to detect buttons between the current and initial mouse position */
int drag_start[2];
@@ -355,6 +384,8 @@ typedef struct uiHandleButtonData {
/* booleans (could be made into flags) */
bool cancel, escapecancel;
bool applied, applied_interactive;
+ /* Button is being applied through an extra icon. */
+ bool apply_through_extra_icon;
bool changed_cursor;
wmTimer *flashtimer;
@@ -364,9 +395,6 @@ typedef struct uiHandleButtonData {
char *origstr;
double value, origvalue, startvalue;
float vec[3], origvec[3];
-#if 0 /* UNUSED */
- int togdual, togonly;
-#endif
ColorBand *coba;
/* Tool-tip. */
@@ -430,6 +458,8 @@ typedef struct uiHandleButtonData {
uiSelectContextStore select_others;
#endif
+ struct uiBlockInteraction_Handle *custom_interaction_handle;
+
/* Text field undo. */
struct uiUndoStack_Text *undo_stack_text;
@@ -462,18 +492,22 @@ typedef struct uiAfterFunc {
wmOperator *popup_op;
wmOperatorType *optype;
- int opcontext;
+ wmOperatorCallContext opcontext;
PointerRNA *opptr;
PointerRNA rnapoin;
PropertyRNA *rnaprop;
void *search_arg;
- uiButSearchArgFreeFn search_arg_free_fn;
+ uiFreeArgFunc search_arg_free_fn;
+
+ uiBlockInteraction_CallbackData custom_interaction_callbacks;
+ uiBlockInteraction_Handle *custom_interaction_handle;
bContextStore *context;
char undostr[BKE_UNDO_STR_MAX];
+ char drawstr[UI_MAX_DRAW_STR];
} uiAfterFunc;
static void button_activate_init(bContext *C,
@@ -733,23 +767,38 @@ static uiAfterFunc *ui_afterfunc_new(void)
* For executing operators after the button is pressed.
* (some non operator buttons need to trigger operators), see: T37795.
*
+ * \param context_but: A button from which to get the context from (`uiBut.context`) for the
+ * operator execution.
+ *
+ * \note Ownership over \a properties is moved here. The #uiAfterFunc owns it now.
* \note Can only call while handling buttons.
*/
-PointerRNA *ui_handle_afterfunc_add_operator(wmOperatorType *ot, int opcontext, bool create_props)
+static void ui_handle_afterfunc_add_operator_ex(wmOperatorType *ot,
+ PointerRNA **properties,
+ wmOperatorCallContext opcontext,
+ const uiBut *context_but)
{
- PointerRNA *ptr = NULL;
uiAfterFunc *after = ui_afterfunc_new();
after->optype = ot;
after->opcontext = opcontext;
+ if (properties) {
+ after->opptr = *properties;
+ *properties = NULL;
+ }
- if (create_props) {
- ptr = MEM_callocN(sizeof(PointerRNA), __func__);
- WM_operator_properties_create_ptr(ptr, ot);
- after->opptr = ptr;
+ if (context_but && context_but->context) {
+ after->context = CTX_store_copy(context_but->context);
}
- return ptr;
+ if (context_but) {
+ ui_but_drawstr_without_sep_char(context_but, after->drawstr, sizeof(after->drawstr));
+ }
+}
+
+void ui_handle_afterfunc_add_operator(wmOperatorType *ot, wmOperatorCallContext opcontext)
+{
+ ui_handle_afterfunc_add_operator_ex(ot, NULL, opcontext, NULL);
}
static void popup_check(bContext *C, wmOperator *op)
@@ -769,72 +818,97 @@ static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
(block->handle && block->handle->popup_op));
}
+/**
+ * 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.
+ */
static void ui_apply_but_func(bContext *C, uiBut *but)
{
uiBlock *block = but->block;
+ if (!ui_afterfunc_check(block, but)) {
+ return;
+ }
- /* 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();
+ 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;
- }
+ 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->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->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->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;
+ 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 (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;
- }
+ if (block->handle) {
+ after->popup_op = block->handle->popup_op;
+ }
- after->optype = but->optype;
- after->opcontext = but->opcontext;
- after->opptr = but->opptr;
+ after->optype = but->optype;
+ after->opcontext = but->opcontext;
+ after->opptr = but->opptr;
- after->rnapoin = but->rnapoin;
- after->rnaprop = but->rnaprop;
+ 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->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);
+ if (but->active != NULL) {
+ uiHandleButtonData *data = but->active;
+ if (data->custom_interaction_handle != NULL) {
+ after->custom_interaction_callbacks = block->custom_interaction_callbacks;
+ after->custom_interaction_handle = data->custom_interaction_handle;
+
+ /* Ensure this callback runs once and last. */
+ uiAfterFunc *after_prev = after->prev;
+ if (after_prev &&
+ (after_prev->custom_interaction_handle == data->custom_interaction_handle)) {
+ after_prev->custom_interaction_handle = NULL;
+ memset(&after_prev->custom_interaction_callbacks,
+ 0x0,
+ sizeof(after_prev->custom_interaction_callbacks));
+ }
+ else {
+ after->custom_interaction_handle->user_count++;
+ }
}
+ }
- but->optype = NULL;
- but->opcontext = 0;
- but->opptr = NULL;
+ if (but->context) {
+ after->context = CTX_store_copy(but->context);
}
+
+ ui_but_drawstr_without_sep_char(but, after->drawstr, sizeof(after->drawstr));
+
+ but->optype = NULL;
+ but->opcontext = 0;
+ but->opptr = NULL;
}
/* typically call ui_apply_but_undo(), ui_apply_but_autokey() */
@@ -953,7 +1027,8 @@ static void ui_apply_but_funcs_after(bContext *C)
}
if (after.optype) {
- WM_operator_name_call_ptr(C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL);
+ WM_operator_name_call_ptr_with_depends_on_cursor(
+ C, after.optype, after.opcontext, (after.opptr) ? &opptr : NULL, after.drawstr);
}
if (after.opptr) {
@@ -997,6 +1072,18 @@ static void ui_apply_but_funcs_after(bContext *C)
after.search_arg_free_fn(after.search_arg);
}
+ if (after.custom_interaction_handle != NULL) {
+ after.custom_interaction_handle->user_count--;
+ BLI_assert(after.custom_interaction_handle->user_count >= 0);
+ if (after.custom_interaction_handle->user_count == 0) {
+ ui_block_interaction_update(
+ C, &after.custom_interaction_callbacks, after.custom_interaction_handle);
+ ui_block_interaction_end(
+ C, &after.custom_interaction_callbacks, after.custom_interaction_handle);
+ }
+ after.custom_interaction_handle = NULL;
+ }
+
ui_afterfunc_update_preferences_dirty(&after);
if (after.undostr[0]) {
@@ -1076,6 +1163,52 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu
data->applied = true;
}
+static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
+{
+ if (data->apply_through_extra_icon) {
+ /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons.
+ */
+ return;
+ }
+ ui_apply_but_ROW(C, block, but, data);
+}
+
+/**
+ * \note Ownership of \a properties is moved here. The #uiAfterFunc owns it now.
+ *
+ * \param context_but: The button to use context from when calling or polling the operator.
+ *
+ * \returns true if the operator was executed, otherwise false.
+ */
+static bool ui_list_invoke_item_operator(bContext *C,
+ const uiBut *context_but,
+ wmOperatorType *ot,
+ PointerRNA **properties)
+{
+ if (!ui_but_context_poll_operator(C, ot, context_but)) {
+ return false;
+ }
+
+ /* Allow the context to be set from the hovered button, so the list item draw callback can set
+ * context for the operators. */
+ ui_handle_afterfunc_add_operator_ex(ot, properties, WM_OP_INVOKE_DEFAULT, context_but);
+ return true;
+}
+
+static void ui_apply_but_LISTROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
+{
+ uiBut *listbox = ui_list_find_from_row(data->region, but);
+ if (listbox) {
+ uiList *list = listbox->custom_data;
+ if (list && list->dyn_data->custom_activate_optype) {
+ ui_list_invoke_item_operator(
+ C, but, list->dyn_data->custom_activate_optype, &list->dyn_data->custom_activate_opptr);
+ }
+ }
+
+ ui_apply_but_ROW(C, block, but, data);
+}
+
static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
{
if (!data->str) {
@@ -1290,8 +1423,8 @@ static bool ui_multibut_states_tag(uiBut *but_active,
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;
+ seg[1][0] = event->xy[0];
+ seg[1][1] = event->xy[1];
BLI_assert(data->multi_data.init == BUTTON_MULTI_INIT_SETUP);
@@ -1350,7 +1483,7 @@ static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *dat
}
/* edit buttons proportionally to eachother
- * note: if we mix buttons which are proportional and others which are not,
+ * 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))) {
@@ -1502,7 +1635,7 @@ static bool ui_drag_toggle_set_xy_xy(
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)
+ /* 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)) {
@@ -1548,7 +1681,7 @@ static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const
*/
if (drag_info->is_xy_lock_init == false) {
/* first store the buttons original coords */
- uiBut *but = ui_but_find_mouse_over_ex(region, xy_input[0], xy_input[1], true);
+ uiBut *but = ui_but_find_mouse_over_ex(region, xy_input, true, NULL, NULL);
if (but) {
if (but->flag & UI_BUT_DRAG_LOCK) {
@@ -1610,7 +1743,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void
break;
}
case MOUSEMOVE: {
- ui_drag_toggle_set(C, drag_info, &event->x);
+ ui_drag_toggle_set(C, drag_info, event->xy);
break;
}
}
@@ -1618,8 +1751,7 @@ static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void
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);
+ uiBut *but = ui_but_find_mouse_over_ex(region, drag_info->xy_init, true, NULL, NULL);
if (but) {
ui_apply_but_undo(but);
@@ -1686,7 +1818,7 @@ static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore
break;
}
uiSelectContextElem *other = &selctx_data->elems[i];
- /* TODO,. de-duplicate copy_to_selected_button */
+ /* TODO: de-duplicate copy_to_selected_button. */
if (link->ptr.data != ptr.data) {
if (use_path_from_id) {
/* Path relative to ID. */
@@ -1928,7 +2060,8 @@ static bool ui_but_drag_init(bContext *C,
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) {
+ if (abs(data->dragstartx - event->xy[0]) + abs(data->dragstarty - event->xy[1]) >
+ drag_threshold) {
button_activate_state(C, but, BUTTON_STATE_EXIT);
data->cancel = true;
#ifdef USE_DRAG_TOGGLE
@@ -1943,8 +2076,8 @@ static bool ui_but_drag_init(bContext *C,
drag_info->pushed_state = ui_drag_toggle_but_pushed_state(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);
+ copy_v2_v2_int(drag_info->xy_init, event->xy);
+ copy_v2_v2_int(drag_info->xy_last, event->xy);
/* needed for toggle drag on popups */
region_prev = CTX_wm_region(C);
@@ -1987,7 +2120,7 @@ static bool ui_but_drag_init(bContext *C,
bool valid = false;
uiDragColorHandle *drag_info = MEM_callocN(sizeof(*drag_info), __func__);
- /* TODO support more button pointer types */
+ /* 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;
@@ -2012,6 +2145,12 @@ static bool ui_but_drag_init(bContext *C,
return false;
}
}
+ else if (but->type == UI_BTYPE_TREEROW) {
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but;
+ if (tree_row_but->tree_item) {
+ UI_tree_view_item_drag_start(C, tree_row_but->tree_item);
+ }
+ }
else {
wmDrag *drag = WM_event_start_drag(
C,
@@ -2030,6 +2169,12 @@ static bool ui_but_drag_init(bContext *C,
BLI_rctf_size_x(&but->rect),
BLI_rctf_size_y(&but->rect));
}
+
+ /* Special feature for assets: We add another drag item that supports multiple assets. It
+ * gets the assets from context. */
+ if (ELEM(but->dragtype, WM_DRAG_ASSET, WM_DRAG_ID)) {
+ WM_event_start_drag(C, ICON_NONE, WM_DRAG_ASSET_LIST, NULL, 0, WM_DRAG_NOP);
+ }
}
return true;
}
@@ -2181,9 +2326,14 @@ static void ui_apply_but(
ui_apply_but_TOG(C, but, data);
break;
case UI_BTYPE_ROW:
- case UI_BTYPE_LISTROW:
ui_apply_but_ROW(C, block, but, data);
break;
+ case UI_BTYPE_TREEROW:
+ ui_apply_but_TREEROW(C, block, but, data);
+ break;
+ case UI_BTYPE_LISTROW:
+ ui_apply_but_LISTROW(C, block, but, data);
+ break;
case UI_BTYPE_DATASETROW:
ui_apply_but_ROW(C, block, but, data);
break;
@@ -2283,38 +2433,10 @@ static void ui_apply_but(
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);
- }
- }
+ if (data->custom_interaction_handle != NULL) {
+ ui_block_interaction_update(
+ C, &block->custom_interaction_callbacks, data->custom_interaction_handle);
}
}
@@ -2428,7 +2550,7 @@ static void ui_but_paste_numeric_array(bContext *C,
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 */
+ * 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');
}
@@ -2517,15 +2639,9 @@ static void ui_but_copy_text(uiBut *but, char *output, int 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);
+ BLI_assert(but->active == data);
+ UNUSED_VARS_NDEBUG(data);
+ ui_but_set_string_interactive(C, but, buf_paste);
}
static void ui_but_copy_colorband(uiBut *but)
@@ -2810,8 +2926,9 @@ static int ui_text_position_from_hidden(uiBut *but, int pos)
{
const char *butstr = (but->editstr) ? but->editstr : but->drawstr;
const char *strpos = butstr;
+ const char *str_end = butstr + strlen(butstr);
for (int i = 0; i < pos; i++) {
- strpos = BLI_str_find_next_char_utf8(strpos, NULL);
+ strpos = BLI_str_find_next_char_utf8(strpos, str_end);
}
return (strpos - butstr);
@@ -2868,6 +2985,24 @@ void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR],
/** \name Button Text Selection/Editing
* \{ */
+/**
+ * Use handling code to set a string for the button. Handles the case where the string is set for a
+ * search button while the search menu is open, so the results are updated accordingly.
+ * This is basically the same as pasting the string into the button.
+ */
+void ui_but_set_string_interactive(bContext *C, uiBut *but, const char *value)
+{
+ button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING);
+ ui_textedit_string_set(but, but->active, value);
+
+ if (but->type == UI_BTYPE_SEARCH_MENU && but->active) {
+ but->changed = true;
+ ui_searchbox_update(C, but->active->searchbox, but, true);
+ }
+
+ button_activate_state(C, but, BUTTON_STATE_EXIT);
+}
+
void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but)
{
if (!but->active) {
@@ -2943,7 +3078,7 @@ static bool ui_textedit_set_cursor_pos_foreach_glyph(const char *UNUSED(str),
/**
* \param x: Screen space cursor location - #wmEvent.x
*
- * \note ``but->block->aspect`` is used here, so drawing button style is getting scaled too.
+ * \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)
{
@@ -2963,11 +3098,6 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con
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)) {
@@ -3018,10 +3148,6 @@ static void ui_textedit_set_cursor_pos(uiBut *but, uiHandleButtonData *data, con
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);
}
@@ -3236,7 +3362,7 @@ static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const in
if (pbuf) {
if (UI_but_is_utf8(but)) {
- buf_len -= BLI_utf8_invalid_strip(pbuf, (size_t)buf_len);
+ buf_len -= BLI_str_utf8_invalid_strip(pbuf, (size_t)buf_len);
}
ui_textedit_insert_buf(but, data, pbuf, buf_len);
@@ -3268,7 +3394,7 @@ static bool ui_textedit_copypaste(uiBut *but, uiHandleButtonData *data, const in
}
#ifdef WITH_INPUT_IME
-/* enable ime, and set up uibut ime data */
+/* Enable IME, and setup #uiBut IME data. */
static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
{
/* XXX Is this really needed? */
@@ -3277,14 +3403,14 @@ static void ui_textedit_ime_begin(wmWindow *win, uiBut *UNUSED(but))
BLI_assert(win->ime_data == NULL);
/* enable IME and position to cursor, it's a trick */
- x = win->eventstate->x;
+ x = win->eventstate->xy[0];
/* flip y and move down a bit, prevent the IME panel cover the edit button */
- y = win->eventstate->y - 12;
+ y = win->eventstate->xy[1] - 12;
wm_window_IME_begin(win, x, y, 0, 0, true);
}
-/* disable ime, and clear uibut ime data */
+/* Disable IME, and clear #uiBut IME data. */
static void ui_textedit_ime_end(wmWindow *win, uiBut *UNUSED(but))
{
wm_window_IME_end(win);
@@ -3315,10 +3441,7 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
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;
- }
+ MEM_SAFE_FREE(data->str);
#ifdef USE_DRAG_MULTINUM
/* this can happen from multi-drag */
@@ -3396,6 +3519,11 @@ static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
ui_but_update(but);
+ /* Popup blocks don't support moving after creation, so don't change the view for them. */
+ if (!data->searchbox) {
+ UI_but_ensure_in_view(C, data->region, but);
+ }
+
WM_cursor_modal_set(win, WM_CURSOR_TEXT_EDIT);
#ifdef WITH_INPUT_IME
@@ -3411,7 +3539,7 @@ static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
if (but) {
if (UI_but_is_utf8(but)) {
- const int strip = BLI_utf8_invalid_strip(but->editstr, strlen(but->editstr));
+ const int strip = BLI_str_utf8_invalid_strip(but->editstr, strlen(but->editstr));
/* not a file?, strip non utf-8 chars */
if (strip) {
/* won't happen often so isn't that annoying to keep it here for a while */
@@ -3591,18 +3719,18 @@ static void ui_do_but_textedit(
/* 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);
+ inbox = ui_searchbox_inside(data->searchbox, event->xy);
}
/* 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;
+ float mx = event->xy[0];
+ float my = event->xy[1];
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);
+ ui_textedit_set_cursor_pos(but, data, event->xy[0]);
but->selsta = but->selend = but->pos;
data->sel_pos_init = but->pos;
@@ -3801,7 +3929,7 @@ static void ui_do_but_textedit(
/* 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 (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 */
@@ -3873,11 +4001,11 @@ static void ui_do_but_textedit_select(
switch (event->type) {
case MOUSEMOVE: {
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
- ui_textedit_set_cursor_select(but, data, event->x);
+ ui_textedit_set_cursor_select(but, data, event->xy[0]);
retval = WM_UI_HANDLER_BREAK;
break;
}
@@ -4069,14 +4197,17 @@ static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleBu
static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon)
{
+ but->active->apply_through_extra_icon = true;
+
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);
+ WM_operator_name_call_ptr_with_depends_on_cursor(C,
+ op_icon->optype_params->optype,
+ op_icon->optype_params->opcontext,
+ op_icon->optype_params->opptr,
+ NULL);
/* Force recreation of extra operator icons (pseudo update). */
ui_but_extra_operator_icons_free(but);
@@ -4210,7 +4341,7 @@ static uiBut *ui_but_list_row_text_activate(bContext *C,
uiButtonActivateType activate_type)
{
ARegion *region = CTX_wm_region(C);
- uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->x, event->y, true);
+ uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->xy, true, NULL, NULL);
if (labelbut && labelbut->type == UI_BTYPE_TEXT && !(labelbut->flag & UI_BUT_DISABLED)) {
/* exit listrow */
@@ -4237,7 +4368,7 @@ static uiButExtraOpIcon *ui_but_extra_operator_icon_mouse_over_get(uiBut *but,
{
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;
+ int x = event->xy[0], y = event->xy[1];
ui_window_to_block(data->region, but->block, &x, &y);
if (!BLI_rctf_isect_pt(&but->rect, x, y)) {
@@ -4284,7 +4415,7 @@ static bool ui_do_but_extra_operator_icon(bContext *C,
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. */
+ /* NOTE: 'but', 'data' may now be freed, don't access. */
return true;
}
@@ -4321,20 +4452,16 @@ static bool ui_do_but_ANY_drag_toggle(
{
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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
*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
+ /* 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);
@@ -4414,7 +4541,7 @@ static int ui_do_but_HOTKEYEVT(bContext *C,
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) {
+ if (ui_but_contains_point_px(but, but->active->region, event->xy) == false) {
/* data->cancel doesn't work, this button opens immediate */
if (but->flag & UI_BUT_IMMEDIATE) {
ui_but_value_set(but, 0);
@@ -4611,17 +4738,13 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons
/* Behave like other menu items. */
do_activate = (event->val == KM_RELEASE);
}
- else {
+ else if (!ui_do_but_extra_operator_icon(C, but, data, event)) {
/* 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;
}
@@ -4673,37 +4796,87 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons
return WM_UI_HANDLER_CONTINUE;
}
+static int ui_do_but_TREEROW(bContext *C,
+ uiBut *but,
+ uiHandleButtonData *data,
+ const wmEvent *event)
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but;
+ BLI_assert(tree_row_but->but.type == UI_BTYPE_TREEROW);
+
+ if (data->state == BUTTON_STATE_HIGHLIGHT) {
+ if (event->type == LEFTMOUSE) {
+ switch (event->val) {
+ case KM_PRESS:
+ /* Extra icons have priority, don't mess with them. */
+ if (ui_but_extra_operator_icon_mouse_over_get(but, data, event)) {
+ return WM_UI_HANDLER_BREAK;
+ }
+ button_activate_state(C, but, BUTTON_STATE_WAIT_DRAG);
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
+ return WM_UI_HANDLER_CONTINUE;
+
+ case KM_CLICK:
+ button_activate_state(C, but, BUTTON_STATE_EXIT);
+ return WM_UI_HANDLER_BREAK;
+
+ case KM_DBL_CLICK:
+ data->cancel = true;
+ UI_tree_view_item_begin_rename(tree_row_but->tree_item);
+ ED_region_tag_redraw(CTX_wm_region(C));
+ return WM_UI_HANDLER_BREAK;
+ }
+ }
+ }
+ else if (data->state == BUTTON_STATE_WAIT_DRAG) {
+ /* Let "default" button handling take care of the drag logic. */
+ return ui_do_but_EXIT(C, but, data, event);
+ }
+
+ 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 */
+ /* First handle click on icon-drag 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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
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 */
+ /* XXX: (a bit ugly) Special case handling for file-browser drag button. */
if (but->dragpoin && but->imb && ui_but_contains_point_px_icon(but, data->region, event)) {
ret = WM_UI_HANDLER_CONTINUE;
}
+ /* Same special case handling for UI lists. Return CONTINUE so that a tweak or CLICK event
+ * will be sent for the list to work with. */
+ const uiBut *listbox = ui_list_find_mouse_over(data->region, event);
+ if (listbox) {
+ const uiList *ui_list = listbox->custom_data;
+ if (ui_list && ui_list->dyn_data->custom_drag_optype) {
+ ret = WM_UI_HANDLER_CONTINUE;
+ }
+ }
button_activate_state(C, but, BUTTON_STATE_EXIT);
return ret;
}
@@ -4852,6 +5025,8 @@ static bool ui_numedit_but_NUM(uiButNumber *number_but,
return changed;
}
+ ui_block_interaction_begin_ensure(but->block->evil_C, but->block, data, false);
+
if (ui_but_is_cursor_warp(but)) {
const float softmin = but->softmin;
const float softmax = but->softmax;
@@ -5104,8 +5279,8 @@ static void ui_numedit_set_active(uiBut *but)
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;
+ int mx = data->window->eventstate->xy[0];
+ int my = data->window->eventstate->xy[1];
ui_window_to_block(data->region, but->block, &mx, &my);
if (mx < (but->rect.xmin + handle_width)) {
@@ -5145,10 +5320,10 @@ static int ui_do_but_NUM(
int retval = WM_UI_HANDLER_CONTINUE;
/* mouse location scaled to fit the UI */
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
/* mouse location kept at screen pixel coords */
- const int screen_mx = event->x;
+ const int screen_mx = event->xy[0];
BLI_assert(but->type == UI_BTYPE_NUM);
@@ -5200,7 +5375,7 @@ static int ui_do_but_NUM(
}
#ifdef USE_DRAG_MULTINUM
- copy_v2_v2_int(data->multi_data.drag_start, &event->x);
+ copy_v2_v2_int(data->multi_data.drag_start, event->xy);
#endif
}
}
@@ -5362,6 +5537,8 @@ static bool ui_numedit_but_SLI(uiBut *but,
return changed;
}
+ ui_block_interaction_begin_ensure(but->block->evil_C, but->block, data, false);
+
const PropertyScaleType scale_type = ui_but_scale_type(but);
softmin = but->softmin;
@@ -5492,8 +5669,8 @@ static int ui_do_but_SLI(
int click = 0;
int retval = WM_UI_HANDLER_CONTINUE;
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -5552,7 +5729,7 @@ static int ui_do_but_SLI(
}
}
#ifdef USE_DRAG_MULTINUM
- copy_v2_v2_int(data->multi_data.drag_start, &event->x);
+ copy_v2_v2_int(data->multi_data.drag_start, event->xy);
#endif
}
else if (data->state == BUTTON_STATE_NUM_EDITING) {
@@ -5712,8 +5889,8 @@ static int ui_do_but_SCROLL(
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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -5763,21 +5940,21 @@ static int ui_do_but_GRIP(
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
+ /* 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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
retval = WM_UI_HANDLER_BREAK;
}
@@ -5836,20 +6013,20 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co
{
if (data->state == BUTTON_STATE_HIGHLIGHT) {
- /* first handle click on icondrag type button */
+ /* First handle click on icon-drag 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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
return WM_UI_HANDLER_BREAK;
}
#endif
@@ -5896,7 +6073,7 @@ static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, co
* 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.
+ * and remove this mouse-move event.
*/
WM_event_add_mousemove(data->window);
@@ -6021,13 +6198,13 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
uiButColor *color_but = (uiButColor *)but;
if (data->state == BUTTON_STATE_HIGHLIGHT) {
- /* first handle click on icondrag type button */
+ /* First handle click on icon-drag 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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
return WM_UI_HANDLER_BREAK;
}
}
@@ -6035,8 +6212,8 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
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;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
return WM_UI_HANDLER_BREAK;
}
#endif
@@ -6062,7 +6239,7 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
}
else {
- const float fac = 0.005 * (event->y - event->prevy);
+ const float fac = 0.005 * (event->xy[1] - event->prev_xy[1]);
hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f);
}
@@ -6166,8 +6343,8 @@ static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, co
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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -6435,7 +6612,7 @@ static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but,
CLAMP(hsv[2], hsv_but->but.softmin, hsv_but->but.softmax);
break;
default:
- BLI_assert(!"invalid hsv type");
+ BLI_assert_msg(0, "invalid hsv type");
break;
}
@@ -6460,8 +6637,8 @@ 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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -6638,7 +6815,7 @@ static bool ui_numedit_but_HSVCIRCLE(uiBut *but,
ui_color_picker_hsv_to_rgb(hsv, rgb);
- if ((cpicker->use_luminosity_lock)) {
+ if (cpicker->use_luminosity_lock) {
if (!is_zero_v3(rgb)) {
normalize_v3_length(rgb, cpicker->luminosity_lock_value);
}
@@ -6736,8 +6913,8 @@ static int ui_do_but_HSVCIRCLE(
{
ColorPicker *cpicker = but->custom_data;
float *hsv = cpicker->hsv_perceptual;
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -6865,8 +7042,8 @@ static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int m
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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -6951,8 +7128,8 @@ static bool ui_numedit_but_CURVE(uiBlock *block,
CurveMapPoint *cmp = cuma->curve;
bool changed = false;
- /* evtx evty and drag coords are absolute mousecoords,
- * prevents errors when editing when layout changes */
+ /* evtx evty and drag coords are absolute mouse-coords,
+ * prevents errors when editing when layout changes. */
int mx = evtx;
int my = evty;
ui_window_to_block(data->region, block, &mx, &my);
@@ -7010,7 +7187,7 @@ static bool ui_numedit_but_CURVE(uiBlock *block,
changed = true;
#ifdef USE_CONT_MOUSE_CORRECT
- /* note: using 'cmp_last' is weak since there may be multiple points selected,
+ /* 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 */
@@ -7061,8 +7238,8 @@ static int ui_do_but_CURVE(
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -7154,10 +7331,10 @@ static int ui_do_but_CURVE(
data->dragsel = sel;
- data->dragstartx = event->x;
- data->dragstarty = event->y;
- data->draglastx = event->x;
- data->draglasty = event->y;
+ data->dragstartx = event->xy[0];
+ data->dragstarty = event->xy[1];
+ data->draglastx = event->xy[0];
+ data->draglasty = event->xy[1];
button_activate_state(C, but, BUTTON_STATE_NUM_EDITING);
return WM_UI_HANDLER_BREAK;
@@ -7165,10 +7342,15 @@ static int ui_do_but_CURVE(
}
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)) {
+ if (event->xy[0] != data->draglastx || event->xy[1] != data->draglasty) {
+
+ if (ui_numedit_but_CURVE(block,
+ but,
+ data,
+ event->xy[0],
+ event->xy[1],
+ event->ctrl != 0,
+ event->shift != 0)) {
ui_numedit_apply(C, block, but, data);
}
}
@@ -7219,8 +7401,8 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
CurveProfilePoint *pts = profile->path;
bool changed = false;
- /* evtx evty and drag coords are absolute mousecoords,
- * prevents errors when editing when layout changes */
+ /* evtx evty and drag coords are absolute mouse-coords,
+ * prevents errors when editing when layout changes. */
int mx = evtx;
int my = evty;
ui_window_to_block(data->region, block, &mx, &my);
@@ -7281,7 +7463,7 @@ static bool ui_numedit_but_CURVEPROFILE(uiBlock *block,
data->draglasty = evty;
changed = true;
#ifdef USE_CONT_MOUSE_CORRECT
- /* note: using 'cmp_last' is weak since there may be multiple points selected,
+ /* 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 */
@@ -7342,8 +7524,8 @@ 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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
@@ -7425,7 +7607,7 @@ static int ui_do_but_CURVEPROFILE(
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++) {
+ for (int i = 1; i <= BKE_curveprofile_table_size(profile); i++) {
copy_v2_v2(f_xy_prev, f_xy);
BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x);
@@ -7525,8 +7707,8 @@ static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int m
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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -7598,8 +7780,8 @@ static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx
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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -7689,8 +7871,8 @@ static bool ui_numedit_but_TRACKPREVIEW(
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;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(data->region, block, &mx, &my);
if (data->state == BUTTON_STATE_HIGHLIGHT) {
@@ -7746,7 +7928,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
/* 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)) {
+ if (data->state == BUTTON_STATE_HIGHLIGHT) {
/* handle copy and paste */
bool is_press_ctrl_but_no_shift = event->val == KM_PRESS && IS_EVENT_MOD(event, ctrl, oskey) &&
@@ -7772,8 +7954,16 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
/* handle menu */
if ((event->type == RIGHTMOUSE) && !IS_EVENT_MOD(event, shift, ctrl, alt, oskey) &&
(event->val == KM_PRESS)) {
+ /* For some button types that are typically representing entire sets of data, right-clicking
+ * to spawn the context menu should also activate the item. This makes it clear which item
+ * will be operated on.
+ * Apply the button immediately, so context menu polls get the right active item. */
+ if (ELEM(but->type, UI_BTYPE_TREEROW)) {
+ ui_apply_but(C, but->block, but, but->active, true);
+ }
+
/* RMB has two options now */
- if (ui_popup_context_menu_for_button(C, but)) {
+ if (ui_popup_context_menu_for_button(C, but, event)) {
return WM_UI_HANDLER_BREAK;
}
}
@@ -7787,11 +7977,6 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
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) &&
@@ -7830,6 +8015,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
case UI_BTYPE_DATASETROW:
retval = ui_do_but_TOG(C, but, data, event);
break;
+ case UI_BTYPE_TREEROW:
+ retval = ui_do_but_TREEROW(C, but, data, event);
+ break;
case UI_BTYPE_SCROLL:
retval = ui_do_but_SCROLL(C, block, but, data, event);
break;
@@ -7853,6 +8041,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
case UI_BTYPE_IMAGE:
case UI_BTYPE_PROGRESS_BAR:
case UI_BTYPE_NODE_SOCKET:
+ case UI_BTYPE_PREVIEW_TILE:
retval = ui_do_but_EXIT(C, but, data, event);
break;
case UI_BTYPE_HISTOGRAM:
@@ -7939,7 +8128,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
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;
+ data->multi_data.drag_lock_x = event->xy[0];
}
else {
data->multi_data.init = BUTTON_MULTI_INIT_DISABLE;
@@ -7952,9 +8141,9 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
/* 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) &&
+ ((abs(data->multi_data.drag_lock_x - event->xy[0]) > margin_x) &&
/* Just to be sure, check we're dragging more horizontally then vertically. */
- abs(event->prevx - event->x) > abs(event->prevy - event->y))) {
+ abs(event->prev_xy[0] - event->xy[0]) > abs(event->prev_xy[1] - event->xy[1]))) {
if (data->multi_data.has_mbuts) {
ui_multibut_states_create(but, data);
data->multi_data.init = BUTTON_MULTI_INIT_ENABLE;
@@ -8042,7 +8231,11 @@ static ARegion *ui_but_tooltip_init(
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);
+ const wmWindow *win = CTX_wm_window(C);
+ uiButExtraOpIcon *extra_icon = ui_but_extra_operator_icon_mouse_over_get(
+ but, but->active, win->eventstate);
+
+ return UI_tooltip_create_from_button_or_extra_icon(C, region, but, extra_icon, is_label);
}
return NULL;
}
@@ -8234,11 +8427,21 @@ static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState s
}
}
- /* wait for mousemove to enable drag */
+ /* Wait for mouse-move to enable drag. */
if (state == BUTTON_STATE_WAIT_DRAG) {
but->flag &= ~UI_SELECT;
}
+ if (state == BUTTON_STATE_TEXT_EDITING) {
+ ui_block_interaction_begin_ensure(C, but->block, data, true);
+ }
+ else if (state == BUTTON_STATE_EXIT) {
+ if (data->state == BUTTON_STATE_NUM_EDITING) {
+ /* This happens on pasting values for example. */
+ ui_block_interaction_begin_ensure(C, but->block, data, true);
+ }
+ }
+
data->state = state;
if (state != BUTTON_STATE_EXIT) {
@@ -8467,21 +8670,33 @@ static void button_activate_exit(
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;
+ if ((but->flag & UI_BUT_DRAG_MULTI) == 0) {
+ if (data->custom_interaction_handle != NULL) {
+ /* Should only set when the button is modal. */
+ BLI_assert(but->active != NULL);
+ data->custom_interaction_handle->user_count--;
+
+ BLI_assert(data->custom_interaction_handle->user_count >= 0);
+ if (data->custom_interaction_handle->user_count == 0) {
+ ui_block_interaction_end(
+ C, &but->block->custom_interaction_callbacks, data->custom_interaction_handle);
+ }
+ data->custom_interaction_handle = NULL;
+ }
}
+ /* clean up button */
+ MEM_SAFE_FREE(but->active);
+
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
+ /* Adds empty mouse-move in queue for re-initialize 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 */
+ * at this point the mouse may be over a button in another region. */
if (mousemove) {
WM_event_add_mousemove(CTX_wm_window(C));
}
@@ -8580,7 +8795,7 @@ 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);
+ return ui_block_find_mouse_over_ex(region, xy, only_clip);
}
/**
@@ -8614,9 +8829,9 @@ 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
+ /* TODO(campbell): look into a better way to handle the button change
* currently this is mainly so reset defaults works for the
- * operator redo panel - campbell */
+ * operator redo panel. */
uiBlock *block = activebut->block;
if (block->handle_func) {
block->handle_func(C, block->handle_func_arg, activebut->retval);
@@ -8722,6 +8937,26 @@ void UI_context_update_anim_flag(const bContext *C)
}
}
+/**
+ * In some cases we may want to update the view (#View2D) in-between layout definition and drawing.
+ * E.g. to make sure a button is visible while editing.
+ */
+void ui_but_update_view_for_active(const bContext *C, const uiBlock *block)
+{
+ uiBut *active_but = ui_block_active_but_get(block);
+ if (!active_but || !active_but->active || !active_but->changed || active_but->block != block) {
+ return;
+ }
+ /* If there is a search popup attached to the button, don't change the view. The popups don't
+ * support updating the position to the button position nicely. */
+ uiHandleButtonData *data = active_but->active;
+ if (data->searchbox) {
+ return;
+ }
+
+ UI_but_ensure_in_view(C, active_but->active->region, active_but);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
@@ -8782,7 +9017,7 @@ void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but)
event.val = KM_PRESS;
event.is_repeat = false;
event.customdata = but;
- event.customdatafree = false;
+ event.customdata_free = false;
ui_do_button(C, but->block, but, &event);
}
@@ -8805,7 +9040,7 @@ void ui_but_execute_begin(struct bContext *UNUSED(C),
{
BLI_assert(region != NULL);
BLI_assert(BLI_findindex(&region->uiblocks, but->block) != -1);
- /* note: ideally we would not have to change 'but->active' however
+ /* 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;
@@ -8959,7 +9194,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
/* 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)) {
+ !ui_but_contains_point_px(but, region, event->xy)) {
exit = true;
}
else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) {
@@ -8970,7 +9205,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
data->cancel = true;
button_activate_state(C, but, BUTTON_STATE_EXIT);
}
- else if (event->x != event->prevx || event->y != event->prevy) {
+ else if (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]) {
/* Re-enable tool-tip on mouse move. */
ui_blocks_set_tooltips(region, true);
button_tooltip_timer_reset(C, but);
@@ -8987,7 +9222,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
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) {
+ if (ui_but_contains_point_px(but, region, event->xy) || but->active) {
button_activate_state(C, but, BUTTON_STATE_MENU_OPEN);
}
}
@@ -9037,12 +9272,12 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
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)) {
+ if (ui_but_contains_point_px(but, region, event->xy)) {
/* 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) <=
+ if (len_manhattan_v2v2_int(event->xy, event->prev_xy) <=
WM_EVENT_CURSOR_MOTION_THRESHOLD) {
/* pass */
}
@@ -9095,7 +9330,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
uiBut *bt;
if (data->menu && data->menu->region) {
- if (ui_region_contains_point_px(data->menu->region, event->x, event->y)) {
+ if (ui_region_contains_point_px(data->menu->region, event->xy)) {
break;
}
}
@@ -9174,7 +9409,7 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
*
* 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. */
+ * This avoids adding mouse-moves, 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);
@@ -9186,6 +9421,148 @@ static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
return retval;
}
+/**
+ * Activate the underlying list-row button, so the row is highlighted.
+ * Early exits if \a activate_dragging is true, but the custom drag operator fails to execute.
+ * Gives the wanted behavior where the item is activated on a tweak event when the custom drag
+ * operator is executed.
+ */
+static int ui_list_activate_hovered_row(bContext *C,
+ ARegion *region,
+ const uiList *ui_list,
+ const wmEvent *event,
+ bool activate_dragging)
+{
+ const bool do_drag = activate_dragging && ui_list->dyn_data->custom_drag_optype;
+
+ if (do_drag) {
+ const uiBut *hovered_but = ui_but_find_mouse_over(region, event);
+ if (!ui_list_invoke_item_operator(C,
+ hovered_but,
+ ui_list->dyn_data->custom_drag_optype,
+ &ui_list->dyn_data->custom_drag_opptr)) {
+ return WM_UI_HANDLER_CONTINUE;
+ }
+ }
+
+ const int *mouse_xy = ISTWEAK(event->type) ? event->prev_click_xy : event->xy;
+ uiBut *listrow = ui_list_row_find_mouse_over(region, mouse_xy);
+ if (listrow) {
+ wmOperatorType *custom_activate_optype = ui_list->dyn_data->custom_activate_optype;
+
+ /* Hacky: Ensure the custom activate operator is not called when the custom drag operator
+ * was. Only one should run! */
+ if (activate_dragging && do_drag) {
+ ((uiList *)ui_list)->dyn_data->custom_activate_optype = NULL;
+ }
+
+ /* Simulate click on listrow button itself (which may be overlapped by another button). Also
+ * calls the custom activate operator (ui_list->custom_activate_opname). */
+ UI_but_execute(C, region, listrow);
+
+ ((uiList *)ui_list)->dyn_data->custom_activate_optype = custom_activate_optype;
+ }
+
+ return WM_UI_HANDLER_BREAK;
+}
+
+static bool ui_list_is_hovering_draggable_but(bContext *C,
+ const uiList *list,
+ const ARegion *region,
+ const wmEvent *event)
+{
+ /* On a tweak event, uses the coordinates from where tweaking was started. */
+ const int *mouse_xy = ISTWEAK(event->type) ? event->prev_click_xy : event->xy;
+ const uiBut *hovered_but = ui_but_find_mouse_over_ex(region, mouse_xy, false, NULL, NULL);
+
+ if (list->dyn_data->custom_drag_optype) {
+ if (ui_but_context_poll_operator(C, list->dyn_data->custom_drag_optype, hovered_but)) {
+ return true;
+ }
+ }
+
+ return (hovered_but && hovered_but->dragpoin);
+}
+
+static int ui_list_handle_click_drag(bContext *C,
+ const uiList *ui_list,
+ ARegion *region,
+ const wmEvent *event)
+{
+ if (!ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) {
+ return WM_HANDLER_CONTINUE;
+ }
+
+ int retval = WM_HANDLER_CONTINUE;
+
+ const bool is_draggable = ui_list_is_hovering_draggable_but(C, ui_list, region, event);
+ bool activate = false;
+ bool activate_dragging = false;
+
+ if (event->type == EVT_TWEAK_L) {
+ if (is_draggable) {
+ activate_dragging = true;
+ activate = true;
+ }
+ }
+ /* #KM_CLICK is only sent after an uncaught release event, so the foreground button gets all
+ * regular events (including mouse presses to start dragging) and this part only kicks in if it
+ * hasn't handled the release event. Note that if there's no overlaid button, the row selects
+ * on the press event already via regular #UI_BTYPE_LISTROW handling. */
+ else if ((event->type == LEFTMOUSE) && (event->val == KM_CLICK)) {
+ activate = true;
+ }
+
+ if (activate) {
+ retval = ui_list_activate_hovered_row(C, region, ui_list, event, activate_dragging);
+ }
+
+ return retval;
+}
+
+static void ui_list_activate_row_from_index(
+ bContext *C, ARegion *region, uiBut *listbox, uiList *ui_list, int index)
+{
+ uiBut *new_active_row = ui_list_row_find_from_index(region, index, listbox);
+ if (new_active_row) {
+ /* Preferred way to update the active item, also calls the custom activate operator
+ * (#uiList.custom_activate_opname). */
+ UI_but_execute(C, region, new_active_row);
+ }
+ else {
+ /* A bit ugly, set the active index in RNA directly. That's because a button that's
+ * scrolled away in the list box isn't created at all.
+ * The custom activate operator (#uiList.custom_activate_opname) is not called in this case
+ * (which may need the row button context). */
+ RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, index);
+ RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
+ ui_apply_but_undo(listbox);
+ }
+
+ ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
+}
+
+static int ui_list_get_increment(const uiList *ui_list, const int type, const int columns)
+{
+ int increment = 0;
+
+ /* Handle column offsets for grid layouts. */
+ if (ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) &&
+ ELEM(ui_list->layout_type, UILST_LAYOUT_GRID, UILST_LAYOUT_BIG_PREVIEW_GRID)) {
+ increment = (type == EVT_UPARROWKEY) ? -columns : columns;
+ }
+ else {
+ /* Left or right in grid layouts or any direction in single column layouts increments by 1. */
+ increment = ELEM(type, EVT_UPARROWKEY, EVT_LEFTARROWKEY, WHEELUPMOUSE) ? -1 : 1;
+ }
+
+ if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) {
+ increment *= -1;
+ }
+
+ return increment;
+}
+
static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox)
{
int retval = WM_UI_HANDLER_CONTINUE;
@@ -9199,8 +9576,8 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
}
uiListDyn *dyn_data = ui_list->dyn_data;
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(region, listbox->block, &mx, &my);
/* Convert pan to scroll-wheel. */
@@ -9219,22 +9596,19 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
}
}
- if (val == KM_PRESS) {
- if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) &&
+ if (ELEM(event->type, LEFTMOUSE, EVT_TWEAK_L)) {
+ retval = ui_list_handle_click_drag(C, ui_list, region, event);
+ }
+ else if (val == KM_PRESS) {
+ if ((ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY, EVT_LEFTARROWKEY, EVT_RIGHTARROWKEY) &&
!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;
+ int value, min, max;
- /* 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;
- }
+ const int inc = ui_list_get_increment(ui_list, type, dyn_data->columns);
if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) {
/* If we have a display order different from
@@ -9281,12 +9655,7 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
CLAMP(value, min, max);
if (value != value_orig) {
- RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value);
- RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
-
- ui_apply_but_undo(listbox);
-
- ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
+ ui_list_activate_row_from_index(C, region, listbox, ui_list, value);
redraw = true;
}
retval = WM_UI_HANDLER_BREAK;
@@ -9322,6 +9691,38 @@ static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *regi
return retval;
}
+static int ui_handle_tree_hover(const wmEvent *event, const ARegion *region)
+{
+ bool has_treerows = false;
+ LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
+ /* Avoid unnecessary work: Tree-rows are assumed to be inside tree-views. */
+ if (BLI_listbase_is_empty(&block->views)) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ if (but->type == UI_BTYPE_TREEROW) {
+ but->flag &= ~UI_ACTIVE;
+ has_treerows = true;
+ }
+ }
+ }
+
+ if (!has_treerows) {
+ /* Avoid unnecessary lookup. */
+ return WM_UI_HANDLER_CONTINUE;
+ }
+
+ /* Always highlight the hovered tree-row, even if the mouse hovers another button inside of it.
+ */
+ uiBut *hovered_row_but = ui_tree_row_find_mouse_over(region, event->xy);
+ if (hovered_row_but) {
+ hovered_row_but->flag |= UI_ACTIVE;
+ }
+
+ return WM_UI_HANDLER_CONTINUE;
+}
+
static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but)
{
uiHandleButtonData *data = but->active;
@@ -9357,8 +9758,7 @@ static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, u
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)) {
+ if (event->type == MOUSEMOVE && ui_but_contains_point_px(but, data->region, event->xy)) {
button_activate_state(C, but, BUTTON_STATE_HIGHLIGHT);
}
else {
@@ -9498,7 +9898,7 @@ static bool ui_mouse_motion_towards_check(uiBlock *block,
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);
+ copy_v2_v2_int(keynav->event_xy, event->xy);
}
/**
* Return true if key-input isn't blocking mouse-motion,
@@ -9507,7 +9907,7 @@ static void ui_mouse_motion_keynav_init(struct uiKeyNavLock *keynav, const wmEve
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)) {
+ (len_manhattan_v2v2_int(keynav->event_xy, event->xy) > BUTTON_KEYNAV_PX_LIMIT)) {
keynav->is_keynav = false;
}
@@ -9697,13 +10097,13 @@ static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlock
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)) {
+ else if (!ui_region_contains_point_px(but->active->region, event->xy)) {
/* 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)) {
+ if (!ui_but_contains_point_px(but, but->active->region, event->xy)) {
but = NULL;
}
}
@@ -9777,8 +10177,8 @@ static int ui_handle_menu_event(bContext *C,
int retval = WM_UI_HANDLER_CONTINUE;
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(region, block, &mx, &my);
/* check if mouse is inside block */
@@ -9799,8 +10199,8 @@ static int ui_handle_menu_event(bContext *C,
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);
+ sub_v2_v2v2_int(mdiff, event->xy, menu->grab_xy_prev);
+ copy_v2_v2_int(menu->grab_xy_prev, event->xy);
add_v2_v2v2_int(menu->popup_create_vars.event_xy, menu->popup_create_vars.event_xy, mdiff);
@@ -9817,7 +10217,7 @@ static int ui_handle_menu_event(bContext *C,
/* 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);
+ ui_mouse_motion_towards_reinit(menu, event->xy);
}
}
else if (event->type == TIMER) {
@@ -9829,7 +10229,7 @@ static int ui_handle_menu_event(bContext *C,
/* 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);
+ ui_mouse_motion_towards_init(menu, event->xy);
}
/* add menu scroll timer, if needed */
@@ -9912,14 +10312,14 @@ static int ui_handle_menu_event(bContext *C,
retval = WM_UI_HANDLER_BREAK;
break;
- /* Smooth scrolling for popovers. */
+ /* Smooth scrolling for pocopy_v2_v2_int(&povers. */
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;
+ const float dy = event->xy[1] - event->prev_xy[1];
if (dy != 0.0f) {
ui_menu_scroll_apply_offset_y(region, block, dy);
@@ -10242,7 +10642,8 @@ static int ui_handle_menu_event(bContext *C,
menu->menuretval = UI_RETURN_OUT;
}
}
- else if (saferct && !BLI_rctf_isect_pt(&saferct->parent, event->x, event->y)) {
+ else if (saferct && !BLI_rctf_isect_pt(
+ &saferct->parent, (float)event->xy[0], (float)event->xy[1])) {
if (block->flag & UI_BLOCK_OUT_1) {
menu->menuretval = UI_RETURN_OK;
}
@@ -10255,7 +10656,7 @@ static int ui_handle_menu_event(bContext *C,
/* 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. */
+ /* NOTE: we could check the cursor is over the parent button. */
menu->menuretval = UI_RETURN_CANCEL;
retval = WM_UI_HANDLER_CONTINUE;
}
@@ -10301,13 +10702,13 @@ static int ui_handle_menu_event(bContext *C,
#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_contains_point_px(but, region, event->xy)) {
if (but) {
UI_but_tooltip_timer_remove(C, but);
}
menu->is_grab = true;
- copy_v2_v2_int(menu->grab_xy_prev, &event->x);
+ copy_v2_v2_int(menu->grab_xy_prev, event->xy);
retval = WM_UI_HANDLER_BREAK;
}
}
@@ -10318,7 +10719,7 @@ static int ui_handle_menu_event(bContext *C,
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);
+ ui_mouse_motion_towards_check(block, menu, event->xy, is_parent_inside == false);
/* Check for all parent rects, enables arrow-keys to be used. */
for (saferct = block->saferct.first; saferct; saferct = saferct->next) {
@@ -10326,10 +10727,10 @@ static int ui_handle_menu_event(bContext *C,
* 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)) {
+ if (BLI_rctf_isect_pt(&saferct->parent, (float)event->xy[0], (float)event->xy[1])) {
break;
}
- if (BLI_rctf_isect_pt(&saferct->safety, (float)event->x, (float)event->y)) {
+ if (BLI_rctf_isect_pt(&saferct->safety, (float)event->xy[0], (float)event->xy[1])) {
break;
}
}
@@ -10373,7 +10774,7 @@ static int ui_handle_menu_event(bContext *C,
}
#endif
- /* Don't handle double click events, rehandle as regular press/release. */
+ /* Don't handle double click events, re-handle as regular press/release. */
if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) {
return retval;
}
@@ -10430,7 +10831,7 @@ static int ui_handle_menu_return_submenu(bContext *C,
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);
+ ui_mouse_motion_towards_reinit(menu, event->xy);
}
if (menu->menuretval) {
@@ -10538,7 +10939,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle
const double duration = menu->scrolltimer->duration;
- float event_xy[2] = {event->x, event->y};
+ float event_xy[2] = {UNPACK2(event->xy)};
ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]);
@@ -10715,7 +11116,7 @@ static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle
case EVT_XKEY:
case EVT_YKEY:
case EVT_ZKEY: {
- if ((event->val == KM_PRESS || event->val == KM_DBL_CLICK) &&
+ if ((ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) &&
!IS_EVENT_MOD(event, shift, ctrl, oskey)) {
LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
if (but->menu_key == event->type) {
@@ -10793,8 +11194,8 @@ static int ui_handle_menus_recursive(bContext *C,
if (do_recursion) {
if (is_parent_inside == false) {
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(menu->region, block, &mx, &my);
inside = BLI_rctf_isect_pt(&block->rect, mx, my);
}
@@ -10860,7 +11261,7 @@ static int ui_handle_menus_recursive(bContext *C,
}
if (do_towards_reinit) {
- ui_mouse_motion_towards_reinit(menu, &event->x);
+ ui_mouse_motion_towards_reinit(menu, event->xy);
}
return retval;
@@ -10921,10 +11322,15 @@ static int ui_region_handler(bContext *C, const wmEvent *event, void *UNUSED(use
}
/* Re-enable tool-tips. */
- if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
+ if (event->type == MOUSEMOVE &&
+ (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1])) {
ui_blocks_set_tooltips(region, true);
}
+ /* Always do this, to reliably update tree-row highlighting, even if the mouse hovers a button
+ * inside the row (it's an overlapping layout). */
+ ui_handle_tree_hover(event, region);
+
/* delayed apply callbacks */
ui_apply_but_funcs_after(C);
@@ -11018,7 +11424,8 @@ static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *UNUSE
}
/* Re-enable tool-tips. */
- if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
+ if (event->type == MOUSEMOVE &&
+ (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1])) {
ui_blocks_set_tooltips(region, true);
}
@@ -11112,7 +11519,8 @@ static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata)
}
else {
/* Re-enable tool-tips */
- if (event->type == MOUSEMOVE && (event->x != event->prevx || event->y != event->prevy)) {
+ if (event->type == MOUSEMOVE &&
+ (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1])) {
ui_blocks_set_tooltips(menu->region, true);
}
}
@@ -11282,20 +11690,25 @@ void UI_screen_free_active_but(const bContext *C, bScreen *screen)
}
}
-/* returns true if highlighted button allows drop of names */
-/* called in region context */
-bool UI_but_active_drop_name(bContext *C)
+uiBut *UI_but_active_drop_name_button(const 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 but;
}
}
- return false;
+ return NULL;
+}
+
+/* returns true if highlighted button allows drop of names */
+/* called in region context */
+bool UI_but_active_drop_name(const bContext *C)
+{
+ return UI_but_active_drop_name_button(C) != NULL;
}
bool UI_but_active_drop_color(bContext *C)
@@ -11314,3 +11727,100 @@ bool UI_but_active_drop_color(bContext *C)
}
/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name UI Block Interaction API
+ * \{ */
+
+void UI_block_interaction_set(uiBlock *block, uiBlockInteraction_CallbackData *callbacks)
+{
+ block->custom_interaction_callbacks = *callbacks;
+}
+
+static uiBlockInteraction_Handle *ui_block_interaction_begin(bContext *C,
+ uiBlock *block,
+ const bool is_click)
+{
+ BLI_assert(block->custom_interaction_callbacks.begin_fn != NULL);
+ uiBlockInteraction_Handle *interaction = MEM_callocN(sizeof(*interaction), __func__);
+
+ int unique_retval_ids_len = 0;
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ if (but->active || (but->flag & UI_BUT_DRAG_MULTI)) {
+ unique_retval_ids_len++;
+ }
+ }
+
+ int *unique_retval_ids = MEM_mallocN(sizeof(*unique_retval_ids) * unique_retval_ids_len,
+ __func__);
+ unique_retval_ids_len = 0;
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ if (but->active || (but->flag & UI_BUT_DRAG_MULTI)) {
+ unique_retval_ids[unique_retval_ids_len++] = but->retval;
+ }
+ }
+
+ if (unique_retval_ids_len > 1) {
+ qsort(unique_retval_ids, unique_retval_ids_len, sizeof(int), BLI_sortutil_cmp_int);
+ unique_retval_ids_len = BLI_array_deduplicate_ordered(unique_retval_ids,
+ unique_retval_ids_len);
+ unique_retval_ids = MEM_reallocN(unique_retval_ids,
+ sizeof(*unique_retval_ids) * unique_retval_ids_len);
+ }
+
+ interaction->params.is_click = is_click;
+ interaction->params.unique_retval_ids = unique_retval_ids;
+ interaction->params.unique_retval_ids_len = unique_retval_ids_len;
+
+ interaction->user_data = block->custom_interaction_callbacks.begin_fn(
+ C, &interaction->params, block->custom_interaction_callbacks.arg1);
+ return interaction;
+}
+
+static void ui_block_interaction_end(bContext *C,
+ uiBlockInteraction_CallbackData *callbacks,
+ uiBlockInteraction_Handle *interaction)
+{
+ BLI_assert(callbacks->end_fn != NULL);
+ callbacks->end_fn(C, &interaction->params, callbacks->arg1, interaction->user_data);
+ MEM_freeN(interaction->params.unique_retval_ids);
+ MEM_freeN(interaction);
+}
+
+static void ui_block_interaction_update(bContext *C,
+ uiBlockInteraction_CallbackData *callbacks,
+ uiBlockInteraction_Handle *interaction)
+{
+ BLI_assert(callbacks->update_fn != NULL);
+ callbacks->update_fn(C, &interaction->params, callbacks->arg1, interaction->user_data);
+}
+
+/**
+ * \note #ui_block_interaction_begin cannot be called when setting the button state
+ * (e.g. #BUTTON_STATE_NUM_EDITING) for the following reasons.
+ *
+ * - Other buttons may still be activated using #UI_BUT_DRAG_MULTI
+ * which is necessary before gathering all the #uiBut.retval values to initialize
+ * #uiBlockInteraction_Params.unique_retval_ids.
+ * - When clicking on a number button it's not known if the event is a click or a drag.
+ *
+ * Instead, it must be called immediately before the drag action begins.
+ */
+static void ui_block_interaction_begin_ensure(bContext *C,
+ uiBlock *block,
+ uiHandleButtonData *data,
+ const bool is_click)
+{
+ if (data->custom_interaction_handle) {
+ return;
+ }
+ if (block->custom_interaction_callbacks.begin_fn == NULL) {
+ return;
+ }
+
+ uiBlockInteraction_Handle *interaction = ui_block_interaction_begin(C, block, is_click);
+ interaction->user_count = 1;
+ data->custom_interaction_handle = interaction;
+}
+
+/** \} */
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index 4defbed940e..f849ec55e4f 100644
--- a/source/blender/editors/interface/interface_icons.c
+++ b/source/blender/editors/interface/interface_icons.c
@@ -47,6 +47,7 @@
#include "DNA_gpencil_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
+#include "DNA_sequence_types.h"
#include "DNA_space_types.h"
#include "RNA_access.h"
@@ -66,6 +67,7 @@
#include "ED_datafiles.h"
#include "ED_keyframes_draw.h"
+#include "ED_keyframes_keylist.h"
#include "ED_render.h"
#include "UI_interface.h"
@@ -251,66 +253,33 @@ static void def_internal_vicon(int icon_id, VectorDrawFunc drawFunc)
/* Utilities */
-static void viconutil_set_point(int pt[2], int x, int y)
-{
- pt[0] = x;
- pt[1] = y;
-}
-
-static void vicon_small_tri_right_draw(int x, int y, int w, int UNUSED(h), float alpha)
-{
- int pts[3][2];
- const int cx = x + w / 2 - 4;
- const int cy = y + w / 2;
- const int d = w / 5, d2 = w / 7;
-
- viconutil_set_point(pts[0], cx - d2, cy + d);
- viconutil_set_point(pts[1], cx - d2, cy - d);
- viconutil_set_point(pts[2], cx + d2, cy);
-
- uint pos = GPU_vertformat_attr_add(
- immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
- immUniformColor4f(0.2f, 0.2f, 0.2f, alpha);
-
- immBegin(GPU_PRIM_TRIS, 3);
- immVertex2iv(pos, pts[0]);
- immVertex2iv(pos, pts[1]);
- immVertex2iv(pos, pts[2]);
- immEnd();
-
- immUnbindProgram();
-}
-
static void vicon_keytype_draw_wrapper(
int x, int y, int w, int h, float alpha, short key_type, short handle_type)
{
- /* init dummy theme state for Action Editor - where these colors are defined
- * (since we're doing this offscreen, free from any particular space_id)
- */
+ /* Initialize dummy theme state for Action Editor - where these colors are defined
+ * (since we're doing this off-screen, free from any particular space_id). */
struct bThemeState theme_state;
UI_Theme_Store(&theme_state);
UI_SetTheme(SPACE_ACTION, RGN_TYPE_WINDOW);
- /* the "x" and "y" given are the bottom-left coordinates of the icon,
- * while the draw_keyframe_shape() function needs the midpoint for
- * the keyframe
- */
+ /* The "x" and "y" given are the bottom-left coordinates of the icon,
+ * while the #draw_keyframe_shape() function needs the midpoint for the keyframe. */
const float xco = x + w / 2 + 0.5f;
const float yco = y + h / 2 + 0.5f;
GPUVertFormat *format = immVertexFormat();
- const uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- const uint size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
- uint color_id = GPU_vertformat_attr_add(
+ KeyframeShaderBindings sh_bindings;
+ sh_bindings.pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+ sh_bindings.size_id = GPU_vertformat_attr_add(format, "size", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
+ sh_bindings.color_id = GPU_vertformat_attr_add(
format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
- uint outline_color_id = GPU_vertformat_attr_add(
+ sh_bindings.outline_color_id = GPU_vertformat_attr_add(
format, "outlineColor", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
- const uint flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
+ sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 1.0f);
immUniform2f("ViewportSize", -1.0f, -1.0f);
immBegin(GPU_PRIM_POINTS, 1);
@@ -328,11 +297,7 @@ static void vicon_keytype_draw_wrapper(
key_type,
KEYFRAME_SHAPE_BOTH,
alpha,
- pos_id,
- size_id,
- color_id,
- outline_color_id,
- flags_id,
+ &sh_bindings,
handle_type,
KEYFRAME_EXTREME_NONE);
@@ -485,6 +450,35 @@ DEF_ICON_COLLECTION_COLOR_DRAW(08, COLLECTION_COLOR_08);
# undef DEF_ICON_COLLECTION_COLOR_DRAW
+static void vicon_strip_color_draw(
+ short color_tag, int x, int y, int w, int UNUSED(h), float UNUSED(alpha))
+{
+ bTheme *btheme = UI_GetTheme();
+ const ThemeStripColor *strip_color = &btheme->strip_color[color_tag];
+
+ const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w;
+
+ UI_icon_draw_ex(x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true);
+}
+
+# define DEF_ICON_STRIP_COLOR_DRAW(index, color) \
+ static void vicon_strip_color_draw_##index(int x, int y, int w, int h, float alpha) \
+ { \
+ vicon_strip_color_draw(color, x, y, w, h, alpha); \
+ }
+
+DEF_ICON_STRIP_COLOR_DRAW(01, SEQUENCE_COLOR_01);
+DEF_ICON_STRIP_COLOR_DRAW(02, SEQUENCE_COLOR_02);
+DEF_ICON_STRIP_COLOR_DRAW(03, SEQUENCE_COLOR_03);
+DEF_ICON_STRIP_COLOR_DRAW(04, SEQUENCE_COLOR_04);
+DEF_ICON_STRIP_COLOR_DRAW(05, SEQUENCE_COLOR_05);
+DEF_ICON_STRIP_COLOR_DRAW(06, SEQUENCE_COLOR_06);
+DEF_ICON_STRIP_COLOR_DRAW(07, SEQUENCE_COLOR_07);
+DEF_ICON_STRIP_COLOR_DRAW(08, SEQUENCE_COLOR_08);
+DEF_ICON_STRIP_COLOR_DRAW(09, SEQUENCE_COLOR_09);
+
+# undef DEF_ICON_STRIP_COLOR_DRAW
+
/* Dynamically render icon instead of rendering a plain color to a texture/buffer
* This is not strictly a "vicon", as it needs access to icon->obj to get the color info,
* but it works in a very similar way.
@@ -957,8 +951,6 @@ static void init_internal_icons(void)
}
}
- def_internal_vicon(ICON_SMALL_TRI_RIGHT_VEC, vicon_small_tri_right_draw);
-
def_internal_vicon(ICON_KEYTYPE_KEYFRAME_VEC, vicon_keytype_keyframe_draw);
def_internal_vicon(ICON_KEYTYPE_BREAKDOWN_VEC, vicon_keytype_breakdown_draw);
def_internal_vicon(ICON_KEYTYPE_EXTREME_VEC, vicon_keytype_extreme_draw);
@@ -1000,6 +992,16 @@ static void init_internal_icons(void)
def_internal_vicon(ICON_COLLECTION_COLOR_06, vicon_collection_color_draw_06);
def_internal_vicon(ICON_COLLECTION_COLOR_07, vicon_collection_color_draw_07);
def_internal_vicon(ICON_COLLECTION_COLOR_08, vicon_collection_color_draw_08);
+
+ def_internal_vicon(ICON_SEQUENCE_COLOR_01, vicon_strip_color_draw_01);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_02, vicon_strip_color_draw_02);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_03, vicon_strip_color_draw_03);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_04, vicon_strip_color_draw_04);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_05, vicon_strip_color_draw_05);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_06, vicon_strip_color_draw_06);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_07, vicon_strip_color_draw_07);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_08, vicon_strip_color_draw_08);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_09, vicon_strip_color_draw_09);
}
static void init_iconfile_list(struct ListBase *list)
@@ -1016,7 +1018,7 @@ static void init_iconfile_list(struct ListBase *list)
int index = 1;
for (int i = 0; i < totfile; i++) {
- if ((dir[i].type & S_IFREG)) {
+ if (dir[i].type & S_IFREG) {
const char *filename = dir[i].relname;
if (BLI_path_extension_check(filename, ".png")) {
@@ -1180,7 +1182,7 @@ static DrawInfo *icon_ensure_drawinfo(Icon *icon)
return di;
}
-/* note!, returns unscaled by DPI */
+/* NOTE:, returns unscaled by DPI. */
int UI_icon_get_width(int icon_id)
{
Icon *icon = BKE_icon_get(icon_id);
@@ -1346,8 +1348,8 @@ void ui_icon_ensure_deferred(const bContext *C, const int icon_id, const bool bi
case ICON_TYPE_PREVIEW: {
ID *id = (icon->id_type != 0) ? icon->obj : NULL;
PreviewImage *prv = id ? BKE_previewimg_id_ensure(id) : icon->obj;
- /* Using jobs for screen previews crashes due to offscreen rendering.
- * XXX would be nicer if PreviewImage could store if it supports jobs */
+ /* Using jobs for screen previews crashes due to off-screen rendering.
+ * XXX: would be nicer if #PreviewImage could store if it supports jobs. */
const bool use_jobs = !id || (GS(id->name) != ID_SCR);
if (prv) {
@@ -1480,6 +1482,78 @@ PreviewImage *UI_icon_to_preview(int icon_id)
return NULL;
}
+/**
+ * Version of #icon_draw_rect() that uses the GPU for scaling. This is only used for
+ * #ICON_TYPE_IMBUF because it's a backported fix for performance issues, see T92922. Only
+ * File/Asset Browser use #ICON_TYPE_IMBUF right now, which makes implications more predictable.
+ *
+ * TODO(Julian): This code is mostly duplicated. #icon_draw_rect() should be ported to use the GPU
+ * instead (D13144).
+ */
+static void icon_draw_rect_fast(float x,
+ float y,
+ int w,
+ int h,
+ float UNUSED(aspect),
+ int rw,
+ int rh,
+ uint *rect,
+ float alpha,
+ const float desaturate)
+{
+ int draw_w = w;
+ int draw_h = h;
+ int draw_x = x;
+ /* We need to round y, to avoid the icon jittering in some cases. */
+ int draw_y = round_fl_to_int(y);
+
+ /* sanity check */
+ if (w <= 0 || h <= 0 || w > 2000 || h > 2000) {
+ printf("%s: icons are %i x %i pixels?\n", __func__, w, h);
+ BLI_assert_msg(0, "invalid icon size");
+ return;
+ }
+ /* modulate color */
+ const float col[4] = {alpha, alpha, alpha, alpha};
+
+ float scale_x = 1.0f;
+ float scale_y = 1.0f;
+ /* rect contains image in 'rendersize', we only scale if needed */
+ if (rw != w || rh != h) {
+ /* preserve aspect ratio and center */
+ if (rw > rh) {
+ draw_w = w;
+ draw_h = (int)(((float)rh / (float)rw) * (float)w);
+ draw_y += (h - draw_h) / 2;
+ }
+ else if (rw < rh) {
+ draw_w = (int)(((float)rw / (float)rh) * (float)h);
+ draw_h = h;
+ draw_x += (w - draw_w) / 2;
+ }
+ scale_x = draw_w / (float)rw;
+ scale_y = draw_h / (float)rh;
+ /* If the image is squared, the `draw_*` initialization values are good. */
+ }
+
+ /* draw */
+ eGPUBuiltinShader shader;
+ if (desaturate != 0.0f) {
+ shader = GPU_SHADER_2D_IMAGE_DESATURATE_COLOR;
+ }
+ else {
+ shader = GPU_SHADER_2D_IMAGE_COLOR;
+ }
+ IMMDrawPixelsTexState state = immDrawPixelsTexSetup(shader);
+
+ if (shader == GPU_SHADER_2D_IMAGE_DESATURATE_COLOR) {
+ immUniform1f("factor", desaturate);
+ }
+
+ immDrawPixelsTexScaled(
+ &state, draw_x, draw_y, rw, rh, GPU_RGBA8, true, rect, scale_x, scale_y, 1.0f, 1.0f, col);
+}
+
static void icon_draw_rect(float x,
float y,
int w,
@@ -1495,12 +1569,13 @@ static void icon_draw_rect(float x,
int draw_w = w;
int draw_h = h;
int draw_x = x;
- int draw_y = y;
+ /* We need to round y, to avoid the icon jittering in some cases. */
+ int draw_y = round_fl_to_int(y);
/* sanity check */
if (w <= 0 || h <= 0 || w > 2000 || h > 2000) {
printf("%s: icons are %i x %i pixels?\n", __func__, w, h);
- BLI_assert(!"invalid icon size");
+ BLI_assert_msg(0, "invalid icon size");
return;
}
/* modulate color */
@@ -1519,7 +1594,7 @@ static void icon_draw_rect(float x,
draw_h = h;
draw_x += (w - draw_w) / 2;
}
- /* if the image is squared, the draw_ initialization values are good */
+ /* If the image is squared, the `draw_*` initialization values are good. */
/* first allocate imbuf for scaling and copy preview into it */
ima = IMB_allocImBuf(rw, rh, 32, IB_rect);
@@ -1803,7 +1878,9 @@ static void icon_draw_size(float x,
ImBuf *ibuf = icon->obj;
GPU_blend(GPU_BLEND_ALPHA_PREMULT);
- icon_draw_rect(x, y, w, h, aspect, ibuf->x, ibuf->y, ibuf->rect, alpha, desaturate);
+ /* These icons are only used by the File/Asset Browser currently. Without this `_fast()`
+ * version, there may be performance issues, see T92922. */
+ icon_draw_rect_fast(x, y, w, h, aspect, ibuf->x, ibuf->y, ibuf->rect, alpha, desaturate);
GPU_blend(GPU_BLEND_ALPHA);
}
else if (di->type == ICON_TYPE_VECTOR) {
@@ -2143,7 +2220,7 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
static int ui_id_screen_get_icon(const bContext *C, ID *id)
{
BKE_icon_id_ensure(id);
- /* Don't use jobs here, offscreen rendering doesn't like this and crashes. */
+ /* Don't use jobs here, off-screen rendering doesn't like this and crashes. */
ui_id_icon_render(C, id, false);
return id->icon_id;
@@ -2201,7 +2278,7 @@ int UI_icon_from_library(const ID *id)
return ICON_NONE;
}
-int UI_icon_from_rnaptr(bContext *C, PointerRNA *ptr, int rnaicon, const bool big)
+int UI_icon_from_rnaptr(const bContext *C, PointerRNA *ptr, int rnaicon, const bool big)
{
ID *id = NULL;
@@ -2294,7 +2371,7 @@ int UI_icon_from_idcode(const int idcode)
case ID_ME:
return ICON_MESH_DATA;
case ID_MSK:
- return ICON_MOD_MASK; /* TODO! this would need its own icon! */
+ return ICON_MOD_MASK; /* TODO: this would need its own icon! */
case ID_NT:
return ICON_NODETREE;
case ID_OB:
@@ -2302,9 +2379,9 @@ int UI_icon_from_idcode(const int idcode)
case ID_PA:
return ICON_PARTICLE_DATA;
case ID_PAL:
- return ICON_COLOR; /* TODO! this would need its own icon! */
+ return ICON_COLOR; /* TODO: this would need its own icon! */
case ID_PC:
- return ICON_CURVE_BEZCURVE; /* TODO! this would need its own icon! */
+ return ICON_CURVE_BEZCURVE; /* TODO: this would need its own icon! */
case ID_LP:
return ICON_OUTLINER_DATA_LIGHTPROBE;
case ID_SCE:
@@ -2427,6 +2504,7 @@ void UI_icon_draw_ex(float x,
ImBuf *UI_icon_alert_imbuf_get(eAlertIcon icon)
{
#ifdef WITH_HEADLESS
+ UNUSED_VARS(icon);
return NULL;
#else
const int ALERT_IMG_SIZE = 256;
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index 97b9bb66f07..c7c6a88de01 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -116,7 +116,7 @@ extern const char ui_radial_dir_to_numpad[8];
extern const short ui_radial_dir_to_angle[8];
/* internal panel drawing defines */
-#define PNL_HEADER (UI_UNIT_Y * 1.2) /* 24 default */
+#define PNL_HEADER (UI_UNIT_Y * 1.25) /* 24 default */
/* bit button defines */
/* Bit operations */
@@ -221,7 +221,8 @@ struct uiBut {
const char *tip;
uiButToolTipFunc tip_func;
- void *tip_argN;
+ void *tip_arg;
+ uiFreeArgFunc tip_arg_free;
/** info on why button is disabled, displayed in tooltip */
const char *disabled_info;
@@ -254,14 +255,14 @@ struct uiBut {
/* Operator data */
struct wmOperatorType *optype;
struct PointerRNA *opptr;
- short opcontext;
+ wmOperatorCallContext opcontext;
/** When non-zero, this is the key used to activate a menu items (`a-z` always lower case). */
uchar menu_key;
ListBase extra_op_icons; /** #uiButExtraOpIcon */
- /* Draggable data, type is WM_DRAG_... */
+ /* Drag-able data, type is WM_DRAG_... */
char dragtype;
short dragflag;
void *dragpoin;
@@ -316,7 +317,7 @@ typedef struct uiButSearch {
void *item_active;
void *arg;
- uiButSearchArgFreeFn arg_free_fn;
+ uiFreeArgFunc arg_free_fn;
uiButSearchContextMenuFn item_context_menu_fn;
uiButSearchTooltipFn item_tooltip_fn;
@@ -359,6 +360,14 @@ typedef struct uiButDatasetRow {
int indentation;
} uiButDatasetRow;
+/** Derived struct for #UI_BTYPE_TREEROW. */
+typedef struct uiButTreeRow {
+ uiBut but;
+
+ uiTreeViewItemHandle *tree_item;
+ int indentation;
+} uiButTreeRow;
+
/** Derived struct for #UI_BTYPE_HSVCUBE. */
typedef struct uiButHSVCube {
uiBut but;
@@ -398,6 +407,7 @@ typedef struct uiButExtraOpIcon {
struct wmOperatorCallParams *optype_params;
bool highlighted;
+ bool disabled;
} uiButExtraOpIcon;
typedef struct ColorPicker {
@@ -487,6 +497,11 @@ struct uiBlock {
ListBase contexts;
+ /** A block can store "views" on data-sets. Currently tree-views (#AbstractTreeView) only.
+ * Others are imaginable, e.g. table-views, grid-views, etc. These are stored here to support
+ * state that is persistent over redraws (e.g. collapsed tree-view items). */
+ ListBase views;
+
char name[UI_MAX_NAME_STR];
float winmat[4][4];
@@ -510,6 +525,9 @@ struct uiBlock {
uiBlockHandleFunc handle_func;
void *handle_func_arg;
+ /** Custom interaction data. */
+ uiBlockInteraction_CallbackData custom_interaction_callbacks;
+
/** Custom extra event handling. */
int (*block_event_func)(const struct bContext *C, struct uiBlock *, const struct wmEvent *);
@@ -593,11 +611,19 @@ typedef struct uiSafetyRct {
void ui_fontscale(short *points, float aspect);
+extern void ui_block_to_region_fl(const struct ARegion *region,
+ uiBlock *block,
+ float *r_x,
+ float *r_y);
extern void ui_block_to_window_fl(const struct ARegion *region,
uiBlock *block,
float *x,
float *y);
extern void ui_block_to_window(const struct ARegion *region, uiBlock *block, int *x, int *y);
+extern void ui_block_to_region_rctf(const struct ARegion *region,
+ uiBlock *block,
+ rctf *rct_dst,
+ const rctf *rct_src);
extern void ui_block_to_window_rctf(const struct ARegion *region,
uiBlock *block,
rctf *rct_dst,
@@ -616,6 +642,9 @@ extern void ui_window_to_region(const struct ARegion *region, int *x, int *y);
extern void ui_window_to_region_rcti(const struct ARegion *region,
rcti *rect_dst,
const rcti *rct_src);
+extern void ui_window_to_region_rctf(const struct ARegion *region,
+ rctf *rect_dst,
+ const rctf *rct_src);
extern void ui_region_to_window(const struct ARegion *region, int *x, int *y);
extern void ui_region_winrct_get_no_margin(const struct ARegion *region, struct rcti *r_rect);
@@ -651,6 +680,7 @@ extern bool ui_but_string_eval_number(struct bContext *C,
extern int ui_but_string_get_max_length(uiBut *but);
/* Clear & exit the active button's string. */
extern void ui_but_active_string_clear_and_exit(struct bContext *C, uiBut *but) ATTR_NONNULL();
+extern void ui_but_set_string_interactive(struct bContext *C, uiBut *but, const char *value);
extern uiBut *ui_but_drag_multi_edit_get(uiBut *but);
void ui_def_but_icon(uiBut *but, const int icon, const int flag);
@@ -666,6 +696,9 @@ void ui_but_range_set_hard(uiBut *but);
void ui_but_range_set_soft(uiBut *but);
bool ui_but_context_poll_operator(struct bContext *C, struct wmOperatorType *ot, const uiBut *but);
+bool ui_but_context_poll_operator_ex(struct bContext *C,
+ const uiBut *but,
+ const struct wmOperatorCallParams *optype_params);
extern void ui_but_update(uiBut *but);
extern void ui_but_update_edited(uiBut *but);
@@ -704,7 +737,7 @@ struct uiPopupBlockCreate {
uiBlockCreateFunc create_func;
uiBlockHandleCreateFunc handle_create_func;
void *arg;
- void (*arg_free)(void *arg);
+ uiFreeArgFunc arg_free;
int event_xy[2];
@@ -798,7 +831,7 @@ struct ARegion *ui_searchbox_create_menu(struct bContext *C,
struct ARegion *butregion,
uiButSearch *search_but);
-bool ui_searchbox_inside(struct ARegion *region, int x, int y);
+bool ui_searchbox_inside(struct ARegion *region, const int xy[2]) ATTR_NONNULL(1, 2);
int ui_searchbox_find_index(struct ARegion *region, const char *name);
void ui_searchbox_update(struct bContext *C, struct ARegion *region, uiBut *but, const bool reset);
int ui_searchbox_autocomplete(struct bContext *C, struct ARegion *region, uiBut *but, char *str);
@@ -828,7 +861,7 @@ uiPopupBlockHandle *ui_popup_block_create(struct bContext *C,
uiBlockCreateFunc create_func,
uiBlockHandleCreateFunc handle_create_func,
void *arg,
- void (*arg_free)(void *arg));
+ uiFreeArgFunc arg_free);
uiPopupBlockHandle *ui_popup_menu_create(struct bContext *C,
struct ARegion *butregion,
uiBut *but,
@@ -849,7 +882,7 @@ void ui_pie_menu_level_create(uiBlock *block,
struct IDProperty *properties,
const EnumPropertyItem *items,
int totitem,
- int context,
+ wmOperatorCallContext context,
int flag);
/* interface_region_popup.c */
@@ -927,9 +960,8 @@ const char *ui_textedit_undo(struct uiUndoStack_Text *undo_stack,
int *r_cursor_index);
/* interface_handlers.c */
-PointerRNA *ui_handle_afterfunc_add_operator(struct wmOperatorType *ot,
- int opcontext,
- bool create_props);
+extern void ui_handle_afterfunc_add_operator(struct wmOperatorType *ot,
+ wmOperatorCallContext opcontext);
extern void ui_pan_to_scroll(const struct wmEvent *event, int *type, int *val);
extern void ui_but_activate_event(struct bContext *C, struct ARegion *region, uiBut *but);
extern void ui_but_activate_over(struct bContext *C, struct ARegion *region, uiBut *but);
@@ -942,6 +974,7 @@ extern void ui_but_execute_end(struct bContext *C,
uiBut *but,
void *active_back);
extern void ui_but_active_free(const struct bContext *C, uiBut *but);
+extern void ui_but_update_view_for_active(const struct bContext *C, const uiBlock *block);
extern int ui_but_menu_direction(uiBut *but);
extern void ui_but_text_password_hide(char password_str[128], uiBut *but, const bool restore);
extern uiBut *ui_but_find_select_in_enum(uiBut *but, int direction);
@@ -1000,9 +1033,7 @@ enum {
struct GPUBatch *ui_batch_roundbox_widget_get(void);
struct GPUBatch *ui_batch_roundbox_shadow_get(void);
-void ui_draw_anti_tria_rect(const rctf *rect, char dir, const float color[4]);
void ui_draw_menu_back(struct uiStyle *style, uiBlock *block, rcti *rect);
-void ui_draw_box_opaque(rcti *rect, int roundboxalign);
void ui_draw_popover_back(struct ARegion *region,
struct uiStyle *style,
uiBlock *block,
@@ -1041,13 +1072,23 @@ void ui_draw_menu_item(const struct uiFontStyle *fstyle,
int state,
uiMenuItemSeparatorType separator_type,
int *r_xmax);
-void ui_draw_preview_item(
- const struct uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int state);
+void ui_draw_preview_item(const struct uiFontStyle *fstyle,
+ rcti *rect,
+ const char *name,
+ int iconid,
+ int state,
+ eFontStyle_Align text_align);
+void ui_draw_preview_item_stateless(const struct uiFontStyle *fstyle,
+ rcti *rect,
+ const char *name,
+ int iconid,
+ const uchar text_col[4],
+ eFontStyle_Align text_align);
#define UI_TEXT_MARGIN_X 0.4f
#define UI_POPUP_MARGIN (UI_DPI_FAC * 12)
/* Margin at top of screen for popups. Note this value must be sufficient
- to draw a popover arrow to avoid cropping it. */
+ * to draw a popover arrow to avoid cropping it. */
#define UI_POPUP_MENU_TOP (int)(10 * UI_DPI_FAC)
#define UI_PIXEL_AA_JITTER 8
@@ -1070,6 +1111,7 @@ void ui_resources_free(void);
/* interface_layout.c */
void ui_layout_add_but(uiLayout *layout, uiBut *but);
+void ui_layout_remove_but(uiLayout *layout, const uiBut *but);
bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but);
uiBut *ui_but_add_search(uiBut *but,
PointerRNA *ptr,
@@ -1121,25 +1163,41 @@ bool ui_but_contains_rect(const uiBut *but, const rctf *rect);
bool ui_but_contains_point_px_icon(const uiBut *but,
struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
-bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, int x, int y)
- ATTR_WARN_UNUSED_RESULT;
+bool ui_but_contains_point_px(const uiBut *but, const struct ARegion *region, const int xy[2])
+ ATTR_NONNULL(1, 2, 3) ATTR_WARN_UNUSED_RESULT;
-uiBut *ui_list_find_mouse_over(struct ARegion *region,
+uiBut *ui_list_find_mouse_over(const struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
-
+uiBut *ui_list_find_from_row(const struct ARegion *region,
+ const uiBut *row_but) ATTR_WARN_UNUSED_RESULT;
+uiBut *ui_list_row_find_mouse_over(const struct ARegion *region, const int xy[2])
+ ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
+uiBut *ui_list_row_find_from_index(const struct ARegion *region,
+ const int index,
+ uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
+uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int xy[2])
+ ATTR_NONNULL(1, 2);
+uiBut *ui_tree_row_find_active(const struct ARegion *region);
+
+typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata);
uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,
- const int x,
- const int y,
- const bool labeledit) ATTR_WARN_UNUSED_RESULT;
+ const int xy[2],
+ const bool labeledit,
+ const uiButFindPollFn find_poll,
+ const void *find_custom_data)
+ ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_find_mouse_over(const struct ARegion *region,
const struct wmEvent *event) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_find_rect_over(const struct ARegion *region,
const rcti *rect_px) ATTR_WARN_UNUSED_RESULT;
-uiBut *ui_list_find_mouse_over_ex(struct ARegion *region, int x, int y) ATTR_WARN_UNUSED_RESULT;
+uiBut *ui_list_find_mouse_over_ex(const struct ARegion *region, const int xy[2])
+ ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
bool ui_but_contains_password(const uiBut *but) ATTR_WARN_UNUSED_RESULT;
+size_t ui_but_drawstr_without_sep_char(const uiBut *but, char *str, size_t str_maxlen)
+ ATTR_NONNULL(1, 2);
size_t ui_but_drawstr_len_without_sep_char(const uiBut *but);
size_t ui_but_tip_len_only_first_line(const uiBut *but);
@@ -1148,15 +1206,14 @@ uiBut *ui_but_next(uiBut *but) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_first(uiBlock *block) ATTR_WARN_UNUSED_RESULT;
uiBut *ui_but_last(uiBlock *block) ATTR_WARN_UNUSED_RESULT;
+uiBut *ui_block_active_but_get(const uiBlock *block);
bool ui_block_is_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;
bool ui_block_is_popover(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;
bool ui_block_is_pie_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;
bool ui_block_is_popup_any(const uiBlock *block) ATTR_WARN_UNUSED_RESULT;
-uiBlock *ui_block_find_mouse_over_ex(const struct ARegion *region,
- const int x,
- const int y,
- bool only_clip);
+uiBlock *ui_block_find_mouse_over_ex(const struct ARegion *region, const int xy[2], bool only_clip)
+ ATTR_NONNULL(1, 2);
uiBlock *ui_block_find_mouse_over(const struct ARegion *region,
const struct wmEvent *event,
bool only_clip);
@@ -1165,17 +1222,17 @@ uiBut *ui_region_find_first_but_test_flag(struct ARegion *region,
int flag_include,
int flag_exclude);
uiBut *ui_region_find_active_but(struct ARegion *region) ATTR_WARN_UNUSED_RESULT;
-bool ui_region_contains_point_px(const struct ARegion *region,
- int x,
- int y) ATTR_WARN_UNUSED_RESULT;
+bool ui_region_contains_point_px(const struct ARegion *region, const int xy[2])
+ ATTR_NONNULL(1, 2) ATTR_WARN_UNUSED_RESULT;
bool ui_region_contains_rect_px(const struct ARegion *region, const rcti *rect_px);
-struct ARegion *ui_screen_region_find_mouse_over_ex(struct bScreen *screen, int x, int y);
+struct ARegion *ui_screen_region_find_mouse_over_ex(struct bScreen *screen, const int xy[2])
+ ATTR_NONNULL(1, 2);
struct ARegion *ui_screen_region_find_mouse_over(struct bScreen *screen,
const struct wmEvent *event);
/* interface_context_menu.c */
-bool ui_popup_context_menu_for_button(struct bContext *C, uiBut *but);
+bool ui_popup_context_menu_for_button(struct bContext *C, uiBut *but, const struct wmEvent *event);
void ui_popup_context_menu_for_panel(struct bContext *C,
struct ARegion *region,
struct Panel *panel);
@@ -1203,6 +1260,9 @@ void UI_OT_eyedropper_driver(struct wmOperatorType *ot);
/* interface_eyedropper_gpencil_color.c */
void UI_OT_eyedropper_gpencil_color(struct wmOperatorType *ot);
+/* interface_template_asset_view.cc */
+struct uiListType *UI_UL_asset_view(void);
+
/**
* For use with #ui_rna_collection_search_update_fn.
*/
@@ -1231,6 +1291,13 @@ bool ui_jump_to_target_button_poll(struct bContext *C);
/* interface_queries.c */
void ui_interface_tag_script_reload_queries(void);
+/* interface_view.cc */
+void ui_block_free_views(struct uiBlock *block);
+uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+ const uiTreeViewHandle *new_view);
+uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
+ const uiTreeViewItemHandle *new_item_handle);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c
index cf3abc9be4a..b792c59481c 100644
--- a/source/blender/editors/interface/interface_layout.c
+++ b/source/blender/editors/interface/interface_layout.c
@@ -82,7 +82,7 @@ typedef struct uiLayoutRoot {
struct uiLayoutRoot *next, *prev;
int type;
- int opcontext;
+ wmOperatorCallContext opcontext;
int emw, emh;
int padding;
@@ -169,7 +169,7 @@ struct uiLayout {
bool enabled;
bool redalert;
bool keepaspect;
- /** For layouts inside gridflow, they and their items shall never have a fixed maximal size. */
+ /** For layouts inside grid-flow, they and their items shall never have a fixed maximal size. */
bool variable_size;
char alignment;
eUIEmbossType emboss;
@@ -282,41 +282,95 @@ static int ui_layout_vary_direction(uiLayout *layout)
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
+ /* Note that this code is probably a bit flaky, 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)
+/**
+ * Factors to apply to #UI_UNIT_X when calculating button width.
+ * This is used when the layout is a varying size, see #ui_layout_variable_size.
+ */
+struct uiTextIconPadFactor {
+ float text;
+ float icon;
+ float icon_only;
+};
+
+/**
+ * This adds over an icons width of padding even when no icon is used,
+ * this is done because most buttons need additional space (drop-down chevron for example).
+ * menus and labels use much smaller `text` values compared to this default.
+ *
+ * \note It may seem odd that the icon only adds 0.25
+ * but taking margins into account its fine,
+ * except for #ui_text_pad_compact where a bit more margin is required.
+ */
+static const struct uiTextIconPadFactor ui_text_pad_default = {
+ .text = 1.50f,
+ .icon = 0.25f,
+ .icon_only = 0.0f,
+};
+
+/** #ui_text_pad_default scaled down. */
+static const struct uiTextIconPadFactor ui_text_pad_compact = {
+ .text = 1.25f,
+ .icon = 0.35f,
+ .icon_only = 0.0f,
+};
+
+/** Least amount of padding not to clip the text or icon. */
+static const struct uiTextIconPadFactor ui_text_pad_none = {
+ .text = 0.25f,
+ .icon = 1.50f,
+ .icon_only = 0.0f,
+};
+
+/**
+ * Estimated size of text + icon.
+ */
+static int ui_text_icon_width_ex(uiLayout *layout,
+ const char *name,
+ int icon,
+ const struct uiTextIconPadFactor *pad_factor)
{
const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f);
+ /* When there is no text, always behave as if this is an icon-only button
+ * since it's not useful to return empty space. */
if (icon && !name[0]) {
- return unit_x; /* icon only */
+ return unit_x * (1.0f + pad_factor->icon_only);
}
if (ui_layout_variable_size(layout)) {
if (!icon && !name[0]) {
- return unit_x; /* No icon or name. */
+ return unit_x * (1.0f + pad_factor->icon_only);
}
+
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;
+
+ float margin = pad_factor->text;
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;
+ margin += pad_factor->icon;
}
- return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin);
+
+ const float aspect = layout->root->block->aspect;
+ const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
+ return UI_fontstyle_string_width_with_block_aspect(fstyle, name, aspect) +
+ (int)ceilf(unit_x * margin);
}
return unit_x * 10;
}
+static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact)
+{
+ return ui_text_icon_width_ex(
+ layout, name, icon, compact ? &ui_text_pad_compact : &ui_text_pad_default);
+}
+
static void ui_item_size(uiItem *item, int *r_w, int *r_h)
{
if (item->type == ITEM_BUTTON) {
@@ -643,7 +697,7 @@ static void ui_item_array(uiLayout *layout,
NULL);
}
else {
- /* note, this block of code is a bit arbitrary and has just been made
+ /* 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 */
@@ -662,7 +716,7 @@ static void ui_item_array(uiLayout *layout,
}
}
- /* show checkboxes for rna on a non-emboss block (menu for eg) */
+ /* Show check-boxes 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)) {
@@ -921,10 +975,10 @@ static void ui_keymap_but_cb(bContext *UNUSED(C), void *but_v, void *UNUSED(key_
{
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);
+ RNA_int_set(&but->rnapoin, "shift", (but->modifier_key & KM_SHIFT) ? KM_MOD_HELD : KM_NOTHING);
+ RNA_int_set(&but->rnapoin, "ctrl", (but->modifier_key & KM_CTRL) ? KM_MOD_HELD : KM_NOTHING);
+ RNA_int_set(&but->rnapoin, "alt", (but->modifier_key & KM_ALT) ? KM_MOD_HELD : KM_NOTHING);
+ RNA_int_set(&but->rnapoin, "oskey", (but->modifier_key & KM_OSKEY) ? KM_MOD_HELD : KM_NOTHING);
}
/**
@@ -1164,7 +1218,7 @@ static uiBut *uiItemFullO_ptr_ex(uiLayout *layout,
const char *name,
int icon,
IDProperty *properties,
- int context,
+ wmOperatorCallContext context,
int flag,
PointerRNA *r_opptr)
{
@@ -1285,7 +1339,7 @@ static void ui_item_menu_hold(struct bContext *C, ARegion *butregion, uiBut *but
UI_menutype_draw(C, mt, layout);
}
else {
- uiItemL(layout, "Menu Missing:", ICON_NONE);
+ uiItemL(layout, TIP_("Menu Missing:"), ICON_NONE);
uiItemL(layout, menu_id, ICON_NONE);
}
UI_popup_menu_end(C, pup);
@@ -1296,7 +1350,7 @@ void uiItemFullO_ptr(uiLayout *layout,
const char *name,
int icon,
IDProperty *properties,
- int context,
+ wmOperatorCallContext context,
int flag,
PointerRNA *r_opptr)
{
@@ -1308,7 +1362,7 @@ void uiItemFullOMenuHold_ptr(uiLayout *layout,
const char *name,
int icon,
IDProperty *properties,
- int context,
+ wmOperatorCallContext context,
int flag,
const char *menu_id,
PointerRNA *r_opptr)
@@ -1322,7 +1376,7 @@ void uiItemFullO(uiLayout *layout,
const char *name,
int icon,
IDProperty *properties,
- int context,
+ wmOperatorCallContext context,
int flag,
PointerRNA *r_opptr)
{
@@ -1411,7 +1465,7 @@ BLI_INLINE bool ui_layout_is_radial(const uiLayout *layout)
}
/**
- * Create ui items for enum items in \a item_array.
+ * Create UI items for enum items in \a item_array.
*
* A version of #uiItemsFullEnumO that takes pre-calculated item array.
*/
@@ -1420,7 +1474,7 @@ void uiItemsFullEnumO_items(uiLayout *layout,
PointerRNA ptr,
PropertyRNA *prop,
IDProperty *properties,
- int context,
+ wmOperatorCallContext context,
int flag,
const EnumPropertyItem *item_array,
int totitem)
@@ -1569,7 +1623,7 @@ void uiItemsFullEnumO(uiLayout *layout,
const char *opname,
const char *propname,
IDProperty *properties,
- int context,
+ wmOperatorCallContext context,
int flag)
{
wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
@@ -1818,7 +1872,7 @@ static void ui_item_rna_size(uiLayout *layout,
}
else if (type == PROP_BOOLEAN) {
if (icon == ICON_NONE) {
- /* Exception for checkboxes, they need a little less space to align nicely. */
+ /* Exception for check-boxes, they need a little less space to align nicely. */
is_checkbox_only = true;
}
icon = ICON_DOT;
@@ -1984,7 +2038,7 @@ void uiItemFullR(uiLayout *layout,
* 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
+ /* Although check-boxes 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
@@ -2062,7 +2116,7 @@ void uiItemFullR(uiLayout *layout,
/* 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. */
+ /* Use check-boxes 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) {
@@ -2353,7 +2407,7 @@ void uiItemFullR(uiLayout *layout,
}
}
- /* Mark non-embossed textfields inside a listbox. */
+ /* Mark non-embossed text-fields inside a list-box. */
if (but && (block->flag & UI_BLOCK_LIST_ITEM) && (but->type == UI_BTYPE_TEXT) &&
ELEM(but->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) {
UI_but_flag_enable(but, UI_BUT_LIST_ITEM);
@@ -2831,7 +2885,7 @@ 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) */
+ /* Panels are created flipped (from event handling POV). */
layout->root->block->flag ^= UI_BLOCK_IS_FLIP;
}
@@ -2857,23 +2911,23 @@ static uiBut *ui_item_menu(uiLayout *layout,
icon = ICON_BLANK1;
}
- int w = ui_text_icon_width(layout, name, icon, 1);
- const int h = UI_UNIT_Y;
-
+ struct uiTextIconPadFactor pad_factor = ui_text_pad_compact;
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;
+ pad_factor.text = 1.85;
+ pad_factor.icon_only = 0.6f;
}
else {
- if (name[0]) {
- w -= UI_UNIT_X / 2;
- }
+ pad_factor.text = 0.75f;
}
}
+ const int w = ui_text_icon_width_ex(layout, name, icon, &pad_factor);
+ const int h = UI_UNIT_Y;
+
if (heading_layout) {
ui_layout_heading_label_add(layout, heading_layout, true, true);
}
@@ -2911,6 +2965,12 @@ static uiBut *ui_item_menu(uiLayout *layout,
void uiItemM_ptr(uiLayout *layout, MenuType *mt, const char *name, int icon)
{
+ uiBlock *block = layout->root->block;
+ bContext *C = block->evil_C;
+ if (WM_menutype_poll(C, mt) == false) {
+ return;
+ }
+
if (!name) {
name = CTX_IFACE_(mt->translation_context, mt->label);
}
@@ -2949,6 +3009,9 @@ void uiItemMContents(uiLayout *layout, const char *menuname)
uiBlock *block = layout->root->block;
bContext *C = block->evil_C;
+ if (WM_menutype_poll(C, mt) == false) {
+ return;
+ }
UI_menutype_draw(C, mt, layout);
}
@@ -3124,8 +3187,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon)
icon = ICON_BLANK1;
}
- const int w = ui_text_icon_width(layout, name, icon, 0);
-
+ const int w = ui_text_icon_width_ex(layout, name, icon, &ui_text_pad_none);
uiBut *but;
if (icon && name[0]) {
but = uiDefIconTextBut(
@@ -3147,7 +3209,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon)
but->drawflag |= UI_BUT_TEXT_RIGHT;
}
- /* Mark as a label inside a listbox. */
+ /* Mark as a label inside a list-box. */
if (block->flag & UI_BLOCK_LIST_ITEM) {
but->flag |= UI_BUT_LIST_ITEM;
}
@@ -3371,7 +3433,7 @@ void uiItemMenuFN(uiLayout *layout, const char *name, int icon, uiMenuCreateFunc
}
typedef struct MenuItemLevel {
- int opcontext;
+ wmOperatorCallContext 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];
@@ -3381,10 +3443,13 @@ typedef struct MenuItemLevel {
static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, void *arg)
{
- MenuItemLevel *lvl = (MenuItemLevel *)(((uiBut *)arg)->func_argN);
+ uiBut *but = arg;
+ MenuItemLevel *lvl = but->func_argN;
+ /* Use the operator properties from the button owning the menu. */
+ IDProperty *op_props = but->opptr ? but->opptr->data : NULL;
uiLayoutSetOperatorContext(layout, lvl->opcontext);
- uiItemsEnumO(layout, lvl->opname, lvl->propname);
+ uiItemsFullEnumO(layout, lvl->opname, lvl->propname, op_props, lvl->opcontext, 0);
layout->root->block->flag |= UI_BLOCK_IS_FLIP;
@@ -3392,12 +3457,13 @@ static void menu_item_enum_opname_menu(bContext *UNUSED(C), uiLayout *layout, vo
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)
+void uiItemMenuEnumFullO_ptr(uiLayout *layout,
+ bContext *C,
+ wmOperatorType *ot,
+ const char *propname,
+ const char *name,
+ int icon,
+ PointerRNA *r_opptr)
{
/* Caller must check */
BLI_assert(ot->srna != NULL);
@@ -3416,6 +3482,15 @@ void uiItemMenuEnumO_ptr(uiLayout *layout,
lvl->opcontext = layout->root->opcontext;
uiBut *but = ui_item_menu(layout, name, icon, menu_item_enum_opname_menu, NULL, lvl, NULL, true);
+ /* Use the menu button as owner for the operator properties, which will then be passed to the
+ * individual menu items. */
+ if (r_opptr) {
+ but->opptr = MEM_callocN(sizeof(PointerRNA), "uiButOpPtr");
+ WM_operator_properties_create_ptr(but->opptr, ot);
+ BLI_assert(but->opptr->data == NULL);
+ WM_operator_properties_alloc(&but->opptr, (IDProperty **)&but->opptr->data, ot->idname);
+ *r_opptr = *but->opptr;
+ }
/* add hotkey here, lower UI code can't detect it */
if ((layout->root->block->flag & UI_BLOCK_LOOP) && (ot->prop && ot->invoke)) {
@@ -3427,12 +3502,13 @@ void uiItemMenuEnumO_ptr(uiLayout *layout,
}
}
-void uiItemMenuEnumO(uiLayout *layout,
- bContext *C,
- const char *opname,
- const char *propname,
- const char *name,
- int icon)
+void uiItemMenuEnumFullO(uiLayout *layout,
+ bContext *C,
+ const char *opname,
+ const char *propname,
+ const char *name,
+ int icon,
+ PointerRNA *r_opptr)
{
wmOperatorType *ot = WM_operatortype_find(opname, 0); /* print error next */
@@ -3444,7 +3520,17 @@ void uiItemMenuEnumO(uiLayout *layout,
return;
}
- uiItemMenuEnumO_ptr(layout, C, ot, propname, name, icon);
+ uiItemMenuEnumFullO_ptr(layout, C, ot, propname, name, icon, r_opptr);
+}
+
+void uiItemMenuEnumO(uiLayout *layout,
+ bContext *C,
+ const char *opname,
+ const char *propname,
+ const char *name,
+ int icon)
+{
+ uiItemMenuEnumFullO(layout, C, opname, propname, name, icon, NULL);
}
static void menu_item_enum_rna_menu(bContext *UNUSED(C), uiLayout *layout, void *arg)
@@ -4615,7 +4701,7 @@ static void ui_litem_init_from_parent(uiLayout *litem, uiLayout *layout, int ali
{
litem->root = layout->root;
litem->align = align;
- /* Children of gridflow layout shall never have "ideal big size" returned as estimated size. */
+ /* Children of grid-flow 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;
@@ -5523,28 +5609,52 @@ void ui_layout_add_but(uiLayout *layout, uiBut *but)
ui_button_group_add_but(uiLayoutGetBlock(layout), but);
}
-bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but)
+static uiButtonItem *ui_layout_find_button_item(const uiLayout *layout, const uiBut *but)
{
- ListBase *child_list = layout->child_items_layout ? &layout->child_items_layout->items :
- &layout->items;
+ const 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;
+ if (bitem->but == but) {
+ return bitem;
}
}
else {
- if (ui_layout_replace_but_ptr((uiLayout *)item, old_but_ptr, new_but)) {
- return true;
+ uiButtonItem *nested_item = ui_layout_find_button_item((uiLayout *)item, but);
+ if (nested_item) {
+ return nested_item;
}
}
}
- return false;
+ return NULL;
+}
+
+void ui_layout_remove_but(uiLayout *layout, const uiBut *but)
+{
+ uiButtonItem *bitem = ui_layout_find_button_item(layout, but);
+ if (!bitem) {
+ return;
+ }
+
+ BLI_freelinkN(&layout->items, bitem);
+}
+
+/**
+ * \return true if the button was successfully replaced.
+ */
+bool ui_layout_replace_but_ptr(uiLayout *layout, const void *old_but_ptr, uiBut *new_but)
+{
+ uiButtonItem *bitem = ui_layout_find_button_item(layout, old_but_ptr);
+ if (!bitem) {
+ return false;
+ }
+
+ bitem->but = new_but;
+ return true;
}
void uiLayoutSetFixedSize(uiLayout *layout, bool fixed_size)
@@ -5562,7 +5672,7 @@ bool uiLayoutGetFixedSize(uiLayout *layout)
return (layout->item.flag & UI_ITEM_FIXED_SIZE) != 0;
}
-void uiLayoutSetOperatorContext(uiLayout *layout, int opcontext)
+void uiLayoutSetOperatorContext(uiLayout *layout, wmOperatorCallContext opcontext)
{
layout->root->opcontext = opcontext;
}
@@ -5932,8 +6042,8 @@ uiLayout *uiItemsAlertBox(uiBlock *block, const int size, const eAlertIcon icon)
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. */
+ * 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) /
diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c
index ce5c17a0718..c962a1107ae 100644
--- a/source/blender/editors/interface/interface_ops.c
+++ b/source/blender/editors/interface/interface_ops.c
@@ -75,6 +75,32 @@
#include "ED_text.h"
/* -------------------------------------------------------------------- */
+/** \name Immediate redraw helper
+ *
+ * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That
+ * violates the Model-View-Controller pattern.
+ *
+ * But there are some operators which really need to re-run the layout definitions for various
+ * reasons. For example, "Edit Source" does it to find out which exact Python code added a button.
+ * Other operators may need to access buttons that aren't currently visible. In Blender's UI code
+ * design that typically means just not adding the button in the first place, for a particular
+ * redraw. So the operator needs to change context and re-create the layout, so the button becomes
+ * available to act on.
+ *
+ * \{ */
+
+static void ui_region_redraw_immediately(bContext *C, ARegion *region)
+{
+ ED_region_do_layout(C, region);
+ WM_draw_region_viewport_bind(region);
+ ED_region_do_draw(C, region);
+ WM_draw_region_viewport_unbind(region);
+ region->do_draw = false;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Copy Data Path Operator
* \{ */
@@ -649,7 +675,7 @@ static int override_remove_button_exec(bContext *C, wmOperator *op)
PropertyRNA *src_prop;
RNA_id_pointer_create(id->override_library->reference, &id_refptr);
if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) {
- BLI_assert(0 && "Failed to create matching source (linked data) RNA pointer");
+ BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer");
}
}
@@ -823,6 +849,9 @@ bool UI_context_copy_to_selected_list(bContext *C,
else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) {
*r_lb = CTX_data_collection_get(C, "selected_nla_strips");
}
+ else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) {
+ *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks");
+ }
else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) &&
(path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) !=
NULL) {
@@ -972,55 +1001,69 @@ static bool copy_to_selected_button(bContext *C, bool all, bool poll)
UI_context_active_but_prop_get(C, &ptr, &prop, &index);
/* if there is a valid property that is editable... */
- if (ptr.data && prop) {
- char *path = NULL;
- bool use_path_from_id;
- ListBase lb = {NULL};
-
- if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) &&
- !BLI_listbase_is_empty(&lb)) {
- LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) {
- 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;
- }
+ if (ptr.data == NULL || prop == NULL) {
+ return false;
+ }
- if (lptr.data == ptr.data) {
- /* lptr might not be the same as link->ptr! */
- continue;
- }
+ char *path = NULL;
+ bool use_path_from_id;
+ ListBase lb = {NULL};
- if (lprop == prop) {
- if (RNA_property_editable(&lptr, lprop)) {
- if (poll) {
- success = true;
- break;
- }
- if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) {
- RNA_property_update(C, &lptr, prop);
- success = true;
- }
- }
- }
- }
- }
- }
+ if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) {
+ return false;
+ }
+ if (BLI_listbase_is_empty(&lb)) {
MEM_SAFE_FREE(path);
- BLI_freelistN(&lb);
+ return false;
}
+ LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) {
+ if (link->ptr.data == ptr.data) {
+ continue;
+ }
+
+ 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;
+ }
+
+ if (lptr.data == ptr.data) {
+ /* lptr might not be the same as link->ptr! */
+ continue;
+ }
+
+ if (lprop != prop) {
+ continue;
+ }
+
+ if (!RNA_property_editable(&lptr, lprop)) {
+ continue;
+ }
+
+ if (poll) {
+ success = true;
+ break;
+ }
+ if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) {
+ RNA_property_update(C, &lptr, prop);
+ success = true;
+ }
+ }
+
+ MEM_SAFE_FREE(path);
+ BLI_freelistN(&lb);
+
return success;
}
@@ -1215,7 +1258,7 @@ static void UI_OT_jump_to_target_button(wmOperatorType *ot)
/* ------------------------------------------------------------------------- */
/* EditSource Utility funcs and operator,
- * note, this includes utility functions and button matching checks */
+ * NOTE: this includes utility functions and button matching checks. */
typedef struct uiEditSourceStore {
uiBut but_orig;
@@ -1340,17 +1383,8 @@ static int editsource_text_edit(bContext *C,
txt_move_toline(text, line - 1, false);
/* naughty!, find text area to set, not good behavior
- * but since this is a dev tool lets allow it - campbell */
- ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0);
- if (area) {
- SpaceText *st = area->spacedata.first;
- ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
- st->text = text;
- if (region) {
- ED_text_scroll_to_cursor(st, region, true);
- }
- }
- else {
+ * but since this is a developer tool lets allow it - campbell */
+ if (!ED_text_activate_in_screen(C, text)) {
BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2);
}
@@ -1379,11 +1413,7 @@ static int editsource_exec(bContext *C, wmOperator *op)
ui_editsource_active_but_set(but);
/* redraw and get active button python info */
- ED_region_do_layout(C, region);
- WM_draw_region_viewport_bind(region);
- ED_region_do_draw(C, region);
- WM_draw_region_viewport_unbind(region);
- region->do_draw = false;
+ ui_region_redraw_immediately(C, region);
for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash);
BLI_ghashIterator_done(&ghi) == false;
@@ -1535,7 +1565,7 @@ static int edittranslation_exec(bContext *C, wmOperator *op)
}
/* Try to find a valid po file for current language... */
edittranslation_find_po_file(root, uilng, popath, FILE_MAX);
- /* printf("po path: %s\n", popath); */
+ // printf("po path: %s\n", popath);
if (popath[0] == '\0') {
BKE_reportf(
op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root);
@@ -1656,7 +1686,8 @@ static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *ev
bScreen *screen = CTX_wm_screen(C);
const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed");
ARegion *region_prev = CTX_wm_region(C);
- ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->x, event->y) :
+ ARegion *region = screen ? BKE_screen_find_region_xy(
+ screen, RGN_TYPE_ANY, event->xy[0], event->xy[1]) :
NULL;
if (region == NULL) {
@@ -1737,10 +1768,7 @@ static void UI_OT_button_string_clear(wmOperatorType *ot)
/** \name Drop Color Operator
* \{ */
-bool UI_drop_color_poll(struct bContext *C,
- wmDrag *drag,
- const wmEvent *UNUSED(event),
- const char **UNUSED(r_tooltip))
+bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event))
{
/* should only return true for regions that include buttons, for now
* return true always */
@@ -1836,6 +1864,184 @@ static void UI_OT_drop_color(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Drop Name Operator
+ * \{ */
+
+static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ uiBut *but = UI_but_active_drop_name_button(C);
+ char *str = RNA_string_get_alloc(op->ptr, "string", NULL, 0, NULL);
+
+ if (str) {
+ ui_but_set_string_interactive(C, but, str);
+ MEM_freeN(str);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void UI_OT_drop_name(wmOperatorType *ot)
+{
+ ot->name = "Drop Name";
+ ot->idname = "UI_OT_drop_name";
+ ot->description = "Drop name to button";
+
+ ot->poll = ED_operator_regionactive;
+ ot->invoke = drop_name_invoke;
+ ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
+
+ RNA_def_string(
+ ot->srna, "string", NULL, 0, "String", "The string value to drop into the button");
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name UI List Search Operator
+ * \{ */
+
+static bool ui_list_focused_poll(bContext *C)
+{
+ const ARegion *region = CTX_wm_region(C);
+ const wmWindow *win = CTX_wm_window(C);
+ const uiList *list = UI_list_find_mouse_over(region, win->eventstate);
+
+ return list != NULL;
+}
+
+/**
+ * Ensure the filter options are set to be visible in the UI list.
+ * \return if the visibility changed, requiring a redraw.
+ */
+static bool ui_list_unhide_filter_options(uiList *list)
+{
+ if (list->filter_flag & UILST_FLT_SHOW) {
+ /* Nothing to be done. */
+ return false;
+ }
+
+ list->filter_flag |= UILST_FLT_SHOW;
+ return true;
+}
+
+static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+{
+ ARegion *region = CTX_wm_region(C);
+ uiList *list = UI_list_find_mouse_over(region, event);
+ /* Poll should check. */
+ BLI_assert(list != NULL);
+
+ if (ui_list_unhide_filter_options(list)) {
+ ui_region_redraw_immediately(C, region);
+ }
+
+ if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) {
+ return OPERATOR_CANCELLED;
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void UI_OT_list_start_filter(wmOperatorType *ot)
+{
+ ot->name = "List Filter";
+ ot->idname = "UI_OT_list_start_filter";
+ ot->description = "Start entering filter text for the list in focus";
+
+ ot->invoke = ui_list_start_filter_invoke;
+ ot->poll = ui_list_focused_poll;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name UI Tree-View Drop Operator
+ * \{ */
+
+static bool ui_tree_view_drop_poll(bContext *C)
+{
+ const wmWindow *win = CTX_wm_window(C);
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
+ region, win->eventstate->xy);
+
+ return hovered_tree_item != NULL;
+}
+
+static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+{
+ if (event->custom != EVT_DATA_DRAGDROP) {
+ return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
+ }
+
+ const ARegion *region = CTX_wm_region(C);
+ uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(region, event->xy);
+
+ if (!UI_tree_view_item_drop_handle(hovered_tree_item, event->customdata)) {
+ return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void UI_OT_tree_view_drop(wmOperatorType *ot)
+{
+ ot->name = "Tree View drop";
+ ot->idname = "UI_OT_tree_view_drop";
+ ot->description = "Drag and drop items onto a tree item";
+
+ ot->invoke = ui_tree_view_drop_invoke;
+ ot->poll = ui_tree_view_drop_poll;
+
+ ot->flag = OPTYPE_INTERNAL;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+/** \name UI Tree-View Item Rename Operator
+ *
+ * General purpose renaming operator for tree-views. Thanks to this, to add a rename button to
+ * context menus for example, tree-view API users don't have to implement own renaming operators
+ * with the same logic as they already have for their #ui::AbstractTreeViewItem::rename() override.
+ *
+ * \{ */
+
+static bool ui_tree_view_item_rename_poll(bContext *C)
+{
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region);
+ return active_item != NULL && UI_tree_view_item_can_rename(active_item);
+}
+
+static int ui_tree_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ ARegion *region = CTX_wm_region(C);
+ uiTreeViewItemHandle *active_item = UI_block_tree_view_find_active_item(region);
+
+ UI_tree_view_item_begin_rename(active_item);
+ ED_region_tag_redraw(region);
+
+ return OPERATOR_FINISHED;
+}
+
+static void UI_OT_tree_view_item_rename(wmOperatorType *ot)
+{
+ ot->name = "Rename Tree-View Item";
+ ot->idname = "UI_OT_tree_view_item_rename";
+ ot->description = "Rename the active item in the tree";
+
+ ot->exec = ui_tree_view_item_rename_exec;
+ ot->poll = ui_tree_view_item_rename_poll;
+ /* Could get a custom tooltip via the `get_description()` callback and another overridable
+ * function of the tree-view. */
+
+ ot->flag = OPTYPE_INTERNAL;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Operator & Keymap Registration
* \{ */
@@ -1852,6 +2058,7 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_copy_to_selected_button);
WM_operatortype_append(UI_OT_jump_to_target_button);
WM_operatortype_append(UI_OT_drop_color);
+ WM_operatortype_append(UI_OT_drop_name);
#ifdef WITH_PYTHON
WM_operatortype_append(UI_OT_editsource);
WM_operatortype_append(UI_OT_edittranslation_init);
@@ -1860,6 +2067,11 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_button_execute);
WM_operatortype_append(UI_OT_button_string_clear);
+ WM_operatortype_append(UI_OT_list_start_filter);
+
+ WM_operatortype_append(UI_OT_tree_view_drop);
+ WM_operatortype_append(UI_OT_tree_view_item_rename);
+
/* external */
WM_operatortype_append(UI_OT_eyedropper_color);
WM_operatortype_append(UI_OT_eyedropper_colorramp);
diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c
index dc0650af7a7..072362492d8 100644
--- a/source/blender/editors/interface/interface_panel.c
+++ b/source/blender/editors/interface/interface_panel.c
@@ -258,7 +258,7 @@ static Panel *panel_add_instanced(ARegion *region,
/* 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
+ * 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) {
@@ -1112,23 +1112,14 @@ 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;
- }
+ const bTheme *btheme = UI_GetTheme();
+ const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f;
+ UI_draw_roundbox_corner_set(UI_CNR_ALL);
float color[4];
UI_GetThemeColor4fv(TH_SELECT_ACTIVE, color);
@@ -1172,18 +1163,17 @@ static void panel_draw_aligned_widgets(const uiStyle *style,
/* 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);
+ const float size_y = BLI_rcti_size_y(&widget_rect);
+ GPU_blend(GPU_BLEND_ALPHA);
+ UI_icon_draw_ex(widget_rect.xmin + size_y * 0.2f,
+ widget_rect.ymin + size_y * 0.2f,
+ UI_panel_is_closed(panel) ? ICON_RIGHTARROW : ICON_DOWNARROW_HLT,
+ aspect * U.inv_dpi_fac,
+ 0.7f,
+ 0.0f,
+ title_color,
+ false);
+ GPU_blend(GPU_BLEND_NONE);
}
/* Draw text label. */
@@ -1243,7 +1233,6 @@ 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);
@@ -1251,90 +1240,52 @@ static void panel_draw_aligned_backdrop(const Panel *panel,
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);
- }
+ const bTheme *btheme = UI_GetTheme();
+ const float radius = btheme->tui.panel_roundness * U.widget_unit * 0.5f;
- /* 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();
- }
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ GPU_blend(GPU_BLEND_ALPHA);
+
+ /* Panel backdrop. */
+ if (is_open || panel->type->flag & PANEL_TYPE_NO_HEADER) {
+ float panel_backcolor[4];
+ UI_draw_roundbox_corner_set(is_open ? UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT : UI_CNR_ALL);
+ UI_GetThemeColor4fv((is_subpanel ? TH_PANEL_SUB_BACK : TH_PANEL_BACK), panel_backcolor);
+
+ UI_draw_roundbox_4fv(
+ &(const rctf){
+ .xmin = rect->xmin,
+ .xmax = rect->xmax,
+ .ymin = rect->ymin,
+ .ymax = rect->ymax,
+ },
+ true,
+ radius,
+ panel_backcolor);
+ }
+
+ /* Panel header backdrops for non sub-panels. */
+ if (!is_subpanel) {
+ float panel_headercolor[4];
+ UI_GetThemeColor4fv(UI_panel_matches_search_filter(panel) ? TH_MATCH : TH_PANEL_HEADER,
+ panel_headercolor);
+ UI_draw_roundbox_corner_set(is_open ? UI_CNR_TOP_RIGHT | UI_CNR_TOP_LEFT : UI_CNR_ALL);
+
+ /* Change the width a little bit to line up with the sides. */
+ UI_draw_roundbox_4fv(
+ &(const rctf){
+ .xmin = rect->xmin,
+ .xmax = rect->xmax,
+ .ymin = header_rect->ymin,
+ .ymax = header_rect->ymax,
+ },
+ true,
+ radius,
+ panel_headercolor);
+ }
+
+ GPU_blend(GPU_BLEND_NONE);
+ immUnbindProgram();
}
/**
@@ -1447,10 +1398,6 @@ void UI_panel_category_draw_all(ARegion *region, const char *category_id_active)
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);
@@ -1603,7 +1550,7 @@ void UI_panel_category_draw_all(ARegion *region, const char *category_id_active)
}
BLF_position(fontid, rct->xmax - text_v_ofs, rct->ymin + tab_v_pad_text, 0.0f);
- BLF_color3ubv(fontid, theme_col_text);
+ BLF_color3ubv(fontid, is_active ? theme_col_text_hi : theme_col_text);
BLF_draw(fontid, category_id_draw, category_draw_len);
GPU_blend(GPU_BLEND_NONE);
@@ -1620,10 +1567,6 @@ void UI_panel_category_draw_all(ARegion *region, const char *category_id_active)
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
@@ -1797,9 +1740,9 @@ static bool uiAlignPanelStep(ARegion *region, const float factor, const bool dra
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;
+ const bool no_header = ps->panel->type->flag & PANEL_TYPE_NO_HEADER;
ps->panel->runtime.region_ofsx = region_offset_x;
- ps->new_offset_x = region_offset_x + ((use_box) ? UI_PANEL_BOX_STYLE_MARGIN : 0);
+ ps->new_offset_x = region_offset_x + (no_header ? 0 : UI_PANEL_MARGIN_X);
}
/* Y offset. */
@@ -1807,10 +1750,7 @@ static bool uiAlignPanelStep(ARegion *region, const float factor, const bool dra
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;
- }
+ y -= UI_PANEL_MARGIN_Y;
ps->new_offset_y = y;
/* The header still draws offset by the size of closed panels, so apply the offset here. */
if (UI_panel_is_closed(ps->panel)) {
@@ -1892,7 +1832,7 @@ static void ui_do_animate(bContext *C, Panel *panel)
}
else {
if (UI_panel_is_dragging(panel)) {
- /* Note: doing this in #panel_activate_state would require
+ /* NOTE: doing this in #panel_activate_state would require
* removing `const` for context in many other places. */
reorder_instanced_panel_list(C, region, panel);
}
@@ -1986,7 +1926,7 @@ static void ui_do_drag(const bContext *C, const wmEvent *event, Panel *panel)
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);
+ const int y = clamp_i(event->xy[1], region->winrct.ymin, region->winrct.ymax + DRAG_REGION_PAD);
float dy = (float)(y - data->starty);
@@ -2101,7 +2041,7 @@ static int ui_panel_drag_collapse_handler(bContext *C, const wmEvent *event, voi
switch (event->type) {
case MOUSEMOVE:
- ui_panel_drag_collapse(C, dragcol_data, &event->x);
+ ui_panel_drag_collapse(C, dragcol_data, event->xy);
retval = WM_UI_HANDLER_BREAK;
break;
@@ -2130,7 +2070,7 @@ static void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was
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);
+ copy_v2_v2_int(dragcol_data->xy_init, event->xy);
WM_event_add_ui_handler(C,
&win->modalhandlers,
@@ -2160,7 +2100,7 @@ static void ui_handle_panel_header(const bContext *C,
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 use_pin = UI_panel_category_is_visible(region) && UI_panel_can_be_pinned(panel);
const bool show_pin = use_pin && (panel->flag & PNL_PIN);
const bool show_drag = !is_subpanel;
@@ -2423,7 +2363,7 @@ int ui_handler_panel_region(bContext *C,
}
/* Scroll-bars can overlap panels now, they have handling priority. */
- if (UI_view2d_mouse_in_scrollers(region, &region->v2d, event->x, event->y)) {
+ if (UI_view2d_mouse_in_scrollers(region, &region->v2d, event->xy)) {
return WM_UI_HANDLER_CONTINUE;
}
@@ -2466,8 +2406,8 @@ int ui_handler_panel_region(bContext *C,
continue;
}
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(region, block, &mx, &my);
const uiPanelMouseState mouse_state = ui_panel_mouse_state_get(block, panel, mx, my);
@@ -2545,8 +2485,8 @@ PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wm
continue;
}
- int mx = event->x;
- int my = event->y;
+ int mx = event->xy[0];
+ int my = event->xy[1];
ui_window_to_block(region, block, &mx, &my);
const int mouse_state = ui_panel_mouse_state_get(block, panel, mx, my);
if (ELEM(mouse_state, PANEL_MOUSE_INSIDE_CONTENT, PANEL_MOUSE_INSIDE_HEADER)) {
@@ -2557,13 +2497,18 @@ PointerRNA *UI_region_panel_custom_data_under_cursor(const bContext *C, const wm
return NULL;
}
+bool UI_panel_can_be_pinned(const Panel *panel)
+{
+ return (panel->type->parent == NULL) && !(panel->type->flag & PANEL_TYPE_INSTANCED);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
/** \name Window Level Modal Panel Interaction
* \{ */
-/* Note, this is modal handler and should not swallow events for animation. */
+/* 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;
@@ -2619,8 +2564,8 @@ static void panel_handle_data_ensure(const bContext *C,
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->startx = win->eventstate->xy[0];
+ data->starty = win->eventstate->xy[1];
data->startofsx = panel->ofsx;
data->startofsy = panel->ofsy;
data->start_cur_xmin = region->v2d.cur.xmin;
@@ -2653,7 +2598,7 @@ static void panel_activate_state(const bContext *C, Panel *panel, const uiHandle
/* Initiate edge panning during drags for scrolling beyond the initial region view. */
wmOperatorType *ot = WM_operatortype_find("VIEW2D_OT_edge_pan", true);
- ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT, true);
+ ui_handle_afterfunc_add_operator(ot, WM_OP_INVOKE_DEFAULT);
}
else if (state == PANEL_STATE_ANIMATION) {
panel_set_flag_recursive(panel, PNL_SELECT, false);
diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c
index 7d561aa1c71..bdf93d7c82e 100644
--- a/source/blender/editors/interface/interface_query.c
+++ b/source/blender/editors/interface/interface_query.c
@@ -23,6 +23,7 @@
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_rect.h"
+#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_screen_types.h"
@@ -68,7 +69,8 @@ bool ui_but_is_toggle(const uiBut *but)
UI_BTYPE_CHECKBOX,
UI_BTYPE_CHECKBOX_N,
UI_BTYPE_ROW,
- UI_BTYPE_DATASETROW);
+ UI_BTYPE_DATASETROW,
+ UI_BTYPE_TREEROW);
}
/**
@@ -78,7 +80,7 @@ bool ui_but_is_toggle(const uiBut *but)
*/
bool ui_but_is_interactive(const uiBut *but, const bool labeledit)
{
- /* note, UI_BTYPE_LABEL is included for highlights, this allows drags */
+ /* NOTE: #UI_BTYPE_LABEL is included for highlights, this allows drags. */
if ((but->type == UI_BTYPE_LABEL) && but->dragpoin == NULL) {
return false;
}
@@ -220,14 +222,14 @@ 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)
+bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, const int xy[2])
{
uiBlock *block = but->block;
- if (!ui_region_contains_point_px(region, x, y)) {
+ if (!ui_region_contains_point_px(region, xy)) {
return false;
}
- float mx = x, my = y;
+ float mx = xy[0], my = xy[1];
ui_window_to_block_fl(region, block, &mx, &my);
if (but->pie_dir != UI_RADIAL_NONE) {
@@ -245,7 +247,7 @@ bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, int x, in
bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEvent *event)
{
rcti rect;
- int x = event->x, y = event->y;
+ int x = event->xy[0], y = event->xy[1];
ui_window_to_block(region, but->block, &x, &y);
@@ -266,22 +268,42 @@ bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEv
return BLI_rcti_isect_pt(&rect, x, y);
}
+static uiBut *ui_but_find(const ARegion *region,
+ const uiButFindPollFn find_poll,
+ const void *find_custom_data)
+{
+ LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
+ LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
+ if (find_poll && find_poll(but, find_custom_data) == false) {
+ continue;
+ }
+ return but;
+ }
+ }
+
+ return NULL;
+}
+
/* x and y are only used in case event is NULL... */
uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
- const int x,
- const int y,
- const bool labeledit)
+ const int xy[2],
+ const bool labeledit,
+ const uiButFindPollFn find_poll,
+ const void *find_custom_data)
{
uiBut *butover = NULL;
- if (!ui_region_contains_point_px(region, x, y)) {
+ if (!ui_region_contains_point_px(region, xy)) {
return NULL;
}
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
- float mx = x, my = y;
+ float mx = xy[0], my = xy[1];
ui_window_to_block_fl(region, block, &mx, &my);
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
+ if (find_poll && find_poll(but, find_custom_data) == false) {
+ continue;
+ }
if (ui_but_is_interactive(but, labeledit)) {
if (but->pie_dir != UI_RADIAL_NONE) {
if (ui_but_isect_pie_seg(block, but)) {
@@ -310,7 +332,7 @@ uiBut *ui_but_find_mouse_over_ex(const ARegion *region,
uiBut *ui_but_find_mouse_over(const ARegion *region, const wmEvent *event)
{
- return ui_but_find_mouse_over_ex(region, event->x, event->y, event->ctrl != 0);
+ return ui_but_find_mouse_over_ex(region, event->xy, event->ctrl != 0, NULL, NULL);
}
uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px)
@@ -351,13 +373,13 @@ uiBut *ui_but_find_rect_over(const struct ARegion *region, const rcti *rect_px)
return butover;
}
-uiBut *ui_list_find_mouse_over_ex(ARegion *region, int x, int y)
+uiBut *ui_list_find_mouse_over_ex(const ARegion *region, const int xy[2])
{
- if (!ui_region_contains_point_px(region, x, y)) {
+ if (!ui_region_contains_point_px(region, xy)) {
return NULL;
}
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
- float mx = x, my = y;
+ float mx = xy[0], my = xy[1];
ui_window_to_block_fl(region, block, &mx, &my);
LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block->buttons) {
if (but->type == UI_BTYPE_LISTBOX && ui_but_contains_pt(but, mx, my)) {
@@ -369,9 +391,100 @@ uiBut *ui_list_find_mouse_over_ex(ARegion *region, int x, int y)
return NULL;
}
-uiBut *ui_list_find_mouse_over(ARegion *region, const wmEvent *event)
+uiBut *ui_list_find_mouse_over(const ARegion *region, const wmEvent *event)
+{
+ if (event == NULL) {
+ /* If there is no info about the mouse, just act as if there is nothing underneath it. */
+ return NULL;
+ }
+ return ui_list_find_mouse_over_ex(region, event->xy);
+}
+
+uiList *UI_list_find_mouse_over(const ARegion *region, const wmEvent *event)
+{
+ uiBut *list_but = ui_list_find_mouse_over(region, event);
+ if (!list_but) {
+ return NULL;
+ }
+
+ return list_but->custom_data;
+}
+
+static bool ui_list_contains_row(const uiBut *listbox_but, const uiBut *listrow_but)
+{
+ BLI_assert(listbox_but->type == UI_BTYPE_LISTBOX);
+ BLI_assert(listrow_but->type == UI_BTYPE_LISTROW);
+ /* The list box and its rows have the same RNA data (active data pointer/prop). */
+ return ui_but_rna_equals(listbox_but, listrow_but);
+}
+
+static bool ui_but_is_listbox_with_row(const uiBut *but, const void *customdata)
+{
+ const uiBut *row_but = customdata;
+ return (but->type == UI_BTYPE_LISTBOX) && ui_list_contains_row(but, row_but);
+}
+
+uiBut *ui_list_find_from_row(const ARegion *region, const uiBut *row_but)
+{
+ return ui_but_find(region, ui_but_is_listbox_with_row, row_but);
+}
+
+static bool ui_but_is_listrow(const uiBut *but, const void *UNUSED(customdata))
+{
+ return but->type == UI_BTYPE_LISTROW;
+}
+
+uiBut *ui_list_row_find_mouse_over(const ARegion *region, const int xy[2])
+{
+ return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_listrow, NULL);
+}
+
+struct ListRowFindIndexData {
+ int index;
+ uiBut *listbox;
+};
+
+static bool ui_but_is_listrow_at_index(const uiBut *but, const void *customdata)
+{
+ const struct ListRowFindIndexData *find_data = customdata;
+
+ return ui_but_is_listrow(but, NULL) && ui_list_contains_row(find_data->listbox, but) &&
+ (but->hardmax == find_data->index);
+}
+
+uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut *listbox)
+{
+ BLI_assert(listbox->type == UI_BTYPE_LISTBOX);
+ struct ListRowFindIndexData data = {
+ .index = index,
+ .listbox = listbox,
+ };
+ return ui_but_find(region, ui_but_is_listrow_at_index, &data);
+}
+
+static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata))
+{
+ return but->type == UI_BTYPE_TREEROW;
+}
+
+uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int xy[2])
+{
+ return ui_but_find_mouse_over_ex(region, xy, false, ui_but_is_treerow, NULL);
+}
+
+static bool ui_but_is_active_treerow(const uiBut *but, const void *customdata)
+{
+ if (!ui_but_is_treerow(but, customdata)) {
+ return false;
+ }
+
+ const uiButTreeRow *treerow_but = (const uiButTreeRow *)but;
+ return UI_tree_view_item_is_active(treerow_but->tree_item);
+}
+
+uiBut *ui_tree_row_find_active(const ARegion *region)
{
- return ui_list_find_mouse_over_ex(region, event->x, event->y);
+ return ui_but_find(region, ui_but_is_active_treerow, NULL);
}
/** \} */
@@ -466,6 +579,12 @@ size_t ui_but_drawstr_len_without_sep_char(const uiBut *but)
return strlen(but->drawstr);
}
+size_t ui_but_drawstr_without_sep_char(const uiBut *but, char *str, size_t str_maxlen)
+{
+ size_t str_len_clip = ui_but_drawstr_len_without_sep_char(but);
+ return BLI_strncpy_rlen(str, but->drawstr, min_zz(str_len_clip + 1, str_maxlen));
+}
+
size_t ui_but_tip_len_only_first_line(const uiBut *but)
{
if (but->tip == NULL) {
@@ -485,6 +604,17 @@ size_t ui_but_tip_len_only_first_line(const uiBut *but)
/** \name Block (#uiBlock) State
* \{ */
+uiBut *ui_block_active_but_get(const uiBlock *block)
+{
+ LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
+ if (but->active) {
+ return but;
+ }
+ }
+
+ return NULL;
+}
+
bool ui_block_is_menu(const uiBlock *block)
{
return (((block->flag & UI_BLOCK_LOOP) != 0) &&
@@ -551,12 +681,9 @@ bool UI_block_can_add_separator(const uiBlock *block)
/** \name Block (#uiBlock) Spatial
* \{ */
-uiBlock *ui_block_find_mouse_over_ex(const ARegion *region,
- const int x,
- const int y,
- bool only_clip)
+uiBlock *ui_block_find_mouse_over_ex(const ARegion *region, const int xy[2], bool only_clip)
{
- if (!ui_region_contains_point_px(region, x, y)) {
+ if (!ui_region_contains_point_px(region, xy)) {
return NULL;
}
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
@@ -565,7 +692,7 @@ uiBlock *ui_block_find_mouse_over_ex(const ARegion *region,
continue;
}
}
- float mx = x, my = y;
+ float mx = xy[0], my = xy[1];
ui_window_to_block_fl(region, block, &mx, &my);
if (BLI_rctf_isect_pt(&block->rect, mx, my)) {
return block;
@@ -576,7 +703,7 @@ uiBlock *ui_block_find_mouse_over_ex(const ARegion *region,
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);
+ return ui_block_find_mouse_over_ex(region, event->xy, only_clip);
}
/** \} */
@@ -588,10 +715,9 @@ uiBlock *ui_block_find_mouse_over(const ARegion *region, const wmEvent *event, b
uiBut *ui_region_find_active_but(ARegion *region)
{
LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
- LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
- if (but->active) {
- return but;
- }
+ uiBut *but = ui_block_active_but_get(block);
+ if (but) {
+ return but;
}
}
@@ -617,11 +743,11 @@ uiBut *ui_region_find_first_but_test_flag(ARegion *region, int flag_include, int
/** \name Region (#ARegion) Spatial
* \{ */
-bool ui_region_contains_point_px(const ARegion *region, int x, int y)
+bool ui_region_contains_point_px(const ARegion *region, const int xy[2])
{
rcti winrct;
ui_region_winrct_get_no_margin(region, &winrct);
- if (!BLI_rcti_isect_pt(&winrct, x, y)) {
+ if (!BLI_rcti_isect_pt_v(&winrct, xy)) {
return false;
}
@@ -632,11 +758,11 @@ bool ui_region_contains_point_px(const ARegion *region, int x, int y)
*/
if (region->v2d.mask.xmin != region->v2d.mask.xmax) {
const View2D *v2d = &region->v2d;
- int mx = x, my = y;
+ int mx = xy[0], my = xy[1];
ui_window_to_region(region, &mx, &my);
if (!BLI_rcti_isect_pt(&v2d->mask, mx, my) ||
- UI_view2d_mouse_in_scrollers(region, &region->v2d, x, y)) {
+ UI_view2d_mouse_in_scrollers(region, &region->v2d, xy)) {
return false;
}
}
@@ -673,14 +799,14 @@ bool ui_region_contains_rect_px(const ARegion *region, const rcti *rect_px)
* \{ */
/** Check if the cursor is over any popups. */
-ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y)
+ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, const int xy[2])
{
LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
rcti winrct;
ui_region_winrct_get_no_margin(region, &winrct);
- if (BLI_rcti_isect_pt(&winrct, x, y)) {
+ if (BLI_rcti_isect_pt_v(&winrct, xy)) {
return region;
}
}
@@ -689,7 +815,7 @@ ARegion *ui_screen_region_find_mouse_over_ex(bScreen *screen, int x, int y)
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);
+ return ui_screen_region_find_mouse_over_ex(screen, event->xy);
}
/** \} */
diff --git a/source/blender/editors/interface/interface_region_color_picker.c b/source/blender/editors/interface/interface_region_color_picker.c
index e68705e4321..48952c4f121 100644
--- a/source/blender/editors/interface/interface_region_color_picker.c
+++ b/source/blender/editors/interface/interface_region_color_picker.c
@@ -624,7 +624,7 @@ static void ui_block_colorpicker(uiBlock *block,
bt->custom_data = cpicker;
}
- /* Note: don't disable UI_BUT_UNDO for RGBA values, since these don't add undo steps. */
+ /* NOTE: don't disable UI_BUT_UNDO for RGBA values, since these don't add undo steps. */
/* RGB values */
UI_block_align_begin(block);
diff --git a/source/blender/editors/interface/interface_region_menu_pie.c b/source/blender/editors/interface/interface_region_menu_pie.c
index 05aa139e055..0ffbdd6911c 100644
--- a/source/blender/editors/interface/interface_region_menu_pie.c
+++ b/source/blender/editors/interface/interface_region_menu_pie.c
@@ -147,9 +147,9 @@ uiPieMenu *UI_pie_menu_begin(struct bContext *C, const char *title, int icon, co
pie->layout = UI_block_layout(
pie->block_radial, UI_LAYOUT_VERTICAL, UI_LAYOUT_PIEMENU, 0, 0, 200, 0, 0, style);
- /* Note event->x/y is where we started dragging in case of KM_CLICK_DRAG. */
- pie->mx = event->x;
- pie->my = event->y;
+ /* NOTE: #wmEvent.xy is where we started dragging in case of #KM_CLICK_DRAG. */
+ pie->mx = event->xy[0];
+ pie->my = event->xy[1];
/* create title button */
if (title[0]) {
@@ -330,7 +330,7 @@ typedef struct PieMenuLevelData {
wmOperatorType *ot;
const char *propname;
IDProperty *properties;
- int context, flag;
+ wmOperatorCallContext context, flag;
} PieMenuLevelData;
/**
@@ -381,7 +381,7 @@ void ui_pie_menu_level_create(uiBlock *block,
IDProperty *properties,
const EnumPropertyItem *items,
int totitem,
- int context,
+ wmOperatorCallContext context,
int flag)
{
const int totitem_parent = PIE_MAX_ITEMS - 1;
diff --git a/source/blender/editors/interface/interface_region_menu_popup.c b/source/blender/editors/interface/interface_region_menu_popup.c
index 58a74a3473e..408953f8d0e 100644
--- a/source/blender/editors/interface/interface_region_menu_popup.c
+++ b/source/blender/editors/interface/interface_region_menu_popup.c
@@ -338,8 +338,8 @@ uiPopupBlockHandle *ui_popup_menu_create(
if (!but) {
/* no button to start from, means we are a popup */
- pup->mx = window->eventstate->x;
- pup->my = window->eventstate->y;
+ pup->mx = window->eventstate->xy[0];
+ pup->my = window->eventstate->xy[1];
pup->popup = true;
pup->block->flag |= UI_BLOCK_NO_FLIP;
}
@@ -403,7 +403,7 @@ uiPopupMenu *UI_popup_menu_begin_ex(bContext *C,
pup->layout = UI_block_layout(
pup->block, UI_LAYOUT_VERTICAL, UI_LAYOUT_MENU, 0, 0, 200, 0, UI_MENU_PADDING, style);
- /* note, this intentionally differs from the menu & sub-menu default because many operators
+ /* NOTE: this intentionally differs from the menu & sub-menu default because many operators
* use popups like this to select one of their options -
* where having invoke doesn't make sense */
uiLayoutSetOperatorContext(pup->layout, WM_OP_EXEC_REGION_WIN);
@@ -468,8 +468,8 @@ void UI_popup_menu_end(bContext *C, uiPopupMenu *pup)
ARegion *butregion = NULL;
pup->popup = true;
- pup->mx = window->eventstate->x;
- pup->my = window->eventstate->y;
+ pup->mx = window->eventstate->xy[0];
+ pup->my = window->eventstate->xy[1];
if (pup->but) {
but = pup->but;
@@ -591,7 +591,7 @@ int UI_popup_menu_invoke(bContext *C, const char *idname, ReportList *reports)
* \{ */
void UI_popup_block_invoke_ex(
- bContext *C, uiBlockCreateFunc func, void *arg, void (*arg_free)(void *arg), bool can_refresh)
+ bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free, bool can_refresh)
{
wmWindow *window = CTX_wm_window(C);
uiPopupBlockHandle *handle;
@@ -608,10 +608,7 @@ void UI_popup_block_invoke_ex(
WM_event_add_mousemove(window);
}
-void UI_popup_block_invoke(bContext *C,
- uiBlockCreateFunc func,
- void *arg,
- void (*arg_free)(void *arg))
+void UI_popup_block_invoke(bContext *C, uiBlockCreateFunc func, void *arg, uiFreeArgFunc arg_free)
{
UI_popup_block_invoke_ex(C, func, arg, arg_free, true);
}
@@ -643,7 +640,7 @@ void UI_popup_block_ex(bContext *C,
}
#if 0 /* UNUSED */
-void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, int opcontext)
+void uiPupBlockOperator(bContext *C, uiBlockCreateFunc func, wmOperator *op, wmOperatorCallContext opcontext)
{
wmWindow *window = CTX_wm_window(C);
uiPopupBlockHandle *handle;
diff --git a/source/blender/editors/interface/interface_region_popover.c b/source/blender/editors/interface/interface_region_popover.c
index a9f72233cb1..5e7e0bfe9b5 100644
--- a/source/blender/editors/interface/interface_region_popover.c
+++ b/source/blender/editors/interface/interface_region_popover.c
@@ -90,7 +90,7 @@ struct uiPopover {
#endif
};
-static void ui_popover_create_block(bContext *C, uiPopover *pup, int opcontext)
+static void ui_popover_create_block(bContext *C, uiPopover *pup, wmOperatorCallContext opcontext)
{
BLI_assert(pup->ui_size_x != 0);
@@ -188,7 +188,7 @@ static uiBlock *ui_block_func_POPOVER(bContext *C, uiPopupBlockHandle *handle, v
}
}
- /* Estimated a maximum size so we don't go offscreen for low height
+ /* Estimated a maximum size so we don't go off-screen for low height
* areas near the bottom of the window on refreshes. */
handle->max_size_y = UI_UNIT_Y * 16.0f;
}
@@ -420,7 +420,7 @@ void UI_popover_end(bContext *C, uiPopover *pup, wmKeyMap *keymap)
* For now close this style of popovers when accessed. */
UI_block_flag_disable(pup->block, UI_BLOCK_KEEP_OPEN);
- /* panels are created flipped (from event handling pov) */
+ /* Panels are created flipped (from event handling POV). */
pup->block->flag ^= UI_BLOCK_IS_FLIP;
}
diff --git a/source/blender/editors/interface/interface_region_popup.c b/source/blender/editors/interface/interface_region_popup.c
index 8135f5a203e..0e53100f91b 100644
--- a/source/blender/editors/interface/interface_region_popup.c
+++ b/source/blender/editors/interface/interface_region_popup.c
@@ -184,10 +184,10 @@ static void ui_popup_block_position(wmWindow *window,
dir1 &= (UI_DIR_UP | UI_DIR_DOWN);
}
- if ((dir2 == 0) && (dir1 == UI_DIR_LEFT || dir1 == UI_DIR_RIGHT)) {
+ if ((dir2 == 0) && (ELEM(dir1, UI_DIR_LEFT, UI_DIR_RIGHT))) {
dir2 = UI_DIR_DOWN;
}
- if ((dir2 == 0) && (dir1 == UI_DIR_UP || dir1 == UI_DIR_DOWN)) {
+ if ((dir2 == 0) && (ELEM(dir1, UI_DIR_UP, UI_DIR_DOWN))) {
dir2 = UI_DIR_LEFT;
}
@@ -341,7 +341,7 @@ static void ui_popup_block_position(wmWindow *window,
block->safety.ymax = block->rect.ymax + s1;
}
- /* exception for switched pulldowns... */
+ /* Exception for switched pull-downs. */
if (dir1 && (dir1 & block->direction) == 0) {
if (dir2 == UI_DIR_RIGHT) {
block->safety.xmax = block->rect.xmax + s2;
@@ -773,7 +773,7 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C,
uiBlockCreateFunc create_func,
uiBlockHandleCreateFunc handle_create_func,
void *arg,
- void (*arg_free)(void *arg))
+ uiFreeArgFunc arg_free)
{
wmWindow *window = CTX_wm_window(C);
uiBut *activebut = UI_context_active_but_get(C);
@@ -803,7 +803,7 @@ uiPopupBlockHandle *ui_popup_block_create(bContext *C,
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);
+ copy_v2_v2_int(handle->popup_create_vars.event_xy, window->eventstate->xy);
/* don't allow by default, only if popup type explicitly supports it */
handle->can_refresh = false;
diff --git a/source/blender/editors/interface/interface_region_search.c b/source/blender/editors/interface/interface_region_search.c
index c35dbc5d7a6..b8a19d06be1 100644
--- a/source/blender/editors/interface/interface_region_search.c
+++ b/source/blender/editors/interface/interface_region_search.c
@@ -290,11 +290,11 @@ int ui_searchbox_find_index(ARegion *region, const char *name)
}
/* x and y in screen-coords. */
-bool ui_searchbox_inside(ARegion *region, int x, int y)
+bool ui_searchbox_inside(ARegion *region, const int xy[2])
{
uiSearchboxData *data = region->regiondata;
- return BLI_rcti_isect_pt(&data->bbox, x - region->winrct.xmin, y - region->winrct.ymin);
+ return BLI_rcti_isect_pt(&data->bbox, xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin);
}
/* string validated to be of correct length (but->hardmax) */
@@ -316,7 +316,11 @@ bool ui_searchbox_apply(uiBut *but, ARegion *region)
const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : NULL;
- BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen);
+ /* Search button with dynamic string properties may have their own method of applying
+ * the search results, so only copy the result if there is a proper space for it. */
+ if (but->hardmax != 0) {
+ BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen);
+ }
search_but->item_active = data->items.pointers[data->active];
@@ -400,8 +404,9 @@ bool ui_searchbox_event(
* (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)) {
+ if (BLI_rcti_isect_pt(&rect,
+ event->xy[0] - region->winrct.xmin,
+ event->xy[1] - region->winrct.ymin)) {
void *active = data->items.pointers[data->active];
if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) {
@@ -415,14 +420,14 @@ bool ui_searchbox_event(
case MOUSEMOVE: {
bool is_inside = false;
- if (BLI_rcti_isect_pt(&region->winrct, event->x, event->y)) {
+ if (BLI_rcti_isect_pt(&region->winrct, event->xy[0], event->xy[1])) {
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)) {
+ &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin)) {
is_inside = true;
if (data->active != a) {
data->active = a;
@@ -528,9 +533,7 @@ void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool re
/* 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++) {
+ for (int 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] :
@@ -572,7 +575,7 @@ int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *st
return match;
}
-static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
+static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region)
{
uiSearchboxData *data = region->regiondata;
@@ -586,11 +589,10 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
/* draw text */
if (data->items.totitem) {
rcti rect;
- int a;
if (data->preview) {
/* draw items */
- for (a = 0; a < data->items.totitem; a++) {
+ for (int 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 */
@@ -599,8 +601,12 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
ui_searchbox_butrect(&rect, data, a);
/* widget itself */
- ui_draw_preview_item(
- &data->fstyle, &rect, data->items.names[a], data->items.icons[a], state);
+ ui_draw_preview_item(&data->fstyle,
+ &rect,
+ data->items.names[a],
+ data->items.icons[a],
+ state,
+ UI_STYLE_TEXT_LEFT);
}
/* indicate more */
@@ -620,7 +626,7 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
else {
const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0;
/* draw items */
- for (a = 0; a < data->items.totitem; a++) {
+ for (int 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];
@@ -684,26 +690,25 @@ static void ui_searchbox_region_draw_cb(const bContext *C, ARegion *region)
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);
+ 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);
+ 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)
+static void ui_searchbox_region_free_fn(ARegion *region)
{
uiSearchboxData *data = region->regiondata;
- int a;
/* free search data */
- for (a = 0; a < data->items.maxitem; a++) {
+ for (int a = 0; a < data->items.maxitem; a++) {
MEM_freeN(data->items.names[a]);
}
MEM_freeN(data->items.names);
@@ -735,8 +740,8 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C,
static ARegionType type;
memset(&type, 0, sizeof(ARegionType));
- type.draw = ui_searchbox_region_draw_cb;
- type.free = ui_searchbox_region_free_cb;
+ type.draw = ui_searchbox_region_draw_fn;
+ type.free = ui_searchbox_region_free_fn;
type.regionid = RGN_TYPE_TEMPORARY;
region->type = &type;
@@ -873,7 +878,8 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C,
else {
data->items.maxitem = SEARCH_ITEMS;
}
- data->items.maxstrlen = but->hardmax;
+ /* In case the button's string is dynamic, make sure there are buffers available. */
+ data->items.maxstrlen = but->hardmax == 0 ? UI_MAX_NAME_STR : 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");
@@ -881,7 +887,7 @@ static ARegion *ui_searchbox_create_generic_ex(bContext *C,
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");
+ data->items.names[i] = MEM_callocN(data->items.maxstrlen + 1, "search pointers");
}
return region;
@@ -900,10 +906,9 @@ ARegion *ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiButSearc
*/
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++) {
+ for (size_t i = 0; (i < len) && str[i]; i++) {
if (str[i] >= 'A' && str[i] <= 'Z') {
if (prev_delim == false) {
str[i] += 'a' - 'A';
@@ -931,10 +936,9 @@ static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARe
/* draw text */
if (data->items.totitem) {
rcti rect;
- int a;
/* draw items */
- for (a = 0; a < data->items.totitem; a++) {
+ for (int a = 0; a < data->items.totitem; a++) {
rcti rect_pre, rect_post;
ui_searchbox_butrect(&rect, data, a);
@@ -986,13 +990,13 @@ static void ui_searchbox_region_draw_cb__operator(const bContext *UNUSED(C), ARe
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);
+ 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);
+ UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP);
GPU_blend(GPU_BLEND_NONE);
}
}
@@ -1033,8 +1037,6 @@ ARegion *ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *
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) */
@@ -1042,14 +1044,14 @@ void ui_but_search_refresh(uiButSearch *search_but)
return;
}
- items = MEM_callocN(sizeof(uiSearchItems), "search items");
+ uiSearchItems *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");
+ for (int i = 0; i < items->maxitem; i++) {
+ items->names[i] = MEM_callocN(but->hardmax + 1, "search names");
}
ui_searchbox_update_fn(but->block->evil_C, search_but, but->drawstr, items);
@@ -1066,8 +1068,8 @@ void ui_but_search_refresh(uiButSearch *search_but)
}
}
- for (x1 = 0; x1 < items->maxitem; x1++) {
- MEM_freeN(items->names[x1]);
+ for (int i = 0; i < items->maxitem; i++) {
+ MEM_freeN(items->names[i]);
}
MEM_freeN(items->names);
MEM_freeN(items);
diff --git a/source/blender/editors/interface/interface_region_tooltip.c b/source/blender/editors/interface/interface_region_tooltip.c
index bf3425dd3eb..0d8bdfc5817 100644
--- a/source/blender/editors/interface/interface_region_tooltip.c
+++ b/source/blender/editors/interface/interface_region_tooltip.c
@@ -435,7 +435,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
}
}
else {
- /* Note, this is an exceptional case, we could even remove it
+ /* NOTE: this is an exceptional case, we could even remove it
* however there have been reports of tooltips failing, so keep it for now. */
expr_result = BLI_strdup(IFACE_("Internal error!"));
is_error = true;
@@ -492,7 +492,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
}
}
else {
- /* Note, this is an exceptional case, we could even remove it
+ /* NOTE: this is an exceptional case, we could even remove it
* however there have been reports of tooltips failing, so keep it for now. */
expr_result = BLI_strdup(TIP_("Internal error!"));
is_error = true;
@@ -574,7 +574,7 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
shortcut_toolbar,
ARRAY_SIZE(shortcut_toolbar))) {
/* Generate keymap in order to inspect it.
- * Note, we could make a utility to avoid the keymap generation part of this. */
+ * NOTE: we could make a utility to avoid the keymap generation part of this. */
const char *expr_imports[] = {
"bpy", "bl_keymap_utils", "bl_keymap_utils.keymap_from_toolbar", NULL};
const char *expr =
@@ -761,7 +761,9 @@ static uiTooltipData *ui_tooltip_data_from_tool(bContext *C, uiBut *but, bool is
return data;
}
-static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
+static uiTooltipData *ui_tooltip_data_from_button_or_extra_icon(bContext *C,
+ uiBut *but,
+ uiButExtraOpIcon *extra_icon)
{
uiStringInfo but_label = {BUT_GET_LABEL, NULL};
uiStringInfo but_tip = {BUT_GET_TIP, NULL};
@@ -774,20 +776,29 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
char buf[512];
+ wmOperatorType *optype = extra_icon ? UI_but_extra_operator_icon_optype_get(extra_icon) :
+ but->optype;
+ PropertyRNA *rnaprop = extra_icon ? NULL : but->rnaprop;
+
/* create tooltip data */
uiTooltipData *data = MEM_callocN(sizeof(uiTooltipData), "uiTooltipData");
- UI_but_string_info_get(C,
- but,
- &but_label,
- &but_tip,
- &enum_label,
- &enum_tip,
- &op_keymap,
- &prop_keymap,
- &rna_struct,
- &rna_prop,
- NULL);
+ if (extra_icon) {
+ UI_but_extra_icon_string_info_get(C, extra_icon, &but_label, &but_tip, &op_keymap, NULL);
+ }
+ else {
+ UI_but_string_info_get(C,
+ but,
+ &but_label,
+ &but_tip,
+ &enum_label,
+ &enum_tip,
+ &op_keymap,
+ &prop_keymap,
+ &rna_struct,
+ &rna_prop,
+ NULL);
+ }
/* Tip Label (only for buttons not already showing the label).
* Check prefix instead of comparing because the button may include the shortcut. */
@@ -818,8 +829,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
/* special case enum rna buttons */
- if ((but->type & UI_BTYPE_ROW) && but->rnaprop &&
- RNA_property_flag(but->rnaprop) & PROP_ENUM_FLAG) {
+ if ((but->type & UI_BTYPE_ROW) && rnaprop && RNA_property_flag(rnaprop) & PROP_ENUM_FLAG) {
uiTooltipField *field = text_field_add(data,
&(uiTooltipFormat){
.style = UI_TIP_STYLE_NORMAL,
@@ -863,7 +873,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) {
/* better not show the value of a password */
- if ((but->rnaprop && (RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) == 0) {
+ if ((rnaprop && (RNA_property_subtype(rnaprop) == PROP_PASSWORD)) == 0) {
/* full string */
ui_but_string_get(but, buf, sizeof(buf));
if (buf[0]) {
@@ -878,15 +888,14 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
}
- if (but->rnaprop) {
+ if (rnaprop) {
const int unit_type = UI_but_unit_type_get(but);
if (unit_type == PROP_UNIT_ROTATION) {
- if (RNA_property_type(but->rnaprop) == PROP_FLOAT) {
- float value = RNA_property_array_check(but->rnaprop) ?
- RNA_property_float_get_index(
- &but->rnapoin, but->rnaprop, but->rnaindex) :
- RNA_property_float_get(&but->rnapoin, but->rnaprop);
+ if (RNA_property_type(rnaprop) == PROP_FLOAT) {
+ float value = RNA_property_array_check(rnaprop) ?
+ RNA_property_float_get_index(&but->rnapoin, rnaprop, but->rnaindex) :
+ RNA_property_float_get(&but->rnapoin, rnaprop);
uiTooltipField *field = text_field_add(data,
&(uiTooltipFormat){
@@ -920,15 +929,15 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
}
}
- else if (but->optype) {
- PointerRNA *opptr;
- char *str;
- opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */
+ else if (optype) {
+ PointerRNA *opptr = extra_icon ? UI_but_extra_operator_icon_opptr_get(extra_icon) :
+ /* allocated when needed, the button owns it */
+ UI_but_operator_ptr_get(but);
/* so the context is passed to fieldf functions (some py fieldf functions use it) */
WM_operator_properties_sanitize(opptr, false);
- str = ui_tooltip_text_python_from_op(C, but->optype, opptr);
+ char *str = ui_tooltip_text_python_from_op(C, optype, opptr);
/* operator info */
if (U.flag & USER_TOOLTIPS_PYTHON) {
@@ -945,18 +954,21 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
/* button is disabled, we may be able to tell user why */
- if (but->flag & UI_BUT_DISABLED) {
+ if ((but->flag & UI_BUT_DISABLED) || extra_icon) {
const char *disabled_msg = NULL;
bool disabled_msg_free = false;
/* if operator poll check failed, it can give pretty precise info why */
- if (but->optype) {
+ if (optype) {
+ const wmOperatorCallContext opcontext = extra_icon ? extra_icon->optype_params->opcontext :
+ but->opcontext;
CTX_wm_operator_poll_msg_clear(C);
- WM_operator_poll_context(C, but->optype, but->opcontext);
+ ui_but_context_poll_operator_ex(
+ C, but, &(wmOperatorCallParams){.optype = optype, .opcontext = opcontext});
disabled_msg = CTX_wm_operator_poll_msg_get(C, &disabled_msg_free);
}
/* alternatively, buttons can store some reasoning too */
- else if (but->disabled_info) {
+ else if (!extra_icon && but->disabled_info) {
disabled_msg = TIP_(but->disabled_info);
}
@@ -973,7 +985,7 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
}
}
- if ((U.flag & USER_TOOLTIPS_PYTHON) && !but->optype && rna_struct.strinfo) {
+ if ((U.flag & USER_TOOLTIPS_PYTHON) && !optype && rna_struct.strinfo) {
{
uiTooltipField *field = text_field_add(data,
&(uiTooltipFormat){
@@ -1002,9 +1014,9 @@ static uiTooltipData *ui_tooltip_data_from_button(bContext *C, uiBut *but)
/* never fails */
/* move ownership (no need for re-alloc) */
- if (but->rnaprop) {
+ if (rnaprop) {
field->text = RNA_path_full_property_py_ex(
- CTX_data_main(C), &but->rnapoin, but->rnaprop, but->rnaindex, true);
+ CTX_data_main(C), &but->rnapoin, rnaprop, but->rnaindex, true);
}
else {
field->text = RNA_path_full_struct_py(CTX_data_main(C), &but->rnapoin);
@@ -1175,9 +1187,6 @@ static ARegion *ui_tooltip_create_with_data(bContext *C,
data->wrap_width = min_ii(UI_TIP_MAXWIDTH * U.pixelsize / aspect, winx - (UI_TIP_PADDING * 2));
font_flag |= BLF_WORD_WRAP;
- if (data->fstyle.kerning == 1) {
- font_flag |= BLF_KERNING_DEFAULT;
- }
BLF_enable(data->fstyle.uifont_id, font_flag);
BLF_enable(blf_mono_font, font_flag);
BLF_wordwrap(data->fstyle.uifont_id, data->wrap_width);
@@ -1397,11 +1406,8 @@ static ARegion *ui_tooltip_create_with_data(bContext *C,
/** \name ToolTip Public API
* \{ */
-/**
- * \param is_label: When true, show a small tip that only shows the name,
- * otherwise show the full tooltip.
- */
-ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label)
+ARegion *UI_tooltip_create_from_button_or_extra_icon(
+ bContext *C, ARegion *butregion, uiBut *but, uiButExtraOpIcon *extra_icon, bool is_label)
{
wmWindow *win = CTX_wm_window(C);
/* aspect values that shrink text are likely unreadable */
@@ -1418,7 +1424,11 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b
}
if (data == NULL) {
- data = ui_tooltip_data_from_button(C, but);
+ data = ui_tooltip_data_from_button_or_extra_icon(C, but, extra_icon);
+ }
+
+ if (data == NULL) {
+ data = ui_tooltip_data_from_button_or_extra_icon(C, but, NULL);
}
if (data == NULL) {
@@ -1445,7 +1455,7 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b
init_position[1] = but->rect.ymin;
if (butregion) {
ui_block_to_window_fl(butregion, but->block, &init_position[0], &init_position[1]);
- init_position[0] = win->eventstate->x;
+ init_position[0] = win->eventstate->xy[0];
}
init_position[1] -= (UI_POPUP_MARGIN / 2);
}
@@ -1456,11 +1466,20 @@ ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *b
return region;
}
+/**
+ * \param is_label: When true, show a small tip that only shows the name, otherwise show the full
+ * tooltip.
+ */
+ARegion *UI_tooltip_create_from_button(bContext *C, ARegion *butregion, uiBut *but, bool is_label)
+{
+ return UI_tooltip_create_from_button_or_extra_icon(C, butregion, but, NULL, is_label);
+}
+
ARegion *UI_tooltip_create_from_gizmo(bContext *C, wmGizmo *gz)
{
wmWindow *win = CTX_wm_window(C);
const float aspect = 1.0f;
- float init_position[2] = {win->eventstate->x, win->eventstate->y};
+ float init_position[2] = {win->eventstate->xy[0], win->eventstate->xy[1]};
uiTooltipData *data = ui_tooltip_data_from_gizmo(C, gz);
if (data == NULL) {
@@ -1544,7 +1563,7 @@ ARegion *UI_tooltip_create_from_search_item_generic(
const float aspect = 1.0f;
const wmWindow *win = CTX_wm_window(C);
float init_position[2];
- init_position[0] = win->eventstate->x;
+ init_position[0] = win->eventstate->xy[0];
init_position[1] = item_rect->ymin + searchbox_region->winrct.ymin - (UI_POPUP_MARGIN / 2);
return ui_tooltip_create_with_data(C, data, init_position, NULL, aspect);
diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c
index 88ab6a377d0..4c640851999 100644
--- a/source/blender/editors/interface/interface_style.c
+++ b/source/blender/editors/interface/interface_style.c
@@ -83,7 +83,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id
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;
@@ -92,7 +91,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id
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;
@@ -101,7 +99,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id
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;
@@ -110,7 +107,6 @@ static uiStyle *ui_style_new(ListBase *styles, const char *name, short uifont_id
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;
@@ -164,9 +160,6 @@ void UI_fontstyle_draw_ex(const uiFontStyle *fs,
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;
}
@@ -278,19 +271,12 @@ void UI_fontstyle_draw_rotated(const uiFontStyle *fs,
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);
- }
}
/**
@@ -302,18 +288,10 @@ void UI_fontstyle_draw_rotated(const uiFontStyle *fs,
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);
- }
}
/**
@@ -326,10 +304,6 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs,
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);
{
@@ -338,11 +312,8 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs,
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(
+ UI_draw_roundbox_4fv(
&(const rctf){
.xmin = x - margin,
.xmax = x + width + margin,
@@ -351,16 +322,12 @@ void UI_fontstyle_draw_simple_backdrop(const uiFontStyle *fs,
},
true,
margin,
- color);
+ col_bg);
}
BLF_position(fs->uifont_id, x, y, 0.0f);
BLF_color4fv(fs->uifont_id, col_fg);
BLF_draw(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX);
-
- if (fs->kerning == 1) {
- BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT);
- }
}
/* ************** helpers ************************ */
@@ -405,20 +372,38 @@ const uiStyle *UI_style_get_dpi(void)
int UI_fontstyle_string_width(const uiFontStyle *fs, const char *str)
{
- int width;
+ UI_fontstyle_set(fs);
+ return (int)BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX);
+}
- if (fs->kerning == 1) {
- /* for BLF_width */
- BLF_enable(fs->uifont_id, BLF_KERNING_DEFAULT);
+/**
+ * Return the width of `str` with the spacing & kerning of `fs` with `aspect`
+ * (representing #uiBlock.aspect) applied.
+ *
+ * When calculating text width, the UI layout logic calculate widths without scale,
+ * only applying scale when drawing. This causes problems for fonts since kerning at
+ * smaller sizes often makes them wider than a scaled down version of the larger text.
+ * Resolve this by calculating the text at the on-screen size,
+ * returning the result scaled back to 1:1. See T92361.
+ */
+int UI_fontstyle_string_width_with_block_aspect(const uiFontStyle *fs,
+ const char *str,
+ const float aspect)
+{
+ uiFontStyle fs_buf;
+ if (aspect != 1.0f) {
+ fs_buf = *fs;
+ ui_fontscale(&fs_buf.points, aspect);
+ fs = &fs_buf;
}
- UI_fontstyle_set(fs);
- width = BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX);
+ int width = UI_fontstyle_string_width(fs, str);
- if (fs->kerning == 1) {
- BLF_disable(fs->uifont_id, BLF_KERNING_DEFAULT);
+ if (aspect != 1.0f) {
+ /* While in most cases rounding up isn't important, it can make a difference
+ * with small fonts (3px or less), zooming out in the node-editor for e.g. */
+ width = (int)ceilf(width * aspect);
}
-
return width;
}
@@ -434,7 +419,7 @@ int UI_fontstyle_height_max(const uiFontStyle *fs)
/* reading without uifont will create one */
void uiStyleInit(void)
{
- uiStyle *style = U.uistyles.first;
+ const uiStyle *style = U.uistyles.first;
/* recover from uninitialized dpi */
if (U.dpi == 0) {
@@ -505,9 +490,13 @@ void uiStyleInit(void)
}
if (style == NULL) {
- ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT);
+ style = ui_style_new(&U.uistyles, "Default Style", UIFONT_DEFAULT);
}
+ BLF_cache_flush_set_fn(UI_widgetbase_draw_cache_flush);
+
+ BLF_default_size(style->widgetlabel.points);
+
/* XXX, this should be moved into a style,
* but for now best only load the monospaced font once. */
BLI_assert(blf_mono_font == -1);
diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc
new file mode 100644
index 00000000000..7b2fb8f784e
--- /dev/null
+++ b/source/blender/editors/interface/interface_template_asset_view.cc
@@ -0,0 +1,292 @@
+/*
+ * 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 "DNA_space_types.h"
+#include "DNA_userdef_types.h"
+
+#include "BKE_screen.h"
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+#include "BLI_string_ref.hh"
+
+#include "BLO_readfile.h"
+
+#include "ED_asset.h"
+#include "ED_screen.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+
+#include "UI_interface.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "interface_intern.h"
+
+struct AssetViewListData {
+ AssetLibraryReference asset_library_ref;
+ bScreen *screen;
+ bool show_names;
+};
+
+static void asset_view_item_but_drag_set(uiBut *but,
+ AssetViewListData *list_data,
+ AssetHandle *asset_handle)
+{
+ ID *id = ED_asset_handle_get_local_id(asset_handle);
+ if (id != nullptr) {
+ UI_but_drag_set_id(but, id);
+ return;
+ }
+
+ char blend_path[FILE_MAX_LIBEXTRA];
+ /* Context can be null here, it's only needed for a File Browser specific hack that should go
+ * away before too long. */
+ ED_asset_handle_get_full_library_path(
+ nullptr, &list_data->asset_library_ref, asset_handle, blend_path);
+
+ if (blend_path[0]) {
+ ImBuf *imbuf = ED_assetlist_asset_image_get(asset_handle);
+ UI_but_drag_set_asset(but,
+ asset_handle,
+ BLI_strdup(blend_path),
+ ED_asset_handle_get_metadata(asset_handle),
+ FILE_ASSET_IMPORT_APPEND,
+ ED_asset_handle_get_preview_icon_id(asset_handle),
+ imbuf,
+ 1.0f);
+ }
+}
+
+static void asset_view_draw_item(uiList *ui_list,
+ bContext *UNUSED(C),
+ uiLayout *layout,
+ PointerRNA *UNUSED(dataptr),
+ PointerRNA *itemptr,
+ int UNUSED(icon),
+ PointerRNA *UNUSED(active_dataptr),
+ const char *UNUSED(active_propname),
+ int UNUSED(index),
+ int UNUSED(flt_flag))
+{
+ AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata;
+
+ BLI_assert(RNA_struct_is_a(itemptr->type, &RNA_AssetHandle));
+ AssetHandle *asset_handle = (AssetHandle *)itemptr->data;
+
+ uiLayoutSetContextPointer(layout, "asset_handle", itemptr);
+
+ uiBlock *block = uiLayoutGetBlock(layout);
+ const bool show_names = list_data->show_names;
+ /* TODO ED_fileselect_init_layout(). Share somehow? */
+ const float size_x = (96.0f / 20.0f) * UI_UNIT_X;
+ const float size_y = (96.0f / 20.0f) * UI_UNIT_Y - (show_names ? 0 : UI_UNIT_Y);
+ uiBut *but = uiDefIconTextBut(block,
+ UI_BTYPE_PREVIEW_TILE,
+ 0,
+ ED_asset_handle_get_preview_icon_id(asset_handle),
+ show_names ? ED_asset_handle_get_name(asset_handle) : "",
+ 0,
+ 0,
+ size_x,
+ size_y,
+ nullptr,
+ 0,
+ 0,
+ 0,
+ 0,
+ "");
+ ui_def_but_icon(but,
+ ED_asset_handle_get_preview_icon_id(asset_handle),
+ /* NOLINTNEXTLINE: bugprone-suspicious-enum-usage */
+ UI_HAS_ICON | UI_BUT_ICON_PREVIEW);
+ if (!ui_list->dyn_data->custom_drag_optype) {
+ asset_view_item_but_drag_set(but, list_data, asset_handle);
+ }
+}
+
+static void asset_view_listener(uiList *ui_list, wmRegionListenerParams *params)
+{
+ AssetViewListData *list_data = (AssetViewListData *)ui_list->dyn_data->customdata;
+ const wmNotifier *notifier = params->notifier;
+
+ switch (notifier->category) {
+ case NC_ID: {
+ if (ELEM(notifier->action, NA_RENAME)) {
+ ED_assetlist_storage_tag_main_data_dirty();
+ }
+ break;
+ }
+ }
+
+ if (ED_assetlist_listen(&list_data->asset_library_ref, params->notifier)) {
+ ED_region_tag_redraw(params->region);
+ }
+}
+
+uiListType *UI_UL_asset_view()
+{
+ uiListType *list_type = (uiListType *)MEM_callocN(sizeof(*list_type), __func__);
+
+ BLI_strncpy(list_type->idname, "UI_UL_asset_view", sizeof(list_type->idname));
+ list_type->draw_item = asset_view_draw_item;
+ list_type->listener = asset_view_listener;
+
+ return list_type;
+}
+
+static void asset_view_template_refresh_asset_collection(
+ const AssetLibraryReference &asset_library_ref,
+ const AssetFilterSettings &filter_settings,
+ PointerRNA &assets_dataptr,
+ const char *assets_propname)
+{
+ PropertyRNA *assets_prop = RNA_struct_find_property(&assets_dataptr, assets_propname);
+ if (!assets_prop) {
+ RNA_warning("Asset collection not found");
+ return;
+ }
+ if (RNA_property_type(assets_prop) != PROP_COLLECTION) {
+ RNA_warning("Expected a collection property");
+ return;
+ }
+ if (!RNA_struct_is_a(RNA_property_pointer_type(&assets_dataptr, assets_prop),
+ &RNA_AssetHandle)) {
+ RNA_warning("Expected a collection property for AssetHandle items");
+ return;
+ }
+
+ RNA_property_collection_clear(&assets_dataptr, assets_prop);
+
+ ED_assetlist_iterate(asset_library_ref, [&](AssetHandle asset) {
+ if (!ED_asset_filter_matches_asset(&filter_settings, &asset)) {
+ /* Don't do anything else, but return true to continue iterating. */
+ return true;
+ }
+
+ PointerRNA itemptr, fileptr;
+ RNA_property_collection_add(&assets_dataptr, assets_prop, &itemptr);
+
+ RNA_pointer_create(
+ nullptr, &RNA_FileSelectEntry, const_cast<FileDirEntry *>(asset.file_data), &fileptr);
+ RNA_pointer_set(&itemptr, "file_data", fileptr);
+
+ return true;
+ });
+}
+
+void uiTemplateAssetView(uiLayout *layout,
+ bContext *C,
+ const char *list_id,
+ PointerRNA *asset_library_dataptr,
+ const char *asset_library_propname,
+ PointerRNA *assets_dataptr,
+ const char *assets_propname,
+ PointerRNA *active_dataptr,
+ const char *active_propname,
+ const AssetFilterSettings *filter_settings,
+ const int display_flags,
+ const char *activate_opname,
+ PointerRNA *r_activate_op_properties,
+ const char *drag_opname,
+ PointerRNA *r_drag_op_properties)
+{
+ if (!list_id || !list_id[0]) {
+ RNA_warning("Asset view needs a valid identifier");
+ return;
+ }
+
+ uiLayout *col = uiLayoutColumn(layout, false);
+
+ PropertyRNA *asset_library_prop = RNA_struct_find_property(asset_library_dataptr,
+ asset_library_propname);
+ AssetLibraryReference asset_library_ref = ED_asset_library_reference_from_enum_value(
+ RNA_property_enum_get(asset_library_dataptr, asset_library_prop));
+
+ uiLayout *row = uiLayoutRow(col, true);
+ if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_LIBRARY) == 0) {
+ uiItemFullR(row, asset_library_dataptr, asset_library_prop, RNA_NO_INDEX, 0, 0, "", 0);
+ if (asset_library_ref.type != ASSET_LIBRARY_LOCAL) {
+ uiItemO(row, "", ICON_FILE_REFRESH, "ASSET_OT_list_refresh");
+ }
+ }
+
+ ED_assetlist_storage_fetch(&asset_library_ref, C);
+ ED_assetlist_ensure_previews_job(&asset_library_ref, C);
+ const int tot_items = ED_assetlist_size(&asset_library_ref);
+
+ asset_view_template_refresh_asset_collection(
+ asset_library_ref, *filter_settings, *assets_dataptr, assets_propname);
+
+ AssetViewListData *list_data = (AssetViewListData *)MEM_mallocN(sizeof(*list_data),
+ "AssetViewListData");
+ list_data->asset_library_ref = asset_library_ref;
+ list_data->screen = CTX_wm_screen(C);
+ list_data->show_names = (display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) == 0;
+
+ uiTemplateListFlags template_list_flags = UI_TEMPLATE_LIST_NO_GRIP;
+ if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_NAMES) != 0) {
+ template_list_flags |= UI_TEMPLATE_LIST_NO_NAMES;
+ }
+ if ((display_flags & UI_TEMPLATE_ASSET_DRAW_NO_FILTER) != 0) {
+ template_list_flags |= UI_TEMPLATE_LIST_NO_FILTER_OPTIONS;
+ }
+
+ /* TODO can we have some kind of model-view API to handle referencing, filtering and lazy loading
+ * (of previews) of the items? */
+ uiList *list = uiTemplateList_ex(col,
+ C,
+ "UI_UL_asset_view",
+ list_id,
+ assets_dataptr,
+ assets_propname,
+ active_dataptr,
+ active_propname,
+ nullptr,
+ tot_items,
+ 0,
+ UILST_LAYOUT_BIG_PREVIEW_GRID,
+ 0,
+ template_list_flags,
+ list_data);
+ if (!list) {
+ /* List creation failed. */
+ MEM_freeN(list_data);
+ return;
+ }
+
+ if (activate_opname) {
+ PointerRNA *ptr = UI_list_custom_activate_operator_set(
+ list, activate_opname, r_activate_op_properties != nullptr);
+ if (r_activate_op_properties && ptr) {
+ *r_activate_op_properties = *ptr;
+ }
+ }
+ if (drag_opname) {
+ PointerRNA *ptr = UI_list_custom_drag_operator_set(
+ list, drag_opname, r_drag_op_properties != nullptr);
+ if (r_drag_op_properties && ptr) {
+ *r_drag_op_properties = *ptr;
+ }
+ }
+}
diff --git a/source/blender/editors/interface/interface_template_attribute_search.cc b/source/blender/editors/interface/interface_template_attribute_search.cc
new file mode 100644
index 00000000000..85a6147432b
--- /dev/null
+++ b/source/blender/editors/interface/interface_template_attribute_search.cc
@@ -0,0 +1,126 @@
+/*
+ * 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_string_ref.hh"
+#include "BLI_string_search.h"
+
+#include "DNA_customdata_types.h"
+
+#include "RNA_access.h"
+#include "RNA_enum_types.h"
+
+#include "BLT_translation.h"
+
+#include "NOD_geometry_nodes_eval_log.hh"
+
+#include "UI_interface.h"
+#include "UI_interface.hh"
+#include "UI_resources.h"
+
+using blender::nodes::geometry_nodes_eval_log::GeometryAttributeInfo;
+
+namespace blender::ui {
+
+static StringRef attribute_data_type_string(const CustomDataType type)
+{
+ const char *name = nullptr;
+ RNA_enum_name_from_value(rna_enum_attribute_type_items, type, &name);
+ return StringRef(IFACE_(name));
+}
+
+static StringRef attribute_domain_string(const AttributeDomain domain)
+{
+ const char *name = nullptr;
+ RNA_enum_name_from_value(rna_enum_attribute_domain_items, domain, &name);
+ return StringRef(IFACE_(name));
+}
+
+static bool attribute_search_item_add(uiSearchItems *items, const GeometryAttributeInfo &item)
+{
+ const StringRef data_type_name = attribute_data_type_string(item.data_type);
+ const StringRef domain_name = attribute_domain_string(item.domain);
+ std::string search_item_text = domain_name + " " + UI_MENU_ARROW_SEP + item.name + UI_SEP_CHAR +
+ data_type_name;
+
+ return UI_search_item_add(
+ items, search_item_text.c_str(), (void *)&item, ICON_NONE, UI_BUT_HAS_SEP_CHAR, 0);
+}
+
+void attribute_search_add_items(StringRefNull str,
+ const bool is_output,
+ Span<const GeometryAttributeInfo *> infos,
+ uiSearchItems *seach_items,
+ const bool is_first)
+{
+ static GeometryAttributeInfo dummy_info;
+
+ /* Any string may be valid, so add the current search string along with the hints. */
+ if (str[0] != '\0') {
+ bool contained = false;
+ for (const GeometryAttributeInfo *attribute_info : infos) {
+ if (attribute_info->name == str) {
+ contained = true;
+ break;
+ }
+ }
+ if (!contained) {
+ dummy_info.name = str;
+ UI_search_item_add(
+ seach_items, str.c_str(), &dummy_info, is_output ? ICON_ADD : ICON_NONE, 0, 0);
+ }
+ }
+
+ if (str[0] == '\0' && !is_first) {
+ /* Allow clearing the text field when the string is empty, but not on the first pass,
+ * or opening an attribute field for the first time would show this search item. */
+ dummy_info.name = str;
+ UI_search_item_add(seach_items, str.c_str(), &dummy_info, ICON_X, 0, 0);
+ }
+
+ /* Don't filter when the menu is first opened, but still run the search
+ * so the items are in the same order they will appear in while searching. */
+ const char *string = is_first ? "" : str.c_str();
+
+ StringSearch *search = BLI_string_search_new();
+ for (const GeometryAttributeInfo *item : infos) {
+
+ /* Don't show the legacy "normal" attribute. */
+ if (item->name == "normal" && item->domain == ATTR_DOMAIN_FACE) {
+ continue;
+ }
+
+ BLI_string_search_add(search, item->name.c_str(), (void *)item);
+ }
+
+ GeometryAttributeInfo **filtered_items;
+ const int filtered_amount = BLI_string_search_query(search, string, (void ***)&filtered_items);
+
+ for (const int i : IndexRange(filtered_amount)) {
+ const GeometryAttributeInfo *item = filtered_items[i];
+ if (!attribute_search_item_add(seach_items, *item)) {
+ break;
+ }
+ }
+
+ MEM_freeN(filtered_items);
+ BLI_string_search_free(search);
+}
+
+} // namespace blender::ui
diff --git a/source/blender/editors/interface/interface_template_list.cc b/source/blender/editors/interface/interface_template_list.cc
new file mode 100644
index 00000000000..845a7813da2
--- /dev/null
+++ b/source/blender/editors/interface/interface_template_list.cc
@@ -0,0 +1,1333 @@
+/*
+ * 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 <cstdlib>
+#include <cstring>
+
+#include "BLI_fnmatch.h"
+#include "BLI_listbase.h"
+#include "BLI_math_base.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_screen.h"
+
+#include "BLT_translation.h"
+
+#include "ED_asset.h"
+#include "ED_screen.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+
+#include "UI_interface.h"
+#include "UI_view2d.h"
+
+#include "WM_api.h"
+
+#include "interface_intern.h"
+
+/**
+ * The validated data that was passed to #uiTemplateList (typically through Python).
+ * Populated through #ui_template_list_data_retrieve().
+ */
+struct TemplateListInputData {
+ PointerRNA dataptr;
+ PropertyRNA *prop;
+ PointerRNA active_dataptr;
+ PropertyRNA *activeprop;
+ const char *item_dyntip_propname;
+
+ /* Index as stored in the input property. I.e. the index before sorting. */
+ int active_item_idx;
+};
+
+/**
+ * Internal wrapper for a single item in the list (well, actually stored as a vector).
+ */
+struct _uilist_item {
+ PointerRNA item;
+ int org_idx;
+ int flt_flag;
+};
+
+/**
+ * Container for the item vector and additional info.
+ */
+struct TemplateListItems {
+ _uilist_item *item_vec;
+ /* Index of the active item following visual order. I.e. unlike
+ * TemplateListInputData.active_item_idx, this is the index after sorting. */
+ int active_item_idx;
+ int tot_items;
+};
+
+struct TemplateListLayoutDrawData {
+ uiListDrawItemFunc draw_item;
+ uiListDrawFilterFunc draw_filter;
+
+ int rows;
+ int maxrows;
+ int columns;
+};
+
+struct TemplateListVisualInfo {
+ 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. */
+};
+
+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(nullptr, &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);
+ }
+}
+
+struct StringCmp {
+ char name[MAX_IDPROP_NAME];
+ int org_idx;
+};
+
+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 = nullptr;
+ 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 = nullptr;
+ int order_idx = 0, i = 0;
+
+ if (order_by_name) {
+ names = static_cast<StringCmp *>(MEM_callocN(sizeof(StringCmp) * len, "StringCmp"));
+ }
+ if (filter_raw[0]) {
+ const size_t slen = strlen(filter_raw);
+
+ dyn_data->items_filter_flags = static_cast<int *>(
+ 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 = static_cast<char *>(
+ 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;
+ if (RNA_struct_is_a(itemptr.type, &RNA_AssetHandle)) {
+ /* XXX The AssetHandle design is hacky and meant to be temporary. It can't have a proper
+ * name property, so for now this hardcoded exception is needed. */
+ AssetHandle *asset_handle = (AssetHandle *)itemptr.data;
+ const char *asset_name = ED_asset_handle_get_name(asset_handle);
+ namebuf = BLI_strdup(asset_name);
+ }
+ else {
+ namebuf = RNA_struct_name_get_alloc(&itemptr, nullptr, 0, nullptr);
+ }
+
+ 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 = static_cast<int *>(
+ 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);
+ }
+ }
+}
+
+static void uilist_free_dyn_data(uiList *ui_list)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ if (!dyn_data) {
+ return;
+ }
+
+ if (dyn_data->custom_activate_opptr) {
+ WM_operator_properties_free(dyn_data->custom_activate_opptr);
+ MEM_freeN(dyn_data->custom_activate_opptr);
+ }
+ if (dyn_data->custom_drag_opptr) {
+ WM_operator_properties_free(dyn_data->custom_drag_opptr);
+ MEM_freeN(dyn_data->custom_drag_opptr);
+ }
+
+ MEM_SAFE_FREE(dyn_data->items_filter_flags);
+ MEM_SAFE_FREE(dyn_data->items_filter_neworder);
+ MEM_SAFE_FREE(dyn_data->customdata);
+}
+
+/**
+ * Validate input parameters and initialize \a r_data from that. Plus find the list-type and return
+ * it in \a r_list_type.
+ *
+ * \return false if the input data isn't valid. Will also raise an RNA warning in that case.
+ */
+static bool ui_template_list_data_retrieve(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,
+ TemplateListInputData *r_input_data,
+ uiListType **r_list_type)
+{
+ memset(r_input_data, 0, sizeof(*r_input_data));
+
+ /* 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 false;
+ }
+
+ if (!active_dataptr->data) {
+ RNA_warning("No active data");
+ return false;
+ }
+
+ r_input_data->dataptr = *dataptr;
+ if (dataptr->data) {
+ r_input_data->prop = RNA_struct_find_property(dataptr, propname);
+ if (!r_input_data->prop) {
+ RNA_warning("Property not found: %s.%s", RNA_struct_identifier(dataptr->type), propname);
+ return false;
+ }
+ }
+
+ r_input_data->active_dataptr = *active_dataptr;
+ r_input_data->activeprop = RNA_struct_find_property(active_dataptr, active_propname);
+ if (!r_input_data->activeprop) {
+ RNA_warning(
+ "Property not found: %s.%s", RNA_struct_identifier(active_dataptr->type), active_propname);
+ return false;
+ }
+
+ if (r_input_data->prop) {
+ const PropertyType type = RNA_property_type(r_input_data->prop);
+ if (type != PROP_COLLECTION) {
+ RNA_warning("Expected a collection data property");
+ return false;
+ }
+ }
+
+ const PropertyType activetype = RNA_property_type(r_input_data->activeprop);
+ if (activetype != PROP_INT) {
+ RNA_warning("Expected an integer active data property");
+ return false;
+ }
+
+ /* Find the uiList type. */
+ if (!(*r_list_type = WM_uilisttype_find(listtype_name, false))) {
+ RNA_warning("List type %s not found", listtype_name);
+ return false;
+ }
+
+ r_input_data->active_item_idx = RNA_property_int_get(&r_input_data->active_dataptr,
+ r_input_data->activeprop);
+ r_input_data->item_dyntip_propname = item_dyntip_propname;
+
+ return true;
+}
+
+static void ui_template_list_collect_items(PointerRNA *list_ptr,
+ PropertyRNA *list_prop,
+ uiListDyn *dyn_data,
+ int filter_exclude,
+ bool order_reverse,
+ int activei,
+ TemplateListItems *r_items)
+{
+ int i = 0;
+ int reorder_i = 0;
+ bool activei_mapping_pending = true;
+
+ RNA_PROP_BEGIN (list_ptr, itemptr, list_prop) {
+ if (!dyn_data->items_filter_flags ||
+ ((dyn_data->items_filter_flags[i] & UILST_FLT_ITEM) ^ filter_exclude)) {
+ int new_order_idx;
+ if (dyn_data->items_filter_neworder) {
+ new_order_idx = dyn_data->items_filter_neworder[reorder_i++];
+ new_order_idx = order_reverse ? dyn_data->items_shown - new_order_idx - 1 : new_order_idx;
+ }
+ else {
+ new_order_idx = order_reverse ? dyn_data->items_shown - ++reorder_i : reorder_i++;
+ }
+ // printf("%s: ii: %d\n", __func__, ii);
+ r_items->item_vec[new_order_idx].item = itemptr;
+ r_items->item_vec[new_order_idx].org_idx = i;
+ r_items->item_vec[new_order_idx].flt_flag = dyn_data->items_filter_flags ?
+ dyn_data->items_filter_flags[i] :
+ 0;
+
+ if (activei_mapping_pending && activei == i) {
+ activei = new_order_idx;
+ /* 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_order_idx >= 0) {
+ activei = prev_order_idx;
+ RNA_property_int_set(active_dataptr, activeprop, prev_i);
+ }
+ else {
+ activei = new_order_idx;
+ RNA_property_int_set(active_dataptr, activeprop, i);
+ }
+ }
+ prev_i = i;
+ prev_ii = new_order_idx;
+#endif
+ }
+ i++;
+ }
+ RNA_PROP_END;
+
+ /* If mapping is still pending, no active item was found. Mark as invalid (-1) */
+ r_items->active_item_idx = activei_mapping_pending ? -1 : activei;
+}
+
+/**
+ * Create the UI-list representation of the list items, sorted and filtered if needed.
+ */
+static void ui_template_list_collect_display_items(bContext *C,
+ uiList *ui_list,
+ TemplateListInputData *input_data,
+ const uiListFilterItemsFunc filter_items_fn,
+ TemplateListItems *r_items)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ memset(r_items, 0, sizeof(*r_items));
+
+ /* Filter list items! (not for compact layout, though) */
+ if (input_data->dataptr.data && input_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;
+#if 0
+ int prev_ii = -1, prev_i;
+#endif
+
+ if (ui_list->layout_type == UILST_LAYOUT_COMPACT) {
+ dyn_data->items_len = dyn_data->items_shown = RNA_property_collection_length(
+ &input_data->dataptr, input_data->prop);
+ }
+ else {
+ // printf("%s: filtering...\n", __func__);
+ filter_items_fn(ui_list, C, &input_data->dataptr, RNA_property_identifier(input_data->prop));
+ // printf("%s: filtering done.\n", __func__);
+ }
+
+ items_shown = dyn_data->items_shown;
+ if (items_shown >= 0) {
+ r_items->item_vec = static_cast<_uilist_item *>(
+ MEM_mallocN(sizeof(*r_items->item_vec) * items_shown, __func__));
+ // printf("%s: items shown: %d.\n", __func__, items_shown);
+
+ ui_template_list_collect_items(&input_data->dataptr,
+ input_data->prop,
+ dyn_data,
+ filter_exclude,
+ order_reverse,
+ input_data->active_item_idx,
+ r_items);
+ }
+ if (dyn_data->items_shown >= 0) {
+ r_items->tot_items = dyn_data->items_shown;
+ }
+ else {
+ r_items->tot_items = dyn_data->items_len;
+ }
+ }
+}
+
+static void ui_template_list_free_items(TemplateListItems *items)
+{
+ if (items->item_vec) {
+ MEM_freeN(items->item_vec);
+ }
+}
+
+static void uilist_prepare(uiList *ui_list,
+ const TemplateListItems *items,
+ const TemplateListLayoutDrawData *layout_data,
+ TemplateListVisualInfo *r_visual_info)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ const bool use_auto_size = (ui_list->list_grip <
+ (layout_data->rows - UI_LIST_AUTO_SIZE_THRESHOLD));
+
+ int actual_rows = layout_data->rows;
+ int actual_maxrows = layout_data->maxrows;
+ int columns = layout_data->columns;
+
+ /* default rows */
+ if (actual_rows <= 0) {
+ actual_rows = 5;
+ }
+ dyn_data->visual_height_min = actual_rows;
+ if (actual_maxrows < actual_rows) {
+ actual_maxrows = max_ii(actual_rows, 5);
+ }
+ if (columns <= 0) {
+ columns = 9;
+ }
+
+ int activei_row;
+ if (columns > 1) {
+ dyn_data->height = (int)ceil((double)items->tot_items / (double)columns);
+ activei_row = (int)floor((double)items->active_item_idx / (double)columns);
+ }
+ else {
+ dyn_data->height = items->tot_items;
+ activei_row = items->active_item_idx;
+ }
+
+ dyn_data->columns = columns;
+
+ if (!use_auto_size) {
+ /* No auto-size, yet we clamp at min size! */
+ actual_rows = max_ii(ui_list->list_grip, actual_rows);
+ }
+ else if ((actual_rows != actual_maxrows) && (dyn_data->height > actual_rows)) {
+ /* Expand size if needed and possible. */
+ actual_rows = min_ii(dyn_data->height, actual_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 != items->tot_items) ||
+ (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 + actual_rows) {
+ ui_list->list_scroll = activei_row - actual_rows + 1;
+ }
+ ui_list->flag &= ~UILST_SCROLL_TO_ACTIVE_ITEM;
+ }
+
+ const int max_scroll = max_ii(0, dyn_data->height - actual_rows);
+ CLAMP(ui_list->list_scroll, 0, max_scroll);
+ ui_list->list_last_len = items->tot_items;
+ dyn_data->visual_height = actual_rows;
+ r_visual_info->visual_items = actual_rows * columns;
+ r_visual_info->start_idx = ui_list->list_scroll * columns;
+ r_visual_info->end_idx = min_ii(r_visual_info->start_idx + actual_rows * columns,
+ items->tot_items);
+}
+
+static void uilist_resize_update_cb(bContext *C, void *arg1, void *UNUSED(arg2))
+{
+ uiList *ui_list = static_cast<uiList *>(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, nullptr, 0, nullptr);
+ }
+ }
+ return nullptr;
+}
+
+static char *uilist_item_tooltip_func(bContext *UNUSED(C), void *argN, const char *tip)
+{
+ char *dyn_tooltip = static_cast<char *>(argN);
+ return BLI_sprintfN("%s - %s", tip, dyn_tooltip);
+}
+
+/**
+ * \note Note that \a layout_type may be null.
+ */
+static uiList *ui_list_ensure(bContext *C,
+ uiListType *ui_list_type,
+ const char *list_id,
+ int layout_type,
+ bool sort_reverse,
+ bool sort_lock)
+{
+ /* Allows to work in popups. */
+ ARegion *region = CTX_wm_menu(C);
+ if (region == nullptr) {
+ region = CTX_wm_region(C);
+ }
+
+ /* Find or add the uiList to the current Region. */
+
+ char full_list_id[UI_MAX_NAME_STR];
+ WM_uilisttype_to_full_list_id(ui_list_type, list_id, full_list_id);
+
+ uiList *ui_list = static_cast<uiList *>(
+ BLI_findstring(&region->ui_lists, full_list_id, offsetof(uiList, list_id)));
+
+ if (!ui_list) {
+ ui_list = static_cast<uiList *>(MEM_callocN(sizeof(uiList), "uiList"));
+ BLI_strncpy(ui_list->list_id, full_list_id, sizeof(ui_list->list_id));
+ BLI_addtail(&region->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 = static_cast<uiListDyn *>(
+ MEM_callocN(sizeof(uiListDyn), "uiList.dyn_data"));
+ }
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ /* Note that this isn't a `uiListType` callback, it's stored in the runtime list data. Otherwise
+ * the runtime data could leak when the type is unregistered (e.g. on "Reload Scripts"). */
+ dyn_data->free_runtime_data_fn = uilist_free_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;
+
+ return ui_list;
+}
+
+static void ui_template_list_layout_draw(bContext *C,
+ uiList *ui_list,
+ uiLayout *layout,
+ TemplateListInputData *input_data,
+ TemplateListItems *items,
+ const TemplateListLayoutDrawData *layout_data,
+ const enum uiTemplateListFlags flags)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ const char *active_propname = RNA_property_identifier(input_data->activeprop);
+
+ uiLayout *glob = nullptr, *box, *row, *col, *subrow, *sub, *overlap;
+ char numstr[32];
+ int rnaicon = ICON_NONE, icon = ICON_NONE;
+ uiBut *but;
+
+ uiBlock *block = uiLayoutGetBlock(layout);
+
+ /* get icon */
+ if (input_data->dataptr.data && input_data->prop) {
+ StructRNA *ptype = RNA_property_pointer_type(&input_data->dataptr, input_data->prop);
+ rnaicon = RNA_struct_ui_icon(ptype);
+ }
+
+ TemplateListVisualInfo visual_info;
+ switch (ui_list->layout_type) {
+ case UILST_LAYOUT_DEFAULT: {
+ /* layout */
+ box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop);
+ glob = uiLayoutColumn(box, true);
+ row = uiLayoutRow(glob, false);
+ col = uiLayoutColumn(row, true);
+
+ TemplateListLayoutDrawData adjusted_layout_data = *layout_data;
+ adjusted_layout_data.columns = 1;
+ /* init numbers */
+ uilist_prepare(ui_list, items, &adjusted_layout_data, &visual_info);
+
+ int i = 0;
+ if (input_data->dataptr.data && input_data->prop) {
+ /* create list items */
+ for (i = visual_info.start_idx; i < visual_info.end_idx; i++) {
+ PointerRNA *itemptr = &items->item_vec[i].item;
+ void *dyntip_data;
+ const int org_i = items->item_vec[i].org_idx;
+ const int flt_flag = items->item_vec[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 */
+ uiLayoutRow(overlap, false);
+
+ but = uiDefButR_prop(subblock,
+ UI_BTYPE_LISTROW,
+ 0,
+ "",
+ 0,
+ 0,
+ UI_UNIT_X * 10,
+ UI_UNIT_Y,
+ &input_data->active_dataptr,
+ input_data->activeprop,
+ 0,
+ 0,
+ org_i,
+ 0,
+ 0,
+ TIP_("Double click to rename"));
+ if ((dyntip_data = uilist_item_use_dynamic_tooltip(itemptr,
+ input_data->item_dyntip_propname))) {
+ UI_but_func_tooltip_set(but, uilist_item_tooltip_func, dyntip_data, MEM_freeN);
+ }
+
+ sub = uiLayoutRow(overlap, false);
+
+ icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false);
+ if (icon == ICON_DOT) {
+ icon = ICON_NONE;
+ }
+ layout_data->draw_item(ui_list,
+ C,
+ sub,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->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 == items->active_item_idx) {
+ ui_layout_list_set_labels_active(sub);
+ }
+
+ UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM);
+ }
+ }
+
+ /* add dummy buttons to fill space */
+ for (; i < visual_info.start_idx + visual_info.visual_items; i++) {
+ uiItemL(col, "", ICON_NONE);
+ }
+
+ /* add scrollbar */
+ if (items->tot_items > visual_info.visual_items) {
+ 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 ((input_data->dataptr.data && input_data->prop) && (dyn_data->items_shown > 0) &&
+ (items->active_item_idx >= 0) && (items->active_item_idx < dyn_data->items_shown)) {
+ PointerRNA *itemptr = &items->item_vec[items->active_item_idx].item;
+ const int org_i = items->item_vec[items->active_item_idx].org_idx;
+
+ icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false);
+ if (icon == ICON_DOT) {
+ icon = ICON_NONE;
+ }
+ layout_data->draw_item(ui_list,
+ C,
+ row,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->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,
+ &input_data->active_dataptr,
+ input_data->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, &input_data->active_dataptr, input_data->activeprop);
+ glob = uiLayoutColumn(box, true);
+ row = uiLayoutRow(glob, false);
+ col = uiLayoutColumn(row, true);
+ subrow = nullptr; /* Quite gcc warning! */
+
+ uilist_prepare(ui_list, items, layout_data, &visual_info);
+
+ int i = 0;
+ if (input_data->dataptr.data && input_data->prop) {
+ /* create list items */
+ for (i = visual_info.start_idx; i < visual_info.end_idx; i++) {
+ PointerRNA *itemptr = &items->item_vec[i].item;
+ const int org_i = items->item_vec[i].org_idx;
+ const int flt_flag = items->item_vec[i].flt_flag;
+
+ /* create button */
+ if (!(i % layout_data->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 */
+ uiLayoutRow(overlap, false);
+
+ but = uiDefButR_prop(subblock,
+ UI_BTYPE_LISTROW,
+ 0,
+ "",
+ 0,
+ 0,
+ UI_UNIT_X * 10,
+ UI_UNIT_Y,
+ &input_data->active_dataptr,
+ input_data->activeprop,
+ 0,
+ 0,
+ org_i,
+ 0,
+ 0,
+ nullptr);
+ UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP);
+
+ sub = uiLayoutRow(overlap, false);
+
+ icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false);
+ layout_data->draw_item(ui_list,
+ C,
+ sub,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->active_dataptr,
+ active_propname,
+ org_i,
+ flt_flag);
+
+ /* If we are "drawing" active item, set all labels as active. */
+ if (i == items->active_item_idx) {
+ ui_layout_list_set_labels_active(sub);
+ }
+
+ UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM);
+ }
+ }
+
+ /* add dummy buttons to fill space */
+ for (; i < visual_info.start_idx + visual_info.visual_items; i++) {
+ if (!(i % layout_data->columns)) {
+ subrow = uiLayoutRow(col, false);
+ }
+ uiItemL(subrow, "", ICON_NONE);
+ }
+
+ /* add scrollbar */
+ if (items->tot_items > visual_info.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_BIG_PREVIEW_GRID:
+ box = uiLayoutListBox(layout, ui_list, &input_data->active_dataptr, input_data->activeprop);
+ /* For grip button. */
+ glob = uiLayoutColumn(box, true);
+ /* For scrollbar. */
+ row = uiLayoutRow(glob, false);
+
+ const bool show_names = (flags & UI_TEMPLATE_LIST_NO_NAMES) == 0;
+
+ /* TODO ED_fileselect_init_layout(). Share somehow? */
+ float size_x = (96.0f / 20.0f) * UI_UNIT_X;
+ float size_y = (96.0f / 20.0f) * UI_UNIT_Y;
+
+ if (!show_names) {
+ size_y -= UI_UNIT_Y;
+ }
+
+ const int cols_per_row = MAX2((uiLayoutGetWidth(box) - V2D_SCROLL_WIDTH) / size_x, 1);
+ uiLayout *grid = uiLayoutGridFlow(row, true, cols_per_row, true, true, true);
+
+ TemplateListLayoutDrawData adjusted_layout_data = *layout_data;
+ adjusted_layout_data.columns = cols_per_row;
+ uilist_prepare(ui_list, items, &adjusted_layout_data, &visual_info);
+
+ if (input_data->dataptr.data && input_data->prop) {
+ /* create list items */
+ for (int i = visual_info.start_idx; i < visual_info.end_idx; i++) {
+ PointerRNA *itemptr = &items->item_vec[i].item;
+ const int org_i = items->item_vec[i].org_idx;
+ const int flt_flag = items->item_vec[i].flt_flag;
+
+ overlap = uiLayoutOverlap(grid);
+ col = uiLayoutColumn(overlap, false);
+
+ uiBlock *subblock = uiLayoutGetBlock(col);
+ UI_block_flag_enable(subblock, UI_BLOCK_LIST_ITEM);
+
+ but = uiDefButR_prop(subblock,
+ UI_BTYPE_LISTROW,
+ 0,
+ "",
+ 0,
+ 0,
+ size_x,
+ size_y,
+ &input_data->active_dataptr,
+ input_data->activeprop,
+ 0,
+ 0,
+ org_i,
+ 0,
+ 0,
+ nullptr);
+ UI_but_drawflag_enable(but, UI_BUT_NO_TOOLTIP);
+
+ col = uiLayoutColumn(overlap, false);
+
+ icon = UI_icon_from_rnaptr(C, itemptr, rnaicon, false);
+ layout_data->draw_item(ui_list,
+ C,
+ col,
+ &input_data->dataptr,
+ itemptr,
+ icon,
+ &input_data->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(col);
+
+ /* If we are "drawing" active item, set all labels as active. */
+ if (i == items->active_item_idx) {
+ ui_layout_list_set_labels_active(col);
+ }
+
+ UI_block_flag_disable(subblock, UI_BLOCK_LIST_ITEM);
+ }
+ }
+
+ if (items->tot_items > visual_info.visual_items) {
+ /* col = */ uiLayoutColumn(row, false);
+ uiDefButI(block,
+ UI_BTYPE_SCROLL,
+ 0,
+ "",
+ 0,
+ 0,
+ V2D_SCROLL_WIDTH,
+ size_y * dyn_data->visual_height,
+ &ui_list->list_scroll,
+ 0,
+ dyn_data->height - dyn_data->visual_height,
+ dyn_data->visual_height,
+ 0,
+ "");
+ }
+ break;
+ }
+
+ const bool add_filters_but = (flags & UI_TEMPLATE_LIST_NO_FILTER_OPTIONS) == 0;
+ if (glob && add_filters_but) {
+ const bool add_grip_but = (flags & UI_TEMPLATE_LIST_NO_GRIP) == 0;
+
+ /* 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 */
+
+ if (add_grip_but) {
+ 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, nullptr);
+ }
+
+ 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,
+ nullptr,
+ 0.0,
+ 0.0,
+ 0,
+ 0,
+ "");
+
+ layout_data->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 */
+
+ if (add_grip_but) {
+ 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, nullptr);
+ }
+
+ UI_block_emboss_set(subblock, UI_EMBOSS);
+ }
+ }
+}
+
+uiList *uiTemplateList_ex(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,
+ enum uiTemplateListFlags flags,
+ void *customdata)
+{
+ TemplateListInputData input_data = {{nullptr}};
+ uiListType *ui_list_type;
+ if (!ui_template_list_data_retrieve(listtype_name,
+ list_id,
+ dataptr,
+ propname,
+ active_dataptr,
+ active_propname,
+ item_dyntip_propname,
+ &input_data,
+ &ui_list_type)) {
+ return nullptr;
+ }
+
+ 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;
+
+ uiList *ui_list = ui_list_ensure(C,
+ ui_list_type,
+ list_id,
+ layout_type,
+ flags & UI_TEMPLATE_LIST_SORT_REVERSE,
+ flags & UI_TEMPLATE_LIST_SORT_LOCK);
+ uiListDyn *dyn_data = ui_list->dyn_data;
+
+ MEM_SAFE_FREE(dyn_data->customdata);
+ dyn_data->customdata = customdata;
+
+ /* When active item changed since last draw, scroll to it. */
+ if (input_data.active_item_idx != ui_list->list_last_activei) {
+ ui_list->flag |= UILST_SCROLL_TO_ACTIVE_ITEM;
+ ui_list->list_last_activei = input_data.active_item_idx;
+ }
+
+ TemplateListItems items;
+ ui_template_list_collect_display_items(C, ui_list, &input_data, filter_items, &items);
+
+ TemplateListLayoutDrawData layout_data;
+ layout_data.draw_item = draw_item;
+ layout_data.draw_filter = draw_filter;
+ layout_data.rows = rows;
+ layout_data.maxrows = maxrows;
+ layout_data.columns = columns;
+
+ ui_template_list_layout_draw(C, ui_list, layout, &input_data, &items, &layout_data, flags);
+
+ ui_template_list_free_items(&items);
+
+ return ui_list;
+}
+
+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,
+ enum uiTemplateListFlags flags)
+{
+ uiTemplateList_ex(layout,
+ C,
+ listtype_name,
+ list_id,
+ dataptr,
+ propname,
+ active_dataptr,
+ active_propname,
+ item_dyntip_propname,
+ rows,
+ maxrows,
+ layout_type,
+ columns,
+ flags,
+ nullptr);
+}
+
+/**
+ * \return: A RNA pointer for the operator properties.
+ */
+PointerRNA *UI_list_custom_activate_operator_set(uiList *ui_list,
+ const char *opname,
+ bool create_properties)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ dyn_data->custom_activate_optype = WM_operatortype_find(opname, false);
+ if (!dyn_data->custom_activate_optype) {
+ return nullptr;
+ }
+
+ if (create_properties) {
+ PointerRNA *opptr = dyn_data->custom_activate_opptr;
+ WM_operator_properties_alloc(
+ &dyn_data->custom_activate_opptr, opptr ? (IDProperty **)&opptr->data : nullptr, opname);
+ }
+
+ return dyn_data->custom_activate_opptr;
+}
+
+/**
+ * \return: A RNA pointer for the operator properties.
+ */
+PointerRNA *UI_list_custom_drag_operator_set(uiList *ui_list,
+ const char *opname,
+ bool create_properties)
+{
+ uiListDyn *dyn_data = ui_list->dyn_data;
+ dyn_data->custom_drag_optype = WM_operatortype_find(opname, false);
+ if (!dyn_data->custom_drag_optype) {
+ return nullptr;
+ }
+
+ if (create_properties) {
+ PointerRNA *opptr = dyn_data->custom_drag_opptr;
+ WM_operator_properties_alloc(
+ &dyn_data->custom_drag_opptr, opptr ? (IDProperty **)&opptr->data : nullptr, opname);
+ }
+
+ return dyn_data->custom_drag_opptr;
+}
+
+/* -------------------------------------------------------------------- */
+
+/** \name List-types Registration
+ * \{ */
+
+void ED_uilisttypes_ui(void)
+{
+ WM_uilisttype_add(UI_UL_asset_view());
+}
+
+/** \} */
diff --git a/source/blender/editors/interface/interface_template_search_menu.c b/source/blender/editors/interface/interface_template_search_menu.c
index 91ad6619889..26250e105eb 100644
--- a/source/blender/editors/interface/interface_template_search_menu.c
+++ b/source/blender/editors/interface/interface_template_search_menu.c
@@ -69,9 +69,6 @@
/** \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.
@@ -118,7 +115,7 @@ struct MenuSearch_Item {
struct {
wmOperatorType *type;
PointerRNA *opptr;
- short opcontext;
+ wmOperatorCallContext opcontext;
bContextStore *context;
} op;
@@ -350,24 +347,28 @@ static void menu_types_add_from_keymap_items(bContext *C,
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;
+ wmEventHandler_KeymapResult km_result;
+ WM_event_get_keymaps_from_handler(wm, win, handler, &km_result);
+ for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
+ wmKeyMap *keymap = km_result.keymaps[km_index];
+ 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;
+ }
}
}
}
@@ -411,7 +412,7 @@ static void menu_items_from_all_operators(bContext *C, struct MenuSearch_Data *d
char uiname[256];
WM_operator_py_idname(idname_as_py, ot->idname);
- SNPRINTF(uiname, "%s " MENU_SEP "%s", idname_as_py, ot_ui_name);
+ SNPRINTF(uiname, "%s " UI_MENU_ARROW_SEP "%s", idname_as_py, ot_ui_name);
item->drawwstr_full = strdup_memarena(memarena, uiname);
item->drawstr = ot_ui_name;
@@ -837,7 +838,7 @@ static struct MenuSearch_Data *menu_items_from_ui_create(
}
while (menu_parent) {
BLI_dynstr_append(dyn_str, menu_parent->drawstr);
- BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ BLI_dynstr_append(dyn_str, " " UI_MENU_ARROW_SEP " ");
menu_parent = menu_parent->temp_child;
}
}
@@ -855,13 +856,13 @@ static struct MenuSearch_Data *menu_items_from_ui_create(
BLI_dynstr_appendf(dyn_str, " (%s)", kmi_str);
}
- BLI_dynstr_append(dyn_str, " " MENU_SEP " ");
+ BLI_dynstr_append(dyn_str, " " UI_MENU_ARROW_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, " " UI_MENU_ARROW_SEP " ");
}
BLI_dynstr_append(dyn_str, item->drawstr);
@@ -873,7 +874,7 @@ static struct MenuSearch_Data *menu_items_from_ui_create(
/* Finally sort menu items.
*
- * Note: we might want to keep the in-menu order, for now sort all. */
+ * 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);
@@ -955,7 +956,8 @@ static void menu_search_exec_fn(bContext *C, void *UNUSED(arg1), void *arg2)
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);
+ WM_operator_name_call_ptr_with_depends_on_cursor(
+ C, item->op.type, item->op.opcontext, item->op.opptr, item->drawstr);
CTX_store_set(C, NULL);
break;
}
@@ -1037,7 +1039,7 @@ static void menu_search_update_fn(const bContext *UNUSED(C),
static bool ui_search_menu_create_context_menu(struct bContext *C,
void *arg,
void *active,
- const struct wmEvent *UNUSED(event))
+ const struct wmEvent *event)
{
struct MenuSearch_Data *data = arg;
struct MenuSearch_Item *item = active;
@@ -1058,7 +1060,7 @@ static bool ui_search_menu_create_context_menu(struct bContext *C,
CTX_wm_region_set(C, item->wm_context->region);
}
- if (ui_popup_context_menu_for_button(C, but)) {
+ if (ui_popup_context_menu_for_button(C, but, event)) {
has_menu = true;
}
@@ -1097,8 +1099,8 @@ static struct ARegion *ui_search_menu_create_tooltip(struct bContext *C,
/* 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);
+ tip_init[0] = event->xy[0];
+ tip_init[1] = event->xy[1] - (UI_UNIT_Y / 2);
ui_window_to_block_fl(region, block, &tip_init[0], &tip_init[1]);
but->rect.xmin = tip_init[0];
@@ -1155,7 +1157,7 @@ void UI_but_func_menu_search(uiBut *but)
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);
+ UI_but_func_search_set_sep_string(but, UI_MENU_ARROW_SEP);
}
void uiTemplateMenuSearch(uiLayout *layout)
@@ -1172,6 +1174,4 @@ void uiTemplateMenuSearch(uiLayout *layout)
UI_but_func_menu_search(but);
}
-#undef MENU_SEP
-
/** \} */
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c
index 2c58277293d..b30a86c5fcf 100644
--- a/source/blender/editors/interface/interface_templates.c
+++ b/source/blender/editors/interface/interface_templates.c
@@ -84,6 +84,8 @@
#include "ED_screen.h"
#include "ED_undo.h"
+#include "RE_engine.h"
+
#include "RNA_access.h"
#include "WM_api.h"
@@ -395,7 +397,7 @@ static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchIt
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);
+ const bool has_sep_char = ID_IS_LINKED(id);
/* When using previews, the library hint (linked, overridden, missing) is added with a
* character prefix, otherwise we can use a icon. */
@@ -671,8 +673,8 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
}
}
else {
- if (BKE_lib_id_make_local(bmain, id, false, 0)) {
- BKE_main_id_newptr_and_tag_clear(bmain);
+ if (BKE_lib_id_make_local(bmain, id, 0)) {
+ BKE_id_newptr_and_tag_clear(id);
/* Reassign to get proper updates/notifiers. */
idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
@@ -877,7 +879,7 @@ static uiBut *template_id_def_new_but(uiBlock *block,
BLT_I18NCONTEXT_ID_POINTCLOUD,
BLT_I18NCONTEXT_ID_VOLUME,
BLT_I18NCONTEXT_ID_SIMULATION, );
- /* Note: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters,
+ /* 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. */
@@ -1029,7 +1031,7 @@ static void template_ID(const bContext *C,
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) ||
+ const bool disabled = (!BKE_idtype_idcode_is_localizable(GS(id->name)) ||
(idfrom && idfrom->lib));
but = uiDefIconBut(block,
UI_BTYPE_BUT,
@@ -1110,24 +1112,41 @@ static void template_ID(const bContext *C,
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
- if (id->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, 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 (!ID_IS_LINKED(id)) {
+ if (ID_IS_ASSET(id)) {
+ uiDefIconButO(block,
+ /* Using `_N` version allows us to get the 'active' state by default. */
+ UI_BTYPE_ICON_TOGGLE_N,
+ "ASSET_OT_clear",
+ WM_OP_INVOKE_DEFAULT,
+ /* 'active' state of a toggle button uses icon + 1, so to get proper asset
+ * icon we need to pass its value - 1 here. */
+ ICON_ASSET_MANAGER - 1,
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ NULL);
+ }
+ else if (!(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, 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);
+ }
}
}
@@ -1722,7 +1741,7 @@ static void template_search_add_button_name(uiBlock *block,
static void template_search_add_button_operator(uiBlock *block,
const char *const operator_name,
- const int opcontext,
+ const wmOperatorCallContext opcontext,
const int icon,
const bool editable)
{
@@ -2362,7 +2381,7 @@ static eAutoPropButsReturn template_operator_property_buts_draw_single(
/* 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");
+ UI_block_lock_set(block, true, "Operator can't redo");
return return_info;
}
@@ -2399,8 +2418,8 @@ static eAutoPropButsReturn template_operator_property_buts_draw_single(
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. */
+ /* #UI_LAYOUT_OP_SHOW_EMPTY ignored. retun_info is ignored too.
+ * We could allow #wmOperatorType.ui callback to return this, but not needed right now. */
}
else {
wmWindowManager *wm = CTX_wm_manager(C);
@@ -2556,7 +2575,7 @@ void uiTemplateOperatorPropertyButs(
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. */
+ * check-boxes only use half the width. */
if (ui_layout_operator_properties_only_booleans(C, wm, op, flag)) {
flag |= UI_TEMPLATE_OP_PROPS_NO_SPLIT_LAYOUT;
}
@@ -2621,6 +2640,72 @@ 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 constraint_ops_extra_draw(bContext *C, uiLayout *layout, void *con_v)
+{
+ PointerRNA op_ptr;
+ uiLayout *row;
+ bConstraint *con = (bConstraint *)con_v;
+
+ PointerRNA ptr;
+ Object *ob = ED_object_active_context(C);
+
+ RNA_pointer_create(&ob->id, &RNA_Constraint, con, &ptr);
+ uiLayoutSetContextPointer(layout, "constraint", &ptr);
+ uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
+
+ uiLayoutSetUnitsX(layout, 4.0f);
+
+ /* Apply. */
+ uiItemO(layout,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Apply"),
+ ICON_CHECKMARK,
+ "CONSTRAINT_OT_apply");
+
+ /* Duplicate. */
+ uiItemO(layout,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Duplicate"),
+ ICON_DUPLICATE,
+ "CONSTRAINT_OT_copy");
+
+ uiItemO(layout,
+ CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"),
+ 0,
+ "CONSTRAINT_OT_copy_to_selected");
+
+ uiItemS(layout);
+
+ /* Move to first. */
+ row = uiLayoutColumn(layout, false);
+ uiItemFullO(row,
+ "CONSTRAINT_OT_move_to_index",
+ IFACE_("Move to First"),
+ ICON_TRIA_UP,
+ NULL,
+ WM_OP_INVOKE_DEFAULT,
+ 0,
+ &op_ptr);
+ RNA_int_set(&op_ptr, "index", 0);
+ if (!con->prev) {
+ uiLayoutSetEnabled(row, false);
+ }
+
+ /* Move to last. */
+ row = uiLayoutColumn(layout, false);
+ uiItemFullO(row,
+ "CONSTRAINT_OT_move_to_index",
+ IFACE_("Move to Last"),
+ ICON_TRIA_DOWN,
+ NULL,
+ WM_OP_INVOKE_DEFAULT,
+ 0,
+ &op_ptr);
+ ListBase *constraint_list = ED_object_constraint_list_from_constraint(ob, con, NULL);
+ RNA_int_set(&op_ptr, "index", BLI_listbase_count(constraint_list) - 1);
+ if (!con->next) {
+ uiLayoutSetEnabled(row, false);
+ }
+}
+
static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *con)
{
bPoseChannel *pchan = BKE_pose_channel_active(ob);
@@ -2652,11 +2737,13 @@ static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *co
UI_block_emboss_set(block, UI_EMBOSS);
+ uiLayout *row = uiLayoutRow(layout, true);
+
if (proxy_protected == 0) {
- uiItemR(layout, &ptr, "name", 0, "", ICON_NONE);
+ uiItemR(row, &ptr, "name", 0, "", ICON_NONE);
}
else {
- uiItemL(layout, con->name, ICON_NONE);
+ uiItemL(row, IFACE_(con->name), ICON_NONE);
}
/* proxy-protected constraints cannot be edited, so hide up/down + close buttons */
@@ -2697,22 +2784,22 @@ static void draw_constraint_header(uiLayout *layout, Object *ob, bConstraint *co
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);
+ /* Enabled eye icon. */
+ uiItemR(row, &ptr, "enabled", 0, "", ICON_NONE);
- uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
+ /* Extra operators menu. */
+ uiItemMenuF(row, "", ICON_DOWNARROW_HLT, constraint_ops_extra_draw, con);
/* 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);
+ sub = uiLayoutRow(row, false);
+ uiLayoutSetEmboss(sub, UI_EMBOSS_NONE);
+ uiLayoutSetOperatorContext(sub, WM_OP_INVOKE_DEFAULT);
+ uiItemO(sub, "", ICON_X, "CONSTRAINT_OT_delete");
}
+ /* 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"));
@@ -4015,23 +4102,23 @@ static void curvemap_tools_dofunc(bContext *C, void *cumap_v, int event)
case UICURVE_FUNC_RESET_VIEW:
BKE_curvemapping_reset_view(cumap);
break;
- case UICURVE_FUNC_HANDLE_VECTOR: /* set vector */
+ 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 */
+ 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 */
+ 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 */
+ case UICURVE_FUNC_EXTEND_HOZ: /* Extend horizontal. */
cumap->flag &= ~CUMA_EXTEND_EXTRAPOLATE;
BKE_curvemapping_changed(cumap, false);
break;
- case UICURVE_FUNC_EXTEND_EXP: /* extend extrapolate */
+ case UICURVE_FUNC_EXTEND_EXP: /* Extend extrapolate. */
cumap->flag |= CUMA_EXTEND_EXTRAPOLATE;
BKE_curvemapping_changed(cumap, false);
break;
@@ -5645,887 +5732,6 @@ void uiTemplateLayers(uiLayout *layout,
/** \} */
/* -------------------------------------------------------------------- */
-/** \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(&region->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(&region->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
* \{ */
@@ -6634,6 +5840,11 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C)
icon = ICON_SEQUENCE;
break;
}
+ if (WM_jobs_test(wm, scene, WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL)) {
+ 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;
@@ -6762,7 +5973,7 @@ void uiTemplateRunningJobs(uiLayout *layout, bContext *C)
NULL);
but_progress->progress = progress;
- UI_but_func_tooltip_set(&but_progress->but, progress_tooltip_func, tip_arg);
+ UI_but_func_tooltip_set(&but_progress->but, progress_tooltip_func, tip_arg, MEM_freeN);
}
if (!wm->is_interface_locked) {
@@ -6931,8 +6142,8 @@ void uiTemplateInputStatus(uiLayout *layout, struct bContext *C)
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);
+ const char *msg = TIP_(WM_window_cursor_keymap_status_get(win, i, 0));
+ const char *msg_drag = TIP_(WM_window_cursor_keymap_status_get(win, i, 1));
if (msg || (msg_drag == NULL)) {
uiItemL(row, msg ? msg : "", (ICON_MOUSE_LMB + i));
@@ -7276,6 +6487,44 @@ void uiTemplateCacheFile(uiLayout *layout,
row = uiLayoutRow(layout, false);
uiItemR(row, &fileptr, "is_sequence", 0, NULL, ICON_NONE);
+ /* Only enable render procedural option if the active engine supports it. */
+ const struct RenderEngineType *engine_type = CTX_data_engine_type(C);
+
+ Scene *scene = CTX_data_scene(C);
+ const bool engine_supports_procedural = RE_engine_supports_alembic_procedural(engine_type,
+ scene);
+
+ if (!engine_supports_procedural) {
+ row = uiLayoutRow(layout, false);
+ /* For Cycles, verify that experimental features are enabled. */
+ if (BKE_scene_uses_cycles(scene) && !BKE_scene_uses_cycles_experimental_features(scene)) {
+ uiItemL(
+ row,
+ TIP_(
+ "The Cycles Alembic Procedural is only available with the experimental feature set"),
+ ICON_INFO);
+ }
+ else {
+ uiItemL(
+ row, TIP_("The active render engine does not have an Alembic Procedural"), ICON_INFO);
+ }
+ }
+
+ row = uiLayoutRow(layout, false);
+ uiLayoutSetActive(row, engine_supports_procedural);
+ uiItemR(row, &fileptr, "use_render_procedural", 0, NULL, ICON_NONE);
+
+ const bool use_render_procedural = RNA_boolean_get(&fileptr, "use_render_procedural");
+ const bool use_prefetch = RNA_boolean_get(&fileptr, "use_prefetch");
+
+ row = uiLayoutRow(layout, false);
+ uiLayoutSetEnabled(row, use_render_procedural);
+ uiItemR(row, &fileptr, "use_prefetch", 0, NULL, ICON_NONE);
+
+ sub = uiLayoutRow(layout, false);
+ uiLayoutSetEnabled(sub, use_prefetch && use_render_procedural);
+ uiItemR(sub, &fileptr, "prefetch_cache_size", 0, NULL, ICON_NONE);
+
row = uiLayoutRowWithHeading(layout, true, IFACE_("Override Frame"));
sub = uiLayoutRow(row, true);
uiLayoutSetPropDecorate(sub, false);
diff --git a/source/blender/editors/interface/interface_utils.c b/source/blender/editors/interface/interface_utils.c
index 6ad1de68a1f..1a41dc8e9fb 100644
--- a/source/blender/editors/interface/interface_utils.c
+++ b/source/blender/editors/interface/interface_utils.c
@@ -29,6 +29,8 @@
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
+#include "ED_screen.h"
+
#include "BLI_alloca.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
@@ -38,6 +40,7 @@
#include "BLT_translation.h"
+#include "BKE_context.h"
#include "BKE_lib_id.h"
#include "BKE_report.h"
@@ -48,6 +51,7 @@
#include "UI_interface.h"
#include "UI_interface_icons.h"
#include "UI_resources.h"
+#include "UI_view2d.h"
#include "WM_api.h"
#include "WM_types.h"
@@ -531,7 +535,7 @@ void ui_rna_collection_search_update_fn(const struct bContext *C,
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);
+ has_sep_char = ID_IS_LINKED(id);
}
}
else {
@@ -701,7 +705,7 @@ int UI_calc_float_precision(int prec, double value)
/* 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.
+ * _but_, this is only for small values as 10.0001 will not get the same treatment.
*/
value = fabs(value);
if ((value < pow10_neg[prec]) && (value > (1.0 / max_pow))) {
@@ -774,6 +778,98 @@ bool UI_but_online_manual_id_from_active(const struct bContext *C, char *r_str,
}
/* -------------------------------------------------------------------- */
+
+static rctf ui_but_rect_to_view(const uiBut *but, const ARegion *region, const View2D *v2d)
+{
+ rctf region_rect;
+ ui_block_to_region_rctf(region, but->block, &region_rect, &but->rect);
+
+ rctf view_rect;
+ UI_view2d_region_to_view_rctf(v2d, &region_rect, &view_rect);
+
+ return view_rect;
+}
+
+/**
+ * To get a margin (typically wanted), add the margin to \a rect directly.
+ *
+ * Based on #file_ensure_inside_viewbounds(), could probably share code.
+ *
+ * \return true if anything changed.
+ */
+static bool ui_view2d_cur_ensure_rect_in_view(View2D *v2d, const rctf *rect)
+{
+ const float rect_width = BLI_rctf_size_x(rect);
+ const float rect_height = BLI_rctf_size_y(rect);
+
+ rctf *cur = &v2d->cur;
+ const float cur_width = BLI_rctf_size_x(cur);
+ const float cur_height = BLI_rctf_size_y(cur);
+
+ bool changed = false;
+
+ /* Snap to bottom edge. Also use if rect is higher than view bounds (could be a parameter). */
+ if ((cur->ymin > rect->ymin) || (rect_height > cur_height)) {
+ cur->ymin = rect->ymin;
+ cur->ymax = cur->ymin + cur_height;
+ changed = true;
+ }
+ /* Snap to upper edge. */
+ else if (cur->ymax < rect->ymax) {
+ cur->ymax = rect->ymax;
+ cur->ymin = cur->ymax - cur_height;
+ changed = true;
+ }
+ /* Snap to left edge. Also use if rect is wider than view bounds. */
+ else if ((cur->xmin > rect->xmin) || (rect_width > cur_width)) {
+ cur->xmin = rect->xmin;
+ cur->xmax = cur->xmin + cur_width;
+ changed = true;
+ }
+ /* Snap to right edge. */
+ else if (cur->xmax < rect->xmax) {
+ cur->xmax = rect->xmax;
+ cur->xmin = cur->xmax - cur_width;
+ changed = true;
+ }
+ else {
+ BLI_assert(BLI_rctf_inside_rctf(cur, rect));
+ }
+
+ return changed;
+}
+
+/**
+ * Adjust the view so the rectangle of \a but is in view, with some extra margin.
+ *
+ * It's important that this is only executed after buttons received their final #uiBut.rect. E.g.
+ * #UI_panels_end() modifies them, so if that is executed, this function must not be called before
+ * it.
+ *
+ * \param region: The region the button is placed in. Make sure this is actually the one the button
+ * is placed in, not just the context region.
+ */
+void UI_but_ensure_in_view(const bContext *C, ARegion *region, const uiBut *but)
+{
+ View2D *v2d = &region->v2d;
+ /* Uninitialized view or region that doesn't use View2D. */
+ if ((v2d->flag & V2D_IS_INIT) == 0) {
+ return;
+ }
+
+ rctf rect = ui_but_rect_to_view(but, region, v2d);
+
+ const int margin = UI_UNIT_X * 0.5f;
+ BLI_rctf_pad(&rect, margin, margin);
+
+ const bool changed = ui_view2d_cur_ensure_rect_in_view(v2d, &rect);
+ if (changed) {
+ UI_view2d_curRect_changed(C, v2d);
+ ED_region_tag_redraw_no_rebuild(region);
+ }
+}
+
+/* -------------------------------------------------------------------- */
/** \name Button Store
*
* Modal Button Store API.
@@ -932,7 +1028,7 @@ void UI_butstore_update(uiBlock *block)
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
+ * 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_view.cc b/source/blender/editors/interface/interface_view.cc
new file mode 100644
index 00000000000..4e38f245155
--- /dev/null
+++ b/source/blender/editors/interface/interface_view.cc
@@ -0,0 +1,186 @@
+/*
+ * 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
+ *
+ * This part of the UI-View API is mostly needed to support persistent state of items within the
+ * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we
+ * can compare the old view items with the new view items and keep state persistent for matching
+ * ones.
+ */
+
+#include <memory>
+#include <variant>
+
+#include "DNA_screen_types.h"
+
+#include "BLI_listbase.h"
+
+#include "interface_intern.h"
+
+#include "UI_interface.hh"
+#include "UI_tree_view.hh"
+
+using namespace blender;
+using namespace blender::ui;
+
+/**
+ * Wrapper to store views in a #ListBase. There's no `uiView` base class, we just store views as a
+ * #std::variant.
+ */
+struct ViewLink : public Link {
+ using TreeViewPtr = std::unique_ptr<AbstractTreeView>;
+
+ std::string idname;
+ /* Note: Can't use std::get() on this until minimum macOS deployment target is 10.14. */
+ std::variant<TreeViewPtr> view;
+};
+
+template<class T> T *get_view_from_link(ViewLink &link)
+{
+ auto *t_uptr = std::get_if<std::unique_ptr<T>>(&link.view);
+ return t_uptr ? t_uptr->get() : nullptr;
+}
+
+/**
+ * Override this for all available tree types.
+ */
+AbstractTreeView *UI_block_add_view(uiBlock &block,
+ StringRef idname,
+ std::unique_ptr<AbstractTreeView> tree_view)
+{
+ ViewLink *view_link = OBJECT_GUARDED_NEW(ViewLink);
+ BLI_addtail(&block.views, view_link);
+
+ view_link->view = std::move(tree_view);
+ view_link->idname = idname;
+
+ return get_view_from_link<AbstractTreeView>(*view_link);
+}
+
+void ui_block_free_views(uiBlock *block)
+{
+ LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) {
+ OBJECT_GUARDED_DELETE(link, ViewLink);
+ }
+}
+
+/**
+ * \param x, y: Coordinate to find a tree-row item at, in window space.
+ */
+uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region, const int xy[2])
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, xy);
+ if (!tree_row_but) {
+ return nullptr;
+ }
+
+ return tree_row_but->tree_item;
+}
+
+uiTreeViewItemHandle *UI_block_tree_view_find_active_item(const ARegion *region)
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_active(region);
+ if (!tree_row_but) {
+ return nullptr;
+ }
+
+ return tree_row_but->tree_item;
+}
+
+static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view)
+{
+ /* First get the idname the of the view we're looking for. */
+ LISTBASE_FOREACH (ViewLink *, view_link, &block.views) {
+ if (get_view_from_link<AbstractTreeView>(*view_link) == &view) {
+ return view_link->idname;
+ }
+ }
+
+ return {};
+}
+
+static AbstractTreeView *ui_block_view_find_matching_in_old_block(const uiBlock &new_block,
+ const AbstractTreeView &new_view)
+{
+ uiBlock *old_block = new_block.oldblock;
+ if (!old_block) {
+ return nullptr;
+ }
+
+ StringRef idname = ui_block_view_find_idname(new_block, new_view);
+ if (idname.is_empty()) {
+ return nullptr;
+ }
+
+ LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) {
+ if (old_view_link->idname == idname) {
+ return get_view_from_link<AbstractTreeView>(*old_view_link);
+ }
+ }
+
+ return nullptr;
+}
+
+uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+ const uiTreeViewHandle *new_view_handle)
+{
+ BLI_assert(new_block && new_view_handle);
+ const AbstractTreeView &new_view = reinterpret_cast<const AbstractTreeView &>(*new_view_handle);
+
+ AbstractTreeView *old_view = ui_block_view_find_matching_in_old_block(*new_block, new_view);
+ return reinterpret_cast<uiTreeViewHandle *>(old_view);
+}
+
+uiButTreeRow *ui_block_view_find_treerow_in_old_block(const uiBlock *new_block,
+ const uiTreeViewItemHandle *new_item_handle)
+{
+ uiBlock *old_block = new_block->oldblock;
+ if (!old_block) {
+ return nullptr;
+ }
+
+ const AbstractTreeViewItem &new_item = *reinterpret_cast<const AbstractTreeViewItem *>(
+ new_item_handle);
+ const AbstractTreeView *old_tree_view = ui_block_view_find_matching_in_old_block(
+ *new_block, new_item.get_tree_view());
+ if (!old_tree_view) {
+ return nullptr;
+ }
+
+ LISTBASE_FOREACH (uiBut *, old_but, &old_block->buttons) {
+ if (old_but->type != UI_BTYPE_TREEROW) {
+ continue;
+ }
+ uiButTreeRow *old_treerow_but = (uiButTreeRow *)old_but;
+ if (!old_treerow_but->tree_item) {
+ continue;
+ }
+ AbstractTreeViewItem &old_item = *reinterpret_cast<AbstractTreeViewItem *>(
+ old_treerow_but->tree_item);
+ /* Check if the row is from the expected tree-view. */
+ if (&old_item.get_tree_view() != old_tree_view) {
+ continue;
+ }
+
+ if (UI_tree_view_item_matches(new_item_handle, old_treerow_but->tree_item)) {
+ return old_treerow_but;
+ }
+ }
+
+ return nullptr;
+}
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index 91a4c430023..7d1b7b80ccd 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -105,6 +105,7 @@ typedef enum {
/* specials */
UI_WTYPE_ICON,
UI_WTYPE_ICON_LABEL,
+ UI_WTYPE_PREVIEW_TILE,
UI_WTYPE_SWATCH,
UI_WTYPE_RGB_PICKER,
UI_WTYPE_UNITVEC,
@@ -114,6 +115,7 @@ typedef enum {
UI_WTYPE_PROGRESSBAR,
UI_WTYPE_NODESOCKET,
UI_WTYPE_DATASETROW,
+ UI_WTYPE_TREEROW,
} uiWidgetTypeEnum;
/* Button state argument shares bits with 'uiBut.flag'.
@@ -237,7 +239,7 @@ typedef struct uiWidgetTrias {
#define WIDGET_SIZE_MAX (WIDGET_CURVE_RESOLU * 4)
typedef struct uiWidgetBase {
- /* TODO remove these completely */
+ /* TODO: remove these completely. */
int totvert, halfwayvert;
float outer_v[WIDGET_SIZE_MAX][2];
float inner_v[WIDGET_SIZE_MAX][2];
@@ -399,7 +401,7 @@ static struct {
GPUBatch *roundbox_widget;
GPUBatch *roundbox_shadow;
- /* TODO remove */
+ /* TODO: remove. */
GPUVertFormat format;
uint vflag_id;
} g_ui_batch_cache = {0};
@@ -516,14 +518,14 @@ GPUBatch *ui_batch_roundbox_shadow_get(void)
/** \name Draw Triangle Arrow
* \{ */
-void UI_draw_anti_tria(
+static void draw_anti_tria(
float x1, float y1, float x2, float y2, float x3, float y3, const float color[4])
{
const float tri_arr[3][2] = {{x1, y1}, {x2, y2}, {x3, y3}};
float draw_color[4];
copy_v4_v4(draw_color, color);
- /* Note: This won't give back the original color. */
+ /* NOTE: This won't give back the original color. */
draw_color[3] *= 1.0f / WIDGET_AA_JITTER;
GPU_blend(GPU_BLEND_ALPHA);
@@ -557,66 +559,31 @@ void UI_draw_icon_tri(float x, float y, char dir, const float color[4])
const float f7 = 0.25 * U.widget_unit;
if (dir == 'h') {
- UI_draw_anti_tria(x - f3, y - f5, x - f3, y + f5, x + f7, y, color);
+ draw_anti_tria(x - f3, y - f5, x - f3, y + f5, x + f7, y, color);
}
else if (dir == 't') {
- UI_draw_anti_tria(x - f5, y - f7, x + f5, y - f7, x, y + f3, color);
+ draw_anti_tria(x - f5, y - f7, x + f5, y - f7, x, y + f3, color);
}
else { /* 'v' = vertical, down. */
- UI_draw_anti_tria(x - f5, y + f3, x + f5, y + f3, x, y - f7, color);
+ draw_anti_tria(x - f5, y + f3, x + f5, y + f3, x, y - f7, color);
}
}
/* triangle 'icon' inside rect */
-void ui_draw_anti_tria_rect(const rctf *rect, char dir, const float color[4])
+static void draw_anti_tria_rect(const rctf *rect, char dir, const float color[4])
{
if (dir == 'h') {
const float half = 0.5f * BLI_rctf_size_y(rect);
- UI_draw_anti_tria(
+ draw_anti_tria(
rect->xmin, rect->ymin, rect->xmin, rect->ymax, rect->xmax, rect->ymin + half, color);
}
else {
const float half = 0.5f * BLI_rctf_size_x(rect);
- UI_draw_anti_tria(
+ draw_anti_tria(
rect->xmin, rect->ymax, rect->xmax, rect->ymax, rect->xmin + half, rect->ymin, color);
}
}
-void UI_draw_anti_fan(float tri_array[][2], uint length, const float color[4])
-{
- float draw_color[4];
-
- copy_v4_v4(draw_color, color);
- draw_color[3] *= 2.0f / WIDGET_AA_JITTER;
-
- GPU_blend(GPU_BLEND_ALPHA);
-
- const uint pos = GPU_vertformat_attr_add(
- immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
-
- immUniformColor4fv(draw_color);
-
- /* for each AA step */
- for (int j = 0; j < WIDGET_AA_JITTER; j++) {
- immBegin(GPU_PRIM_TRI_FAN, length);
- immVertex2f(pos, tri_array[0][0], tri_array[0][1]);
- immVertex2f(pos, tri_array[1][0], tri_array[1][1]);
-
- /* We jitter only the middle of the fan, the extremes are pinned. */
- for (int i = 2; i < length - 1; i++) {
- immVertex2f(pos, tri_array[i][0] + jit[j][0], tri_array[i][1] + jit[j][1]);
- }
-
- immVertex2f(pos, tri_array[length - 1][0], tri_array[length - 1][1]);
- immEnd();
- }
-
- immUnbindProgram();
-
- GPU_blend(GPU_BLEND_NONE);
-}
-
static void widget_init(uiWidgetBase *wtb)
{
wtb->totvert = wtb->halfwayvert = 0;
@@ -768,7 +735,7 @@ static void round_box__edges(
BLI_rctf_rcti_copy(&wt->uniform_params.rect, rect);
BLI_rctf_init(&wt->uniform_params.recti, minxi, maxxi, minyi, maxyi);
- /* mult */
+ /* Multiply by radius. */
for (int a = 0; a < WIDGET_CURVE_RESOLU; a++) {
veci[a][0] = radi * cornervec[a][0];
veci[a][1] = radi * cornervec[a][1];
@@ -1377,8 +1344,6 @@ static int ui_but_draw_menu_icon(const uiBut *but)
static void widget_draw_icon(
const uiBut *but, BIFIconID icon, float alpha, const rcti *rect, const uchar mono_color[4])
{
- float xs = 0.0f, ys = 0.0f;
-
if (but->flag & UI_BUT_ICON_PREVIEW) {
GPU_blend(GPU_BLEND_ALPHA);
widget_draw_preview(icon, alpha, rect);
@@ -1420,6 +1385,7 @@ static void widget_draw_icon(
if (icon && icon != ICON_BLANK1) {
const float ofs = 1.0f / aspect;
+ float xs, ys;
if (but->drawflag & UI_BUT_ICON_LEFT) {
/* special case - icon_only pie buttons */
@@ -1441,8 +1407,8 @@ static void widget_draw_icon(
/* force positions to integers, for zoom levels near 1. draws icons crisp. */
if (aspect > 0.95f && aspect < 1.05f) {
- xs = (int)(xs + 0.1f);
- ys = (int)(ys + 0.1f);
+ xs = roundf(xs);
+ ys = roundf(ys);
}
/* Get theme color. */
@@ -1493,20 +1459,20 @@ static void widget_draw_submenu_tria(const uiBut *but,
GPU_blend(GPU_BLEND_ALPHA);
UI_widgetbase_draw_cache_flush();
GPU_blend(GPU_BLEND_NONE);
- ui_draw_anti_tria_rect(&tria_rect, 'h', col);
+ draw_anti_tria_rect(&tria_rect, 'h', col);
}
static void ui_text_clip_give_prev_off(uiBut *but, const char *str)
{
- const char *prev_utf8 = BLI_str_find_prev_char_utf8(str, str + but->ofs);
+ const char *prev_utf8 = BLI_str_find_prev_char_utf8(str + but->ofs, str);
const int bytes = str + but->ofs - prev_utf8;
but->ofs -= bytes;
}
-static void ui_text_clip_give_next_off(uiBut *but, const char *str)
+static void ui_text_clip_give_next_off(uiBut *but, const char *str, const char *str_end)
{
- const char *next_utf8 = BLI_str_find_next_char_utf8(str + but->ofs, NULL);
+ const char *next_utf8 = BLI_str_find_next_char_utf8(str + but->ofs, str_end);
const int bytes = next_utf8 - (str + but->ofs);
but->ofs += bytes;
@@ -1576,11 +1542,6 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle,
/* need to set this first */
UI_fontstyle_set(fstyle);
- if (fstyle->kerning == 1) {
- /* for BLF_width */
- BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
float strwidth = BLF_width(fstyle->uifont_id, str, max_len);
if ((okwidth > 0.0f) && (strwidth > okwidth)) {
@@ -1641,7 +1602,7 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle,
/* Corner case, the str already takes all available mem,
* and the ellipsis chars would actually add more chars.
* Better to just trim one or two letters to the right in this case...
- * Note: with a single-char ellipsis, this should never happen! But better be safe
+ * NOTE: with a single-char ellipsis, this should never happen! But better be safe
* here...
*/
ui_text_clip_right_ex(
@@ -1674,10 +1635,6 @@ float UI_text_clip_middle_ex(const uiFontStyle *fstyle,
strwidth = BLF_width(fstyle->uifont_id, str, max_len);
}
- if (fstyle->kerning == 1) {
- BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
BLI_assert(strwidth <= okwidth);
return strwidth;
@@ -1736,11 +1693,6 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct
/* need to set this first */
UI_fontstyle_set(fstyle);
- if (fstyle->kerning == 1) {
- /* for BLF_width */
- BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
/* define ofs dynamically */
if (but->ofs > but->pos) {
but->ofs = but->pos;
@@ -1753,7 +1705,8 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct
but->strwidth = BLF_width(fstyle->uifont_id, but->editstr + but->ofs, INT_MAX);
if (but->strwidth > okwidth) {
- int len = strlen(but->editstr);
+ const int editstr_len = strlen(but->editstr);
+ int len = editstr_len;
while (but->strwidth > okwidth) {
float width;
@@ -1763,7 +1716,7 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct
/* if cursor is at 20 pixels of right side button we clip left */
if (width > okwidth - 20) {
- ui_text_clip_give_next_off(but, but->editstr);
+ ui_text_clip_give_next_off(but, but->editstr, but->editstr + editstr_len);
}
else {
int bytes;
@@ -1771,7 +1724,7 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct
if (width < 20 && but->ofs > 0) {
ui_text_clip_give_prev_off(but, but->editstr);
}
- bytes = BLI_str_utf8_size(BLI_str_find_prev_char_utf8(but->editstr, but->editstr + len));
+ bytes = BLI_str_utf8_size(BLI_str_find_prev_char_utf8(but->editstr + len, but->editstr));
if (bytes == -1) {
bytes = 1;
}
@@ -1785,10 +1738,6 @@ static void ui_text_clip_cursor(const uiFontStyle *fstyle, uiBut *but, const rct
}
}
}
-
- if (fstyle->kerning == 1) {
- BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
}
/**
@@ -1806,11 +1755,6 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons
/* need to set this first */
UI_fontstyle_set(fstyle);
- if (fstyle->kerning == 1) {
- /* for BLF_width */
- BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
but->strwidth = BLF_width(fstyle->uifont_id, but->drawstr, sizeof(but->drawstr));
but->ofs = 0;
@@ -1828,7 +1772,7 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons
/* chop off the leading text, starting from the right */
while (but->strwidth > okwidth && cp2 > but->drawstr) {
- const char *prev_utf8 = BLI_str_find_prev_char_utf8(but->drawstr, cp2);
+ const char *prev_utf8 = BLI_str_find_prev_char_utf8(cp2, but->drawstr);
const int bytes = cp2 - prev_utf8;
/* shift the text after and including cp2 back by 1 char,
@@ -1848,7 +1792,7 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons
/* after the leading text is gone, chop off the : and following space, with ofs */
while ((but->strwidth > okwidth) && (but->ofs < 2)) {
- ui_text_clip_give_next_off(but, but->drawstr);
+ ui_text_clip_give_next_off(but, but->drawstr, but->drawstr + drawstr_len);
but->strwidth = BLF_width(
fstyle->uifont_id, but->drawstr + but->ofs, sizeof(but->drawstr) - but->ofs);
if (but->strwidth < 10) {
@@ -1870,10 +1814,6 @@ static void ui_text_clip_right_label(const uiFontStyle *fstyle, uiBut *but, cons
but->strwidth = strwidth;
but->drawstr[drawstr_len] = 0;
}
-
- if (fstyle->kerning == 1) {
- BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
}
#ifdef WITH_INPUT_IME
@@ -1985,11 +1925,6 @@ static void widget_draw_text(const uiFontStyle *fstyle,
align = UI_STYLE_TEXT_CENTER;
}
- if (fstyle->kerning == 1) {
- /* for BLF_width */
- BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
/* Special case: when we're entering text for multiple buttons,
* don't draw the text for any of the multi-editing buttons */
if (UNLIKELY(but->flag & UI_BUT_DRAG_MULTI)) {
@@ -2006,14 +1941,15 @@ static void widget_draw_text(const uiFontStyle *fstyle,
drawstr_left_len = INT_MAX;
#ifdef WITH_INPUT_IME
- /* FIXME, IME is modifying 'const char *drawstr! */
+ /* FIXME: IME is modifying `const char *drawstr`! */
ime_data = ui_but_ime_data_get(but);
if (ime_data && ime_data->composite_len) {
/* insert composite string into cursor pos */
BLI_snprintf((char *)drawstr,
UI_MAX_DRAW_STR,
- "%s%s%s",
+ "%.*s%s%s",
+ but->pos,
but->editstr,
ime_data->str_composite,
but->editstr + but->pos);
@@ -2029,8 +1965,11 @@ static void widget_draw_text(const uiFontStyle *fstyle,
/* text button selection, cursor, composite underline */
if (but->editstr && but->pos != -1) {
int but_pos_ofs;
- /* Shape of the cursor for drawing. */
- rcti but_cursor_shape;
+
+#ifdef WITH_INPUT_IME
+ bool ime_reposition_window = false;
+ int ime_win_x, ime_win_y;
+#endif
/* text button selection */
if ((but->selend - but->selsta) > 0) {
@@ -2055,14 +1994,28 @@ static void widget_draw_text(const uiFontStyle *fstyle,
immVertexFormat(), "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ rcti selection_shape;
+ selection_shape.xmin = rect->xmin + selsta_draw;
+ selection_shape.xmax = min_ii(rect->xmin + selwidth_draw, rect->xmax - 2);
+ selection_shape.ymin = rect->ymin + U.pixelsize;
+ selection_shape.ymax = rect->ymax - U.pixelsize;
immUniformColor4ubv(wcol->item);
immRecti(pos,
- rect->xmin + selsta_draw,
- rect->ymin + U.pixelsize,
- min_ii(rect->xmin + selwidth_draw, rect->xmax - 2),
- rect->ymax - U.pixelsize);
+ selection_shape.xmin,
+ selection_shape.ymin,
+ selection_shape.xmax,
+ selection_shape.ymax);
immUnbindProgram();
+
+#ifdef WITH_INPUT_IME
+ /* IME candidate window uses selection position. */
+ if (!ime_reposition_window) {
+ ime_reposition_window = true;
+ ime_win_x = selection_shape.xmin;
+ ime_win_y = selection_shape.ymin;
+ }
+#endif
}
}
@@ -2070,7 +2023,7 @@ static void widget_draw_text(const uiFontStyle *fstyle,
but_pos_ofs = but->pos;
#ifdef WITH_INPUT_IME
- /* if is ime compositing, move the cursor */
+ /* If is IME compositing, move the cursor. */
if (ime_data && ime_data->composite_len && ime_data->cursor_pos != -1) {
but_pos_ofs += ime_data->cursor_pos;
}
@@ -2095,6 +2048,8 @@ static void widget_draw_text(const uiFontStyle *fstyle,
immUniformThemeColor(TH_WIDGET_TEXT_CURSOR);
+ /* Shape of the cursor for drawing. */
+ rcti but_cursor_shape;
but_cursor_shape.xmin = (rect->xmin + t) - U.pixelsize;
but_cursor_shape.ymin = rect->ymin + U.pixelsize;
but_cursor_shape.xmax = (rect->xmin + t) + U.pixelsize;
@@ -2108,25 +2063,29 @@ static void widget_draw_text(const uiFontStyle *fstyle,
but_cursor_shape.ymax);
immUnbindProgram();
- }
#ifdef WITH_INPUT_IME
- if (ime_data && ime_data->composite_len) {
- /* ime cursor following */
- if (but->pos >= but->ofs) {
- ui_but_ime_reposition(but, but_cursor_shape.xmax + 5, but_cursor_shape.ymin + 3, false);
+ /* IME candidate window uses cursor position. */
+ if (!ime_reposition_window) {
+ ime_reposition_window = true;
+ ime_win_x = but_cursor_shape.xmax + 5;
+ ime_win_y = but_cursor_shape.ymin + 3;
}
+#endif
+ }
- /* composite underline */
+#ifdef WITH_INPUT_IME
+ /* IME cursor following. */
+ if (ime_reposition_window) {
+ ui_but_ime_reposition(but, ime_win_x, ime_win_y, false);
+ }
+ if (ime_data && ime_data->composite_len) {
+ /* Composite underline. */
widget_draw_text_ime_underline(fstyle, wcol, but, rect, ime_data, drawstr);
}
#endif
}
- if (fstyle->kerning == 1) {
- BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
#if 0
ui_rasterpos_safe(x, y, but->aspect);
transopts = ui_translate_buttons();
@@ -2204,10 +2163,6 @@ static void widget_draw_text(const uiFontStyle *fstyle,
}
if (ul_index != -1) {
- if (fstyle->kerning == 1) {
- BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
int ul_width = round_fl_to_int(BLF_width(fstyle->uifont_id, "_", 2));
struct UnderlineData ul_data = {
@@ -2228,10 +2183,6 @@ static void widget_draw_text(const uiFontStyle *fstyle,
BLF_position(fstyle->uifont_id, pos_x, pos_y, 0.0f);
BLF_color4ubv(fstyle->uifont_id, wcol->text);
BLF_draw(fstyle->uifont_id, "_", 2);
-
- if (fstyle->kerning == 1) {
- BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
}
}
}
@@ -2264,7 +2215,7 @@ static void widget_draw_extra_icons(const uiWidgetColors *wcol,
const float icon_size = ICON_SIZE_FROM_BUTRECT(rect);
/* Offset of icons from the right edge. Keep in sync
- with 'ui_but_extra_operator_icon_mouse_over_get'. */
+ * with 'ui_but_extra_operator_icon_mouse_over_get'. */
if (!BLI_listbase_is_empty(&but->extra_op_icons)) {
/* Eyeballed. */
rect->xmax -= 0.2 * icon_size;
@@ -2277,7 +2228,10 @@ static void widget_draw_extra_icons(const uiWidgetColors *wcol,
temp.xmin = temp.xmax - icon_size;
- if (!op_icon->highlighted) {
+ if (op_icon->disabled) {
+ alpha_this *= 0.4f;
+ }
+ else if (!op_icon->highlighted) {
alpha_this *= 0.75f;
}
@@ -2453,8 +2407,8 @@ static void widget_draw_text_icon(const uiFontStyle *fstyle,
}
else {
/* In case a separate text label and some other button are placed under each other,
- and the outline of the button does not contrast with the background.
- Add an offset (thickness of the outline) so that the text does not stick out visually. */
+ * and the outline of the button does not contrast with the background.
+ * Add an offset (thickness of the outline) so that the text does not stick out visually. */
if (but->drawflag & UI_BUT_TEXT_LEFT) {
rect->xmin += U.pixelsize;
}
@@ -2493,7 +2447,7 @@ static void widget_draw_text_icon(const uiFontStyle *fstyle,
ui_text_clip_middle(fstyle, but, rect);
}
- /* always draw text for textbutton cursor */
+ /* Always draw text for text-button cursor. */
widget_draw_text(fstyle, wcol, but, rect);
ui_but_text_password_hide(password_str, but, true);
@@ -2572,7 +2526,7 @@ static void widget_state(uiWidgetType *wt, int state, int drawflag, eUIEmbossTyp
{
uiWidgetStateColors *wcol_state = wt->wcol_state;
- if ((state & UI_BUT_LIST_ITEM) && !(state & UI_STATE_TEXT_INPUT)) {
+ if (state & UI_BUT_LIST_ITEM) {
/* Override default widget's colors. */
bTheme *btheme = UI_GetTheme();
wt->wcol_theme = &btheme->tui.wcol_list_item;
@@ -3088,7 +3042,7 @@ void ui_draw_gradient(const rcti *rect,
copy_v3_v3(col1[3], col1[2]);
break;
default:
- BLI_assert(!"invalid 'type' argument");
+ BLI_assert_msg(0, "invalid 'type' argument");
hsv_to_rgb(1.0, 1.0, 1.0, &col1[2][0], &col1[2][1], &col1[2][2]);
copy_v3_v3(col1[0], col1[2]);
copy_v3_v3(col1[1], col1[2]);
@@ -3489,7 +3443,7 @@ static void widget_menubut(uiWidgetColors *wcol, rcti *rect, int UNUSED(state),
/**
* Draw menu buttons still with triangles when field is not embossed
*/
-static void widget_menubut_embossn(uiBut *UNUSED(but),
+static void widget_menubut_embossn(const uiBut *UNUSED(but),
uiWidgetColors *wcol,
rcti *rect,
int UNUSED(state),
@@ -3512,7 +3466,7 @@ static void widget_menubut_embossn(uiBut *UNUSED(but),
* Draw number buttons still with triangles when field is not embossed
*/
static void widget_numbut_embossn(
- uiBut *UNUSED(but), uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
+ const uiBut *UNUSED(but), uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
{
widget_numbut_draw(wcol, rect, state, roundboxalign, true);
}
@@ -3692,16 +3646,11 @@ static void widget_progressbar(
/* "slider" bar color */
copy_v3_v3_uchar(wcol->inner, wcol->item);
widgetbase_draw(&wtb_bar, wcol);
-
- /* raise text a bit */
- rect->xmin += (BLI_rcti_size_x(&rect_prog) / 2);
- rect->xmax += (BLI_rcti_size_x(&rect_prog) / 2);
}
-static void widget_datasetrow(
- uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign))
+static void widget_treerow_exec(
+ uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign), int indentation)
{
- uiButDatasetRow *but_componentrow = (uiButDatasetRow *)but;
uiWidgetBase wtb;
widget_init(&wtb);
@@ -3714,16 +3663,30 @@ static void widget_datasetrow(
widgetbase_draw(&wtb, wcol);
}
- BLI_rcti_resize(rect,
- BLI_rcti_size_x(rect) - UI_UNIT_X * but_componentrow->indentation,
- BLI_rcti_size_y(rect));
- BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * but_componentrow->indentation, 0);
+ BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect));
+ BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0);
+}
+
+static void widget_treerow(
+ uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
+{
+ uiButTreeRow *tree_row = (uiButTreeRow *)but;
+ BLI_assert(but->type == UI_BTYPE_TREEROW);
+ widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation);
+}
+
+static void widget_datasetrow(
+ uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
+{
+ uiButDatasetRow *dataset_row = (uiButDatasetRow *)but;
+ BLI_assert(but->type == UI_BTYPE_DATASETROW);
+ widget_treerow_exec(wcol, rect, state, roundboxalign, dataset_row->indentation);
}
static void widget_nodesocket(
uiBut *but, uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign))
{
- const int radi = 5;
+ const int radi = 0.25f * BLI_rcti_size_y(rect);
uiWidgetBase wtb;
widget_init(&wtb);
@@ -3995,6 +3958,14 @@ static void widget_textbut(uiWidgetColors *wcol, rcti *rect, int state, int roun
widgetbase_draw(&wtb, wcol);
}
+static void widget_preview_tile(
+ uiBut *but, uiWidgetColors *wcol, rcti *rect, int UNUSED(state), int UNUSED(roundboxalign))
+{
+ const uiStyle *style = UI_style_get();
+ ui_draw_preview_item_stateless(
+ &style->widget, rect, but->drawstr, but->icon, wcol->text, UI_STYLE_TEXT_CENTER);
+}
+
static void widget_menuiconbut(uiWidgetColors *wcol,
rcti *rect,
int UNUSED(state),
@@ -4046,9 +4017,15 @@ static void widget_menu_itembut(uiWidgetColors *wcol,
uiWidgetBase wtb;
widget_init(&wtb);
- /* not rounded, no outline */
+ /* Padding on the sides. */
+ const float padding = 0.125f * BLI_rcti_size_y(rect);
+ rect->xmin += padding;
+ rect->xmax -= padding;
+
+ /* No outline. */
wtb.draw_outline = false;
- round_box_edges(&wtb, 0, rect, 0.0f);
+ const float rad = wcol->roundness * BLI_rcti_size_y(rect);
+ round_box_edges(&wtb, UI_CNR_ALL, rect, rad);
widgetbase_draw(&wtb, wcol);
}
@@ -4306,7 +4283,7 @@ static void widget_draw_extra_mask(const bContext *C, uiBut *but, uiWidgetType *
widget_init(&wtb);
if (but->block->drawextra) {
- /* note: drawextra can change rect +1 or -1, to match round errors of existing previews */
+ /* NOTE: drawextra can change rect +1 or -1, to match round errors of existing previews. */
but->block->drawextra(
C, but->poin, but->block->drawextra_arg1, but->block->drawextra_arg2, rect);
@@ -4460,6 +4437,13 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type)
wt.custom = widget_icon_has_anim;
break;
+ case UI_WTYPE_PREVIEW_TILE:
+ wt.draw = NULL;
+ /* Drawn via the `custom` callback. */
+ wt.text = NULL;
+ wt.custom = widget_preview_tile;
+ break;
+
case UI_WTYPE_SWATCH:
wt.custom = widget_swatch;
break;
@@ -4496,6 +4480,10 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type)
wt.custom = widget_datasetrow;
break;
+ case UI_WTYPE_TREEROW:
+ wt.custom = widget_treerow;
+ break;
+
case UI_WTYPE_NODESOCKET:
wt.custom = widget_nodesocket;
break;
@@ -4609,6 +4597,9 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
switch (but->type) {
case UI_BTYPE_LABEL:
wt = widget_type(UI_WTYPE_ICON_LABEL);
+ if (!(but->flag & UI_HAS_ICON)) {
+ but->drawflag |= UI_BUT_NO_TEXT_PADDING;
+ }
break;
default:
wt = widget_type(UI_WTYPE_ICON);
@@ -4755,6 +4746,10 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
wt = widget_type(UI_WTYPE_BOX);
break;
+ case UI_BTYPE_PREVIEW_TILE:
+ wt = widget_type(UI_WTYPE_PREVIEW_TILE);
+ break;
+
case UI_BTYPE_EXTRA:
widget_draw_extra_mask(C, but, widget_type(UI_WTYPE_BOX), rect);
break;
@@ -4804,9 +4799,6 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
break;
case UI_BTYPE_CURVE:
- /* do not draw right to edge of rect */
- rect->xmin += (0.2f * UI_UNIT_X);
- rect->xmax -= (0.2f * UI_UNIT_X);
ui_draw_but_CURVE(region, but, &tui->wcol_regular, rect);
break;
@@ -4824,6 +4816,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
fstyle = &style->widgetlabel;
break;
+ case UI_BTYPE_TREEROW:
+ wt = widget_type(UI_WTYPE_TREEROW);
+ fstyle = &style->widgetlabel;
+ break;
+
case UI_BTYPE_SCROLL:
wt = widget_type(UI_WTYPE_SCROLL);
break;
@@ -4908,13 +4905,15 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
wt->draw(&wt->wcol, rect, state, roundboxalign);
}
- if (use_alpha_blend) {
- GPU_blend(GPU_BLEND_ALPHA);
- }
+ if (wt->text) {
+ if (use_alpha_blend) {
+ GPU_blend(GPU_BLEND_ALPHA);
+ }
- wt->text(fstyle, &wt->wcol, but, rect);
- if (use_alpha_blend) {
- GPU_blend(GPU_BLEND_NONE);
+ wt->text(fstyle, &wt->wcol, but, rect);
+ if (use_alpha_blend) {
+ GPU_blend(GPU_BLEND_NONE);
+ }
}
}
@@ -4956,30 +4955,6 @@ void ui_draw_menu_back(uiStyle *UNUSED(style), uiBlock *block, rcti *rect)
}
/**
- * Uses the widget base drawing and colors from the box widget, but ensures an opaque
- * inner color.
- */
-void ui_draw_box_opaque(rcti *rect, int roundboxalign)
-{
- uiWidgetType *wt = widget_type(UI_WTYPE_BOX);
-
- /* Alpha blend with the region's background color to force an opaque background. */
- uiWidgetColors *wcol = &wt->wcol;
- wt->state(wt, 0, 0, UI_EMBOSS_UNDEFINED);
- float background[4];
- UI_GetThemeColor4fv(TH_BACK, background);
- float new_inner[4];
- rgba_uchar_to_float(new_inner, wcol->inner);
- new_inner[0] = (new_inner[0] * new_inner[3]) + (background[0] * (1.0f - new_inner[3]));
- new_inner[1] = (new_inner[1] * new_inner[3]) + (background[1] * (1.0f - new_inner[3]));
- new_inner[2] = (new_inner[2] * new_inner[3]) + (background[2] * (1.0f - new_inner[3]));
- new_inner[3] = 1.0f;
- rgba_float_to_uchar(wcol->inner, new_inner);
-
- wt->custom(NULL, wcol, rect, 0, roundboxalign);
-}
-
-/**
* Similar to 'widget_menu_back', however we can't use the widget preset system
* because we need to pass in the original location so we know where to show the arrow.
*/
@@ -5324,11 +5299,6 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
/* need to set this first */
UI_fontstyle_set(fstyle);
- if (fstyle->kerning == 1) {
- /* for BLF_width */
- BLF_enable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
- }
-
if (separator_type == UI_MENU_ITEM_SEPARATOR_SHORTCUT) {
/* Shrink rect to exclude the shortcut string. */
rect->xmax -= BLF_width(fstyle->uifont_id, cpoin + 1, INT_MAX) + UI_DPI_ICON_SIZE;
@@ -5351,11 +5321,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
}
}
else {
- BLI_assert(!"Unknwon menu item separator type");
- }
-
- if (fstyle->kerning == 1) {
- BLF_disable(fstyle->uifont_id, BLF_KERNING_DEFAULT);
+ BLI_assert_msg(0, "Unknown menu item separator type");
}
}
}
@@ -5436,30 +5402,38 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
}
}
-void ui_draw_preview_item(
- const uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int state)
+/**
+ * Version of #ui_draw_preview_item() that does not draw the menu background and item text based on
+ * state. It just draws the preview and text directly.
+ */
+void ui_draw_preview_item_stateless(const uiFontStyle *fstyle,
+ rcti *rect,
+ const char *name,
+ int iconid,
+ const uchar text_col[4],
+ eFontStyle_Align text_align)
{
rcti trect = *rect;
const float text_size = UI_UNIT_Y;
float font_dims[2] = {0.0f, 0.0f};
- uiWidgetType *wt = widget_type(UI_WTYPE_MENU_ITEM);
-
- /* drawing button background */
- wt->state(wt, state, 0, UI_EMBOSS_UNDEFINED);
- wt->draw(&wt->wcol, rect, 0, 0);
+ const bool has_text = name && name[0];
- /* draw icon in rect above the space reserved for the label */
- rect->ymin += text_size;
+ if (has_text) {
+ /* draw icon in rect above the space reserved for the label */
+ rect->ymin += text_size;
+ }
GPU_blend(GPU_BLEND_ALPHA);
widget_draw_preview(iconid, 1.0f, rect);
GPU_blend(GPU_BLEND_NONE);
+ if (!has_text) {
+ return;
+ }
+
BLF_width_and_height(
fstyle->uifont_id, name, BLF_DRAW_STR_DUMMY_MAX, &font_dims[0], &font_dims[1]);
/* text rect */
- trect.xmin += 0;
- trect.xmax = trect.xmin + font_dims[0] + U.widget_unit / 2;
trect.ymin += U.widget_unit / 2;
trect.ymax = trect.ymin + font_dims[1];
if (trect.xmax > rect->xmax - PREVIEW_PAD) {
@@ -5478,11 +5452,27 @@ void ui_draw_preview_item(
UI_fontstyle_draw(fstyle,
&trect,
drawstr,
- wt->wcol.text,
+ text_col,
&(struct uiFontStyleDraw_Params){
- .align = UI_STYLE_TEXT_CENTER,
+ .align = text_align,
});
}
}
+void ui_draw_preview_item(const uiFontStyle *fstyle,
+ rcti *rect,
+ const char *name,
+ int iconid,
+ int state,
+ eFontStyle_Align text_align)
+{
+ uiWidgetType *wt = widget_type(UI_WTYPE_MENU_ITEM);
+
+ /* drawing button background */
+ wt->state(wt, state, 0, UI_EMBOSS_UNDEFINED);
+ wt->draw(&wt->wcol, rect, 0, 0);
+
+ ui_draw_preview_item_stateless(fstyle, rect, name, iconid, wt->wcol.text, text_align);
+}
+
/** \} */
diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c
index afac254f542..aece2e58f1e 100644
--- a/source/blender/editors/interface/resources.c
+++ b/source/blender/editors/interface/resources.c
@@ -85,7 +85,7 @@ 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 header_active[4] = {0, 0, 0, 255};
static uchar back[4] = {0, 0, 0, 255};
static uchar setting = 0;
const uchar *cp = error;
@@ -249,15 +249,18 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case TH_HEADER:
cp = ts->header;
break;
- case TH_HEADERDESEL:
- /* We calculate a dynamic builtin header deselect color, also for pull-downs. */
+
+ case TH_HEADER_ACTIVE:
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;
+ const int factor = 5;
+ /* Lighten the header color when editor is active. */
+ header_active[0] = cp[0] > 245 ? cp[0] - factor : cp[0] + factor;
+ header_active[1] = cp[1] > 245 ? cp[1] - factor : cp[1] + factor;
+ header_active[2] = cp[2] > 245 ? cp[2] - factor : cp[2] + factor;
+ header_active[3] = cp[3];
+ cp = header_active;
break;
+
case TH_HEADER_TEXT:
cp = ts->header_text;
break;
@@ -631,7 +634,7 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case TH_NODE_SHADER:
cp = ts->nodeclass_shader;
break;
- case TH_NODE_CONVERTOR:
+ case TH_NODE_CONVERTER:
cp = ts->syntaxv;
break;
case TH_NODE_GROUP:
@@ -1089,7 +1092,7 @@ bTheme *UI_GetTheme(void)
}
/**
- * for the rare case we need to temp swap in a different theme (offscreen render)
+ * For the rare case we need to temp swap in a different theme (off-screen render).
*/
void UI_Theme_Store(struct bThemeState *theme_state)
{
diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc
new file mode 100644
index 00000000000..fcc878c440c
--- /dev/null
+++ b/source/blender/editors/interface/tree_view.cc
@@ -0,0 +1,790 @@
+/*
+ * 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 "DNA_userdef_types.h"
+#include "DNA_windowmanager_types.h"
+
+#include "BKE_context.h"
+
+#include "BLT_translation.h"
+
+#include "interface_intern.h"
+
+#include "UI_interface.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "UI_tree_view.hh"
+
+namespace blender::ui {
+
+/* ---------------------------------------------------------------------- */
+
+/**
+ * Add a tree-item to the container. This is the only place where items should be added, it handles
+ * important invariants!
+ */
+AbstractTreeViewItem &TreeViewItemContainer::add_tree_item(
+ std::unique_ptr<AbstractTreeViewItem> item)
+{
+ children_.append(std::move(item));
+
+ /* The first item that will be added to the root sets this. */
+ if (root_ == nullptr) {
+ root_ = this;
+ }
+
+ AbstractTreeViewItem &added_item = *children_.last();
+ added_item.root_ = root_;
+ if (root_ != this) {
+ /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely
+ * nice to static_cast this, but well... */
+ added_item.parent_ = static_cast<AbstractTreeViewItem *>(this);
+ }
+
+ return added_item;
+}
+
+void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const
+{
+ for (const auto &child : children_) {
+ iter_fn(*child);
+ if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) {
+ continue;
+ }
+
+ child->foreach_item_recursive(iter_fn, options);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const
+{
+ foreach_item_recursive(iter_fn, options);
+}
+
+bool AbstractTreeView::is_renaming() const
+{
+ return rename_buffer_ != nullptr;
+}
+
+void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder)
+{
+ uiLayout *prev_layout = builder.current_layout();
+
+ uiLayout *box = uiLayoutBox(prev_layout);
+ uiLayoutColumn(box, false);
+
+ foreach_item([&builder](AbstractTreeViewItem &item) { builder.build_row(item); },
+ IterOptions::SkipCollapsed);
+
+ UI_block_layout_set_current(&builder.block(), prev_layout);
+}
+
+void AbstractTreeView::update_from_old(uiBlock &new_block)
+{
+ uiBlock *old_block = new_block.oldblock;
+ if (!old_block) {
+ /* Initial construction, nothing to update. */
+ is_reconstructed_ = true;
+ return;
+ }
+
+ uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
+ &new_block, reinterpret_cast<uiTreeViewHandle *>(this));
+ BLI_assert(old_view_handle);
+
+ AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle);
+
+ /* Update own persistent data. */
+ /* Keep the rename buffer persistent while renaming! The rename button uses the buffer's
+ * pointer to identify itself over redraws. */
+ rename_buffer_ = std::move(old_view.rename_buffer_);
+ old_view.rename_buffer_ = nullptr;
+
+ update_children_from_old_recursive(*this, old_view);
+
+ /* Finished (re-)constructing the tree. */
+ is_reconstructed_ = true;
+}
+
+void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemContainer &new_items,
+ const TreeViewItemContainer &old_items)
+{
+ for (const auto &new_item : new_items.children_) {
+ AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items);
+ if (!matching_old_item) {
+ continue;
+ }
+
+ new_item->update_from_old(*matching_old_item);
+
+ /* Recurse into children of the matched item. */
+ update_children_from_old_recursive(*new_item, *matching_old_item);
+ }
+}
+
+AbstractTreeViewItem *AbstractTreeView::find_matching_child(
+ const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items)
+{
+ for (const auto &iter_item : items.children_) {
+ if (lookup_item.matches(*iter_item)) {
+ /* We have a matching item! */
+ return iter_item.get();
+ }
+ }
+
+ return nullptr;
+}
+
+bool AbstractTreeView::is_reconstructed() const
+{
+ return is_reconstructed_;
+}
+
+void AbstractTreeView::change_state_delayed()
+{
+ BLI_assert_msg(
+ is_reconstructed(),
+ "These state changes are supposed to be delayed until reconstruction is completed");
+ foreach_item([](AbstractTreeViewItem &item) { item.change_state_delayed(); });
+}
+
+/* ---------------------------------------------------------------------- */
+
+void AbstractTreeViewItem::tree_row_click_fn(struct bContext * /*C*/,
+ void *but_arg1,
+ void * /*arg2*/)
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1;
+ AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>(
+ *tree_row_but->tree_item);
+
+ tree_item.activate();
+}
+
+void AbstractTreeViewItem::add_treerow_button(uiBlock &block)
+{
+ /* For some reason a width > (UI_UNIT_X * 2) make the layout system use all available width. */
+ tree_row_but_ = (uiButTreeRow *)uiDefBut(
+ &block, UI_BTYPE_TREEROW, 0, "", 0, 0, UI_UNIT_X * 10, UI_UNIT_Y, nullptr, 0, 0, 0, 0, "");
+
+ tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this);
+ UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr);
+}
+
+void AbstractTreeViewItem::add_indent(uiLayout &row) const
+{
+ uiBlock *block = uiLayoutGetBlock(&row);
+ uiLayout *subrow = uiLayoutRow(&row, true);
+ uiLayoutSetFixedSize(subrow, true);
+
+ const float indent_size = count_parents() * UI_DPI_ICON_SIZE;
+ uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, indent_size, 0, nullptr, 0.0, 0.0, 0, 0, "");
+
+ /* Indent items without collapsing icon some more within their parent. Makes it clear that they
+ * are actually nested and not just a row at the same level without a chevron. */
+ if (!is_collapsible() && parent_) {
+ uiDefBut(block, UI_BTYPE_SEPR, 0, "", 0, 0, 0.2f * UI_UNIT_X, 0, nullptr, 0.0, 0.0, 0, 0, "");
+ }
+
+ /* Restore. */
+ UI_block_layout_set_current(block, &row);
+}
+
+void AbstractTreeViewItem::collapse_chevron_click_fn(struct bContext *C,
+ void * /*but_arg1*/,
+ void * /*arg2*/)
+{
+ /* There's no data we could pass to this callback. It must be either the button itself or a
+ * consistent address to match buttons over redraws. So instead of passing it somehow, just
+ * lookup the hovered item via context here. */
+
+ const wmWindow *win = CTX_wm_window(C);
+ const ARegion *region = CTX_wm_region(C);
+ uiTreeViewItemHandle *hovered_item_handle = UI_block_tree_view_find_item_at(region,
+ win->eventstate->xy);
+ AbstractTreeViewItem *hovered_item = reinterpret_cast<AbstractTreeViewItem *>(
+ hovered_item_handle);
+ BLI_assert(hovered_item != nullptr);
+
+ hovered_item->toggle_collapsed();
+ /* When collapsing an item with an active child, make this collapsed item active instead so the
+ * active item stays visible. */
+ if (hovered_item->has_active_child()) {
+ hovered_item->activate();
+ }
+}
+
+bool AbstractTreeViewItem::is_collapse_chevron_but(const uiBut *but)
+{
+ return but->type == UI_BTYPE_BUT_TOGGLE && ELEM(but->icon, ICON_TRIA_RIGHT, ICON_TRIA_DOWN) &&
+ (but->func == collapse_chevron_click_fn);
+}
+
+void AbstractTreeViewItem::add_collapse_chevron(uiBlock &block) const
+{
+ if (!is_collapsible()) {
+ return;
+ }
+
+ const BIFIconID icon = is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN;
+ uiBut *but = uiDefIconBut(
+ &block, UI_BTYPE_BUT_TOGGLE, 0, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, nullptr, 0, 0, 0, 0, "");
+ /* Note that we're passing the tree-row button here, not the chevron one. */
+ UI_but_func_set(but, collapse_chevron_click_fn, nullptr, nullptr);
+ UI_but_flag_disable(but, UI_BUT_UNDO);
+
+ /* Check if the query for the button matches the created button. */
+ BLI_assert(is_collapse_chevron_but(but));
+}
+
+AbstractTreeViewItem *AbstractTreeViewItem::find_tree_item_from_rename_button(
+ const uiBut &rename_but)
+{
+ /* A minimal sanity check, can't do much more here. */
+ BLI_assert(rename_but.type == UI_BTYPE_TEXT && rename_but.poin);
+
+ LISTBASE_FOREACH (uiBut *, but, &rename_but.block->buttons) {
+ if (but->type != UI_BTYPE_TREEROW) {
+ continue;
+ }
+
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but;
+ AbstractTreeViewItem *item = reinterpret_cast<AbstractTreeViewItem *>(tree_row_but->tree_item);
+ const AbstractTreeView &tree_view = item->get_tree_view();
+
+ if (item->is_renaming() && (tree_view.rename_buffer_->data() == rename_but.poin)) {
+ return item;
+ }
+ }
+
+ return nullptr;
+}
+
+void AbstractTreeViewItem::rename_button_fn(bContext *UNUSED(C), void *arg, char *UNUSED(origstr))
+{
+ const uiBut *rename_but = static_cast<uiBut *>(arg);
+ AbstractTreeViewItem *item = find_tree_item_from_rename_button(*rename_but);
+ BLI_assert(item);
+
+ const AbstractTreeView &tree_view = item->get_tree_view();
+ item->rename(tree_view.rename_buffer_->data());
+ item->end_renaming();
+}
+
+void AbstractTreeViewItem::add_rename_button(uiLayout &row)
+{
+ uiBlock *block = uiLayoutGetBlock(&row);
+ eUIEmbossType previous_emboss = UI_block_emboss_get(block);
+
+ uiLayoutRow(&row, false);
+ /* Enable emboss for the text button. */
+ UI_block_emboss_set(block, UI_EMBOSS);
+
+ AbstractTreeView &tree_view = get_tree_view();
+ uiBut *rename_but = uiDefBut(block,
+ UI_BTYPE_TEXT,
+ 1,
+ "",
+ 0,
+ 0,
+ UI_UNIT_X * 10,
+ UI_UNIT_Y,
+ tree_view.rename_buffer_->data(),
+ 1.0f,
+ tree_view.rename_buffer_->max_size(),
+ 0,
+ 0,
+ "");
+
+ /* Gotta be careful with what's passed to the `arg1` here. Any tree data will be freed once the
+ * callback is executed. */
+ UI_but_func_rename_set(rename_but, AbstractTreeViewItem::rename_button_fn, rename_but);
+ UI_but_flag_disable(rename_but, UI_BUT_UNDO);
+
+ const bContext *evil_C = static_cast<bContext *>(block->evil_C);
+ ARegion *region = CTX_wm_region(evil_C);
+ /* Returns false if the button was removed. */
+ if (UI_but_active_only(evil_C, region, block, rename_but) == false) {
+ end_renaming();
+ }
+
+ UI_block_emboss_set(block, previous_emboss);
+ UI_block_layout_set_current(block, &row);
+}
+
+bool AbstractTreeViewItem::has_active_child() const
+{
+ bool found = false;
+ foreach_item_recursive([&found](const AbstractTreeViewItem &item) {
+ if (item.is_active()) {
+ found = true;
+ }
+ });
+
+ return found;
+}
+
+void AbstractTreeViewItem::on_activate()
+{
+ /* Do nothing by default. */
+}
+
+void AbstractTreeViewItem::is_active(IsActiveFn is_active_fn)
+{
+ is_active_fn_ = is_active_fn;
+}
+
+std::unique_ptr<AbstractTreeViewItemDragController> AbstractTreeViewItem::create_drag_controller()
+ const
+{
+ /* There's no drag controller (and hence no drag support) by default. */
+ return nullptr;
+}
+
+std::unique_ptr<AbstractTreeViewItemDropController> AbstractTreeViewItem::create_drop_controller()
+ const
+{
+ /* There's no drop controller (and hence no drop support) by default. */
+ return nullptr;
+}
+
+bool AbstractTreeViewItem::can_rename() const
+{
+ /* No renaming by default. */
+ return false;
+}
+
+bool AbstractTreeViewItem::rename(StringRefNull new_name)
+{
+ /* It is important to update the label after renaming, so #AbstractTreeViewItem::matches()
+ * recognizes the item. (It only compares labels by default.) */
+ label_ = new_name;
+ return true;
+}
+
+void AbstractTreeViewItem::build_context_menu(bContext & /*C*/, uiLayout & /*column*/) const
+{
+ /* No context menu by default. */
+}
+
+void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old)
+{
+ is_open_ = old.is_open_;
+ is_active_ = old.is_active_;
+ is_renaming_ = old.is_renaming_;
+}
+
+bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const
+{
+ return label_ == other.label_;
+}
+
+void AbstractTreeViewItem::begin_renaming()
+{
+ AbstractTreeView &tree_view = get_tree_view();
+ if (tree_view.is_renaming() || !can_rename()) {
+ return;
+ }
+
+ is_renaming_ = true;
+
+ tree_view.rename_buffer_ = std::make_unique<decltype(tree_view.rename_buffer_)::element_type>();
+ std::copy(std::begin(label_), std::end(label_), std::begin(*tree_view.rename_buffer_));
+}
+
+void AbstractTreeViewItem::end_renaming()
+{
+ if (!is_renaming()) {
+ return;
+ }
+
+ is_renaming_ = false;
+
+ AbstractTreeView &tree_view = get_tree_view();
+ tree_view.rename_buffer_ = nullptr;
+}
+
+AbstractTreeView &AbstractTreeViewItem::get_tree_view() const
+{
+ return static_cast<AbstractTreeView &>(*root_);
+}
+
+int AbstractTreeViewItem::count_parents() const
+{
+ int i = 0;
+ for (TreeViewItemContainer *parent = parent_; parent; parent = parent->parent_) {
+ i++;
+ }
+ return i;
+}
+
+void AbstractTreeViewItem::activate()
+{
+ BLI_assert_msg(get_tree_view().is_reconstructed(),
+ "Item activation can't be done until reconstruction is completed");
+
+ if (is_active()) {
+ return;
+ }
+
+ /* Deactivate other items in the tree. */
+ get_tree_view().foreach_item([](auto &item) { item.deactivate(); });
+
+ on_activate();
+ /* Make sure the active item is always visible. */
+ ensure_parents_uncollapsed();
+
+ is_active_ = true;
+}
+
+void AbstractTreeViewItem::deactivate()
+{
+ is_active_ = false;
+}
+
+bool AbstractTreeViewItem::is_active() const
+{
+ BLI_assert_msg(get_tree_view().is_reconstructed(),
+ "State can't be queried until reconstruction is completed");
+ return is_active_;
+}
+
+bool AbstractTreeViewItem::is_hovered() const
+{
+ BLI_assert_msg(get_tree_view().is_reconstructed(),
+ "State can't be queried until reconstruction is completed");
+ BLI_assert_msg(tree_row_but_ != nullptr,
+ "Hovered state can't be queried before the tree row is being built");
+
+ const uiTreeViewItemHandle *this_handle = reinterpret_cast<const uiTreeViewItemHandle *>(this);
+ /* The new layout hasn't finished construction yet, so the final state of the button is unknown.
+ * Get the matching button from the previous redraw instead. */
+ uiButTreeRow *old_treerow_but = ui_block_view_find_treerow_in_old_block(tree_row_but_->but.block,
+ this_handle);
+ return old_treerow_but && (old_treerow_but->but.flag & UI_ACTIVE);
+}
+
+bool AbstractTreeViewItem::is_collapsed() const
+{
+ BLI_assert_msg(get_tree_view().is_reconstructed(),
+ "State can't be queried until reconstruction is completed");
+ return is_collapsible() && !is_open_;
+}
+
+void AbstractTreeViewItem::toggle_collapsed()
+{
+ is_open_ = !is_open_;
+}
+
+void AbstractTreeViewItem::set_collapsed(bool collapsed)
+{
+ is_open_ = !collapsed;
+}
+
+bool AbstractTreeViewItem::is_collapsible() const
+{
+ return !children_.is_empty();
+}
+
+bool AbstractTreeViewItem::is_renaming() const
+{
+ return is_renaming_;
+}
+
+void AbstractTreeViewItem::ensure_parents_uncollapsed()
+{
+ for (AbstractTreeViewItem *parent = parent_; parent; parent = parent->parent_) {
+ parent->set_collapsed(false);
+ }
+}
+
+bool AbstractTreeViewItem::matches_including_parents(const AbstractTreeViewItem &other) const
+{
+ if (!matches(other)) {
+ return false;
+ }
+ if (count_parents() != other.count_parents()) {
+ return false;
+ }
+
+ for (AbstractTreeViewItem *parent = parent_, *other_parent = other.parent_;
+ parent && other_parent;
+ parent = parent->parent_, other_parent = other_parent->parent_) {
+ if (!parent->matches(*other_parent)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+uiButTreeRow *AbstractTreeViewItem::tree_row_button()
+{
+ return tree_row_but_;
+}
+
+void AbstractTreeViewItem::change_state_delayed()
+{
+ if (is_active_fn_()) {
+ activate();
+ }
+}
+/* ---------------------------------------------------------------------- */
+
+AbstractTreeViewItemDropController::AbstractTreeViewItemDropController(AbstractTreeView &tree_view)
+ : tree_view_(tree_view)
+{
+}
+
+/* ---------------------------------------------------------------------- */
+
+TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block)
+{
+}
+
+void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view)
+{
+ tree_view.build_tree();
+ tree_view.update_from_old(block_);
+ tree_view.change_state_delayed();
+ tree_view.build_layout_from_tree(TreeViewLayoutBuilder(block_));
+}
+
+/* ---------------------------------------------------------------------- */
+
+TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block)
+{
+}
+
+/**
+ * Moves the button following the last added chevron closer to the list item.
+ *
+ * Iterates backwards over buttons until finding the tree-row button, which is assumed to be the
+ * first button added for the row, and can act as a delimiter that way.
+ */
+void TreeViewLayoutBuilder::polish_layout(const uiBlock &block)
+{
+ LISTBASE_FOREACH_BACKWARD (uiBut *, but, &block.buttons) {
+ if (AbstractTreeViewItem::is_collapse_chevron_but(but) && but->next &&
+ /* Embossed buttons with padding-less text padding look weird, so don't touch them. */
+ ELEM(but->next->emboss, UI_EMBOSS_NONE, UI_EMBOSS_NONE_OR_STATUS)) {
+ UI_but_drawflag_enable(static_cast<uiBut *>(but->next), UI_BUT_NO_TEXT_PADDING);
+ }
+
+ if (but->type == UI_BTYPE_TREEROW) {
+ break;
+ }
+ }
+}
+
+void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const
+{
+ uiBlock &block_ = block();
+
+ uiLayout *prev_layout = current_layout();
+ eUIEmbossType previous_emboss = UI_block_emboss_get(&block_);
+
+ uiLayout *overlap = uiLayoutOverlap(prev_layout);
+
+ uiLayoutRow(overlap, false);
+ /* Every item gets one! Other buttons can be overlapped on top. */
+ item.add_treerow_button(block_);
+
+ /* After adding tree-row button (would disable hover highlighting). */
+ UI_block_emboss_set(&block_, UI_EMBOSS_NONE);
+
+ uiLayout *row = uiLayoutRow(overlap, true);
+ item.add_indent(*row);
+ item.add_collapse_chevron(block_);
+
+ if (item.is_renaming()) {
+ item.add_rename_button(*row);
+ }
+ else {
+ item.build_row(*row);
+ }
+ polish_layout(block_);
+
+ UI_block_emboss_set(&block_, previous_emboss);
+ UI_block_layout_set_current(&block_, prev_layout);
+}
+
+uiBlock &TreeViewLayoutBuilder::block() const
+{
+ return block_;
+}
+
+uiLayout *TreeViewLayoutBuilder::current_layout() const
+{
+ return block().curlayout;
+}
+
+/* ---------------------------------------------------------------------- */
+
+BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(icon_)
+{
+ label_ = label;
+}
+
+void BasicTreeViewItem::build_row(uiLayout &row)
+{
+ add_label(row);
+}
+
+void BasicTreeViewItem::add_label(uiLayout &layout, StringRefNull label_override)
+{
+ const StringRefNull label = label_override.is_empty() ? StringRefNull(label_) : label_override;
+
+ /* Some padding for labels without collapse chevron and no icon. Looks weird without. */
+ if (icon == ICON_NONE && !is_collapsible()) {
+ uiItemS_ex(&layout, 0.8f);
+ }
+ uiItemL(&layout, IFACE_(label.c_str()), icon);
+}
+
+void BasicTreeViewItem::on_activate()
+{
+ if (activate_fn_) {
+ activate_fn_(*this);
+ }
+}
+
+void BasicTreeViewItem::on_activate(ActivateFn fn)
+{
+ activate_fn_ = fn;
+}
+
+} // namespace blender::ui
+
+using namespace blender::ui;
+
+bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
+ return item.is_active();
+}
+
+bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle,
+ const uiTreeViewItemHandle *b_handle)
+{
+ const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle);
+ const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle);
+ /* TODO should match the tree-view as well. */
+ return a.matches_including_parents(b);
+}
+
+/**
+ * Attempt to start dragging the tree-item \a item_. This will not work if the tree item doesn't
+ * support dragging, i.e. it won't create a drag-controller upon request.
+ * \return True if dragging started successfully, otherwise false.
+ */
+bool UI_tree_view_item_drag_start(bContext *C, uiTreeViewItemHandle *item_)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ const std::unique_ptr<AbstractTreeViewItemDragController> drag_controller =
+ item.create_drag_controller();
+ if (!drag_controller) {
+ return false;
+ }
+
+ WM_event_start_drag(C,
+ ICON_NONE,
+ drag_controller->get_drag_type(),
+ drag_controller->create_drag_data(),
+ 0,
+ WM_DRAG_FREE_DATA);
+ return true;
+}
+
+bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_,
+ const wmDrag *drag,
+ const char **r_disabled_hint)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
+ item.create_drop_controller();
+ if (!drop_controller) {
+ return false;
+ }
+
+ return drop_controller->can_drop(*drag, r_disabled_hint);
+}
+
+char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, const wmDrag *drag)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ const std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
+ item.create_drop_controller();
+ if (!drop_controller) {
+ return nullptr;
+ }
+
+ return BLI_strdup(drop_controller->drop_tooltip(*drag).c_str());
+}
+
+/**
+ * Let a tree-view item handle a drop event.
+ * \return True if the drop was handled by the tree-view item.
+ */
+bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase *drags)
+{
+ AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_);
+ std::unique_ptr<AbstractTreeViewItemDropController> drop_controller =
+ item.create_drop_controller();
+
+ const char *disabled_hint_dummy = nullptr;
+ LISTBASE_FOREACH (const wmDrag *, drag, drags) {
+ if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) {
+ return drop_controller->on_drop(*drag);
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Can \a item_handle be renamed right now? Not that this isn't just a mere wrapper around
+ * #AbstractTreeViewItem::can_rename(). This also checks if there is another item being renamed,
+ * and returns false if so.
+ */
+bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
+ const AbstractTreeView &tree_view = item.get_tree_view();
+ return !tree_view.is_renaming() && item.can_rename();
+}
+
+void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle)
+{
+ AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_handle);
+ item.begin_renaming();
+}
+
+void UI_tree_view_item_context_menu_build(bContext *C,
+ const uiTreeViewItemHandle *item_handle,
+ uiLayout *column)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
+ item.build_context_menu(*C, *column);
+}
diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c
index 5eb20ae601b..eea6512f0f8 100644
--- a/source/blender/editors/interface/view2d.c
+++ b/source/blender/editors/interface/view2d.c
@@ -32,6 +32,7 @@
#include "DNA_userdef_types.h"
#include "BLI_array.h"
+#include "BLI_easing.h"
#include "BLI_link_utils.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
@@ -166,7 +167,7 @@ static void view2d_masks(View2D *v2d, const rcti *mask_scroll)
scroll = view2d_scroll_mapped(v2d->scroll);
- /* scrollers are based off regionsize
+ /* Scrollers are based off region-size:
* - they can only be on one to two edges of the region they define
* - if they overlap, they must not occupy the corners (which are reserved for other widgets)
*/
@@ -347,7 +348,7 @@ void UI_view2d_region_reinit(View2D *v2d, short type, int winx, int winy)
v2d->align = (V2D_ALIGN_NO_NEG_X | V2D_ALIGN_NO_POS_Y);
v2d->keeptot = V2D_KEEPTOT_BOUNDS;
- /* note, scroll is being flipped in ED_region_panels() drawing */
+ /* NOTE: scroll is being flipped in #ED_region_panels() drawing. */
v2d->scroll |= (V2D_SCROLL_HORIZONTAL_HIDE | V2D_SCROLL_VERTICAL_HIDE);
if (do_init) {
@@ -717,7 +718,7 @@ static void ui_view2d_curRect_validate_resize(View2D *v2d, bool resize)
*
* So, resolution is to just shift view by the gap between the extremities.
* We favor moving the 'minimum' across, as that's origin for most things.
- * (XXX - in the past, max was favored... if there are bugs, swap!)
+ * (XXX: in the past, max was favored... if there are bugs, swap!)
*/
if ((cur->xmin < tot->xmin) && (cur->xmax > tot->xmax)) {
/* outside boundaries on both sides,
@@ -866,6 +867,11 @@ void UI_view2d_curRect_changed(const bContext *C, View2D *v2d)
/* ------------------ */
+bool UI_view2d_area_supports_sync(ScrArea *area)
+{
+ return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH);
+}
+
/* Called by menus to activate it, or by view2d operators
* to make sure 'related' views stay in synchrony */
void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag)
@@ -903,6 +909,9 @@ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag)
/* check if doing whole screen syncing (i.e. time/horizontal) */
if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) {
LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
+ if (!UI_view2d_area_supports_sync(area_iter)) {
+ continue;
+ }
LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) {
/* don't operate on self */
if (v2dcur != &region->v2d) {
@@ -1059,7 +1068,7 @@ void UI_view2d_zoom_cache_reset(void)
/* While scaling we can accumulate fonts at many sizes (~20 or so).
* Not an issue with embedded font, but can use over 500Mb with i18n ones! See T38244. */
- /* Note: only some views draw text, we could check for this case to avoid cleaning cache. */
+ /* NOTE: only some views draw text, we could check for this case to avoid cleaning cache. */
BLF_cache_clear();
}
@@ -1158,7 +1167,7 @@ void UI_view2d_view_orthoSpecial(ARegion *region, View2D *v2d, const bool xaxis)
* correspondence with pixels for smooth UI drawing,
* but only applied where requested.
*/
- /* XXX temp (ton) */
+ /* XXX(ton): temp. */
xofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_X) ? GLA_PIXEL_OFS : 0.0f;
yofs = 0.0f; // (v2d->flag & V2D_PIXELOFS_Y) ? GLA_PIXEL_OFS : 0.0f;
@@ -1194,78 +1203,6 @@ void UI_view2d_view_restore(const bContext *C)
/** \name Grid-Line Drawing
* \{ */
-/* Draw a constant grid in given 2d-region */
-void UI_view2d_constant_grid_draw(const View2D *v2d, float step)
-{
- float start_x, start_y;
- int count_x, count_y;
-
- start_x = v2d->cur.xmin;
- if (start_x < 0.0) {
- start_x += -(float)fmod(v2d->cur.xmin, step);
- }
- else {
- start_x += (step - (float)fmod(v2d->cur.xmin, step));
- }
-
- if (start_x > v2d->cur.xmax) {
- count_x = 0;
- }
- else {
- count_x = (v2d->cur.xmax - start_x) / step + 1;
- }
-
- start_y = v2d->cur.ymin;
- if (start_y < 0.0) {
- start_y += -(float)fmod(v2d->cur.ymin, step);
- }
- else {
- start_y += (step - (float)fabs(fmod(v2d->cur.ymin, step)));
- }
-
- if (start_y > v2d->cur.ymax) {
- count_y = 0;
- }
- else {
- count_y = (v2d->cur.ymax - start_y) / step + 1;
- }
-
- if (count_x > 0 || count_y > 0) {
- GPUVertFormat *format = immVertexFormat();
- const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- const uint color = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
- float theme_color[3];
-
- UI_GetThemeColorShade3fv(TH_BACK, -10, theme_color);
-
- immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
- immBegin(GPU_PRIM_LINES, count_x * 2 + count_y * 2 + 4);
-
- immAttr3fv(color, theme_color);
- for (int i = 0; i < count_x; start_x += step, i++) {
- immVertex2f(pos, start_x, v2d->cur.ymin);
- immVertex2f(pos, start_x, v2d->cur.ymax);
- }
-
- for (int i = 0; i < count_y; start_y += step, i++) {
- immVertex2f(pos, v2d->cur.xmin, start_y);
- immVertex2f(pos, v2d->cur.xmax, start_y);
- }
-
- /* X and Y axis */
- UI_GetThemeColorShade3fv(TH_BACK, -18, theme_color);
-
- immAttr3fv(color, theme_color);
- immVertex2f(pos, 0.0f, v2d->cur.ymin);
- immVertex2f(pos, 0.0f, v2d->cur.ymax);
- immVertex2f(pos, v2d->cur.xmin, 0.0f);
- immVertex2f(pos, v2d->cur.xmax, 0.0f);
-
- immEnd();
- immUnbindProgram();
- }
-}
-
/* Draw a multi-level grid in given 2d-region */
void UI_view2d_multi_grid_draw(
const View2D *v2d, int colorid, float step, int level_size, int totlevels)
@@ -1355,6 +1292,114 @@ void UI_view2d_multi_grid_draw(
immUnbindProgram();
}
+static void grid_axis_start_and_count(
+ const float step, const float min, const float max, float *r_start, int *r_count)
+{
+ *r_start = min;
+ if (*r_start < 0.0f) {
+ *r_start += -(float)fmod(min, step);
+ }
+ else {
+ *r_start += step - (float)fabs(fmod(min, step));
+ }
+
+ if (*r_start > max) {
+ *r_count = 0;
+ }
+ else {
+ *r_count = (max - *r_start) / step + 1;
+ }
+}
+
+typedef struct DotGridLevelInfo {
+ /* The factor applied to the #min_step argument. This could be easily computed in runtime,
+ * but seeing it together with the other values is helpful. */
+ float step_factor;
+ /* The normalized zoom level at which the grid level starts to fade in.
+ * At lower zoom levels, the points will not be visible and the level will be skipped. */
+ float fade_in_start_zoom;
+ /* The normalized zoom level at which the grid finishes fading in.
+ * At higher zoom levels, the points will be opaque. */
+ float fade_in_end_zoom;
+} DotGridLevelInfo;
+
+static const DotGridLevelInfo level_info[9] = {
+ {6.4f, -0.1f, 0.01f},
+ {3.2f, 0.0f, 0.025f},
+ {1.6f, 0.025f, 0.15f},
+ {0.8f, 0.05f, 0.2f},
+ {0.4f, 0.1f, 0.25f},
+ {0.2f, 0.125f, 0.3f},
+ {0.1f, 0.25f, 0.5f},
+ {0.05f, 0.7f, 0.9f},
+ {0.025f, 0.6f, 0.9f},
+};
+
+/**
+ * Draw a multi-level grid of dots, with a dynamic number of levels based on the fading.
+ *
+ * \param grid_color_id: The theme color used for the points. Faded dynamically based on zoom.
+ * \param min_step: The base size of the grid. At different zoom levels, the visible grid may have
+ * a larger step size.
+ * \param grid_levels: The maximum grid depth. Larger grid levels will subdivide the grid more.
+ */
+void UI_view2d_dot_grid_draw(const View2D *v2d,
+ const int grid_color_id,
+ const float min_step,
+ const int grid_levels)
+{
+ BLI_assert(grid_levels > 0 && grid_levels < 10);
+ const float zoom_x = (float)(BLI_rcti_size_x(&v2d->mask) + 1) / BLI_rctf_size_x(&v2d->cur);
+ const float zoom_normalized = (zoom_x - v2d->minzoom) / (v2d->maxzoom - v2d->minzoom);
+
+ GPUVertFormat *format = immVertexFormat();
+ const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+ const uint color_id = GPU_vertformat_attr_add(format, "color", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+ immBindBuiltinProgram(GPU_SHADER_2D_FLAT_COLOR);
+ GPU_point_size(3.0f * UI_DPI_FAC);
+
+ float color[4];
+ UI_GetThemeColor3fv(grid_color_id, color);
+
+ for (int level = 0; level < grid_levels; level++) {
+ const DotGridLevelInfo *info = &level_info[level];
+ const float step = min_step * info->step_factor * U.widget_unit;
+
+ const float alpha_factor = (zoom_normalized - info->fade_in_start_zoom) /
+ (info->fade_in_end_zoom - info->fade_in_start_zoom);
+ color[3] = clamp_f(BLI_easing_cubic_ease_in_out(alpha_factor, 0.0f, 1.0f, 1.0f), 0.0f, 1.0f);
+ if (color[3] == 0.0f) {
+ break;
+ }
+
+ int count_x;
+ float start_x;
+ grid_axis_start_and_count(step, v2d->cur.xmin, v2d->cur.xmax, &start_x, &count_x);
+ int count_y;
+ float start_y;
+ grid_axis_start_and_count(step, v2d->cur.ymin, v2d->cur.ymax, &start_y, &count_y);
+ if (count_x == 0 || count_y == 0) {
+ continue;
+ }
+
+ immBegin(GPU_PRIM_POINTS, count_x * count_y);
+
+ /* Theoretically drawing on top of lower grid levels could be avoided, but it would also
+ * increase the complexity of this loop, which isn't worth the time at the moment. */
+ for (int i_y = 0; i_y < count_y; i_y++) {
+ const float y = start_y + step * i_y;
+ for (int i_x = 0; i_x < count_x; i_x++) {
+ const float x = start_x + step * i_x;
+ immAttr4fv(color_id, color);
+ immVertex2f(pos, x, y);
+ }
+ }
+
+ immEnd();
+ }
+
+ immUnbindProgram();
+}
/** \} */
/* -------------------------------------------------------------------- */
@@ -1844,7 +1889,7 @@ View2D *UI_view2d_fromcontext(const bContext *C)
return &(region->v2d);
}
-/* same as above, but it returns regionwindow. Utility for pulldowns or buttons */
+/* Same as above, but it returns region-window. Utility for pull-downs or buttons. */
View2D *UI_view2d_fromcontext_rwin(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
@@ -1914,7 +1959,7 @@ float UI_view2d_scale_get_y(const View2D *v2d)
return BLI_rcti_size_y(&v2d->mask) / BLI_rctf_size_y(&v2d->cur);
}
/**
- * Same as ``UI_view2d_scale_get() - 1.0f / x, y``
+ * Same as `UI_view2d_scale_get() - 1.0f / x, y`.
*/
void UI_view2d_scale_get_inverse(const View2D *v2d, float *r_x, float *r_y)
{
@@ -1988,8 +2033,10 @@ void UI_view2d_offset(struct View2D *v2d, float xfac, float yfac)
* - 'v' = in vertical scroller.
* - 0 = not in scroller.
*/
-char UI_view2d_mouse_in_scrollers_ex(
- const ARegion *region, const View2D *v2d, int x, int y, int *r_scroll)
+char UI_view2d_mouse_in_scrollers_ex(const ARegion *region,
+ const View2D *v2d,
+ const int xy[2],
+ int *r_scroll)
{
const int scroll = view2d_scroll_mapped(v2d->scroll);
*r_scroll = scroll;
@@ -1997,8 +2044,8 @@ char UI_view2d_mouse_in_scrollers_ex(
if (scroll) {
/* Move to region-coordinates. */
const int co[2] = {
- x - region->winrct.xmin,
- y - region->winrct.ymin,
+ xy[0] - region->winrct.xmin,
+ xy[1] - region->winrct.ymin,
};
if (scroll & V2D_SCROLL_HORIZONTAL) {
if (IN_2D_HORIZ_SCROLL(v2d, co)) {
@@ -2042,10 +2089,10 @@ char UI_view2d_rect_in_scrollers_ex(const ARegion *region,
return 0;
}
-char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, int x, int y)
+char UI_view2d_mouse_in_scrollers(const ARegion *region, const View2D *v2d, const int xy[2])
{
int scroll_dummy = 0;
- return UI_view2d_mouse_in_scrollers_ex(region, v2d, x, y, &scroll_dummy);
+ return UI_view2d_mouse_in_scrollers_ex(region, v2d, xy, &scroll_dummy);
}
char UI_view2d_rect_in_scrollers(const ARegion *region, const View2D *v2d, const rcti *rect)
diff --git a/source/blender/editors/interface/view2d_draw.c b/source/blender/editors/interface/view2d_draw.c
index 5801b7cdbdb..b1869fbf2f9 100644
--- a/source/blender/editors/interface/view2d_draw.c
+++ b/source/blender/editors/interface/view2d_draw.c
@@ -216,7 +216,7 @@ static void draw_parallel_lines(const ParallelLinesSet *lines,
immBindBuiltinProgram(GPU_SHADER_3D_POLYLINE_UNIFORM_COLOR);
immUniform2fv("viewportSize", &viewport[2]);
/* -1.0f offset here is because the line is too fat due to the builtin anti-aliasing.
- * TODO make a variant or a uniform to toggle it off. */
+ * TODO: make a variant or a uniform to toggle it off. */
immUniform1f("lineWidth", U.pixelsize - 1.0f);
}
else {
@@ -326,20 +326,35 @@ static void draw_horizontal_scale_indicators(const ARegion *region,
const float xmin = rect->xmin;
const float xmax = rect->xmax;
- for (uint i = 0; i < steps; i++) {
- const float xpos_view = start + i * distance;
- const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view);
- char text[32];
- to_string(to_string_data, xpos_view, distance, sizeof(text), text);
- const float text_width = BLF_width(font_id, text, strlen(text));
+ char text[32];
- if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) {
- BLF_draw_default_ascii(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text));
+ /* Calculate max_label_count and draw_frequency based on largest visible label. */
+ int draw_frequency;
+ {
+ to_string(to_string_data, start, 0, sizeof(text), text);
+ const float left_text_width = BLF_width(font_id, text, strlen(text));
+ to_string(to_string_data, start + steps * distance, 0, sizeof(text), text);
+ const float right_text_width = BLF_width(font_id, text, strlen(text));
+ const float max_text_width = max_ff(left_text_width, right_text_width);
+ const float max_label_count = BLI_rcti_size_x(&v2d->mask) / (max_text_width + 10.0f);
+ draw_frequency = ceil((float)steps / max_label_count);
+ }
+
+ if (draw_frequency != 0) {
+ const int start_index = abs((int)(start / distance)) % draw_frequency;
+ for (uint i = start_index; i < steps; i += draw_frequency) {
+ const float xpos_view = start + i * distance;
+ const float xpos_region = UI_view2d_view_to_region_x(v2d, xpos_view);
+ to_string(to_string_data, xpos_view, distance, sizeof(text), text);
+ const float text_width = BLF_width(font_id, text, strlen(text));
+
+ if (xpos_region - text_width / 2.0f >= xmin && xpos_region + text_width / 2.0f <= xmax) {
+ BLF_draw_default(xpos_region - text_width / 2.0f, ypos, 0.0f, text, sizeof(text));
+ }
}
}
BLF_batch_draw_end();
-
GPU_matrix_pop_projection();
}
@@ -379,29 +394,33 @@ static void draw_vertical_scale_indicators(const ARegion *region,
const int font_id = BLF_default();
UI_FontThemeColor(font_id, colorid);
- BLF_enable(font_id, BLF_ROTATION);
- BLF_rotation(font_id, M_PI_2);
-
BLF_batch_draw_begin();
- const float xpos = rect->xmax - 2.0f * UI_DPI_FAC;
+ BLF_enable(font_id, BLF_SHADOW);
+ const float shadow_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+ BLF_shadow(font_id, 5, shadow_color);
+ BLF_shadow_offset(font_id, 1, -1);
+
+ const float x_offset = 8.0f;
+ const float xpos = (rect->xmin + x_offset) * UI_DPI_FAC;
const float ymin = rect->ymin;
const float ymax = rect->ymax;
+ const float y_offset = (BLF_height(font_id, "0", 1) / 2.0f) - U.pixelsize;
for (uint i = 0; i < steps; i++) {
const float ypos_view = start + i * distance;
const float ypos_region = UI_view2d_view_to_region_y(v2d, ypos_view + display_offset);
char text[32];
to_string(to_string_data, ypos_view, distance, sizeof(text), text);
- const float text_width = BLF_width(font_id, text, strlen(text));
- if (ypos_region - text_width / 2.0f >= ymin && ypos_region + text_width / 2.0f <= ymax) {
- BLF_draw_default_ascii(xpos, ypos_region - text_width / 2.0f, 0.0f, text, sizeof(text));
+ if (ypos_region - y_offset >= ymin && ypos_region + y_offset <= ymax) {
+ BLF_draw_default(xpos, ypos_region - y_offset, 0.0f, text, sizeof(text));
}
}
+ BLF_disable(font_id, BLF_SHADOW);
+
BLF_batch_draw_end();
- BLF_disable(font_id, BLF_ROTATION);
GPU_matrix_pop_projection();
}
@@ -413,11 +432,15 @@ static void view_to_string__frame_number(
}
static void view_to_string__time(
- void *user_data, float v2d_pos, float UNUSED(v2d_step), uint max_len, char *r_str)
+ void *user_data, float v2d_pos, float v2d_step, uint max_len, char *r_str)
{
const Scene *scene = (const Scene *)user_data;
- const int brevity_level = 0;
+ int brevity_level = 0;
+ if (U.timecode_style == USER_TIMECODE_MINIMAL && v2d_step >= FPS) {
+ brevity_level = 1;
+ }
+
BLI_timecode_string_from_time(
r_str, max_len, brevity_level, v2d_pos / (float)FPS, FPS, U.timecode_style);
}
@@ -460,10 +483,11 @@ float UI_view2d_grid_resolution_y__values(const struct View2D *v2d)
/* Line Drawing API
**************************************************/
-void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d)
+void UI_view2d_draw_lines_x__discrete_values(const View2D *v2d, bool display_minor_lines)
{
const uint major_line_distance = view2d_major_step_x__discrete(v2d);
- view2d_draw_lines(v2d, major_line_distance, major_line_distance > 1, 'v');
+ view2d_draw_lines(
+ v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v');
}
void UI_view2d_draw_lines_x__values(const View2D *v2d)
@@ -478,21 +502,25 @@ void UI_view2d_draw_lines_y__values(const View2D *v2d)
view2d_draw_lines(v2d, major_line_distance, true, 'h');
}
-void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d, const Scene *scene)
+void UI_view2d_draw_lines_x__discrete_time(const View2D *v2d,
+ const Scene *scene,
+ bool display_minor_lines)
{
const float major_line_distance = view2d_major_step_x__time(v2d, scene);
- view2d_draw_lines(v2d, major_line_distance, major_line_distance > 1, 'v');
+ view2d_draw_lines(
+ v2d, major_line_distance, display_minor_lines && (major_line_distance > 1), 'v');
}
void UI_view2d_draw_lines_x__discrete_frames_or_seconds(const View2D *v2d,
const Scene *scene,
- bool display_seconds)
+ bool display_seconds,
+ bool display_minor_lines)
{
if (display_seconds) {
- UI_view2d_draw_lines_x__discrete_time(v2d, scene);
+ UI_view2d_draw_lines_x__discrete_time(v2d, scene, display_minor_lines);
}
else {
- UI_view2d_draw_lines_x__discrete_values(v2d);
+ UI_view2d_draw_lines_x__discrete_values(v2d, display_minor_lines);
}
}
@@ -501,7 +529,7 @@ void UI_view2d_draw_lines_x__frames_or_seconds(const View2D *v2d,
bool display_seconds)
{
if (display_seconds) {
- UI_view2d_draw_lines_x__discrete_time(v2d, scene);
+ UI_view2d_draw_lines_x__discrete_time(v2d, scene, true);
}
else {
UI_view2d_draw_lines_x__values(v2d);
diff --git a/source/blender/editors/interface/view2d_edge_pan.c b/source/blender/editors/interface/view2d_edge_pan.c
index ca32a754f1d..8d8b9a4fe48 100644
--- a/source/blender/editors/interface/view2d_edge_pan.c
+++ b/source/blender/editors/interface/view2d_edge_pan.c
@@ -71,7 +71,8 @@ void UI_view2d_edge_pan_init(bContext *C,
float outside_pad,
float speed_ramp,
float max_speed,
- float delay)
+ float delay,
+ float zoom_influence)
{
if (!UI_view2d_edge_pan_poll(C)) {
return;
@@ -89,6 +90,9 @@ void UI_view2d_edge_pan_init(bContext *C,
vpd->speed_ramp = speed_ramp;
vpd->max_speed = max_speed;
vpd->delay = delay;
+ vpd->zoom_influence = zoom_influence;
+
+ vpd->enabled = false;
/* Calculate translation factor, based on size of view. */
const float winx = (float)(BLI_rcti_size_x(&vpd->region->winrct) + 1);
@@ -104,6 +108,7 @@ void UI_view2d_edge_pan_reset(View2DEdgePanData *vpd)
vpd->edge_pan_start_time_x = 0.0;
vpd->edge_pan_start_time_y = 0.0;
vpd->edge_pan_last_time = PIL_check_seconds_timer();
+ vpd->initial_rect = vpd->region->v2d.cur;
}
/**
@@ -160,7 +165,7 @@ static float edge_pan_speed(View2DEdgePanData *vpd,
distance = min - event_loc;
}
else {
- BLI_assert(!"Calculating speed outside of pan zones");
+ BLI_assert_msg(0, "Calculating speed outside of pan zones");
return 0.0f;
}
float distance_factor = distance / (vpd->speed_ramp * U.widget_unit);
@@ -168,9 +173,17 @@ static float edge_pan_speed(View2DEdgePanData *vpd,
/* Apply a fade in to the speed based on a start time delay. */
const double start_time = x_dir ? vpd->edge_pan_start_time_x : vpd->edge_pan_start_time_y;
- const float delay_factor = smootherstep(vpd->delay, (float)(current_time - start_time));
+ const float delay_factor = vpd->delay > 0.01f ?
+ smootherstep(vpd->delay, (float)(current_time - start_time)) :
+ 1.0f;
+
+ /* Zoom factor increases speed when zooming in and decreases speed when zooming out. */
+ const float zoomx = (float)(BLI_rcti_size_x(&region->winrct) + 1) /
+ BLI_rctf_size_x(&region->v2d.cur);
+ const float zoom_factor = 1.0f + CLAMPIS(vpd->zoom_influence, 0.0f, 1.0f) * (zoomx - 1.0f);
- return distance_factor * delay_factor * vpd->max_speed * U.widget_unit * (float)U.dpi_fac;
+ return distance_factor * delay_factor * zoom_factor * vpd->max_speed * U.widget_unit *
+ (float)U.dpi_fac;
}
static void edge_pan_apply_delta(bContext *C, View2DEdgePanData *vpd, float dx, float dy)
@@ -206,7 +219,7 @@ static void edge_pan_apply_delta(bContext *C, View2DEdgePanData *vpd, float dx,
UI_view2d_sync(vpd->screen, vpd->area, v2d, V2D_LOCK_COPY);
}
-void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, int x, int y)
+void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, const int xy[2])
{
ARegion *region = vpd->region;
@@ -216,20 +229,27 @@ void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, int x, int y)
BLI_rcti_pad(&inside_rect, -vpd->inside_pad * U.widget_unit, -vpd->inside_pad * U.widget_unit);
BLI_rcti_pad(&outside_rect, vpd->outside_pad * U.widget_unit, vpd->outside_pad * U.widget_unit);
+ /* Check if we can actually start the edge pan (e.g. adding nodes outside the view will start
+ * disabled). */
+ if (BLI_rcti_isect_pt_v(&inside_rect, xy)) {
+ /* We are inside once, can start. */
+ vpd->enabled = true;
+ }
+
int pan_dir_x = 0;
int pan_dir_y = 0;
- if ((vpd->outside_pad == 0) || BLI_rcti_isect_pt(&outside_rect, x, y)) {
+ if (vpd->enabled && ((vpd->outside_pad == 0) || BLI_rcti_isect_pt_v(&outside_rect, xy))) {
/* Find whether the mouse is beyond X and Y edges. */
- if (x > inside_rect.xmax) {
+ if (xy[0] > inside_rect.xmax) {
pan_dir_x = 1;
}
- else if (x < inside_rect.xmin) {
+ else if (xy[0] < inside_rect.xmin) {
pan_dir_x = -1;
}
- if (y > inside_rect.ymax) {
+ if (xy[1] > inside_rect.ymax) {
pan_dir_y = 1;
}
- else if (y < inside_rect.ymin) {
+ else if (xy[1] < inside_rect.ymin) {
pan_dir_y = -1;
}
}
@@ -241,11 +261,11 @@ void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, int x, int y)
const float dtime = (float)(current_time - vpd->edge_pan_last_time);
float dx = 0.0f, dy = 0.0f;
if (pan_dir_x != 0) {
- const float speed = edge_pan_speed(vpd, x, true, current_time);
+ const float speed = edge_pan_speed(vpd, xy[0], true, current_time);
dx = dtime * speed * (float)pan_dir_x;
}
if (pan_dir_y != 0) {
- const float speed = edge_pan_speed(vpd, y, false, current_time);
+ const float speed = edge_pan_speed(vpd, xy[1], false, current_time);
dy = dtime * speed * (float)pan_dir_y;
}
vpd->edge_pan_last_time = current_time;
@@ -261,7 +281,28 @@ void UI_view2d_edge_pan_apply_event(bContext *C, View2DEdgePanData *vpd, const w
return;
}
- UI_view2d_edge_pan_apply(C, vpd, event->x, event->y);
+ UI_view2d_edge_pan_apply(C, vpd, event->xy);
+}
+
+void UI_view2d_edge_pan_cancel(bContext *C, View2DEdgePanData *vpd)
+{
+ View2D *v2d = vpd->v2d;
+ if (!v2d) {
+ return;
+ }
+
+ v2d->cur = vpd->initial_rect;
+
+ /* Inform v2d about changes after this operation. */
+ UI_view2d_curRect_changed(C, v2d);
+
+ /* Don't rebuild full tree in outliner, since we're just changing our view. */
+ ED_region_tag_redraw_no_rebuild(vpd->region);
+
+ /* Request updates to be done. */
+ WM_event_add_mousemove(CTX_wm_window(C));
+
+ UI_view2d_sync(vpd->screen, vpd->area, v2d, V2D_LOCK_COPY);
}
void UI_view2d_edge_pan_operator_properties(wmOperatorType *ot)
@@ -272,7 +313,8 @@ void UI_view2d_edge_pan_operator_properties(wmOperatorType *ot)
/*outside_pad*/ 0.0f,
/*speed_ramp*/ 1.0f,
/*max_speed*/ 500.0f,
- /*delay*/ 1.0f);
+ /*delay*/ 1.0f,
+ /*zoom_influence*/ 0.0f);
}
void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
@@ -280,7 +322,8 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
float outside_pad,
float speed_ramp,
float max_speed,
- float delay)
+ float delay,
+ float zoom_influence)
{
RNA_def_float(
ot->srna,
@@ -329,6 +372,15 @@ void UI_view2d_edge_pan_operator_properties_ex(struct wmOperatorType *ot,
"Delay in seconds before maximum speed is reached",
0.0f,
10.0f);
+ RNA_def_float(ot->srna,
+ "zoom_influence",
+ zoom_influence,
+ 0.0f,
+ 1.0f,
+ "Zoom Influence",
+ "Influence of the zoom factor on scroll speed",
+ 0.0f,
+ 1.0f);
}
void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOperator *op)
@@ -339,7 +391,8 @@ void UI_view2d_edge_pan_operator_init(bContext *C, View2DEdgePanData *vpd, wmOpe
RNA_float_get(op->ptr, "outside_padding"),
RNA_float_get(op->ptr, "speed_ramp"),
RNA_float_get(op->ptr, "max_speed"),
- RNA_float_get(op->ptr, "delay"));
+ RNA_float_get(op->ptr, "delay"),
+ RNA_float_get(op->ptr, "zoom_influence"));
}
/** \} */
diff --git a/source/blender/editors/interface/view2d_gizmo_navigate.c b/source/blender/editors/interface/view2d_gizmo_navigate.c
index 30b4a7c097a..3ff5b471731 100644
--- a/source/blender/editors/interface/view2d_gizmo_navigate.c
+++ b/source/blender/editors/interface/view2d_gizmo_navigate.c
@@ -127,11 +127,24 @@ struct NavigateWidgetGroup {
int region_size[2];
};
-static bool WIDGETGROUP_navigate_poll(const bContext *UNUSED(C), wmGizmoGroupType *UNUSED(gzgt))
+static bool WIDGETGROUP_navigate_poll(const bContext *C, wmGizmoGroupType *UNUSED(gzgt))
{
if ((U.uiflag & USER_SHOW_GIZMO_NAVIGATE) == 0) {
return false;
}
+ ScrArea *area = CTX_wm_area(C);
+ if (area == NULL) {
+ return false;
+ }
+ switch (area->spacetype) {
+ case SPACE_SEQ: {
+ const SpaceSeq *sseq = area->spacedata.first;
+ if (sseq->gizmo_flag & (SEQ_GIZMO_HIDE | SEQ_GIZMO_HIDE_NAVIGATE)) {
+ return false;
+ }
+ break;
+ }
+ }
return true;
}
diff --git a/source/blender/editors/interface/view2d_ops.c b/source/blender/editors/interface/view2d_ops.c
index 69acfc657dc..0bca4e327cc 100644
--- a/source/blender/editors/interface/view2d_ops.c
+++ b/source/blender/editors/interface/view2d_ops.c
@@ -147,6 +147,8 @@ static void view_pan_init(bContext *C, wmOperator *op)
const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1);
vpd->facx = (BLI_rctf_size_x(&vpd->v2d->cur)) / winx;
vpd->facy = (BLI_rctf_size_y(&vpd->v2d->cur)) / winy;
+
+ vpd->v2d->flag |= V2D_IS_NAVIGATING;
}
/* apply transform to view (i.e. adjust 'cur' rect) */
@@ -190,6 +192,8 @@ static void view_pan_apply(bContext *C, wmOperator *op)
/* Cleanup temp custom-data. */
static void view_pan_exit(wmOperator *op)
{
+ v2dViewPanData *vpd = op->customdata;
+ vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@@ -220,13 +224,13 @@ static int view_pan_invoke(bContext *C, wmOperator *op, const wmEvent *event)
View2D *v2d = vpd->v2d;
/* set initial settings */
- vpd->startx = vpd->lastx = event->x;
- vpd->starty = vpd->lasty = event->y;
+ vpd->startx = vpd->lastx = event->xy[0];
+ vpd->starty = vpd->lasty = event->xy[1];
vpd->invoke_event = event->type;
if (event->type == MOUSEPAN) {
- RNA_int_set(op->ptr, "deltax", event->prevx - event->x);
- RNA_int_set(op->ptr, "deltay", event->prevy - event->y);
+ RNA_int_set(op->ptr, "deltax", event->prev_xy[0] - event->xy[0]);
+ RNA_int_set(op->ptr, "deltay", event->prev_xy[1] - event->xy[1]);
view_pan_apply(C, op);
view_pan_exit(op);
@@ -262,16 +266,16 @@ static int view_pan_modal(bContext *C, wmOperator *op, const wmEvent *event)
switch (event->type) {
case MOUSEMOVE: {
/* calculate new delta transform, then store mouse-coordinates for next-time */
- RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->x));
- RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->y));
+ RNA_int_set(op->ptr, "deltax", (vpd->lastx - event->xy[0]));
+ RNA_int_set(op->ptr, "deltay", (vpd->lasty - event->xy[1]));
- vpd->lastx = event->x;
- vpd->lasty = event->y;
+ vpd->lastx = event->xy[0];
+ vpd->lasty = event->xy[1];
view_pan_apply(C, op);
break;
}
- /* XXX - Mode switching isn't implemented. See comments in 36818.
+ /* XXX: Mode switching isn't implemented. See comments in 36818.
* switch to zoom */
#if 0
case LEFTMOUSE:
@@ -358,6 +362,7 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event
View2DEdgePanData *vpd = op->customdata;
if (event->val == KM_RELEASE || event->type == EVT_ESCKEY) {
+ vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
return (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH);
}
@@ -371,6 +376,8 @@ static int view_edge_pan_modal(bContext *C, wmOperator *op, const wmEvent *event
static void view_edge_pan_cancel(bContext *UNUSED(C), wmOperator *op)
{
+ v2dViewPanData *vpd = op->customdata;
+ vpd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@@ -680,6 +687,8 @@ static void view_zoomdrag_init(bContext *C, wmOperator *op)
vzd->v2d = &vzd->region->v2d;
/* False by default. Interactive callbacks (ie invoke()) can set it to true. */
vzd->zoom_to_mouse_pos = false;
+
+ vzd->v2d->flag |= V2D_IS_NAVIGATING;
}
/* apply transform to view (i.e. adjust 'cur' rect) */
@@ -809,7 +818,8 @@ static void view_zoomstep_apply(bContext *C, wmOperator *op)
static void view_zoomstep_exit(wmOperator *op)
{
UI_view2d_zoom_cache_reset();
-
+ v2dViewZoomData *vzd = op->customdata;
+ vzd->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_SAFE_FREE(op->customdata);
}
@@ -1041,6 +1051,7 @@ static void view_zoomdrag_exit(bContext *C, wmOperator *op)
if (op->customdata) {
v2dViewZoomData *vzd = op->customdata;
+ vzd->v2d->flag &= ~V2D_IS_NAVIGATING;
if (vzd->timer) {
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), vzd->timer);
@@ -1086,8 +1097,8 @@ static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *even
}
if (ELEM(event->type, MOUSEZOOM, MOUSEPAN)) {
- vzd->lastx = event->prevx;
- vzd->lasty = event->prevy;
+ vzd->lastx = event->prev_xy[0];
+ vzd->lasty = event->prev_xy[1];
float facx, facy;
float zoomfac = 0.01f;
@@ -1145,8 +1156,8 @@ static int view_zoomdrag_invoke(bContext *C, wmOperator *op, const wmEvent *even
}
/* set initial settings */
- vzd->lastx = event->x;
- vzd->lasty = event->y;
+ vzd->lastx = event->xy[0];
+ vzd->lasty = event->xy[1];
RNA_float_set(op->ptr, "deltax", 0);
RNA_float_set(op->ptr, "deltay", 0);
@@ -1205,12 +1216,12 @@ static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event
/* x-axis transform */
dist = BLI_rcti_size_x(&v2d->mask) / 2.0f;
len_old[0] = zoomfac * fabsf(vzd->lastx - vzd->region->winrct.xmin - dist);
- len_new[0] = zoomfac * fabsf(event->x - vzd->region->winrct.xmin - dist);
+ len_new[0] = zoomfac * fabsf(event->xy[0] - vzd->region->winrct.xmin - dist);
/* y-axis transform */
dist = BLI_rcti_size_y(&v2d->mask) / 2.0f;
len_old[1] = zoomfac * fabsf(vzd->lasty - vzd->region->winrct.ymin - dist);
- len_new[1] = zoomfac * fabsf(event->y - vzd->region->winrct.ymin - dist);
+ len_new[1] = zoomfac * fabsf(event->xy[1] - vzd->region->winrct.ymin - dist);
/* Calculate distance */
if (v2d->keepzoom & V2D_KEEPASPECT) {
@@ -1226,8 +1237,8 @@ static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event
dy *= BLI_rctf_size_y(&v2d->cur);
}
else { /* USER_ZOOM_CONTINUE or USER_ZOOM_DOLLY */
- float facx = zoomfac * (event->x - vzd->lastx);
- float facy = zoomfac * (event->y - vzd->lasty);
+ float facx = zoomfac * (event->xy[0] - vzd->lastx);
+ float facy = zoomfac * (event->xy[1] - vzd->lasty);
/* Only respect user setting zoom axis if the view does not have any zoom restrictions
* any will be scaled uniformly */
@@ -1273,8 +1284,8 @@ static int view_zoomdrag_modal(bContext *C, wmOperator *op, const wmEvent *event
* to starting point to determine rate of change.
*/
if (U.viewzoom != USER_ZOOM_CONTINUE) { /* XXX store this setting as RNA prop? */
- vzd->lastx = event->x;
- vzd->lasty = event->y;
+ vzd->lastx = event->xy[0];
+ vzd->lasty = event->xy[1];
}
/* apply zooming */
@@ -1527,7 +1538,7 @@ struct SmoothView2DStore {
/**
* function to get a factor out of a rectangle
*
- * note: this doesn't always work as well as it might because the target size
+ * NOTE: this doesn't always work as well as it might because the target size
* may not be reached because of clamping the desired rect, we _could_
* attempt to clamp the rect before working out the zoom factor but its
* not really worthwhile for the few cases this happens.
@@ -1745,7 +1756,7 @@ typedef struct v2dScrollerMove {
* This is a CUT DOWN VERSION of the 'real' version, which is defined in view2d.c,
* as we only need focus bubble info.
*
- * \warning: The start of this struct must not change,
+ * \warning The start of this struct must not change,
* so that it stays in sync with the 'real' version.
* For now, we don't need to have a separate (internal) header for structs like this...
*/
@@ -1835,7 +1846,7 @@ static bool scroller_activate_poll(bContext *C)
wmEvent *event = win->eventstate;
/* check if mouse in scrollbars, if they're enabled */
- return (UI_view2d_mouse_in_scrollers(region, v2d, event->x, event->y) != 0);
+ return (UI_view2d_mouse_in_scrollers(region, v2d, event->xy) != 0);
}
/* initialize customdata for scroller manipulation operator */
@@ -1857,8 +1868,8 @@ static void scroller_activate_init(bContext *C,
vsm->scroller = in_scroller;
/* store mouse-coordinates, and convert mouse/screen coordinates to region coordinates */
- vsm->lastx = event->x;
- vsm->lasty = event->y;
+ vsm->lastx = event->xy[0];
+ vsm->lasty = event->xy[1];
/* 'zone' depends on where mouse is relative to bubble
* - zooming must be allowed on this axis, otherwise, default to pan
*/
@@ -1911,6 +1922,8 @@ static void scroller_activate_init(bContext *C,
vsm->scrollbar_orig = ((scrollers.vert_max + scrollers.vert_min) / 2) + region->winrct.ymin;
}
+ vsm->v2d->flag |= V2D_IS_NAVIGATING;
+
ED_region_tag_redraw_no_rebuild(region);
}
@@ -1921,6 +1934,7 @@ static void scroller_activate_exit(bContext *C, wmOperator *op)
v2dScrollerMove *vsm = op->customdata;
vsm->v2d->scroll_ui &= ~(V2D_SCROLL_H_ACTIVE | V2D_SCROLL_V_ACTIVE);
+ vsm->v2d->flag &= ~V2D_IS_NAVIGATING;
MEM_freeN(op->customdata);
op->customdata = NULL;
@@ -2010,11 +2024,11 @@ static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *e
switch (vsm->scroller) {
case 'h': /* horizontal scroller - so only horizontal movement
* ('cur' moves opposite to mouse) */
- vsm->delta = (float)(event->x - vsm->lastx);
+ vsm->delta = (float)(event->xy[0] - vsm->lastx);
break;
case 'v': /* vertical scroller - so only vertical movement
* ('cur' moves opposite to mouse) */
- vsm->delta = (float)(event->y - vsm->lasty);
+ vsm->delta = (float)(event->xy[1] - vsm->lasty);
break;
}
}
@@ -2023,18 +2037,18 @@ static int scroller_activate_modal(bContext *C, wmOperator *op, const wmEvent *e
switch (vsm->scroller) {
case 'h': /* horizontal scroller - so only horizontal movement
* ('cur' moves with mouse) */
- vsm->delta = (float)(vsm->lastx - event->x);
+ vsm->delta = (float)(vsm->lastx - event->xy[0]);
break;
case 'v': /* vertical scroller - so only vertical movement
* ('cur' moves with to mouse) */
- vsm->delta = (float)(vsm->lasty - event->y);
+ vsm->delta = (float)(vsm->lasty - event->xy[1]);
break;
}
}
/* store previous coordinates */
- vsm->lastx = event->x;
- vsm->lasty = event->y;
+ vsm->lastx = event->xy[0];
+ vsm->lasty = event->xy[1];
scroller_activate_apply(C, op);
break;
@@ -2076,7 +2090,7 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *
View2D *v2d = &region->v2d;
/* check if mouse in scrollbars, if they're enabled */
- const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->x, event->y);
+ const char in_scroller = UI_view2d_mouse_in_scrollers(region, v2d, event->xy);
/* if in a scroller, init customdata then set modal handler which will
* catch mouse-down to start doing useful stuff */
@@ -2090,11 +2104,11 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *
switch (vsm->scroller) {
case 'h': /* horizontal scroller - so only horizontal movement
* ('cur' moves opposite to mouse) */
- vsm->delta = (float)(event->x - vsm->scrollbar_orig);
+ vsm->delta = (float)(event->xy[0] - vsm->scrollbar_orig);
break;
case 'v': /* vertical scroller - so only vertical movement
* ('cur' moves opposite to mouse) */
- vsm->delta = (float)(event->y - vsm->scrollbar_orig);
+ vsm->delta = (float)(event->xy[1] - vsm->scrollbar_orig);
break;
}
scroller_activate_apply(C, op);
@@ -2132,7 +2146,7 @@ static int scroller_activate_invoke(bContext *C, wmOperator *op, const wmEvent *
scroller_activate_exit(C, op);
/* can't catch this event for ourselves, so let it go to someone else? */
- /* XXX note: if handlers use mask rect to clip input, input will fail for this case */
+ /* XXX NOTE: if handlers use mask rect to clip input, input will fail for this case. */
return OPERATOR_PASS_THROUGH;
}