diff options
Diffstat (limited to 'source/blender/editors')
95 files changed, 3418 insertions, 1307 deletions
diff --git a/source/blender/editors/animation/anim_channels_edit.c b/source/blender/editors/animation/anim_channels_edit.c index afbd9b2c92d..69fabd004cc 100644 --- a/source/blender/editors/animation/anim_channels_edit.c +++ b/source/blender/editors/animation/anim_channels_edit.c @@ -51,6 +51,7 @@ #include "BKE_mask.h" #include "BKE_nla.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_build.h" @@ -2522,10 +2523,10 @@ static void ANIM_OT_channels_fcurves_enable(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } -/* ****************** Find / Set Filter Operator ******************** */ +/* ****************** Select Filter Textbox Operator ******************** */ /* XXX: make this generic? */ -static bool animchannels_find_poll(bContext *C) +static bool animchannels_select_filter_poll(bContext *C) { ScrArea *area = CTX_wm_area(C); @@ -2537,64 +2538,62 @@ static bool animchannels_find_poll(bContext *C) return ELEM(area->spacetype, SPACE_ACTION, SPACE_GRAPH, SPACE_NLA); } -/* find_invoke() - Get initial channels */ -static int animchannels_find_invoke(bContext *C, wmOperator *op, const wmEvent *event) +static int animchannels_select_filter_invoke(struct bContext *C, + struct wmOperator *op, + const struct wmEvent *UNUSED(event)) { - bAnimContext ac; + ScrArea *area = CTX_wm_area(C); + ARegion *region_ctx = CTX_wm_region(C); + ARegion *region_channels = BKE_area_find_region_type(area, RGN_TYPE_CHANNELS); - /* get editor data */ - if (ANIM_animdata_get_context(C, &ac) == 0) { - return OPERATOR_CANCELLED; + CTX_wm_region_set(C, region_channels); + + /* Show the channel region if it's hidden. This means that direct activation of the input field + * is impossible, as it may not exist yet. For that reason, the actual activation is deferred to + * the modal callback function; by the time it runs, the screen has been redrawn and the UI + * element is there to activate. */ + if (region_channels->flag & RGN_FLAG_HIDDEN) { + ED_region_toggle_hidden(C, region_channels); + ED_region_tag_redraw(region_channels); } - /* set initial filter text, and enable filter */ - RNA_string_set(op->ptr, "query", ac.ads->searchstr); + WM_event_add_modal_handler(C, op); - /* defer to popup */ - return WM_operator_props_popup(C, op, event); + CTX_wm_region_set(C, region_ctx); + return OPERATOR_RUNNING_MODAL; } -/* find_exec() - Called to set the value */ -static int animchannels_find_exec(bContext *C, wmOperator *op) +static int animchannels_select_filter_modal(bContext *C, + wmOperator *UNUSED(op), + const wmEvent *UNUSED(event)) { bAnimContext ac; - - /* get editor data */ if (ANIM_animdata_get_context(C, &ac) == 0) { return OPERATOR_CANCELLED; } - /* update filter text */ - RNA_string_get(op->ptr, "query", ac.ads->searchstr); - - /* redraw */ - WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL); + ARegion *region = CTX_wm_region(C); + if (UI_textbutton_activate_rna(C, region, ac.ads, "filter_text")) { + /* Redraw to make sure it shows the cursor after activating */ + WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, NULL); + } return OPERATOR_FINISHED; } -static void ANIM_OT_channels_find(wmOperatorType *ot) +static void ANIM_OT_channels_select_filter(wmOperatorType *ot) { /* identifiers */ - ot->name = "Find Channels"; - ot->idname = "ANIM_OT_channels_find"; - ot->description = "Filter the set of channels shown to only include those with matching names"; + ot->name = "Filter Channels"; + ot->idname = "ANIM_OT_channels_select_filter"; + ot->description = + "Start entering text which filters the set of channels shown to only include those with " + "matching names"; /* callbacks */ - ot->invoke = animchannels_find_invoke; - ot->exec = animchannels_find_exec; - ot->poll = animchannels_find_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - ot->prop = RNA_def_string(ot->srna, - "query", - "Query", - sizeof(((bDopeSheet *)NULL)->searchstr), - "", - "Text to search for in channel names"); + ot->invoke = animchannels_select_filter_invoke; + ot->modal = animchannels_select_filter_modal; + ot->poll = animchannels_select_filter_poll; } /* ********************** Select All Operator *********************** */ @@ -3563,7 +3562,7 @@ void ED_operatortypes_animchannels(void) WM_operatortype_append(ANIM_OT_channel_select_keys); WM_operatortype_append(ANIM_OT_channels_rename); - WM_operatortype_append(ANIM_OT_channels_find); + WM_operatortype_append(ANIM_OT_channels_select_filter); WM_operatortype_append(ANIM_OT_channels_setting_enable); WM_operatortype_append(ANIM_OT_channels_setting_disable); diff --git a/source/blender/editors/armature/armature_select.c b/source/blender/editors/armature/armature_select.c index bd799c00373..937385f9ffa 100644 --- a/source/blender/editors/armature/armature_select.c +++ b/source/blender/editors/armature/armature_select.c @@ -673,11 +673,7 @@ static EditBone *get_nearest_editbonepoint( } if (use_cycle) { - static int last_mval[2] = {-100, -100}; - if ((len_manhattan_v2v2_int(vc->mval, last_mval) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) == 0) { - use_cycle = false; - } - copy_v2_v2_int(last_mval, vc->mval); + use_cycle = !WM_cursor_test_motion_and_update(vc->mval); } const bool do_nearest = !(XRAY_ACTIVE(vc->v3d) || use_cycle); diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh index 8b8fc4d3574..8da8fc0d6c9 100644 --- a/source/blender/editors/asset/ED_asset_catalog.hh +++ b/source/blender/editors/asset/ED_asset_catalog.hh @@ -37,3 +37,6 @@ void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogI void ED_asset_catalog_rename(AssetLibrary *library, blender::bke::CatalogID catalog_id, blender::StringRefNull new_name); +void ED_asset_catalog_move(AssetLibrary *library, + blender::bke::CatalogID src_catalog_id, + blender::bke::CatalogID dst_parent_catalog_id); diff --git a/source/blender/editors/asset/ED_asset_mark_clear.h b/source/blender/editors/asset/ED_asset_mark_clear.h index bab1d1bf8a5..8e6a8e11d69 100644 --- a/source/blender/editors/asset/ED_asset_mark_clear.h +++ b/source/blender/editors/asset/ED_asset_mark_clear.h @@ -26,6 +26,7 @@ extern "C" { struct ID; struct bContext; +struct Main; /** * Mark the datablock as asset. @@ -52,6 +53,8 @@ void ED_asset_generate_preview(const struct bContext *C, struct ID *id); * \return whether the asset metadata was actually removed; false when the ID was not an asset. */ bool ED_asset_clear_id(struct ID *id); +void ED_assets_pre_save(struct Main *bmain); + bool ED_asset_can_mark_single_from_context(const struct bContext *C); #ifdef __cplusplus diff --git a/source/blender/editors/asset/ED_asset_type.h b/source/blender/editors/asset/ED_asset_type.h index 5629ae189c0..36cbb4591e9 100644 --- a/source/blender/editors/asset/ED_asset_type.h +++ b/source/blender/editors/asset/ED_asset_type.h @@ -29,7 +29,8 @@ extern "C" { struct ID; bool ED_asset_type_id_is_non_experimental(const struct ID *id); -#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS (FILTER_ID_MA | FILTER_ID_AC | FILTER_ID_WO) +#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS \ + (FILTER_ID_MA | FILTER_ID_OB | FILTER_ID_AC | FILTER_ID_WO) /** * Check if the asset type for \a id (which doesn't need to be an asset right now) can be an asset, @@ -51,7 +52,7 @@ int64_t ED_asset_types_supported_as_filter_flags(void); * strings with this (not all UI code supports dynamic strings nicely). * Should start with a consonant, so usages can prefix it with "a" (not "an"). */ -#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING "Material, Pose Action, or World" +#define ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING "Material, Object, Pose Action, or World" #ifdef __cplusplus } diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc index dae960cbb0a..8e1e5be2e47 100644 --- a/source/blender/editors/asset/intern/asset_catalog.cc +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -107,17 +107,44 @@ void ED_asset_catalog_rename(::AssetLibrary *library, AssetCatalog *catalog = catalog_service->find_catalog(catalog_id); - AssetCatalogPath new_path = catalog->path.parent(); - new_path = new_path / StringRef(new_name); + const AssetCatalogPath new_path = catalog->path.parent() / StringRef(new_name); + const AssetCatalogPath clean_new_path = new_path.cleanup(); - if (new_path == catalog->path) { + if (new_path == catalog->path || clean_new_path == catalog->path) { /* Nothing changed, so don't bother renaming for nothing. */ return; } catalog_service->undo_push(); catalog_service->tag_has_unsaved_changes(catalog); - catalog_service->update_catalog_path(catalog_id, new_path); + catalog_service->update_catalog_path(catalog_id, clean_new_path); + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); +} + +void ED_asset_catalog_move(::AssetLibrary *library, + const CatalogID src_catalog_id, + const CatalogID dst_parent_catalog_id) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + BLI_assert_unreachable(); + return; + } + + AssetCatalog *src_catalog = catalog_service->find_catalog(src_catalog_id); + AssetCatalog *dst_catalog = catalog_service->find_catalog(dst_parent_catalog_id); + + const AssetCatalogPath new_path = dst_catalog->path / StringRef(src_catalog->path.name()); + const AssetCatalogPath clean_new_path = new_path.cleanup(); + + if (new_path == src_catalog->path || clean_new_path == src_catalog->path) { + /* Nothing changed, so don't bother renaming for nothing. */ + return; + } + + catalog_service->undo_push(); + catalog_service->tag_has_unsaved_changes(src_catalog); + catalog_service->update_catalog_path(src_catalog_id, clean_new_path); WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); } diff --git a/source/blender/editors/asset/intern/asset_mark_clear.cc b/source/blender/editors/asset/intern/asset_mark_clear.cc index 4be7376a1c3..eb254dcd28b 100644 --- a/source/blender/editors/asset/intern/asset_mark_clear.cc +++ b/source/blender/editors/asset/intern/asset_mark_clear.cc @@ -25,7 +25,9 @@ #include "BKE_asset.h" #include "BKE_context.h" +#include "BKE_idtype.h" #include "BKE_lib_id.h" +#include "BKE_main.h" #include "BLO_readfile.h" @@ -52,7 +54,9 @@ bool ED_asset_mark_id(ID *id) id_fake_user_set(id); + const IDTypeInfo *id_type_info = BKE_idtype_get_info_from_id(id); id->asset_data = BKE_asset_metadata_create(); + id->asset_data->local_type_info = id_type_info->asset_type_info; /* Important for asset storage to update properly! */ ED_assetlist_storage_tag_main_data_dirty(); @@ -79,6 +83,21 @@ bool ED_asset_clear_id(ID *id) return true; } +void ED_assets_pre_save(struct Main *bmain) +{ + ID *id; + FOREACH_MAIN_ID_BEGIN (bmain, id) { + if (!id->asset_data || !id->asset_data->local_type_info) { + continue; + } + + if (id->asset_data->local_type_info->pre_save_fn) { + id->asset_data->local_type_info->pre_save_fn(id, id->asset_data); + } + } + FOREACH_MAIN_ID_END; +} + bool ED_asset_can_mark_single_from_context(const bContext *C) { /* Context needs a "id" pointer to be set for #ASSET_OT_mark()/#ASSET_OT_clear() to use. */ diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index e2ae3b3893b..d2fd8ab88a4 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -427,7 +427,12 @@ static int asset_catalog_new_exec(bContext *C, wmOperator *op) struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); char *parent_path = RNA_string_get_alloc(op->ptr, "parent_path", nullptr, 0, nullptr); - ED_asset_catalog_add(asset_library, "Catalog", parent_path); + blender::bke::AssetCatalog *new_catalog = ED_asset_catalog_add( + asset_library, "Catalog", parent_path); + + if (sfile) { + ED_fileselect_activate_asset_catalog(sfile, new_catalog->catalog_id); + } MEM_freeN(parent_path); @@ -554,7 +559,7 @@ static bool asset_catalog_redo_poll(bContext *C) static void ASSET_OT_catalog_redo(struct wmOperatorType *ot) { /* identifiers */ - ot->name = "redo Catalog Edits"; + ot->name = "Redo Catalog Edits"; ot->description = "Redo the last undone edit to the asset catalogs"; ot->idname = "ASSET_OT_catalog_redo"; diff --git a/source/blender/editors/asset/intern/asset_type.cc b/source/blender/editors/asset/intern/asset_type.cc index cdff538a712..028c0cb9ffc 100644 --- a/source/blender/editors/asset/intern/asset_type.cc +++ b/source/blender/editors/asset/intern/asset_type.cc @@ -30,7 +30,7 @@ bool ED_asset_type_id_is_non_experimental(const ID *id) { /* Remember to update #ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_UI_STRING and * #ED_ASSET_TYPE_IDS_NON_EXPERIMENTAL_FLAGS() with this! */ - return ELEM(GS(id->name), ID_MA, ID_AC, ID_WO); + return ELEM(GS(id->name), ID_MA, ID_OB, ID_AC, ID_WO); } bool ED_asset_type_is_supported(const ID *id) diff --git a/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c index 07117c0153b..aed58e31798 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c @@ -220,7 +220,7 @@ static void cage3d_draw_circle_wire(const float r[3], immUniform2fv("viewportSize", &viewport[2]); immUniform1f("lineWidth", line_width * U.pixelsize); - imm_draw_cube_wire_3d(pos, (float[3]){0}, r); + imm_draw_cube_wire_3d(pos, (const float[3]){0}, r); #if 0 if (transform_flag & ED_GIZMO_CAGE2D_XFORM_FLAG_TRANSLATE) { diff --git a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c index 33532bd0549..93ee6ec2d81 100644 --- a/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c +++ b/source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c @@ -225,8 +225,10 @@ static void snap_gizmo_setup(wmGizmo *gz) gz->flag |= WM_GIZMO_NO_TOOLTIP; SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz; snap_gizmo->snap_state = ED_view3d_cursor_snap_active(); - snap_gizmo->snap_state->draw_point = true; - snap_gizmo->snap_state->draw_plane = false; + if (snap_gizmo->snap_state) { + snap_gizmo->snap_state->draw_point = true; + snap_gizmo->snap_state->draw_plane = false; + } rgba_float_to_uchar(snap_gizmo->snap_state->color_point, gz->color); } @@ -284,7 +286,9 @@ static int snap_gizmo_invoke(bContext *UNUSED(C), static void snap_gizmo_free(wmGizmo *gz) { SnapGizmo3D *snap_gizmo = (SnapGizmo3D *)gz; - ED_view3d_cursor_snap_deactive(snap_gizmo->snap_state); + if (snap_gizmo->snap_state) { + ED_view3d_cursor_snap_deactive(snap_gizmo->snap_state); + } } static void GIZMO_GT_snap_3d(wmGizmoType *gzt) diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h index 3beabaf2d1d..68b6e44371c 100644 --- a/source/blender/editors/include/ED_fileselect.h +++ b/source/blender/editors/include/ED_fileselect.h @@ -23,6 +23,8 @@ #pragma once +#include "DNA_uuid_types.h" + #ifdef __cplusplus extern "C" { #endif @@ -145,7 +147,9 @@ bool ED_fileselect_is_asset_browser(const struct SpaceFile *sfile); struct AssetLibrary *ED_fileselect_active_asset_library_get(const struct SpaceFile *sfile); struct ID *ED_fileselect_active_asset_get(const struct SpaceFile *sfile); -/* Activate the file that corresponds to the given ID. +void ED_fileselect_activate_asset_catalog(const struct SpaceFile *sfile, bUUID catalog_id); + +/* Activate and select the file that corresponds to the given ID. * Pass deferred=true to wait for the next refresh before activating. */ void ED_fileselect_activate_by_id(struct SpaceFile *sfile, struct ID *asset_id, diff --git a/source/blender/editors/include/ED_node.h b/source/blender/editors/include/ED_node.h index 1d51a3e77cf..e68617f7867 100644 --- a/source/blender/editors/include/ED_node.h +++ b/source/blender/editors/include/ED_node.h @@ -49,7 +49,7 @@ typedef enum { NODE_RIGHT = 8, } NodeBorder; -#define NODE_GRID_STEPS 5 +#define NODE_GRID_STEP_SIZE 10 #define NODE_EDGE_PAN_INSIDE_PAD 2 #define NODE_EDGE_PAN_OUTSIDE_PAD 0 /* Disable clamping for node panning, use whole screen. */ #define NODE_EDGE_PAN_SPEED_RAMP 1 @@ -64,7 +64,6 @@ void ED_node_cursor_location_set(struct SpaceNode *snode, const float value[2]); int ED_node_tree_path_length(struct SpaceNode *snode); void ED_node_tree_path_get(struct SpaceNode *snode, char *value); -void ED_node_tree_path_get_fixedbuf(struct SpaceNode *snode, char *value, int max_length); void ED_node_tree_start(struct SpaceNode *snode, struct bNodeTree *ntree, diff --git a/source/blender/editors/include/ED_object.h b/source/blender/editors/include/ED_object.h index 083d167c573..2a557f1abd3 100644 --- a/source/blender/editors/include/ED_object.h +++ b/source/blender/editors/include/ED_object.h @@ -122,6 +122,8 @@ void ED_object_xform_skip_child_container_item_ensure(struct XFormObjectSkipChil struct Object *ob_parent_recurse, int mode); +void ED_object_xform_array_m4(struct Object **objects, uint objects_len, const float matrix[4][4]); + /* object_ops.c */ void ED_operatortypes_object(void); void ED_operatormacros_object(void); @@ -202,7 +204,7 @@ void ED_object_parent(struct Object *ob, const char *substr); char *ED_object_ot_drop_named_material_tooltip(struct bContext *C, struct PointerRNA *properties, - const struct wmEvent *event); + const int mval[2]); /* bitflags for enter/exit editmode */ enum { diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index b90c7f27c57..08b6c02a8d0 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -317,6 +317,7 @@ bool ED_operator_regionactive(struct bContext *C); bool ED_operator_scene(struct bContext *C); bool ED_operator_scene_editable(struct bContext *C); bool ED_operator_objectmode(struct bContext *C); +bool ED_operator_objectmode_poll_msg(struct bContext *C); bool ED_operator_view3d_active(struct bContext *C); bool ED_operator_region_view3d_active(struct bContext *C); diff --git a/source/blender/editors/include/ED_transform_snap_object_context.h b/source/blender/editors/include/ED_transform_snap_object_context.h index 7002db163b6..62d1dfbf0b1 100644 --- a/source/blender/editors/include/ED_transform_snap_object_context.h +++ b/source/blender/editors/include/ED_transform_snap_object_context.h @@ -44,6 +44,7 @@ typedef enum { SNAP_NOT_SELECTED = 1, SNAP_NOT_ACTIVE = 2, SNAP_ONLY_ACTIVE = 3, + SNAP_SELECTABLE = 4, } eSnapSelect; typedef enum { diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 67c470a005f..6d20044d8cf 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -276,12 +276,15 @@ typedef struct V3DSnapCursorState { eV3DPlaceOrient plane_orient; uchar color_line[4]; uchar color_point[4]; + uchar color_box[4]; float *prevpoint; + float box_dimensions[3]; short snap_elem_force; /* If zero, use scene settings. */ short plane_axis; bool use_plane_axis_auto; bool draw_point; bool draw_plane; + bool draw_box; } V3DSnapCursorState; void ED_view3d_cursor_snap_state_default_set(V3DSnapCursorState *state); @@ -293,7 +296,6 @@ V3DSnapCursorData *ED_view3d_cursor_snap_data_get(V3DSnapCursorState *state, const struct bContext *C, const int x, const int y); - struct SnapObjectContext *ED_view3d_cursor_snap_context_ensure(struct Scene *scene); void ED_view3d_cursor_snap_draw_util(struct RegionView3D *rv3d, const float loc_prev[3], @@ -302,7 +304,6 @@ void ED_view3d_cursor_snap_draw_util(struct RegionView3D *rv3d, const uchar color_line[4], const uchar color_point[4], const short snap_elem_type); -void ED_view3d_cursor_snap_exit(void); /* view3d_iterators.c */ diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h index 67d034f4ab6..725c9921d13 100644 --- a/source/blender/editors/include/UI_interface.h +++ b/source/blender/editors/include/UI_interface.h @@ -37,6 +37,7 @@ extern "C" { struct ARegion; struct AssetFilterSettings; struct AssetHandle; +struct AssetMetaData; struct AutoComplete; struct EnumPropertyItem; struct FileDirEntry; @@ -246,7 +247,7 @@ enum { #define UI_DEFAULT_TEXT_POINTS 11 /* Larger size used for title text. */ -#define UI_DEFAULT_TITLE_POINTS 12 +#define UI_DEFAULT_TITLE_POINTS 11 #define UI_PANEL_WIDTH 340 #define UI_COMPACT_PANEL_WIDTH 160 @@ -423,10 +424,6 @@ typedef enum eButGradientType { * Functions to draw various shapes, taking theme settings into account. * Used for code that draws its own UI style elements. */ -void UI_draw_anti_tria( - float x1, float y1, float x2, float y2, float x3, float y3, const float color[4]); -void UI_draw_anti_fan(float tri_array[][2], unsigned int length, const float color[4]); - void UI_draw_roundbox_corner_set(int type); void UI_draw_roundbox_aa(const struct rctf *rect, bool filled, float rad, const float color[4]); void UI_draw_roundbox_4fv(const struct rctf *rect, bool filled, float rad, const float col[4]); @@ -437,12 +434,6 @@ void UI_draw_roundbox_3ub_alpha(const struct rctf *rect, unsigned char alpha); void UI_draw_roundbox_3fv_alpha( const struct rctf *rect, bool filled, float rad, const float col[3], float alpha); -void UI_draw_roundbox_shade_x(const struct rctf *rect, - bool filled, - float rad, - float shadetop, - float shadedown, - const float col[4]); void UI_draw_roundbox_4fv_ex(const struct rctf *rect, const float inner1[4], const float inner2[4], @@ -785,6 +776,7 @@ void UI_but_drag_set_id(uiBut *but, struct ID *id); void UI_but_drag_set_asset(uiBut *but, const struct AssetHandle *asset, const char *path, + struct AssetMetaData *metadata, int import_type, /* eFileAssetImportType */ int icon, struct ImBuf *imb, @@ -796,7 +788,8 @@ void UI_but_drag_set_value(uiBut *but); void UI_but_drag_set_image( uiBut *but, const char *path, int icon, struct ImBuf *imb, float scale, const bool use_free); -bool UI_but_active_drop_name(struct bContext *C); +uiBut *UI_but_active_drop_name_button(const struct bContext *C); +bool UI_but_active_drop_name(const struct bContext *C); bool UI_but_active_drop_color(struct bContext *C); void UI_but_flag_enable(uiBut *but, int flag); @@ -2687,7 +2680,12 @@ void UI_fontstyle_draw_simple_backdrop(const struct uiFontStyle *fs, const float col_fg[4], const float col_bg[4]); -int UI_fontstyle_string_width(const struct uiFontStyle *fs, const char *str); +int UI_fontstyle_string_width(const struct uiFontStyle *fs, + const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2); +int UI_fontstyle_string_width_with_block_aspect(const struct uiFontStyle *fs, + const char *str, + const float aspect) ATTR_WARN_UNUSED_RESULT + ATTR_NONNULL(1, 2); int UI_fontstyle_height_max(const struct uiFontStyle *fs); void UI_draw_icon_tri(float x, float y, char dir, const float[4]); @@ -2780,12 +2778,12 @@ void UI_interface_tag_script_reload(void); bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item); bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a, const uiTreeViewItemHandle *b); -bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const struct wmDrag *drag); +bool UI_tree_view_item_drag_start(struct bContext *C, uiTreeViewItemHandle *item_); +bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, + const struct wmDrag *drag, + const char **r_disabled_hint); +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, const struct wmDrag *drag); bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const struct ListBase *drags); -char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item, - const struct bContext *C, - const struct wmDrag *drag, - const struct wmEvent *event); bool UI_tree_view_item_can_rename(const uiTreeViewItemHandle *item_handle); void UI_tree_view_item_begin_rename(uiTreeViewItemHandle *item_handle); diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh index 5edccfa8c88..b14ee6c4a59 100644 --- a/source/blender/editors/include/UI_interface.hh +++ b/source/blender/editors/include/UI_interface.hh @@ -23,15 +23,40 @@ #include <memory> #include "BLI_string_ref.hh" +#include "BLI_vector.hh" + +#include "UI_resources.h" namespace blender::nodes::geometry_nodes_eval_log { struct GeometryAttributeInfo; } struct uiBlock; +struct StructRNA; +struct uiSearchItems; + namespace blender::ui { + class AbstractTreeView; +/** + * An item in a breadcrumb-like context. Currently this struct is very simple, but more + * could be added to it in the future, to support interactivity or tooltips, for example. + */ +struct ContextPathItem { + /* Text to display in the UI. */ + std::string name; + /* #BIFIconID */ + int icon; +}; + +void context_path_add_generic(Vector<ContextPathItem> &path, + StructRNA &rna_type, + void *ptr, + const BIFIconID icon_override = ICON_NONE); + +void template_breadcrumbs(uiLayout &layout, Span<ContextPathItem> context_path); + void attribute_search_add_items( StringRefNull str, const bool is_output, diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh index b1ec22c57a6..0d18eedeac9 100644 --- a/source/blender/editors/include/UI_tree_view.hh +++ b/source/blender/editors/include/UI_tree_view.hh @@ -48,6 +48,8 @@ namespace blender::ui { class AbstractTreeView; class AbstractTreeViewItem; +class AbstractTreeViewItemDropController; +class AbstractTreeViewItemDragController; /* ---------------------------------------------------------------------- */ /** \name Tree-View Item Container @@ -242,17 +244,7 @@ class AbstractTreeViewItem : public TreeViewItemContainer { * arguments for checking if the item is currently in an active state. */ virtual void is_active(IsActiveFn is_active_fn); - virtual bool on_drop(const wmDrag &drag); - virtual bool can_drop(const wmDrag &drag) const; - /** - * Custom text to display when dragging over a tree item. Should explain what happens when - * dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop() - * returns true, so the implementing override doesn't have to check that again. - * The returned value must be a translated string. - */ - virtual std::string drop_tooltip(const bContext &C, - const wmDrag &drag, - const wmEvent &event) const; + /** * Queries if the tree-view item supports renaming in principle. Renaming may still fail, e.g. if * another item is already being renamed. @@ -282,6 +274,20 @@ class AbstractTreeViewItem : public TreeViewItemContainer { */ virtual bool matches(const AbstractTreeViewItem &other) const; + /** + * If an item wants to support being dragged, it has to return a drag controller here. + * That is an object implementing #AbstractTreeViewItemDragController. + */ + virtual std::unique_ptr<AbstractTreeViewItemDragController> create_drag_controller() const; + /** + * If an item wants to support dropping data into it, it has to return a drop controller here. + * That is an object implementing #AbstractTreeViewItemDropController. + * + * \note This drop controller may be requested for each event. The tree-view doesn't keep a drop + * controller around currently. So it can not contain persistent state. + */ + virtual std::unique_ptr<AbstractTreeViewItemDropController> create_drop_controller() const; + void begin_renaming(); void end_renaming(); @@ -344,6 +350,62 @@ class AbstractTreeViewItem : public TreeViewItemContainer { /** \} */ /* ---------------------------------------------------------------------- */ +/** \name Drag 'n Drop + * \{ */ + +/** + * Class to enable dragging a tree-item. An item can return a drop controller for itself via a + * custom implementation of #AbstractTreeViewItem::create_drag_controller(). + */ +class AbstractTreeViewItemDragController { + public: + virtual ~AbstractTreeViewItemDragController() = default; + + virtual int get_drag_type() const = 0; + virtual void *create_drag_data() const = 0; +}; + +/** + * Class to customize the drop behavior of a tree-item, plus the behavior when dragging over this + * item. An item can return a drop controller for itself via a custom implementation of + * #AbstractTreeViewItem::create_drop_controller(). + */ +class AbstractTreeViewItemDropController { + protected: + AbstractTreeView &tree_view_; + + public: + AbstractTreeViewItemDropController(AbstractTreeView &tree_view); + virtual ~AbstractTreeViewItemDropController() = default; + + /** + * Check if the data dragged with \a drag can be dropped on the item this controller is for. + * \param r_disabled_hint: Return a static string to display to the user, explaining why dropping + * isn't possible on this item. Shouldn't be done too aggressively, e.g. + * don't set this if the drag-type can't be dropped here; only if it can + * but there's another reason it can't be dropped. + * Can assume this is a non-null pointer. + */ + virtual bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const = 0; + /** + * Custom text to display when dragging over a tree item. Should explain what happens when + * dropping the data onto this item. Will only be used if #AbstractTreeViewItem::can_drop() + * returns true, so the implementing override doesn't have to check that again. + * The returned value must be a translated string. + */ + virtual std::string drop_tooltip(const wmDrag &drag) const = 0; + /** + * Execute the logic to apply a drop of the data dragged with \a drag onto/into the item this + * controller is for. + */ + virtual bool on_drop(const wmDrag &drag) = 0; + + template<class TreeViewType> inline TreeViewType &tree_view() const; +}; + +/** \} */ + +/* ---------------------------------------------------------------------- */ /** \name Predefined Tree-View Item Types * * Common, Basic Tree-View Item Types. @@ -357,9 +419,10 @@ class BasicTreeViewItem : public AbstractTreeViewItem { using ActivateFn = std::function<void(BasicTreeViewItem &new_active)>; BIFIconID icon; - BasicTreeViewItem(StringRef label, BIFIconID icon = ICON_NONE); + explicit BasicTreeViewItem(StringRef label, BIFIconID icon = ICON_NONE); void build_row(uiLayout &row) override; + void add_label(uiLayout &layout, StringRefNull label_override = ""); void on_activate(ActivateFn fn); protected: @@ -390,4 +453,11 @@ inline ItemT &TreeViewItemContainer::add_tree_item(Args &&...args) add_tree_item(std::make_unique<ItemT>(std::forward<Args>(args)...))); } +template<class TreeViewType> TreeViewType &AbstractTreeViewItemDropController::tree_view() const +{ + static_assert(std::is_base_of<AbstractTreeView, TreeViewType>::value, + "Type must derive from and implement the AbstractTreeView interface"); + return static_cast<TreeViewType &>(tree_view_); +} + } // namespace blender::ui diff --git a/source/blender/editors/include/UI_view2d.h b/source/blender/editors/include/UI_view2d.h index 13895879f01..122e5a7d839 100644 --- a/source/blender/editors/include/UI_view2d.h +++ b/source/blender/editors/include/UI_view2d.h @@ -147,6 +147,10 @@ void UI_view2d_view_restore(const struct bContext *C); /* grid drawing */ void UI_view2d_multi_grid_draw( const struct View2D *v2d, int colorid, float step, int level_size, int totlevels); +void UI_view2d_dot_grid_draw(const struct View2D *v2d, + int grid_color_id, + float step, + int grid_levels); void UI_view2d_draw_lines_y__values(const struct View2D *v2d); void UI_view2d_draw_lines_x__values(const struct View2D *v2d); @@ -311,6 +315,9 @@ typedef struct View2DEdgePanData { /** View2d we're operating in. */ struct View2D *v2d; + /** Panning should only start once being in the inside rect once (e.g. adding nodes can happen + * outside). */ + bool enabled; /** Inside distance in UI units from the edge of the region within which to start panning. */ float inside_pad; /** Outside distance in UI units from the edge of the region at which to stop panning. */ diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt index b2659f5ed52..84172c7efce 100644 --- a/source/blender/editors/interface/CMakeLists.txt +++ b/source/blender/editors/interface/CMakeLists.txt @@ -43,6 +43,7 @@ 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 diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c index 5068969946a..62c84ed38ff 100644 --- a/source/blender/editors/interface/interface.c +++ b/source/blender/editors/interface/interface.c @@ -6231,12 +6231,13 @@ void UI_but_drag_set_id(uiBut *but, ID *id) void UI_but_drag_set_asset(uiBut *but, const AssetHandle *asset, const char *path, + struct AssetMetaData *metadata, int import_type, int icon, struct ImBuf *imb, float scale) { - wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, path, import_type); + wmDragAsset *asset_drag = WM_drag_create_asset_data(asset, metadata, path, import_type); /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the * #wmDropBox. 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 6cb0fcd499c..e45a5fc61c6 100644 --- a/source/blender/editors/interface/interface_draw.c +++ b/source/blender/editors/interface/interface_draw.c @@ -178,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; diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc index 62250a34cf4..81a1354cbe7 100644 --- a/source/blender/editors/interface/interface_dropboxes.cc +++ b/source/blender/editors/interface/interface_dropboxes.cc @@ -22,6 +22,10 @@ #include "DNA_space_types.h" +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" + #include "WM_api.h" #include "UI_interface.h" @@ -35,24 +39,43 @@ static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *eve return false; } - return UI_tree_view_item_can_drop(hovered_tree_item, drag); + if (drag->free_disabled_info) { + MEM_SAFE_FREE(drag->disabled_info); + } + + drag->free_disabled_info = false; + return UI_tree_view_item_can_drop(hovered_tree_item, drag, &drag->disabled_info); } static char *ui_tree_view_drop_tooltip(bContext *C, wmDrag *drag, - const wmEvent *event, + 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, - event->xy); + 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, C, drag, event); + 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); @@ -63,4 +86,10 @@ void ED_dropboxes_ui() 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_handlers.c b/source/blender/editors/interface/interface_handlers.c index 52ab13e5cd0..51ebe5399b3 100644 --- a/source/blender/editors/interface/interface_handlers.c +++ b/source/blender/editors/interface/interface_handlers.c @@ -2145,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, @@ -2437,39 +2443,6 @@ static void ui_apply_but( /** \} */ /* -------------------------------------------------------------------- */ -/** \name Button Drop Event - * \{ */ - -/* only call if event type is EVT_DROP */ -static void ui_but_drop(bContext *C, const wmEvent *event, uiBut *but, uiHandleButtonData *data) -{ - ListBase *drags = event->customdata; /* drop event type has listbase customdata by default */ - - LISTBASE_FOREACH (wmDrag *, wmd, drags) { - /* TODO: asset dropping. */ - if (wmd->type == WM_DRAG_ID) { - /* align these types with UI_but_active_drop_name */ - if (ELEM(but->type, UI_BTYPE_TEXT, UI_BTYPE_SEARCH_MENU)) { - ID *id = WM_drag_get_local_ID(wmd, 0); - - button_activate_state(C, but, BUTTON_STATE_TEXT_EDITING); - - ui_textedit_string_set(but, data, id->name + 2); - - if (ELEM(but->type, UI_BTYPE_SEARCH_MENU)) { - but->changed = true; - ui_searchbox_update(C, data->searchbox, but, true); - } - - button_activate_state(C, but, BUTTON_STATE_EXIT); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ /** \name Button Copy & Paste * \{ */ @@ -2666,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) @@ -3018,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) { @@ -4821,19 +4806,33 @@ static int ui_do_but_TREEROW(bContext *C, if (data->state == BUTTON_STATE_HIGHLIGHT) { if (event->type == LEFTMOUSE) { - if (event->val == KM_CLICK) { - button_activate_state(C, but, BUTTON_STATE_EXIT); - return WM_UI_HANDLER_BREAK; - } - if (event->val == KM_DBL_CLICK) { - data->cancel = true; + 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; - UI_tree_view_item_begin_rename(tree_row_but->tree_item); - ED_region_tag_redraw(CTX_wm_region(C)); - return WM_UI_HANDLER_BREAK; + 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; } @@ -7929,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) && @@ -7978,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) && @@ -10648,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, (float)event->xy[0], (float)event->xy[1])) { + 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; } @@ -10779,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; } @@ -11695,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) diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h index 0826157b5e6..f766bb1465f 100644 --- a/source/blender/editors/interface/interface_intern.h +++ b/source/blender/editors/interface/interface_intern.h @@ -680,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); @@ -1031,7 +1032,6 @@ 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_popover_back(struct ARegion *region, struct uiStyle *style, diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c index e54b261facd..25ba0e13487 100644 --- a/source/blender/editors/interface/interface_layout.c +++ b/source/blender/editors/interface/interface_layout.c @@ -351,12 +351,16 @@ static int ui_text_icon_width_ex(uiLayout *layout, if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) { layout->item.flag |= UI_ITEM_FIXED_SIZE; } - const uiFontStyle *fstyle = UI_FSTYLE_WIDGET; + float margin = pad_factor->text; if (icon) { 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; } diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c index 1a1d52b0425..c962a1107ae 100644 --- a/source/blender/editors/interface/interface_ops.c +++ b/source/blender/editors/interface/interface_ops.c @@ -1864,6 +1864,39 @@ 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 * \{ */ @@ -2025,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); diff --git a/source/blender/editors/interface/interface_panel.c b/source/blender/editors/interface/interface_panel.c index a22351eea7e..072362492d8 100644 --- a/source/blender/editors/interface/interface_panel.c +++ b/source/blender/editors/interface/interface_panel.c @@ -1550,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); diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c index 6b1ff92a855..92a9f14c77d 100644 --- a/source/blender/editors/interface/interface_style.c +++ b/source/blender/editors/interface/interface_style.c @@ -376,6 +376,37 @@ int UI_fontstyle_string_width(const uiFontStyle *fs, const char *str) return (int)BLF_width(fs->uifont_id, str, BLF_DRAW_STR_DUMMY_MAX); } +/** + * 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; + } + + int width = UI_fontstyle_string_width(fs, str); + + if (aspect != 1.0f) { + /* While in most cases rounding up isn't important, it can make a difference + * with small fonts (3px or less), zooming out in the node-editor for e.g. */ + width = (int)ceilf(width * aspect); + } + return width; +} + int UI_fontstyle_height_max(const uiFontStyle *fs) { UI_fontstyle_set(fs); diff --git a/source/blender/editors/interface/interface_template_asset_view.cc b/source/blender/editors/interface/interface_template_asset_view.cc index f27b37a27de..d3ce7ebc3db 100644 --- a/source/blender/editors/interface/interface_template_asset_view.cc +++ b/source/blender/editors/interface/interface_template_asset_view.cc @@ -70,6 +70,7 @@ static void asset_view_item_but_drag_set(uiBut *but, 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, diff --git a/source/blender/editors/interface/interface_template_attribute_search.cc b/source/blender/editors/interface/interface_template_attribute_search.cc index 0157d0b66a3..85a6147432b 100644 --- a/source/blender/editors/interface/interface_template_attribute_search.cc +++ b/source/blender/editors/interface/interface_template_attribute_search.cc @@ -80,9 +80,10 @@ void attribute_search_add_items(StringRefNull str, break; } } - if (!contained && is_output) { + if (!contained) { dummy_info.name = str; - UI_search_item_add(seach_items, str.c_str(), &dummy_info, ICON_ADD, 0, 0); + UI_search_item_add( + seach_items, str.c_str(), &dummy_info, is_output ? ICON_ADD : ICON_NONE, 0, 0); } } @@ -122,4 +123,4 @@ void attribute_search_add_items(StringRefNull str, BLI_string_search_free(search); } -} // namespace blender::ui
\ No newline at end of file +} // namespace blender::ui diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c index e9acc65ed37..3e9042d29a0 100644 --- a/source/blender/editors/interface/interface_widgets.c +++ b/source/blender/editors/interface/interface_widgets.c @@ -518,7 +518,7 @@ 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}}; @@ -559,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; @@ -1494,7 +1459,7 @@ 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) @@ -3721,7 +3686,7 @@ static void widget_datasetrow( 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); @@ -4632,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); diff --git a/source/blender/editors/interface/resources.c b/source/blender/editors/interface/resources.c index ad7c6332ee9..aece2e58f1e 100644 --- a/source/blender/editors/interface/resources.c +++ b/source/blender/editors/interface/resources.c @@ -252,10 +252,11 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid) case TH_HEADER_ACTIVE: cp = ts->header; + const int factor = 5; /* Lighten the header color when editor is active. */ - header_active[0] = cp[0] > 245 ? cp[0] - 10 : cp[0] + 10; - header_active[1] = cp[1] > 245 ? cp[1] - 10 : cp[1] + 10; - header_active[2] = cp[2] > 245 ? cp[2] - 10 : cp[2] + 10; + 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; diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc index 88aa362deb5..c08fa51d5a5 100644 --- a/source/blender/editors/interface/tree_view.cc +++ b/source/blender/editors/interface/tree_view.cc @@ -29,6 +29,7 @@ #include "UI_interface.h" +#include "WM_api.h" #include "WM_types.h" #include "UI_tree_view.hh" @@ -354,22 +355,18 @@ void AbstractTreeViewItem::is_active(IsActiveFn is_active_fn) is_active_fn_ = is_active_fn; } -bool AbstractTreeViewItem::on_drop(const wmDrag & /*drag*/) +std::unique_ptr<AbstractTreeViewItemDragController> AbstractTreeViewItem::create_drag_controller() + const { - /* Do nothing by default. */ - return false; -} - -bool AbstractTreeViewItem::can_drop(const wmDrag & /*drag*/) const -{ - return false; + /* There's no drag controller (and hence no drag support) by default. */ + return nullptr; } -std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/, - const wmDrag & /*drag*/, - const wmEvent & /*event*/) const +std::unique_ptr<AbstractTreeViewItemDropController> AbstractTreeViewItem::create_drop_controller() + const { - return TIP_("Drop into/onto tree item"); + /* There's no drop controller (and hence no drop support) by default. */ + return nullptr; } bool AbstractTreeViewItem::can_rename() const @@ -553,6 +550,12 @@ void AbstractTreeViewItem::change_state_delayed() activate(); } } +/* ---------------------------------------------------------------------- */ + +AbstractTreeViewItemDropController::AbstractTreeViewItemDropController(AbstractTreeView &tree_view) + : tree_view_(tree_view) +{ +} /* ---------------------------------------------------------------------- */ @@ -646,7 +649,18 @@ BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_) : icon(ic void BasicTreeViewItem::build_row(uiLayout &row) { - uiItemL(&row, label_.c_str(), icon); + 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, label.c_str(), icon); } void BasicTreeViewItem::on_activate() @@ -680,19 +694,53 @@ bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle, return a.matches_including_parents(b); } -bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const wmDrag *drag) +/** + * 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_); - return item.can_drop(*drag); + 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 bContext *C, - const wmDrag *drag, - const wmEvent *event) +char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_, const wmDrag *drag) { const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_); - return BLI_strdup(item.drop_tooltip(*C, *drag, *event).c_str()); + 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()); } /** @@ -702,10 +750,13 @@ char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *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 (item.can_drop(*drag)) { - return item.on_drop(*drag); + if (drop_controller->can_drop(*drag, &disabled_hint_dummy)) { + return drop_controller->on_drop(*drag); } } diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c index ca96fde9810..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) */ @@ -1291,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(); +} /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/interface/view2d_edge_pan.c b/source/blender/editors/interface/view2d_edge_pan.c index a49666ebbd3..8d8b9a4fe48 100644 --- a/source/blender/editors/interface/view2d_edge_pan.c +++ b/source/blender/editors/interface/view2d_edge_pan.c @@ -92,6 +92,8 @@ void UI_view2d_edge_pan_init(bContext *C, 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); const float winy = (float)(BLI_rcti_size_y(&vpd->region->winrct) + 1); @@ -227,9 +229,16 @@ void UI_view2d_edge_pan_apply(bContext *C, View2DEdgePanData *vpd, const int xy[ 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_v(&outside_rect, xy)) { + 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 (xy[0] > inside_rect.xmax) { pan_dir_x = 1; diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c index d073f5f2ba4..b712cfc24ed 100644 --- a/source/blender/editors/mesh/editmesh_knife.c +++ b/source/blender/editors/mesh/editmesh_knife.c @@ -184,8 +184,6 @@ typedef struct KnifeMeasureData { float cage[3]; float mval[2]; bool is_stored; - float corr_prev_cage[3]; /* "knife_start_cut" updates prev.cage breaking angle calculations, - * store correct version. */ } KnifeMeasureData; typedef struct KnifeUndoFrame { @@ -242,6 +240,7 @@ typedef struct KnifeTool_OpData { BLI_mempool *kverts; BLI_mempool *kedges; + bool no_cuts; /* A cut has not been made yet. */ BLI_Stack *undostack; BLI_Stack *splitstack; /* Store edge splits by #knife_split_edge. */ @@ -496,7 +495,7 @@ static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd) const int distance_precision = 4; /* Calculate distance and convert to string. */ - const float cut_len = len_v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage); + const float cut_len = len_v3v3(kcd->prev.cage, kcd->curr.cage); UnitSettings *unit = &kcd->scene->unit; if (unit->system == USER_UNIT_NONE) { @@ -703,7 +702,7 @@ static void knifetool_draw_visible_angles(const KnifeTool_OpData *kcd) else { tempkfv = tempkfe->v2; } - angle = angle_v3v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage, tempkfv->cageco); + angle = angle_v3v3v3(kcd->prev.cage, kcd->curr.cage, tempkfv->cageco); if (angle < min_angle) { min_angle = angle; kfe = tempkfe; @@ -717,7 +716,7 @@ static void knifetool_draw_visible_angles(const KnifeTool_OpData *kcd) ED_view3d_project_float_global(kcd->region, end, end_ss, V3D_PROJ_TEST_NOP); knifetool_draw_angle(kcd, - kcd->mdata.corr_prev_cage, + kcd->prev.cage, kcd->curr.cage, end, kcd->prev.mval, @@ -730,11 +729,11 @@ static void knifetool_draw_visible_angles(const KnifeTool_OpData *kcd) kfe = kcd->curr.edge; /* Check for most recent cut (if cage is part of previous cut). */ - if (!compare_v3v3(kfe->v1->cageco, kcd->mdata.corr_prev_cage, KNIFE_FLT_EPSBIG) && - !compare_v3v3(kfe->v2->cageco, kcd->mdata.corr_prev_cage, KNIFE_FLT_EPSBIG)) { + if (!compare_v3v3(kfe->v1->cageco, kcd->prev.cage, KNIFE_FLT_EPSBIG) && + !compare_v3v3(kfe->v2->cageco, kcd->prev.cage, KNIFE_FLT_EPSBIG)) { /* Determine acute angle. */ - float angle1 = angle_v3v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage, kfe->v1->cageco); - float angle2 = angle_v3v3v3(kcd->mdata.corr_prev_cage, kcd->curr.cage, kfe->v2->cageco); + float angle1 = angle_v3v3v3(kcd->prev.cage, kcd->curr.cage, kfe->v1->cageco); + float angle2 = angle_v3v3v3(kcd->prev.cage, kcd->curr.cage, kfe->v2->cageco); float angle; float *end; @@ -751,14 +750,8 @@ static void knifetool_draw_visible_angles(const KnifeTool_OpData *kcd) float end_ss[2]; ED_view3d_project_float_global(kcd->region, end, end_ss, V3D_PROJ_TEST_NOP); - knifetool_draw_angle(kcd, - kcd->mdata.corr_prev_cage, - kcd->curr.cage, - end, - kcd->prev.mval, - kcd->curr.mval, - end_ss, - angle); + knifetool_draw_angle( + kcd, kcd->prev.cage, kcd->curr.cage, end, kcd->prev.mval, kcd->curr.mval, end_ss, angle); } } @@ -852,10 +845,10 @@ static void knifetool_draw_visible_angles(const KnifeTool_OpData *kcd) kcd, kcd->curr.cage, kcd->prev.cage, end, kcd->curr.mval, kcd->prev.mval, end_ss, angle); } else if (kcd->mdata.is_stored && !kcd->prev.is_space) { - float angle = angle_v3v3v3(kcd->curr.cage, kcd->mdata.corr_prev_cage, kcd->mdata.cage); + float angle = angle_v3v3v3(kcd->curr.cage, kcd->prev.cage, kcd->mdata.cage); knifetool_draw_angle(kcd, kcd->curr.cage, - kcd->mdata.corr_prev_cage, + kcd->prev.cage, kcd->mdata.cage, kcd->curr.mval, kcd->prev.mval, @@ -1468,7 +1461,10 @@ static void knife_input_ray_segment(KnifeTool_OpData *kcd, ED_view3d_unproject_v3(kcd->vc.region, mval[0], mval[1], ofs, r_origin_ofs); } -static void knifetool_recast_cageco(KnifeTool_OpData *kcd, float mval[3], float r_cage[3]) +/* No longer used, but may be useful in the future. */ +static void UNUSED_FUNCTION(knifetool_recast_cageco)(KnifeTool_OpData *kcd, + float mval[3], + float r_cage[3]) { float origin[3]; float origin_ofs[3]; @@ -2396,7 +2392,7 @@ static void knife_add_cut(KnifeTool_OpData *kcd) } /* Save values for angle drawing calculations. */ - copy_v3_v3(kcd->mdata.cage, kcd->mdata.corr_prev_cage); + copy_v3_v3(kcd->mdata.cage, kcd->prev.cage); copy_v2_v2(kcd->mdata.mval, kcd->prev.mval); kcd->mdata.is_stored = true; @@ -4094,6 +4090,8 @@ static void knifetool_init(bContext *C, knife_init_colors(&kcd->colors); } + kcd->no_cuts = true; + kcd->axis_string[0] = ' '; kcd->axis_string[1] = '\0'; @@ -4506,12 +4504,23 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) handled = true; break; case KNF_MODAL_NEW_CUT: + /* If no cuts have been made, exit. + * Preserves right click cancel workflow which most tools use, + * but stops accidentally deleting entire cuts with right click. + */ + if (kcd->no_cuts) { + ED_region_tag_redraw(kcd->region); + knifetool_exit(op); + ED_workspace_status_text(C, NULL); + return OPERATOR_CANCELLED; + } ED_region_tag_redraw(kcd->region); knife_finish_cut(kcd); kcd->mode = MODE_IDLE; handled = true; break; case KNF_MODAL_ADD_CUT: + kcd->no_cuts = false; knife_recalc_ortho(kcd); /* Get the value of the event which triggered this one. */ @@ -4525,16 +4534,6 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event) kcd->init = kcd->curr; } - /* Preserve correct prev.cage for angle drawing calculations. */ - if (kcd->prev.edge == NULL && kcd->prev.vert == NULL) { - /* "knife_start_cut" moves prev.cage so needs to be recalculated. */ - /* Only occurs if prev was started on a face. */ - knifetool_recast_cageco(kcd, kcd->prev.mval, kcd->mdata.corr_prev_cage); - } - else { - copy_v3_v3(kcd->mdata.corr_prev_cage, kcd->prev.cage); - } - /* Freehand drawing is incompatible with cut-through. */ if (kcd->cut_through == false) { kcd->is_drag_hold = true; @@ -4811,7 +4810,7 @@ void MESH_OT_knife_tool(wmOperatorType *ot) "Occlude Geometry", "Only cut the front most geometry"); RNA_def_boolean(ot->srna, "only_selected", false, "Only Selected", "Only cut selected geometry"); - RNA_def_boolean(ot->srna, "xray", true, "X-Ray", "Show cuts through geometry"); + RNA_def_boolean(ot->srna, "xray", true, "X-Ray", "Show cuts hidden by geometry"); RNA_def_enum(ot->srna, "visible_measurements", diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c index 2fcf8fa6f8f..e0768bcff24 100644 --- a/source/blender/editors/mesh/editmesh_select.c +++ b/source/blender/editors/mesh/editmesh_select.c @@ -884,9 +884,8 @@ static bool unified_findnearest(ViewContext *vc, BMFace **r_efa) { BMEditMesh *em = vc->em; - static short mval_prev[2] = {-1, -1}; - /* only cycle while the mouse remains still */ - const bool use_cycle = ((mval_prev[0] == vc->mval[0]) && (mval_prev[1] == vc->mval[1])); + + const bool use_cycle = !WM_cursor_test_motion_and_update(vc->mval); const float dist_init = ED_view3d_select_dist_px(); /* since edges select lines, we give dots advantage of ~20 pix */ const float dist_margin = (dist_init / 2); @@ -988,9 +987,6 @@ static bool unified_findnearest(ViewContext *vc, } } - mval_prev[0] = vc->mval[0]; - mval_prev[1] = vc->mval[1]; - /* Only one element type will be non-null. */ BLI_assert(((hit.v.ele != NULL) + (hit.e.ele != NULL) + (hit.f.ele != NULL)) <= 1); diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index 114f540b614..d22ae5bc804 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -1323,6 +1323,7 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) const bool use_in_front = RNA_boolean_get(op->ptr, "use_in_front"); const bool use_lights = RNA_boolean_get(op->ptr, "use_lights"); const int stroke_depth_order = RNA_enum_get(op->ptr, "stroke_depth_order"); + const float stroke_depth_offset = RNA_float_get(op->ptr, "stroke_depth_offset"); ushort local_view_bits; float loc[3], rot[3]; @@ -1454,6 +1455,7 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) if (stroke_depth_order == GP_DRAWMODE_3D) { gpd->draw_mode = GP_DRAWMODE_3D; } + md->stroke_depth_offset = stroke_depth_offset; } break; @@ -1491,9 +1493,10 @@ static void object_add_ui(bContext *UNUSED(C), wmOperator *op) uiItemR(layout, op->ptr, "use_lights", 0, NULL, ICON_NONE); uiItemR(layout, op->ptr, "use_in_front", 0, NULL, ICON_NONE); bool in_front = RNA_boolean_get(op->ptr, "use_in_front"); - uiLayout *row = uiLayoutRow(layout, false); - uiLayoutSetActive(row, !in_front); - uiItemR(row, op->ptr, "stroke_depth_order", 0, NULL, ICON_NONE); + uiLayout *col = uiLayoutColumn(layout, false); + uiLayoutSetActive(col, !in_front); + uiItemR(col, op->ptr, "stroke_depth_offset", 0, NULL, ICON_NONE); + uiItemR(col, op->ptr, "stroke_depth_order", 0, NULL, ICON_NONE); } } @@ -1532,9 +1535,18 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot) ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", ""); RNA_def_boolean(ot->srna, "use_in_front", - false, - "In Front", + true, + "Show In Front", "Show line art grease pencil in front of everything"); + RNA_def_float(ot->srna, + "stroke_depth_offset", + 0.05f, + 0.0f, + FLT_MAX, + "Stroke Offset", + "Stroke offset for the line art modifier", + 0.0f, + 0.5f); RNA_def_boolean( ot->srna, "use_lights", false, "Use Lights", "Use lights for this grease pencil object"); RNA_def_enum( @@ -1543,7 +1555,7 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot) rna_enum_gpencil_add_stroke_depth_order_items, GP_DRAWMODE_3D, "Stroke Depth Order", - "Defines how the strokes are ordered in 3D space for objects not displayed 'In Front'"); + "Defines how the strokes are ordered in 3D space for objects not displayed 'In Front')"); } /** \} */ @@ -2133,7 +2145,7 @@ static void copy_object_set_idnew(bContext *C) Main *bmain = CTX_data_main(C); CTX_DATA_BEGIN (C, Object *, ob, selected_editable_objects) { - BKE_libblock_relink_to_newid(&ob->id); + BKE_libblock_relink_to_newid(bmain, &ob->id); } CTX_DATA_END; @@ -2366,7 +2378,7 @@ static void make_object_duplilist_real(bContext *C, Object *ob_dst = BLI_ghash_lookup(dupli_gh, dob); /* Remap new object to itself, and clear again newid pointer of orig object. */ - BKE_libblock_relink_to_newid(&ob_dst->id); + BKE_libblock_relink_to_newid(bmain, &ob_dst->id); DEG_id_tag_update(&ob_dst->id, ID_RECALC_GEOMETRY); @@ -3363,7 +3375,7 @@ Base *ED_object_add_duplicate( ob = basen->object; /* link own references to the newly duplicated data T26816. */ - BKE_libblock_relink_to_newid(&ob->id); + BKE_libblock_relink_to_newid(bmain, &ob->id); /* DAG_relations_tag_update(bmain); */ /* caller must do */ @@ -3469,19 +3481,6 @@ void OBJECT_OT_duplicate(wmOperatorType *ot) * Use for drag & drop. * \{ */ -static Base *object_add_ensure_in_view_layer(Main *bmain, ViewLayer *view_layer, Object *ob) -{ - Base *base = BKE_view_layer_base_find(view_layer, ob); - - if (!base) { - LayerCollection *layer_collection = BKE_layer_collection_get_active(view_layer); - BKE_collection_object_add(bmain, layer_collection->collection, ob); - base = BKE_view_layer_base_find(view_layer, ob); - } - - return base; -} - static int object_add_named_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); @@ -3489,8 +3488,7 @@ static int object_add_named_exec(bContext *C, wmOperator *op) ViewLayer *view_layer = CTX_data_view_layer(C); Base *basen; Object *ob; - const bool duplicate = RNA_boolean_get(op->ptr, "duplicate"); - const bool linked = duplicate && RNA_boolean_get(op->ptr, "linked"); + const bool linked = RNA_boolean_get(op->ptr, "linked"); const eDupli_ID_Flags dupflag = (linked) ? 0 : (eDupli_ID_Flags)U.dupflag; char name[MAX_ID_NAME - 2]; @@ -3504,41 +3502,26 @@ static int object_add_named_exec(bContext *C, wmOperator *op) } /* prepare dupli */ - if (duplicate) { - basen = object_add_duplicate_internal( - bmain, - scene, - view_layer, - ob, - dupflag, - /* Sub-process flag because the new-ID remapping (#BKE_libblock_relink_to_newid()) in this - * function will only work if the object is already linked in the view layer, which is not - * the case here. So we have to do the new-ID relinking ourselves - * (#copy_object_set_idnew()). - */ - LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID); - } - else { - /* basen is actually not a new base in this case. */ - basen = object_add_ensure_in_view_layer(bmain, view_layer, ob); - } + basen = object_add_duplicate_internal( + bmain, + scene, + view_layer, + ob, + dupflag, + /* Sub-process flag because the new-ID remapping (#BKE_libblock_relink_to_newid()) in this + * function will only work if the object is already linked in the view layer, which is not + * the case here. So we have to do the new-ID relinking ourselves + * (#copy_object_set_idnew()). + */ + LIB_ID_DUPLICATE_IS_SUBPROCESS | LIB_ID_DUPLICATE_IS_ROOT_ID); if (basen == NULL) { - BKE_report(op->reports, - RPT_ERROR, - duplicate ? "Object could not be duplicated" : - "Object could not be linked to the view layer"); + BKE_report(op->reports, RPT_ERROR, "Object could not be duplicated"); return OPERATOR_CANCELLED; } basen->object->visibility_flag &= ~OB_HIDE_VIEWPORT; - int mval[2]; - if (object_add_drop_xy_get(C, op, &mval)) { - ED_object_location_from_view(C, basen->object->loc); - ED_view3d_cursor3d_position(C, mval, false, basen->object->loc); - } - /* object_add_duplicate_internal() doesn't deselect other objects, unlike object_add_common() or * BKE_view_layer_base_deselect_all(). */ ED_object_base_deselect_all(view_layer, NULL, SEL_DESELECT); @@ -3556,44 +3539,164 @@ static int object_add_named_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); ED_outliner_select_sync_from_object_tag(C); + PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix"); + if (RNA_property_is_set(op->ptr, prop_matrix)) { + Object *ob_add = basen->object; + RNA_property_float_get_array(op->ptr, prop_matrix, &ob_add->obmat[0][0]); + BKE_object_apply_mat4(ob_add, ob_add->obmat, true, true); + + DEG_id_tag_update(&ob_add->id, ID_RECALC_TRANSFORM); + } + else { + int mval[2]; + if (object_add_drop_xy_get(C, op, &mval)) { + ED_object_location_from_view(C, basen->object->loc); + ED_view3d_cursor3d_position(C, mval, false, basen->object->loc); + } + } + return OPERATOR_FINISHED; } void OBJECT_OT_add_named(wmOperatorType *ot) { /* identifiers */ - ot->name = "Add Named Object"; + ot->name = "Add Object"; ot->description = "Add named object"; ot->idname = "OBJECT_OT_add_named"; /* api callbacks */ ot->invoke = object_add_drop_xy_generic_invoke; ot->exec = object_add_named_exec; - ot->poll = ED_operator_objectmode; + ot->poll = ED_operator_objectmode_poll_msg; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; PropertyRNA *prop; - - prop = RNA_def_boolean( - ot->srna, - "duplicate", - true, - "Duplicate", - "Create a duplicate of the object. If not set, only ensures the object is linked into the " - "active view layer, positions and selects/activates it (deselecting others)"); - RNA_def_property_flag(prop, PROP_HIDDEN); - RNA_def_boolean(ot->srna, "linked", false, "Linked", - "Duplicate object but not object data, linking to the original data (ignored if " - "'duplicate' is false)"); + "Duplicate object but not object data, linking to the original data"); RNA_def_string(ot->srna, "name", NULL, MAX_ID_NAME - 2, "Name", "Object name to add"); + prop = RNA_def_float_matrix( + ot->srna, "matrix", 4, 4, NULL, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + + object_add_drop_xy_props(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Transform Object to Mouse Operator + * \{ */ + +/** + * Alternate behavior for dropping an asset that positions the appended object(s). + */ +static int object_transform_to_mouse_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *ob; + + if (RNA_struct_property_is_set(op->ptr, "name")) { + char name[MAX_ID_NAME - 2]; + RNA_string_get(op->ptr, "name", name); + ob = (Object *)BKE_libblock_find_name(bmain, ID_OB, name); + } + else { + ob = OBACT(view_layer); + } + + if (ob == NULL) { + BKE_report(op->reports, RPT_ERROR, "Object not found"); + return OPERATOR_CANCELLED; + } + + /* Don't transform a linked object. There's just nothing to do here in this case, so return + * #OPERATOR_FINISHED. */ + if (ID_IS_LINKED(ob)) { + return OPERATOR_FINISHED; + } + + /* Ensure the locations are updated so snap reads the evaluated active location. */ + CTX_data_ensure_evaluated_depsgraph(C); + + PropertyRNA *prop_matrix = RNA_struct_find_property(op->ptr, "matrix"); + if (RNA_property_is_set(op->ptr, prop_matrix)) { + uint objects_len; + Object **objects = BKE_view_layer_array_selected_objects(view_layer, NULL, &objects_len, {0}); + + float matrix[4][4]; + RNA_property_float_get_array(op->ptr, prop_matrix, &matrix[0][0]); + + float mat_src_unit[4][4]; + float mat_dst_unit[4][4]; + float final_delta[4][4]; + + normalize_m4_m4(mat_src_unit, ob->obmat); + normalize_m4_m4(mat_dst_unit, matrix); + invert_m4(mat_src_unit); + mul_m4_m4m4(final_delta, mat_dst_unit, mat_src_unit); + + ED_object_xform_array_m4(objects, objects_len, final_delta); + + MEM_freeN(objects); + } + else { + int mval[2]; + if (object_add_drop_xy_get(C, op, &mval)) { + float cursor[3]; + ED_object_location_from_view(C, cursor); + ED_view3d_cursor3d_position(C, mval, false, cursor); + + /* Use the active objects location since this is the ID which the user selected to drop. + * + * This transforms all selected objects, so that dropping a single object which links in + * other objects will have their relative transformation preserved. + * For example a child/parent relationship or other objects used with a boolean modifier. + * + * The caller is responsible for ensuring the selection state gives useful results. + * Link/append does this using #FILE_AUTOSELECT. */ + ED_view3d_snap_selected_to_location(C, cursor, V3D_AROUND_ACTIVE); + } + } + + return OPERATOR_FINISHED; +} + +void OBJECT_OT_transform_to_mouse(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Place Object Under Mouse"; + ot->description = "Snap selected item(s) to the mouse location"; + ot->idname = "OBJECT_OT_transform_to_mouse"; + + /* api callbacks */ + ot->invoke = object_add_drop_xy_generic_invoke; + ot->exec = object_transform_to_mouse_exec; + ot->poll = ED_operator_objectmode; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + PropertyRNA *prop; + RNA_def_string(ot->srna, + "name", + NULL, + MAX_ID_NAME - 2, + "Name", + "Object name to place (when unset use the active object)"); + + prop = RNA_def_float_matrix( + ot->srna, "matrix", 4, 4, NULL, 0.0f, 0.0f, "Matrix", "", 0.0f, 0.0f); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + object_add_drop_xy_props(ot); } diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h index ea9a2de090b..fe07ecef438 100644 --- a/source/blender/editors/object/object_intern.h +++ b/source/blender/editors/object/object_intern.h @@ -106,6 +106,7 @@ void OBJECT_OT_select_same_collection(struct wmOperatorType *ot); /* object_add.c */ void OBJECT_OT_add(struct wmOperatorType *ot); void OBJECT_OT_add_named(struct wmOperatorType *ot); +void OBJECT_OT_transform_to_mouse(struct wmOperatorType *ot); void OBJECT_OT_metaball_add(struct wmOperatorType *ot); void OBJECT_OT_text_add(struct wmOperatorType *ot); void OBJECT_OT_armature_add(struct wmOperatorType *ot); diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index b3bf2c64a91..b171da42522 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -112,6 +112,7 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_volume_import); WM_operatortype_append(OBJECT_OT_add); WM_operatortype_append(OBJECT_OT_add_named); + WM_operatortype_append(OBJECT_OT_transform_to_mouse); WM_operatortype_append(OBJECT_OT_effector_add); WM_operatortype_append(OBJECT_OT_collection_instance_add); WM_operatortype_append(OBJECT_OT_data_instance_add); diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c index d81143d6081..acd3f058554 100644 --- a/source/blender/editors/object/object_relations.c +++ b/source/blender/editors/object/object_relations.c @@ -1685,18 +1685,20 @@ static bool single_data_needs_duplication(ID *id) return (id != NULL && (id->us > 1 || ID_IS_LINKED(id))); } -static void libblock_relink_collection(Collection *collection, const bool do_collection) +static void libblock_relink_collection(Main *bmain, + Collection *collection, + const bool do_collection) { if (do_collection) { - BKE_libblock_relink_to_newid(&collection->id); + BKE_libblock_relink_to_newid(bmain, &collection->id); } for (CollectionObject *cob = collection->gobject.first; cob != NULL; cob = cob->next) { - BKE_libblock_relink_to_newid(&cob->ob->id); + BKE_libblock_relink_to_newid(bmain, &cob->ob->id); } LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { - libblock_relink_collection(child->collection, true); + libblock_relink_collection(bmain, child->collection, true); } } @@ -1766,10 +1768,10 @@ static void single_object_users( single_object_users_collection(bmain, scene, master_collection, flag, copy_collections, true); /* Will also handle the master collection. */ - BKE_libblock_relink_to_newid(&scene->id); + BKE_libblock_relink_to_newid(bmain, &scene->id); /* Collection and object pointers in collections */ - libblock_relink_collection(scene->master_collection, false); + libblock_relink_collection(bmain, scene->master_collection, false); /* We also have to handle runtime things in UI. */ if (v3d) { @@ -2589,10 +2591,10 @@ void OBJECT_OT_make_single_user(wmOperatorType *ot) char *ED_object_ot_drop_named_material_tooltip(bContext *C, PointerRNA *properties, - const wmEvent *event) + const int mval[2]) { int mat_slot = 0; - Object *ob = ED_view3d_give_material_slot_under_cursor(C, event->mval, &mat_slot); + Object *ob = ED_view3d_give_material_slot_under_cursor(C, mval, &mat_slot); if (ob == NULL) { return BLI_strdup(""); } @@ -2642,8 +2644,10 @@ static int drop_named_material_invoke(bContext *C, wmOperator *op, const wmEvent return OPERATOR_FINISHED; } -/* used for dropbox */ -/* assigns to object under cursor, only first material slot */ +/** + * Used for drop-box. + * Assigns to object under cursor, only first material slot. + */ void OBJECT_OT_drop_named_material(wmOperatorType *ot) { /* identifiers */ diff --git a/source/blender/editors/object/object_utils.c b/source/blender/editors/object/object_utils.c index 66390f6f165..c7dfe911ce7 100644 --- a/source/blender/editors/object/object_utils.c +++ b/source/blender/editors/object/object_utils.c @@ -36,6 +36,7 @@ #include "BKE_armature.h" #include "BKE_editmesh.h" #include "BKE_lattice.h" +#include "BKE_object.h" #include "BKE_scene.h" #include "DEG_depsgraph_query.h" @@ -430,3 +431,70 @@ void ED_object_data_xform_container_destroy(struct XFormObjectData_Container *xd } /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Transform Object Array + * + * Low level object transform function, transforming objects by `matrix`. + * Simple alternative to full transform logic. + * \{ */ + +static bool object_parent_in_set(GSet *objects_set, Object *ob) +{ + for (Object *parent = ob->parent; parent; parent = parent->parent) { + if (BLI_gset_lookup(objects_set, parent)) { + return true; + } + } + return false; +} + +void ED_object_xform_array_m4(Object **objects, uint objects_len, const float matrix[4][4]) +{ + /* Filter out objects that have parents in `objects_set`. */ + { + GSet *objects_set = BLI_gset_ptr_new_ex(__func__, objects_len); + for (uint i = 0; i < objects_len; i++) { + BLI_gset_add(objects_set, objects[i]); + } + for (uint i = 0; i < objects_len;) { + if (object_parent_in_set(objects_set, objects[i])) { + objects[i] = objects[--objects_len]; + } + else { + i++; + } + } + BLI_gset_free(objects_set, NULL); + } + + /* Detect translation only matrix, prevent rotation/scale channels from being touched at all. */ + bool is_translation_only; + { + float test_m4_a[4][4], test_m4_b[4][4]; + unit_m4(test_m4_a); + copy_m4_m4(test_m4_b, matrix); + zero_v3(test_m4_b[3]); + is_translation_only = equals_m4m4(test_m4_a, test_m4_b); + } + + if (is_translation_only) { + for (uint i = 0; i < objects_len; i++) { + Object *ob = objects[i]; + add_v3_v3(ob->loc, matrix[3]); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + } + } + else { + for (uint i = 0; i < objects_len; i++) { + float m4[4][4]; + Object *ob = objects[i]; + BKE_object_to_mat4(ob, m4); + mul_m4_m4m4(m4, matrix, m4); + BKE_object_apply_mat4(ob, m4, true, true); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + } + } +} + +/** \} */ diff --git a/source/blender/editors/physics/particle_object.c b/source/blender/editors/physics/particle_object.c index 3ac6dca3044..367d72b0ad7 100644 --- a/source/blender/editors/physics/particle_object.c +++ b/source/blender/editors/physics/particle_object.c @@ -1235,9 +1235,15 @@ static int copy_particle_systems_exec(bContext *C, wmOperator *op) const bool use_active = RNA_boolean_get(op->ptr, "use_active"); Scene *scene = CTX_data_scene(C); Object *ob_from = ED_object_active_context(C); - ParticleSystem *psys_from = - use_active ? CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem).data : - NULL; + + ParticleSystem *psys_from = NULL; + if (use_active) { + psys_from = CTX_data_pointer_get_type(C, "particle_system", &RNA_ParticleSystem).data; + if (psys_from == NULL) { + /* Particle System context pointer is only valid in the Properties Editor. */ + psys_from = psys_get_current(ob_from); + } + } int changed_tot = 0; int fail = 0; diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c index b69a563166a..80c14371c16 100644 --- a/source/blender/editors/screen/area.c +++ b/source/blender/editors/screen/area.c @@ -1744,7 +1744,7 @@ static void ed_default_handlers( if (flag & ED_KEYMAP_TOOL) { if (flag & ED_KEYMAP_GIZMO) { WM_event_add_keymap_handler_dynamic( - ®ion->handlers, WM_event_get_keymap_from_toolsystem_fallback, area); + ®ion->handlers, WM_event_get_keymap_from_toolsystem_with_gizmos, area); } else { WM_event_add_keymap_handler_dynamic( @@ -1940,7 +1940,7 @@ void ED_area_init(wmWindowManager *wm, wmWindow *win, ScrArea *area) rcti window_rect; WM_window_rect_calc(win, &window_rect); - /* set typedefinitions */ + /* Set type-definitions. */ area->type = BKE_spacetype_from_id(area->spacetype); if (area->type == NULL) { @@ -3027,7 +3027,7 @@ void ED_region_panels_layout_ex(const bContext *C, search_filter); } - /* Draw "polyinstantaited" panels that don't have a 1 to 1 correspondence with their types. */ + /* Draw "poly-instantiated" panels that don't have a 1 to 1 correspondence with their types. */ if (has_instanced_panel) { LISTBASE_FOREACH (Panel *, panel, ®ion->panels) { if (panel->type == NULL) { diff --git a/source/blender/editors/screen/screen_edit.c b/source/blender/editors/screen/screen_edit.c index 841792d5f2d..fa0cfd16817 100644 --- a/source/blender/editors/screen/screen_edit.c +++ b/source/blender/editors/screen/screen_edit.c @@ -935,6 +935,10 @@ void ED_screen_set_active_region(bContext *C, wmWindow *win, const int xy[2]) } } } + + /* Ensure test-motion values are never shared between regions. */ + const bool use_cycle = !WM_cursor_test_motion_and_update((const int[2]){-1, -1}); + UNUSED_VARS(use_cycle); } /* Cursors, for time being set always on edges, diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index e5fbcbb0b6c..66140cba9c6 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -219,6 +219,20 @@ bool ED_operator_objectmode(bContext *C) return true; } +/** + * Same as #ED_operator_objectmode() but additionally sets a "disabled hint". That is, a message + * to be displayed to the user explaining why the operator can't be used in current context. + */ +bool ED_operator_objectmode_poll_msg(bContext *C) +{ + if (!ED_operator_objectmode(C)) { + CTX_wm_operator_poll_msg_set(C, "Only supported in object mode"); + return false; + } + + return true; +} + static bool ed_spacetype_test(bContext *C, int type) { if (ED_operator_areaactive(C)) { @@ -1283,7 +1297,7 @@ static ScrEdge *screen_area_edge_from_cursor(const bContext *C, * * callbacks: * - * invoke() gets called on shift+lmb drag in action-zone + * invoke() gets called on Shift-LMB drag in action-zone * exec() execute without any user interaction, based on properties * call init(), add handler * @@ -2078,7 +2092,7 @@ typedef struct sAreaSplitData { int bigger, smaller; /* constraints for moving new edge */ int delta; /* delta move edge */ int origmin, origsize; /* to calculate fac, for property storage */ - int previewmode; /* draw previewline, then split */ + int previewmode; /* draw preview-line, then split. */ void *draw_callback; /* call `screen_draw_split_preview` */ bool do_snap; @@ -2628,8 +2642,8 @@ static int area_max_regionsize(ScrArea *area, ARegion *scalear, AZEdge edge) dist = BLI_rcti_size_y(&area->totrct); } - /* subtractwidth of regions on opposite side - * prevents dragging regions into other opposite regions */ + /* Subtract the width of regions on opposite side + * prevents dragging regions into other opposite regions. */ LISTBASE_FOREACH (ARegion *, region, &area->regionbase) { if (region == scalear) { continue; @@ -3082,12 +3096,12 @@ static int keyframe_jump_exec(bContext *C, wmOperator *op) float cfra = (float)(CFRA); - /* init binarytree-list for getting keyframes */ + /* Initialize binary-tree-list for getting keyframes. */ struct AnimKeylist *keylist = ED_keylist_create(); - /* seed up dummy dopesheet context with flags to perform necessary filtering */ + /* Speed up dummy dope-sheet context with flags to perform necessary filtering. */ if ((scene->flag & SCE_KEYS_NO_SELONLY) == 0) { - /* only selected channels are included */ + /* Only selected channels are included. */ ads.filterflag |= ADS_FILTER_ONLYSEL; } @@ -4206,7 +4220,7 @@ static void SCREEN_OT_header_toggle_menus(wmOperatorType *ot) /** \} */ /* -------------------------------------------------------------------- */ -/** \name Region Context Menu Operator (Header/Footer/Navbar) +/** \name Region Context Menu Operator (Header/Footer/Navigation-Bar) * \{ */ static void screen_area_menu_items(ScrArea *area, uiLayout *layout) @@ -5058,6 +5072,18 @@ static int userpref_show_exec(bContext *C, wmOperator *op) int sizex = (500 + UI_NAVIGATION_REGION_WIDTH) * UI_DPI_FAC; int sizey = 520 * UI_DPI_FAC; + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section"); + if (prop && RNA_property_is_set(op->ptr, prop)) { + /* Set active section via RNA, so it can fail properly. */ + + PointerRNA pref_ptr; + RNA_pointer_create(NULL, &RNA_Preferences, &U, &pref_ptr); + PropertyRNA *active_section_prop = RNA_struct_find_property(&pref_ptr, "active_section"); + + RNA_property_enum_set(&pref_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop)); + RNA_property_update(C, &pref_ptr, active_section_prop); + } + /* changes context! */ if (WM_window_open(C, IFACE_("Blender Preferences"), @@ -5091,14 +5117,24 @@ static int userpref_show_exec(bContext *C, wmOperator *op) static void SCREEN_OT_userpref_show(struct wmOperatorType *ot) { + PropertyRNA *prop; + /* identifiers */ - ot->name = "Show Preferences"; + ot->name = "Open Preferences..."; ot->description = "Edit user preferences and system settings"; ot->idname = "SCREEN_OT_userpref_show"; /* api callbacks */ ot->exec = userpref_show_exec; ot->poll = ED_operator_screenactive_nobackground; /* Not in background as this opens a window. */ + + prop = RNA_def_enum(ot->srna, + "section", + rna_enum_preference_section_items, + 0, + "", + "Section to activate in the Preferences"); + RNA_def_property_flag(prop, PROP_HIDDEN); } /** \} */ diff --git a/source/blender/editors/sculpt_paint/paint_vertex.c b/source/blender/editors/sculpt_paint/paint_vertex.c index 4e0dcfe8e3c..fede01a614b 100644 --- a/source/blender/editors/sculpt_paint/paint_vertex.c +++ b/source/blender/editors/sculpt_paint/paint_vertex.c @@ -295,7 +295,7 @@ static uint vpaint_blend(const VPaint *vp, uint color_blend = ED_vpaint_blend_tool(blend, color_curr, color_paint, alpha_i); - /* if no accumulate, clip color adding with colorig & orig alpha */ + /* If no accumulate, clip color adding with `color_orig` & `color_test`. */ if (!brush_use_accumulate(vp)) { uint color_test, a; char *cp, *ct, *co; diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index c305a11daf4..e6b76e05e16 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -60,6 +60,7 @@ class AssetCatalogTreeView : public ui::AbstractTreeView { SpaceFile &space_file_; friend class AssetCatalogTreeViewItem; + friend class AssetCatalogDropController; public: AssetCatalogTreeView(::AssetLibrary *library, @@ -86,25 +87,52 @@ class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem { public: AssetCatalogTreeViewItem(AssetCatalogTreeItem *catalog_item); - static bool has_droppable_item(const wmDrag &drag); - static bool drop_into_catalog(const AssetCatalogTreeView &tree_view, - const wmDrag &drag, - CatalogID catalog_id, - StringRefNull simple_name = ""); - void on_activate() override; void build_row(uiLayout &row) override; void build_context_menu(bContext &C, uiLayout &column) const override; - bool can_drop(const wmDrag &drag) const override; - std::string drop_tooltip(const bContext &C, - const wmDrag &drag, - const wmEvent &event) const override; - bool on_drop(const wmDrag &drag) override; - bool can_rename() const override; bool rename(StringRefNull new_name) override; + + /** Add drag support for catalog items. */ + std::unique_ptr<ui::AbstractTreeViewItemDragController> create_drag_controller() const override; + /** Add dropping support for catalog items. */ + std::unique_ptr<ui::AbstractTreeViewItemDropController> create_drop_controller() const override; +}; + +class AssetCatalogDragController : public ui::AbstractTreeViewItemDragController { + AssetCatalogTreeItem &catalog_item_; + + public: + explicit AssetCatalogDragController(AssetCatalogTreeItem &catalog_item); + + int get_drag_type() const override; + void *create_drag_data() const override; +}; + +class AssetCatalogDropController : public ui::AbstractTreeViewItemDropController { + AssetCatalogTreeItem &catalog_item_; + + public: + AssetCatalogDropController(AssetCatalogTreeView &tree_view, AssetCatalogTreeItem &catalog_item); + + bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; + std::string drop_tooltip(const wmDrag &drag) const override; + bool on_drop(const wmDrag &drag) override; + + ::AssetLibrary &get_asset_library() const; + + static bool has_droppable_asset(const wmDrag &drag, const char **r_disabled_hint); + static bool drop_assets_into_catalog(const AssetCatalogTreeView &tree_view, + const wmDrag &drag, + CatalogID catalog_id, + StringRefNull simple_name = ""); + + private: + bool drop_asset_catalog_into_catalog(const wmDrag &drag); + std::string drop_tooltip_asset_list(const wmDrag &drag) const; + std::string drop_tooltip_asset_catalog(const wmDrag &drag) const; }; /** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level @@ -118,11 +146,15 @@ class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem { class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { using BasicTreeViewItem::BasicTreeViewItem; - bool can_drop(const wmDrag &drag) const override; - std::string drop_tooltip(const bContext &C, - const wmDrag &drag, - const wmEvent &event) const override; - bool on_drop(const wmDrag &drag) override; + struct DropController : public ui::AbstractTreeViewItemDropController { + DropController(AssetCatalogTreeView &tree_view); + + bool can_drop(const wmDrag &drag, const char **r_disabled_hint) const override; + std::string drop_tooltip(const wmDrag &drag) const override; + bool on_drop(const wmDrag &drag) override; + }; + + std::unique_ptr<ui::AbstractTreeViewItemDropController> create_drop_controller() const override; }; /* ---------------------------------------------------------------------- */ @@ -219,12 +251,8 @@ void AssetCatalogTreeViewItem::on_activate() void AssetCatalogTreeViewItem::build_row(uiLayout &row) { - if (catalog_item_.has_unsaved_changes()) { - uiItemL(&row, (label_ + "*").c_str(), icon); - } - else { - uiItemL(&row, label_.c_str(), icon); - } + const std::string label_override = catalog_item_.has_unsaved_changes() ? (label_ + "*") : label_; + add_label(row, label_override); if (!is_hovered()) { return; @@ -275,31 +303,80 @@ void AssetCatalogTreeViewItem::build_context_menu(bContext &C, uiLayout &column) UI_menutype_draw(&C, mt, &column); } -bool AssetCatalogTreeViewItem::has_droppable_item(const wmDrag &drag) +bool AssetCatalogTreeViewItem::can_rename() const { - const ListBase *asset_drags = WM_drag_asset_list_get(&drag); + return true; +} - /* There needs to be at least one asset from the current file. */ - LISTBASE_FOREACH (const wmDragAssetListItem *, asset_item, asset_drags) { - if (!asset_item->is_external) { - return true; - } +bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) +{ + /* Important to keep state. */ + BasicTreeViewItem::rename(new_name); + + const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( + get_tree_view()); + ED_asset_catalog_rename(tree_view.asset_library_, catalog_item_.get_catalog_id(), new_name); + return true; +} + +std::unique_ptr<ui::AbstractTreeViewItemDropController> AssetCatalogTreeViewItem:: + create_drop_controller() const +{ + return std::make_unique<AssetCatalogDropController>( + static_cast<AssetCatalogTreeView &>(get_tree_view()), catalog_item_); +} + +std::unique_ptr<ui::AbstractTreeViewItemDragController> AssetCatalogTreeViewItem:: + create_drag_controller() const +{ + return std::make_unique<AssetCatalogDragController>(catalog_item_); +} + +/* ---------------------------------------------------------------------- */ + +AssetCatalogDropController::AssetCatalogDropController(AssetCatalogTreeView &tree_view, + AssetCatalogTreeItem &catalog_item) + : ui::AbstractTreeViewItemDropController(tree_view), catalog_item_(catalog_item) +{ +} + +bool AssetCatalogDropController::can_drop(const wmDrag &drag, const char **r_disabled_hint) const +{ + if (drag.type == WM_DRAG_ASSET_CATALOG) { + /* Always supported. */ + return true; + } + if (drag.type == WM_DRAG_ASSET_LIST) { + return has_droppable_asset(drag, r_disabled_hint); } return false; } -bool AssetCatalogTreeViewItem::can_drop(const wmDrag &drag) const +std::string AssetCatalogDropController::drop_tooltip(const wmDrag &drag) const { - if (drag.type != WM_DRAG_ASSET_LIST) { - return false; + if (drag.type == WM_DRAG_ASSET_CATALOG) { + return drop_tooltip_asset_catalog(drag); } - return has_droppable_item(drag); + return drop_tooltip_asset_list(drag); } -std::string AssetCatalogTreeViewItem::drop_tooltip(const bContext & /*C*/, - const wmDrag &drag, - const wmEvent & /*event*/) const +std::string AssetCatalogDropController::drop_tooltip_asset_catalog(const wmDrag &drag) const { + BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); + + const ::AssetLibrary *asset_library = tree_view<AssetCatalogTreeView>().asset_library_; + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(asset_library); + wmDragAssetCatalog *catalog_drag = WM_drag_get_asset_catalog_data(&drag); + AssetCatalog *src_catalog = catalog_service->find_catalog(catalog_drag->drag_catalog_id); + + return std::string(TIP_("Move Catalog")) + " '" + src_catalog->path.name() + "' " + + IFACE_("into") + " '" + catalog_item_.get_name() + "'"; +} + +std::string AssetCatalogDropController::drop_tooltip_asset_list(const wmDrag &drag) const +{ + BLI_assert(drag.type == WM_DRAG_ASSET_LIST); + const ListBase *asset_drags = WM_drag_asset_list_get(&drag); const bool is_multiple_assets = !BLI_listbase_is_single(asset_drags); @@ -312,11 +389,34 @@ std::string AssetCatalogTreeViewItem::drop_tooltip(const bContext & /*C*/, ")"; } -bool AssetCatalogTreeViewItem::drop_into_catalog(const AssetCatalogTreeView &tree_view, - const wmDrag &drag, - CatalogID catalog_id, - StringRefNull simple_name) +bool AssetCatalogDropController::on_drop(const wmDrag &drag) { + if (drag.type == WM_DRAG_ASSET_CATALOG) { + return drop_asset_catalog_into_catalog(drag); + } + return drop_assets_into_catalog(tree_view<AssetCatalogTreeView>(), + drag, + catalog_item_.get_catalog_id(), + catalog_item_.get_simple_name()); +} + +bool AssetCatalogDropController::drop_asset_catalog_into_catalog(const wmDrag &drag) +{ + BLI_assert(drag.type == WM_DRAG_ASSET_CATALOG); + wmDragAssetCatalog *catalog_drag = WM_drag_get_asset_catalog_data(&drag); + ED_asset_catalog_move( + &get_asset_library(), catalog_drag->drag_catalog_id, catalog_item_.get_catalog_id()); + + WM_main_add_notifier(NC_ASSET | ND_ASSET_CATALOGS, nullptr); + return true; +} + +bool AssetCatalogDropController::drop_assets_into_catalog(const AssetCatalogTreeView &tree_view, + const wmDrag &drag, + CatalogID catalog_id, + StringRefNull simple_name) +{ + BLI_assert(drag.type == WM_DRAG_ASSET_LIST); const ListBase *asset_drags = WM_drag_asset_list_get(&drag); if (!asset_drags) { return false; @@ -339,28 +439,46 @@ bool AssetCatalogTreeViewItem::drop_into_catalog(const AssetCatalogTreeView &tre return true; } -bool AssetCatalogTreeViewItem::on_drop(const wmDrag &drag) +bool AssetCatalogDropController::has_droppable_asset(const wmDrag &drag, + const char **r_disabled_hint) { - const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( - get_tree_view()); - return drop_into_catalog( - tree_view, drag, catalog_item_.get_catalog_id(), catalog_item_.get_simple_name()); + const ListBase *asset_drags = WM_drag_asset_list_get(&drag); + + *r_disabled_hint = nullptr; + /* There needs to be at least one asset from the current file. */ + LISTBASE_FOREACH (const wmDragAssetListItem *, asset_item, asset_drags) { + if (!asset_item->is_external) { + return true; + } + } + + *r_disabled_hint = "Only assets from this current file can be moved between catalogs"; + return false; } -bool AssetCatalogTreeViewItem::can_rename() const +::AssetLibrary &AssetCatalogDropController::get_asset_library() const { - return true; + return *tree_view<AssetCatalogTreeView>().asset_library_; } -bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) +/* ---------------------------------------------------------------------- */ + +AssetCatalogDragController::AssetCatalogDragController(AssetCatalogTreeItem &catalog_item) + : catalog_item_(catalog_item) { - /* Important to keep state. */ - BasicTreeViewItem::rename(new_name); +} - const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( - get_tree_view()); - ED_asset_catalog_rename(tree_view.asset_library_, catalog_item_.get_catalog_id(), new_name); - return true; +int AssetCatalogDragController::get_drag_type() const +{ + return WM_DRAG_ASSET_CATALOG; +} + +void *AssetCatalogDragController::create_drag_data() const +{ + wmDragAssetCatalog *drag_catalog = (wmDragAssetCatalog *)MEM_callocN(sizeof(*drag_catalog), + __func__); + drag_catalog->drag_catalog_id = catalog_item_.get_catalog_id(); + return drag_catalog; } /* ---------------------------------------------------------------------- */ @@ -382,17 +500,29 @@ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) /* ---------------------------------------------------------------------- */ -bool AssetCatalogTreeViewUnassignedItem::can_drop(const wmDrag &drag) const +std::unique_ptr<ui::AbstractTreeViewItemDropController> AssetCatalogTreeViewUnassignedItem:: + create_drop_controller() const +{ + return std::make_unique<AssetCatalogTreeViewUnassignedItem::DropController>( + static_cast<AssetCatalogTreeView &>(get_tree_view())); +} + +AssetCatalogTreeViewUnassignedItem::DropController::DropController(AssetCatalogTreeView &tree_view) + : ui::AbstractTreeViewItemDropController(tree_view) +{ +} + +bool AssetCatalogTreeViewUnassignedItem::DropController::can_drop( + const wmDrag &drag, const char **r_disabled_hint) const { if (drag.type != WM_DRAG_ASSET_LIST) { return false; } - return AssetCatalogTreeViewItem::has_droppable_item(drag); + return AssetCatalogDropController::has_droppable_asset(drag, r_disabled_hint); } -std::string AssetCatalogTreeViewUnassignedItem::drop_tooltip(const bContext & /*C*/, - const wmDrag &drag, - const wmEvent & /*event*/) const +std::string AssetCatalogTreeViewUnassignedItem::DropController::drop_tooltip( + const wmDrag &drag) const { const ListBase *asset_drags = WM_drag_asset_list_get(&drag); const bool is_multiple_assets = !BLI_listbase_is_single(asset_drags); @@ -401,16 +531,17 @@ std::string AssetCatalogTreeViewUnassignedItem::drop_tooltip(const bContext & /* TIP_("Move asset out of any catalog"); } -bool AssetCatalogTreeViewUnassignedItem::on_drop(const wmDrag &drag) +bool AssetCatalogTreeViewUnassignedItem::DropController::on_drop(const wmDrag &drag) { - const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( - get_tree_view()); /* Assign to nil catalog ID. */ - return AssetCatalogTreeViewItem::drop_into_catalog(tree_view, drag, CatalogID{}); + return AssetCatalogDropController::drop_assets_into_catalog( + tree_view<AssetCatalogTreeView>(), drag, CatalogID{}); } } // namespace blender::ed::asset_browser +/* ---------------------------------------------------------------------- */ + namespace blender::ed::asset_browser { class AssetCatalogFilterSettings { diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index 24b24eb81dd..2e2f0c146d6 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -190,6 +190,7 @@ static void file_draw_icon(const SpaceFile *sfile, UI_but_drag_set_asset(but, &(AssetHandle){.file_data = file}, BLI_strdup(blend_path), + file->asset_data, asset_params->import_type, icon, preview_image, @@ -242,8 +243,9 @@ static void file_draw_string(int sx, } /** - * \param r_sx, r_sy: The lower right corner of the last line drawn. AKA the cursor position on - * completion. + * \param r_sx, r_sy: The lower right corner of the last line drawn, plus the height of the last + * line. This is the cursor position on completion to allow drawing more text + * behind that. */ static void file_draw_string_multiline(int sx, int sy, @@ -515,6 +517,7 @@ static void file_draw_preview(const SpaceFile *sfile, UI_but_drag_set_asset(but, &(AssetHandle){.file_data = file}, BLI_strdup(blend_path), + file->asset_data, asset_params->import_type, icon, imb, @@ -1064,7 +1067,9 @@ void file_draw_list(const bContext *C, ARegion *region) layout->curr_size = params->thumbnail_size; } -static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion *region) +static void file_draw_invalid_library_hint(const bContext *C, + const SpaceFile *sfile, + ARegion *region) { const FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); @@ -1072,9 +1077,7 @@ static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion file_path_to_ui_path(asset_params->base_params.dir, library_ui_path, sizeof(library_ui_path)); uchar text_col[4]; - uchar text_alert_col[4]; UI_GetThemeColor4ubv(TH_TEXT, text_col); - UI_GetThemeColor4ubv(TH_REDALERT, text_alert_col); const View2D *v2d = ®ion->v2d; const int pad = sfile->layout->tile_border_x; @@ -1085,23 +1088,42 @@ static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion int sy = v2d->tot.ymax; { - const char *message = TIP_("Library not found"); - const int draw_string_str_len = strlen(message) + 2 + sizeof(library_ui_path); - char *draw_string = alloca(draw_string_str_len); - BLI_snprintf(draw_string, draw_string_str_len, "%s: %s", message, library_ui_path); - file_draw_string_multiline(sx, sy, draw_string, width, line_height, text_alert_col, NULL, &sy); + const char *message = TIP_("Path to asset library does not exist:"); + file_draw_string_multiline(sx, sy, message, width, line_height, text_col, NULL, &sy); + + sy -= line_height; + file_draw_string(sx, sy, library_ui_path, width, line_height, UI_STYLE_TEXT_LEFT, text_col); } - /* Next line, but separate it a bit further. */ - sy -= line_height; + /* Separate a bit further. */ + sy -= line_height * 2.2f; { UI_icon_draw(sx, sy - UI_UNIT_Y, ICON_INFO); const char *suggestion = TIP_( - "Set up the library or edit libraries in the Preferences, File Paths section"); + "Asset Libraries are local directories that can contain .blend files with assets inside.\n" + "Manage Asset Libraries from the File Paths section in Preferences."); file_draw_string_multiline( - sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, NULL, NULL); + sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, NULL, &sy); + + uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS); + uiBut *but = uiDefIconTextButO(block, + UI_BTYPE_BUT, + "SCREEN_OT_userpref_show", + WM_OP_INVOKE_DEFAULT, + ICON_PREFERENCES, + NULL, + sx + UI_UNIT_X, + sy - line_height - UI_UNIT_Y * 1.2f, + UI_UNIT_X * 8, + UI_UNIT_Y, + NULL); + PointerRNA *but_opptr = UI_but_operator_ptr_get(but); + RNA_enum_set(but_opptr, "section", USER_SECTION_FILE_PATHS); + + UI_block_end(C, block); + UI_block_draw(C, block); } } @@ -1109,7 +1131,7 @@ static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion * Draw a string hint if the file list is invalid. * \return true if the list is invalid and a hint was drawn. */ -bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region) +bool file_draw_hint_if_invalid(const bContext *C, const SpaceFile *sfile, ARegion *region) { FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); /* Only for asset browser. */ @@ -1122,7 +1144,7 @@ bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region) return false; } - file_draw_invalid_library_hint(sfile, region); + file_draw_invalid_library_hint(C, sfile, region); return true; } diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index ba08777e4e2..4be5d6d8008 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -47,7 +47,7 @@ struct View2D; void file_calc_previews(const bContext *C, ARegion *region); void file_draw_list(const bContext *C, ARegion *region); -bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region); +bool file_draw_hint_if_invalid(const bContext *C, const SpaceFile *sfile, ARegion *region); void file_draw_check_ex(bContext *C, struct ScrArea *area); void file_draw_check(bContext *C); @@ -79,6 +79,7 @@ void FILE_OT_directory_new(struct wmOperatorType *ot); void FILE_OT_previous(struct wmOperatorType *ot); void FILE_OT_next(struct wmOperatorType *ot); void FILE_OT_refresh(struct wmOperatorType *ot); +void FILE_OT_asset_library_refresh(struct wmOperatorType *ot); void FILE_OT_filenum(struct wmOperatorType *ot); void FILE_OT_delete(struct wmOperatorType *ot); void FILE_OT_rename(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index f647e1d4e4f..4eb10e65867 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -1950,8 +1950,36 @@ void FILE_OT_refresh(struct wmOperatorType *ot) /* api callbacks */ ot->exec = file_refresh_exec; - /* Operator works for file or asset browsing */ - ot->poll = ED_operator_file_active; /* <- important, handler is on window level */ + ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */ +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Refresh Asset Library Operator + * \{ */ + +static int file_asset_library_refresh_exec(bContext *C, wmOperator *UNUSED(unused)) +{ + wmWindowManager *wm = CTX_wm_manager(C); + SpaceFile *sfile = CTX_wm_space_file(C); + + ED_fileselect_clear(wm, sfile); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); + + return OPERATOR_FINISHED; +} + +void FILE_OT_asset_library_refresh(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Refresh Asset Library"; + ot->description = "Reread assets and asset catalogs from the asset library on disk"; + ot->idname = "FILE_OT_asset_library_refresh"; + + /* api callbacks */ + ot->exec = file_asset_library_refresh_exec; + ot->poll = ED_operator_asset_browsing_active; } /** \} */ diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c index 51d0581d6a4..0e468718a04 100644 --- a/source/blender/editors/space_file/file_panels.c +++ b/source/blender/editors/space_file/file_panels.c @@ -247,7 +247,7 @@ static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *pane uiItemR(row, ¶ms_ptr, "asset_library_ref", 0, "", ICON_NONE); if (params->asset_library_ref.type != ASSET_LIBRARY_LOCAL) { - uiItemO(row, "", ICON_FILE_REFRESH, "FILE_OT_refresh"); + uiItemO(row, "", ICON_FILE_REFRESH, "FILE_OT_asset_library_refresh"); } uiItemS(col); diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index d329a8809c7..a73fa2b9740 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -817,88 +817,85 @@ static bool is_filtered_hidden(const char *filename, return false; } -static bool is_filtered_file(FileListInternEntry *file, - const char *UNUSED(root), - FileListFilter *filter) +/** + * Apply the filter string as file path matching pattern. + * \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file_relpath(const FileListInternEntry *file, const FileListFilter *filter) { - bool is_filtered = !is_filtered_hidden(file->relpath, filter, file); + if (filter->filter_search[0] == '\0') { + return true; + } - if (is_filtered && !FILENAME_IS_CURRPAR(file->relpath)) { - /* We only check for types if some type are enabled in filtering. */ - if (filter->filter && (filter->flags & FLF_DO_FILTER)) { - if (file->typeflag & FILE_TYPE_DIR) { - if (file->typeflag & - (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { - is_filtered = false; - } - } - else { - if (!(filter->filter & FILE_TYPE_FOLDER)) { - is_filtered = false; - } + /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ + return fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) == 0; +} + +/** \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file_type(const FileListInternEntry *file, const FileListFilter *filter) +{ + if (is_filtered_hidden(file->relpath, filter, file)) { + return false; + } + + if (FILENAME_IS_CURRPAR(file->relpath)) { + return false; + } + + /* We only check for types if some type are enabled in filtering. */ + if (filter->filter && (filter->flags & FLF_DO_FILTER)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + return false; } } else { - if (!(file->typeflag & filter->filter)) { - is_filtered = false; + if (!(filter->filter & FILE_TYPE_FOLDER)) { + return false; } } } - /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ - if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { - is_filtered = false; + else { + if (!(file->typeflag & filter->filter)) { + return false; } } } + return true; +} - return is_filtered; +/** \return true when the file should be in the result set, false if it should be filtered out. */ +static bool is_filtered_file(FileListInternEntry *file, + const char *UNUSED(root), + FileListFilter *filter) +{ + return is_filtered_file_type(file, filter) && is_filtered_file_relpath(file, filter); } -static bool is_filtered_id_file(const FileListInternEntry *file, - const char *id_group, - const char *name, - const FileListFilter *filter) +static bool is_filtered_id_file_type(const FileListInternEntry *file, + const char *id_group, + const char *name, + const FileListFilter *filter) { - bool is_filtered = !is_filtered_hidden(file->relpath, filter, file); - if (is_filtered && !FILENAME_IS_CURRPAR(file->relpath)) { - /* We only check for types if some type are enabled in filtering. */ - if ((filter->filter || filter->filter_id) && (filter->flags & FLF_DO_FILTER)) { - if (file->typeflag & FILE_TYPE_DIR) { - if (file->typeflag & - (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { - is_filtered = false; - } - } - else { - if (!(filter->filter & FILE_TYPE_FOLDER)) { - is_filtered = false; - } - } - } - if (is_filtered && id_group) { - if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { - is_filtered = false; - } - else { - uint64_t filter_id = groupname_to_filter_id(id_group); - if (!(filter_id & filter->filter_id)) { - is_filtered = false; - } - } + if (!is_filtered_file_type(file, filter)) { + return false; + } + + /* We only check for types if some type are enabled in filtering. */ + if ((filter->filter || filter->filter_id) && (filter->flags & FLF_DO_FILTER)) { + if (id_group) { + if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { + return false; } - } - /* If there's a filter string, apply it as filter even if FLF_DO_FILTER is not set. */ - if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { - is_filtered = false; + + uint64_t filter_id = groupname_to_filter_id(id_group); + if (!(filter_id & filter->filter_id)) { + return false; } } } - return is_filtered; + return true; } /** @@ -921,40 +918,100 @@ static void prepare_filter_asset_library(const FileList *filelist, FileListFilte file_ensure_updated_catalog_filter_data(filter->asset_catalog_filter, filelist->asset_library); } +/** + * Copy a string from source to `dest`, but prefix and suffix it with a single space. + * Assumes `dest` has at least space enough for the two spaces. + */ +static void tag_copy_with_spaces(char *dest, const char *source, const size_t dest_size) +{ + BLI_assert(dest_size > 2); + const size_t source_length = BLI_strncpy_rlen(dest + 1, source, dest_size - 2); + dest[0] = ' '; + dest[source_length + 1] = ' '; + dest[source_length + 2] = '\0'; +} + +/** + * Return whether at least one tag matches the search filter. + * Tags are searched as "entire words", so instead of searching for "tag" in the + * filter string, this function searches for " tag ". Assumes the search filter + * starts and ends with a space. + * + * Here the tags on the asset are written in set notation: + * + * `asset_tag_matches_filter(" some tags ", {"some", "blue"})` -> true + * `asset_tag_matches_filter(" some tags ", {"som", "tag"})` -> false + * `asset_tag_matches_filter(" some tags ", {})` -> false + */ +static bool asset_tag_matches_filter(const char *filter_search, const AssetMetaData *asset_data) +{ + LISTBASE_FOREACH (const AssetTag *, asset_tag, &asset_data->tags) { + char tag_name[MAX_NAME + 2]; /* sizeof(AssetTag::name) + 2 */ + tag_copy_with_spaces(tag_name, asset_tag->name, sizeof(tag_name)); + if (BLI_strcasestr(filter_search, tag_name) != NULL) { + return true; + } + } + return false; +} + static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter) { + const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); + /* Not used yet for the asset view template. */ - if (!filter->asset_catalog_filter) { + if (filter->asset_catalog_filter && !file_is_asset_visible_in_catalog_filter_settings( + filter->asset_catalog_filter, asset_data)) { + return false; + } + + if (filter->filter_search[0] == '\0') { + /* If there is no filter text, everything matches. */ return true; } - const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file); - return file_is_asset_visible_in_catalog_filter_settings(filter->asset_catalog_filter, - asset_data); + /* filter->filter_search contains "*the search text*". */ + char filter_search[66]; /* sizeof(FileListFilter::filter_search) */ + const size_t string_length = STRNCPY_RLEN(filter_search, filter->filter_search); + + /* When doing a name comparison, get rid of the leading/trailing asterisks. */ + filter_search[string_length - 1] = '\0'; + if (BLI_strcasestr(file->name, filter_search + 1) != NULL) { + return true; + } + + /* Replace the asterisks with spaces, so that we can do matching on " sometag "; that way + * an artist searching for "redder" doesn't result in a match for the tag "red". */ + filter_search[string_length - 1] = ' '; + filter_search[0] = ' '; + + return asset_tag_matches_filter(filter_search, asset_data); } -static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) +static bool is_filtered_lib_type(FileListInternEntry *file, + const char *root, + FileListFilter *filter) { - bool is_filtered; char path[FILE_MAX_LIBEXTRA], dir[FILE_MAX_LIBEXTRA], *group, *name; BLI_join_dirfile(path, sizeof(path), root, file->relpath); if (BLO_library_path_explode(path, dir, &group, &name)) { - is_filtered = is_filtered_id_file(file, group, name, filter); - } - else { - is_filtered = is_filtered_file(file, root, filter); + return is_filtered_id_file_type(file, group, name, filter); } + return is_filtered_file_type(file, filter); +} - return is_filtered; +static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) +{ + return is_filtered_lib_type(file, root, filter) && is_filtered_file_relpath(file, filter); } static bool is_filtered_asset_library(FileListInternEntry *file, const char *root, FileListFilter *filter) { - return is_filtered_lib(file, root, filter) && is_filtered_asset(file, filter); + return is_filtered_lib_type(file, root, filter) && is_filtered_asset(file, filter); } static bool is_filtered_main(FileListInternEntry *file, @@ -969,7 +1026,7 @@ static bool is_filtered_main_assets(FileListInternEntry *file, FileListFilter *filter) { /* "Filtered" means *not* being filtered out... So return true if the file should be visible. */ - return is_filtered_id_file(file, file->relpath, file->name, filter) && + return is_filtered_id_file_type(file, file->relpath, file->name, filter) && is_filtered_asset(file, filter); } @@ -1854,6 +1911,7 @@ static void filelist_clear_asset_library(FileList *filelist) { /* The AssetLibraryService owns the AssetLibrary pointer, so no need for us to free it. */ filelist->asset_library = NULL; + file_delete_asset_catalog_filter_settings(&filelist->filter_data.asset_catalog_filter); } void filelist_clear_ex(struct FileList *filelist, @@ -1953,7 +2011,6 @@ void filelist_free(struct FileList *filelist) filelist->selection_state = NULL; } - file_delete_asset_catalog_filter_settings(&filelist->filter_data.asset_catalog_filter); MEM_SAFE_FREE(filelist->asset_library_ref); memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 6ab7e4eeecf..ce76fd65a86 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -486,6 +486,18 @@ struct ID *ED_fileselect_active_asset_get(const SpaceFile *sfile) return filelist_file_get_id(file); } +void ED_fileselect_activate_asset_catalog(const SpaceFile *sfile, const bUUID catalog_id) +{ + if (!ED_fileselect_is_asset_browser(sfile)) { + return; + } + + FileAssetSelectParams *params = ED_fileselect_get_asset_params(sfile); + params->asset_catalog_visibility = FILE_SHOW_ASSETS_FROM_CATALOG; + params->catalog_id = catalog_id; + WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, NULL); +} + static void on_reload_activate_by_id(SpaceFile *sfile, onReloadFnData custom_data) { ID *asset_id = (ID *)custom_data; @@ -517,14 +529,12 @@ void ED_fileselect_activate_by_id(SpaceFile *sfile, ID *asset_id, const bool def const FileDirEntry *file = filelist_file_ex(files, file_index, false); if (filelist_file_get_id(file) != asset_id) { - filelist_entry_select_set(files, file, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); continue; } params->active_file = file_index; filelist_entry_select_set(files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); - - /* Keep looping to deselect the other files. */ + break; } WM_main_add_notifier(NC_ASSET | NA_ACTIVATED, NULL); @@ -984,6 +994,8 @@ static void file_attribute_columns_init(const FileSelectParams *params, FileLayo void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region) { FileSelectParams *params = ED_fileselect_get_active_params(sfile); + /* Request a slightly more compact layout for asset browsing. */ + const bool compact = ED_fileselect_is_asset_browser(sfile); FileLayout *layout = NULL; View2D *v2d = ®ion->v2d; int numfiles; @@ -1003,12 +1015,13 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region) layout->textheight = textheight; if (params->display == FILE_IMGDISPLAY) { + const float pad_fac = compact ? 0.15f : 0.3f; layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X; layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y; - layout->tile_border_x = 0.3f * UI_UNIT_X; - layout->tile_border_y = 0.3f * UI_UNIT_X; - layout->prv_border_x = 0.3f * UI_UNIT_X; - layout->prv_border_y = 0.3f * UI_UNIT_Y; + layout->tile_border_x = pad_fac * UI_UNIT_X; + layout->tile_border_y = pad_fac * UI_UNIT_X; + layout->prv_border_x = pad_fac * UI_UNIT_X; + layout->prv_border_y = pad_fac * UI_UNIT_Y; layout->tile_w = layout->prv_w + 2 * layout->prv_border_x; layout->tile_h = layout->prv_h + 2 * layout->prv_border_y + textheight; layout->width = (int)(BLI_rctf_size_x(&v2d->cur) - 2 * layout->tile_border_x); diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index a875b7a2c12..b115c63a569 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -659,7 +659,7 @@ static void file_main_region_draw(const bContext *C, ARegion *region) file_highlight_set(sfile, region, event->xy[0], event->xy[1]); } - if (!file_draw_hint_if_invalid(sfile, region)) { + if (!file_draw_hint_if_invalid(C, sfile, region)) { file_draw_list(C, region); } @@ -688,6 +688,7 @@ static void file_operatortypes(void) WM_operatortype_append(FILE_OT_previous); WM_operatortype_append(FILE_OT_next); WM_operatortype_append(FILE_OT_refresh); + WM_operatortype_append(FILE_OT_asset_library_refresh); WM_operatortype_append(FILE_OT_bookmark_add); WM_operatortype_append(FILE_OT_bookmark_delete); WM_operatortype_append(FILE_OT_bookmark_cleanup); diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c index 4694d8652f6..bf2d20cf4c9 100644 --- a/source/blender/editors/space_nla/nla_draw.c +++ b/source/blender/editors/space_nla/nla_draw.c @@ -323,12 +323,19 @@ static void nla_draw_strip_curves(NlaStrip *strip, float yminc, float ymaxc, uin { const float yheight = ymaxc - yminc; - immUniformColor3f(0.7f, 0.7f, 0.7f); - /* draw with AA'd line */ GPU_line_smooth(true); GPU_blend(GPU_BLEND_ALPHA); + /* Fully opaque line on selected strips. */ + if (strip->flag & NLASTRIP_FLAG_SELECT) { + /* TODO: Use theme setting. */ + immUniformColor3f(1.0f, 1.0f, 1.0f); + } + else { + immUniformColor4f(1.0f, 1.0f, 1.0f, 0.5f); + } + /* influence -------------------------- */ if (strip->flag & NLASTRIP_FLAG_USR_INFLUENCE) { FCurve *fcu = BKE_fcurve_find(&strip->fcurves, "influence", 0); @@ -501,7 +508,7 @@ static void nla_draw_strip(SpaceNla *snla, /* strip is in normal track */ UI_draw_roundbox_corner_set(UI_CNR_ALL); /* all corners rounded */ - UI_draw_roundbox_shade_x( + UI_draw_roundbox_4fv( &(const rctf){ .xmin = strip->start, .xmax = strip->end, @@ -509,9 +516,7 @@ static void nla_draw_strip(SpaceNla *snla, .ymax = ymaxc, }, true, - 0.0, - 0.5, - 0.1, + 0.0f, color); /* restore current vertex format & program (roundbox trashes it) */ @@ -545,11 +550,9 @@ static void nla_draw_strip(SpaceNla *snla, /* draw strip outline * - color used here is to indicate active vs non-active */ - if (strip->flag & NLASTRIP_FLAG_ACTIVE) { + if (strip->flag & (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT)) { /* strip should appear 'sunken', so draw a light border around it */ - color[0] = 0.9f; /* FIXME: hardcoded temp-hack colors */ - color[1] = 1.0f; - color[2] = 0.9f; + color[0] = color[1] = color[2] = 1.0f; /* FIXME: hardcoded temp-hack colors */ } else { /* strip should appear to stand out, so draw a dark border around it */ @@ -566,7 +569,7 @@ static void nla_draw_strip(SpaceNla *snla, } else { /* non-muted - draw solid, rounded outline */ - UI_draw_roundbox_shade_x( + UI_draw_roundbox_4fv( &(const rctf){ .xmin = strip->start, .xmax = strip->end, @@ -574,9 +577,7 @@ static void nla_draw_strip(SpaceNla *snla, .ymax = ymaxc, }, false, - 0.0, - 0.0, - 0.1, + 0.0f, color); /* restore current vertex format & program (roundbox trashes it) */ @@ -661,7 +662,7 @@ static void nla_draw_strip_text(AnimData *adt, } /* set text color - if colors (see above) are light, draw black text, otherwise draw white */ - if (strip->flag & (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_SELECT | NLASTRIP_FLAG_TWEAKUSER)) { + if (strip->flag & (NLASTRIP_FLAG_ACTIVE | NLASTRIP_FLAG_TWEAKUSER)) { col[0] = col[1] = col[2] = 0; } else { @@ -805,29 +806,6 @@ void draw_nla_main_data(bAnimContext *ac, SpaceNla *snla, ARegion *region) immRectf( pos, v2d->cur.xmin, ymin + NLACHANNEL_SKIP, v2d->cur.xmax, ymax - NLACHANNEL_SKIP); - /* draw 'embossed' lines above and below the strip for effect */ - /* white base-lines */ - GPU_line_width(2.0f); - immUniformColor4f(1.0f, 1.0f, 1.0f, 0.3f); - immBegin(GPU_PRIM_LINES, 4); - immVertex2f(pos, v2d->cur.xmin, ymin + NLACHANNEL_SKIP); - immVertex2f(pos, v2d->cur.xmax, ymin + NLACHANNEL_SKIP); - immVertex2f(pos, v2d->cur.xmin, ymax - NLACHANNEL_SKIP); - immVertex2f(pos, v2d->cur.xmax, ymax - NLACHANNEL_SKIP); - immEnd(); - - /* black top-lines */ - GPU_line_width(1.0f); - immUniformColor3f(0.0f, 0.0f, 0.0f); - immBegin(GPU_PRIM_LINES, 4); - immVertex2f(pos, v2d->cur.xmin, ymin + NLACHANNEL_SKIP); - immVertex2f(pos, v2d->cur.xmax, ymin + NLACHANNEL_SKIP); - immVertex2f(pos, v2d->cur.xmin, ymax - NLACHANNEL_SKIP); - immVertex2f(pos, v2d->cur.xmax, ymax - NLACHANNEL_SKIP); - immEnd(); - - /* TODO: these lines but better --^ */ - immUnbindProgram(); /* draw keyframes in the action */ diff --git a/source/blender/editors/space_node/CMakeLists.txt b/source/blender/editors/space_node/CMakeLists.txt index 80d3b43bf6b..600309c2c86 100644 --- a/source/blender/editors/space_node/CMakeLists.txt +++ b/source/blender/editors/space_node/CMakeLists.txt @@ -40,6 +40,7 @@ set(INC set(SRC drawnode.cc node_add.cc + node_context_path.cc node_draw.cc node_edit.cc node_geometry_attribute_search.cc diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc index 8a63a1f3505..24f5decacdf 100644 --- a/source/blender/editors/space_node/drawnode.cc +++ b/source/blender/editors/space_node/drawnode.cc @@ -3619,7 +3619,39 @@ static void std_node_socket_draw( break; } case SOCK_IMAGE: { - uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0); + const bNodeTree *node_tree = (const bNodeTree *)node_ptr->owner_id; + if (node_tree->type == NTREE_GEOMETRY) { + if (text[0] == '\0') { + uiTemplateID(layout, + C, + ptr, + "default_value", + "image.new", + "image.open", + nullptr, + 0, + ICON_NONE, + nullptr); + } + else { + /* 0.3 split ratio is inconsistent, but use it here because the "New" button is large. */ + uiLayout *row = uiLayoutSplit(layout, 0.3f, false); + uiItemL(row, text, 0); + uiTemplateID(row, + C, + ptr, + "default_value", + "image.new", + "image.open", + nullptr, + 0, + ICON_NONE, + nullptr); + } + } + else { + uiItemR(layout, ptr, "default_value", DEFAULT_FLAGS, text, 0); + } break; } case SOCK_COLLECTION: { @@ -4297,6 +4329,25 @@ void node_draw_link_bezier(const View2D *v2d, UI_GetThemeColor4fv(th_col2, colors[2]); } + /* Highlight links connected to selected nodes. */ + const bool is_fromnode_selected = link->fromnode && link->fromnode->flag & SELECT; + const bool is_tonode_selected = link->tonode && link->tonode->flag & SELECT; + if (is_fromnode_selected || is_tonode_selected) { + float color_selected[4]; + UI_GetThemeColor4fv(TH_EDGE_SELECT, color_selected); + const float alpha = color_selected[3]; + + /* Interpolate color if highlight color is not fully transparent. */ + if (alpha != 0.0) { + if (is_fromnode_selected) { + interp_v3_v3v3(colors[1], colors[1], color_selected, alpha); + } + if (is_tonode_selected) { + interp_v3_v3v3(colors[2], colors[2], color_selected, alpha); + } + } + } + if (g_batch_link.enabled && !highlighted) { /* Add link to batch. */ nodelink_batch_add_link(snode, @@ -4370,15 +4421,6 @@ void node_draw_link(View2D *v2d, SpaceNode *snode, bNodeLink *link) else if (link->flag & NODE_LINK_MUTED) { th_col1 = th_col2 = TH_REDALERT; } - else { - /* Regular link, highlight if connected to selected node. */ - if (link->fromnode && link->fromnode->flag & SELECT) { - th_col1 = TH_EDGE_SELECT; - } - if (link->tonode && link->tonode->flag & SELECT) { - th_col2 = TH_EDGE_SELECT; - } - } } else { /* Invalid link. */ diff --git a/source/blender/editors/space_node/node_add.cc b/source/blender/editors/space_node/node_add.cc index 7b6ca5e6e61..cb66d0dbd2b 100644 --- a/source/blender/editors/space_node/node_add.cc +++ b/source/blender/editors/space_node/node_add.cc @@ -758,7 +758,7 @@ static bool node_add_file_poll(bContext *C) { const SpaceNode *snode = CTX_wm_space_node(C); return ED_operator_node_editable(C) && - ELEM(snode->nodetree->type, NTREE_SHADER, NTREE_TEXTURE, NTREE_COMPOSIT); + ELEM(snode->nodetree->type, NTREE_SHADER, NTREE_TEXTURE, NTREE_COMPOSIT, NTREE_GEOMETRY); } static int node_add_file_exec(bContext *C, wmOperator *op) @@ -784,6 +784,9 @@ static int node_add_file_exec(bContext *C, wmOperator *op) case NTREE_COMPOSIT: type = CMP_NODE_IMAGE; break; + case NTREE_GEOMETRY: + type = GEO_NODE_IMAGE_TEXTURE; + break; default: return OPERATOR_CANCELLED; } @@ -797,7 +800,14 @@ static int node_add_file_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - node->id = (ID *)ima; + if (type == GEO_NODE_IMAGE_TEXTURE) { + bNodeSocket *image_socket = (bNodeSocket *)node->inputs.first; + bNodeSocketValueImage *socket_value = (bNodeSocketValueImage *)image_socket->default_value; + socket_value->value = ima; + } + else { + node->id = (ID *)ima; + } /* When adding new image file via drag-drop we need to load imbuf in order * to get proper image source. diff --git a/source/blender/editors/space_node/node_context_path.cc b/source/blender/editors/space_node/node_context_path.cc new file mode 100644 index 00000000000..a0ff7f3ce25 --- /dev/null +++ b/source/blender/editors/space_node/node_context_path.cc @@ -0,0 +1,184 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup spnode + * \brief Node breadcrumbs drawing + */ + +#include "BLI_vector.hh" + +#include "DNA_node_types.h" + +#include "BKE_context.h" +#include "BKE_material.h" +#include "BKE_modifier.h" +#include "BKE_object.h" + +#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 "UI_interface.hh" + +#include "node_intern.h" + +struct Mesh; +struct Curve; +struct Light; +struct World; +struct Material; + +namespace blender::ed::space_node { + +static void context_path_add_object_data(Vector<ui::ContextPathItem> &path, Object &object) +{ + if (object.type == OB_MESH && object.data) { + Mesh *mesh = (Mesh *)object.data; + ui::context_path_add_generic(path, RNA_Mesh, mesh); + } + if (object.type == OB_LAMP && object.data) { + Light *light = (Light *)object.data; + ui::context_path_add_generic(path, RNA_Light, light); + } + if (ELEM(object.type, OB_CURVE, OB_FONT, OB_SURF) && object.data) { + Curve *curve = (Curve *)object.data; + ui::context_path_add_generic(path, RNA_Curve, curve); + } +} + +static void context_path_add_node_tree_and_node_groups(const SpaceNode &snode, + Vector<ui::ContextPathItem> &path, + const bool skip_base = false) +{ + Vector<const bNodeTreePath *> tree_path = snode.treepath; + for (const bNodeTreePath *path_item : tree_path.as_span().drop_front(int(skip_base))) { + ui::context_path_add_generic(path, RNA_NodeTree, path_item->nodetree, ICON_NODETREE); + } +} + +static void get_context_path_node_shader(const bContext &C, + SpaceNode &snode, + Vector<ui::ContextPathItem> &path) +{ + if (snode.flag & SNODE_PIN) { + if (snode.shaderfrom == SNODE_SHADER_WORLD) { + Scene *scene = CTX_data_scene(&C); + ui::context_path_add_generic(path, RNA_Scene, scene); + if (scene != nullptr) { + World *world = scene->world; + ui::context_path_add_generic(path, RNA_World, world); + } + /* Skip the base node tree here, because the world contains a node tree already. */ + context_path_add_node_tree_and_node_groups(snode, path, true); + } + else { + context_path_add_node_tree_and_node_groups(snode, path); + } + } + else { + Object *object = CTX_data_active_object(&C); + if (snode.shaderfrom == SNODE_SHADER_OBJECT && object != nullptr) { + ui::context_path_add_generic(path, RNA_Object, object); + if (!(object->matbits && object->matbits[object->actcol - 1])) { + context_path_add_object_data(path, *object); + } + Material *material = BKE_object_material_get(object, object->actcol); + ui::context_path_add_generic(path, RNA_Material, material); + } + else if (snode.shaderfrom == SNODE_SHADER_WORLD) { + Scene *scene = CTX_data_scene(&C); + ui::context_path_add_generic(path, RNA_Scene, scene); + if (scene != nullptr) { + World *world = scene->world; + ui::context_path_add_generic(path, RNA_World, world); + } + } +#ifdef WITH_FREESTYLE + else if (snode.shaderfrom == SNODE_SHADER_LINESTYLE) { + ViewLayer *viewlayer = CTX_data_view_layer(&C); + FreestyleLineStyle *linestyle = BKE_linestyle_active_from_view_layer(viewlayer); + ui::context_path_add_generic(path, RNA_ViewLayer, viewlayer); + Material *mat = BKE_object_material_get(object, object->actcol); + ui::context_path_add_generic(path, RNA_Material, mat); + } +#endif + context_path_add_node_tree_and_node_groups(snode, path, true); + } +} + +static void get_context_path_node_compositor(const bContext &C, + SpaceNode &snode, + Vector<ui::ContextPathItem> &path) +{ + if (snode.flag & SNODE_PIN) { + context_path_add_node_tree_and_node_groups(snode, path); + } + else { + Scene *scene = CTX_data_scene(&C); + ui::context_path_add_generic(path, RNA_Scene, scene); + context_path_add_node_tree_and_node_groups(snode, path); + } +} + +static void get_context_path_node_geometry(const bContext &C, + SpaceNode &snode, + Vector<ui::ContextPathItem> &path) +{ + if (snode.flag & SNODE_PIN) { + context_path_add_node_tree_and_node_groups(snode, path); + } + else { + Object *object = CTX_data_active_object(&C); + ui::context_path_add_generic(path, RNA_Object, object); + ModifierData *modifier = BKE_object_active_modifier(object); + ui::context_path_add_generic(path, RNA_Modifier, modifier, ICON_MODIFIER); + context_path_add_node_tree_and_node_groups(snode, path); + } +} + +Vector<ui::ContextPathItem> context_path_for_space_node(const bContext &C) +{ + SpaceNode *snode = CTX_wm_space_node(&C); + if (snode == nullptr) { + return {}; + } + + Vector<ui::ContextPathItem> context_path; + + if (snode->edittree->type == NTREE_GEOMETRY) { + get_context_path_node_geometry(C, *snode, context_path); + } + else if (snode->edittree->type == NTREE_SHADER) { + get_context_path_node_shader(C, *snode, context_path); + } + else if (snode->edittree->type == NTREE_COMPOSIT) { + get_context_path_node_compositor(C, *snode, context_path); + } + + return context_path; +} + +} // namespace blender::ed::space_node diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 97655080192..a6496294f96 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -71,8 +71,10 @@ #include "ED_gpencil.h" #include "ED_node.h" +#include "ED_screen.h" #include "ED_space_api.h" +#include "UI_interface.hh" #include "UI_resources.h" #include "UI_view2d.h" @@ -728,7 +730,7 @@ static void node_draw_mute_line(const View2D *v2d, const SpaceNode *snode, const GPU_blend(GPU_BLEND_ALPHA); LISTBASE_FOREACH (const bNodeLink *, link, &node->internal_links) { - node_draw_link_bezier(v2d, snode, link, TH_REDALERT, TH_REDALERT, -1); + node_draw_link_bezier(v2d, snode, link, TH_WIRE_INNER, TH_WIRE_INNER, TH_WIRE); } GPU_blend(GPU_BLEND_NONE); @@ -807,12 +809,10 @@ static void node_socket_outline_color_get(const bool selected, float r_outline_color[4]) { if (selected) { - UI_GetThemeColor4fv(TH_TEXT_HI, r_outline_color); - r_outline_color[3] = 0.9f; + UI_GetThemeColor4fv(TH_ACTIVE, r_outline_color); } else { - copy_v4_fl(r_outline_color, 0.0f); - r_outline_color[3] = 0.6f; + UI_GetThemeColor4fv(TH_WIRE, r_outline_color); } /* Until there is a better place for per socket color, @@ -832,11 +832,6 @@ void node_socket_color_get( RNA_pointer_create((ID *)ntree, &RNA_NodeSocket, sock, &ptr); sock->typeinfo->draw_color(C, &ptr, node_ptr, r_color); - - bNode *node = (bNode *)node_ptr->data; - if (node->flag & NODE_MUTED) { - r_color[3] *= 0.25f; - } } struct SocketTooltipData { @@ -854,60 +849,7 @@ static void create_inspection_string_for_generic_value(const geo_log::GenericVal const GPointer value = value_log.value(); const CPPType &type = *value.type(); - if (const FieldCPPType *field_type = dynamic_cast<const FieldCPPType *>(&type)) { - const CPPType &base_type = field_type->field_type(); - BUFFER_FOR_CPP_TYPE_VALUE(base_type, buffer); - const GField &field = field_type->get_gfield(value.get()); - if (field.node().depends_on_input()) { - if (base_type.is<int>()) { - ss << TIP_("Integer Field"); - } - else if (base_type.is<float>()) { - ss << TIP_("Float Field"); - } - else if (base_type.is<blender::float3>()) { - ss << TIP_("Vector Field"); - } - else if (base_type.is<bool>()) { - ss << TIP_("Boolean Field"); - } - else if (base_type.is<std::string>()) { - ss << TIP_("String Field"); - } - ss << TIP_(" based on:\n"); - - /* Use vector set to deduplicate inputs. */ - VectorSet<std::reference_wrapper<const FieldInput>> field_inputs; - field.node().foreach_field_input( - [&](const FieldInput &field_input) { field_inputs.add(field_input); }); - for (const FieldInput &field_input : field_inputs) { - ss << "\u2022 " << field_input.socket_inspection_name(); - if (field_input != field_inputs.as_span().last().get()) { - ss << ".\n"; - } - } - } - else { - blender::fn::evaluate_constant_field(field, buffer); - if (base_type.is<int>()) { - ss << *(int *)buffer << TIP_(" (Integer)"); - } - else if (base_type.is<float>()) { - ss << *(float *)buffer << TIP_(" (Float)"); - } - else if (base_type.is<blender::float3>()) { - ss << *(blender::float3 *)buffer << TIP_(" (Vector)"); - } - else if (base_type.is<bool>()) { - ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); - } - else if (base_type.is<std::string>()) { - ss << *(std::string *)buffer << TIP_(" (String)"); - } - base_type.destruct(buffer); - } - } - else if (type.is<Object *>()) { + if (type.is<Object *>()) { id_to_inspection_string((ID *)*value.get<Object *>(), ID_OB); } else if (type.is<Material *>()) { @@ -924,6 +866,71 @@ static void create_inspection_string_for_generic_value(const geo_log::GenericVal } } +static void create_inspection_string_for_gfield(const geo_log::GFieldValueLog &value_log, + std::stringstream &ss) +{ + const CPPType &type = value_log.type(); + const GField &field = value_log.field(); + const Span<std::string> input_tooltips = value_log.input_tooltips(); + + if (input_tooltips.is_empty()) { + if (field) { + BUFFER_FOR_CPP_TYPE_VALUE(type, buffer); + blender::fn::evaluate_constant_field(field, buffer); + if (type.is<int>()) { + ss << *(int *)buffer << TIP_(" (Integer)"); + } + else if (type.is<float>()) { + ss << *(float *)buffer << TIP_(" (Float)"); + } + else if (type.is<blender::float3>()) { + ss << *(blender::float3 *)buffer << TIP_(" (Vector)"); + } + else if (type.is<bool>()) { + ss << ((*(bool *)buffer) ? TIP_("True") : TIP_("False")) << TIP_(" (Boolean)"); + } + else if (type.is<std::string>()) { + ss << *(std::string *)buffer << TIP_(" (String)"); + } + type.destruct(buffer); + } + else { + /* Constant values should always be logged. */ + BLI_assert_unreachable(); + ss << "Value has not been logged"; + } + } + else { + if (type.is<int>()) { + ss << TIP_("Integer field"); + } + else if (type.is<float>()) { + ss << TIP_("Float field"); + } + else if (type.is<blender::float3>()) { + ss << TIP_("Vector field"); + } + else if (type.is<bool>()) { + ss << TIP_("Boolean field"); + } + else if (type.is<std::string>()) { + ss << TIP_("String field"); + } + else if (type.is<blender::ColorGeometry4f>()) { + ss << TIP_("Color field"); + } + ss << TIP_(" based on:\n"); + + for (const int i : input_tooltips.index_range()) { + const blender::StringRef tooltip = input_tooltips[i]; + ss << "\u2022 " << tooltip; + if (i < input_tooltips.size() - 1) { + ss << ".\n"; + } + } + } +} + static void create_inspection_string_for_geometry(const geo_log::GeometryValueLog &value_log, std::stringstream &ss) { @@ -1015,6 +1022,10 @@ static std::optional<std::string> create_socket_inspection_string(bContext *C, dynamic_cast<const geo_log::GenericValueLog *>(value_log)) { create_inspection_string_for_generic_value(*generic_value_log, ss); } + if (const geo_log::GFieldValueLog *gfield_value_log = + dynamic_cast<const geo_log::GFieldValueLog *>(value_log)) { + create_inspection_string_for_gfield(*gfield_value_log, ss); + } else if (const geo_log::GeometryValueLog *geo_value_log = dynamic_cast<const geo_log::GeometryValueLog *>(value_log)) { create_inspection_string_for_geometry(*geo_value_log, ss); @@ -1156,7 +1167,7 @@ void ED_node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[ GPU_program_point_size(true); immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); - immUniform1f("outline_scale", 0.7f); + immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", -1.0f, -1.0f); /* Single point. */ @@ -1301,7 +1312,7 @@ void node_draw_sockets(const View2D *v2d, GPU_blend(GPU_BLEND_ALPHA); GPU_program_point_size(true); immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE); - immUniform1f("outline_scale", 0.7f); + immUniform1f("outline_scale", 1.0f); immUniform2f("ViewportSize", -1.0f, -1.0f); /* Set handle size. */ @@ -1595,24 +1606,13 @@ static void node_draw_basis(const bContext *C, /* Shadow. */ node_draw_shadow(snode, node, BASIS_RAD, 1.0f); + rctf *rct = &node->totr; float color[4]; int color_id = node_get_colorid(node); - if (node->flag & NODE_MUTED) { - /* Muted nodes are semi-transparent and colorless. */ - UI_GetThemeColor3fv(TH_NODE, color); - color[3] = 0.25f; - } - else { - /* Opaque headers for regular nodes. */ - UI_GetThemeColor3fv(color_id, color); - color[3] = 1.0f; - } GPU_line_width(1.0f); - rctf *rct = &node->totr; - UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT); - + /* Header. */ { const rctf rect = { rct->xmin, @@ -1620,7 +1620,19 @@ static void node_draw_basis(const bContext *C, rct->ymax - NODE_DY, rct->ymax, }; - UI_draw_roundbox_aa(&rect, true, BASIS_RAD, color); + + float color_header[4]; + + /* Muted nodes get a mix of the background with the node color. */ + if (node->flag & NODE_MUTED) { + UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.1f, color_header); + } + else { + UI_GetThemeColorBlend4f(TH_NODE, color_id, 0.4f, color_header); + } + + UI_draw_roundbox_corner_set(UI_CNR_TOP_LEFT | UI_CNR_TOP_RIGHT); + UI_draw_roundbox_4fv(&rect, true, BASIS_RAD, color_header); } /* Show/hide icons. */ @@ -1703,31 +1715,28 @@ static void node_draw_basis(const bContext *C, UI_GetThemeColorBlendShade4fv(TH_SELECT, color_id, 0.4f, 10, color); } - /* Open/close entirely. */ + /* Collapse/expand icon. */ { - int but_size = U.widget_unit * 0.8f; - /* XXX button uses a custom triangle draw below, so make it invisible without icon. */ + const int but_size = U.widget_unit * 0.8f; UI_block_emboss_set(node->block, UI_EMBOSS_NONE); - uiBut *but = uiDefBut(node->block, - UI_BTYPE_BUT_TOGGLE, - 0, - "", - rct->xmin + 0.35f * U.widget_unit, - rct->ymax - NODE_DY / 2.2f - but_size / 2, - but_size, - but_size, - nullptr, - 0, - 0, - 0, - 0, - ""); + + uiBut *but = uiDefIconBut(node->block, + UI_BTYPE_BUT_TOGGLE, + 0, + ICON_DOWNARROW_HLT, + rct->xmin + (NODE_MARGIN_X / 3), + rct->ymax - NODE_DY / 2.2f - but_size / 2, + but_size, + but_size, + nullptr, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + ""); + UI_but_func_set(but, node_toggle_button_cb, node, (void *)"NODE_OT_hide_toggle"); UI_block_emboss_set(node->block, UI_EMBOSS); - - UI_GetThemeColor4fv(TH_TEXT, color); - /* Custom draw function for this button. */ - UI_draw_icon_tri(rct->xmin + 0.65f * U.widget_unit, rct->ymax - NODE_DY / 2.2f, 'v', color); } char showname[128]; @@ -1737,7 +1746,7 @@ static void node_draw_basis(const bContext *C, UI_BTYPE_LABEL, 0, showname, - (int)(rct->xmin + NODE_MARGIN_X), + (int)(rct->xmin + NODE_MARGIN_X + 0.4f), (int)(rct->ymax - NODE_DY), (short)(iconofs - rct->xmin - (18.0f * U.dpi_fac)), (short)NODE_DY, @@ -1751,49 +1760,96 @@ static void node_draw_basis(const bContext *C, UI_but_flag_enable(but, UI_BUT_INACTIVE); } + /* Wire across the node when muted/disabled. */ + if (node->flag & NODE_MUTED) { + node_draw_mute_line(v2d, snode, node); + } + /* Body. */ - if (nodeTypeUndefined(node)) { + const float outline_width = 1.0f; + { /* Use warning color to indicate undefined types. */ - UI_GetThemeColor4fv(TH_REDALERT, color); - } - else if (node->flag & NODE_MUTED) { - /* Muted nodes are semi-transparent and colorless. */ - UI_GetThemeColor4fv(TH_NODE, color); - } - else if (node->flag & NODE_CUSTOM_COLOR) { - rgba_float_args_set(color, node->color[0], node->color[1], node->color[2], 1.0f); - } - else { - UI_GetThemeColor4fv(TH_NODE, color); - } + if (nodeTypeUndefined(node)) { + UI_GetThemeColorBlend4f(TH_REDALERT, TH_NODE, 0.4f, color); + } + /* Muted nodes get a mix of the background with the node color. */ + else if (node->flag & NODE_MUTED) { + UI_GetThemeColorBlend4f(TH_BACK, TH_NODE, 0.2f, color); + } + else if (node->flag & NODE_CUSTOM_COLOR) { + rgba_float_args_set(color, node->color[0], node->color[1], node->color[2], 1.0f); + } + else { + UI_GetThemeColor4fv(TH_NODE, color); + } - if (node->flag & NODE_MUTED) { - color[3] = 0.5f; + /* Draw selected nodes fully opaque. */ + if (node->flag & SELECT) { + color[3] = 1.0f; + } + + /* Draw muted nodes slightly transparent so the wires inside are visible. */ + if (node->flag & NODE_MUTED) { + color[3] -= 0.2f; + } + + const rctf rect = { + rct->xmin, + rct->xmax, + rct->ymin, + rct->ymax - (NODE_DY + outline_width), + }; + + UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_LEFT | UI_CNR_BOTTOM_RIGHT); + UI_draw_roundbox_4fv(&rect, true, BASIS_RAD, color); } + /* Header underline. */ { - UI_draw_roundbox_corner_set(UI_CNR_BOTTOM_LEFT | UI_CNR_BOTTOM_RIGHT); + float color_underline[4]; + + if (node->flag & NODE_MUTED) { + UI_GetThemeColor4fv(TH_WIRE, color_underline); + } + else { + UI_GetThemeColorBlend4f(TH_BACK, color_id, 0.2f, color_underline); + } + const rctf rect = { rct->xmin, rct->xmax, - rct->ymin, + rct->ymax - (NODE_DY + outline_width), rct->ymax - NODE_DY, }; - UI_draw_roundbox_aa(&rect, true, BASIS_RAD, color); + + UI_draw_roundbox_corner_set(UI_CNR_NONE); + UI_draw_roundbox_4fv(&rect, true, 0.0f, color_underline); } - /* Outline active and selected emphasis. */ - if (node->flag & SELECT) { - UI_GetThemeColorShadeAlpha4fv( - (node->flag & NODE_ACTIVE) ? TH_ACTIVE : TH_SELECT, 0, -40, color); + /* Outline. */ + { + const rctf rect = { + rct->xmin - outline_width, + rct->xmax + outline_width, + rct->ymin - outline_width, + rct->ymax + outline_width, + }; - UI_draw_roundbox_corner_set(UI_CNR_ALL); - UI_draw_roundbox_aa(rct, false, BASIS_RAD, color); - } + /* Color the outline according to active, selected, or undefined status. */ + float color_outline[4]; - /* Disable lines. */ - if (node->flag & NODE_MUTED) { - node_draw_mute_line(v2d, snode, node); + if (node->flag & SELECT) { + UI_GetThemeColor4fv((node->flag & NODE_ACTIVE) ? TH_ACTIVE : TH_SELECT, color_outline); + } + else if (nodeTypeUndefined(node)) { + UI_GetThemeColor4fv(TH_REDALERT, color_outline); + } + else { + UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline); + } + + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_4fv(&rect, false, BASIS_RAD, color_outline); } node_draw_sockets(v2d, C, ntree, node, true, false); @@ -1828,46 +1884,45 @@ static void node_draw_hidden(const bContext *C, float scale; UI_view2d_scale_get(v2d, &scale, nullptr); + const int color_id = node_get_colorid(node); + /* Shadow. */ node_draw_shadow(snode, node, hiddenrad, 1.0f); - /* Body. */ - float color[4]; - int color_id = node_get_colorid(node); + /* Wire across the node when muted/disabled. */ if (node->flag & NODE_MUTED) { - /* Muted nodes are semi-transparent and colorless. */ - UI_GetThemeColor4fv(TH_NODE, color); - color[3] = 0.25f; - } - else { - UI_GetThemeColor4fv(color_id, color); + node_draw_mute_line(v2d, snode, node); } - UI_draw_roundbox_aa(rct, true, hiddenrad, color); - - /* Outline active and selected emphasis. */ - if (node->flag & SELECT) { - UI_GetThemeColorShadeAlpha4fv( - (node->flag & NODE_ACTIVE) ? TH_ACTIVE : TH_SELECT, 0, -40, color); - - UI_draw_roundbox_aa(rct, false, hiddenrad, color); - } + /* Body. */ + float color[4]; + { + if (nodeTypeUndefined(node)) { + /* Use warning color to indicate undefined types. */ + UI_GetThemeColorBlend4f(TH_REDALERT, TH_NODE, 0.4f, color); + } + else if (node->flag & NODE_MUTED) { + /* Muted nodes get a mix of the background with the node color. */ + UI_GetThemeColorBlendShade4fv(TH_BACK, color_id, 0.1f, 0, color); + } + else if (node->flag & NODE_CUSTOM_COLOR) { + rgba_float_args_set(color, node->color[0], node->color[1], node->color[2], 1.0f); + } + else { + UI_GetThemeColorBlend4f(TH_NODE, color_id, 0.4f, color); + } - /* Custom color inline. */ - if (node->flag & NODE_CUSTOM_COLOR) { - GPU_blend(GPU_BLEND_ALPHA); - GPU_line_smooth(true); + /* Draw selected nodes fully opaque. */ + if (node->flag & SELECT) { + color[3] = 1.0f; + } - const rctf rect = { - rct->xmin + 1, - rct->xmax - 1, - rct->ymin + 1, - rct->ymax - 1, - }; - UI_draw_roundbox_3fv_alpha(&rect, false, hiddenrad, node->color, 1.0f); + /* Draw muted nodes slightly transparent so the wires inside are visible. */ + if (node->flag & NODE_MUTED) { + color[3] -= 0.2f; + } - GPU_line_smooth(false); - GPU_blend(GPU_BLEND_NONE); + UI_draw_roundbox_4fv(rct, true, hiddenrad, color); } /* Title. */ @@ -1878,36 +1933,28 @@ static void node_draw_hidden(const bContext *C, UI_GetThemeColorBlendShade4fv(TH_SELECT, color_id, 0.4f, 10, color); } - /* Open / collapse icon. */ + /* Collapse/expand icon. */ { - int but_size = U.widget_unit * 0.8f; - /* XXX button uses a custom triangle draw below, so make it invisible without icon */ + const int but_size = U.widget_unit * 1.0f; UI_block_emboss_set(node->block, UI_EMBOSS_NONE); - uiBut *but = uiDefBut(node->block, - UI_BTYPE_BUT_TOGGLE, - 0, - "", - rct->xmin + 0.35f * U.widget_unit, - centy - but_size / 2, - but_size, - but_size, - nullptr, - 0, - 0, - 0, - 0, - ""); - UI_but_func_set(but, node_toggle_button_cb, node, (void *)"NODE_OT_hide_toggle"); - UI_block_emboss_set(node->block, UI_EMBOSS); - UI_GetThemeColor4fv(TH_TEXT, color); - /* Custom draw function for this button. */ - UI_draw_icon_tri(rct->xmin + 0.65f * U.widget_unit, centy, 'h', color); - } + uiBut *but = uiDefIconBut(node->block, + UI_BTYPE_BUT_TOGGLE, + 0, + ICON_RIGHTARROW, + rct->xmin + (NODE_MARGIN_X / 3), + centy - but_size / 2, + but_size, + but_size, + nullptr, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + ""); - /* Disable lines. */ - if (node->flag & NODE_MUTED) { - node_draw_mute_line(v2d, snode, node); + UI_but_func_set(but, node_toggle_button_cb, node, (void *)"NODE_OT_hide_toggle"); + UI_block_emboss_set(node->block, UI_EMBOSS); } char showname[128]; @@ -1927,15 +1974,44 @@ static void node_draw_hidden(const bContext *C, 0, 0, ""); + + /* Outline. */ + { + const float outline_width = 1.0f; + const rctf rect = { + rct->xmin - outline_width, + rct->xmax + outline_width, + rct->ymin - outline_width, + rct->ymax + outline_width, + }; + + /* Color the outline according to active, selected, or undefined status. */ + float color_outline[4]; + + if (node->flag & SELECT) { + UI_GetThemeColor4fv((node->flag & NODE_ACTIVE) ? TH_ACTIVE : TH_SELECT, color_outline); + } + else if (nodeTypeUndefined(node)) { + UI_GetThemeColor4fv(TH_REDALERT, color_outline); + } + else { + UI_GetThemeColorBlendShade4fv(TH_BACK, TH_NODE, 0.4f, -20, color_outline); + } + + UI_draw_roundbox_corner_set(UI_CNR_ALL); + UI_draw_roundbox_4fv(&rect, false, hiddenrad, color_outline); + } + if (node->flag & NODE_MUTED) { UI_but_flag_enable(but, UI_BUT_INACTIVE); } /* Scale widget thing. */ uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); + GPU_blend(GPU_BLEND_ALPHA); immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR); - immUniformThemeColorShade(color_id, -10); + immUniformThemeColorShadeAlpha(TH_TEXT, -40, -180); float dx = 10.0f; immBegin(GPU_PRIM_LINES, 4); @@ -1946,7 +2022,7 @@ static void node_draw_hidden(const bContext *C, immVertex2f(pos, rct->xmax - dx - 3.0f * snode->runtime->aspect, centy + 4.0f); immEnd(); - immUniformThemeColorShade(color_id, 30); + immUniformThemeColorShadeAlpha(TH_TEXT, 0, -180); dx -= snode->runtime->aspect; immBegin(GPU_PRIM_LINES, 4); @@ -1958,6 +2034,7 @@ static void node_draw_hidden(const bContext *C, immEnd(); immUnbindProgram(); + GPU_blend(GPU_BLEND_NONE); node_draw_sockets(v2d, C, ntree, node, true, false); @@ -2142,15 +2219,34 @@ void node_draw_nodetree(const bContext *C, } } -/* Draw tree path info in lower left corner. */ -static void draw_tree_path(SpaceNode *snode) +/* Draw the breadcrumb on the bottom of the editor. */ +static void draw_tree_path(const bContext &C, ARegion ®ion) { - char info[256]; + using namespace blender; + + GPU_matrix_push_projection(); + wmOrtho2_region_pixelspace(®ion); + + const rcti *rect = ED_region_visible_rect(®ion); + + const uiStyle *style = UI_style_get_dpi(); + const float padding_x = 16 * UI_DPI_FAC; + const int x = rect->xmin + padding_x; + const int y = region.winy - UI_UNIT_Y * 0.6f; + const int width = BLI_rcti_size_x(rect) - 2 * padding_x; - ED_node_tree_path_get_fixedbuf(snode, info, sizeof(info)); + uiBlock *block = UI_block_begin(&C, ®ion, __func__, UI_EMBOSS_NONE); + uiLayout *layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, x, y, width, 1, 0, style); - UI_FontThemeColor(BLF_default(), TH_TEXT_HI); - BLF_draw_default(1.5f * UI_UNIT_X, 1.5f * UI_UNIT_Y, 0.0f, info, sizeof(info)); + Vector<ui::ContextPathItem> context_path = ed::space_node::context_path_for_space_node(C); + ui::template_breadcrumbs(*layout, context_path); + + UI_block_layout_resolve(block, nullptr, nullptr); + UI_block_end(&C, block); + UI_block_draw(&C, block); + + GPU_matrix_pop_projection(); } static void snode_setup_v2d(SpaceNode *snode, ARegion *region, const float center[2]) @@ -2227,8 +2323,6 @@ void node_draw_space(const bContext *C, ARegion *region) snode->runtime->cursor[0] /= UI_DPI_FAC; snode->runtime->cursor[1] /= UI_DPI_FAC; - int grid_levels = UI_GetThemeValueType(TH_NODE_GRID_LEVELS, SPACE_NODE); - ED_region_draw_cb_draw(C, region, REGION_DRAW_PRE_VIEW); /* Only set once. */ @@ -2237,6 +2331,9 @@ void node_draw_space(const bContext *C, ARegion *region) /* Nodes. */ snode_set_context(C); + const int grid_levels = UI_GetThemeValueType(TH_NODE_GRID_LEVELS, SPACE_NODE); + UI_view2d_dot_grid_draw(v2d, TH_GRID, NODE_GRID_STEP_SIZE, grid_levels); + /* Draw parent node trees. */ if (snode->treepath.last) { bNodeTreePath *path = (bNodeTreePath *)snode->treepath.last; @@ -2264,9 +2361,6 @@ void node_draw_space(const bContext *C, ARegion *region) if (ntree) { snode_setup_v2d(snode, region, center); - /* Grid. */ - UI_view2d_multi_grid_draw(v2d, TH_GRID, ED_node_grid_size(), NODE_GRID_STEPS, grid_levels); - /* Backdrop. */ draw_nodespace_back_pix(C, region, snode, path->parent_key); @@ -2305,8 +2399,6 @@ void node_draw_space(const bContext *C, ARegion *region) } } else { - /* Default grid. */ - UI_view2d_multi_grid_draw(v2d, TH_GRID, ED_node_grid_size(), NODE_GRID_STEPS, grid_levels); /* Backdrop. */ draw_nodespace_back_pix(C, region, snode, NODE_INSTANCE_KEY_NONE); @@ -2324,8 +2416,10 @@ void node_draw_space(const bContext *C, ARegion *region) } } - /* Tree path info. */ - draw_tree_path(snode); + /* Draw context path. */ + if (snode->edittree) { + draw_tree_path(*C, *region); + } /* Scrollers. */ UI_view2d_scrollers_draw(v2d, nullptr); diff --git a/source/blender/editors/space_node/node_intern.h b/source/blender/editors/space_node/node_intern.h index f069038cc09..c0d50e753ff 100644 --- a/source/blender/editors/space_node/node_intern.h +++ b/source/blender/editors/space_node/node_intern.h @@ -332,7 +332,7 @@ extern const char *node_context_dir[]; #define NODE_SOCKDY (0.1f * U.widget_unit) #define NODE_WIDTH(node) (node->width * UI_DPI_FAC) #define NODE_HEIGHT(node) (node->height * UI_DPI_FAC) -#define NODE_MARGIN_X (1.10f * U.widget_unit) +#define NODE_MARGIN_X (1.2f * U.widget_unit) #define NODE_SOCKSIZE (0.25f * U.widget_unit) #define NODE_MULTI_INPUT_LINK_GAP (0.25f * U.widget_unit) #define NODE_RESIZE_MARGIN (0.20f * U.widget_unit) @@ -341,3 +341,11 @@ extern const char *node_context_dir[]; #ifdef __cplusplus } #endif + +#ifdef __cplusplus +# include "BLI_vector.hh" +# include "UI_interface.hh" +namespace blender::ed::space_node { +Vector<ui::ContextPathItem> context_path_for_space_node(const bContext &C); +} +#endif
\ No newline at end of file diff --git a/source/blender/editors/space_node/node_ops.c b/source/blender/editors/space_node/node_ops.c index df4f63af20b..0c54da65e9c 100644 --- a/source/blender/editors/space_node/node_ops.c +++ b/source/blender/editors/space_node/node_ops.c @@ -156,6 +156,7 @@ void ED_operatormacros_node(void) OPTYPE_UNDO | OPTYPE_REGISTER); mot = WM_operatortype_macro_define(ot, "TRANSFORM_OT_translate"); RNA_boolean_set(mot->ptr, "remove_on_cancel", true); + RNA_boolean_set(mot->ptr, "view2d_edge_pan", true); WM_operatortype_macro_define(ot, "NODE_OT_attach"); WM_operatortype_macro_define(ot, "NODE_OT_insert_offset"); diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index b69e7e98bca..76aad684b4c 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -57,8 +57,12 @@ #include "BLT_translation.h" +#include "NOD_node_tree_ref.hh" + #include "node_intern.h" /* own include */ +using namespace blender::nodes::node_tree_ref_types; + /* -------------------------------------------------------------------- */ /** \name Relations Helpers * \{ */ @@ -612,160 +616,282 @@ static void snode_autoconnect(Main *bmain, /** \name Link Viewer Operator * \{ */ -static int node_link_viewer(const bContext *C, bNode *tonode) -{ - SpaceNode *snode = CTX_wm_space_node(C); +namespace blender::ed::nodes::viewer_linking { - /* context check */ - if (tonode == nullptr || BLI_listbase_is_empty(&tonode->outputs)) { - return OPERATOR_CANCELLED; +/* Depending on the node tree type, different socket types are supported by viewer nodes. */ +static bool socket_can_be_viewed(const OutputSocketRef &socket) +{ + if (nodeSocketIsHidden(socket.bsocket())) { + return false; } - if (ELEM(tonode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER)) { - return OPERATOR_CANCELLED; + if (socket.idname() == "NodeSocketVirtual") { + return false; } + if (socket.tree().btree()->type != NTREE_GEOMETRY) { + return true; + } + return ELEM(socket.typeinfo()->type, + SOCK_GEOMETRY, + SOCK_FLOAT, + SOCK_VECTOR, + SOCK_INT, + SOCK_BOOLEAN, + SOCK_RGBA); +} - /* get viewer */ - bNode *viewer_node = nullptr; - LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) { - if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER)) { - if (node->flag & NODE_DO_OUTPUT) { - viewer_node = node; - break; - } - } +static CustomDataType socket_type_to_custom_data_type(const eNodeSocketDatatype socket_type) +{ + switch (socket_type) { + case SOCK_FLOAT: + return CD_PROP_FLOAT; + case SOCK_INT: + return CD_PROP_INT32; + case SOCK_VECTOR: + return CD_PROP_FLOAT3; + case SOCK_BOOLEAN: + return CD_PROP_BOOL; + case SOCK_RGBA: + return CD_PROP_COLOR; + default: + /* Fallback. */ + return CD_AUTO_FROM_NAME; } - /* no viewer, we make one active */ - if (viewer_node == nullptr) { - LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) { - if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER)) { - node->flag |= NODE_DO_OUTPUT; - viewer_node = node; - break; +} + +/** + * Find the socket to link to in a viewer node. + */ +static bNodeSocket *node_link_viewer_get_socket(bNodeTree *ntree, + bNode *viewer_node, + bNodeSocket *src_socket) +{ + if (viewer_node->type != GEO_NODE_VIEWER) { + /* In viewer nodes in the compositor, only the first input should be linked to. */ + return (bNodeSocket *)viewer_node->inputs.first; + } + /* For the geometry nodes viewer, find the socket with the correct type. */ + LISTBASE_FOREACH (bNodeSocket *, viewer_socket, &viewer_node->inputs) { + if (viewer_socket->type == src_socket->type) { + if (viewer_socket->type == SOCK_GEOMETRY) { + return viewer_socket; } + NodeGeometryViewer *storage = (NodeGeometryViewer *)viewer_node->storage; + const CustomDataType data_type = socket_type_to_custom_data_type( + (eNodeSocketDatatype)src_socket->type); + BLI_assert(data_type != CD_AUTO_FROM_NAME); + storage->data_type = data_type; + nodeUpdate(ntree, viewer_node); + return viewer_socket; } } + return nullptr; +} - bNodeSocket *sock = nullptr; - bNodeLink *link = nullptr; +static bool is_viewer_node(const NodeRef &node) +{ + return ELEM(node.bnode()->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER); +} - /* try to find an already connected socket to cycle to the next */ - if (viewer_node) { - link = nullptr; +static Vector<const NodeRef *> find_viewer_nodes(const NodeTreeRef &tree) +{ + Vector<const NodeRef *> viewer_nodes; + for (const NodeRef *node : tree.nodes()) { + if (is_viewer_node(*node)) { + viewer_nodes.append(node); + } + } + return viewer_nodes; +} - for (link = (bNodeLink *)snode->edittree->links.first; link; link = link->next) { - if (link->tonode == viewer_node && link->fromnode == tonode) { - if (link->tosock == viewer_node->inputs.first) { - break; - } - } +static bool is_viewer_socket_in_viewer(const InputSocketRef &socket) +{ + const NodeRef &node = socket.node(); + BLI_assert(is_viewer_node(node)); + if (node.typeinfo()->type == GEO_NODE_VIEWER) { + return true; + } + return socket.index() == 0; +} + +static bool is_linked_to_viewer(const OutputSocketRef &socket, const NodeRef &viewer_node) +{ + for (const InputSocketRef *target_socket : socket.directly_linked_sockets()) { + if (&target_socket->node() != &viewer_node) { + continue; + } + if (!target_socket->is_available()) { + continue; + } + if (is_viewer_socket_in_viewer(*target_socket)) { + return true; } - if (link) { - /* unlink existing connection */ - sock = link->fromsock; - nodeRemLink(snode->edittree, link); + } + return false; +} - /* find a socket after the previously connected socket */ - if (ED_node_is_geometry(snode)) { - /* Geometry nodes viewer only supports geometry sockets for now. */ - for (sock = sock->next; sock; sock = sock->next) { - if (sock->type == SOCK_GEOMETRY && !nodeSocketIsHidden(sock)) { - break; - } - } - } - else { - for (sock = sock->next; sock; sock = sock->next) { - if (!nodeSocketIsHidden(sock)) { - break; - } - } +static int get_default_viewer_type(const bContext *C) +{ + SpaceNode *snode = CTX_wm_space_node(C); + return ED_node_is_compositor(snode) ? CMP_NODE_VIEWER : GEO_NODE_VIEWER; +} + +static void remove_links_to_unavailable_viewer_sockets(bNodeTree &btree, bNode &viewer_node) +{ + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) { + if (link->tonode == &viewer_node) { + if (link->tosock->flag & SOCK_UNAVAIL) { + nodeRemLink(&btree, link); } } } +} - if (tonode) { - /* Find a selected socket that overrides the socket to connect to */ - if (ED_node_is_geometry(snode)) { - /* Geometry nodes viewer only supports geometry sockets for now. */ - LISTBASE_FOREACH (bNodeSocket *, sock2, &tonode->outputs) { - if (sock2->type == SOCK_GEOMETRY && !nodeSocketIsHidden(sock2) && sock2->flag & SELECT) { - sock = sock2; - break; - } - } +static const NodeRef *get_existing_viewer(const NodeTreeRef &tree) +{ + Vector<const NodeRef *> viewer_nodes = find_viewer_nodes(tree); + + /* Check if there is already an active viewer node that should be used. */ + for (const NodeRef *viewer_node : viewer_nodes) { + if (viewer_node->bnode()->flag & NODE_DO_OUTPUT) { + return viewer_node; } - else { - LISTBASE_FOREACH (bNodeSocket *, sock2, &tonode->outputs) { - if (!nodeSocketIsHidden(sock2) && sock2->flag & SELECT) { - sock = sock2; - break; - } - } + } + + /* If no active but non-active viewers exist, make one active. */ + if (!viewer_nodes.is_empty()) { + viewer_nodes[0]->bnode()->flag |= NODE_DO_OUTPUT; + return viewer_nodes[0]; + } + return nullptr; +} + +static const OutputSocketRef *find_output_socket_to_be_viewed(const NodeRef *active_viewer_node, + const NodeRef &node_to_view) +{ + /* Check if any of the output sockets is selected, which is the case when the user just clicked + * on the socket. */ + for (const OutputSocketRef *output_socket : node_to_view.outputs()) { + if (output_socket->bsocket()->flag & SELECT) { + return output_socket; } } - /* find a socket starting from the first socket */ - if (!sock) { - if (ED_node_is_geometry(snode)) { - /* Geometry nodes viewer only supports geometry sockets for now. */ - for (sock = (bNodeSocket *)tonode->outputs.first; sock; sock = sock->next) { - if (sock->type == SOCK_GEOMETRY && !nodeSocketIsHidden(sock)) { - break; - } + const OutputSocketRef *last_socket_linked_to_viewer = nullptr; + if (active_viewer_node != nullptr) { + for (const OutputSocketRef *output_socket : node_to_view.outputs()) { + if (!socket_can_be_viewed(*output_socket)) { + continue; } - } - else { - for (sock = (bNodeSocket *)tonode->outputs.first; sock; sock = sock->next) { - if (!nodeSocketIsHidden(sock)) { - break; - } + if (is_linked_to_viewer(*output_socket, *active_viewer_node)) { + last_socket_linked_to_viewer = output_socket; } } } - - if (sock) { - /* add a new viewer if none exists yet */ - if (!viewer_node) { - /* XXX location is a quick hack, just place it next to the linked socket */ - const int viewer_type = ED_node_is_compositor(snode) ? CMP_NODE_VIEWER : GEO_NODE_VIEWER; - viewer_node = node_add_node(C, nullptr, viewer_type, sock->locx + 100, sock->locy); - if (!viewer_node) { - return OPERATOR_CANCELLED; + if (last_socket_linked_to_viewer == nullptr) { + /* If no output is connected to a viewer, use the first output that can be viewed. */ + for (const OutputSocketRef *output_socket : node_to_view.outputs()) { + if (socket_can_be_viewed(*output_socket)) { + return output_socket; } - - link = nullptr; } - else { - /* get link to viewer */ - for (link = (bNodeLink *)snode->edittree->links.first; link; link = link->next) { - if (link->tonode == viewer_node && link->tosock == viewer_node->inputs.first) { - break; - } + } + else { + /* Pick the next socket to be linked to the viewer. */ + const int tot_outputs = node_to_view.outputs().size(); + for (const int offset : IndexRange(1, tot_outputs - 1)) { + const int index = (last_socket_linked_to_viewer->index() + offset) % tot_outputs; + const OutputSocketRef &output_socket = node_to_view.output(index); + if (!socket_can_be_viewed(output_socket)) { + continue; + } + if (is_linked_to_viewer(output_socket, *active_viewer_node)) { + continue; } + return &output_socket; } + } + return nullptr; +} - if (link == nullptr) { - nodeAddLink( - snode->edittree, tonode, sock, viewer_node, (bNodeSocket *)viewer_node->inputs.first); - } - else { - link->fromnode = tonode; - link->fromsock = sock; - /* make sure the dependency sorting is updated */ - snode->edittree->update |= NTREE_UPDATE_LINKS; +static int link_socket_to_viewer(const bContext *C, + bNode *viewer_bnode, + bNode *bnode_to_view, + bNodeSocket *bsocket_to_view) +{ + SpaceNode *snode = CTX_wm_space_node(C); + bNodeTree *btree = snode->edittree; + + if (viewer_bnode == nullptr) { + /* Create a new viewer node if none exists. */ + const int viewer_type = get_default_viewer_type(C); + viewer_bnode = node_add_node( + C, nullptr, viewer_type, bsocket_to_view->locx + 100, bsocket_to_view->locy); + if (viewer_bnode == nullptr) { + return OPERATOR_CANCELLED; } - if (ED_node_is_geometry(snode)) { - ED_spreadsheet_context_paths_set_geometry_node(CTX_data_main(C), snode, viewer_node); + } + + bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, viewer_bnode, bsocket_to_view); + if (viewer_bsocket == nullptr) { + return OPERATOR_CANCELLED; + } + + bNodeLink *link_to_change = nullptr; + LISTBASE_FOREACH (bNodeLink *, link, &btree->links) { + if (link->tosock == viewer_bsocket) { + link_to_change = link; + break; } + } - ntreeUpdateTree(CTX_data_main(C), snode->edittree); - snode_update(snode, viewer_node); - DEG_id_tag_update(&snode->edittree->id, 0); + if (link_to_change == nullptr) { + nodeAddLink(btree, bnode_to_view, bsocket_to_view, viewer_bnode, viewer_bsocket); + } + else { + link_to_change->fromnode = bnode_to_view; + link_to_change->fromsock = bsocket_to_view; + btree->update |= NTREE_UPDATE_LINKS; } + remove_links_to_unavailable_viewer_sockets(*btree, *viewer_bnode); + + if (btree->type == NTREE_GEOMETRY) { + ED_spreadsheet_context_paths_set_geometry_node(CTX_data_main(C), snode, viewer_bnode); + } + + ntreeUpdateTree(CTX_data_main(C), btree); + snode_update(snode, viewer_bnode); + DEG_id_tag_update(&btree->id, 0); + return OPERATOR_FINISHED; } +static int node_link_viewer(const bContext *C, bNode *bnode_to_view) +{ + if (bnode_to_view == nullptr) { + return OPERATOR_CANCELLED; + } + + SpaceNode *snode = CTX_wm_space_node(C); + bNodeTree *btree = snode->edittree; + + const NodeTreeRef tree{btree}; + const NodeRef &node_to_view = *tree.find_node(*bnode_to_view); + const NodeRef *active_viewer_node = get_existing_viewer(tree); + + const OutputSocketRef *socket_to_view = find_output_socket_to_be_viewed(active_viewer_node, + node_to_view); + if (socket_to_view == nullptr) { + return OPERATOR_FINISHED; + } + + bNodeSocket *bsocket_to_view = socket_to_view->bsocket(); + bNode *viewer_bnode = active_viewer_node ? active_viewer_node->bnode() : nullptr; + return link_socket_to_viewer(C, viewer_bnode, bnode_to_view, bsocket_to_view); +} + +} // namespace blender::ed::nodes::viewer_linking + static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op)) { SpaceNode *snode = CTX_wm_space_node(C); @@ -777,7 +903,7 @@ static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op)) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - if (node_link_viewer(C, node) == OPERATOR_CANCELLED) { + if (blender::ed::nodes::viewer_linking::node_link_viewer(C, node) == OPERATOR_CANCELLED) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/space_node/space_node.c b/source/blender/editors/space_node/space_node.c index bd2559c4d4d..0b5d7cdda82 100644 --- a/source/blender/editors/space_node/space_node.c +++ b/source/blender/editors/space_node/space_node.c @@ -92,9 +92,6 @@ void ED_node_tree_start(SpaceNode *snode, bNodeTree *ntree, ID *id, ID *from) snode->id = id; snode->from = from; - snode->overlay.flag |= SN_OVERLAY_SHOW_OVERLAYS; - snode->overlay.flag |= SN_OVERLAY_SHOW_WIRE_COLORS; - ED_node_set_active_viewer_key(snode); WM_main_add_notifier(NC_SCENE | ND_NODES, NULL); @@ -204,27 +201,6 @@ void ED_node_tree_path_get(SpaceNode *snode, char *value) } } -void ED_node_tree_path_get_fixedbuf(SpaceNode *snode, char *value, int max_length) -{ - int size; - - value[0] = '\0'; - int i = 0; - LISTBASE_FOREACH_INDEX (bNodeTreePath *, path, &snode->treepath, i) { - if (i == 0) { - size = BLI_strncpy_rlen(value, path->display_name, max_length); - } - else { - size = BLI_snprintf_rlen(value, max_length, "/%s", path->display_name); - } - max_length -= size; - if (max_length <= 0) { - break; - } - value += size; - } -} - void ED_node_set_active_viewer_key(SpaceNode *snode) { bNodeTreePath *path = snode->treepath.last; @@ -259,6 +235,8 @@ static SpaceLink *node_create(const ScrArea *UNUSED(area), const Scene *UNUSED(s snode->spacetype = SPACE_NODE; snode->flag = SNODE_SHOW_GPENCIL | SNODE_USE_ALPHA; + snode->overlay.flag |= SN_OVERLAY_SHOW_OVERLAYS; + snode->overlay.flag |= SN_OVERLAY_SHOW_WIRE_COLORS; /* backdrop */ snode->zoom = 1.0f; diff --git a/source/blender/editors/space_outliner/outliner_dragdrop.c b/source/blender/editors/space_outliner/outliner_dragdrop.c index a82f516b125..a391d032d7e 100644 --- a/source/blender/editors/space_outliner/outliner_dragdrop.c +++ b/source/blender/editors/space_outliner/outliner_dragdrop.c @@ -868,7 +868,7 @@ static bool datastack_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) static char *datastack_drop_tooltip(bContext *UNUSED(C), wmDrag *drag, - const wmEvent *UNUSED(event), + const int UNUSED(xy[2]), struct wmDropBox *UNUSED(drop)) { StackDropData *drop_data = drag->poin; @@ -1201,11 +1201,13 @@ static bool collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event static char *collection_drop_tooltip(bContext *C, wmDrag *drag, - const wmEvent *event, + const int UNUSED(xy[2]), wmDropBox *UNUSED(drop)) { + wmWindowManager *wm = CTX_wm_manager(C); + const wmEvent *event = wm->winactive ? wm->winactive->eventstate : NULL; CollectionDrop data; - if (!event->shift && collection_drop_init(C, drag, event, &data)) { + if (event && !event->shift && collection_drop_init(C, drag, event, &data)) { TreeElement *te = data.te; if (!data.from || event->ctrl) { return BLI_strdup(TIP_("Link inside Collection")); diff --git a/source/blender/editors/space_outliner/outliner_tools.c b/source/blender/editors/space_outliner/outliner_tools.c index 75bdc5dbac6..ae2b1870884 100644 --- a/source/blender/editors/space_outliner/outliner_tools.c +++ b/source/blender/editors/space_outliner/outliner_tools.c @@ -197,11 +197,24 @@ static void get_element_operation_type( static TreeElement *get_target_element(SpaceOutliner *space_outliner) { TreeElement *te = outliner_find_element_with_flag(&space_outliner->tree, TSE_ACTIVE); - BLI_assert(te); return te; } +static bool outliner_operation_tree_element_poll(bContext *C) +{ + if (!ED_operator_outliner_active(C)) { + return false; + } + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); + TreeElement *te = get_target_element(space_outliner); + if (te == NULL) { + return false; + } + + return true; +} + static void unlink_action_fn(bContext *C, ReportList *UNUSED(reports), Scene *UNUSED(scene), @@ -1426,7 +1439,7 @@ static void outliner_do_data_operation( } } -static Base *outline_batch_delete_hierarchy( +static Base *outliner_batch_delete_hierarchy( ReportList *reports, Main *bmain, ViewLayer *view_layer, Scene *scene, Base *base) { Base *child_base, *base_next; @@ -1444,7 +1457,7 @@ static Base *outline_batch_delete_hierarchy( /* pass */ } if (parent) { - base_next = outline_batch_delete_hierarchy(reports, bmain, view_layer, scene, child_base); + base_next = outliner_batch_delete_hierarchy(reports, bmain, view_layer, scene, child_base); } } @@ -1497,7 +1510,7 @@ static void object_batch_delete_hierarchy_fn(bContext *C, ED_object_editmode_exit(C, EM_FREEDATA); } - outline_batch_delete_hierarchy(reports, CTX_data_main(C), view_layer, scene, base); + outliner_batch_delete_hierarchy(reports, CTX_data_main(C), view_layer, scene, base); } } @@ -1868,6 +1881,10 @@ static bool outliner_id_operation_item_poll(bContext *C, PropertyRNA *UNUSED(prop), const int enum_value) { + if (!outliner_operation_tree_element_poll(C)) { + return false; + } + SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); TreeElement *te = get_target_element(space_outliner); TreeStoreElem *tselem = TREESTORE(te); @@ -2254,7 +2271,7 @@ void OUTLINER_OT_id_operation(wmOperatorType *ot) /* callbacks */ ot->invoke = WM_menu_invoke; ot->exec = outliner_id_operation_exec; - ot->poll = ED_operator_outliner_active; + ot->poll = outliner_operation_tree_element_poll; ot->flag = 0; @@ -2361,7 +2378,7 @@ void OUTLINER_OT_lib_operation(wmOperatorType *ot) /* callbacks */ ot->invoke = WM_menu_invoke; ot->exec = outliner_lib_operation_exec; - ot->poll = ED_operator_outliner_active; + ot->poll = outliner_operation_tree_element_poll; ot->prop = RNA_def_enum( ot->srna, "type", outliner_lib_op_type_items, 0, "Library Operation", ""); @@ -2420,14 +2437,8 @@ static int outliner_action_set_exec(bContext *C, wmOperator *op) Main *bmain = CTX_data_main(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; - bAction *act; - /* check for invalid states */ - if (space_outliner == NULL) { - return OPERATOR_CANCELLED; - } - TreeElement *te = get_target_element(space_outliner); get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); @@ -2482,7 +2493,7 @@ void OUTLINER_OT_action_set(wmOperatorType *ot) /* api callbacks */ ot->invoke = WM_enum_search_invoke; ot->exec = outliner_action_set_exec; - ot->poll = ED_operator_outliner_active; + ot->poll = outliner_operation_tree_element_poll; /* flags */ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; @@ -2531,12 +2542,6 @@ static int outliner_animdata_operation_exec(bContext *C, wmOperator *op) wmWindowManager *wm = CTX_wm_manager(C); SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; - - /* check for invalid states */ - if (space_outliner == NULL) { - return OPERATOR_CANCELLED; - } - TreeElement *te = get_target_element(space_outliner); get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); @@ -2722,12 +2727,6 @@ static int outliner_data_operation_exec(bContext *C, wmOperator *op) { SpaceOutliner *space_outliner = CTX_wm_space_outliner(C); int scenelevel = 0, objectlevel = 0, idlevel = 0, datalevel = 0; - - /* check for invalid states */ - if (space_outliner == NULL) { - return OPERATOR_CANCELLED; - } - TreeElement *te = get_target_element(space_outliner); get_element_operation_type(te, &scenelevel, &objectlevel, &idlevel, &datalevel); @@ -2806,6 +2805,13 @@ static const EnumPropertyItem *outliner_data_op_sets_enum_item_fn(bContext *C, return DummyRNA_DEFAULT_items; } + TreeElement *te = get_target_element(space_outliner); + if (te == NULL) { + return DummyRNA_NULL_items; + } + + TreeStoreElem *tselem = TREESTORE(te); + static const EnumPropertyItem optype_sel_and_hide[] = { {OL_DOP_SELECT, "SELECT", 0, "Select", ""}, {OL_DOP_DESELECT, "DESELECT", 0, "Deselect", ""}, @@ -2816,9 +2822,6 @@ static const EnumPropertyItem *outliner_data_op_sets_enum_item_fn(bContext *C, static const EnumPropertyItem optype_sel_linked[] = { {OL_DOP_SELECT_LINKED, "SELECT_LINKED", 0, "Select Linked", ""}, {0, NULL, 0, NULL, NULL}}; - TreeElement *te = get_target_element(space_outliner); - TreeStoreElem *tselem = TREESTORE(te); - if (tselem->type == TSE_RNA_STRUCT) { return optype_sel_linked; } @@ -2835,7 +2838,7 @@ void OUTLINER_OT_data_operation(wmOperatorType *ot) /* callbacks */ ot->invoke = WM_menu_invoke; ot->exec = outliner_data_operation_exec; - ot->poll = ED_operator_outliner_active; + ot->poll = outliner_operation_tree_element_poll; ot->flag = 0; diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c index 2a29125af19..8c70f4e3f7a 100644 --- a/source/blender/editors/space_sequencer/sequencer_edit.c +++ b/source/blender/editors/space_sequencer/sequencer_edit.c @@ -1709,16 +1709,24 @@ static int sequencer_delete_exec(bContext *C, wmOperator *UNUSED(op)) { Main *bmain = CTX_data_main(C); Scene *scene = CTX_data_scene(C); - Editing *ed = SEQ_editing_get(scene); + ListBase *seqbasep = SEQ_active_seqbase_get(SEQ_editing_get(scene)); SEQ_prefetch_stop(scene); - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + const bool is_preview = sequencer_view_preview_poll(C); + if (is_preview) { + SEQ_query_rendered_strips_to_tag(seqbasep, scene->r.cfra, 0); + } + + LISTBASE_FOREACH (Sequence *, seq, seqbasep) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (seq->flag & SELECT) { - SEQ_edit_flag_for_removal(scene, ed->seqbasep, seq); + SEQ_edit_flag_for_removal(scene, seqbasep, seq); } } - SEQ_edit_remove_flagged_sequences(scene, ed->seqbasep); + SEQ_edit_remove_flagged_sequences(scene, seqbasep); DEG_id_tag_update(&scene->id, ID_RECALC_SEQUENCER_STRIPS); DEG_relations_tag_update(bmain); diff --git a/source/blender/editors/space_sequencer/sequencer_select.c b/source/blender/editors/space_sequencer/sequencer_select.c index b4271ebd812..8a8a24f08ff 100644 --- a/source/blender/editors/space_sequencer/sequencer_select.c +++ b/source/blender/editors/space_sequencer/sequencer_select.c @@ -25,6 +25,8 @@ #include <stdlib.h> #include <string.h> +#include "MEM_guardedalloc.h" + #include "BLI_blenlib.h" #include "BLI_math.h" #include "BLI_utildefines.h" @@ -414,9 +416,17 @@ static int sequencer_de_select_all_exec(bContext *C, wmOperator *op) Editing *ed = SEQ_editing_get(scene); Sequence *seq; + const bool is_preview = sequencer_view_preview_poll(C); + if (is_preview) { + SEQ_query_rendered_strips_to_tag(ed->seqbasep, scene->r.cfra, 0); + } + if (action == SEL_TOGGLE) { action = SEL_SELECT; for (seq = ed->seqbasep->first; seq; seq = seq->next) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (seq->flag & SEQ_ALLSEL) { action = SEL_DESELECT; break; @@ -425,6 +435,9 @@ static int sequencer_de_select_all_exec(bContext *C, wmOperator *op) } for (seq = ed->seqbasep->first; seq; seq = seq->next) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } switch (action) { case SEL_SELECT: seq->flag &= ~(SEQ_LEFTSEL + SEQ_RIGHTSEL); @@ -481,7 +494,15 @@ static int sequencer_select_inverse_exec(bContext *C, wmOperator *UNUSED(op)) Editing *ed = SEQ_editing_get(scene); Sequence *seq; + const bool is_preview = sequencer_view_preview_poll(C); + if (is_preview) { + SEQ_query_rendered_strips_to_tag(ed->seqbasep, scene->r.cfra, 0); + } + for (seq = ed->seqbasep->first; seq; seq = seq->next) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (seq->flag & SELECT) { seq->flag &= ~SEQ_ALLSEL; } @@ -635,11 +656,51 @@ static void sequencer_select_linked_handle(const bContext *C, } } -/* Check if click happened on image which belongs to strip. If multiple strips are found, loop - * through them in order. */ -static Sequence *seq_select_seq_from_preview(const bContext *C, - const int mval[2], - const bool center) +/** Collect sequencer that are candidates for being selected. */ +struct SeqSelect_Link { + struct SeqSelect_Link *next, *prev; + Sequence *seq; + /** Only use for center selection. */ + float center_dist_sq; +}; + +static int seq_sort_for_depth_select(const void *a, const void *b) +{ + const struct SeqSelect_Link *slink_a = a; + const struct SeqSelect_Link *slink_b = b; + + /* Exactly overlapping strips, sort by machine (so the top-most is first). */ + if (slink_a->seq->machine < slink_b->seq->machine) { + return 1; + } + if (slink_a->seq->machine > slink_b->seq->machine) { + return -1; + } + return 0; +} + +static int seq_sort_for_center_select(const void *a, const void *b) +{ + const struct SeqSelect_Link *slink_a = a; + const struct SeqSelect_Link *slink_b = b; + if (slink_a->center_dist_sq > slink_b->center_dist_sq) { + return 1; + } + if (slink_a->center_dist_sq < slink_b->center_dist_sq) { + return -1; + } + + /* Exactly overlapping strips, use depth. */ + return seq_sort_for_depth_select(a, b); +} + +/** + * Check if click happened on image which belongs to strip. + * If multiple strips are found, loop through them in order + * (depth (top-most first) or closest to mouse when `center` is true). + */ +static Sequence *seq_select_seq_from_preview( + const bContext *C, const int mval[2], const bool toggle, const bool extend, const bool center) { Scene *scene = CTX_data_scene(C); Editing *ed = SEQ_editing_get(scene); @@ -650,70 +711,82 @@ static Sequence *seq_select_seq_from_preview(const bContext *C, float mouseco_view[2]; UI_view2d_region_to_view(v2d, mval[0], mval[1], &mouseco_view[0], &mouseco_view[1]); + /* Always update the coordinates (check extended after). */ + const bool use_cycle = (!WM_cursor_test_motion_and_update(mval) || extend || toggle); + SeqCollection *strips = SEQ_query_rendered_strips(seqbase, scene->r.cfra, sseq->chanshown); /* Allow strips this far from the closest center to be included. * This allows cycling over center points which are near enough * to overlapping from the users perspective. */ - const float center_threshold_cycle_px = 5.0f; - const float center_dist_sq_eps = square_f(center_threshold_cycle_px * U.pixelsize); + const float center_dist_sq_max = square_f(75.0f * U.pixelsize); const float center_scale_px[2] = { UI_view2d_scale_get_x(v2d), UI_view2d_scale_get_y(v2d), }; - float center_co_best[2] = {0.0f}; - - if (center) { - Sequence *seq_best = NULL; - float center_dist_sq_best = 0.0f; - - Sequence *seq; - SEQ_ITERATOR_FOREACH (seq, strips) { - float co[2]; - SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, co); - const float center_dist_sq_test = len_squared_v2v2(co, mouseco_view); - if ((seq_best == NULL) || (center_dist_sq_test < center_dist_sq_best)) { - seq_best = seq; - center_dist_sq_best = center_dist_sq_test; - copy_v2_v2(center_co_best, co); - } - } - } + struct SeqSelect_Link *slink_active = NULL; + Sequence *seq_active = SEQ_select_active_get(scene); ListBase strips_ordered = {NULL}; Sequence *seq; SEQ_ITERATOR_FOREACH (seq, strips) { bool isect = false; + float center_dist_sq_test = 0.0f; if (center) { /* Detect overlapping center points (scaled by the zoom level). */ float co[2]; SEQ_image_transform_origin_offset_pixelspace_get(scene, seq, co); - sub_v2_v2(co, center_co_best); + sub_v2_v2(co, mouseco_view); mul_v2_v2(co, center_scale_px); - isect = len_squared_v2(co) <= center_dist_sq_eps; + center_dist_sq_test = len_squared_v2(co); + isect = center_dist_sq_test <= center_dist_sq_max; + if (isect) { + /* Use an active strip penalty for "center" selection when cycle is enabled. */ + if (use_cycle && (seq == seq_active) && (seq_active->flag & SELECT)) { + center_dist_sq_test = square_f(sqrtf(center_dist_sq_test) + (3.0f * U.pixelsize)); + } + } } else { isect = seq_point_image_isect(scene, seq, mouseco_view); } if (isect) { - BLI_remlink(seqbase, seq); - BLI_addtail(&strips_ordered, seq); + struct SeqSelect_Link *slink = MEM_callocN(sizeof(*slink), __func__); + slink->seq = seq; + slink->center_dist_sq = center_dist_sq_test; + BLI_addtail(&strips_ordered, slink); + + if (seq == seq_active) { + slink_active = slink; + } } } SEQ_collection_free(strips); - SEQ_sort(&strips_ordered); - Sequence *seq_active = SEQ_select_active_get(scene); - Sequence *seq_select = strips_ordered.first; - LISTBASE_FOREACH (Sequence *, seq_iter, &strips_ordered) { - if (seq_iter == seq_active && seq_iter->next != NULL) { - seq_select = seq_iter->next; - break; + BLI_listbase_sort(&strips_ordered, + center ? seq_sort_for_center_select : seq_sort_for_depth_select); + + struct SeqSelect_Link *slink_select = strips_ordered.first; + Sequence *seq_select = NULL; + if (slink_select != NULL) { + /* Only use special behavior for the active strip when it's selected. */ + if ((center == false) && slink_active && (seq_active->flag & SELECT)) { + if (use_cycle) { + if (slink_active->next) { + slink_select = slink_active->next; + } + } + else { + /* Match object selection behavior: keep the current active item unless cycle is enabled. + * Clicking again in the same location will cycle away from the active object. */ + slink_select = slink_active; + } } + seq_select = slink_select->seq; } - BLI_movelisttolist(seqbase, &strips_ordered); + BLI_freelistN(&strips_ordered); return seq_select; } @@ -759,7 +832,7 @@ static void sequencer_select_strip_impl(const Editing *ed, action = 0; } else { - if ((seq->flag & SELECT) == 0 || is_active) { + if (!((seq->flag & SELECT) && is_active)) { action = 1; } else if (toggle) { @@ -812,7 +885,7 @@ static int sequencer_select_exec(bContext *C, wmOperator *op) int handle_clicked = SEQ_SIDE_NONE; Sequence *seq = NULL; if (region->regiontype == RGN_TYPE_PREVIEW) { - seq = seq_select_seq_from_preview(C, mval, center); + seq = seq_select_seq_from_preview(C, mval, toggle, extend, center); } else { seq = find_nearest_seq(scene, v2d, &handle_clicked, mval); @@ -821,7 +894,7 @@ static int sequencer_select_exec(bContext *C, wmOperator *op) /* NOTE: `side_of_frame` and `linked_time` functionality is designed to be shared on one keymap, * therefore both properties can be true at the same time. */ if (seq && RNA_boolean_get(op->ptr, "linked_time")) { - if (!extend) { + if (!extend && !toggle) { ED_sequencer_deselect_all(scene); } sequencer_select_strip_impl(ed, seq, handle_clicked, extend, deselect, toggle); @@ -833,7 +906,7 @@ static int sequencer_select_exec(bContext *C, wmOperator *op) /* Select left, right or overlapping the current frame. */ if (RNA_boolean_get(op->ptr, "side_of_frame")) { - if (!extend) { + if (!extend && !toggle) { ED_sequencer_deselect_all(scene); } sequencer_select_side_of_frame(C, v2d, mval, scene); @@ -843,7 +916,7 @@ static int sequencer_select_exec(bContext *C, wmOperator *op) /* On Alt selection, select the strip and bordering handles. */ if (seq && RNA_boolean_get(op->ptr, "linked_handle")) { - if (!extend) { + if (!extend && !toggle) { ED_sequencer_deselect_all(scene); } sequencer_select_linked_handle(C, seq, handle_clicked); @@ -1694,11 +1767,17 @@ static const EnumPropertyItem sequencer_prop_select_grouped_types[] = { #define SEQ_CHANNEL_CHECK(_seq, _chan) (ELEM((_chan), 0, (_seq)->machine)) -static bool select_grouped_type(Editing *ed, Sequence *actseq, const int channel) +static bool select_grouped_type(ListBase *seqbasep, + const bool is_preview, + Sequence *actseq, + const int channel) { bool changed = false; - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbasep) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == actseq->type) { seq->flag |= SELECT; changed = true; @@ -1708,12 +1787,18 @@ static bool select_grouped_type(Editing *ed, Sequence *actseq, const int channel return changed; } -static bool select_grouped_type_basic(Editing *ed, Sequence *actseq, const int channel) +static bool select_grouped_type_basic(ListBase *seqbase, + const bool is_preview, + Sequence *actseq, + const int channel) { bool changed = false; const bool is_sound = SEQ_IS_SOUND(actseq); - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (SEQ_CHANNEL_CHECK(seq, channel) && (is_sound ? SEQ_IS_SOUND(seq) : !SEQ_IS_SOUND(seq))) { seq->flag |= SELECT; changed = true; @@ -1723,12 +1808,18 @@ static bool select_grouped_type_basic(Editing *ed, Sequence *actseq, const int c return changed; } -static bool select_grouped_type_effect(Editing *ed, Sequence *actseq, const int channel) +static bool select_grouped_type_effect(ListBase *seqbase, + const bool is_preview, + Sequence *actseq, + const int channel) { bool changed = false; const bool is_effect = SEQ_IS_EFFECT(actseq); - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (SEQ_CHANNEL_CHECK(seq, channel) && (is_effect ? SEQ_IS_EFFECT(seq) : !SEQ_IS_EFFECT(seq))) { seq->flag |= SELECT; @@ -1739,7 +1830,10 @@ static bool select_grouped_type_effect(Editing *ed, Sequence *actseq, const int return changed; } -static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel) +static bool select_grouped_data(ListBase *seqbase, + const bool is_preview, + Sequence *actseq, + const int channel) { bool changed = false; const char *dir = actseq->strip ? actseq->strip->dir : NULL; @@ -1749,7 +1843,10 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel } if (SEQ_HAS_PATH(actseq) && dir) { - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (SEQ_CHANNEL_CHECK(seq, channel) && SEQ_HAS_PATH(seq) && seq->strip && STREQ(seq->strip->dir, dir)) { seq->flag |= SELECT; @@ -1759,7 +1856,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel } else if (actseq->type == SEQ_TYPE_SCENE) { Scene *sce = actseq->scene; - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_SCENE && seq->scene == sce) { seq->flag |= SELECT; changed = true; @@ -1768,7 +1865,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel } else if (actseq->type == SEQ_TYPE_MOVIECLIP) { MovieClip *clip = actseq->clip; - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_MOVIECLIP && seq->clip == clip) { seq->flag |= SELECT; @@ -1778,7 +1875,7 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel } else if (actseq->type == SEQ_TYPE_MASK) { struct Mask *mask = actseq->mask; - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { if (SEQ_CHANNEL_CHECK(seq, channel) && seq->type == SEQ_TYPE_MASK && seq->mask == mask) { seq->flag |= SELECT; changed = true; @@ -1789,7 +1886,10 @@ static bool select_grouped_data(Editing *ed, Sequence *actseq, const int channel return changed; } -static bool select_grouped_effect(Editing *ed, Sequence *actseq, const int channel) +static bool select_grouped_effect(ListBase *seqbase, + const bool is_preview, + Sequence *actseq, + const int channel) { bool changed = false; bool effects[SEQ_TYPE_MAX + 1]; @@ -1798,14 +1898,20 @@ static bool select_grouped_effect(Editing *ed, Sequence *actseq, const int chann effects[i] = false; } - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (SEQ_CHANNEL_CHECK(seq, channel) && (seq->type & SEQ_TYPE_EFFECT) && ELEM(actseq, seq->seq1, seq->seq2, seq->seq3)) { effects[seq->type] = true; } } - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (SEQ_CHANNEL_CHECK(seq, channel) && effects[seq->type]) { if (seq->seq1) { seq->seq1->flag |= SELECT; @@ -1823,11 +1929,14 @@ static bool select_grouped_effect(Editing *ed, Sequence *actseq, const int chann return changed; } -static bool select_grouped_time_overlap(Editing *ed, Sequence *actseq) +static bool select_grouped_time_overlap(ListBase *seqbase, const bool is_preview, Sequence *actseq) { bool changed = false; - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } if (seq->startdisp < actseq->enddisp && seq->enddisp > actseq->startdisp) { seq->flag |= SELECT; changed = true; @@ -1856,12 +1965,11 @@ static void query_lower_channel_strips(Sequence *seq_reference, /* Select all strips within time range and with lower channel of initial selection. Then select * effect chains of these strips. */ -static bool select_grouped_effect_link(Editing *ed, +static bool select_grouped_effect_link(ListBase *seqbase, + const bool is_preview, Sequence *UNUSED(actseq), const int UNUSED(channel)) { - ListBase *seqbase = SEQ_active_seqbase_get(ed); - /* Get collection of strips. */ SeqCollection *collection = SEQ_query_selected_strips(seqbase); const int selected_strip_count = BLI_gset_len(collection->set); @@ -1874,6 +1982,9 @@ static bool select_grouped_effect_link(Editing *ed, /* Actual logic. */ Sequence *seq; SEQ_ITERATOR_FOREACH (seq, collection) { + if (is_preview && (seq->tmp_tag == false)) { + continue; + } seq->flag |= SELECT; } @@ -1889,9 +2000,17 @@ static bool select_grouped_effect_link(Editing *ed, static int sequencer_select_grouped_exec(bContext *C, wmOperator *op) { Scene *scene = CTX_data_scene(C); - Editing *ed = SEQ_editing_get(scene); + ListBase *seqbase = SEQ_active_seqbase_get(SEQ_editing_get(scene)); Sequence *actseq = SEQ_select_active_get(scene); + const bool is_preview = sequencer_view_preview_poll(C); + if (is_preview) { + SEQ_query_rendered_strips_to_tag(seqbase, scene->r.cfra, 0); + if (actseq && actseq->tmp_tag == false) { + actseq = NULL; + } + } + if (actseq == NULL) { BKE_report(op->reports, RPT_ERROR, "No active sequence!"); return OPERATOR_CANCELLED; @@ -1904,7 +2023,7 @@ static int sequencer_select_grouped_exec(bContext *C, wmOperator *op) bool changed = false; if (!extend) { - LISTBASE_FOREACH (Sequence *, seq, SEQ_active_seqbase_get(ed)) { + LISTBASE_FOREACH (Sequence *, seq, seqbase) { seq->flag &= ~SELECT; changed = true; } @@ -1912,25 +2031,25 @@ static int sequencer_select_grouped_exec(bContext *C, wmOperator *op) switch (type) { case SEQ_SELECT_GROUP_TYPE: - changed |= select_grouped_type(ed, actseq, channel); + changed |= select_grouped_type(seqbase, is_preview, actseq, channel); break; case SEQ_SELECT_GROUP_TYPE_BASIC: - changed |= select_grouped_type_basic(ed, actseq, channel); + changed |= select_grouped_type_basic(seqbase, is_preview, actseq, channel); break; case SEQ_SELECT_GROUP_TYPE_EFFECT: - changed |= select_grouped_type_effect(ed, actseq, channel); + changed |= select_grouped_type_effect(seqbase, is_preview, actseq, channel); break; case SEQ_SELECT_GROUP_DATA: - changed |= select_grouped_data(ed, actseq, channel); + changed |= select_grouped_data(seqbase, is_preview, actseq, channel); break; case SEQ_SELECT_GROUP_EFFECT: - changed |= select_grouped_effect(ed, actseq, channel); + changed |= select_grouped_effect(seqbase, is_preview, actseq, channel); break; case SEQ_SELECT_GROUP_EFFECT_LINK: - changed |= select_grouped_effect_link(ed, actseq, channel); + changed |= select_grouped_effect_link(seqbase, is_preview, actseq, channel); break; case SEQ_SELECT_GROUP_OVERLAP: - changed |= select_grouped_time_overlap(ed, actseq); + changed |= select_grouped_time_overlap(seqbase, is_preview, actseq); break; default: BLI_assert(0); diff --git a/source/blender/editors/space_sequencer/sequencer_view.c b/source/blender/editors/space_sequencer/sequencer_view.c index 79593b0bbb0..2d2e7de7135 100644 --- a/source/blender/editors/space_sequencer/sequencer_view.c +++ b/source/blender/editors/space_sequencer/sequencer_view.c @@ -92,7 +92,14 @@ static int sequencer_view_all_exec(bContext *C, wmOperator *op) Scene *scene = CTX_data_scene(C); const Editing *ed = SEQ_editing_get(scene); - SEQ_timeline_boundbox(scene, SEQ_active_seqbase_get(ed), &box); + SEQ_timeline_init_boundbox(scene, &box); + MetaStack *ms = SEQ_meta_stack_active_get(ed); + /* Use meta strip range instead of scene. */ + if (ms != NULL) { + box.xmin = ms->disp_range[0] - 1; + box.xmax = ms->disp_range[1] + 1; + } + SEQ_timeline_expand_boundbox(SEQ_active_seqbase_get(ed), &box); UI_view2d_smooth_view(C, region, &box, smooth_viewtx); return OPERATOR_FINISHED; } diff --git a/source/blender/editors/space_spreadsheet/CMakeLists.txt b/source/blender/editors/space_spreadsheet/CMakeLists.txt index e903feeec1b..91fe1bc01b7 100644 --- a/source/blender/editors/space_spreadsheet/CMakeLists.txt +++ b/source/blender/editors/space_spreadsheet/CMakeLists.txt @@ -35,6 +35,7 @@ set(INC set(SRC space_spreadsheet.cc + spreadsheet_cache.cc spreadsheet_column.cc spreadsheet_context.cc spreadsheet_data_source.cc @@ -47,6 +48,7 @@ set(SRC spreadsheet_row_filter.cc spreadsheet_row_filter_ui.cc + spreadsheet_cache.hh spreadsheet_cell_value.hh spreadsheet_column.hh spreadsheet_column_values.hh diff --git a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc index a82648aeee0..73e0be76466 100644 --- a/source/blender/editors/space_spreadsheet/space_spreadsheet.cc +++ b/source/blender/editors/space_spreadsheet/space_spreadsheet.cc @@ -112,7 +112,7 @@ static void spreadsheet_free(SpaceLink *sl) { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl; - MEM_SAFE_FREE(sspreadsheet->runtime); + delete sspreadsheet->runtime; LISTBASE_FOREACH_MUTABLE (SpreadsheetRowFilter *, row_filter, &sspreadsheet->row_filters) { spreadsheet_row_filter_free(row_filter); @@ -129,8 +129,7 @@ static void spreadsheet_init(wmWindowManager *UNUSED(wm), ScrArea *area) { SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)area->spacedata.first; if (sspreadsheet->runtime == nullptr) { - sspreadsheet->runtime = (SpaceSpreadsheet_Runtime *)MEM_callocN( - sizeof(SpaceSpreadsheet_Runtime), __func__); + sspreadsheet->runtime = new SpaceSpreadsheet_Runtime(); } } @@ -138,7 +137,7 @@ static SpaceLink *spreadsheet_duplicate(SpaceLink *sl) { const SpaceSpreadsheet *sspreadsheet_old = (SpaceSpreadsheet *)sl; SpaceSpreadsheet *sspreadsheet_new = (SpaceSpreadsheet *)MEM_dupallocN(sspreadsheet_old); - sspreadsheet_new->runtime = (SpaceSpreadsheet_Runtime *)MEM_dupallocN(sspreadsheet_old->runtime); + sspreadsheet_new->runtime = new SpaceSpreadsheet_Runtime(*sspreadsheet_old->runtime); BLI_listbase_clear(&sspreadsheet_new->row_filters); LISTBASE_FOREACH (const SpreadsheetRowFilter *, src_filter, &sspreadsheet_old->row_filters) { @@ -294,16 +293,39 @@ static std::unique_ptr<DataSource> get_data_source(const bContext *C) return {}; } -static float get_column_width(const ColumnValues &values) +static float get_default_column_width(const ColumnValues &values) { - if (values.default_width > 0) { + if (values.default_width > 0.0f) { return values.default_width; } + static const float float_width = 3; + switch (values.type()) { + case SPREADSHEET_VALUE_TYPE_BOOL: + return 2.0f; + case SPREADSHEET_VALUE_TYPE_INT32: + return float_width; + case SPREADSHEET_VALUE_TYPE_FLOAT: + return float_width; + case SPREADSHEET_VALUE_TYPE_FLOAT2: + return 2.0f * float_width; + case SPREADSHEET_VALUE_TYPE_FLOAT3: + return 3.0f * float_width; + case SPREADSHEET_VALUE_TYPE_COLOR: + return 4.0f * float_width; + case SPREADSHEET_VALUE_TYPE_INSTANCES: + return 8.0f; + } + return float_width; +} + +static float get_column_width(const ColumnValues &values) +{ + float data_width = get_default_column_width(values); const int fontid = UI_style_get()->widget.uifont_id; BLF_size(fontid, UI_DEFAULT_TEXT_POINTS, U.dpi); const StringRefNull name = values.name(); const float name_width = BLF_width(fontid, name.data(), name.size()); - return std::max<float>(name_width / UI_UNIT_X + 1.0f, 3.0f); + return std::max<float>(name_width / UI_UNIT_X + 1.0f, data_width); } static float get_column_width_in_pixels(const ColumnValues &values) @@ -339,21 +361,28 @@ static void update_visible_columns(ListBase &columns, DataSource &data_source) } } - data_source.foreach_default_column_ids([&](const SpreadsheetColumnID &column_id) { - std::unique_ptr<ColumnValues> values = data_source.get_column_values(column_id); - if (values) { - if (used_ids.add(column_id)) { - SpreadsheetColumnID *new_id = spreadsheet_column_id_copy(&column_id); - SpreadsheetColumn *new_column = spreadsheet_column_new(new_id); - BLI_addtail(&columns, new_column); - } - } - }); + data_source.foreach_default_column_ids( + [&](const SpreadsheetColumnID &column_id, const bool is_extra) { + std::unique_ptr<ColumnValues> values = data_source.get_column_values(column_id); + if (values) { + if (used_ids.add(column_id)) { + SpreadsheetColumnID *new_id = spreadsheet_column_id_copy(&column_id); + SpreadsheetColumn *new_column = spreadsheet_column_new(new_id); + if (is_extra) { + BLI_addhead(&columns, new_column); + } + else { + BLI_addtail(&columns, new_column); + } + } + } + }); } static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); + sspreadsheet->runtime->cache.set_all_unused(); spreadsheet_update_context_path(C); std::unique_ptr<DataSource> data_source = get_data_source(C); @@ -394,6 +423,9 @@ static void spreadsheet_main_region_draw(const bContext *C, ARegion *region) ED_region_tag_redraw(footer); ARegion *sidebar = BKE_area_find_region_type(CTX_wm_area(C), RGN_TYPE_UI); ED_region_tag_redraw(sidebar); + + /* Free all cache items that have not been used. */ + sspreadsheet->runtime->cache.remove_all_unused(); } static void spreadsheet_main_region_listener(const wmRegionListenerParams *params) diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cache.cc b/source/blender/editors/space_spreadsheet/spreadsheet_cache.cc new file mode 100644 index 00000000000..2a399e018b6 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cache.cc @@ -0,0 +1,79 @@ +/* + * 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. + */ + +#include "spreadsheet_cache.hh" + +namespace blender::ed::spreadsheet { + +void SpreadsheetCache::add(std::unique_ptr<Key> key, std::unique_ptr<Value> value) +{ + key->is_used = true; + cache_map_.add_overwrite(*key, std::move(value)); + keys_.append(std::move(key)); +} + +SpreadsheetCache::Value *SpreadsheetCache::lookup(const Key &key) +{ + std::unique_ptr<Value> *value = cache_map_.lookup_ptr(key); + if (value == nullptr) { + return nullptr; + } + const Key &stored_cache_key = cache_map_.lookup_key(key); + stored_cache_key.is_used = true; + return value->get(); +} + +SpreadsheetCache::Value &SpreadsheetCache::lookup_or_add( + std::unique_ptr<Key> key, FunctionRef<std::unique_ptr<Value>()> create_value) +{ + Value *value = this->lookup(*key); + if (value != nullptr) { + return *value; + } + std::unique_ptr<Value> new_value = create_value(); + value = new_value.get(); + this->add(std::move(key), std::move(new_value)); + return *value; +} + +void SpreadsheetCache::set_all_unused() +{ + for (std::unique_ptr<Key> &key : keys_) { + key->is_used = false; + } +} + +void SpreadsheetCache::remove_all_unused() +{ + /* First remove the keys from the map and free the values. */ + for (auto it = cache_map_.keys().begin(); it != cache_map_.keys().end(); ++it) { + const Key &key = *it; + if (!key.is_used) { + cache_map_.remove(it); + } + } + /* Then free the keys. */ + for (int i = 0; i < keys_.size();) { + if (keys_[i]->is_used) { + i++; + } + else { + keys_.remove_and_reorder(i); + } + } +} + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_cache.hh b/source/blender/editors/space_spreadsheet/spreadsheet_cache.hh new file mode 100644 index 00000000000..d370bdab5c1 --- /dev/null +++ b/source/blender/editors/space_spreadsheet/spreadsheet_cache.hh @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#pragma once + +#include <atomic> + +#include "BLI_function_ref.hh" +#include "BLI_map.hh" +#include "BLI_vector.hh" + +namespace blender::ed::spreadsheet { + +/** + * A generic cache for the spreadsheet. Different data sources can cache custom data using custom + * keys. + * + * Elements are removed from the cache when they are not used during a redraw. + */ +class SpreadsheetCache { + public: + class Key { + public: + virtual ~Key() = default; + + mutable bool is_used = false; + + virtual uint64_t hash() const = 0; + + friend bool operator==(const Key &a, const Key &b) + { + return a.is_equal_to(b); + } + + private: + virtual bool is_equal_to(const Key &other) const = 0; + }; + + class Value { + public: + virtual ~Value() = default; + }; + + private: + Vector<std::unique_ptr<Key>> keys_; + Map<std::reference_wrapper<const Key>, std::unique_ptr<Value>> cache_map_; + + public: + /* Adding or looking up a key tags it as being used, so that it won't be removed. */ + void add(std::unique_ptr<Key> key, std::unique_ptr<Value> value); + Value *lookup(const Key &key); + Value &lookup_or_add(std::unique_ptr<Key> key, + FunctionRef<std::unique_ptr<Value>()> create_value); + + void set_all_unused(); + void remove_all_unused(); + + template<typename T> T &lookup_or_add(std::unique_ptr<Key> key) + { + return dynamic_cast<T &>( + this->lookup_or_add(std::move(key), []() { return std::make_unique<T>(); })); + } +}; + +} // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh index 68370cf6a44..877651d6530 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh @@ -97,9 +97,4 @@ std::unique_ptr<ColumnValues> column_values_from_function(const eSpreadsheetColu return column_values; } -static constexpr float default_float_column_width = 3; -static constexpr float default_float2_column_width = 2 * default_float_column_width; -static constexpr float default_float3_column_width = 3 * default_float_column_width; -static constexpr float default_color_column_width = 4 * default_float_column_width; - } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_context.cc b/source/blender/editors/space_spreadsheet/spreadsheet_context.cc index c38e765caee..e55a7cae6a6 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_context.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_context.cc @@ -373,6 +373,21 @@ void ED_spreadsheet_context_path_set_evaluated_object(SpaceSpreadsheet *sspreads BLI_addtail(&sspreadsheet->context_path, context); } +static bScreen *find_screen_to_search_for_context(wmWindow *window, + SpaceSpreadsheet *current_space) +{ + bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + if (ELEM(screen->state, SCREENMAXIMIZED, SCREENFULL)) { + /* If the spreadsheet is maximized, try to find the context in the unmaximized screen. */ + ScrArea *main_area = (ScrArea *)screen->areabase.first; + SpaceLink *sl = (SpaceLink *)main_area->spacedata.first; + if (sl == (SpaceLink *)current_space) { + return main_area->full; + } + } + return screen; +} + void ED_spreadsheet_context_path_guess(const bContext *C, SpaceSpreadsheet *sspreadsheet) { ED_spreadsheet_context_path_clear(sspreadsheet); @@ -385,9 +400,12 @@ void ED_spreadsheet_context_path_guess(const bContext *C, SpaceSpreadsheet *sspr if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { - bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + bScreen *screen = find_screen_to_search_for_context(window, sspreadsheet); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { SpaceLink *sl = (SpaceLink *)area->spacedata.first; + if (sl == nullptr) { + continue; + } if (sl->spacetype == SPACE_NODE) { SpaceNode *snode = (SpaceNode *)sl; if (snode->edittree != nullptr) { @@ -466,9 +484,12 @@ bool ED_spreadsheet_context_path_is_active(const bContext *C, SpaceSpreadsheet * } LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { - bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + bScreen *screen = find_screen_to_search_for_context(window, sspreadsheet); LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { SpaceLink *sl = (SpaceLink *)area->spacedata.first; + if (sl == nullptr) { + continue; + } if (sl->spacetype != SPACE_NODE) { continue; } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh index 2ea7fb5809f..873735c81e5 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh @@ -36,8 +36,12 @@ class DataSource { * Calls the callback with all the column ids that should be displayed as long as the user does * not manually add or remove columns. The column id can be stack allocated. Therefore, the * callback should not keep a reference to it (and copy it instead). + * + * The `is_extra` argument indicates that this column is special and should be drawn as the first + * column. (This can be made a bit more generic in the future when necessary.) */ - virtual void foreach_default_column_ids(FunctionRef<void(const SpreadsheetColumnID &)> fn) const + virtual void foreach_default_column_ids( + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const { UNUSED_VARS(fn); } diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc index 78d9f61d8d5..c1d345d1861 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc @@ -33,18 +33,99 @@ #include "NOD_geometry_nodes_eval_log.hh" +#include "FN_field_cpp_type.hh" + #include "bmesh.h" #include "spreadsheet_data_source_geometry.hh" #include "spreadsheet_intern.hh" namespace geo_log = blender::nodes::geometry_nodes_eval_log; +using blender::fn::GField; namespace blender::ed::spreadsheet { +static std::optional<eSpreadsheetColumnValueType> cpp_type_to_column_value_type( + const fn::CPPType &type) +{ + if (type.is<bool>()) { + return SPREADSHEET_VALUE_TYPE_BOOL; + } + if (type.is<int>()) { + return SPREADSHEET_VALUE_TYPE_INT32; + } + if (type.is<float>()) { + return SPREADSHEET_VALUE_TYPE_FLOAT; + } + if (type.is<float2>()) { + return SPREADSHEET_VALUE_TYPE_FLOAT2; + } + if (type.is<float3>()) { + return SPREADSHEET_VALUE_TYPE_FLOAT3; + } + if (type.is<ColorGeometry4f>()) { + return SPREADSHEET_VALUE_TYPE_COLOR; + } + return std::nullopt; +} + +void ExtraColumns::foreach_default_column_ids( + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const +{ + for (const auto &item : columns_.items()) { + SpreadsheetColumnID column_id; + column_id.name = (char *)item.key.c_str(); + fn(column_id, true); + } +} + +std::unique_ptr<ColumnValues> ExtraColumns::get_column_values( + const SpreadsheetColumnID &column_id) const +{ + const fn::GSpan *values = columns_.lookup_ptr(column_id.name); + if (values == nullptr) { + return {}; + } + eSpreadsheetColumnValueType column_type = *cpp_type_to_column_value_type(values->type()); + return column_values_from_function(column_type, + column_id.name, + values->size(), + [column_type, values](int index, CellValue &r_cell_value) { + const void *value = (*values)[index]; + switch (column_type) { + case SPREADSHEET_VALUE_TYPE_BOOL: + r_cell_value.value_bool = *(const bool *)value; + break; + case SPREADSHEET_VALUE_TYPE_INT32: + r_cell_value.value_int = *(const int *)value; + break; + case SPREADSHEET_VALUE_TYPE_FLOAT: + r_cell_value.value_float = *(const float *)value; + break; + case SPREADSHEET_VALUE_TYPE_FLOAT2: + r_cell_value.value_float2 = *(const float2 *)value; + break; + case SPREADSHEET_VALUE_TYPE_FLOAT3: + r_cell_value.value_float3 = *(const float3 *)value; + break; + case SPREADSHEET_VALUE_TYPE_COLOR: + r_cell_value.value_color = *( + const ColorGeometry4f *)value; + break; + case SPREADSHEET_VALUE_TYPE_INSTANCES: + break; + } + }); +} + void GeometryDataSource::foreach_default_column_ids( - FunctionRef<void(const SpreadsheetColumnID &)> fn) const + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const { + if (component_->attribute_domain_size(domain_) == 0) { + return; + } + + extra_columns_.foreach_default_column_ids(fn); component_->attribute_foreach( [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { if (meta_data.domain != domain_) { @@ -55,7 +136,7 @@ void GeometryDataSource::foreach_default_column_ids( } SpreadsheetColumnID column_id; column_id.name = (char *)attribute_id.name().data(); - fn(column_id); + fn(column_id, false); return true; }); } @@ -63,8 +144,17 @@ void GeometryDataSource::foreach_default_column_ids( std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( const SpreadsheetColumnID &column_id) const { + if (component_->attribute_domain_size(domain_) == 0) { + return {}; + } + std::lock_guard lock{mutex_}; + std::unique_ptr<ColumnValues> extra_column_values = extra_columns_.get_column_values(column_id); + if (extra_column_values) { + return extra_column_values; + } + bke::ReadAttributeLookup attribute = component_->attribute_try_get_for_read(column_id.name); if (!attribute) { return {}; @@ -86,14 +176,16 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( r_cell_value.value_float = value; }); case CD_PROP_INT32: - return column_values_from_function(SPREADSHEET_VALUE_TYPE_INT32, - column_id.name, - domain_size, - [varray](int index, CellValue &r_cell_value) { - int value; - varray->get(index, &value); - r_cell_value.value_int = value; - }); + return column_values_from_function( + SPREADSHEET_VALUE_TYPE_INT32, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + int value; + varray->get(index, &value); + r_cell_value.value_int = value; + }, + STREQ(column_id.name, "id") ? 5.5f : 0.0f); case CD_PROP_BOOL: return column_values_from_function(SPREADSHEET_VALUE_TYPE_BOOL, column_id.name, @@ -104,40 +196,34 @@ std::unique_ptr<ColumnValues> GeometryDataSource::get_column_values( r_cell_value.value_bool = value; }); case CD_PROP_FLOAT2: { - return column_values_from_function( - SPREADSHEET_VALUE_TYPE_FLOAT2, - column_id.name, - domain_size, - [varray](int index, CellValue &r_cell_value) { - float2 value; - varray->get(index, &value); - r_cell_value.value_float2 = value; - }, - default_float2_column_width); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT2, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + float2 value; + varray->get(index, &value); + r_cell_value.value_float2 = value; + }); } case CD_PROP_FLOAT3: { - return column_values_from_function( - SPREADSHEET_VALUE_TYPE_FLOAT3, - column_id.name, - domain_size, - [varray](int index, CellValue &r_cell_value) { - float3 value; - varray->get(index, &value); - r_cell_value.value_float3 = value; - }, - default_float3_column_width); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT3, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + float3 value; + varray->get(index, &value); + r_cell_value.value_float3 = value; + }); } case CD_PROP_COLOR: { - return column_values_from_function( - SPREADSHEET_VALUE_TYPE_COLOR, - column_id.name, - domain_size, - [varray](int index, CellValue &r_cell_value) { - ColorGeometry4f value; - varray->get(index, &value); - r_cell_value.value_color = value; - }, - default_color_column_width); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_COLOR, + column_id.name, + domain_size, + [varray](int index, CellValue &r_cell_value) { + ColorGeometry4f value; + varray->get(index, &value); + r_cell_value.value_color = value; + }); } default: break; @@ -293,18 +379,20 @@ void GeometryDataSource::apply_selection_filter(MutableSpan<bool> rows_included) } void InstancesDataSource::foreach_default_column_ids( - FunctionRef<void(const SpreadsheetColumnID &)> fn) const + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const { if (component_->instances_amount() == 0) { return; } + extra_columns_.foreach_default_column_ids(fn); + SpreadsheetColumnID column_id; column_id.name = (char *)"Name"; - fn(column_id); - for (const char *name : {"Position", "Rotation", "Scale", "ID"}) { + fn(column_id, false); + for (const char *name : {"Position", "Rotation", "Scale", "id"}) { column_id.name = (char *)name; - fn(column_id); + fn(column_id, false); } } @@ -315,6 +403,11 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( return {}; } + std::unique_ptr<ColumnValues> extra_column_values = extra_columns_.get_column_values(column_id); + if (extra_column_values) { + return extra_column_values; + } + const int size = this->tot_rows(); if (STREQ(column_id.name, "Name")) { Span<int> reference_handles = component_->instance_reference_handles(); @@ -346,7 +439,6 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( } } }); - values->default_width = 8.0f; return values; } Span<float4x4> transforms = component_->instance_transforms(); @@ -357,38 +449,35 @@ std::unique_ptr<ColumnValues> InstancesDataSource::get_column_values( size, [transforms](int index, CellValue &r_cell_value) { r_cell_value.value_float3 = transforms[index].translation(); - }, - default_float3_column_width); + }); } if (STREQ(column_id.name, "Rotation")) { - return column_values_from_function( - SPREADSHEET_VALUE_TYPE_FLOAT3, - column_id.name, - size, - [transforms](int index, CellValue &r_cell_value) { - r_cell_value.value_float3 = transforms[index].to_euler(); - }, - default_float3_column_width); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT3, + column_id.name, + size, + [transforms](int index, CellValue &r_cell_value) { + r_cell_value.value_float3 = transforms[index].to_euler(); + }); } if (STREQ(column_id.name, "Scale")) { - return column_values_from_function( - SPREADSHEET_VALUE_TYPE_FLOAT3, - column_id.name, - size, - [transforms](int index, CellValue &r_cell_value) { - r_cell_value.value_float3 = transforms[index].scale(); - }, - default_float3_column_width); + return column_values_from_function(SPREADSHEET_VALUE_TYPE_FLOAT3, + column_id.name, + size, + [transforms](int index, CellValue &r_cell_value) { + r_cell_value.value_float3 = transforms[index].scale(); + }); } Span<int> ids = component_->instance_ids(); - if (STREQ(column_id.name, "ID")) { - /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ - return column_values_from_function( - SPREADSHEET_VALUE_TYPE_INT32, - column_id.name, - size, - [ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; }, - 5.5f); + if (!ids.is_empty()) { + if (STREQ(column_id.name, "id")) { + /* Make the column a bit wider by default, since the IDs tend to be large numbers. */ + return column_values_from_function( + SPREADSHEET_VALUE_TYPE_INT32, + column_id.name, + size, + [ids](int index, CellValue &r_cell_value) { r_cell_value.value_int = ids[index]; }, + 5.5f); + } } return {}; } @@ -469,6 +558,36 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread return geometry_set; } +static void find_fields_to_evaluate(const SpaceSpreadsheet *sspreadsheet, + Map<std::string, GField> &r_fields) +{ + if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) { + return; + } + if (BLI_listbase_count(&sspreadsheet->context_path) <= 1) { + /* No viewer is currently referenced by the context path. */ + return; + } + const geo_log::NodeLog *node_log = geo_log::ModifierLog::find_node_by_spreadsheet_editor_context( + *sspreadsheet); + if (node_log == nullptr) { + return; + } + for (const geo_log::SocketLog &socket_log : node_log->input_logs()) { + const geo_log::ValueLog *value_log = socket_log.value(); + if (value_log == nullptr) { + continue; + } + if (const geo_log::GFieldValueLog *field_value_log = + dynamic_cast<const geo_log::GFieldValueLog *>(value_log)) { + const GField &field = field_value_log->field(); + if (field) { + r_fields.add("Viewer", std::move(field)); + } + } + } +} + static GeometryComponentType get_display_component_type(const bContext *C, Object *object_eval) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); @@ -481,6 +600,69 @@ static GeometryComponentType get_display_component_type(const bContext *C, Objec return GEO_COMPONENT_TYPE_MESH; } +class GeometryComponentCacheKey : public SpreadsheetCache::Key { + public: + /* Use the pointer to the geometry component as a key to detect when the geometry changed. */ + const GeometryComponent *component; + + GeometryComponentCacheKey(const GeometryComponent &component) : component(&component) + { + } + + uint64_t hash() const override + { + return get_default_hash(this->component); + } + + bool is_equal_to(const Key &other) const override + { + if (const GeometryComponentCacheKey *other_geo = + dynamic_cast<const GeometryComponentCacheKey *>(&other)) { + return this->component == other_geo->component; + } + return false; + } +}; + +class GeometryComponentCacheValue : public SpreadsheetCache::Value { + public: + /* Stores the result of fields evaluated on a geometry component. Without this, fields would have + * to be reevaluated on every redraw. */ + Map<std::pair<AttributeDomain, GField>, fn::GArray<>> arrays; +}; + +static void add_fields_as_extra_columns(SpaceSpreadsheet *sspreadsheet, + const GeometryComponent &component, + ExtraColumns &r_extra_columns) +{ + Map<std::string, GField> fields_to_show; + find_fields_to_evaluate(sspreadsheet, fields_to_show); + + GeometryComponentCacheValue &cache = + sspreadsheet->runtime->cache.lookup_or_add<GeometryComponentCacheValue>( + std::make_unique<GeometryComponentCacheKey>(component)); + + const AttributeDomain domain = (AttributeDomain)sspreadsheet->attribute_domain; + const int domain_size = component.attribute_domain_size(domain); + for (const auto &item : fields_to_show.items()) { + StringRef name = item.key; + const GField &field = item.value; + + /* Use the cached evaluated array if it exists, otherwise evaluate the field now. */ + fn::GArray<> &evaluated_array = cache.arrays.lookup_or_add_cb({domain, field}, [&]() { + fn::GArray<> evaluated_array(field.cpp_type(), domain_size); + + bke::GeometryComponentFieldContext field_context{component, domain}; + fn::FieldEvaluator field_evaluator{field_context, domain_size}; + field_evaluator.add_with_destination(field, evaluated_array); + field_evaluator.evaluate(); + return evaluated_array; + }); + + r_extra_columns.add(std::move(name), evaluated_array.as_span()); + } +} + std::unique_ptr<DataSource> data_source_from_geometry(const bContext *C, Object *object_eval) { SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C); @@ -493,10 +675,15 @@ std::unique_ptr<DataSource> data_source_from_geometry(const bContext *C, Object return {}; } + const GeometryComponent &component = *geometry_set.get_component_for_read(component_type); + ExtraColumns extra_columns; + add_fields_as_extra_columns(sspreadsheet, component, extra_columns); + if (component_type == GEO_COMPONENT_TYPE_INSTANCES) { - return std::make_unique<InstancesDataSource>(geometry_set); + return std::make_unique<InstancesDataSource>(geometry_set, std::move(extra_columns)); } - return std::make_unique<GeometryDataSource>(object_eval, geometry_set, component_type, domain); + return std::make_unique<GeometryDataSource>( + object_eval, geometry_set, component_type, domain, std::move(extra_columns)); } } // namespace blender::ed::spreadsheet diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh index d1b5dc6845e..6c88a94f585 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh @@ -28,12 +28,34 @@ struct bContext; namespace blender::ed::spreadsheet { +/** + * Contains additional named columns that should be displayed that are not stored on the geometry + * directly. This is used for displaying the evaluated fields connected to a viewer node. + */ +class ExtraColumns { + private: + /** Maps column names to their data. The data is actually stored in the spreadsheet cache. */ + Map<std::string, fn::GSpan> columns_; + + public: + void add(std::string name, fn::GSpan data) + { + columns_.add(std::move(name), data); + } + + void foreach_default_column_ids( + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const; + + std::unique_ptr<ColumnValues> get_column_values(const SpreadsheetColumnID &column_id) const; +}; + class GeometryDataSource : public DataSource { private: Object *object_eval_; const GeometrySet geometry_set_; const GeometryComponent *component_; AttributeDomain domain_; + ExtraColumns extra_columns_; /* Some data is computed on the fly only when it is requested. Computing it does not change the * logical state of this data source. Therefore, the corresponding methods are const and need to @@ -45,11 +67,13 @@ class GeometryDataSource : public DataSource { GeometryDataSource(Object *object_eval, GeometrySet geometry_set, const GeometryComponentType component_type, - const AttributeDomain domain) + const AttributeDomain domain, + ExtraColumns extra_columns) : object_eval_(object_eval), geometry_set_(std::move(geometry_set)), component_(geometry_set_.get_component_for_read(component_type)), - domain_(domain) + domain_(domain), + extra_columns_(std::move(extra_columns)) { } @@ -62,7 +86,7 @@ class GeometryDataSource : public DataSource { void apply_selection_filter(MutableSpan<bool> rows_included) const; void foreach_default_column_ids( - FunctionRef<void(const SpreadsheetColumnID &)> fn) const override; + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const override; std::unique_ptr<ColumnValues> get_column_values( const SpreadsheetColumnID &column_id) const override; @@ -73,16 +97,18 @@ class GeometryDataSource : public DataSource { class InstancesDataSource : public DataSource { const GeometrySet geometry_set_; const InstancesComponent *component_; + ExtraColumns extra_columns_; public: - InstancesDataSource(GeometrySet geometry_set) + InstancesDataSource(GeometrySet geometry_set, ExtraColumns extra_columns) : geometry_set_(std::move(geometry_set)), - component_(geometry_set_.get_component_for_read<InstancesComponent>()) + component_(geometry_set_.get_component_for_read<InstancesComponent>()), + extra_columns_(std::move(extra_columns)) { } void foreach_default_column_ids( - FunctionRef<void(const SpreadsheetColumnID &)> fn) const override; + FunctionRef<void(const SpreadsheetColumnID &, bool is_extra)> fn) const override; std::unique_ptr<ColumnValues> get_column_values( const SpreadsheetColumnID &column_id) const override; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh b/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh index 8be5283fd63..8b050c2e69b 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh +++ b/source/blender/editors/space_spreadsheet/spreadsheet_intern.hh @@ -17,12 +17,24 @@ #pragma once #include "BKE_geometry_set.hh" +#include "spreadsheet_cache.hh" -typedef struct SpaceSpreadsheet_Runtime { - int visible_rows; - int tot_rows; - int tot_columns; -} SpaceSpreadsheet_Runtime; +struct SpaceSpreadsheet_Runtime { + public: + int visible_rows = 0; + int tot_rows = 0; + int tot_columns = 0; + + blender::ed::spreadsheet::SpreadsheetCache cache; + + SpaceSpreadsheet_Runtime() = default; + + /* The cache is not copied currently. */ + SpaceSpreadsheet_Runtime(const SpaceSpreadsheet_Runtime &other) + : visible_rows(other.visible_rows), tot_rows(other.tot_rows), tot_columns(other.tot_columns) + { + } +}; struct bContext; diff --git a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc index 1a5eac53306..355899be279 100644 --- a/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc +++ b/source/blender/editors/space_spreadsheet/spreadsheet_layout.cc @@ -93,7 +93,9 @@ class SpreadsheetLayoutDrawer : public SpreadsheetDrawer { const int real_index = spreadsheet_layout_.row_indices[row_index]; const ColumnValues &column = *spreadsheet_layout_.columns[column_index].values; CellValue cell_value; - column.get_value(real_index, cell_value); + if (real_index < column.size()) { + column.get_value(real_index, cell_value); + } if (cell_value.value_int.has_value()) { const int value = *cell_value.value_int; diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c index 7999018a6b6..6acf51aec6e 100644 --- a/source/blender/editors/space_view3d/space_view3d.c +++ b/source/blender/editors/space_view3d/space_view3d.c @@ -40,12 +40,14 @@ #include "BLT_translation.h" +#include "BKE_asset.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_global.h" #include "BKE_icons.h" #include "BKE_idprop.h" #include "BKE_lattice.h" +#include "BKE_layer.h" #include "BKE_main.h" #include "BKE_mball.h" #include "BKE_mesh.h" @@ -55,6 +57,7 @@ #include "BKE_workspace.h" #include "ED_object.h" +#include "ED_outliner.h" #include "ED_render.h" #include "ED_screen.h" #include "ED_space_api.h" @@ -82,6 +85,7 @@ #endif #include "DEG_depsgraph.h" +#include "DEG_depsgraph_build.h" #include "view3d_intern.h" /* own include */ @@ -515,10 +519,74 @@ static bool view3d_drop_id_in_main_region_poll(bContext *C, return WM_drag_is_ID_type(drag, id_type); } +static void view3d_ob_drop_draw_activate(struct wmDropBox *drop, wmDrag *drag) +{ + V3DSnapCursorState *state = drop->draw_data; + if (state) { + return; + } + + /* Don't use the snap cursor when linking the object. Object transform isn't editable then and + * would be reset on reload. */ + if (WM_drag_asset_will_import_linked(drag)) { + return; + } + + state = drop->draw_data = ED_view3d_cursor_snap_active(); + state->draw_plane = true; + + float dimensions[3] = {0.0f}; + if (drag->type == WM_DRAG_ID) { + Object *ob = (Object *)WM_drag_get_local_ID(drag, ID_OB); + BKE_object_dimensions_get(ob, dimensions); + } + else { + struct AssetMetaData *meta_data = WM_drag_get_asset_meta_data(drag, ID_OB); + IDProperty *dimensions_prop = BKE_asset_metadata_idprop_find(meta_data, "dimensions"); + if (dimensions_prop) { + copy_v3_v3(dimensions, IDP_Array(dimensions_prop)); + } + } + + if (!is_zero_v3(dimensions)) { + mul_v3_v3fl(state->box_dimensions, dimensions, 0.5f); + UI_GetThemeColor4ubv(TH_GIZMO_PRIMARY, state->color_box); + state->draw_box = true; + } +} + +static void view3d_ob_drop_draw_deactivate(struct wmDropBox *drop, wmDrag *UNUSED(drag)) +{ + V3DSnapCursorState *state = drop->draw_data; + if (state) { + ED_view3d_cursor_snap_deactive(state); + drop->draw_data = NULL; + } +} + static bool view3d_ob_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { return view3d_drop_id_in_main_region_poll(C, drag, event, ID_OB); } +static bool view3d_ob_drop_poll_external_asset(bContext *C, wmDrag *drag, const wmEvent *event) +{ + if (!view3d_ob_drop_poll(C, drag, event) || (drag->type != WM_DRAG_ASSET)) { + return false; + } + return true; +} + +/** + * \note the term local here refers to not being an external asset, + * poll will succeed for linked library objects. + */ +static bool view3d_ob_drop_poll_local_id(bContext *C, wmDrag *drag, const wmEvent *event) +{ + if (!view3d_ob_drop_poll(C, drag, event) || (drag->type != WM_DRAG_ID)) { + return false; + } + return true; +} static bool view3d_collection_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) { @@ -532,12 +600,17 @@ static bool view3d_mat_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event static char *view3d_mat_drop_tooltip(bContext *C, wmDrag *drag, - const wmEvent *event, + const int xy[2], struct wmDropBox *drop) { const char *name = WM_drag_get_item_name(drag); + ARegion *region = CTX_wm_region(C); RNA_string_set(drop->ptr, "name", name); - return ED_object_ot_drop_named_material_tooltip(C, drop->ptr, event); + int mval[2] = { + xy[0] - region->winrct.xmin, + xy[1] - region->winrct.ymin, + }; + return ED_object_ot_drop_named_material_tooltip(C, drop->ptr, mval); } static bool view3d_world_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) @@ -556,7 +629,7 @@ static bool view3d_object_data_drop_poll(bContext *C, wmDrag *drag, const wmEven static char *view3d_object_data_drop_tooltip(bContext *UNUSED(C), wmDrag *UNUSED(drag), - const wmEvent *UNUSED(event), + const int UNUSED(xy[2]), wmDropBox *UNUSED(drop)) { return BLI_strdup(TIP_("Create object instance from object-data")); @@ -626,14 +699,85 @@ static bool view3d_volume_drop_poll(bContext *UNUSED(C), return (drag->type == WM_DRAG_PATH) && (drag->icon == ICON_FILE_VOLUME); } -static void view3d_ob_drop_copy(wmDrag *drag, wmDropBox *drop) +static void view3d_ob_drop_matrix_from_snap(V3DSnapCursorState *snap_state, + Object *ob, + float obmat_final[4][4]) { - ID *id = WM_drag_get_local_ID_or_import_from_asset(drag, ID_OB); + V3DSnapCursorData *snap_data; + snap_data = ED_view3d_cursor_snap_data_get(snap_state, NULL, 0, 0); + BLI_assert(snap_state->draw_box || snap_state->draw_plane); + copy_m4_m3(obmat_final, snap_data->plane_omat); + copy_v3_v3(obmat_final[3], snap_data->loc); + + float scale[3]; + mat4_to_size(scale, ob->obmat); + rescale_m4(obmat_final, scale); + + BoundBox *bb = BKE_object_boundbox_get(ob); + if (bb) { + float offset[3]; + BKE_boundbox_calc_center_aabb(bb, offset); + offset[2] = bb->vec[0][2]; + mul_mat3_m4_v3(obmat_final, offset); + sub_v3_v3(obmat_final[3], offset); + } +} + +static void view3d_ob_drop_copy_local_id(wmDrag *drag, wmDropBox *drop) +{ + ID *id = WM_drag_get_local_ID(drag, ID_OB); RNA_string_set(drop->ptr, "name", id->name + 2); /* Don't duplicate ID's which were just imported. Only do that for existing, local IDs. */ - const bool is_imported_id = drag->type == WM_DRAG_ASSET; - RNA_boolean_set(drop->ptr, "duplicate", !is_imported_id); + BLI_assert(drag->type != WM_DRAG_ASSET); + + V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_state_get(); + float obmat_final[4][4]; + + view3d_ob_drop_matrix_from_snap(snap_state, (Object *)id, obmat_final); + + RNA_float_set_array(drop->ptr, "matrix", &obmat_final[0][0]); +} + +static void view3d_ob_drop_copy_external_asset(wmDrag *drag, wmDropBox *drop) +{ + /* NOTE(@campbellbarton): Selection is handled here, de-selecting objects before append, + * using auto-select to ensure the new objects are selected. + * This is done so #OBJECT_OT_transform_to_mouse (which runs after this drop handler) + * can use the context setup here to place the objects. */ + BLI_assert(drag->type == WM_DRAG_ASSET); + + wmDragAsset *asset_drag = WM_drag_get_asset_data(drag, 0); + bContext *C = asset_drag->evil_C; + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + + BKE_view_layer_base_deselect_all(view_layer); + + ID *id = WM_drag_asset_id_import(asset_drag, FILE_AUTOSELECT); + + /* TODO(sergey): Only update relations for the current scene. */ + DEG_relations_tag_update(CTX_data_main(C)); + WM_event_add_notifier(C, NC_SCENE | ND_LAYER_CONTENT, scene); + + RNA_string_set(drop->ptr, "name", id->name + 2); + + Base *base = BKE_view_layer_base_find(view_layer, (Object *)id); + if (base != NULL) { + BKE_view_layer_base_select_and_set_active(view_layer, base); + WM_main_add_notifier(NC_SCENE | ND_OB_ACTIVE, scene); + } + DEG_id_tag_update(&scene->id, ID_RECALC_SELECT); + ED_outliner_select_sync_from_object_tag(C); + + V3DSnapCursorState *snap_state = drop->draw_data; + if (snap_state) { + float obmat_final[4][4]; + + view3d_ob_drop_matrix_from_snap(snap_state, (Object *)id, obmat_final); + + RNA_float_set_array(drop->ptr, "matrix", &obmat_final[0][0]); + } } static void view3d_collection_drop_copy(wmDrag *drag, wmDropBox *drop) @@ -698,12 +842,31 @@ static void view3d_dropboxes(void) { ListBase *lb = WM_dropboxmap_find("View3D", SPACE_VIEW3D, RGN_TYPE_WINDOW); - WM_dropbox_add(lb, - "OBJECT_OT_add_named", - view3d_ob_drop_poll, - view3d_ob_drop_copy, - WM_drag_free_imported_drag_ID, - NULL); + struct wmDropBox *drop; + drop = WM_dropbox_add(lb, + "OBJECT_OT_add_named", + view3d_ob_drop_poll_local_id, + view3d_ob_drop_copy_local_id, + WM_drag_free_imported_drag_ID, + NULL); + + drop->draw = WM_drag_draw_item_name_fn; + drop->draw_activate = view3d_ob_drop_draw_activate; + drop->draw_deactivate = view3d_ob_drop_draw_deactivate; + drop->opcontext = WM_OP_EXEC_DEFAULT; /* Not really needed. */ + + drop = WM_dropbox_add(lb, + "OBJECT_OT_transform_to_mouse", + view3d_ob_drop_poll_external_asset, + view3d_ob_drop_copy_external_asset, + WM_drag_free_imported_drag_ID, + NULL); + + drop->draw = WM_drag_draw_item_name_fn; + drop->draw_activate = view3d_ob_drop_draw_activate; + drop->draw_deactivate = view3d_ob_drop_draw_deactivate; + drop->opcontext = WM_OP_INVOKE_DEFAULT; + WM_dropbox_add(lb, "OBJECT_OT_drop_named_material", view3d_mat_drop_poll, @@ -1616,6 +1779,7 @@ static void space_view3d_refresh(const bContext *C, ScrArea *area) const char *view3d_context_dir[] = { "active_object", + "selected_ids", NULL, }; @@ -1626,8 +1790,9 @@ static int view3d_context(const bContext *C, const char *member, bContextDataRes if (CTX_data_dir(member)) { CTX_data_dir_set(result, view3d_context_dir); + return CTX_RESULT_OK; } - else if (CTX_data_equals(member, "active_object")) { + if (CTX_data_equals(member, "active_object")) { /* In most cases the active object is the `view_layer->basact->object`. * For the 3D view however it can be NULL when hidden. * @@ -1651,13 +1816,21 @@ static int view3d_context(const bContext *C, const char *member, bContextDataRes } } - return 1; + return CTX_RESULT_OK; } - else { - return 0; /* not found */ + if (CTX_data_equals(member, "selected_ids")) { + ListBase selected_objects; + CTX_data_selected_objects(C, &selected_objects); + LISTBASE_FOREACH (CollectionPointerLink *, object_ptr_link, &selected_objects) { + ID *selected_id = object_ptr_link->ptr.owner_id; + CTX_data_id_list_add(result, selected_id); + } + BLI_freelistN(&selected_objects); + CTX_data_type_set(result, CTX_DATA_TYPE_COLLECTION); + return CTX_RESULT_OK; } - return -1; /* found but not available */ + return CTX_RESULT_MEMBER_NOT_FOUND; } static void view3d_id_remap(ScrArea *area, SpaceLink *slink, ID *old_id, ID *new_id) diff --git a/source/blender/editors/space_view3d/view3d_cursor_snap.c b/source/blender/editors/space_view3d/view3d_cursor_snap.c index 1cb650910ce..baf61befcba 100644 --- a/source/blender/editors/space_view3d/view3d_cursor_snap.c +++ b/source/blender/editors/space_view3d/view3d_cursor_snap.c @@ -37,6 +37,7 @@ #include "BKE_main.h" #include "BKE_object.h" #include "BKE_scene.h" +#include "BKE_screen.h" #include "GPU_immediate.h" #include "GPU_matrix.h" @@ -54,27 +55,25 @@ #include "WM_api.h" -#define STATE_LEN 3 +#define STATE_INTERN_GET(state) \ + (SnapStateIntern *)((char *)state - offsetof(SnapStateIntern, snap_state)) typedef struct SnapStateIntern { + struct SnapStateIntern *next, *prev; V3DSnapCursorState snap_state; - float prevpoint_stack[3]; - int state_active_prev; - bool is_active; } SnapStateIntern; typedef struct SnapCursorDataIntern { V3DSnapCursorState state_default; - SnapStateIntern state_intern[STATE_LEN]; + ListBase state_intern; V3DSnapCursorData snap_data; - int state_active_len; - int state_active; - struct SnapObjectContext *snap_context_v3d; const Scene *scene; short snap_elem_hidden; + float prevpoint_stack[3]; + /* Copy of the parameters of the last event state in order to detect updates. */ struct { int x; @@ -94,17 +93,6 @@ typedef struct SnapCursorDataIntern { bool is_initiated; } SnapCursorDataIntern; -static void UNUSED_FUNCTION(v3d_cursor_snap_state_init)(V3DSnapCursorState *state) -{ - state->prevpoint = NULL; - state->snap_elem_force = (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | SCE_SNAP_MODE_FACE | - SCE_SNAP_MODE_EDGE_PERPENDICULAR | SCE_SNAP_MODE_EDGE_MIDPOINT); - state->plane_axis = 2; - rgba_uchar_args_set(state->color_point, 255, 255, 255, 255); - rgba_uchar_args_set(state->color_line, 255, 255, 255, 128); - state->draw_point = true; - state->draw_plane = false; -} static SnapCursorDataIntern g_data_intern = { .state_default = {.prevpoint = NULL, .snap_elem_force = (SCE_SNAP_MODE_VERTEX | SCE_SNAP_MODE_EDGE | @@ -113,8 +101,9 @@ static SnapCursorDataIntern g_data_intern = { .plane_axis = 2, .color_point = {255, 255, 255, 255}, .color_line = {255, 255, 255, 128}, - .draw_point = true, - .draw_plane = false}}; + .color_box = {255, 255, 255, 128}, + .box_dimensions = {1.0f, 1.0f, 1.0f}, + .draw_point = true}}; /** * Calculate a 3x3 orientation matrix from the surface under the cursor. @@ -373,6 +362,24 @@ static void v3d_cursor_plane_draw(const RegionView3D *rv3d, } } +static void cursor_box_draw(const float dimensions[3], uchar color[4]) +{ + GPUVertFormat *format = immVertexFormat(); + const uint pos_id = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT); + + GPU_blend(GPU_BLEND_ALPHA); + GPU_line_smooth(true); + GPU_line_width(1.0f); + + immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); + immUniformColor4ubv(color); + imm_draw_cube_corners_3d(pos_id, (const float[3]){0.0f, 0.0f, dimensions[2]}, dimensions, 0.15f); + immUnbindProgram(); + + GPU_line_smooth(false); + GPU_blend(GPU_BLEND_NONE); +} + void ED_view3d_cursor_snap_draw_util(RegionView3D *rv3d, const float loc_prev[3], const float loc_curr[3], @@ -601,7 +608,7 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, ushort snap_elements = v3d_cursor_snap_elements(state, scene); data_intern->snap_elem_hidden = 0; - const bool draw_plane = state->draw_plane; + const bool draw_plane = state->draw_plane || state->draw_box; if (draw_plane && !(snap_elements & SCE_SNAP_MODE_FACE)) { data_intern->snap_elem_hidden = SCE_SNAP_MODE_FACE; snap_elements |= SCE_SNAP_MODE_FACE; @@ -674,6 +681,7 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, } if (draw_plane) { + RegionView3D *rv3d = region->regiondata; bool orient_surface = snap_elem && (state->plane_orient == V3D_PLACE_ORIENT_SURFACE); if (orient_surface) { copy_m3_m4(omat, obmat); @@ -686,7 +694,6 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, ED_transform_calc_orientation_from_type_ex( scene, view_layer, v3d, region->regiondata, ob, ob, orient_index, pivot_point, omat); - RegionView3D *rv3d = region->regiondata; if (state->use_plane_axis_auto) { mat3_align_axis_to_v3(omat, state->plane_axis, rv3d->viewinv[2]); } @@ -699,6 +706,9 @@ static void v3d_cursor_snap_update(V3DSnapCursorState *state, orthogonalize_m3(omat, state->plane_axis); if (orient_surface) { + if (dot_v3v3(rv3d->viewinv[2], face_nor) < 0.0f) { + negate_v3(face_nor); + } v3d_cursor_poject_surface_normal(face_nor, obmat, omat); } } @@ -755,16 +765,12 @@ static bool v3d_cursor_snap_pool_fn(bContext *C) return false; } - ARegion *region = CTX_wm_region(C); - if (region->regiontype != RGN_TYPE_WINDOW) { - return false; - } - ScrArea *area = CTX_wm_area(C); if (area->spacetype != SPACE_VIEW3D) { return false; } + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); RegionView3D *rv3d = region->regiondata; if (rv3d->rflag & RV3D_NAVIGATING) { /* Don't draw the cursor while navigating. It can be distracting. */ @@ -781,7 +787,8 @@ static void v3d_cursor_snap_draw_fn(bContext *C, int x, int y, void *UNUSED(cust V3DSnapCursorData *snap_data = &data_intern->snap_data; wmWindowManager *wm = CTX_wm_manager(C); - ARegion *region = CTX_wm_region(C); + ScrArea *area = CTX_wm_area(C); + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); x -= region->winrct.xmin; y -= region->winrct.ymin; if (v3d_cursor_eventstate_has_changed(data_intern, state, wm, x, y)) { @@ -791,7 +798,7 @@ static void v3d_cursor_snap_draw_fn(bContext *C, int x, int y, void *UNUSED(cust v3d_cursor_snap_update(state, C, wm, depsgraph, scene, region, v3d, x, y); } - const bool draw_plane = state->draw_plane; + const bool draw_plane = state->draw_plane || state->draw_box; if (!snap_data->snap_elem && !draw_plane) { return; } @@ -802,8 +809,6 @@ static void v3d_cursor_snap_draw_fn(bContext *C, int x, int y, void *UNUSED(cust GPU_matrix_projection_set(rv3d->winmat); GPU_matrix_set(rv3d->viewmat); - GPU_blend(GPU_BLEND_ALPHA); - float matrix[4][4]; if (draw_plane) { copy_m4_m3(matrix, snap_data->plane_omat); @@ -812,7 +817,7 @@ static void v3d_cursor_snap_draw_fn(bContext *C, int x, int y, void *UNUSED(cust v3d_cursor_plane_draw(rv3d, state->plane_axis, matrix); } - if (snap_data->snap_elem && state->draw_point) { + if (snap_data->snap_elem && (state->draw_point || state->draw_box)) { const float *prev_point = (snap_data->snap_elem & SCE_SNAP_MODE_EDGE_PERPENDICULAR) ? state->prevpoint : NULL; @@ -829,7 +834,10 @@ static void v3d_cursor_snap_draw_fn(bContext *C, int x, int y, void *UNUSED(cust snap_data->snap_elem); } - GPU_blend(GPU_BLEND_NONE); + if (state->draw_box) { + GPU_matrix_mul(matrix); + cursor_box_draw(state->box_dimensions, state->color_box); + } /* Restore matrix. */ wmWindowViewport(CTX_wm_window(C)); @@ -839,10 +847,11 @@ static void v3d_cursor_snap_draw_fn(bContext *C, int x, int y, void *UNUSED(cust V3DSnapCursorState *ED_view3d_cursor_snap_state_get(void) { - if (!g_data_intern.state_active_len) { + SnapCursorDataIntern *data_intern = &g_data_intern; + if (BLI_listbase_is_empty(&data_intern->state_intern)) { return &g_data_intern.state_default; } - return (V3DSnapCursorState *)&g_data_intern.state_intern[g_data_intern.state_active]; + return &((SnapStateIntern *)data_intern->state_intern.last)->snap_state; } static void v3d_cursor_snap_activate(void) @@ -872,20 +881,16 @@ static void v3d_cursor_snap_activate(void) static void v3d_cursor_snap_free(void) { SnapCursorDataIntern *data_intern = &g_data_intern; - if (data_intern->handle && G_MAIN->wm.first) { - WM_paint_cursor_end(data_intern->handle); + if (data_intern->handle) { + if (G_MAIN->wm.first) { + WM_paint_cursor_end(data_intern->handle); + } data_intern->handle = NULL; } if (data_intern->snap_context_v3d) { ED_transform_snap_object_context_destroy(data_intern->snap_context_v3d); data_intern->snap_context_v3d = NULL; } - - for (SnapStateIntern *state_intern = data_intern->state_intern; - state_intern < &data_intern->state_intern[STATE_LEN]; - state_intern++) { - state_intern->is_active = false; - } } void ED_view3d_cursor_snap_state_default_set(V3DSnapCursorState *state) @@ -896,56 +901,41 @@ void ED_view3d_cursor_snap_state_default_set(V3DSnapCursorState *state) V3DSnapCursorState *ED_view3d_cursor_snap_active(void) { SnapCursorDataIntern *data_intern = &g_data_intern; - if (!data_intern->state_active_len) { + if (!data_intern->handle) { v3d_cursor_snap_activate(); } - data_intern->state_active_len++; - for (int i = 0; i < STATE_LEN; i++) { - SnapStateIntern *state_intern = &g_data_intern.state_intern[i]; - if (!state_intern->is_active) { - state_intern->snap_state = g_data_intern.state_default; - state_intern->is_active = true; - state_intern->state_active_prev = data_intern->state_active; - data_intern->state_active = i; - return (V3DSnapCursorState *)state_intern; - } - } + SnapStateIntern *state_intern = MEM_mallocN(sizeof(*state_intern), __func__); + state_intern->snap_state = g_data_intern.state_default; + BLI_addtail(&g_data_intern.state_intern, state_intern); - BLI_assert(false); - data_intern->state_active_len--; - return NULL; + return (V3DSnapCursorState *)&state_intern->snap_state; } void ED_view3d_cursor_snap_deactive(V3DSnapCursorState *state) { SnapCursorDataIntern *data_intern = &g_data_intern; - if (!data_intern->state_active_len) { - BLI_assert(false); - return; - } - - SnapStateIntern *state_intern = (SnapStateIntern *)state; - if (!state_intern->is_active) { + if (BLI_listbase_is_empty(&data_intern->state_intern)) { return; } - state_intern->is_active = false; - data_intern->state_active_len--; - if (!data_intern->state_active_len) { + SnapStateIntern *state_intern = STATE_INTERN_GET(state); + BLI_remlink(&data_intern->state_intern, state_intern); + MEM_freeN(state_intern); + if (BLI_listbase_is_empty(&data_intern->state_intern)) { v3d_cursor_snap_free(); } - else { - data_intern->state_active = state_intern->state_active_prev; - } } void ED_view3d_cursor_snap_prevpoint_set(V3DSnapCursorState *state, const float prev_point[3]) { - SnapStateIntern *state_intern = (SnapStateIntern *)state; + SnapCursorDataIntern *data_intern = &g_data_intern; + if (!state) { + state = ED_view3d_cursor_snap_state_get(); + } if (prev_point) { - copy_v3_v3(state_intern->prevpoint_stack, prev_point); - state->prevpoint = state_intern->prevpoint_stack; + copy_v3_v3(data_intern->prevpoint_stack, prev_point); + state->prevpoint = data_intern->prevpoint_stack; } else { state->prevpoint = NULL; @@ -958,12 +948,13 @@ V3DSnapCursorData *ED_view3d_cursor_snap_data_get(V3DSnapCursorState *state, const int y) { SnapCursorDataIntern *data_intern = &g_data_intern; - if (C && data_intern->state_active_len) { + if (C) { wmWindowManager *wm = CTX_wm_manager(C); if (v3d_cursor_eventstate_has_changed(data_intern, state, wm, x, y)) { Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = DEG_get_input_scene(depsgraph); - ARegion *region = CTX_wm_region(C); + ScrArea *area = CTX_wm_area(C); + ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW); View3D *v3d = CTX_wm_view3d(C); if (!state) { @@ -982,8 +973,3 @@ struct SnapObjectContext *ED_view3d_cursor_snap_context_ensure(Scene *scene) v3d_cursor_snap_context_ensure(scene); return data_intern->snap_context_v3d; } - -void ED_view3d_cursor_snap_exit(void) -{ - v3d_cursor_snap_free(); -} diff --git a/source/blender/editors/space_view3d/view3d_draw.c b/source/blender/editors/space_view3d/view3d_draw.c index fe347e89600..fceb6553cab 100644 --- a/source/blender/editors/space_view3d/view3d_draw.c +++ b/source/blender/editors/space_view3d/view3d_draw.c @@ -347,6 +347,8 @@ static void view3d_xr_mirror_setup(const wmWindowManager *wm, (wm->xr.session_settings.draw_flags & V3D_OFSDRAW_XR_SHOW_CUSTOM_OVERLAYS) != 0, V3D_XR_SHOW_CUSTOM_OVERLAYS); + /* Hide navigation gizmo since it gets distorted if the view matrix has a scale factor. */ + v3d->gizmo_flag |= V3D_GIZMO_HIDE_NAVIGATE; /* Reset overridden View3D data. */ v3d->lens = lens_old; diff --git a/source/blender/editors/space_view3d/view3d_placement.c b/source/blender/editors/space_view3d/view3d_placement.c index 7fe97705765..572fc8e3156 100644 --- a/source/blender/editors/space_view3d/view3d_placement.c +++ b/source/blender/editors/space_view3d/view3d_placement.c @@ -742,16 +742,19 @@ static void view3d_interactive_add_begin(bContext *C, wmOperator *op, const wmEv ipd->launch_event = WM_userdef_event_type_from_keymap_type(event->type); - ipd->snap_state = ED_view3d_cursor_snap_active(); - ipd->snap_state->draw_point = true; - ipd->snap_state->draw_plane = true; + V3DSnapCursorState *snap_state_new = ED_view3d_cursor_snap_active(); + if (snap_state_new) { + ipd->snap_state = snap_state = snap_state_new; + } + snap_state->draw_point = true; + snap_state->draw_plane = true; ipd->is_snap_found = view3d_interactive_add_calc_snap( C, event, ipd->co_src, ipd->matrix_orient, &ipd->use_snap, &ipd->is_snap_invert) != 0; - ipd->snap_state->draw_plane = false; - ED_view3d_cursor_snap_prevpoint_set(ipd->snap_state, ipd->co_src); + snap_state->draw_plane = false; + ED_view3d_cursor_snap_prevpoint_set(snap_state, ipd->co_src); ipd->orient_axis = plane_axis; for (int i = 0; i < 2; i++) { @@ -1515,10 +1518,12 @@ static void preview_plane_free_fn(void *customdata) static void WIDGETGROUP_placement_setup(const bContext *UNUSED(C), wmGizmoGroup *gzgroup) { V3DSnapCursorState *snap_state = ED_view3d_cursor_snap_active(); - snap_state->draw_plane = true; + if (snap_state) { + snap_state->draw_plane = true; - gzgroup->customdata = snap_state; - gzgroup->customdata_free = preview_plane_free_fn; + gzgroup->customdata = snap_state; + gzgroup->customdata_free = preview_plane_free_fn; + } } void VIEW3D_GGT_placement(wmGizmoGroupType *gzgt) diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c index 07f1f8a753c..18820039c7f 100644 --- a/source/blender/editors/space_view3d/view3d_select.c +++ b/source/blender/editors/space_view3d/view3d_select.c @@ -2047,19 +2047,16 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc, bool enumerate, bool *r_do_nearest) { - static int last_mval[2] = {-100, -100}; bool do_nearest = false; View3D *v3d = vc->v3d; /* define if we use solid nearest select or not */ if (use_cycle) { + /* Update the coordinates (even if the return value isn't used). */ + const bool has_motion = WM_cursor_test_motion_and_update(mval); if (!XRAY_ACTIVE(v3d)) { - do_nearest = true; - if (len_manhattan_v2v2_int(mval, last_mval) <= WM_EVENT_CURSOR_MOTION_THRESHOLD) { - do_nearest = false; - } + do_nearest = has_motion; } - copy_v2_v2_int(last_mval, mval); } else { if (!XRAY_ACTIVE(v3d)) { diff --git a/source/blender/editors/space_view3d/view3d_snap.c b/source/blender/editors/space_view3d/view3d_snap.c index 55ec6652495..67b61ed77d8 100644 --- a/source/blender/editors/space_view3d/view3d_snap.c +++ b/source/blender/editors/space_view3d/view3d_snap.c @@ -615,7 +615,7 @@ static int snap_selected_to_cursor_exec(bContext *C, wmOperator *op) const float *snap_target_global = scene->cursor.location; const int pivot_point = scene->toolsettings->transform_pivot_point; - if (snap_selected_to_location(C, snap_target_global, pivot_point, use_offset, true)) { + if (snap_selected_to_location(C, snap_target_global, use_offset, pivot_point, true)) { return OPERATOR_CANCELLED; } return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_view3d/view3d_view.c b/source/blender/editors/space_view3d/view3d_view.c index f5da7c14a88..46a664f10fa 100644 --- a/source/blender/editors/space_view3d/view3d_view.c +++ b/source/blender/editors/space_view3d/view3d_view.c @@ -1730,7 +1730,12 @@ void ED_view3d_xr_shading_update(wmWindowManager *wm, const View3D *v3d, const S if (v3d->runtime.flag & V3D_RUNTIME_XR_SESSION_ROOT) { View3DShading *xr_shading = &wm->xr.session_settings.shading; /* Flags that shouldn't be overridden by the 3D View shading. */ - const int flag_copy = V3D_SHADING_WORLD_ORIENTATION; + int flag_copy = 0; + if (v3d->shading.type != + OB_SOLID) { /* Don't set V3D_SHADING_WORLD_ORIENTATION for solid shading since it results + in distorted lighting when the view matrix has a scale factor. */ + flag_copy |= V3D_SHADING_WORLD_ORIENTATION; + } BLI_assert(WM_xr_session_exists(&wm->xr)); diff --git a/source/blender/editors/transform/transform_gizmo_3d.c b/source/blender/editors/transform/transform_gizmo_3d.c index e4c20fa0be1..466c4202dbd 100644 --- a/source/blender/editors/transform/transform_gizmo_3d.c +++ b/source/blender/editors/transform/transform_gizmo_3d.c @@ -646,10 +646,8 @@ int ED_transform_calc_gizmo_stats(const bContext *C, Depsgraph *depsgraph = CTX_data_expect_evaluated_depsgraph(C); ViewLayer *view_layer = CTX_data_view_layer(C); View3D *v3d = area->spacedata.first; - Object *obedit = CTX_data_edit_object(C); RegionView3D *rv3d = region->regiondata; Base *base; - Object *ob = OBACT(view_layer); bGPdata *gpd = CTX_data_gpencil_data(C); const bool is_gp_edit = GPENCIL_ANY_MODE(gpd); const bool is_curve_edit = GPENCIL_CURVE_EDIT_SESSIONS_ON(gpd); @@ -660,6 +658,15 @@ int ED_transform_calc_gizmo_stats(const bContext *C, (params->orientation_index - 1) : BKE_scene_orientation_get_index(scene, SCE_ORIENT_DEFAULT); + Object *ob = OBACT(view_layer); + Object *obedit = OBEDIT_FROM_OBACT(ob); + if (ob && ob->mode & OB_MODE_WEIGHT_PAINT) { + Object *obpose = BKE_object_pose_armature_get(ob); + if (obpose != NULL) { + ob = obpose; + } + } + /* transform widget matrix */ unit_m4(rv3d->twmat); diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c index dea8a7c6f03..c779fbe4a33 100644 --- a/source/blender/editors/transform/transform_snap_object.c +++ b/source/blender/editors/transform/transform_snap_object.c @@ -493,6 +493,11 @@ static void iter_snap_objects(SnapObjectContext *sctx, continue; } } + else if (snap_select == SNAP_SELECTABLE) { + if (!(base->flag & BASE_SELECTABLE)) { + continue; + } + } Object *obj_eval = DEG_get_evaluated_object(sctx->runtime.depsgraph, base->object); if (obj_eval->transflag & OB_DUPLI || BKE_object_has_geometry_set_instances(obj_eval)) { @@ -2308,7 +2313,7 @@ static short snapMesh(SnapObjectContext *sctx, float dist_px_sq = square_f(*dist_px); /* Test BoundBox */ - BoundBox *bb = BKE_mesh_boundbox_get(ob_eval); + BoundBox *bb = BKE_object_boundbox_get(ob_eval); if (bb && !snap_bound_box_check_dist( bb->vec[0], bb->vec[6], lpmat, sctx->runtime.win_size, sctx->runtime.mval, dist_px_sq)) { diff --git a/source/blender/editors/transform/transform_snap_sequencer.c b/source/blender/editors/transform/transform_snap_sequencer.c index 2acdf5cfd9c..7bcf6812ce9 100644 --- a/source/blender/editors/transform/transform_snap_sequencer.c +++ b/source/blender/editors/transform/transform_snap_sequencer.c @@ -220,13 +220,13 @@ static void seq_snap_target_points_build(const TransInfo *t, int content_end = max_ii(seq->startdisp, seq->start + seq->len); /* Effects and single image strips produce incorrect content length. Skip these strips. */ if ((seq->type & SEQ_TYPE_EFFECT) != 0 || seq->len == 1) { - if (seq->anim_startofs == 0 && seq->startstill == 0) { - content_start = seq->startdisp; - } - if (seq->anim_endofs == 0 && seq->endstill == 0) { - content_end = seq->enddisp; - } + content_start = seq->startdisp; + content_end = seq->enddisp; } + + CLAMP(content_start, seq->startdisp, seq->enddisp); + CLAMP(content_end, seq->startdisp, seq->enddisp); + snap_data->target_snap_points[i] = content_start; snap_data->target_snap_points[i + 1] = content_end; i += 2; |