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.txt3
-rw-r--r--source/blender/editors/interface/interface.c59
-rw-r--r--source/blender/editors/interface/interface_dropboxes.cc66
-rw-r--r--source/blender/editors/interface/interface_handlers.c20
-rw-r--r--source/blender/editors/interface/interface_icons.c42
-rw-r--r--source/blender/editors/interface/interface_intern.h19
-rw-r--r--source/blender/editors/interface/interface_layout.c85
-rw-r--r--source/blender/editors/interface/interface_ops.c58
-rw-r--r--source/blender/editors/interface/interface_query.c13
-rw-r--r--source/blender/editors/interface/interface_view.cc133
-rw-r--r--source/blender/editors/interface/interface_widgets.c39
-rw-r--r--source/blender/editors/interface/tree_view.cc381
-rw-r--r--source/blender/editors/interface/view2d.c8
13 files changed, 883 insertions, 43 deletions
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt
index 64c874e26a9..f27ff9694c2 100644
--- a/source/blender/editors/interface/CMakeLists.txt
+++ b/source/blender/editors/interface/CMakeLists.txt
@@ -42,6 +42,7 @@ set(SRC
interface_button_group.c
interface_context_menu.c
interface_draw.c
+ interface_dropboxes.cc
interface_eyedropper.c
interface_eyedropper_color.c
interface_eyedropper_colorband.c
@@ -73,8 +74,10 @@ set(SRC
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
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index fd75be5b847..c53bffca778 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -743,6 +743,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
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;
}
@@ -856,10 +865,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 */
@@ -2203,6 +2223,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;
@@ -3447,6 +3476,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);
}
@@ -3942,6 +3972,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";
@@ -4141,6 +4175,7 @@ static uiBut *ui_def_but(uiBlock *block,
UI_BTYPE_BUT_MENU,
UI_BTYPE_SEARCH_MENU,
UI_BTYPE_DATASETROW,
+ UI_BTYPE_TREEROW,
UI_BTYPE_POPOVER)) {
but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT);
}
@@ -6198,6 +6233,13 @@ void UI_but_drag_set_asset(uiBut *but,
asset_drag->id_type = ED_asset_handle_get_id_type(asset);
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) {
@@ -6878,6 +6920,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.
*/
diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc
new file mode 100644
index 00000000000..cb33e7f736e
--- /dev/null
+++ b/source/blender/editors/interface/interface_dropboxes.cc
@@ -0,0 +1,66 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edinterface
+ */
+
+#include "BKE_context.h"
+
+#include "DNA_space_types.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->x, event->y);
+ if (!hovered_tree_item) {
+ return false;
+ }
+
+ return UI_tree_view_item_can_drop(hovered_tree_item, drag);
+}
+
+static char *ui_tree_view_drop_tooltip(bContext *C,
+ wmDrag *drag,
+ const wmEvent *event,
+ wmDropBox *UNUSED(drop))
+{
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
+ region, event->x, event->y);
+ if (!hovered_tree_item) {
+ return nullptr;
+ }
+
+ return UI_tree_view_item_drop_tooltip(hovered_tree_item, C, drag, event);
+}
+
+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);
+}
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 77ae16d7cc7..aee66ec3a93 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -384,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;
@@ -1164,6 +1166,16 @@ 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.
*
@@ -2307,6 +2319,9 @@ static void ui_apply_but(
case UI_BTYPE_ROW:
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;
@@ -4194,6 +4209,8 @@ 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);
}
@@ -4737,7 +4754,7 @@ 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);
}
@@ -7966,6 +7983,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
case UI_BTYPE_CHECKBOX:
case UI_BTYPE_CHECKBOX_N:
case UI_BTYPE_ROW:
+ case UI_BTYPE_TREEROW:
case UI_BTYPE_DATASETROW:
retval = ui_do_but_TOG(C, but, data, event);
break;
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index f739830cfdb..c20129b4184 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"
@@ -309,7 +310,7 @@ static void vicon_keytype_draw_wrapper(
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);
@@ -480,6 +481,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.
@@ -995,6 +1025,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)
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index d61104f094e..8b45d9faae6 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -360,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;
@@ -488,6 +496,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];
@@ -1158,6 +1171,7 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region,
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 x, const int y);
typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata);
uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,
@@ -1274,6 +1288,11 @@ 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);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c
index 66c75c63050..64c16e57f56 100644
--- a/source/blender/editors/interface/interface_layout.c
+++ b/source/blender/editors/interface/interface_layout.c
@@ -288,35 +288,85 @@ static bool ui_layout_variable_size(uiLayout *layout)
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);
}
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) {
@@ -2857,23 +2907,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);
}
@@ -3133,8 +3183,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(
diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c
index dd10d942fc9..1fc07bce341 100644
--- a/source/blender/editors/interface/interface_ops.c
+++ b/source/blender/editors/interface/interface_ops.c
@@ -1381,16 +1381,7 @@ static int editsource_text_edit(bContext *C,
/* naughty!, find text area to set, not good behavior
* but since this is a developer 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 {
+ if (!ED_text_activate_in_screen(C, text)) {
BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2);
}
@@ -1927,6 +1918,51 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
+/** \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->x, win->eventstate->y);
+
+ 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->x, event->y);
+
+ 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 Operator & Keymap Registration
* \{ */
@@ -1953,6 +1989,8 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_list_start_filter);
+ WM_operatortype_append(UI_OT_tree_view_drop);
+
/* external */
WM_operatortype_append(UI_OT_eyedropper_color);
WM_operatortype_append(UI_OT_eyedropper_colorramp);
diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c
index 09429bb6df5..2f6bda3252d 100644
--- a/source/blender/editors/interface/interface_query.c
+++ b/source/blender/editors/interface/interface_query.c
@@ -69,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);
}
/**
@@ -462,6 +463,16 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut
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 x, const int y)
+{
+ return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_treerow, NULL);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc
new file mode 100644
index 00000000000..b199ce9562e
--- /dev/null
+++ b/source/blender/editors/interface/interface_view.cc
@@ -0,0 +1,133 @@
+/*
+ * 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 x,
+ const int y)
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, x, y);
+ 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 {};
+}
+
+uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+ const uiTreeViewHandle *new_view_handle)
+{
+ const AbstractTreeView &needle_view = reinterpret_cast<const AbstractTreeView &>(
+ *new_view_handle);
+
+ uiBlock *old_block = new_block->oldblock;
+ if (!old_block) {
+ return nullptr;
+ }
+
+ StringRef idname = ui_block_view_find_idname(*new_block, needle_view);
+ if (idname.is_empty()) {
+ return nullptr;
+ }
+
+ LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) {
+ if (old_view_link->idname == idname) {
+ return reinterpret_cast<uiTreeViewHandle *>(
+ get_view_from_link<AbstractTreeView>(*old_view_link));
+ }
+ }
+
+ return nullptr;
+}
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index 0dc7c2d3f9a..466deedf3bb 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -115,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'.
@@ -3679,10 +3680,9 @@ static void widget_progressbar(
widgetbase_draw(&wtb_bar, wcol);
}
-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);
@@ -3695,10 +3695,24 @@ 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(
@@ -4492,6 +4506,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;
@@ -4824,6 +4842,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;
@@ -5348,7 +5371,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
}
}
else {
- BLI_assert_msg(0, "Unknwon menu item separator type");
+ BLI_assert_msg(0, "Unknown menu item separator type");
}
}
}
diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc
new file mode 100644
index 00000000000..0ea15a2a5bb
--- /dev/null
+++ b/source/blender/editors/interface/tree_view.cc
@@ -0,0 +1,381 @@
+/*
+ * 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 "BLT_translation.h"
+
+#include "interface_intern.h"
+
+#include "UI_interface.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);
+}
+
+void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder)
+{
+ uiLayout *prev_layout = builder.current_layout();
+
+ uiLayoutColumn(prev_layout, true);
+
+ 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) {
+ return;
+ }
+
+ uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
+ &new_block, reinterpret_cast<uiTreeViewHandle *>(this));
+ if (!old_view_handle) {
+ return;
+ }
+
+ AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle);
+ update_children_from_old_recursive(*this, old_view);
+}
+
+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;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void AbstractTreeViewItem::on_activate()
+{
+ /* Do nothing by default. */
+}
+
+bool AbstractTreeViewItem::on_drop(const wmDrag & /*drag*/)
+{
+ /* Do nothing by default. */
+ return false;
+}
+
+bool AbstractTreeViewItem::can_drop(const wmDrag & /*drag*/) const
+{
+ return false;
+}
+
+std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/,
+ const wmDrag & /*drag*/,
+ const wmEvent & /*event*/) const
+{
+ return TIP_("Drop into/onto tree item");
+}
+
+void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old)
+{
+ is_open_ = old.is_open_;
+ is_active_ = old.is_active_;
+}
+
+bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const
+{
+ return label_ == other.label_;
+}
+
+const 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::set_active(bool value)
+{
+ if (value && !is_active()) {
+ /* Deactivate other items in the tree. */
+ get_tree_view().foreach_item([](auto &item) { item.set_active(false); });
+ on_activate();
+ }
+ is_active_ = value;
+}
+
+bool AbstractTreeViewItem::is_active() const
+{
+ return is_active_;
+}
+
+bool AbstractTreeViewItem::is_collapsed() const
+{
+ 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();
+}
+
+/* ---------------------------------------------------------------------- */
+
+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.build_layout_from_tree(TreeViewLayoutBuilder(block_));
+}
+
+/* ---------------------------------------------------------------------- */
+
+TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block)
+{
+}
+
+void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const
+{
+ uiLayout *prev_layout = current_layout();
+ uiLayout *row = uiLayoutRow(prev_layout, false);
+
+ item.build_row(*row);
+
+ 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_, ActivateFn activate_fn)
+ : icon(icon_), activate_fn_(activate_fn)
+{
+ label_ = label;
+}
+
+static void tree_row_click_fn(struct bContext *UNUSED(C), void *but_arg1, void *UNUSED(arg2))
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1;
+ AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>(
+ *tree_row_but->tree_item);
+
+ /* Let a click on an opened item activate it, a second click will close it then.
+ * TODO Should this be for asset catalogs only? */
+ if (tree_item.is_collapsed() || tree_item.is_active()) {
+ tree_item.toggle_collapsed();
+ }
+ tree_item.set_active();
+}
+
+void BasicTreeViewItem::build_row(uiLayout &row)
+{
+ uiBlock *block = uiLayoutGetBlock(&row);
+ tree_row_but_ = (uiButTreeRow *)uiDefIconTextBut(block,
+ UI_BTYPE_TREEROW,
+ 0,
+ /* TODO allow icon besides the chevron icon? */
+ get_draw_icon(),
+ label_.data(),
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ nullptr,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+
+ 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);
+ UI_but_treerow_indentation_set(&tree_row_but_->but, count_parents());
+}
+
+void BasicTreeViewItem::on_activate()
+{
+ if (activate_fn_) {
+ activate_fn_(*this);
+ }
+}
+
+BIFIconID BasicTreeViewItem::get_draw_icon() const
+{
+ if (icon) {
+ return icon;
+ }
+
+ if (is_collapsible()) {
+ return is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN;
+ }
+
+ return ICON_NONE;
+}
+
+uiBut *BasicTreeViewItem::button()
+{
+ return &tree_row_but_->but;
+}
+
+} // 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);
+ return a.matches(b);
+}
+
+bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const wmDrag *drag)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ return item.can_drop(*drag);
+}
+
+char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_,
+ const bContext *C,
+ const wmDrag *drag,
+ const wmEvent *event)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ return BLI_strdup(item.drop_tooltip(*C, *drag, *event).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_);
+
+ LISTBASE_FOREACH (const wmDrag *, drag, drags) {
+ if (item.can_drop(*drag)) {
+ return item.on_drop(*drag);
+ }
+ }
+
+ return false;
+}
diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c
index 23c8a0d35bf..0036a812a87 100644
--- a/source/blender/editors/interface/view2d.c
+++ b/source/blender/editors/interface/view2d.c
@@ -866,6 +866,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 +908,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) {