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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors')
-rw-r--r--source/blender/editors/animation/anim_channels_edit.c79
-rw-r--r--source/blender/editors/armature/armature_select.c6
-rw-r--r--source/blender/editors/asset/ED_asset_catalog.hh3
-rw-r--r--source/blender/editors/asset/ED_asset_mark_clear.h3
-rw-r--r--source/blender/editors/asset/ED_asset_type.h5
-rw-r--r--source/blender/editors/asset/intern/asset_catalog.cc35
-rw-r--r--source/blender/editors/asset/intern/asset_mark_clear.cc19
-rw-r--r--source/blender/editors/asset/intern/asset_ops.cc9
-rw-r--r--source/blender/editors/asset/intern/asset_type.cc2
-rw-r--r--source/blender/editors/gizmo_library/gizmo_types/cage3d_gizmo.c2
-rw-r--r--source/blender/editors/gizmo_library/gizmo_types/snap3d_gizmo.c10
-rw-r--r--source/blender/editors/include/ED_fileselect.h6
-rw-r--r--source/blender/editors/include/ED_node.h3
-rw-r--r--source/blender/editors/include/ED_object.h4
-rw-r--r--source/blender/editors/include/ED_screen.h1
-rw-r--r--source/blender/editors/include/ED_transform_snap_object_context.h1
-rw-r--r--source/blender/editors/include/ED_view3d.h5
-rw-r--r--source/blender/editors/include/UI_interface.h34
-rw-r--r--source/blender/editors/include/UI_interface.hh25
-rw-r--r--source/blender/editors/include/UI_tree_view.hh94
-rw-r--r--source/blender/editors/include/UI_view2d.h7
-rw-r--r--source/blender/editors/interface/CMakeLists.txt1
-rw-r--r--source/blender/editors/interface/interface.c3
-rw-r--r--source/blender/editors/interface/interface_context_path.cc85
-rw-r--r--source/blender/editors/interface/interface_draw.c29
-rw-r--r--source/blender/editors/interface/interface_dropboxes.cc39
-rw-r--r--source/blender/editors/interface/interface_handlers.c128
-rw-r--r--source/blender/editors/interface/interface_intern.h2
-rw-r--r--source/blender/editors/interface/interface_layout.c8
-rw-r--r--source/blender/editors/interface/interface_ops.c34
-rw-r--r--source/blender/editors/interface/interface_panel.c2
-rw-r--r--source/blender/editors/interface/interface_style.c31
-rw-r--r--source/blender/editors/interface/interface_template_asset_view.cc1
-rw-r--r--source/blender/editors/interface/interface_template_attribute_search.cc7
-rw-r--r--source/blender/editors/interface/interface_widgets.c56
-rw-r--r--source/blender/editors/interface/resources.c7
-rw-r--r--source/blender/editors/interface/tree_view.cc95
-rw-r--r--source/blender/editors/interface/view2d.c111
-rw-r--r--source/blender/editors/interface/view2d_edge_pan.c11
-rw-r--r--source/blender/editors/mesh/editmesh_knife.c63
-rw-r--r--source/blender/editors/mesh/editmesh_select.c8
-rw-r--r--source/blender/editors/object/object_add.c235
-rw-r--r--source/blender/editors/object/object_intern.h1
-rw-r--r--source/blender/editors/object/object_ops.c1
-rw-r--r--source/blender/editors/object/object_relations.c24
-rw-r--r--source/blender/editors/object/object_utils.c68
-rw-r--r--source/blender/editors/physics/particle_object.c12
-rw-r--r--source/blender/editors/screen/area.c6
-rw-r--r--source/blender/editors/screen/screen_edit.c4
-rw-r--r--source/blender/editors/screen/screen_ops.c54
-rw-r--r--source/blender/editors/sculpt_paint/paint_vertex.c2
-rw-r--r--source/blender/editors/space_file/asset_catalog_tree_view.cc259
-rw-r--r--source/blender/editors/space_file/file_draw.c54
-rw-r--r--source/blender/editors/space_file/file_intern.h3
-rw-r--r--source/blender/editors/space_file/file_ops.c32
-rw-r--r--source/blender/editors/space_file/file_panels.c2
-rw-r--r--source/blender/editors/space_file/filelist.c209
-rw-r--r--source/blender/editors/space_file/filesel.c27
-rw-r--r--source/blender/editors/space_file/space_file.c3
-rw-r--r--source/blender/editors/space_nla/nla_draw.c54
-rw-r--r--source/blender/editors/space_node/CMakeLists.txt1
-rw-r--r--source/blender/editors/space_node/drawnode.cc62
-rw-r--r--source/blender/editors/space_node/node_add.cc14
-rw-r--r--source/blender/editors/space_node/node_context_path.cc184
-rw-r--r--source/blender/editors/space_node/node_draw.cc502
-rw-r--r--source/blender/editors/space_node/node_intern.h10
-rw-r--r--source/blender/editors/space_node/node_ops.c1
-rw-r--r--source/blender/editors/space_node/node_relationships.cc360
-rw-r--r--source/blender/editors/space_node/space_node.c26
-rw-r--r--source/blender/editors/space_outliner/outliner_dragdrop.c8
-rw-r--r--source/blender/editors/space_outliner/outliner_tools.c61
-rw-r--r--source/blender/editors/space_sequencer/sequencer_edit.c16
-rw-r--r--source/blender/editors/space_sequencer/sequencer_select.c259
-rw-r--r--source/blender/editors/space_sequencer/sequencer_view.c9
-rw-r--r--source/blender/editors/space_spreadsheet/CMakeLists.txt2
-rw-r--r--source/blender/editors/space_spreadsheet/space_spreadsheet.cc66
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_cache.cc79
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_cache.hh78
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_column_values.hh5
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_context.cc25
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_data_source.hh6
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.cc333
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_data_source_geometry.hh38
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_intern.hh22
-rw-r--r--source/blender/editors/space_spreadsheet/spreadsheet_layout.cc4
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c209
-rw-r--r--source/blender/editors/space_view3d/view3d_cursor_snap.c150
-rw-r--r--source/blender/editors/space_view3d/view3d_draw.c2
-rw-r--r--source/blender/editors/space_view3d/view3d_placement.c21
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c9
-rw-r--r--source/blender/editors/space_view3d/view3d_snap.c2
-rw-r--r--source/blender/editors/space_view3d/view3d_view.c7
-rw-r--r--source/blender/editors/transform/transform_gizmo_3d.c11
-rw-r--r--source/blender/editors/transform/transform_snap_object.c7
-rw-r--r--source/blender/editors/transform/transform_snap_sequencer.c12
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(
- &region->handlers, WM_event_get_keymap_from_toolsystem_fallback, area);
+ &region->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, &region->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 = &region->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, &params_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 = &region->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 &region)
{
- char info[256];
+ using namespace blender;
+
+ GPU_matrix_push_projection();
+ wmOrtho2_region_pixelspace(&region);
+
+ const rcti *rect = ED_region_visible_rect(&region);
+
+ 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, &region, __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;