diff options
author | Peter Kim <pk15950@gmail.com> | 2021-10-03 06:22:05 +0300 |
---|---|---|
committer | Peter Kim <pk15950@gmail.com> | 2021-10-03 06:22:05 +0300 |
commit | 6fc81d6bca6424a1e44305df7cdc3598e03b00ba (patch) | |
tree | a66f17c5378f2a68f4c5d8b09f56687c3d9bf888 /source/blender/editors/interface | |
parent | 85e1f28fcaafd137a546bf192777b00f96851e80 (diff) | |
parent | d3afe0c1265c9ebb53053de68f176b30f0132281 (diff) |
Merge branch 'master' into xr-controller-supportxr-controller-support
Diffstat (limited to 'source/blender/editors/interface')
-rw-r--r-- | source/blender/editors/interface/CMakeLists.txt | 3 | ||||
-rw-r--r-- | source/blender/editors/interface/interface.c | 59 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_dropboxes.cc | 66 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_handlers.c | 20 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_icons.c | 42 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_intern.h | 19 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_layout.c | 85 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_ops.c | 58 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_query.c | 13 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_view.cc | 133 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_widgets.c | 39 | ||||
-rw-r--r-- | source/blender/editors/interface/tree_view.cc | 381 | ||||
-rw-r--r-- | source/blender/editors/interface/view2d.c | 8 |
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 != ®ion->v2d) { |