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:
authorPeter Kim <pk15950@gmail.com>2021-10-03 06:22:05 +0300
committerPeter Kim <pk15950@gmail.com>2021-10-03 06:22:05 +0300
commit6fc81d6bca6424a1e44305df7cdc3598e03b00ba (patch)
treea66f17c5378f2a68f4c5d8b09f56687c3d9bf888 /source/blender/editors
parent85e1f28fcaafd137a546bf192777b00f96851e80 (diff)
parentd3afe0c1265c9ebb53053de68f176b30f0132281 (diff)
Merge branch 'master' into xr-controller-supportxr-controller-support
Diffstat (limited to 'source/blender/editors')
-rw-r--r--source/blender/editors/animation/anim_filter.c30
-rw-r--r--source/blender/editors/animation/anim_ipo_utils.c7
-rw-r--r--source/blender/editors/animation/anim_ops.c9
-rw-r--r--source/blender/editors/animation/drivers.c2
-rw-r--r--source/blender/editors/animation/keyframes_draw.c18
-rw-r--r--source/blender/editors/animation/keyframing.c30
-rw-r--r--source/blender/editors/animation/keyingsets.c2
-rw-r--r--source/blender/editors/animation/time_scrub_ui.c35
-rw-r--r--source/blender/editors/armature/pose_transform.c2
-rw-r--r--source/blender/editors/asset/CMakeLists.txt2
-rw-r--r--source/blender/editors/asset/ED_asset_catalog.hh35
-rw-r--r--source/blender/editors/asset/ED_asset_mark_clear.h15
-rw-r--r--source/blender/editors/asset/intern/asset_catalog.cc81
-rw-r--r--source/blender/editors/asset/intern/asset_list.cc4
-rw-r--r--source/blender/editors/asset/intern/asset_mark_clear.cc3
-rw-r--r--source/blender/editors/asset/intern/asset_ops.cc175
-rw-r--r--source/blender/editors/gpencil/annotate_paint.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_edit.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_mesh.c2
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c2
-rw-r--r--source/blender/editors/include/ED_anim_api.h2
-rw-r--r--source/blender/editors/include/ED_fileselect.h1
-rw-r--r--source/blender/editors/include/ED_image.h10
-rw-r--r--source/blender/editors/include/ED_keyframes_draw.h2
-rw-r--r--source/blender/editors/include/ED_text.h2
-rw-r--r--source/blender/editors/include/ED_time_scrub_ui.h3
-rw-r--r--source/blender/editors/include/ED_uvedit.h17
-rw-r--r--source/blender/editors/include/UI_icons.h10
-rw-r--r--source/blender/editors/include/UI_interface.h19
-rw-r--r--source/blender/editors/include/UI_interface.hh35
-rw-r--r--source/blender/editors/include/UI_tree_view.hh257
-rw-r--r--source/blender/editors/include/UI_view2d.h1
-rw-r--r--source/blender/editors/interface/CMakeLists.txt3
-rw-r--r--source/blender/editors/interface/interface.c59
-rw-r--r--source/blender/editors/interface/interface_dropboxes.cc66
-rw-r--r--source/blender/editors/interface/interface_handlers.c20
-rw-r--r--source/blender/editors/interface/interface_icons.c42
-rw-r--r--source/blender/editors/interface/interface_intern.h19
-rw-r--r--source/blender/editors/interface/interface_layout.c85
-rw-r--r--source/blender/editors/interface/interface_ops.c58
-rw-r--r--source/blender/editors/interface/interface_query.c13
-rw-r--r--source/blender/editors/interface/interface_view.cc133
-rw-r--r--source/blender/editors/interface/interface_widgets.c39
-rw-r--r--source/blender/editors/interface/tree_view.cc381
-rw-r--r--source/blender/editors/interface/view2d.c8
-rw-r--r--source/blender/editors/mesh/editmesh_knife.c210
-rw-r--r--source/blender/editors/object/object_add.c13
-rw-r--r--source/blender/editors/object/object_edit.c2
-rw-r--r--source/blender/editors/object/object_intern.h1
-rw-r--r--source/blender/editors/object/object_modifier.c4
-rw-r--r--source/blender/editors/object/object_ops.c1
-rw-r--r--source/blender/editors/object/object_relations.c161
-rw-r--r--source/blender/editors/render/render_shading.c2
-rw-r--r--source/blender/editors/screen/area.c9
-rw-r--r--source/blender/editors/screen/screen_context.c11
-rw-r--r--source/blender/editors/screen/screen_intern.h2
-rw-r--r--source/blender/editors/screen/workspace_edit.c9
-rw-r--r--source/blender/editors/sculpt_paint/paint_image_proj.c4
-rw-r--r--source/blender/editors/sculpt_paint/sculpt_smooth.c7
-rw-r--r--source/blender/editors/space_action/action_data.c51
-rw-r--r--source/blender/editors/space_action/space_action.c2
-rw-r--r--source/blender/editors/space_api/spacetypes.c1
-rw-r--r--source/blender/editors/space_clip/clip_dopesheet_draw.c2
-rw-r--r--source/blender/editors/space_clip/tracking_ops.c2
-rw-r--r--source/blender/editors/space_clip/tracking_ops_track.c4
-rw-r--r--source/blender/editors/space_file/CMakeLists.txt2
-rw-r--r--source/blender/editors/space_file/asset_catalog_tree_view.cc230
-rw-r--r--source/blender/editors/space_file/file_intern.h18
-rw-r--r--source/blender/editors/space_file/file_ops.c27
-rw-r--r--source/blender/editors/space_file/file_panels.c32
-rw-r--r--source/blender/editors/space_file/filelist.c402
-rw-r--r--source/blender/editors/space_file/filelist.h12
-rw-r--r--source/blender/editors/space_file/filesel.c19
-rw-r--r--source/blender/editors/space_file/fsmenu.c2
-rw-r--r--source/blender/editors/space_file/space_file.c11
-rw-r--r--source/blender/editors/space_graph/space_graph.c35
-rw-r--r--source/blender/editors/space_image/image_draw.c60
-rw-r--r--source/blender/editors/space_image/space_image.c16
-rw-r--r--source/blender/editors/space_info/info_ops.c2
-rw-r--r--source/blender/editors/space_nla/nla_draw.c2
-rw-r--r--source/blender/editors/space_nla/space_nla.c2
-rw-r--r--source/blender/editors/space_node/drawnode.cc53
-rw-r--r--source/blender/editors/space_node/node_draw.cc52
-rw-r--r--source/blender/editors/space_node/node_relationships.cc5
-rw-r--r--source/blender/editors/space_outliner/outliner_select.c11
-rw-r--r--source/blender/editors/space_sequencer/sequencer_draw.c143
-rw-r--r--source/blender/editors/space_sequencer/sequencer_edit.c39
-rw-r--r--source/blender/editors/space_sequencer/sequencer_intern.h7
-rw-r--r--source/blender/editors/space_sequencer/sequencer_ops.c2
-rw-r--r--source/blender/editors/space_sequencer/space_sequencer.c16
-rw-r--r--source/blender/editors/space_text/text_draw.c20
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c34
-rw-r--r--source/blender/editors/space_view3d/view3d_edit.c85
-rw-r--r--source/blender/editors/space_view3d/view3d_intern.h1
-rw-r--r--source/blender/editors/space_view3d/view3d_ops.c1
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c2
-rw-r--r--source/blender/editors/transform/transform.c13
-rw-r--r--source/blender/editors/transform/transform_convert_armature.c8
-rw-r--r--source/blender/editors/transform/transform_convert_nla.c44
-rw-r--r--source/blender/editors/transform/transform_convert_object.c14
-rw-r--r--source/blender/editors/transform/transform_convert_sequencer_image.c7
-rw-r--r--source/blender/editors/transform/transform_gizmo_2d.c5
-rw-r--r--source/blender/editors/transform/transform_mode_timescale.c2
-rw-r--r--source/blender/editors/transform/transform_snap.c8
-rw-r--r--source/blender/editors/transform/transform_snap_object.c2
-rw-r--r--source/blender/editors/util/CMakeLists.txt2
-rw-r--r--source/blender/editors/uvedit/uvedit_islands.c175
-rw-r--r--source/blender/editors/uvedit/uvedit_unwrap_ops.c91
108 files changed, 3255 insertions, 700 deletions
diff --git a/source/blender/editors/animation/anim_filter.c b/source/blender/editors/animation/anim_filter.c
index b12e0ae5cab..e1d046428a8 100644
--- a/source/blender/editors/animation/anim_filter.c
+++ b/source/blender/editors/animation/anim_filter.c
@@ -3087,7 +3087,10 @@ static size_t animdata_filter_dopesheet_movieclips(bAnimContext *ac,
}
/* Helper for animdata_filter_dopesheet() - For checking if an object should be included or not */
-static bool animdata_filter_base_is_ok(bDopeSheet *ads, Base *base, int filter_mode)
+static bool animdata_filter_base_is_ok(bDopeSheet *ads,
+ Base *base,
+ const eObjectMode object_mode,
+ int filter_mode)
{
Object *ob = base->object;
@@ -3144,10 +3147,21 @@ static bool animdata_filter_base_is_ok(bDopeSheet *ads, Base *base, int filter_m
}
/* check selection and object type filters */
- if ((ads->filterflag & ADS_FILTER_ONLYSEL) &&
- !((base->flag & BASE_SELECTED) /*|| (base == sce->basact) */)) {
- /* only selected should be shown */
- return false;
+ if (ads->filterflag & ADS_FILTER_ONLYSEL) {
+ if (object_mode & OB_MODE_POSE) {
+ /* When in pose-mode handle all pose-mode objects.
+ * This avoids problems with pose-mode where objects may be unselected,
+ * where a selected bone of an unselected object would be hidden. see: T81922. */
+ if (!(base->object->mode & object_mode)) {
+ return false;
+ }
+ }
+ else {
+ /* only selected should be shown (ignore active) */
+ if (!(base->flag & BASE_SELECTED)) {
+ return false;
+ }
+ }
}
/* check if object belongs to the filtering group if option to filter
@@ -3185,7 +3199,7 @@ static Base **animdata_filter_ds_sorted_bases(bDopeSheet *ads,
Base **sorted_bases = MEM_mallocN(sizeof(Base *) * tot_bases, "Dopesheet Usable Sorted Bases");
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
- if (animdata_filter_base_is_ok(ads, base, filter_mode)) {
+ if (animdata_filter_base_is_ok(ads, base, OB_MODE_OBJECT, filter_mode)) {
sorted_bases[num_bases++] = base;
}
}
@@ -3278,8 +3292,10 @@ static size_t animdata_filter_dopesheet(bAnimContext *ac,
/* Filter and add contents of each base (i.e. object) without them sorting first
* NOTE: This saves performance in cases where order doesn't matter
*/
+ Object *obact = OBACT(view_layer);
+ const eObjectMode object_mode = obact ? obact->mode : OB_MODE_OBJECT;
LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
- if (animdata_filter_base_is_ok(ads, base, filter_mode)) {
+ if (animdata_filter_base_is_ok(ads, base, object_mode, filter_mode)) {
/* since we're still here, this object should be usable */
items += animdata_filter_dopesheet_ob(ac, anim_data, ads, base, filter_mode);
}
diff --git a/source/blender/editors/animation/anim_ipo_utils.c b/source/blender/editors/animation/anim_ipo_utils.c
index 33b4882927a..6fe32699907 100644
--- a/source/blender/editors/animation/anim_ipo_utils.c
+++ b/source/blender/editors/animation/anim_ipo_utils.c
@@ -126,9 +126,10 @@ int getname_anim_fcurve(char *name, ID *id, FCurve *fcu)
structname = RNA_struct_ui_name(ptr.type);
}
- /* For the VSE, a strip's 'Transform' or 'Crop' is a nested (under Sequence) struct, but
- * displaying the struct name alone is no meaningful information (and also cannot be
- * filtered well), same for modifiers. So display strip name alongside as well. */
+ /* For the sequencer, a strip's 'Transform' or 'Crop' is a nested (under Sequence)
+ * struct, but displaying the struct name alone is no meaningful information
+ * (and also cannot be filtered well), same for modifiers.
+ * So display strip name alongside as well. */
if (GS(ptr.owner_id->name) == ID_SCE) {
char stripname[256];
if (BLI_str_quoted_substr(
diff --git a/source/blender/editors/animation/anim_ops.c b/source/blender/editors/animation/anim_ops.c
index b4ea33920b2..3958c7f9e34 100644
--- a/source/blender/editors/animation/anim_ops.c
+++ b/source/blender/editors/animation/anim_ops.c
@@ -74,9 +74,16 @@ static bool change_frame_poll(bContext *C)
* this shouldn't show up in 3D editor (or others without 2D timeline view) via search
*/
if (area) {
- if (ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH)) {
+ if (ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP)) {
return true;
}
+ if (area->spacetype == SPACE_GRAPH) {
+ const SpaceGraph *sipo = area->spacedata.first;
+ /* Driver Editor's X axis is not time. */
+ if (sipo->mode != SIPO_MODE_DRIVERS) {
+ return true;
+ }
+ }
}
CTX_wm_operator_poll_msg_set(C, "Expected an animation area to be active");
diff --git a/source/blender/editors/animation/drivers.c b/source/blender/editors/animation/drivers.c
index bfaa76b3bf9..dbf379971fa 100644
--- a/source/blender/editors/animation/drivers.c
+++ b/source/blender/editors/animation/drivers.c
@@ -1115,7 +1115,7 @@ static int add_driver_button_invoke(bContext *C, wmOperator *op, const wmEvent *
}
/* 2) Show editing panel for setting up this driver */
- /* TODO: Use a different one from the editing popever, so we can have the single/all toggle? */
+ /* TODO: Use a different one from the editing popover, so we can have the single/all toggle? */
UI_popover_panel_invoke(C, "GRAPH_PT_drivers_popover", true, op->reports);
}
diff --git a/source/blender/editors/animation/keyframes_draw.c b/source/blender/editors/animation/keyframes_draw.c
index ac7db9f4f46..e3ea8f0ab21 100644
--- a/source/blender/editors/animation/keyframes_draw.c
+++ b/source/blender/editors/animation/keyframes_draw.c
@@ -146,31 +146,31 @@ void draw_keyframe_shape(float x,
/* Handle type to outline shape. */
switch (handle_type) {
case KEYFRAME_HANDLE_AUTO_CLAMP:
- flags = 0x2;
+ flags = GPU_KEYFRAME_SHAPE_CIRCLE;
break; /* circle */
case KEYFRAME_HANDLE_AUTO:
- flags = 0x12;
+ flags = GPU_KEYFRAME_SHAPE_CIRCLE | GPU_KEYFRAME_SHAPE_INNER_DOT;
break; /* circle with dot */
case KEYFRAME_HANDLE_VECTOR:
- flags = 0xC;
+ flags = GPU_KEYFRAME_SHAPE_SQUARE;
break; /* square */
case KEYFRAME_HANDLE_ALIGNED:
- flags = 0x5;
+ flags = GPU_KEYFRAME_SHAPE_DIAMOND | GPU_KEYFRAME_SHAPE_CLIPPED_VERTICAL;
break; /* clipped diamond */
case KEYFRAME_HANDLE_FREE:
default:
- flags = 1; /* diamond */
+ flags = GPU_KEYFRAME_SHAPE_DIAMOND; /* diamond */
}
/* Extreme type to arrow-like shading. */
if (extreme_type & KEYFRAME_EXTREME_MAX) {
- flags |= 0x100;
+ flags |= GPU_KEYFRAME_SHAPE_ARROW_END_MAX;
}
if (extreme_type & KEYFRAME_EXTREME_MIN) {
- flags |= 0x200;
+ flags |= GPU_KEYFRAME_SHAPE_ARROW_END_MIN;
}
- if (extreme_type & KEYFRAME_EXTREME_MIXED) {
+ if (extreme_type & GPU_KEYFRAME_SHAPE_ARROW_END_MIXED) {
flags |= 0x400;
}
}
@@ -584,7 +584,7 @@ static void ED_keylist_draw_list_draw_keys(AnimKeylistDrawList *draw_list, View2
sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 1.0f);
immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1);
immBegin(GPU_PRIM_POINTS, visible_key_len);
diff --git a/source/blender/editors/animation/keyframing.c b/source/blender/editors/animation/keyframing.c
index 8dc4aed9f0e..1ef7ee755ea 100644
--- a/source/blender/editors/animation/keyframing.c
+++ b/source/blender/editors/animation/keyframing.c
@@ -2107,10 +2107,12 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k
return OPERATOR_CANCELLED;
}
+ PropertyRNA *prop = RNA_struct_find_property(op->ptr, "confirm_success");
+ bool confirm = (prop != NULL && RNA_property_boolean_get(op->ptr, prop));
+
if (num_channels > 0) {
/* if the appropriate properties have been set, make a note that we've inserted something */
- PropertyRNA *prop = RNA_struct_find_property(op->ptr, "confirm_success");
- if (prop != NULL && RNA_property_boolean_get(op->ptr, prop)) {
+ if (confirm) {
BKE_reportf(op->reports,
RPT_INFO,
"Successfully removed %d keyframes for keying set '%s'",
@@ -2121,7 +2123,7 @@ static int delete_key_using_keying_set(bContext *C, wmOperator *op, KeyingSet *k
/* send notifiers that keyframes have been changed */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_REMOVED, NULL);
}
- else {
+ else if (confirm) {
BKE_report(op->reports, RPT_WARNING, "Keying set failed to remove any keyframes");
}
@@ -2289,6 +2291,8 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op)
int selected_objects_success_len = 0;
int success_multi = 0;
+ bool confirm = op->flag & OP_IS_INVOKE;
+
CTX_DATA_BEGIN (C, Object *, ob, selected_objects) {
ID *id = &ob->id;
int success = 0;
@@ -2370,20 +2374,20 @@ static int delete_key_v3d_without_keying_set(bContext *C, wmOperator *op)
/* report success (or failure) */
if (selected_objects_success_len) {
- BKE_reportf(op->reports,
- RPT_INFO,
- "%d object(s) successfully had %d keyframes removed",
- selected_objects_success_len,
- success_multi);
+ if (confirm) {
+ BKE_reportf(op->reports,
+ RPT_INFO,
+ "%d object(s) successfully had %d keyframes removed",
+ selected_objects_success_len,
+ success_multi);
+ }
+ /* send updates */
+ WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL);
}
- else {
+ else if (confirm) {
BKE_reportf(
op->reports, RPT_ERROR, "No keyframes removed from %d object(s)", selected_objects_len);
}
-
- /* send updates */
- WM_event_add_notifier(C, NC_OBJECT | ND_KEYS, NULL);
-
return OPERATOR_FINISHED;
}
diff --git a/source/blender/editors/animation/keyingsets.c b/source/blender/editors/animation/keyingsets.c
index 0206aabd359..e1fd3b07f46 100644
--- a/source/blender/editors/animation/keyingsets.c
+++ b/source/blender/editors/animation/keyingsets.c
@@ -610,7 +610,7 @@ void ANIM_keyingset_info_unregister(Main *bmain, KeyingSetInfo *ksi)
KeyingSet *ks, *ksn;
/* find relevant builtin KeyingSets which use this, and remove them */
- /* TODO: this isn't done now, since unregister is really only used atm when we
+ /* TODO: this isn't done now, since unregister is really only used at the moment when we
* reload the scripts, which kindof defeats the purpose of "builtin"? */
for (ks = builtin_keyingsets.first; ks; ks = ksn) {
ksn = ks->next;
diff --git a/source/blender/editors/animation/time_scrub_ui.c b/source/blender/editors/animation/time_scrub_ui.c
index 8aeb6a57124..b0eb014c5d9 100644
--- a/source/blender/editors/animation/time_scrub_ui.c
+++ b/source/blender/editors/animation/time_scrub_ui.c
@@ -91,8 +91,7 @@ static void draw_current_frame(const Scene *scene,
bool display_seconds,
const View2D *v2d,
const rcti *scrub_region_rect,
- int current_frame,
- bool draw_line)
+ int current_frame)
{
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
int frame_x = UI_view2d_view_to_region_x(v2d, current_frame);
@@ -106,21 +105,18 @@ static void draw_current_frame(const Scene *scene,
float bg_color[4];
UI_GetThemeColorShade4fv(TH_CFRAME, -5, bg_color);
- if (draw_line) {
- /* Draw vertical line to from the bottom of the current frame box to the bottom of the screen.
- */
- const float subframe_x = UI_view2d_view_to_region_x(v2d, BKE_scene_ctime_get(scene));
- GPUVertFormat *format = immVertexFormat();
- uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
- immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
- immUniformThemeColor(TH_CFRAME);
- immRectf(pos,
- subframe_x - U.pixelsize,
- scrub_region_rect->ymax - box_padding,
- subframe_x + U.pixelsize,
- 0.0f);
- immUnbindProgram();
- }
+ /* Draw vertical line from the bottom of the current frame box to the bottom of the screen. */
+ const float subframe_x = UI_view2d_view_to_region_x(v2d, BKE_scene_ctime_get(scene));
+ GPUVertFormat *format = immVertexFormat();
+ uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+ immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
+ immUniformThemeColor(TH_CFRAME);
+ immRectf(pos,
+ subframe_x - U.pixelsize,
+ scrub_region_rect->ymax - box_padding,
+ subframe_x + U.pixelsize,
+ 0.0f);
+ immUnbindProgram();
UI_draw_roundbox_corner_set(UI_CNR_ALL);
@@ -152,8 +148,7 @@ static void draw_current_frame(const Scene *scene,
void ED_time_scrub_draw_current_frame(const ARegion *region,
const Scene *scene,
- bool display_seconds,
- bool draw_line)
+ bool display_seconds)
{
const View2D *v2d = &region->v2d;
GPU_matrix_push_projection();
@@ -162,7 +157,7 @@ void ED_time_scrub_draw_current_frame(const ARegion *region,
rcti scrub_region_rect;
get_time_scrub_region_rect(region, &scrub_region_rect);
- draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra, draw_line);
+ draw_current_frame(scene, display_seconds, v2d, &scrub_region_rect, scene->r.cfra);
GPU_matrix_pop_projection();
}
diff --git a/source/blender/editors/armature/pose_transform.c b/source/blender/editors/armature/pose_transform.c
index 3798ca308ed..279f79ac44b 100644
--- a/source/blender/editors/armature/pose_transform.c
+++ b/source/blender/editors/armature/pose_transform.c
@@ -1184,7 +1184,7 @@ static int pose_clear_transform_generic_exec(bContext *C,
ViewLayer *view_layer = CTX_data_view_layer(C);
View3D *v3d = CTX_wm_view3d(C);
FOREACH_OBJECT_IN_MODE_BEGIN (view_layer, v3d, OB_ARMATURE, OB_MODE_POSE, ob_iter) {
- /* XXX: UGLY HACK (for autokey + clear transforms) */
+ /* XXX: UGLY HACK (for auto-key + clear transforms). */
Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob_iter);
ListBase dsources = {NULL, NULL};
bool changed = false;
diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt
index 31c07580570..b6657bfca63 100644
--- a/source/blender/editors/asset/CMakeLists.txt
+++ b/source/blender/editors/asset/CMakeLists.txt
@@ -31,6 +31,7 @@ set(INC_SYS
)
set(SRC
+ intern/asset_catalog.cc
intern/asset_filter.cc
intern/asset_handle.cc
intern/asset_library_reference.cc
@@ -40,6 +41,7 @@ set(SRC
intern/asset_ops.cc
intern/asset_temp_id_consumer.cc
+ ED_asset_catalog.hh
ED_asset_filter.h
ED_asset_handle.h
ED_asset_library.h
diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh
new file mode 100644
index 00000000000..cffd7728a60
--- /dev/null
+++ b/source/blender/editors/asset/ED_asset_catalog.hh
@@ -0,0 +1,35 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edasset
+ */
+
+#pragma once
+
+#include "BKE_asset_catalog.hh"
+
+#include "BLI_string_ref.hh"
+
+struct AssetLibrary;
+namespace blender::bke {
+class AssetCatalog;
+} // namespace blender::bke
+
+blender::bke::AssetCatalog *ED_asset_catalog_add(AssetLibrary *library,
+ blender::StringRefNull name,
+ blender::StringRef parent_path = nullptr);
+void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogID &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 7524ec6b02a..d8b8f15a109 100644
--- a/source/blender/editors/asset/ED_asset_mark_clear.h
+++ b/source/blender/editors/asset/ED_asset_mark_clear.h
@@ -27,7 +27,22 @@ extern "C" {
struct ID;
struct bContext;
+/**
+ * Mark the datablock as asset.
+ *
+ * To ensure the datablock is saved, this sets Fake User.
+ *
+ * \return whether the datablock was marked as asset; false when it is not capable of becoming an
+ * asset, or when it already was an asset. */
bool ED_asset_mark_id(const struct bContext *C, struct ID *id);
+
+/**
+ * Remove the asset metadata, turning the ID into a "normal" ID.
+ *
+ * This clears the Fake User. If for some reason the datablock is meant to be saved anyway, the
+ * caller is responsible for explicitly setting the Fake User.
+ *
+ * \return whether the asset metadata was actually removed; false when the ID was not an asset. */
bool ED_asset_clear_id(struct ID *id);
bool ED_asset_can_mark_single_from_context(const struct bContext *C);
diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc
new file mode 100644
index 00000000000..6e49ca2dd5c
--- /dev/null
+++ b/source/blender/editors/asset/intern/asset_catalog.cc
@@ -0,0 +1,81 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edasset
+ */
+
+#include "BKE_asset_catalog.hh"
+#include "BKE_asset_catalog_path.hh"
+#include "BKE_asset_library.hh"
+
+#include "BLI_string_utils.h"
+
+#include "ED_asset_catalog.hh"
+
+using namespace blender;
+using namespace blender::bke;
+
+struct CatalogUniqueNameFnData {
+ const AssetCatalogService &catalog_service;
+ StringRef parent_path;
+};
+
+static bool catalog_name_exists_fn(void *arg, const char *name)
+{
+ CatalogUniqueNameFnData &fn_data = *static_cast<CatalogUniqueNameFnData *>(arg);
+ AssetCatalogPath fullpath = AssetCatalogPath(fn_data.parent_path) / name;
+ return fn_data.catalog_service.find_catalog_by_path(fullpath);
+}
+
+static std::string catalog_name_ensure_unique(AssetCatalogService &catalog_service,
+ StringRefNull name,
+ StringRef parent_path)
+{
+ CatalogUniqueNameFnData fn_data = {catalog_service, parent_path};
+
+ char unique_name[MAX_NAME] = "";
+ BLI_uniquename_cb(
+ catalog_name_exists_fn, &fn_data, name.c_str(), '.', unique_name, sizeof(unique_name));
+
+ return unique_name;
+}
+
+AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library,
+ StringRefNull name,
+ StringRef parent_path)
+{
+ bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library);
+ if (!catalog_service) {
+ return nullptr;
+ }
+
+ std::string unique_name = catalog_name_ensure_unique(*catalog_service, name, parent_path);
+ AssetCatalogPath fullpath = AssetCatalogPath(parent_path) / unique_name;
+
+ return catalog_service->create_catalog(fullpath);
+}
+
+void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_id)
+{
+ bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library);
+ if (!catalog_service) {
+ BLI_assert_unreachable();
+ return;
+ }
+
+ catalog_service->delete_catalog(catalog_id);
+}
diff --git a/source/blender/editors/asset/intern/asset_list.cc b/source/blender/editors/asset/intern/asset_list.cc
index 57c25e1614b..400b3572c9b 100644
--- a/source/blender/editors/asset/intern/asset_list.cc
+++ b/source/blender/editors/asset/intern/asset_list.cc
@@ -157,7 +157,7 @@ void AssetList::setup()
/* Relevant bits from file_refresh(). */
/* TODO pass options properly. */
- filelist_setrecursion(files, 1);
+ filelist_setrecursion(files, FILE_SELECT_MAX_RECURSIONS);
filelist_setsorting(files, FILE_SORT_ALPHA, false);
filelist_setlibrary(files, &library_ref_);
filelist_setfilter_options(
@@ -390,7 +390,7 @@ std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_file
{
switch (library_reference.type) {
case ASSET_LIBRARY_CUSTOM:
- return FILE_LOADLIB;
+ return FILE_ASSET_LIBRARY;
case ASSET_LIBRARY_LOCAL:
return FILE_MAIN_ASSET;
}
diff --git a/source/blender/editors/asset/intern/asset_mark_clear.cc b/source/blender/editors/asset/intern/asset_mark_clear.cc
index ba348e38823..8290124c209 100644
--- a/source/blender/editors/asset/intern/asset_mark_clear.cc
+++ b/source/blender/editors/asset/intern/asset_mark_clear.cc
@@ -67,8 +67,7 @@ bool ED_asset_clear_id(ID *id)
return false;
}
BKE_asset_metadata_free(&id->asset_data);
- /* Don't clear fake user here, there's no guarantee that it was actually set by
- * #ED_asset_mark_id(), it might have been something/someone else. */
+ id_fake_user_clear(id);
/* Important for asset storage to update properly! */
ED_assetlist_storage_tag_main_data_dirty();
diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc
index d69a2cae94d..5424bae77b4 100644
--- a/source/blender/editors/asset/intern/asset_ops.cc
+++ b/source/blender/editors/asset/intern/asset_ops.cc
@@ -18,27 +18,55 @@
* \ingroup edasset
*/
+#include "BKE_asset_catalog.hh"
#include "BKE_context.h"
+#include "BKE_lib_id.h"
#include "BKE_report.h"
+#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
#include "ED_asset.h"
+#include "ED_asset_catalog.hh"
+/* XXX needs access to the file list, should all be done via the asset system in future. */
+#include "ED_fileselect.h"
#include "RNA_access.h"
+#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
+using namespace blender;
+
/* -------------------------------------------------------------------- */
using PointerRNAVec = blender::Vector<PointerRNA>;
+static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C);
+static PointerRNAVec asset_operation_get_nonexperimental_ids_from_context(const bContext *C);
+static bool asset_type_is_nonexperimental(const ID_Type id_type);
+
static bool asset_operation_poll(bContext * /*C*/)
{
+ /* At this moment only the pose library is non-experimental. Still, directly marking arbitrary
+ * Actions as asset is not part of the stable functionality; instead, the pose library "Create
+ * Pose Asset" operator should be used. Actions can still be marked as asset via
+ * `the_action.asset_mark()` (so a function call instead of this operator), which is what the
+ * pose library uses internally. */
return U.experimental.use_extended_asset_browser;
}
+static bool asset_clear_poll(bContext *C)
+{
+ if (asset_operation_poll(C)) {
+ return true;
+ }
+
+ PointerRNAVec pointers = asset_operation_get_nonexperimental_ids_from_context(C);
+ return !pointers.is_empty();
+}
+
/**
* Return the IDs to operate on as PointerRNA vector. Either a single one ("id" context member) or
* multiple ones ("selected_ids" context member).
@@ -64,6 +92,28 @@ static PointerRNAVec asset_operation_get_ids_from_context(const bContext *C)
return ids;
}
+static PointerRNAVec asset_operation_get_nonexperimental_ids_from_context(const bContext *C)
+{
+ PointerRNAVec nonexperimental;
+ PointerRNAVec pointers = asset_operation_get_ids_from_context(C);
+ for (PointerRNA &ptr : pointers) {
+ BLI_assert(RNA_struct_is_ID(ptr.type));
+
+ ID *id = static_cast<ID *>(ptr.data);
+ if (asset_type_is_nonexperimental(GS(id->name))) {
+ nonexperimental.append(ptr);
+ }
+ }
+ return nonexperimental;
+}
+
+static bool asset_type_is_nonexperimental(const ID_Type id_type)
+{
+ /* At this moment only the pose library is non-experimental. For simplicity, allow asset
+ * operations on all Action datablocks (even though pose assets are limited to single frames). */
+ return ELEM(id_type, ID_AC);
+}
+
/* -------------------------------------------------------------------- */
class AssetMarkHelper {
@@ -166,7 +216,13 @@ static void ASSET_OT_mark(wmOperatorType *ot)
/* -------------------------------------------------------------------- */
class AssetClearHelper {
+ const bool set_fake_user_;
+
public:
+ AssetClearHelper(const bool set_fake_user) : set_fake_user_(set_fake_user)
+ {
+ }
+
void operator()(PointerRNAVec &ids);
void reportResults(const bContext *C, ReportList &reports) const;
@@ -191,10 +247,16 @@ void AssetClearHelper::operator()(PointerRNAVec &ids)
continue;
}
- if (ED_asset_clear_id(id)) {
- stats.tot_cleared++;
- stats.last_id = id;
+ if (!ED_asset_clear_id(id)) {
+ continue;
}
+
+ if (set_fake_user_) {
+ id_fake_user_set(id);
+ }
+
+ stats.tot_cleared++;
+ stats.last_id = id;
}
}
@@ -234,7 +296,8 @@ static int asset_clear_exec(bContext *C, wmOperator *op)
{
PointerRNAVec ids = asset_operation_get_ids_from_context(C);
- AssetClearHelper clear_helper;
+ const bool set_fake_user = RNA_boolean_get(op->ptr, "set_fake_user");
+ AssetClearHelper clear_helper(set_fake_user);
clear_helper(ids);
clear_helper.reportResults(C, *op->reports);
@@ -248,18 +311,39 @@ static int asset_clear_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
+static char *asset_clear_get_description(struct bContext *UNUSED(C),
+ struct wmOperatorType *UNUSED(op),
+ struct PointerRNA *values)
+{
+ const bool set_fake_user = RNA_boolean_get(values, "set_fake_user");
+ if (!set_fake_user) {
+ return nullptr;
+ }
+
+ return BLI_strdup(
+ "Delete all asset metadata, turning the selected asset data-blocks back into normal "
+ "data-blocks, and set Fake User to ensure the data-blocks will still be saved");
+}
+
static void ASSET_OT_clear(wmOperatorType *ot)
{
ot->name = "Clear Asset";
ot->description =
"Delete all asset metadata and turn the selected asset data-blocks back into normal "
"data-blocks";
+ ot->get_description = asset_clear_get_description;
ot->idname = "ASSET_OT_clear";
ot->exec = asset_clear_exec;
- ot->poll = asset_operation_poll;
+ ot->poll = asset_clear_poll;
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna,
+ "set_fake_user",
+ false,
+ "Set Fake User",
+ "Ensure the data-block is saved, even when it is no longer marked as asset");
}
/* -------------------------------------------------------------------- */
@@ -295,10 +379,91 @@ static void ASSET_OT_list_refresh(struct wmOperatorType *ot)
/* -------------------------------------------------------------------- */
+static bool asset_catalog_operator_poll(bContext *C)
+{
+ const SpaceFile *sfile = CTX_wm_space_file(C);
+ return asset_operation_poll(C) && sfile && ED_fileselect_active_asset_library_get(sfile);
+}
+
+static int asset_catalog_new_exec(bContext *C, wmOperator *op)
+{
+ SpaceFile *sfile = CTX_wm_space_file(C);
+ 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);
+
+ MEM_freeN(parent_path);
+
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
+
+ return OPERATOR_FINISHED;
+}
+
+static void ASSET_OT_catalog_new(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "New Asset Catalog";
+ ot->description = "Create a new catalog to put assets in";
+ ot->idname = "ASSET_OT_catalog_new";
+
+ /* api callbacks */
+ ot->exec = asset_catalog_new_exec;
+ ot->poll = asset_catalog_operator_poll;
+
+ RNA_def_string(ot->srna,
+ "parent_path",
+ nullptr,
+ 0,
+ "Parent Path",
+ "Optional path defining the location to put the new catalog under");
+}
+
+static int asset_catalog_delete_exec(bContext *C, wmOperator *op)
+{
+ SpaceFile *sfile = CTX_wm_space_file(C);
+ struct AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile);
+ char *catalog_id_str = RNA_string_get_alloc(op->ptr, "catalog_id", nullptr, 0, nullptr);
+ bke::CatalogID catalog_id;
+ if (!BLI_uuid_parse_string(&catalog_id, catalog_id_str)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ ED_asset_catalog_remove(asset_library, catalog_id);
+
+ MEM_freeN(catalog_id_str);
+
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
+
+ return OPERATOR_FINISHED;
+}
+
+static void ASSET_OT_catalog_delete(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Delete Asset Catalog";
+ ot->description =
+ "Remove an asset catalog from the asset library (contained assets will not be affected and "
+ "show up as unassigned)";
+ ot->idname = "ASSET_OT_catalog_delete";
+
+ /* api callbacks */
+ ot->exec = asset_catalog_delete_exec;
+ ot->invoke = WM_operator_confirm;
+ ot->poll = asset_catalog_operator_poll;
+
+ RNA_def_string(ot->srna, "catalog_id", nullptr, 0, "Catalog ID", "ID of the catalog to delete");
+}
+
+/* -------------------------------------------------------------------- */
+
void ED_operatortypes_asset(void)
{
WM_operatortype_append(ASSET_OT_mark);
WM_operatortype_append(ASSET_OT_clear);
+ WM_operatortype_append(ASSET_OT_catalog_new);
+ WM_operatortype_append(ASSET_OT_catalog_delete);
+
WM_operatortype_append(ASSET_OT_list_refresh);
}
diff --git a/source/blender/editors/gpencil/annotate_paint.c b/source/blender/editors/gpencil/annotate_paint.c
index 68e2aece6e2..59ea105fbbb 100644
--- a/source/blender/editors/gpencil/annotate_paint.c
+++ b/source/blender/editors/gpencil/annotate_paint.c
@@ -2104,7 +2104,7 @@ static void annotation_draw_apply_event(
p->flags |= GP_PAINTFLAG_USE_STABILIZER_TEMP;
}
}
- /* We are using the temporal stabilizer flag atm,
+ /* We are using the temporal stabilizer flag at the moment,
* but shift is not pressed as well as the permanent flag is not used,
* so we don't need the cursor anymore. */
else if (p->flags & GP_PAINTFLAG_USE_STABILIZER_TEMP) {
diff --git a/source/blender/editors/gpencil/gpencil_edit.c b/source/blender/editors/gpencil/gpencil_edit.c
index 75ddfa47c57..1f31c60367e 100644
--- a/source/blender/editors/gpencil/gpencil_edit.c
+++ b/source/blender/editors/gpencil/gpencil_edit.c
@@ -4729,7 +4729,7 @@ static int gpencil_stroke_separate_exec(bContext *C, wmOperator *op)
/* Remove unused slots. */
int actcol = ob_dst->actcol;
for (int slot = 1; slot <= ob_dst->totcol; slot++) {
- while (slot <= ob_dst->totcol && !BKE_object_material_slot_used(ob_dst->data, slot)) {
+ while (slot <= ob_dst->totcol && !BKE_object_material_slot_used(ob_dst, slot)) {
ob_dst->actcol = slot;
BKE_object_material_slot_remove(bmain, ob_dst);
if (actcol >= slot) {
diff --git a/source/blender/editors/gpencil/gpencil_mesh.c b/source/blender/editors/gpencil/gpencil_mesh.c
index 0939d53736b..079089786d0 100644
--- a/source/blender/editors/gpencil/gpencil_mesh.c
+++ b/source/blender/editors/gpencil/gpencil_mesh.c
@@ -345,7 +345,7 @@ static int gpencil_bake_mesh_animation_exec(bContext *C, wmOperator *op)
/* Remove unused materials. */
int actcol = ob_gpencil->actcol;
for (int slot = 1; slot <= ob_gpencil->totcol; slot++) {
- while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil->data, slot)) {
+ while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) {
ob_gpencil->actcol = slot;
BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil);
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 9b157224178..957d8087bbd 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -1001,8 +1001,6 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
gps->points = MEM_callocN(sizeof(bGPDspoint) * gps->totpoints, "gp_stroke_points");
gps->dvert = NULL;
- /* drawing batch cache is dirty now */
- gpencil_update_cache(p->gpd);
/* set pointer to first non-initialized point */
pt = gps->points + (gps->totpoints - totelem);
if (gps->dvert != NULL) {
diff --git a/source/blender/editors/include/ED_anim_api.h b/source/blender/editors/include/ED_anim_api.h
index 6a0a42ee77b..e9601220f2e 100644
--- a/source/blender/editors/include/ED_anim_api.h
+++ b/source/blender/editors/include/ED_anim_api.h
@@ -861,7 +861,7 @@ void ED_operatormacros_action(void);
/* XXX: Should we be doing these here, or at all? */
/* Action Editor - Action Management */
-struct AnimData *ED_actedit_animdata_from_context(struct bContext *C);
+struct AnimData *ED_actedit_animdata_from_context(struct bContext *C, struct ID **r_adt_id_owner);
void ED_animedit_unlink_action(struct bContext *C,
struct ID *id,
struct AnimData *adt,
diff --git a/source/blender/editors/include/ED_fileselect.h b/source/blender/editors/include/ED_fileselect.h
index 82057c726a5..423d619f41a 100644
--- a/source/blender/editors/include/ED_fileselect.h
+++ b/source/blender/editors/include/ED_fileselect.h
@@ -142,6 +142,7 @@ void ED_fileselect_exit(struct wmWindowManager *wm, struct SpaceFile *sfile);
bool ED_fileselect_is_file_browser(const struct SpaceFile *sfile);
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.
diff --git a/source/blender/editors/include/ED_image.h b/source/blender/editors/include/ED_image.h
index 6b0b9f4a27c..9532035a1cd 100644
--- a/source/blender/editors/include/ED_image.h
+++ b/source/blender/editors/include/ED_image.h
@@ -41,6 +41,16 @@ struct SpaceImage;
struct bContext;
struct wmOperator;
struct wmWindowManager;
+struct View2D;
+
+/* image_draw.c */
+float ED_space_image_zoom_level(const struct View2D *v2d, const int grid_dimension);
+void ED_space_image_grid_steps(struct SpaceImage *sima,
+ float grid_steps[SI_GRID_STEPS_LEN],
+ const int grid_dimension);
+float ED_space_image_increment_snap_value(const int grid_dimesnions,
+ const float grid_steps[SI_GRID_STEPS_LEN],
+ const float zoom_factor);
/* image_edit.c, exported for transform */
struct Image *ED_space_image(struct SpaceImage *sima);
diff --git a/source/blender/editors/include/ED_keyframes_draw.h b/source/blender/editors/include/ED_keyframes_draw.h
index 61e37f20b1b..6a7037c3eed 100644
--- a/source/blender/editors/include/ED_keyframes_draw.h
+++ b/source/blender/editors/include/ED_keyframes_draw.h
@@ -41,7 +41,7 @@ struct bDopeSheet;
struct bGPDlayer;
/* draw simple diamond-shape keyframe */
-/* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_DIAMOND,
+/* caller should set up vertex format, bind GPU_SHADER_KEYFRAME_SHAPE,
* immBegin(GPU_PRIM_POINTS, n), then call this n times */
typedef struct KeyframeShaderBindings {
uint pos_id;
diff --git a/source/blender/editors/include/ED_text.h b/source/blender/editors/include/ED_text.h
index 2284c82b3d5..6e012ec1a91 100644
--- a/source/blender/editors/include/ED_text.h
+++ b/source/blender/editors/include/ED_text.h
@@ -34,6 +34,8 @@ struct UndoStep;
struct UndoType;
struct bContext;
+bool ED_text_activate_in_screen(struct bContext *C, struct Text *text);
+
void ED_text_scroll_to_cursor(struct SpaceText *st, struct ARegion *region, bool center);
bool ED_text_region_location_from_cursor(struct SpaceText *st,
diff --git a/source/blender/editors/include/ED_time_scrub_ui.h b/source/blender/editors/include/ED_time_scrub_ui.h
index 6420aaf5ef0..812cb31c9b0 100644
--- a/source/blender/editors/include/ED_time_scrub_ui.h
+++ b/source/blender/editors/include/ED_time_scrub_ui.h
@@ -33,8 +33,7 @@ struct wmEvent;
void ED_time_scrub_draw_current_frame(const struct ARegion *region,
const struct Scene *scene,
- bool display_seconds,
- bool draw_line);
+ bool display_seconds);
void ED_time_scrub_draw(const struct ARegion *region,
const struct Scene *scene,
diff --git a/source/blender/editors/include/ED_uvedit.h b/source/blender/editors/include/ED_uvedit.h
index ea3d921f2c5..f3aba12a924 100644
--- a/source/blender/editors/include/ED_uvedit.h
+++ b/source/blender/editors/include/ED_uvedit.h
@@ -237,6 +237,18 @@ void ED_image_draw_cursor(struct ARegion *region, const float cursor[2]);
void ED_uvedit_buttons_register(struct ARegionType *art);
/* uvedit_islands.c */
+
+struct UVMapUDIM_Params {
+ const struct Image *image;
+ /** Copied from #SpaceImage.tile_grid_shape */
+ int grid_shape[2];
+ bool use_target_udim;
+ int target_udim;
+};
+bool ED_uvedit_udim_params_from_image_space(const struct SpaceImage *sima,
+ bool use_active,
+ struct UVMapUDIM_Params *udim_params);
+
struct UVPackIsland_Params {
uint rotate : 1;
/** -1 not to align to axis, otherwise 0,1 for X,Y. */
@@ -246,9 +258,14 @@ struct UVPackIsland_Params {
uint use_seams : 1;
uint correct_aspect : 1;
};
+
+bool uv_coords_isect_udim(const struct Image *image,
+ const int udim_grid[2],
+ const float coords[2]);
void ED_uvedit_pack_islands_multi(const struct Scene *scene,
Object **objects,
const uint objects_len,
+ const struct UVMapUDIM_Params *udim_params,
const struct UVPackIsland_Params *params);
#ifdef __cplusplus
diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h
index ddd9ca4a98c..8a7df5b54ff 100644
--- a/source/blender/editors/include/UI_icons.h
+++ b/source/blender/editors/include/UI_icons.h
@@ -989,6 +989,16 @@ DEF_ICON_VECTOR(COLLECTION_COLOR_06)
DEF_ICON_VECTOR(COLLECTION_COLOR_07)
DEF_ICON_VECTOR(COLLECTION_COLOR_08)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_01)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_02)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_03)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_04)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_05)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_06)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_07)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_08)
+DEF_ICON_VECTOR(SEQUENCE_COLOR_09)
+
/* Events. */
DEF_ICON_COLOR(EVENT_A)
DEF_ICON_COLOR(EVENT_B)
diff --git a/source/blender/editors/include/UI_interface.h b/source/blender/editors/include/UI_interface.h
index 0f74d81f7bb..59d688ad2d0 100644
--- a/source/blender/editors/include/UI_interface.h
+++ b/source/blender/editors/include/UI_interface.h
@@ -84,6 +84,10 @@ typedef struct uiBlock uiBlock;
typedef struct uiBut uiBut;
typedef struct uiLayout uiLayout;
typedef struct uiPopupBlockHandle uiPopupBlockHandle;
+/* C handle for C++ #ui::AbstractTreeView type. */
+typedef struct uiTreeViewHandle uiTreeViewHandle;
+/* C handle for C++ #ui::AbstractTreeViewItem type. */
+typedef struct uiTreeViewItemHandle uiTreeViewItemHandle;
/* Defines */
@@ -389,6 +393,8 @@ typedef enum {
UI_BTYPE_GRIP = 57 << 9,
UI_BTYPE_DECORATOR = 58 << 9,
UI_BTYPE_DATASETROW = 59 << 9,
+ /* An item in a tree view. Parent items may be collapsible. */
+ UI_BTYPE_TREEROW = 60 << 9,
} eButType;
#define BUTTYPE (63 << 9)
@@ -1672,6 +1678,7 @@ void UI_but_datasetrow_component_set(uiBut *but, uint8_t geometry_component_type
void UI_but_datasetrow_domain_set(uiBut *but, uint8_t attribute_domain);
uint8_t UI_but_datasetrow_component_get(uiBut *but);
uint8_t UI_but_datasetrow_domain_get(uiBut *but);
+void UI_but_treerow_indentation_set(uiBut *but, int indentation);
void UI_but_node_link_set(uiBut *but, struct bNodeSocket *socket, const float draw_color[4]);
@@ -2587,6 +2594,7 @@ typedef struct uiDragColorHandle {
void ED_operatortypes_ui(void);
void ED_keymap_ui(struct wmKeyConfig *keyconf);
+void ED_dropboxes_ui(void);
void ED_uilisttypes_ui(void);
void UI_drop_color_copy(struct wmDrag *drag, struct wmDropBox *drop);
@@ -2755,6 +2763,17 @@ void UI_interface_tag_script_reload(void);
/* Support click-drag motion which presses the button and closes a popover (like a menu). */
#define USE_UI_POPOVER_ONCE
+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_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);
+
+uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const struct ARegion *region, int x, int y);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/include/UI_interface.hh b/source/blender/editors/include/UI_interface.hh
new file mode 100644
index 00000000000..4a583d0225e
--- /dev/null
+++ b/source/blender/editors/include/UI_interface.hh
@@ -0,0 +1,35 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup editorui
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "BLI_string_ref.hh"
+
+struct uiBlock;
+namespace blender::ui {
+class AbstractTreeView;
+}
+
+blender::ui::AbstractTreeView *UI_block_add_view(
+ uiBlock &block,
+ blender::StringRef idname,
+ std::unique_ptr<blender::ui::AbstractTreeView> tree_view);
diff --git a/source/blender/editors/include/UI_tree_view.hh b/source/blender/editors/include/UI_tree_view.hh
new file mode 100644
index 00000000000..d36e688dd65
--- /dev/null
+++ b/source/blender/editors/include/UI_tree_view.hh
@@ -0,0 +1,257 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup editorui
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "BLI_function_ref.hh"
+#include "BLI_vector.hh"
+
+#include "UI_resources.h"
+
+struct bContext;
+struct PointerRNA;
+struct uiBlock;
+struct uiBut;
+struct uiButTreeRow;
+struct uiLayout;
+struct wmEvent;
+struct wmDrag;
+
+namespace blender::ui {
+
+class AbstractTreeView;
+class AbstractTreeViewItem;
+
+/* ---------------------------------------------------------------------- */
+/** \name Tree-View Item Container
+ * \{ */
+
+/**
+ * Helper base class to expose common child-item data and functionality to both #AbstractTreeView
+ * and #AbstractTreeViewItem.
+ *
+ * That means this type can be used whenever either a #AbstractTreeView or a
+ * #AbstractTreeViewItem is needed.
+ */
+class TreeViewItemContainer {
+ friend class AbstractTreeView;
+ friend class AbstractTreeViewItem;
+
+ /* Private constructor, so only the friends above can create this! */
+ TreeViewItemContainer() = default;
+
+ protected:
+ Vector<std::unique_ptr<AbstractTreeViewItem>> children_;
+ /** Adding the first item to the root will set this, then it's passed on to all children. */
+ TreeViewItemContainer *root_ = nullptr;
+ /** Pointer back to the owning item. */
+ AbstractTreeViewItem *parent_ = nullptr;
+
+ public:
+ enum class IterOptions {
+ None = 0,
+ SkipCollapsed = 1 << 0,
+
+ /* Keep ENUM_OPERATORS() below updated! */
+ };
+ using ItemIterFn = FunctionRef<void(AbstractTreeViewItem &)>;
+
+ /**
+ * Convenience wrapper taking the arguments needed to construct an item of type \a ItemT. Calls
+ * the version just below.
+ */
+ template<class ItemT, typename... Args> ItemT &add_tree_item(Args &&...args)
+ {
+ static_assert(std::is_base_of<AbstractTreeViewItem, ItemT>::value,
+ "Type must derive from and implement the AbstractTreeViewItem interface");
+
+ return dynamic_cast<ItemT &>(
+ add_tree_item(std::make_unique<ItemT>(std::forward<Args>(args)...)));
+ }
+
+ AbstractTreeViewItem &add_tree_item(std::unique_ptr<AbstractTreeViewItem> item);
+
+ protected:
+ void foreach_item_recursive(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
+};
+
+ENUM_OPERATORS(TreeViewItemContainer::IterOptions,
+ TreeViewItemContainer::IterOptions::SkipCollapsed);
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Tree-View Builders
+ * \{ */
+
+class TreeViewBuilder {
+ uiBlock &block_;
+
+ public:
+ TreeViewBuilder(uiBlock &block);
+
+ void build_tree_view(AbstractTreeView &tree_view);
+};
+
+class TreeViewLayoutBuilder {
+ uiBlock &block_;
+
+ friend TreeViewBuilder;
+
+ public:
+ void build_row(AbstractTreeViewItem &item) const;
+ uiBlock &block() const;
+ uiLayout *current_layout() const;
+
+ private:
+ /* Created through #TreeViewBuilder. */
+ TreeViewLayoutBuilder(uiBlock &block);
+};
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Tree-View Base Class
+ * \{ */
+
+class AbstractTreeView : public TreeViewItemContainer {
+ friend TreeViewBuilder;
+ friend TreeViewLayoutBuilder;
+
+ public:
+ virtual ~AbstractTreeView() = default;
+
+ void foreach_item(ItemIterFn iter_fn, IterOptions options = IterOptions::None) const;
+
+ protected:
+ virtual void build_tree() = 0;
+
+ private:
+ /** Match the tree-view against an earlier version of itself (if any) and copy the old UI state
+ * (e.g. collapsed, active, selected) to the new one. See
+ * #AbstractTreeViewItem.update_from_old(). */
+ void update_from_old(uiBlock &new_block);
+ static void update_children_from_old_recursive(const TreeViewItemContainer &new_items,
+ const TreeViewItemContainer &old_items);
+ static AbstractTreeViewItem *find_matching_child(const AbstractTreeViewItem &lookup_item,
+ const TreeViewItemContainer &items);
+ void build_layout_from_tree(const TreeViewLayoutBuilder &builder);
+};
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Tree-View Item Type
+ * \{ */
+
+/** \brief Abstract base class for defining a customizable tree-view item.
+ *
+ * The tree-view item defines how to build its data into a tree-row. There are implementations for
+ * common layouts, e.g. #BasicTreeViewItem.
+ * It also stores state information that needs to be persistent over redraws, like the collapsed
+ * state.
+ */
+class AbstractTreeViewItem : public TreeViewItemContainer {
+ friend class AbstractTreeView;
+
+ bool is_open_ = false;
+ bool is_active_ = false;
+
+ protected:
+ /** This label is used for identifying an item (together with its parent's labels). */
+ std::string label_{};
+
+ public:
+ virtual ~AbstractTreeViewItem() = default;
+
+ virtual void build_row(uiLayout &row) = 0;
+
+ virtual void on_activate();
+ 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;
+
+ /** Copy persistent state (e.g. is-collapsed flag, selection, etc.) from a matching item of
+ * the last redraw to this item. If sub-classes introduce more advanced state they should
+ * override this and make it update their state accordingly. */
+ virtual void update_from_old(const AbstractTreeViewItem &old);
+ /** Compare this item to \a other to check if they represent the same data. This is critical for
+ * being able to recognize an item from a previous redraw, to be able to keep its state (e.g.
+ * open/closed, active, etc.). Items are only matched if their parents also match.
+ * By default this just matches the items names/labels (if their parents match!). If that isn't
+ * good enough for a sub-class, that can override it. */
+ virtual bool matches(const AbstractTreeViewItem &other) const;
+
+ const AbstractTreeView &get_tree_view() const;
+ int count_parents() const;
+ void set_active(bool value = true);
+ bool is_active() const;
+ void toggle_collapsed();
+ bool is_collapsed() const;
+ void set_collapsed(bool collapsed);
+ bool is_collapsible() const;
+};
+
+/** \} */
+
+/* ---------------------------------------------------------------------- */
+/** \name Predefined Tree-View Item Types
+ *
+ * Common, Basic Tree-View Item Types.
+ * \{ */
+
+/**
+ * The most basic type, just a label with an icon.
+ */
+class BasicTreeViewItem : public AbstractTreeViewItem {
+ public:
+ using ActivateFn = std::function<void(BasicTreeViewItem &new_active)>;
+ BIFIconID icon;
+
+ BasicTreeViewItem(StringRef label, BIFIconID icon = ICON_NONE, ActivateFn activate_fn = nullptr);
+
+ void build_row(uiLayout &row) override;
+ void on_activate() override;
+
+ protected:
+ /** Created in the #build() function. */
+ uiButTreeRow *tree_row_but_ = nullptr;
+ /** Optionally passed to the #BasicTreeViewItem constructor. Called when activating this tree
+ * view item. This way users don't have to sub-class #BasicTreeViewItem, just to implement
+ * custom activation behavior (a common thing to do). */
+ ActivateFn activate_fn_;
+
+ uiBut *button();
+ BIFIconID get_draw_icon() const;
+};
+
+/** \} */
+
+} // namespace blender::ui
diff --git a/source/blender/editors/include/UI_view2d.h b/source/blender/editors/include/UI_view2d.h
index e3c02b4c249..fdfa07a7e02 100644
--- a/source/blender/editors/include/UI_view2d.h
+++ b/source/blender/editors/include/UI_view2d.h
@@ -123,6 +123,7 @@ void UI_view2d_region_reinit(struct View2D *v2d, short type, int winx, int winy)
void UI_view2d_curRect_validate(struct View2D *v2d);
void UI_view2d_curRect_reset(struct View2D *v2d);
+bool UI_view2d_area_supports_sync(struct ScrArea *area);
void UI_view2d_sync(struct bScreen *screen, struct ScrArea *area, struct View2D *v2dcur, int flag);
/* Perform all required updates after `v2d->cur` as been modified.
diff --git a/source/blender/editors/interface/CMakeLists.txt b/source/blender/editors/interface/CMakeLists.txt
index 64c874e26a9..f27ff9694c2 100644
--- a/source/blender/editors/interface/CMakeLists.txt
+++ b/source/blender/editors/interface/CMakeLists.txt
@@ -42,6 +42,7 @@ set(SRC
interface_button_group.c
interface_context_menu.c
interface_draw.c
+ interface_dropboxes.cc
interface_eyedropper.c
interface_eyedropper_color.c
interface_eyedropper_colorband.c
@@ -73,8 +74,10 @@ set(SRC
interface_templates.c
interface_undo.c
interface_utils.c
+ interface_view.cc
interface_widgets.c
resources.c
+ tree_view.cc
view2d.c
view2d_draw.c
view2d_edge_pan.c
diff --git a/source/blender/editors/interface/interface.c b/source/blender/editors/interface/interface.c
index fd75be5b847..c53bffca778 100644
--- a/source/blender/editors/interface/interface.c
+++ b/source/blender/editors/interface/interface.c
@@ -743,6 +743,15 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
return false;
}
+ if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) {
+ uiButTreeRow *but_treerow = (uiButTreeRow *)but;
+ uiButTreeRow *oldbut_treerow = (uiButTreeRow *)oldbut;
+ if (!but_treerow->tree_item || !oldbut_treerow->tree_item ||
+ !UI_tree_view_item_matches(but_treerow->tree_item, oldbut_treerow->tree_item)) {
+ return false;
+ }
+ }
+
return true;
}
@@ -856,10 +865,21 @@ static void ui_but_update_old_active_from_new(uiBut *oldbut, uiBut *but)
oldbut->hardmax = but->hardmax;
}
- if (oldbut->type == UI_BTYPE_PROGRESS_BAR) {
- uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut;
- uiButProgressbar *progress_but = (uiButProgressbar *)but;
- progress_oldbut->progress = progress_but->progress;
+ switch (oldbut->type) {
+ case UI_BTYPE_PROGRESS_BAR: {
+ uiButProgressbar *progress_oldbut = (uiButProgressbar *)oldbut;
+ uiButProgressbar *progress_but = (uiButProgressbar *)but;
+ progress_oldbut->progress = progress_but->progress;
+ break;
+ }
+ case UI_BTYPE_TREEROW: {
+ uiButTreeRow *treerow_oldbut = (uiButTreeRow *)oldbut;
+ uiButTreeRow *treerow_newbut = (uiButTreeRow *)but;
+ SWAP(uiTreeViewItemHandle *, treerow_newbut->tree_item, treerow_oldbut->tree_item);
+ break;
+ }
+ default:
+ break;
}
/* move/copy string from the new button to the old */
@@ -2203,6 +2223,15 @@ int ui_but_is_pushed_ex(uiBut *but, double *value)
}
}
break;
+ case UI_BTYPE_TREEROW: {
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but;
+
+ is_push = -1;
+ if (tree_row_but->tree_item) {
+ is_push = UI_tree_view_item_is_active(tree_row_but->tree_item);
+ }
+ break;
+ }
default:
is_push = -1;
break;
@@ -3447,6 +3476,7 @@ void UI_block_free(const bContext *C, uiBlock *block)
BLI_freelistN(&block->color_pickers.list);
ui_block_free_button_groups(block);
+ ui_block_free_views(block);
MEM_freeN(block);
}
@@ -3942,6 +3972,10 @@ static void ui_but_alloc_info(const eButType type,
alloc_size = sizeof(uiButDatasetRow);
alloc_str = "uiButDatasetRow";
break;
+ case UI_BTYPE_TREEROW:
+ alloc_size = sizeof(uiButTreeRow);
+ alloc_str = "uiButTreeRow";
+ break;
default:
alloc_size = sizeof(uiBut);
alloc_str = "uiBut";
@@ -4141,6 +4175,7 @@ static uiBut *ui_def_but(uiBlock *block,
UI_BTYPE_BUT_MENU,
UI_BTYPE_SEARCH_MENU,
UI_BTYPE_DATASETROW,
+ UI_BTYPE_TREEROW,
UI_BTYPE_POPOVER)) {
but->drawflag |= (UI_BUT_TEXT_LEFT | UI_BUT_ICON_LEFT);
}
@@ -6198,6 +6233,13 @@ void UI_but_drag_set_asset(uiBut *but,
asset_drag->id_type = ED_asset_handle_get_id_type(asset);
asset_drag->import_type = import_type;
+ /* FIXME: This is temporary evil solution to get scene/viewlayer/etc in the copy callback of the
+ * #wmDropBox.
+ * TODO: Handle link/append in operator called at the end of the drop process, and NOT in its
+ * copy callback.
+ * */
+ asset_drag->evil_C = but->block->evil_C;
+
but->dragtype = WM_DRAG_ASSET;
ui_def_but_icon(but, icon, 0); /* no flag UI_HAS_ICON, so icon doesn't draw in button */
if (but->dragflag & UI_BUT_DRAGPOIN_FREE) {
@@ -6878,6 +6920,15 @@ void UI_but_datasetrow_indentation_set(uiBut *but, int indentation)
BLI_assert(indentation >= 0);
}
+void UI_but_treerow_indentation_set(uiBut *but, int indentation)
+{
+ uiButTreeRow *but_row = (uiButTreeRow *)but;
+ BLI_assert(but->type == UI_BTYPE_TREEROW);
+
+ but_row->indentation = indentation;
+ BLI_assert(indentation >= 0);
+}
+
/**
* Adds a hint to the button which draws right aligned, grayed out and never clipped.
*/
diff --git a/source/blender/editors/interface/interface_dropboxes.cc b/source/blender/editors/interface/interface_dropboxes.cc
new file mode 100644
index 00000000000..cb33e7f736e
--- /dev/null
+++ b/source/blender/editors/interface/interface_dropboxes.cc
@@ -0,0 +1,66 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edinterface
+ */
+
+#include "BKE_context.h"
+
+#include "DNA_space_types.h"
+
+#include "WM_api.h"
+
+#include "UI_interface.h"
+
+static bool ui_tree_view_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
+{
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
+ region, event->x, event->y);
+ if (!hovered_tree_item) {
+ return false;
+ }
+
+ return UI_tree_view_item_can_drop(hovered_tree_item, drag);
+}
+
+static char *ui_tree_view_drop_tooltip(bContext *C,
+ wmDrag *drag,
+ const wmEvent *event,
+ wmDropBox *UNUSED(drop))
+{
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
+ region, event->x, event->y);
+ if (!hovered_tree_item) {
+ return nullptr;
+ }
+
+ return UI_tree_view_item_drop_tooltip(hovered_tree_item, C, drag, event);
+}
+
+void ED_dropboxes_ui()
+{
+ ListBase *lb = WM_dropboxmap_find("User Interface", SPACE_EMPTY, 0);
+
+ WM_dropbox_add(lb,
+ "UI_OT_tree_view_drop",
+ ui_tree_view_drop_poll,
+ nullptr,
+ nullptr,
+ ui_tree_view_drop_tooltip);
+}
diff --git a/source/blender/editors/interface/interface_handlers.c b/source/blender/editors/interface/interface_handlers.c
index 77ae16d7cc7..aee66ec3a93 100644
--- a/source/blender/editors/interface/interface_handlers.c
+++ b/source/blender/editors/interface/interface_handlers.c
@@ -384,6 +384,8 @@ typedef struct uiHandleButtonData {
/* booleans (could be made into flags) */
bool cancel, escapecancel;
bool applied, applied_interactive;
+ /* Button is being applied through an extra icon. */
+ bool apply_through_extra_icon;
bool changed_cursor;
wmTimer *flashtimer;
@@ -1164,6 +1166,16 @@ static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleBu
data->applied = true;
}
+static void ui_apply_but_TREEROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
+{
+ if (data->apply_through_extra_icon) {
+ /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons.
+ */
+ return;
+ }
+ ui_apply_but_ROW(C, block, but, data);
+}
+
/**
* \note Ownership of \a properties is moved here. The #uiAfterFunc owns it now.
*
@@ -2307,6 +2319,9 @@ static void ui_apply_but(
case UI_BTYPE_ROW:
ui_apply_but_ROW(C, block, but, data);
break;
+ case UI_BTYPE_TREEROW:
+ ui_apply_but_TREEROW(C, block, but, data);
+ break;
case UI_BTYPE_LISTROW:
ui_apply_but_LISTROW(C, block, but, data);
break;
@@ -4194,6 +4209,8 @@ static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleBu
static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon)
{
+ but->active->apply_through_extra_icon = true;
+
if (but->active->interactive) {
ui_apply_but(C, but->block, but, but->active, true);
}
@@ -4737,7 +4754,7 @@ static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, cons
/* Behave like other menu items. */
do_activate = (event->val == KM_RELEASE);
}
- else {
+ else if (!ui_do_but_extra_operator_icon(C, but, data, event)) {
/* Also use double-clicks to prevent fast clicks to leak to other handlers (T76481). */
do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK);
}
@@ -7966,6 +7983,7 @@ static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *
case UI_BTYPE_CHECKBOX:
case UI_BTYPE_CHECKBOX_N:
case UI_BTYPE_ROW:
+ case UI_BTYPE_TREEROW:
case UI_BTYPE_DATASETROW:
retval = ui_do_but_TOG(C, but, data, event);
break;
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index f739830cfdb..c20129b4184 100644
--- a/source/blender/editors/interface/interface_icons.c
+++ b/source/blender/editors/interface/interface_icons.c
@@ -47,6 +47,7 @@
#include "DNA_gpencil_types.h"
#include "DNA_object_types.h"
#include "DNA_screen_types.h"
+#include "DNA_sequence_types.h"
#include "DNA_space_types.h"
#include "RNA_access.h"
@@ -309,7 +310,7 @@ static void vicon_keytype_draw_wrapper(
sh_bindings.flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 1.0f);
immUniform2f("ViewportSize", -1.0f, -1.0f);
immBegin(GPU_PRIM_POINTS, 1);
@@ -480,6 +481,35 @@ DEF_ICON_COLLECTION_COLOR_DRAW(08, COLLECTION_COLOR_08);
# undef DEF_ICON_COLLECTION_COLOR_DRAW
+static void vicon_strip_color_draw(
+ short color_tag, int x, int y, int w, int UNUSED(h), float UNUSED(alpha))
+{
+ bTheme *btheme = UI_GetTheme();
+ const ThemeStripColor *strip_color = &btheme->strip_color[color_tag];
+
+ const float aspect = (float)ICON_DEFAULT_WIDTH / (float)w;
+
+ UI_icon_draw_ex(x, y, ICON_SNAP_FACE, aspect, 1.0f, 0.0f, strip_color->color, true);
+}
+
+# define DEF_ICON_STRIP_COLOR_DRAW(index, color) \
+ static void vicon_strip_color_draw_##index(int x, int y, int w, int h, float alpha) \
+ { \
+ vicon_strip_color_draw(color, x, y, w, h, alpha); \
+ }
+
+DEF_ICON_STRIP_COLOR_DRAW(01, SEQUENCE_COLOR_01);
+DEF_ICON_STRIP_COLOR_DRAW(02, SEQUENCE_COLOR_02);
+DEF_ICON_STRIP_COLOR_DRAW(03, SEQUENCE_COLOR_03);
+DEF_ICON_STRIP_COLOR_DRAW(04, SEQUENCE_COLOR_04);
+DEF_ICON_STRIP_COLOR_DRAW(05, SEQUENCE_COLOR_05);
+DEF_ICON_STRIP_COLOR_DRAW(06, SEQUENCE_COLOR_06);
+DEF_ICON_STRIP_COLOR_DRAW(07, SEQUENCE_COLOR_07);
+DEF_ICON_STRIP_COLOR_DRAW(08, SEQUENCE_COLOR_08);
+DEF_ICON_STRIP_COLOR_DRAW(09, SEQUENCE_COLOR_09);
+
+# undef DEF_ICON_STRIP_COLOR_DRAW
+
/* Dynamically render icon instead of rendering a plain color to a texture/buffer
* This is not strictly a "vicon", as it needs access to icon->obj to get the color info,
* but it works in a very similar way.
@@ -995,6 +1025,16 @@ static void init_internal_icons(void)
def_internal_vicon(ICON_COLLECTION_COLOR_06, vicon_collection_color_draw_06);
def_internal_vicon(ICON_COLLECTION_COLOR_07, vicon_collection_color_draw_07);
def_internal_vicon(ICON_COLLECTION_COLOR_08, vicon_collection_color_draw_08);
+
+ def_internal_vicon(ICON_SEQUENCE_COLOR_01, vicon_strip_color_draw_01);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_02, vicon_strip_color_draw_02);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_03, vicon_strip_color_draw_03);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_04, vicon_strip_color_draw_04);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_05, vicon_strip_color_draw_05);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_06, vicon_strip_color_draw_06);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_07, vicon_strip_color_draw_07);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_08, vicon_strip_color_draw_08);
+ def_internal_vicon(ICON_SEQUENCE_COLOR_09, vicon_strip_color_draw_09);
}
static void init_iconfile_list(struct ListBase *list)
diff --git a/source/blender/editors/interface/interface_intern.h b/source/blender/editors/interface/interface_intern.h
index d61104f094e..8b45d9faae6 100644
--- a/source/blender/editors/interface/interface_intern.h
+++ b/source/blender/editors/interface/interface_intern.h
@@ -360,6 +360,14 @@ typedef struct uiButDatasetRow {
int indentation;
} uiButDatasetRow;
+/** Derived struct for #UI_BTYPE_TREEROW. */
+typedef struct uiButTreeRow {
+ uiBut but;
+
+ uiTreeViewItemHandle *tree_item;
+ int indentation;
+} uiButTreeRow;
+
/** Derived struct for #UI_BTYPE_HSVCUBE. */
typedef struct uiButHSVCube {
uiBut but;
@@ -488,6 +496,11 @@ struct uiBlock {
ListBase contexts;
+ /** A block can store "views" on data-sets. Currently tree-views (#AbstractTreeView) only.
+ * Others are imaginable, e.g. table-views, grid-views, etc. These are stored here to support
+ * state that is persistent over redraws (e.g. collapsed tree-view items). */
+ ListBase views;
+
char name[UI_MAX_NAME_STR];
float winmat[4][4];
@@ -1158,6 +1171,7 @@ uiBut *ui_list_row_find_mouse_over(const struct ARegion *region,
uiBut *ui_list_row_find_from_index(const struct ARegion *region,
const int index,
uiBut *listbox) ATTR_WARN_UNUSED_RESULT;
+uiBut *ui_tree_row_find_mouse_over(const struct ARegion *region, const int x, const int y);
typedef bool (*uiButFindPollFn)(const uiBut *but, const void *customdata);
uiBut *ui_but_find_mouse_over_ex(const struct ARegion *region,
@@ -1274,6 +1288,11 @@ bool ui_jump_to_target_button_poll(struct bContext *C);
/* interface_queries.c */
void ui_interface_tag_script_reload_queries(void);
+/* interface_view.cc */
+void ui_block_free_views(struct uiBlock *block);
+uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+ const uiTreeViewHandle *new_view);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/editors/interface/interface_layout.c b/source/blender/editors/interface/interface_layout.c
index 66c75c63050..64c16e57f56 100644
--- a/source/blender/editors/interface/interface_layout.c
+++ b/source/blender/editors/interface/interface_layout.c
@@ -288,35 +288,85 @@ static bool ui_layout_variable_size(uiLayout *layout)
return ui_layout_vary_direction(layout) == UI_ITEM_VARY_X || layout->variable_size;
}
-/* estimated size of text + icon */
-static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact)
+/**
+ * Factors to apply to #UI_UNIT_X when calculating button width.
+ * This is used when the layout is a varying size, see #ui_layout_variable_size.
+ */
+struct uiTextIconPadFactor {
+ float text;
+ float icon;
+ float icon_only;
+};
+
+/**
+ * This adds over an icons width of padding even when no icon is used,
+ * this is done because most buttons need additional space (drop-down chevron for example).
+ * menus and labels use much smaller `text` values compared to this default.
+ *
+ * \note It may seem odd that the icon only adds 0.25
+ * but taking margins into account its fine,
+ * except for #ui_text_pad_compact where a bit more margin is required.
+ */
+static const struct uiTextIconPadFactor ui_text_pad_default = {
+ .text = 1.50f,
+ .icon = 0.25f,
+ .icon_only = 0.0f,
+};
+
+/** #ui_text_pad_default scaled down. */
+static const struct uiTextIconPadFactor ui_text_pad_compact = {
+ .text = 1.25f,
+ .icon = 0.35f,
+ .icon_only = 0.0f,
+};
+
+/** Least amount of padding not to clip the text or icon. */
+static const struct uiTextIconPadFactor ui_text_pad_none = {
+ .text = 0.25f,
+ .icon = 1.50f,
+ .icon_only = 0.0f,
+};
+
+/**
+ * Estimated size of text + icon.
+ */
+static int ui_text_icon_width_ex(uiLayout *layout,
+ const char *name,
+ int icon,
+ const struct uiTextIconPadFactor *pad_factor)
{
const int unit_x = UI_UNIT_X * (layout->scale[0] ? layout->scale[0] : 1.0f);
+ /* When there is no text, always behave as if this is an icon-only button
+ * since it's not useful to return empty space. */
if (icon && !name[0]) {
- return unit_x; /* icon only */
+ return unit_x * (1.0f + pad_factor->icon_only);
}
if (ui_layout_variable_size(layout)) {
if (!icon && !name[0]) {
- return unit_x; /* No icon or name. */
+ return unit_x * (1.0f + pad_factor->icon_only);
}
+
if (layout->alignment != UI_LAYOUT_ALIGN_EXPAND) {
layout->item.flag |= UI_ITEM_FIXED_SIZE;
}
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
- float margin = compact ? 1.25 : 1.50;
+ float margin = pad_factor->text;
if (icon) {
- /* It may seem odd that the icon only adds (unit_x / 4)
- * but taking margins into account its fine, except
- * in compact mode a bit more margin is required. */
- margin += compact ? 0.35 : 0.25;
+ margin += pad_factor->icon;
}
return UI_fontstyle_string_width(fstyle, name) + (unit_x * margin);
}
return unit_x * 10;
}
+static int ui_text_icon_width(uiLayout *layout, const char *name, int icon, bool compact)
+{
+ return ui_text_icon_width_ex(
+ layout, name, icon, compact ? &ui_text_pad_compact : &ui_text_pad_default);
+}
+
static void ui_item_size(uiItem *item, int *r_w, int *r_h)
{
if (item->type == ITEM_BUTTON) {
@@ -2857,23 +2907,23 @@ static uiBut *ui_item_menu(uiLayout *layout,
icon = ICON_BLANK1;
}
- int w = ui_text_icon_width(layout, name, icon, 1);
- const int h = UI_UNIT_Y;
-
+ struct uiTextIconPadFactor pad_factor = ui_text_pad_compact;
if (layout->root->type == UI_LAYOUT_HEADER) { /* Ugly! */
if (icon == ICON_NONE && force_menu) {
/* pass */
}
else if (force_menu) {
- w += 0.6f * UI_UNIT_X;
+ pad_factor.text = 1.85;
+ pad_factor.icon_only = 0.6f;
}
else {
- if (name[0]) {
- w -= UI_UNIT_X / 2;
- }
+ pad_factor.text = 0.75f;
}
}
+ const int w = ui_text_icon_width_ex(layout, name, icon, &pad_factor);
+ const int h = UI_UNIT_Y;
+
if (heading_layout) {
ui_layout_heading_label_add(layout, heading_layout, true, true);
}
@@ -3133,8 +3183,7 @@ static uiBut *uiItemL_(uiLayout *layout, const char *name, int icon)
icon = ICON_BLANK1;
}
- const int w = ui_text_icon_width(layout, name, icon, 0);
-
+ const int w = ui_text_icon_width_ex(layout, name, icon, &ui_text_pad_none);
uiBut *but;
if (icon && name[0]) {
but = uiDefIconTextBut(
diff --git a/source/blender/editors/interface/interface_ops.c b/source/blender/editors/interface/interface_ops.c
index dd10d942fc9..1fc07bce341 100644
--- a/source/blender/editors/interface/interface_ops.c
+++ b/source/blender/editors/interface/interface_ops.c
@@ -1381,16 +1381,7 @@ static int editsource_text_edit(bContext *C,
/* naughty!, find text area to set, not good behavior
* but since this is a developer tool lets allow it - campbell */
- ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0);
- if (area) {
- SpaceText *st = area->spacedata.first;
- ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
- st->text = text;
- if (region) {
- ED_text_scroll_to_cursor(st, region, true);
- }
- }
- else {
+ if (!ED_text_activate_in_screen(C, text)) {
BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2);
}
@@ -1927,6 +1918,51 @@ static void UI_OT_list_start_filter(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name UI Tree-View Drop Operator
+ * \{ */
+
+static bool ui_tree_view_drop_poll(bContext *C)
+{
+ const wmWindow *win = CTX_wm_window(C);
+ const ARegion *region = CTX_wm_region(C);
+ const uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
+ region, win->eventstate->x, win->eventstate->y);
+
+ return hovered_tree_item != NULL;
+}
+
+static int ui_tree_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+{
+ if (event->custom != EVT_DATA_DRAGDROP) {
+ return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
+ }
+
+ const ARegion *region = CTX_wm_region(C);
+ uiTreeViewItemHandle *hovered_tree_item = UI_block_tree_view_find_item_at(
+ region, event->x, event->y);
+
+ if (!UI_tree_view_item_drop_handle(hovered_tree_item, event->customdata)) {
+ return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void UI_OT_tree_view_drop(wmOperatorType *ot)
+{
+ ot->name = "Tree View drop";
+ ot->idname = "UI_OT_tree_view_drop";
+ ot->description = "Drag and drop items onto a tree item";
+
+ ot->invoke = ui_tree_view_drop_invoke;
+ ot->poll = ui_tree_view_drop_poll;
+
+ ot->flag = OPTYPE_INTERNAL;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Operator & Keymap Registration
* \{ */
@@ -1953,6 +1989,8 @@ void ED_operatortypes_ui(void)
WM_operatortype_append(UI_OT_list_start_filter);
+ WM_operatortype_append(UI_OT_tree_view_drop);
+
/* external */
WM_operatortype_append(UI_OT_eyedropper_color);
WM_operatortype_append(UI_OT_eyedropper_colorramp);
diff --git a/source/blender/editors/interface/interface_query.c b/source/blender/editors/interface/interface_query.c
index 09429bb6df5..2f6bda3252d 100644
--- a/source/blender/editors/interface/interface_query.c
+++ b/source/blender/editors/interface/interface_query.c
@@ -69,7 +69,8 @@ bool ui_but_is_toggle(const uiBut *but)
UI_BTYPE_CHECKBOX,
UI_BTYPE_CHECKBOX_N,
UI_BTYPE_ROW,
- UI_BTYPE_DATASETROW);
+ UI_BTYPE_DATASETROW,
+ UI_BTYPE_TREEROW);
}
/**
@@ -462,6 +463,16 @@ uiBut *ui_list_row_find_from_index(const ARegion *region, const int index, uiBut
return ui_but_find(region, ui_but_is_listrow_at_index, &data);
}
+static bool ui_but_is_treerow(const uiBut *but, const void *UNUSED(customdata))
+{
+ return but->type == UI_BTYPE_TREEROW;
+}
+
+uiBut *ui_tree_row_find_mouse_over(const ARegion *region, const int x, const int y)
+{
+ return ui_but_find_mouse_over_ex(region, x, y, false, ui_but_is_treerow, NULL);
+}
+
/** \} */
/* -------------------------------------------------------------------- */
diff --git a/source/blender/editors/interface/interface_view.cc b/source/blender/editors/interface/interface_view.cc
new file mode 100644
index 00000000000..b199ce9562e
--- /dev/null
+++ b/source/blender/editors/interface/interface_view.cc
@@ -0,0 +1,133 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edinterface
+ *
+ * This part of the UI-View API is mostly needed to support persistent state of items within the
+ * view. Views are stored in #uiBlock's, and kept alive with it until after the next redraw. So we
+ * can compare the old view items with the new view items and keep state persistent for matching
+ * ones.
+ */
+
+#include <memory>
+#include <variant>
+
+#include "DNA_screen_types.h"
+
+#include "BLI_listbase.h"
+
+#include "interface_intern.h"
+
+#include "UI_interface.hh"
+#include "UI_tree_view.hh"
+
+using namespace blender;
+using namespace blender::ui;
+
+/**
+ * Wrapper to store views in a #ListBase. There's no `uiView` base class, we just store views as a
+ * #std::variant.
+ */
+struct ViewLink : public Link {
+ using TreeViewPtr = std::unique_ptr<AbstractTreeView>;
+
+ std::string idname;
+ /* Note: Can't use std::get() on this until minimum macOS deployment target is 10.14. */
+ std::variant<TreeViewPtr> view;
+};
+
+template<class T> T *get_view_from_link(ViewLink &link)
+{
+ auto *t_uptr = std::get_if<std::unique_ptr<T>>(&link.view);
+ return t_uptr ? t_uptr->get() : nullptr;
+}
+
+/**
+ * Override this for all available tree types.
+ */
+AbstractTreeView *UI_block_add_view(uiBlock &block,
+ StringRef idname,
+ std::unique_ptr<AbstractTreeView> tree_view)
+{
+ ViewLink *view_link = OBJECT_GUARDED_NEW(ViewLink);
+ BLI_addtail(&block.views, view_link);
+
+ view_link->view = std::move(tree_view);
+ view_link->idname = idname;
+
+ return get_view_from_link<AbstractTreeView>(*view_link);
+}
+
+void ui_block_free_views(uiBlock *block)
+{
+ LISTBASE_FOREACH_MUTABLE (ViewLink *, link, &block->views) {
+ OBJECT_GUARDED_DELETE(link, ViewLink);
+ }
+}
+
+/**
+ * \param x, y: Coordinate to find a tree-row item at, in window space.
+ */
+uiTreeViewItemHandle *UI_block_tree_view_find_item_at(const ARegion *region,
+ const int x,
+ const int y)
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)ui_tree_row_find_mouse_over(region, x, y);
+ if (!tree_row_but) {
+ return nullptr;
+ }
+
+ return tree_row_but->tree_item;
+}
+
+static StringRef ui_block_view_find_idname(const uiBlock &block, const AbstractTreeView &view)
+{
+ /* First get the idname the of the view we're looking for. */
+ LISTBASE_FOREACH (ViewLink *, view_link, &block.views) {
+ if (get_view_from_link<AbstractTreeView>(*view_link) == &view) {
+ return view_link->idname;
+ }
+ }
+
+ return {};
+}
+
+uiTreeViewHandle *ui_block_view_find_matching_in_old_block(const uiBlock *new_block,
+ const uiTreeViewHandle *new_view_handle)
+{
+ const AbstractTreeView &needle_view = reinterpret_cast<const AbstractTreeView &>(
+ *new_view_handle);
+
+ uiBlock *old_block = new_block->oldblock;
+ if (!old_block) {
+ return nullptr;
+ }
+
+ StringRef idname = ui_block_view_find_idname(*new_block, needle_view);
+ if (idname.is_empty()) {
+ return nullptr;
+ }
+
+ LISTBASE_FOREACH (ViewLink *, old_view_link, &old_block->views) {
+ if (old_view_link->idname == idname) {
+ return reinterpret_cast<uiTreeViewHandle *>(
+ get_view_from_link<AbstractTreeView>(*old_view_link));
+ }
+ }
+
+ return nullptr;
+}
diff --git a/source/blender/editors/interface/interface_widgets.c b/source/blender/editors/interface/interface_widgets.c
index 0dc7c2d3f9a..466deedf3bb 100644
--- a/source/blender/editors/interface/interface_widgets.c
+++ b/source/blender/editors/interface/interface_widgets.c
@@ -115,6 +115,7 @@ typedef enum {
UI_WTYPE_PROGRESSBAR,
UI_WTYPE_NODESOCKET,
UI_WTYPE_DATASETROW,
+ UI_WTYPE_TREEROW,
} uiWidgetTypeEnum;
/* Button state argument shares bits with 'uiBut.flag'.
@@ -3679,10 +3680,9 @@ static void widget_progressbar(
widgetbase_draw(&wtb_bar, wcol);
}
-static void widget_datasetrow(
- uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign))
+static void widget_treerow_exec(
+ uiWidgetColors *wcol, rcti *rect, int state, int UNUSED(roundboxalign), int indentation)
{
- uiButDatasetRow *but_componentrow = (uiButDatasetRow *)but;
uiWidgetBase wtb;
widget_init(&wtb);
@@ -3695,10 +3695,24 @@ static void widget_datasetrow(
widgetbase_draw(&wtb, wcol);
}
- BLI_rcti_resize(rect,
- BLI_rcti_size_x(rect) - UI_UNIT_X * but_componentrow->indentation,
- BLI_rcti_size_y(rect));
- BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * but_componentrow->indentation, 0);
+ BLI_rcti_resize(rect, BLI_rcti_size_x(rect) - UI_UNIT_X * indentation, BLI_rcti_size_y(rect));
+ BLI_rcti_translate(rect, 0.5f * UI_UNIT_X * indentation, 0);
+}
+
+static void widget_treerow(
+ uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
+{
+ uiButTreeRow *tree_row = (uiButTreeRow *)but;
+ BLI_assert(but->type == UI_BTYPE_TREEROW);
+ widget_treerow_exec(wcol, rect, state, roundboxalign, tree_row->indentation);
+}
+
+static void widget_datasetrow(
+ uiBut *but, uiWidgetColors *wcol, rcti *rect, int state, int roundboxalign)
+{
+ uiButDatasetRow *dataset_row = (uiButDatasetRow *)but;
+ BLI_assert(but->type == UI_BTYPE_DATASETROW);
+ widget_treerow_exec(wcol, rect, state, roundboxalign, dataset_row->indentation);
}
static void widget_nodesocket(
@@ -4492,6 +4506,10 @@ static uiWidgetType *widget_type(uiWidgetTypeEnum type)
wt.custom = widget_datasetrow;
break;
+ case UI_WTYPE_TREEROW:
+ wt.custom = widget_treerow;
+ break;
+
case UI_WTYPE_NODESOCKET:
wt.custom = widget_nodesocket;
break;
@@ -4824,6 +4842,11 @@ void ui_draw_but(const bContext *C, struct ARegion *region, uiStyle *style, uiBu
fstyle = &style->widgetlabel;
break;
+ case UI_BTYPE_TREEROW:
+ wt = widget_type(UI_WTYPE_TREEROW);
+ fstyle = &style->widgetlabel;
+ break;
+
case UI_BTYPE_SCROLL:
wt = widget_type(UI_WTYPE_SCROLL);
break;
@@ -5348,7 +5371,7 @@ void ui_draw_menu_item(const uiFontStyle *fstyle,
}
}
else {
- BLI_assert_msg(0, "Unknwon menu item separator type");
+ BLI_assert_msg(0, "Unknown menu item separator type");
}
}
}
diff --git a/source/blender/editors/interface/tree_view.cc b/source/blender/editors/interface/tree_view.cc
new file mode 100644
index 00000000000..0ea15a2a5bb
--- /dev/null
+++ b/source/blender/editors/interface/tree_view.cc
@@ -0,0 +1,381 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup edinterface
+ */
+
+#include "DNA_userdef_types.h"
+
+#include "BLT_translation.h"
+
+#include "interface_intern.h"
+
+#include "UI_interface.h"
+
+#include "UI_tree_view.hh"
+
+namespace blender::ui {
+
+/* ---------------------------------------------------------------------- */
+
+/**
+ * Add a tree-item to the container. This is the only place where items should be added, it handles
+ * important invariants!
+ */
+AbstractTreeViewItem &TreeViewItemContainer::add_tree_item(
+ std::unique_ptr<AbstractTreeViewItem> item)
+{
+ children_.append(std::move(item));
+
+ /* The first item that will be added to the root sets this. */
+ if (root_ == nullptr) {
+ root_ = this;
+ }
+
+ AbstractTreeViewItem &added_item = *children_.last();
+ added_item.root_ = root_;
+ if (root_ != this) {
+ /* Any item that isn't the root can be assumed to the a #AbstractTreeViewItem. Not entirely
+ * nice to static_cast this, but well... */
+ added_item.parent_ = static_cast<AbstractTreeViewItem *>(this);
+ }
+
+ return added_item;
+}
+
+void TreeViewItemContainer::foreach_item_recursive(ItemIterFn iter_fn, IterOptions options) const
+{
+ for (const auto &child : children_) {
+ iter_fn(*child);
+ if (bool(options & IterOptions::SkipCollapsed) && child->is_collapsed()) {
+ continue;
+ }
+
+ child->foreach_item_recursive(iter_fn, options);
+ }
+}
+
+/* ---------------------------------------------------------------------- */
+
+void AbstractTreeView::foreach_item(ItemIterFn iter_fn, IterOptions options) const
+{
+ foreach_item_recursive(iter_fn, options);
+}
+
+void AbstractTreeView::build_layout_from_tree(const TreeViewLayoutBuilder &builder)
+{
+ uiLayout *prev_layout = builder.current_layout();
+
+ uiLayoutColumn(prev_layout, true);
+
+ foreach_item([&builder](AbstractTreeViewItem &item) { builder.build_row(item); },
+ IterOptions::SkipCollapsed);
+
+ UI_block_layout_set_current(&builder.block(), prev_layout);
+}
+
+void AbstractTreeView::update_from_old(uiBlock &new_block)
+{
+ uiBlock *old_block = new_block.oldblock;
+ if (!old_block) {
+ return;
+ }
+
+ uiTreeViewHandle *old_view_handle = ui_block_view_find_matching_in_old_block(
+ &new_block, reinterpret_cast<uiTreeViewHandle *>(this));
+ if (!old_view_handle) {
+ return;
+ }
+
+ AbstractTreeView &old_view = reinterpret_cast<AbstractTreeView &>(*old_view_handle);
+ update_children_from_old_recursive(*this, old_view);
+}
+
+void AbstractTreeView::update_children_from_old_recursive(const TreeViewItemContainer &new_items,
+ const TreeViewItemContainer &old_items)
+{
+ for (const auto &new_item : new_items.children_) {
+ AbstractTreeViewItem *matching_old_item = find_matching_child(*new_item, old_items);
+ if (!matching_old_item) {
+ continue;
+ }
+
+ new_item->update_from_old(*matching_old_item);
+
+ /* Recurse into children of the matched item. */
+ update_children_from_old_recursive(*new_item, *matching_old_item);
+ }
+}
+
+AbstractTreeViewItem *AbstractTreeView::find_matching_child(
+ const AbstractTreeViewItem &lookup_item, const TreeViewItemContainer &items)
+{
+ for (const auto &iter_item : items.children_) {
+ if (lookup_item.matches(*iter_item)) {
+ /* We have a matching item! */
+ return iter_item.get();
+ }
+ }
+
+ return nullptr;
+}
+
+/* ---------------------------------------------------------------------- */
+
+void AbstractTreeViewItem::on_activate()
+{
+ /* Do nothing by default. */
+}
+
+bool AbstractTreeViewItem::on_drop(const wmDrag & /*drag*/)
+{
+ /* Do nothing by default. */
+ return false;
+}
+
+bool AbstractTreeViewItem::can_drop(const wmDrag & /*drag*/) const
+{
+ return false;
+}
+
+std::string AbstractTreeViewItem::drop_tooltip(const bContext & /*C*/,
+ const wmDrag & /*drag*/,
+ const wmEvent & /*event*/) const
+{
+ return TIP_("Drop into/onto tree item");
+}
+
+void AbstractTreeViewItem::update_from_old(const AbstractTreeViewItem &old)
+{
+ is_open_ = old.is_open_;
+ is_active_ = old.is_active_;
+}
+
+bool AbstractTreeViewItem::matches(const AbstractTreeViewItem &other) const
+{
+ return label_ == other.label_;
+}
+
+const AbstractTreeView &AbstractTreeViewItem::get_tree_view() const
+{
+ return static_cast<AbstractTreeView &>(*root_);
+}
+
+int AbstractTreeViewItem::count_parents() const
+{
+ int i = 0;
+ for (TreeViewItemContainer *parent = parent_; parent; parent = parent->parent_) {
+ i++;
+ }
+ return i;
+}
+
+void AbstractTreeViewItem::set_active(bool value)
+{
+ if (value && !is_active()) {
+ /* Deactivate other items in the tree. */
+ get_tree_view().foreach_item([](auto &item) { item.set_active(false); });
+ on_activate();
+ }
+ is_active_ = value;
+}
+
+bool AbstractTreeViewItem::is_active() const
+{
+ return is_active_;
+}
+
+bool AbstractTreeViewItem::is_collapsed() const
+{
+ return is_collapsible() && !is_open_;
+}
+
+void AbstractTreeViewItem::toggle_collapsed()
+{
+ is_open_ = !is_open_;
+}
+
+void AbstractTreeViewItem::set_collapsed(bool collapsed)
+{
+ is_open_ = !collapsed;
+}
+
+bool AbstractTreeViewItem::is_collapsible() const
+{
+ return !children_.is_empty();
+}
+
+/* ---------------------------------------------------------------------- */
+
+TreeViewBuilder::TreeViewBuilder(uiBlock &block) : block_(block)
+{
+}
+
+void TreeViewBuilder::build_tree_view(AbstractTreeView &tree_view)
+{
+ tree_view.build_tree();
+ tree_view.update_from_old(block_);
+ tree_view.build_layout_from_tree(TreeViewLayoutBuilder(block_));
+}
+
+/* ---------------------------------------------------------------------- */
+
+TreeViewLayoutBuilder::TreeViewLayoutBuilder(uiBlock &block) : block_(block)
+{
+}
+
+void TreeViewLayoutBuilder::build_row(AbstractTreeViewItem &item) const
+{
+ uiLayout *prev_layout = current_layout();
+ uiLayout *row = uiLayoutRow(prev_layout, false);
+
+ item.build_row(*row);
+
+ UI_block_layout_set_current(&block(), prev_layout);
+}
+
+uiBlock &TreeViewLayoutBuilder::block() const
+{
+ return block_;
+}
+
+uiLayout *TreeViewLayoutBuilder::current_layout() const
+{
+ return block().curlayout;
+}
+
+/* ---------------------------------------------------------------------- */
+
+BasicTreeViewItem::BasicTreeViewItem(StringRef label, BIFIconID icon_, ActivateFn activate_fn)
+ : icon(icon_), activate_fn_(activate_fn)
+{
+ label_ = label;
+}
+
+static void tree_row_click_fn(struct bContext *UNUSED(C), void *but_arg1, void *UNUSED(arg2))
+{
+ uiButTreeRow *tree_row_but = (uiButTreeRow *)but_arg1;
+ AbstractTreeViewItem &tree_item = reinterpret_cast<AbstractTreeViewItem &>(
+ *tree_row_but->tree_item);
+
+ /* Let a click on an opened item activate it, a second click will close it then.
+ * TODO Should this be for asset catalogs only? */
+ if (tree_item.is_collapsed() || tree_item.is_active()) {
+ tree_item.toggle_collapsed();
+ }
+ tree_item.set_active();
+}
+
+void BasicTreeViewItem::build_row(uiLayout &row)
+{
+ uiBlock *block = uiLayoutGetBlock(&row);
+ tree_row_but_ = (uiButTreeRow *)uiDefIconTextBut(block,
+ UI_BTYPE_TREEROW,
+ 0,
+ /* TODO allow icon besides the chevron icon? */
+ get_draw_icon(),
+ label_.data(),
+ 0,
+ 0,
+ UI_UNIT_X,
+ UI_UNIT_Y,
+ nullptr,
+ 0,
+ 0,
+ 0,
+ 0,
+ nullptr);
+
+ tree_row_but_->tree_item = reinterpret_cast<uiTreeViewItemHandle *>(this);
+ UI_but_func_set(&tree_row_but_->but, tree_row_click_fn, tree_row_but_, nullptr);
+ UI_but_treerow_indentation_set(&tree_row_but_->but, count_parents());
+}
+
+void BasicTreeViewItem::on_activate()
+{
+ if (activate_fn_) {
+ activate_fn_(*this);
+ }
+}
+
+BIFIconID BasicTreeViewItem::get_draw_icon() const
+{
+ if (icon) {
+ return icon;
+ }
+
+ if (is_collapsible()) {
+ return is_collapsed() ? ICON_TRIA_RIGHT : ICON_TRIA_DOWN;
+ }
+
+ return ICON_NONE;
+}
+
+uiBut *BasicTreeViewItem::button()
+{
+ return &tree_row_but_->but;
+}
+
+} // namespace blender::ui
+
+using namespace blender::ui;
+
+bool UI_tree_view_item_is_active(const uiTreeViewItemHandle *item_handle)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_handle);
+ return item.is_active();
+}
+
+bool UI_tree_view_item_matches(const uiTreeViewItemHandle *a_handle,
+ const uiTreeViewItemHandle *b_handle)
+{
+ const AbstractTreeViewItem &a = reinterpret_cast<const AbstractTreeViewItem &>(*a_handle);
+ const AbstractTreeViewItem &b = reinterpret_cast<const AbstractTreeViewItem &>(*b_handle);
+ return a.matches(b);
+}
+
+bool UI_tree_view_item_can_drop(const uiTreeViewItemHandle *item_, const wmDrag *drag)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ return item.can_drop(*drag);
+}
+
+char *UI_tree_view_item_drop_tooltip(const uiTreeViewItemHandle *item_,
+ const bContext *C,
+ const wmDrag *drag,
+ const wmEvent *event)
+{
+ const AbstractTreeViewItem &item = reinterpret_cast<const AbstractTreeViewItem &>(*item_);
+ return BLI_strdup(item.drop_tooltip(*C, *drag, *event).c_str());
+}
+
+/**
+ * Let a tree-view item handle a drop event.
+ * \return True if the drop was handled by the tree-view item.
+ */
+bool UI_tree_view_item_drop_handle(uiTreeViewItemHandle *item_, const ListBase *drags)
+{
+ AbstractTreeViewItem &item = reinterpret_cast<AbstractTreeViewItem &>(*item_);
+
+ LISTBASE_FOREACH (const wmDrag *, drag, drags) {
+ if (item.can_drop(*drag)) {
+ return item.on_drop(*drag);
+ }
+ }
+
+ return false;
+}
diff --git a/source/blender/editors/interface/view2d.c b/source/blender/editors/interface/view2d.c
index 23c8a0d35bf..0036a812a87 100644
--- a/source/blender/editors/interface/view2d.c
+++ b/source/blender/editors/interface/view2d.c
@@ -866,6 +866,11 @@ void UI_view2d_curRect_changed(const bContext *C, View2D *v2d)
/* ------------------ */
+bool UI_view2d_area_supports_sync(ScrArea *area)
+{
+ return ELEM(area->spacetype, SPACE_ACTION, SPACE_NLA, SPACE_SEQ, SPACE_CLIP, SPACE_GRAPH);
+}
+
/* Called by menus to activate it, or by view2d operators
* to make sure 'related' views stay in synchrony */
void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag)
@@ -903,6 +908,9 @@ void UI_view2d_sync(bScreen *screen, ScrArea *area, View2D *v2dcur, int flag)
/* check if doing whole screen syncing (i.e. time/horizontal) */
if ((v2dcur->flag & V2D_VIEWSYNC_SCREEN_TIME) && (screen)) {
LISTBASE_FOREACH (ScrArea *, area_iter, &screen->areabase) {
+ if (!UI_view2d_area_supports_sync(area_iter)) {
+ continue;
+ }
LISTBASE_FOREACH (ARegion *, region, &area_iter->regionbase) {
/* don't operate on self */
if (v2dcur != &region->v2d) {
diff --git a/source/blender/editors/mesh/editmesh_knife.c b/source/blender/editors/mesh/editmesh_knife.c
index eee4aec7459..64c008acf8e 100644
--- a/source/blender/editors/mesh/editmesh_knife.c
+++ b/source/blender/editors/mesh/editmesh_knife.c
@@ -113,7 +113,7 @@ typedef struct KnifeColors {
uchar axis_extra[3];
} KnifeColors;
-/* Knifetool Operator. */
+/* Knife-tool Operator. */
typedef struct KnifeVert {
Object *ob;
uint base_index;
@@ -333,6 +333,9 @@ enum {
KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE,
KNF_MODAL_DEPTH_TEST_TOGGLE,
KNF_MODAL_PANNING,
+ KNF_MODAL_X_AXIS,
+ KNF_MODAL_Y_AXIS,
+ KNF_MODAL_Z_AXIS,
KNF_MODAL_ADD_CUT_CLOSED,
};
@@ -366,7 +369,6 @@ enum {
/** \name Drawing
* \{ */
-#if 1
static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3], float r_v2[3])
{
float planes[4][4];
@@ -380,10 +382,6 @@ static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3],
float lambda_best[2] = {-FLT_MAX, FLT_MAX};
int i;
- /* We (sometimes) need the lines to be at the same depth before projecting. */
-# if 0
- sub_v3_v3v3(ray_dir, kcd->curr.cage, kcd->prev.cage);
-# else
{
float curr_cage_adjust[3];
float co_depth[3];
@@ -393,7 +391,6 @@ static void knifetool_raycast_planes(const KnifeTool_OpData *kcd, float r_v1[3],
sub_v3_v3v3(ray_dir, curr_cage_adjust, kcd->prev.cage);
}
-# endif
for (i = 0; i < 4; i++) {
float ray_hit[3];
@@ -445,7 +442,7 @@ static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd)
if (!compare_v3v3(kcd->prev.cage, kcd->curr.cage, KNIFE_FLT_EPSBIG)) {
float v1[3], v2[3];
- /* This is causing buggyness when prev.cage and curr.cage are too close together. */
+ /* This is causing buggy behavior when `prev.cage` and `curr.cage` are too close together. */
knifetool_raycast_planes(kcd, v1, v2);
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 3, GPU_FETCH_FLOAT);
@@ -480,7 +477,6 @@ static void knifetool_draw_orientation_locking(const KnifeTool_OpData *kcd)
immUnbindProgram();
}
}
-#endif
static void knifetool_draw_visible_distances(const KnifeTool_OpData *kcd)
{
@@ -1112,7 +1108,7 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k
"%s: start/define cut, %s: close cut, %s: new cut, "
"%s: midpoint snap (%s), %s: ignore snap (%s), "
"%s: angle constraint %.2f(%.2f) (%s%s%s%s), %s: cut through (%s), "
- "%s: panning, XYZ: orientation lock (%s), "
+ "%s: panning, %s%s%s: orientation lock (%s), "
"%s: distance/angle measurements (%s), "
"%s: x-ray (%s)"),
WM_MODALKEY(KNF_MODAL_CONFIRM),
@@ -1144,6 +1140,9 @@ static void knife_update_header(bContext *C, wmOperator *op, KnifeTool_OpData *k
WM_MODALKEY(KNF_MODAL_CUT_THROUGH_TOGGLE),
WM_bool_as_string(kcd->cut_through),
WM_MODALKEY(KNF_MODAL_PANNING),
+ WM_MODALKEY(KNF_MODAL_X_AXIS),
+ WM_MODALKEY(KNF_MODAL_Y_AXIS),
+ WM_MODALKEY(KNF_MODAL_Z_AXIS),
(kcd->axis_constrained ? kcd->axis_string : WM_bool_as_string(kcd->axis_constrained)),
WM_MODALKEY(KNF_MODAL_SHOW_DISTANCE_ANGLE_TOGGLE),
WM_bool_as_string(kcd->show_dist_angle),
@@ -3900,71 +3899,69 @@ static void knifetool_undo(KnifeTool_OpData *kcd)
KnifeUndoFrame *undo;
BLI_mempool_iter iterkfe;
- if (!BLI_stack_is_empty(kcd->undostack)) {
- undo = BLI_stack_peek(kcd->undostack);
+ undo = BLI_stack_peek(kcd->undostack);
- /* Undo edge splitting. */
- for (int i = 0; i < undo->splits; i++) {
- BLI_stack_pop(kcd->splitstack, &newkfe);
- BLI_stack_pop(kcd->splitstack, &kfe);
- knife_join_edge(newkfe, kfe);
- }
+ /* Undo edge splitting. */
+ for (int i = 0; i < undo->splits; i++) {
+ BLI_stack_pop(kcd->splitstack, &newkfe);
+ BLI_stack_pop(kcd->splitstack, &kfe);
+ knife_join_edge(newkfe, kfe);
+ }
- for (int i = 0; i < undo->cuts; i++) {
+ for (int i = 0; i < undo->cuts; i++) {
- BLI_mempool_iternew(kcd->kedges, &iterkfe);
- for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) {
- if (!kfe->is_cut || kfe->is_invalid || kfe->splits) {
- continue;
- }
- lastkfe = kfe;
+ BLI_mempool_iternew(kcd->kedges, &iterkfe);
+ for (kfe = BLI_mempool_iterstep(&iterkfe); kfe; kfe = BLI_mempool_iterstep(&iterkfe)) {
+ if (!kfe->is_cut || kfe->is_invalid || kfe->splits) {
+ continue;
}
+ lastkfe = kfe;
+ }
- if (lastkfe) {
- lastkfe->is_invalid = true;
-
- /* TODO: Are they always guaranteed to be in this order? */
- v1 = lastkfe->v1;
- v2 = lastkfe->v2;
-
- /* Only remove first vertex if it is the start segment of the cut. */
- if (!v1->is_invalid && !v1->is_splitting) {
- v1->is_invalid = true;
- /* If the first vertex is touching any other cut edges don't remove it. */
- for (ref = v1->edges.first; ref; ref = ref->next) {
- kfe = ref->ref;
- if (kfe->is_cut && !kfe->is_invalid) {
- v1->is_invalid = false;
- break;
- }
+ if (lastkfe) {
+ lastkfe->is_invalid = true;
+
+ /* TODO: Are they always guaranteed to be in this order? */
+ v1 = lastkfe->v1;
+ v2 = lastkfe->v2;
+
+ /* Only remove first vertex if it is the start segment of the cut. */
+ if (!v1->is_invalid && !v1->is_splitting) {
+ v1->is_invalid = true;
+ /* If the first vertex is touching any other cut edges don't remove it. */
+ for (ref = v1->edges.first; ref; ref = ref->next) {
+ kfe = ref->ref;
+ if (kfe->is_cut && !kfe->is_invalid) {
+ v1->is_invalid = false;
+ break;
}
}
+ }
- /* Only remove second vertex if it is the end segment of the cut. */
- if (!v2->is_invalid && !v2->is_splitting) {
- v2->is_invalid = true;
- /* If the second vertex is touching any other cut edges don't remove it. */
- for (ref = v2->edges.first; ref; ref = ref->next) {
- kfe = ref->ref;
- if (kfe->is_cut && !kfe->is_invalid) {
- v2->is_invalid = false;
- break;
- }
+ /* Only remove second vertex if it is the end segment of the cut. */
+ if (!v2->is_invalid && !v2->is_splitting) {
+ v2->is_invalid = true;
+ /* If the second vertex is touching any other cut edges don't remove it. */
+ for (ref = v2->edges.first; ref; ref = ref->next) {
+ kfe = ref->ref;
+ if (kfe->is_cut && !kfe->is_invalid) {
+ v2->is_invalid = false;
+ break;
}
}
}
}
+ }
- if (kcd->mode == MODE_DRAGGING) {
- /* Restore kcd->prev. */
- kcd->prev = undo->pos;
- }
+ if (kcd->mode == MODE_DRAGGING) {
+ /* Restore kcd->prev. */
+ kcd->prev = undo->pos;
+ }
- /* Restore data for distance and angle measurements. */
- kcd->mdata = undo->mdata;
+ /* Restore data for distance and angle measurements. */
+ kcd->mdata = undo->mdata;
- BLI_stack_discard(kcd->undostack);
- }
+ BLI_stack_discard(kcd->undostack);
}
/** \} */
@@ -4100,7 +4097,7 @@ static void knifetool_init(bContext *C,
kcd->axis_string[0] = ' ';
kcd->axis_string[1] = '\0';
- /* Initialise num input handling for angle snapping. */
+ /* Initialize number input handling for angle snapping. */
initNumInput(&kcd->num);
kcd->num.idx_max = 0;
kcd->num.val_flag[0] |= NUM_NO_NEGATIVE;
@@ -4151,7 +4148,7 @@ static void knifetool_exit_ex(KnifeTool_OpData *kcd)
MEM_freeN(kcd->cagecos);
knife_bvh_free(kcd);
- /* Linehits cleanup. */
+ /* Line-hits cleanup. */
if (kcd->linehits) {
MEM_freeN(kcd->linehits);
}
@@ -4307,6 +4304,9 @@ wmKeyMap *knifetool_modal_keymap(wmKeyConfig *keyconf)
{KNF_MODAL_ADD_CUT, "ADD_CUT", 0, "Add Cut", ""},
{KNF_MODAL_ADD_CUT_CLOSED, "ADD_CUT_CLOSED", 0, "Add Cut Closed", ""},
{KNF_MODAL_PANNING, "PANNING", 0, "Panning", ""},
+ {KNF_MODAL_X_AXIS, "X_AXIS", 0, "X Axis Locking", ""},
+ {KNF_MODAL_Y_AXIS, "Y_AXIS", 0, "Y Axis Locking", ""},
+ {KNF_MODAL_Z_AXIS, "Z_AXIS", 0, "Z Axis Locking", ""},
{0, NULL, 0, NULL, NULL},
};
@@ -4404,6 +4404,12 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_FINISHED;
case KNF_MODAL_UNDO:
+ if (BLI_stack_is_empty(kcd->undostack)) {
+ ED_region_tag_redraw(kcd->region);
+ knifetool_exit(op);
+ ED_workspace_status_text(C, NULL);
+ return OPERATOR_CANCELLED;
+ }
knifetool_undo(kcd);
knife_update_active(C, kcd);
ED_region_tag_redraw(kcd->region);
@@ -4614,52 +4620,58 @@ static int knifetool_modal(bContext *C, wmOperator *op, const wmEvent *event)
if (kcd->num.str_cur >= 2) {
knife_reset_snap_angle_input(kcd);
}
- /* Modal numinput inactive, try to handle numeric inputs last... */
- if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) {
- applyNumInput(&kcd->num, &snapping_increment_temp);
- /* Restrict number key input to 0 - 90 degree range. */
- if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT &&
- snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) {
- kcd->angle_snapping_increment = snapping_increment_temp;
+ if (event->type != EVT_MODAL_MAP) {
+ /* Modal number-input inactive, try to handle numeric inputs last. */
+ if (!handled && event->val == KM_PRESS && handleNumInput(C, &kcd->num, event)) {
+ applyNumInput(&kcd->num, &snapping_increment_temp);
+ /* Restrict number key input to 0 - 90 degree range. */
+ if (snapping_increment_temp > KNIFE_MIN_ANGLE_SNAPPING_INCREMENT &&
+ snapping_increment_temp < KNIFE_MAX_ANGLE_SNAPPING_INCREMENT) {
+ kcd->angle_snapping_increment = snapping_increment_temp;
+ }
+ knife_update_active(C, kcd);
+ knife_update_header(C, op, kcd);
+ ED_region_tag_redraw(kcd->region);
+ return OPERATOR_RUNNING_MODAL;
}
- knife_update_active(C, kcd);
- knife_update_header(C, op, kcd);
- ED_region_tag_redraw(kcd->region);
- return OPERATOR_RUNNING_MODAL;
}
}
/* Constrain axes with X,Y,Z keys. */
- if (event->val == KM_PRESS && ELEM(event->type, EVT_XKEY, EVT_YKEY, EVT_ZKEY)) {
- if (event->type == EVT_XKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) {
- kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X;
- kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL;
- kcd->axis_string[0] = 'X';
- }
- else if (event->type == EVT_YKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) {
- kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y;
- kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL;
- kcd->axis_string[0] = 'Y';
- }
- else if (event->type == EVT_ZKEY && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) {
- kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z;
- kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL;
- kcd->axis_string[0] = 'Z';
- }
- else {
- /* Cycle through modes with repeated key presses. */
- if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) {
- kcd->constrain_axis_mode++;
- kcd->axis_string[0] += 32; /* Lower case. */
+ if (event->type == EVT_MODAL_MAP) {
+ if (ELEM(event->val, KNF_MODAL_X_AXIS, KNF_MODAL_Y_AXIS, KNF_MODAL_Z_AXIS)) {
+ if (event->val == KNF_MODAL_X_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_X) {
+ kcd->constrain_axis = KNF_CONSTRAIN_AXIS_X;
+ kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL;
+ kcd->axis_string[0] = 'X';
+ }
+ else if (event->val == KNF_MODAL_Y_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Y) {
+ kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Y;
+ kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL;
+ kcd->axis_string[0] = 'Y';
+ }
+ else if (event->val == KNF_MODAL_Z_AXIS && kcd->constrain_axis != KNF_CONSTRAIN_AXIS_Z) {
+ kcd->constrain_axis = KNF_CONSTRAIN_AXIS_Z;
+ kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_GLOBAL;
+ kcd->axis_string[0] = 'Z';
}
else {
- kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE;
- kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE;
+ /* Cycle through modes with repeated key presses. */
+ if (kcd->constrain_axis_mode != KNF_CONSTRAIN_AXIS_MODE_LOCAL) {
+ kcd->constrain_axis_mode++;
+ kcd->axis_string[0] += 32; /* Lower case. */
+ }
+ else {
+ kcd->constrain_axis = KNF_CONSTRAIN_AXIS_NONE;
+ kcd->constrain_axis_mode = KNF_CONSTRAIN_AXIS_MODE_NONE;
+ }
}
+ kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE);
+ knifetool_disable_angle_snapping(kcd);
+ knife_update_header(C, op, kcd);
+ ED_region_tag_redraw(kcd->region);
+ do_refresh = true;
}
- kcd->axis_constrained = (kcd->constrain_axis != KNF_CONSTRAIN_AXIS_NONE);
- knifetool_disable_angle_snapping(kcd);
- knife_update_header(C, op, kcd);
}
if (kcd->mode == MODE_DRAGGING) {
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
index beadbf2689e..cc4f2acc346 100644
--- a/source/blender/editors/object/object_add.c
+++ b/source/blender/editors/object/object_add.c
@@ -75,6 +75,7 @@
#include "BKE_lattice.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
+#include "BKE_lib_override.h"
#include "BKE_lib_query.h"
#include "BKE_lib_remap.h"
#include "BKE_light.h"
@@ -2015,6 +2016,15 @@ static int object_delete_exec(bContext *C, wmOperator *op)
continue;
}
+ if (!BKE_lib_override_library_id_is_user_deletable(bmain, &ob->id)) {
+ /* Can this case ever happen? */
+ BKE_reportf(op->reports,
+ RPT_WARNING,
+ "Cannot delete object '%s' as it used by override collections",
+ ob->id.name + 2);
+ continue;
+ }
+
if (ID_REAL_USERS(ob) <= 1 && ID_EXTRA_USERS(ob) == 0 &&
BKE_library_ID_is_indirectly_used(bmain, ob)) {
BKE_reportf(op->reports,
@@ -2828,8 +2838,7 @@ static int object_convert_exec(bContext *C, wmOperator *op)
/* Remove unused materials. */
int actcol = ob_gpencil->actcol;
for (int slot = 1; slot <= ob_gpencil->totcol; slot++) {
- while (slot <= ob_gpencil->totcol &&
- !BKE_object_material_slot_used(ob_gpencil->data, slot)) {
+ while (slot <= ob_gpencil->totcol && !BKE_object_material_slot_used(ob_gpencil, slot)) {
ob_gpencil->actcol = slot;
BKE_object_material_slot_remove(CTX_data_main(C), ob_gpencil);
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 5697c2c973d..2bd0ae5f121 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -1739,7 +1739,7 @@ void OBJECT_OT_mode_set_with_submode(wmOperatorType *ot)
OBJECT_OT_mode_set(ot);
/* identifiers */
- ot->name = "Set Object Mode with Submode";
+ ot->name = "Set Object Mode with Sub-mode";
ot->idname = "OBJECT_OT_mode_set_with_submode";
/* properties */
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 50dd9322c5c..d00e6efeb29 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -78,7 +78,6 @@ void OBJECT_OT_mode_set(struct wmOperatorType *ot);
void OBJECT_OT_mode_set_with_submode(struct wmOperatorType *ot);
void OBJECT_OT_editmode_toggle(struct wmOperatorType *ot);
void OBJECT_OT_posemode_toggle(struct wmOperatorType *ot);
-void OBJECT_OT_proxy_make(struct wmOperatorType *ot);
void OBJECT_OT_shade_smooth(struct wmOperatorType *ot);
void OBJECT_OT_shade_flat(struct wmOperatorType *ot);
void OBJECT_OT_paths_calculate(struct wmOperatorType *ot);
diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c
index b9942bc563a..efe19785f31 100644
--- a/source/blender/editors/object/object_modifier.c
+++ b/source/blender/editors/object/object_modifier.c
@@ -1044,6 +1044,10 @@ bool edit_modifier_poll_generic(bContext *C,
Object *ob = (ptr.owner_id) ? (Object *)ptr.owner_id : ED_object_active_context(C);
ModifierData *mod = ptr.data; /* May be NULL. */
+ if (mod == NULL && ob != NULL) {
+ mod = BKE_object_active_modifier(ob);
+ }
+
if (!ob || ID_IS_LINKED(ob)) {
return false;
}
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index aa9ae082317..fa0208a7022 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -56,7 +56,6 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_mode_set_with_submode);
WM_operatortype_append(OBJECT_OT_editmode_toggle);
WM_operatortype_append(OBJECT_OT_posemode_toggle);
- WM_operatortype_append(OBJECT_OT_proxy_make);
WM_operatortype_append(OBJECT_OT_shade_smooth);
WM_operatortype_append(OBJECT_OT_shade_flat);
WM_operatortype_append(OBJECT_OT_paths_calculate);
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index 75269dffec8..5c7e1e1fa01 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -331,167 +331,6 @@ void OBJECT_OT_vertex_parent_set(wmOperatorType *ot)
/** \} */
/* ------------------------------------------------------------------- */
-/** \name Make Proxy Operator
- * \{ */
-
-/* set the object to proxify */
-static int make_proxy_invoke(bContext *C, wmOperator *op, const wmEvent *event)
-{
- Scene *scene = CTX_data_scene(C);
- Object *ob = ED_object_active_context(C);
-
- /* sanity checks */
- if (!scene || ID_IS_LINKED(scene) || !ob) {
- return OPERATOR_CANCELLED;
- }
-
- /* Get object to work on - use a menu if we need to... */
- if (ob->instance_collection && ID_IS_LINKED(ob->instance_collection)) {
- /* gives menu with list of objects in group */
- /* proxy_group_objects_menu(C, op, ob, ob->instance_collection); */
- WM_enum_search_invoke(C, op, event);
- return OPERATOR_CANCELLED;
- }
- if (ID_IS_LINKED(ob)) {
- uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("OK?"), ICON_QUESTION);
- uiLayout *layout = UI_popup_menu_layout(pup);
-
- /* create operator menu item with relevant properties filled in */
- PointerRNA opptr_dummy;
- uiItemFullO_ptr(
- layout, op->type, op->type->name, ICON_NONE, NULL, WM_OP_EXEC_REGION_WIN, 0, &opptr_dummy);
-
- /* present the menu and be done... */
- UI_popup_menu_end(C, pup);
-
- /* this invoke just calls another instance of this operator... */
- return OPERATOR_INTERFACE;
- }
-
- /* error.. cannot continue */
- BKE_report(op->reports, RPT_ERROR, "Can only make proxy for a referenced object or collection");
- return OPERATOR_CANCELLED;
-}
-
-static int make_proxy_exec(bContext *C, wmOperator *op)
-{
- Main *bmain = CTX_data_main(C);
- Object *ob, *gob = ED_object_active_context(C);
- Scene *scene = CTX_data_scene(C);
- ViewLayer *view_layer = CTX_data_view_layer(C);
-
- if (gob->instance_collection != NULL) {
- const ListBase instance_collection_objects = BKE_collection_object_cache_get(
- gob->instance_collection);
- Base *base = BLI_findlink(&instance_collection_objects, RNA_enum_get(op->ptr, "object"));
- ob = base->object;
- }
- else {
- ob = gob;
- gob = NULL;
- }
-
- if (ob) {
- Object *newob;
- char name[MAX_ID_NAME + 4];
-
- BLI_snprintf(name, sizeof(name), "%s_proxy", ((ID *)(gob ? gob : ob))->name + 2);
-
- /* Add new object for the proxy */
- newob = BKE_object_add_from(bmain, scene, view_layer, OB_EMPTY, name, gob ? gob : ob);
-
- /* set layers OK */
- BKE_object_make_proxy(bmain, newob, ob, gob);
-
- /* Set back pointer immediately so dependency graph knows that this is
- * is a proxy and will act accordingly. Otherwise correctness of graph
- * will depend on order of bases.
- *
- * TODO(sergey): We really need to get rid of this bi-directional links
- * in proxies with something like library overrides.
- */
- if (newob->proxy != NULL) {
- newob->proxy->proxy_from = newob;
- }
- else {
- BKE_report(op->reports, RPT_ERROR, "Unable to assign proxy");
- }
-
- /* depsgraph flushes are needed for the new data */
- DEG_relations_tag_update(bmain);
- DEG_id_tag_update(&newob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
- WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, newob);
- }
- else {
- BKE_report(op->reports, RPT_ERROR, "No object to make proxy for");
- return OPERATOR_CANCELLED;
- }
-
- return OPERATOR_FINISHED;
-}
-
-/* Generic itemf's for operators that take library args */
-static const EnumPropertyItem *proxy_collection_object_itemf(bContext *C,
- PointerRNA *UNUSED(ptr),
- PropertyRNA *UNUSED(prop),
- bool *r_free)
-{
- EnumPropertyItem item_tmp = {0}, *item = NULL;
- int totitem = 0;
- int i = 0;
- Object *ob = ED_object_active_context(C);
-
- if (!ob || !ob->instance_collection) {
- return DummyRNA_DEFAULT_items;
- }
-
- /* find the object to affect */
- FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (ob->instance_collection, object) {
- item_tmp.identifier = item_tmp.name = object->id.name + 2;
- item_tmp.value = i++;
- RNA_enum_item_add(&item, &totitem, &item_tmp);
- }
- FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
-
- RNA_enum_item_end(&item, &totitem);
- *r_free = true;
-
- return item;
-}
-
-void OBJECT_OT_proxy_make(wmOperatorType *ot)
-{
- PropertyRNA *prop;
-
- /* identifiers */
- ot->name = "Make Proxy";
- ot->idname = "OBJECT_OT_proxy_make";
- ot->description = "Add empty object to become local replacement data of a library-linked object";
-
- /* callbacks */
- ot->invoke = make_proxy_invoke;
- ot->exec = make_proxy_exec;
- ot->poll = ED_operator_object_active;
-
- /* flags */
- ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
-
- /* properties */
- /* XXX, relies on hard coded ID at the moment */
- prop = RNA_def_enum(ot->srna,
- "object",
- DummyRNA_DEFAULT_items,
- 0,
- "Proxy Object",
- "Name of library-linked/collection object to make a proxy for");
- RNA_def_enum_funcs(prop, proxy_collection_object_itemf);
- RNA_def_property_flag(prop, PROP_ENUM_NO_TRANSLATE);
- ot->prop = prop;
-}
-
-/** \} */
-
-/* ------------------------------------------------------------------- */
/** \name Clear Parent Operator
* \{ */
diff --git a/source/blender/editors/render/render_shading.c b/source/blender/editors/render/render_shading.c
index 8a3d8f9636b..7b2667905ff 100644
--- a/source/blender/editors/render/render_shading.c
+++ b/source/blender/editors/render/render_shading.c
@@ -690,7 +690,7 @@ static int material_slot_remove_unused_exec(bContext *C, wmOperator *op)
Object *ob = objects[ob_index];
int actcol = ob->actcol;
for (int slot = 1; slot <= ob->totcol; slot++) {
- while (slot <= ob->totcol && !BKE_object_material_slot_used(ob->data, slot)) {
+ while (slot <= ob->totcol && !BKE_object_material_slot_used(ob, slot)) {
ob->actcol = slot;
BKE_object_material_slot_remove(bmain, ob);
diff --git a/source/blender/editors/screen/area.c b/source/blender/editors/screen/area.c
index c71e68df2fd..833c9accf95 100644
--- a/source/blender/editors/screen/area.c
+++ b/source/blender/editors/screen/area.c
@@ -1284,8 +1284,8 @@ bool ED_region_is_overlap(int spacetype, int regiontype)
RGN_TYPE_TOOLS,
RGN_TYPE_UI,
RGN_TYPE_TOOL_PROPS,
- RGN_TYPE_HEADER,
- RGN_TYPE_FOOTER)) {
+ RGN_TYPE_FOOTER,
+ RGN_TYPE_TOOL_HEADER)) {
return true;
}
}
@@ -1699,6 +1699,9 @@ static void ed_default_handlers(
wmKeyMap *keymap = WM_keymap_ensure(wm->defaultconf, "User Interface", 0, 0);
WM_event_add_keymap_handler(handlers, keymap);
+ ListBase *dropboxes = WM_dropboxmap_find("User Interface", 0, 0);
+ WM_event_add_dropbox_handler(handlers, dropboxes);
+
/* user interface widgets */
UI_region_handlers_add(handlers);
}
@@ -3000,7 +3003,7 @@ void ED_region_panels_layout_ex(const bContext *C,
/* before setting the view */
if (region_layout_based) {
- /* XXX, only single panel support atm.
+ /* XXX, only single panel support at the moment.
* Can't use x/y values calculated above because they're not using the real height of panels,
* instead they calculate offsets for the next panel to start drawing. */
Panel *panel = region->panels.last;
diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c
index 2ccefb993c7..3d447d90626 100644
--- a/source/blender/editors/screen/screen_context.c
+++ b/source/blender/editors/screen/screen_context.c
@@ -1073,9 +1073,14 @@ static eContextResult screen_ctx_ui_list(const bContext *C, bContextDataResult *
{
wmWindow *win = CTX_wm_window(C);
ARegion *region = CTX_wm_region(C);
- uiList *list = UI_list_find_mouse_over(region, win->eventstate);
- CTX_data_pointer_set(result, NULL, &RNA_UIList, list);
- return CTX_RESULT_OK;
+ if (region) {
+ uiList *list = UI_list_find_mouse_over(region, win->eventstate);
+ if (list) {
+ CTX_data_pointer_set(result, NULL, &RNA_UIList, list);
+ return CTX_RESULT_OK;
+ }
+ }
+ return CTX_RESULT_NO_DATA;
}
/* Registry of context callback functions. */
diff --git a/source/blender/editors/screen/screen_intern.h b/source/blender/editors/screen/screen_intern.h
index 4016ef84bfd..04ee62b1631 100644
--- a/source/blender/editors/screen/screen_intern.h
+++ b/source/blender/editors/screen/screen_intern.h
@@ -62,7 +62,7 @@ typedef enum eScreenAxis {
#define AREAJOINTOLERANCEY (HEADERY * U.dpi_fac)
/* Expanded interaction influence of area borders. */
-#define BORDERPADDING (U.dpi_fac + U.pixelsize)
+#define BORDERPADDING ((2.0f * U.dpi_fac) + U.pixelsize)
/* area.c */
void ED_area_data_copy(ScrArea *area_dst, ScrArea *area_src, const bool do_free);
diff --git a/source/blender/editors/screen/workspace_edit.c b/source/blender/editors/screen/workspace_edit.c
index b99cb831bee..4b81e713080 100644
--- a/source/blender/editors/screen/workspace_edit.c
+++ b/source/blender/editors/screen/workspace_edit.c
@@ -310,7 +310,14 @@ static int workspace_append_activate_exec(bContext *C, wmOperator *op)
RNA_string_get(op->ptr, "filepath", filepath);
WorkSpace *appended_workspace = (WorkSpace *)WM_file_append_datablock(
- bmain, CTX_data_scene(C), CTX_data_view_layer(C), CTX_wm_view3d(C), filepath, ID_WS, idname);
+ bmain,
+ CTX_data_scene(C),
+ CTX_data_view_layer(C),
+ CTX_wm_view3d(C),
+ filepath,
+ ID_WS,
+ idname,
+ BLO_LIBLINK_APPEND_RECURSIVE);
if (appended_workspace) {
/* Set defaults. */
diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.c b/source/blender/editors/sculpt_paint/paint_image_proj.c
index a58b1947b0c..0176b4a1b13 100644
--- a/source/blender/editors/sculpt_paint/paint_image_proj.c
+++ b/source/blender/editors/sculpt_paint/paint_image_proj.c
@@ -1659,7 +1659,9 @@ static float project_paint_uvpixel_mask(const ProjPaintState *ps,
if (other_tpage && (ibuf_other = BKE_image_acquire_ibuf(other_tpage, NULL, NULL))) {
const MLoopTri *lt_other = &ps->mlooptri_eval[tri_index];
- const float *lt_other_tri_uv[3] = {PS_LOOPTRI_AS_UV_3(ps->poly_to_loop_uv, lt_other)};
+ const float *lt_other_tri_uv[3] = {ps->mloopuv_stencil_eval[lt_other->tri[0]].uv,
+ ps->mloopuv_stencil_eval[lt_other->tri[1]].uv,
+ ps->mloopuv_stencil_eval[lt_other->tri[2]].uv};
/* #BKE_image_acquire_ibuf - TODO: this may be slow. */
uchar rgba_ub[4];
diff --git a/source/blender/editors/sculpt_paint/sculpt_smooth.c b/source/blender/editors/sculpt_paint/sculpt_smooth.c
index 38165b7622f..1bfe8e1cbf1 100644
--- a/source/blender/editors/sculpt_paint/sculpt_smooth.c
+++ b/source/blender/editors/sculpt_paint/sculpt_smooth.c
@@ -395,7 +395,12 @@ void SCULPT_smooth(Sculpt *sd,
void SCULPT_do_smooth_brush(Sculpt *sd, Object *ob, PBVHNode **nodes, int totnode)
{
SculptSession *ss = ob->sculpt;
- if (ss->cache->bstrength <= 0.0f) {
+
+ /* NOTE: The enhance brush needs to initialize its state on the first brush step. The stroke
+ * strength can become 0 during the stroke, but it can not change sign (the sign is determined
+ * in the beginning of the stroke. So here it is important to not switch to enhance brush in the
+ * middle of the stroke. */
+ if (ss->cache->bstrength < 0.0f) {
/* Invert mode, intensify details. */
SCULPT_enhance_details_brush(sd, ob, nodes, totnode);
}
diff --git a/source/blender/editors/space_action/action_data.c b/source/blender/editors/space_action/action_data.c
index 717d87c4972..a4fd2d81778 100644
--- a/source/blender/editors/space_action/action_data.c
+++ b/source/blender/editors/space_action/action_data.c
@@ -71,7 +71,7 @@
/* ACTION CREATION */
/* Helper function to find the active AnimData block from the Action Editor context */
-AnimData *ED_actedit_animdata_from_context(bContext *C)
+AnimData *ED_actedit_animdata_from_context(bContext *C, ID **r_adt_id_owner)
{
SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C);
Object *ob = CTX_data_active_object(C);
@@ -82,12 +82,18 @@ AnimData *ED_actedit_animdata_from_context(bContext *C)
/* Currently, "Action Editor" means object-level only... */
if (ob) {
adt = ob->adt;
+ if (r_adt_id_owner) {
+ *r_adt_id_owner = &ob->id;
+ }
}
}
else if (saction->mode == SACTCONT_SHAPEKEY) {
Key *key = BKE_key_from_object(ob);
if (key) {
adt = key->adt;
+ if (r_adt_id_owner) {
+ *r_adt_id_owner = &key->id;
+ }
}
}
@@ -212,6 +218,7 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op))
bAction *oldact = NULL;
AnimData *adt = NULL;
+ ID *adt_id_owner = NULL;
/* hook into UI */
UI_context_active_but_prop_get_templateID(C, &ptr, &prop);
@@ -225,13 +232,14 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op))
/* stash the old action to prevent it from being lost */
if (ptr.type == &RNA_AnimData) {
adt = ptr.data;
+ adt_id_owner = ptr.owner_id;
}
else if (ptr.type == &RNA_SpaceDopeSheetEditor) {
- adt = ED_actedit_animdata_from_context(C);
+ adt = ED_actedit_animdata_from_context(C, &adt_id_owner);
}
}
else {
- adt = ED_actedit_animdata_from_context(C);
+ adt = ED_actedit_animdata_from_context(C, &adt_id_owner);
oldact = adt->action;
}
{
@@ -239,8 +247,9 @@ static int action_new_exec(bContext *C, wmOperator *UNUSED(op))
/* Perform stashing operation - But only if there is an action */
if (adt && oldact) {
+ BLI_assert(adt_id_owner != NULL);
/* stash the action */
- if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ptr.owner_id))) {
+ if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) {
/* The stash operation will remove the user already
* (and unlink the action from the AnimData action slot).
* Hence, we must unset the ref to the action in the
@@ -306,7 +315,7 @@ static bool action_pushdown_poll(bContext *C)
{
if (ED_operator_action_active(C)) {
SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C);
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
/* Check for AnimData, Actions, and that tweak-mode is off. */
if (adt && saction->action) {
@@ -326,7 +335,8 @@ static bool action_pushdown_poll(bContext *C)
static int action_pushdown_exec(bContext *C, wmOperator *op)
{
SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C);
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ ID *adt_id_owner = NULL;
+ AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner);
/* Do the deed... */
if (adt) {
@@ -339,8 +349,7 @@ static int action_pushdown_exec(bContext *C, wmOperator *op)
}
/* action can be safely added */
- const Object *ob = CTX_data_active_object(C);
- BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(ob));
+ BKE_nla_action_pushdown(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner));
/* Stop displaying this action in this editor
* NOTE: The editor itself doesn't set a user...
@@ -373,7 +382,8 @@ void ACTION_OT_push_down(wmOperatorType *ot)
static int action_stash_exec(bContext *C, wmOperator *op)
{
SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C);
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ ID *adt_id_owner = NULL;
+ AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner);
/* Perform stashing operation */
if (adt) {
@@ -385,8 +395,7 @@ static int action_stash_exec(bContext *C, wmOperator *op)
}
/* stash the action */
- Object *ob = CTX_data_active_object(C);
- if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ob))) {
+ if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) {
/* The stash operation will remove the user already,
* so the flushing step later shouldn't double up
* the user-count fixes. Hence, we must unset this ref
@@ -439,7 +448,7 @@ void ACTION_OT_stash(wmOperatorType *ot)
static bool action_stash_create_poll(bContext *C)
{
if (ED_operator_action_active(C)) {
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
/* Check tweak-mode is off (as you don't want to be tampering with the action in that case) */
/* NOTE: unlike for pushdown,
@@ -471,7 +480,8 @@ static bool action_stash_create_poll(bContext *C)
static int action_stash_create_exec(bContext *C, wmOperator *op)
{
SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C);
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ ID *adt_id_owner = NULL;
+ AnimData *adt = ED_actedit_animdata_from_context(C, &adt_id_owner);
/* Check for no action... */
if (saction->action == NULL) {
@@ -488,8 +498,7 @@ static int action_stash_create_exec(bContext *C, wmOperator *op)
}
/* stash the action */
- Object *ob = CTX_data_active_object(C);
- if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(ob))) {
+ if (BKE_nla_action_stash(adt, ID_IS_OVERRIDE_LIBRARY(adt_id_owner))) {
bAction *new_action = NULL;
/* Create new action not based on the old one
@@ -636,7 +645,7 @@ static bool action_unlink_poll(bContext *C)
{
if (ED_operator_action_active(C)) {
SpaceAction *saction = (SpaceAction *)CTX_wm_space_data(C);
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
/* Only when there's an active action, in the right modes... */
if (saction->action && adt) {
@@ -650,7 +659,7 @@ static bool action_unlink_poll(bContext *C)
static int action_unlink_exec(bContext *C, wmOperator *op)
{
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
bool force_delete = RNA_boolean_get(op->ptr, "force_delete");
if (adt && adt->action) {
@@ -775,7 +784,7 @@ static bool action_layer_next_poll(bContext *C)
{
/* Action Editor's action editing modes only */
if (ED_operator_action_active(C)) {
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
if (adt) {
/* only allow if we're in tweak-mode, and there's something above us... */
if (adt->flag & ADT_NLA_EDIT_ON) {
@@ -809,7 +818,7 @@ static bool action_layer_next_poll(bContext *C)
static int action_layer_next_exec(bContext *C, wmOperator *op)
{
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
NlaTrack *act_track;
Scene *scene = CTX_data_scene(C);
@@ -886,7 +895,7 @@ static bool action_layer_prev_poll(bContext *C)
{
/* Action Editor's action editing modes only */
if (ED_operator_action_active(C)) {
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
if (adt) {
if (adt->flag & ADT_NLA_EDIT_ON) {
/* Tweak Mode: We need to check if there are any tracks below the active one
@@ -920,7 +929,7 @@ static bool action_layer_prev_poll(bContext *C)
static int action_layer_prev_exec(bContext *C, wmOperator *op)
{
- AnimData *adt = ED_actedit_animdata_from_context(C);
+ AnimData *adt = ED_actedit_animdata_from_context(C, NULL);
NlaTrack *act_track;
NlaTrack *nlt;
diff --git a/source/blender/editors/space_action/space_action.c b/source/blender/editors/space_action/space_action.c
index f59429ba981..738eeb21e2e 100644
--- a/source/blender/editors/space_action/space_action.c
+++ b/source/blender/editors/space_action/space_action.c
@@ -247,7 +247,7 @@ static void action_main_region_draw_overlay(const bContext *C, ARegion *region)
View2D *v2d = &region->v2d;
/* scrubbing region */
- ED_time_scrub_draw_current_frame(region, scene, saction->flag & SACTION_DRAWTIME, true);
+ ED_time_scrub_draw_current_frame(region, scene, saction->flag & SACTION_DRAWTIME);
/* scrollers */
UI_view2d_scrollers_draw(v2d, NULL);
diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c
index 404c5698b42..149067a94fe 100644
--- a/source/blender/editors/space_api/spacetypes.c
+++ b/source/blender/editors/space_api/spacetypes.c
@@ -177,6 +177,7 @@ void ED_spacemacros_init(void)
ED_operatormacros_gpencil();
/* Register dropboxes (can use macros). */
+ ED_dropboxes_ui();
const ListBase *spacetypes = BKE_spacetypes_list();
LISTBASE_FOREACH (const SpaceType *, type, spacetypes) {
if (type->dropboxes) {
diff --git a/source/blender/editors/space_clip/clip_dopesheet_draw.c b/source/blender/editors/space_clip/clip_dopesheet_draw.c
index 8aaf3faffec..42fda3ef464 100644
--- a/source/blender/editors/space_clip/clip_dopesheet_draw.c
+++ b/source/blender/editors/space_clip/clip_dopesheet_draw.c
@@ -224,7 +224,7 @@ void clip_draw_dopesheet_main(SpaceClip *sc, ARegion *region, Scene *scene)
uint flags_id = GPU_vertformat_attr_add(format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 1.0f);
immUniform2f(
"ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1);
diff --git a/source/blender/editors/space_clip/tracking_ops.c b/source/blender/editors/space_clip/tracking_ops.c
index ff62bcf0cfa..4965099642b 100644
--- a/source/blender/editors/space_clip/tracking_ops.c
+++ b/source/blender/editors/space_clip/tracking_ops.c
@@ -417,7 +417,7 @@ static SlideMarkerData *create_slide_marker_data(SpaceClip *sc,
data->pos = marker->pos;
data->offset = track->offset;
data->old_markers = MEM_callocN(sizeof(*data->old_markers) * track->markersnr,
- "slide marekrs");
+ "slide markers");
for (int a = 0; a < track->markersnr; a++) {
copy_v2_v2(data->old_markers[a], track->markers[a].pos);
}
diff --git a/source/blender/editors/space_clip/tracking_ops_track.c b/source/blender/editors/space_clip/tracking_ops_track.c
index 0a99d1020f6..0e78a3e9a1e 100644
--- a/source/blender/editors/space_clip/tracking_ops_track.c
+++ b/source/blender/editors/space_clip/tracking_ops_track.c
@@ -196,8 +196,8 @@ static bool track_markers_initjob(bContext *C, TrackMarkersJob *tmj, bool backwa
/* XXX: silly to store this, but this data is needed to update scene and
* movie-clip numbers when tracking is finished. This introduces
* better feedback for artists.
- * Maybe there's another way to solve this problem, but can't think
- * better way atm.
+ * Maybe there's another way to solve this problem,
+ * but can't think better way at the moment.
* Anyway, this way isn't more unstable as animation rendering
* animation which uses the same approach (except storing screen).
*/
diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt
index 993a52b9084..4b508f16c1e 100644
--- a/source/blender/editors/space_file/CMakeLists.txt
+++ b/source/blender/editors/space_file/CMakeLists.txt
@@ -34,6 +34,7 @@ set(INC
)
set(SRC
+ asset_catalog_tree_view.cc
file_draw.c
file_ops.c
file_panels.c
@@ -49,6 +50,7 @@ set(SRC
)
set(LIB
+ bf_blenkernel
)
if(WITH_HEADLESS)
diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc
new file mode 100644
index 00000000000..7eea9af925b
--- /dev/null
+++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc
@@ -0,0 +1,230 @@
+/*
+ * 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) 2007 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup spfile
+ */
+
+#include "ED_fileselect.h"
+
+#include "DNA_space_types.h"
+
+#include "BKE_asset_catalog.hh"
+#include "BKE_asset_library.hh"
+
+#include "BLI_string_ref.hh"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+
+#include "UI_interface.h"
+#include "UI_interface.hh"
+#include "UI_resources.h"
+#include "UI_tree_view.hh"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "file_intern.h"
+
+using namespace blender;
+using namespace blender::bke;
+
+namespace blender::ed::asset_browser {
+
+class AssetCatalogTreeView : public ui::AbstractTreeView {
+ /** The asset catalog tree this tree-view represents. */
+ bke::AssetCatalogTree *catalog_tree_;
+ FileAssetSelectParams *params_;
+
+ friend class AssetCatalogTreeViewItem;
+
+ public:
+ AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params);
+
+ void build_tree() override;
+
+ private:
+ ui::BasicTreeViewItem &build_catalog_items_recursive(ui::TreeViewItemContainer &view_parent_item,
+ AssetCatalogTreeItem &catalog);
+
+ void add_all_item();
+ void add_unassigned_item();
+ bool is_active_catalog(CatalogID catalog_id) const;
+};
+/* ---------------------------------------------------------------------- */
+
+class AssetCatalogTreeViewItem : public ui::BasicTreeViewItem {
+ /** The catalog tree item this tree view item represents. */
+ AssetCatalogTreeItem &catalog_item_;
+
+ public:
+ AssetCatalogTreeViewItem(AssetCatalogTreeItem *catalog_item)
+ : BasicTreeViewItem(catalog_item->get_name()), catalog_item_(*catalog_item)
+ {
+ }
+
+ void on_activate() override
+ {
+ const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>(
+ get_tree_view());
+ tree_view.params_->asset_catalog_visibility = FILE_SHOW_ASSETS_FROM_CATALOG;
+ tree_view.params_->catalog_id = catalog_item_.get_catalog_id();
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
+ }
+
+ void build_row(uiLayout &row) override
+ {
+ ui::BasicTreeViewItem::build_row(row);
+
+ if (!is_active()) {
+ return;
+ }
+
+ PointerRNA *props;
+ const CatalogID catalog_id = catalog_item_.get_catalog_id();
+
+ props = UI_but_extra_operator_icon_add(
+ button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD);
+ RNA_string_set(props, "parent_path", catalog_item_.catalog_path().c_str());
+
+ /* Tree items without a catalog ID represent components of catalog paths that are not
+ * associated with an actual catalog. They exist merely by the presence of a child catalog, and
+ * thus cannot be deleted themselves. */
+ if (!BLI_uuid_is_nil(catalog_id)) {
+ char catalog_id_str_buffer[UUID_STRING_LEN] = "";
+ BLI_uuid_format(catalog_id_str_buffer, catalog_id);
+
+ props = UI_but_extra_operator_icon_add(
+ button(), "ASSET_OT_catalog_delete", WM_OP_INVOKE_DEFAULT, ICON_X);
+ RNA_string_set(props, "catalog_id", catalog_id_str_buffer);
+ }
+ }
+};
+
+/** Only reason this isn't just `BasicTreeViewItem` is to add a '+' icon for adding a root level
+ * catalog. */
+class AssetCatalogTreeViewAllItem : public ui::BasicTreeViewItem {
+ using BasicTreeViewItem::BasicTreeViewItem;
+
+ void build_row(uiLayout &row) override
+ {
+ ui::BasicTreeViewItem::build_row(row);
+
+ if (!is_active()) {
+ return;
+ }
+
+ PointerRNA *props;
+ props = UI_but_extra_operator_icon_add(
+ button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD);
+ /* No parent path to use the root level. */
+ RNA_string_set(props, "parent_path", nullptr);
+ }
+};
+
+AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params)
+ : catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params)
+{
+}
+
+void AssetCatalogTreeView::build_tree()
+{
+ add_all_item();
+
+ if (catalog_tree_) {
+ catalog_tree_->foreach_root_item([this](AssetCatalogTreeItem &item) {
+ ui::BasicTreeViewItem &child_view_item = build_catalog_items_recursive(*this, item);
+
+ /* Open root-level items by default. */
+ child_view_item.set_collapsed(false);
+ });
+ }
+
+ add_unassigned_item();
+}
+
+ui::BasicTreeViewItem &AssetCatalogTreeView::build_catalog_items_recursive(
+ ui::TreeViewItemContainer &view_parent_item, AssetCatalogTreeItem &catalog)
+{
+ ui::BasicTreeViewItem &view_item = view_parent_item.add_tree_item<AssetCatalogTreeViewItem>(
+ &catalog);
+ if (is_active_catalog(catalog.get_catalog_id())) {
+ view_item.set_active();
+ }
+
+ catalog.foreach_child([&view_item, this](AssetCatalogTreeItem &child) {
+ build_catalog_items_recursive(view_item, child);
+ });
+ return view_item;
+}
+
+void AssetCatalogTreeView::add_all_item()
+{
+ FileAssetSelectParams *params = params_;
+
+ ui::AbstractTreeViewItem &item = add_tree_item<AssetCatalogTreeViewAllItem>(
+ IFACE_("All"), ICON_HOME, [params](ui::BasicTreeViewItem & /*item*/) {
+ params->asset_catalog_visibility = FILE_SHOW_ASSETS_ALL_CATALOGS;
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
+ });
+ if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_ALL_CATALOGS) {
+ item.set_active();
+ }
+}
+
+void AssetCatalogTreeView::add_unassigned_item()
+{
+ FileAssetSelectParams *params = params_;
+
+ ui::AbstractTreeViewItem &item = add_tree_item<ui::BasicTreeViewItem>(
+ IFACE_("Unassigned"), ICON_FILE_HIDDEN, [params](ui::BasicTreeViewItem & /*item*/) {
+ params->asset_catalog_visibility = FILE_SHOW_ASSETS_WITHOUT_CATALOG;
+ WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
+ });
+ if (params->asset_catalog_visibility == FILE_SHOW_ASSETS_WITHOUT_CATALOG) {
+ item.set_active();
+ }
+}
+
+bool AssetCatalogTreeView::is_active_catalog(CatalogID catalog_id) const
+{
+ return (params_->asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG) &&
+ (params_->catalog_id == catalog_id);
+}
+
+} // namespace blender::ed::asset_browser
+
+/* ---------------------------------------------------------------------- */
+
+void file_create_asset_catalog_tree_view_in_layout(::AssetLibrary *asset_library,
+ uiLayout *layout,
+ FileAssetSelectParams *params)
+{
+ uiBlock *block = uiLayoutGetBlock(layout);
+
+ ui::AbstractTreeView *tree_view = UI_block_add_view(
+ *block,
+ "asset catalog tree view",
+ std::make_unique<ed::asset_browser::AssetCatalogTreeView>(asset_library, params));
+
+ ui::TreeViewBuilder builder(*block);
+ builder.build_tree_view(*tree_view);
+}
diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h
index 905c0aeb8e0..d39aefff691 100644
--- a/source/blender/editors/space_file/file_intern.h
+++ b/source/blender/editors/space_file/file_intern.h
@@ -23,12 +23,19 @@
#pragma once
+#ifdef __cplusplus
+extern "C" {
+#endif
+
/* internal exports only */
struct ARegion;
struct ARegionType;
+struct AssetLibrary;
struct FileSelectParams;
+struct FileAssetSelectParams;
struct SpaceFile;
+struct uiLayout;
struct View2D;
/* file_draw.c */
@@ -147,8 +154,19 @@ void file_on_reload_callback_register(struct SpaceFile *sfile,
/* file_panels.c */
void file_tool_props_region_panels_register(struct ARegionType *art);
void file_execute_region_panels_register(struct ARegionType *art);
+void file_tools_region_panels_register(struct ARegionType *art);
/* file_utils.c */
void file_tile_boundbox(const ARegion *region, FileLayout *layout, const int file, rcti *r_bounds);
void file_path_to_ui_path(const char *path, char *r_pathi, int max_size);
+
+/* asset_catalog_tree_view.cc */
+
+void file_create_asset_catalog_tree_view_in_layout(struct AssetLibrary *asset_library,
+ struct uiLayout *layout,
+ struct FileAssetSelectParams *params);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c
index 2f1acd2ca4d..d0f2a4fdc4c 100644
--- a/source/blender/editors/space_file/file_ops.c
+++ b/source/blender/editors/space_file/file_ops.c
@@ -536,7 +536,7 @@ static rcti file_select_mval_to_select_rect(const int mval[2])
return rect;
}
-static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+static int file_select_exec(bContext *C, wmOperator *op)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
@@ -549,17 +549,27 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
const bool only_activate_if_selected = RNA_boolean_get(op->ptr, "only_activate_if_selected");
/* Used so right mouse clicks can do both, activate and spawn the context menu. */
const bool pass_through = RNA_boolean_get(op->ptr, "pass_through");
+ bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
if (region->regiontype != RGN_TYPE_WINDOW) {
return OPERATOR_CANCELLED;
}
- rect = file_select_mval_to_select_rect(event->mval);
+ int mval[2];
+ mval[0] = RNA_int_get(op->ptr, "mouse_x");
+ mval[1] = RNA_int_get(op->ptr, "mouse_y");
+ rect = file_select_mval_to_select_rect(mval);
if (!ED_fileselect_layout_is_inside_pt(sfile->layout, &region->v2d, rect.xmin, rect.ymin)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
+ if (extend || fill) {
+ wait_to_deselect_others = false;
+ }
+
+ int ret_val = OPERATOR_FINISHED;
+
const FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (sfile && params) {
int idx = params->highlight_file;
@@ -571,6 +581,9 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
if (only_activate_if_selected && is_selected) {
/* Don't deselect other items. */
}
+ else if (wait_to_deselect_others && is_selected) {
+ ret_val = OPERATOR_RUNNING_MODAL;
+ }
/* single select, deselect all selected first */
else if (!extend) {
file_select_deselect_all(sfile, FILE_SEL_SELECTED);
@@ -601,7 +614,10 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
WM_event_add_mousemove(CTX_wm_window(C)); /* for directory changes */
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
- return pass_through ? (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH) : OPERATOR_FINISHED;
+ if ((ret_val == OPERATOR_FINISHED) && pass_through) {
+ ret_val |= OPERATOR_PASS_THROUGH;
+ }
+ return ret_val;
}
void FILE_OT_select(wmOperatorType *ot)
@@ -614,11 +630,14 @@ void FILE_OT_select(wmOperatorType *ot)
ot->description = "Handle mouse clicks to select and activate items";
/* api callbacks */
- ot->invoke = file_select_invoke;
+ ot->invoke = WM_generic_select_invoke;
+ ot->exec = file_select_exec;
+ ot->modal = WM_generic_select_modal;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
/* properties */
+ WM_operator_properties_generic_select(ot);
prop = RNA_def_boolean(ot->srna,
"extend",
false,
diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c
index 7032d55b331..95aad202f1a 100644
--- a/source/blender/editors/space_file/file_panels.c
+++ b/source/blender/editors/space_file/file_panels.c
@@ -47,6 +47,7 @@
#include "WM_types.h"
#include "file_intern.h"
+#include "filelist.h"
#include "fsmenu.h"
#include <string.h>
@@ -57,6 +58,12 @@ static bool file_panel_operator_poll(const bContext *C, PanelType *UNUSED(pt))
return (sfile && sfile->op);
}
+static bool file_panel_asset_browsing_poll(const bContext *C, PanelType *UNUSED(pt))
+{
+ SpaceFile *sfile = CTX_wm_space_file(C);
+ return sfile && sfile->files && ED_fileselect_is_asset_browser(sfile);
+}
+
static void file_panel_operator_header(const bContext *C, Panel *panel)
{
SpaceFile *sfile = CTX_wm_space_file(C);
@@ -222,3 +229,28 @@ void file_execute_region_panels_register(ARegionType *art)
pt->draw = file_panel_execution_buttons_draw;
BLI_addtail(&art->paneltypes, pt);
}
+
+static void file_panel_asset_catalog_buttons_draw(const bContext *C, Panel *panel)
+{
+ SpaceFile *sfile = CTX_wm_space_file(C);
+ /* May be null if the library wasn't loaded yet. */
+ struct AssetLibrary *asset_library = filelist_asset_library(sfile->files);
+ FileAssetSelectParams *params = ED_fileselect_get_asset_params(sfile);
+ BLI_assert(params != NULL);
+
+ file_create_asset_catalog_tree_view_in_layout(asset_library, panel->layout, params);
+}
+
+void file_tools_region_panels_register(ARegionType *art)
+{
+ PanelType *pt;
+
+ pt = MEM_callocN(sizeof(PanelType), "spacetype file asset catalog buttons");
+ strcpy(pt->idname, "FILE_PT_asset_catalog_buttons");
+ strcpy(pt->label, N_("Asset Catalogs"));
+ strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA);
+ pt->flag = PANEL_TYPE_NO_HEADER;
+ pt->poll = file_panel_asset_browsing_poll;
+ pt->draw = file_panel_asset_catalog_buttons_draw;
+ BLI_addtail(&art->paneltypes, pt);
+}
diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c
index 511b5b255e9..bdf61e792d3 100644
--- a/source/blender/editors/space_file/filelist.c
+++ b/source/blender/editors/space_file/filelist.c
@@ -50,12 +50,14 @@
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
+#include "BLI_uuid.h"
#ifdef WIN32
# include "BLI_winstuff.h"
#endif
#include "BKE_asset.h"
+#include "BKE_asset_library.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_icons.h"
@@ -368,6 +370,9 @@ typedef struct FileListFilter {
char filter_glob[FILE_MAXFILE];
char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */
short flags;
+
+ eFileSel_Params_AssetCatalogVisibility asset_catalog_visibility;
+ bUUID asset_catalog_id;
} FileListFilter;
/* FileListFilter.flags */
@@ -386,6 +391,7 @@ typedef struct FileList {
eFileSelectType type;
/* The library this list was created for. Stored here so we know when to re-read. */
AssetLibraryReference *asset_library_ref;
+ struct AssetLibrary *asset_library;
short flags;
@@ -471,6 +477,10 @@ static void filelist_readjob_dir(struct FileListReadJob *job_params,
short *stop,
short *do_update,
float *progress);
+static void filelist_readjob_asset_library(struct FileListReadJob *job_params,
+ short *stop,
+ short *do_update,
+ float *progress);
static void filelist_readjob_main_assets(struct FileListReadJob *job_params,
short *stop,
short *do_update,
@@ -887,6 +897,39 @@ static bool is_filtered_id_file(const FileListInternEntry *file,
return is_filtered;
}
+/**
+ * Get the asset metadata of a file, if it represents an asset. This may either be of a local ID
+ * (ID in the current #Main) or read from an external asset library.
+ */
+static AssetMetaData *filelist_file_internal_get_asset_data(const FileListInternEntry *file)
+{
+ const ID *local_id = file->local_data.id;
+ return local_id ? local_id->asset_data : file->imported_asset_data;
+}
+
+static bool is_filtered_asset(FileListInternEntry *file, FileListFilter *filter)
+{
+ const AssetMetaData *asset_data = filelist_file_internal_get_asset_data(file);
+ bool is_visible = false;
+
+ switch (filter->asset_catalog_visibility) {
+ case FILE_SHOW_ASSETS_WITHOUT_CATALOG:
+ is_visible = BLI_uuid_is_nil(asset_data->catalog_id);
+ break;
+ case FILE_SHOW_ASSETS_FROM_CATALOG:
+ /* TODO show all assets that are in child catalogs of the selected catalog. */
+ is_visible = !BLI_uuid_is_nil(filter->asset_catalog_id) &&
+ BLI_uuid_equal(filter->asset_catalog_id, asset_data->catalog_id);
+ break;
+ case FILE_SHOW_ASSETS_ALL_CATALOGS:
+ /* All asset files should be visible. */
+ is_visible = true;
+ break;
+ }
+
+ return is_visible;
+}
+
static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter)
{
bool is_filtered;
@@ -904,6 +947,13 @@ static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileLis
return is_filtered;
}
+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);
+}
+
static bool is_filtered_main(FileListInternEntry *file,
const char *UNUSED(dir),
FileListFilter *filter)
@@ -916,7 +966,8 @@ 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(file, file->relpath, file->name, filter) &&
+ is_filtered_asset(file, filter);
}
static void filelist_filter_clear(FileList *filelist)
@@ -1032,6 +1083,33 @@ void filelist_setfilter_options(FileList *filelist,
}
/**
+ * \param catalog_id: The catalog that should be filtered by if \a catalog_visibility is
+ * #FILE_SHOW_ASSETS_FROM_CATALOG. May be NULL otherwise.
+ */
+void filelist_set_asset_catalog_filter_options(
+ FileList *filelist,
+ eFileSel_Params_AssetCatalogVisibility catalog_visibility,
+ const bUUID *catalog_id)
+{
+ bool update = false;
+
+ if (filelist->filter_data.asset_catalog_visibility != catalog_visibility) {
+ filelist->filter_data.asset_catalog_visibility = catalog_visibility;
+ update = true;
+ }
+
+ if (filelist->filter_data.asset_catalog_visibility == FILE_SHOW_ASSETS_FROM_CATALOG &&
+ catalog_id && !BLI_uuid_equal(filelist->filter_data.asset_catalog_id, *catalog_id)) {
+ filelist->filter_data.asset_catalog_id = *catalog_id;
+ update = true;
+ }
+
+ if (update) {
+ filelist_filter_clear(filelist);
+ }
+}
+
+/**
* Checks two libraries for equality.
* \return True if the libraries match.
*/
@@ -1723,6 +1801,11 @@ void filelist_settype(FileList *filelist, short type)
filelist->read_job_fn = filelist_readjob_lib;
filelist->filter_fn = is_filtered_lib;
break;
+ case FILE_ASSET_LIBRARY:
+ filelist->check_dir_fn = filelist_checkdir_lib;
+ filelist->read_job_fn = filelist_readjob_asset_library;
+ filelist->filter_fn = is_filtered_asset_library;
+ break;
case FILE_MAIN_ASSET:
filelist->check_dir_fn = filelist_checkdir_main_assets;
filelist->read_job_fn = filelist_readjob_main_assets;
@@ -1739,7 +1822,10 @@ void filelist_settype(FileList *filelist, short type)
filelist->flags |= FL_FORCE_RESET;
}
-void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection)
+void filelist_clear_ex(struct FileList *filelist,
+ const bool do_asset_library,
+ const bool do_cache,
+ const bool do_selection)
{
if (!filelist) {
return;
@@ -1758,11 +1844,18 @@ void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const boo
if (do_selection && filelist->selection_state) {
BLI_ghash_clear(filelist->selection_state, NULL, NULL);
}
+
+ if (do_asset_library && (filelist->asset_library != NULL)) {
+ /* There is no way to refresh the catalogs stored by the AssetLibrary struct, so instead of
+ * "clearing" it, the entire struct is freed. It will be reallocated when needed. */
+ BKE_asset_library_free(filelist->asset_library);
+ filelist->asset_library = NULL;
+ }
}
void filelist_clear(struct FileList *filelist)
{
- filelist_clear_ex(filelist, true, true);
+ filelist_clear_ex(filelist, true, true, true);
}
void filelist_free(struct FileList *filelist)
@@ -1773,7 +1866,7 @@ void filelist_free(struct FileList *filelist)
}
/* No need to clear cache & selection_state, we free them anyway. */
- filelist_clear_ex(filelist, false, false);
+ filelist_clear_ex(filelist, true, false, false);
filelist_cache_free(&filelist->filelist_cache);
if (filelist->selection_state) {
@@ -1788,6 +1881,11 @@ void filelist_free(struct FileList *filelist)
filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING);
}
+AssetLibrary *filelist_asset_library(FileList *filelist)
+{
+ return filelist->asset_library;
+}
+
void filelist_freelib(struct FileList *filelist)
{
if (filelist->libfiledata) {
@@ -2879,76 +2977,129 @@ static int filelist_readjob_list_dir(const char *root,
return nbr_entries;
}
-static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar)
+typedef enum ListLibOptions {
+ /* Will read both the groups + actual ids from the library. Reduces the amount of times that
+ * a library needs to be opened. */
+ LIST_LIB_RECURSIVE = (1 << 0),
+
+ /* Will only list assets. */
+ LIST_LIB_ASSETS_ONLY = (1 << 1),
+
+ /* Add given root as result. */
+ LIST_LIB_ADD_PARENT = (1 << 2),
+} ListLibOptions;
+
+static FileListInternEntry *filelist_readjob_list_lib_group_create(const int idcode,
+ const char *group_name)
+{
+ FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__);
+ entry->relpath = BLI_strdup(group_name);
+ entry->typeflag |= FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR;
+ entry->blentype = idcode;
+ return entry;
+}
+
+static void filelist_readjob_list_lib_add_datablocks(ListBase *entries,
+ LinkNode *datablock_infos,
+ const bool prefix_relpath_with_group_name,
+ const int idcode,
+ const char *group_name)
+{
+ for (LinkNode *ln = datablock_infos; ln; ln = ln->next) {
+ struct BLODataBlockInfo *info = ln->link;
+ FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__);
+ if (prefix_relpath_with_group_name) {
+ entry->relpath = BLI_sprintfN("%s/%s", group_name, info->name);
+ }
+ else {
+ entry->relpath = BLI_strdup(info->name);
+ }
+ entry->typeflag |= FILE_TYPE_BLENDERLIB;
+ if (info && info->asset_data) {
+ entry->typeflag |= FILE_TYPE_ASSET;
+ /* Moves ownership! */
+ entry->imported_asset_data = info->asset_data;
+ }
+ entry->blentype = idcode;
+ BLI_addtail(entries, entry);
+ }
+}
+
+static int filelist_readjob_list_lib(const char *root,
+ ListBase *entries,
+ const ListLibOptions options)
{
- FileListInternEntry *entry;
- LinkNode *ln, *names = NULL, *datablock_infos = NULL;
- int i, nitems, idcode = 0, nbr_entries = 0;
char dir[FILE_MAX_LIBEXTRA], *group;
- bool ok;
struct BlendHandle *libfiledata = NULL;
- /* name test */
- ok = BLO_library_path_explode(root, dir, &group, NULL);
- if (!ok) {
- return nbr_entries;
+ /* Check if the given root is actually a library. All folders are passed to
+ * `filelist_readjob_list_lib` and based on the number of found entries `filelist_readjob_do`
+ * will do a dir listing only when this function does not return any entries. */
+ /* TODO: We should consider introducing its own function to detect if it is a lib and
+ * call it directly from `filelist_readjob_do` to increase readability. */
+ const bool is_lib = BLO_library_path_explode(root, dir, &group, NULL);
+ if (!is_lib) {
+ return 0;
}
- /* there we go */
+ /* Open the library file. */
BlendFileReadReport bf_reports = {.reports = NULL};
libfiledata = BLO_blendhandle_from_file(dir, &bf_reports);
if (libfiledata == NULL) {
- return nbr_entries;
- }
-
- /* memory for strings is passed into filelist[i].entry->relpath
- * and freed in filelist_entry_free. */
- if (group) {
- idcode = groupname_to_code(group);
- datablock_infos = BLO_blendhandle_get_datablock_info(libfiledata, idcode, &nitems);
- }
- else {
- names = BLO_blendhandle_get_linkable_groups(libfiledata);
- nitems = BLI_linklist_count(names);
+ return 0;
}
- BLO_blendhandle_close(libfiledata);
-
- if (!skip_currpar) {
- entry = MEM_callocN(sizeof(*entry), __func__);
+ /* Add current parent when requested. */
+ int parent_len = 0;
+ if (options & LIST_LIB_ADD_PARENT) {
+ FileListInternEntry *entry = MEM_callocN(sizeof(*entry), __func__);
entry->relpath = BLI_strdup(FILENAME_PARENT);
entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR);
BLI_addtail(entries, entry);
- nbr_entries++;
+ parent_len = 1;
}
- for (i = 0, ln = (datablock_infos ? datablock_infos : names); i < nitems; i++, ln = ln->next) {
- struct BLODataBlockInfo *info = datablock_infos ? ln->link : NULL;
- const char *blockname = info ? info->name : ln->link;
-
- entry = MEM_callocN(sizeof(*entry), __func__);
- entry->relpath = BLI_strdup(blockname);
- entry->typeflag |= FILE_TYPE_BLENDERLIB;
- if (info && info->asset_data) {
- entry->typeflag |= FILE_TYPE_ASSET;
- /* Moves ownership! */
- entry->imported_asset_data = info->asset_data;
- }
- if (!(group && idcode)) {
- entry->typeflag |= FILE_TYPE_DIR;
- entry->blentype = groupname_to_code(blockname);
- }
- else {
- entry->blentype = idcode;
+ int group_len = 0;
+ int datablock_len = 0;
+ const bool group_came_from_path = group != NULL;
+ if (group_came_from_path) {
+ const int idcode = groupname_to_code(group);
+ LinkNode *datablock_infos = BLO_blendhandle_get_datablock_info(
+ libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &datablock_len);
+ filelist_readjob_list_lib_add_datablocks(entries, datablock_infos, false, idcode, group);
+ BLI_linklist_freeN(datablock_infos);
+ }
+ else {
+ LinkNode *groups = BLO_blendhandle_get_linkable_groups(libfiledata);
+ group_len = BLI_linklist_count(groups);
+
+ for (LinkNode *ln = groups; ln; ln = ln->next) {
+ const char *group_name = ln->link;
+ const int idcode = groupname_to_code(group_name);
+ FileListInternEntry *group_entry = filelist_readjob_list_lib_group_create(idcode,
+ group_name);
+ BLI_addtail(entries, group_entry);
+
+ if (options & LIST_LIB_RECURSIVE) {
+ int group_datablock_len;
+ LinkNode *group_datablock_infos = BLO_blendhandle_get_datablock_info(
+ libfiledata, idcode, options & LIST_LIB_ASSETS_ONLY, &group_datablock_len);
+ filelist_readjob_list_lib_add_datablocks(
+ entries, group_datablock_infos, true, idcode, group_name);
+ BLI_linklist_freeN(group_datablock_infos);
+ datablock_len += group_datablock_len;
+ }
}
- BLI_addtail(entries, entry);
- nbr_entries++;
+
+ BLI_linklist_freeN(groups);
}
- BLI_linklist_freeN(datablock_infos ? datablock_infos : names);
+ BLO_blendhandle_close(libfiledata);
- return nbr_entries;
+ /* Return the number of items added to entries. */
+ int added_entries_len = group_len + datablock_len + parent_len;
+ return added_entries_len;
}
#if 0
@@ -3136,11 +3287,43 @@ typedef struct FileListReadJob {
* The job system calls #filelist_readjob_update which moves any read file from #tmp_filelist
* into #filelist in a thread-safe way.
*
+ * #tmp_filelist also keeps an `AssetLibrary *` so that it can be loaded in the same thread, and
+ * moved to #filelist once all categories are loaded.
+ *
* NOTE: #tmp_filelist is freed in #filelist_readjob_free, so any copied pointers need to be set
* to NULL to avoid double-freeing them. */
struct FileList *tmp_filelist;
} FileListReadJob;
+static bool filelist_readjob_should_recurse_into_entry(const int max_recursion,
+ const int current_recursion_level,
+ FileListInternEntry *entry)
+{
+ if (max_recursion == 0) {
+ /* Recursive loading is disabled. */
+ return false;
+ }
+ if (current_recursion_level >= max_recursion) {
+ /* No more levels of recursion left. */
+ return false;
+ }
+ if (entry->typeflag & FILE_TYPE_BLENDERLIB) {
+ /* Libraries are already loaded recursively when recursive loaded is used. No need to add
+ * them another time. This loading is done with the `LIST_LIB_RECURSIVE` option. */
+ return false;
+ }
+ if (!(entry->typeflag & FILE_TYPE_DIR)) {
+ /* Cannot recurse into regular file entries. */
+ return false;
+ }
+ if (FILENAME_IS_CURRPAR(entry->relpath)) {
+ /* Don't schedule go to parent entry, (`..`) */
+ return false;
+ }
+
+ return true;
+}
+
static void filelist_readjob_do(const bool do_lib,
FileListReadJob *job_params,
const short *stop,
@@ -3177,7 +3360,6 @@ static void filelist_readjob_do(const bool do_lib,
while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) {
FileListInternEntry *entry;
int nbr_entries = 0;
- bool is_lib = do_lib;
char *subdir;
char rel_subdir[FILE_MAX_LIBEXTRA];
@@ -3200,45 +3382,54 @@ static void filelist_readjob_do(const bool do_lib,
BLI_path_normalize_dir(root, rel_subdir);
BLI_path_rel(rel_subdir, root);
+ bool is_lib = false;
if (do_lib) {
- nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar);
+ ListLibOptions list_lib_options = 0;
+ if (!skip_currpar) {
+ list_lib_options |= LIST_LIB_ADD_PARENT;
+ }
+
+ /* Libraries are loaded recursively when max_recursion is set. It doesn't check if there is
+ * still a recursion level over. */
+ if (max_recursion > 0) {
+ list_lib_options |= LIST_LIB_RECURSIVE;
+ }
+ /* Only load assets when browsing an asset library. For normal file browsing we return all
+ * entries. `FLF_ASSETS_ONLY` filter can be enabled/disabled by the user.*/
+ if (filelist->asset_library_ref) {
+ list_lib_options |= LIST_LIB_ASSETS_ONLY;
+ }
+ nbr_entries = filelist_readjob_list_lib(subdir, &entries, list_lib_options);
+ if (nbr_entries > 0) {
+ is_lib = true;
+ }
}
- if (!nbr_entries) {
- is_lib = false;
+
+ if (!is_lib) {
nbr_entries = filelist_readjob_list_dir(
subdir, &entries, filter_glob, do_lib, job_params->main_name, skip_currpar);
}
for (entry = entries.first; entry; entry = entry->next) {
- BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath);
-
entry->uid = filelist_uid_generate(filelist);
- /* Only thing we change in direntry here, so we need to free it first. */
+ /* When loading entries recursive, the rel_path should be relative from the root dir.
+ * we combine the relative path to the subdir with the relative path of the entry. */
+ BLI_join_dirfile(dir, sizeof(dir), rel_subdir, entry->relpath);
MEM_freeN(entry->relpath);
entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//'
* added by BLI_path_rel to rel_subdir. */
entry->name = fileentry_uiname(root, entry->relpath, entry->typeflag, dir);
entry->free_name = true;
- /* Here we decide whether current filedirentry is to be listed too, or not. */
- if (max_recursion && (is_lib || (recursion_level <= max_recursion))) {
- if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) {
- /* Skip... */
- }
- else if (!is_lib && (recursion_level >= max_recursion) &&
- ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) {
- /* Do not recurse in real directories in this case, only in .blend libs. */
- }
- else {
- /* We have a directory we want to list, add it to todo list! */
- BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath);
- BLI_path_normalize_dir(job_params->main_name, dir);
- td_dir = BLI_stack_push_r(todo_dirs);
- td_dir->level = recursion_level + 1;
- td_dir->dir = BLI_strdup(dir);
- nbr_todo_dirs++;
- }
+ if (filelist_readjob_should_recurse_into_entry(max_recursion, recursion_level, entry)) {
+ /* We have a directory we want to list, add it to todo list! */
+ BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath);
+ BLI_path_normalize_dir(job_params->main_name, dir);
+ td_dir = BLI_stack_push_r(todo_dirs);
+ td_dir->level = recursion_level + 1;
+ td_dir->dir = BLI_strdup(dir);
+ nbr_todo_dirs++;
}
}
@@ -3284,6 +3475,47 @@ static void filelist_readjob_lib(FileListReadJob *job_params,
filelist_readjob_do(true, job_params, stop, do_update, progress);
}
+static void filelist_asset_library_path(const FileListReadJob *job_params,
+ char r_library_root_path[FILE_MAX])
+{
+ if (job_params->filelist->type == FILE_MAIN_ASSET) {
+ /* For the "Current File" library (#FILE_MAIN_ASSET) we get the asset library root path based
+ * on main. */
+ BKE_asset_library_find_suitable_root_path_from_main(job_params->current_main,
+ r_library_root_path);
+ }
+ else {
+ BLI_strncpy(r_library_root_path, job_params->tmp_filelist->filelist.root, FILE_MAX);
+ }
+}
+
+/**
+ * Load asset library data, which currently means loading the asset catalogs for the library.
+ */
+static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params, short *do_update)
+{
+ FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
+
+ if (job_params->filelist->asset_library_ref != NULL) {
+ char library_root_path[FILE_MAX];
+ filelist_asset_library_path(job_params, library_root_path);
+
+ /* Load asset catalogs, into the temp filelist for thread-safety.
+ * #filelist_readjob_endjob() will move it into the real filelist. */
+ tmp_filelist->asset_library = BKE_asset_library_load(library_root_path);
+ *do_update = true;
+ }
+}
+
+static void filelist_readjob_asset_library(FileListReadJob *job_params,
+ short *stop,
+ short *do_update,
+ float *progress)
+{
+ filelist_readjob_load_asset_library_data(job_params, do_update);
+ filelist_readjob_lib(job_params, stop, do_update, progress);
+}
+
static void filelist_readjob_main(FileListReadJob *job_params,
short *stop,
short *do_update,
@@ -3305,6 +3537,8 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params,
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET));
+ filelist_readjob_load_asset_library_data(job_params, do_update);
+
/* A valid, but empty directory from now. */
filelist->filelist.nbr_entries = 0;
@@ -3355,6 +3589,8 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update
BLI_mutex_lock(&flrj->lock);
BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist);
+ BLI_assert_msg(flrj->filelist->asset_library == NULL,
+ "Asset library should not yet be assigned at start of read job");
flrj->tmp_filelist = MEM_dupallocN(flrj->filelist);
@@ -3394,11 +3630,17 @@ static void filelist_readjob_update(void *flrjv)
flrj->tmp_filelist->filelist.nbr_entries = 0;
}
+ if (flrj->tmp_filelist->asset_library) {
+ flrj->filelist->asset_library = flrj->tmp_filelist->asset_library;
+ flrj->tmp_filelist->asset_library = NULL; /* MUST be NULL to avoid double-free. */
+ }
+
BLI_mutex_unlock(&flrj->lock);
if (new_nbr_entries) {
- /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! */
- filelist_clear_ex(flrj->filelist, true, false);
+ /* Do not clear selection cache, we can assume already 'selected' UIDs are still valid! Keep
+ * the asset library data we just read. */
+ filelist_clear_ex(flrj->filelist, false, true, false);
flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING);
}
diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h
index 1fb05e0f9ac..d1f37b5b365 100644
--- a/source/blender/editors/space_file/filelist.h
+++ b/source/blender/editors/space_file/filelist.h
@@ -31,6 +31,7 @@ struct AssetLibraryReference;
struct BlendHandle;
struct FileList;
struct FileSelection;
+struct bUUID;
struct wmWindowManager;
struct FileDirEntry;
@@ -71,6 +72,10 @@ void filelist_setfilter_options(struct FileList *filelist,
const bool filter_assets_only,
const char *filter_glob,
const char *filter_search);
+void filelist_set_asset_catalog_filter_options(
+ struct FileList *filelist,
+ eFileSel_Params_AssetCatalogVisibility catalog_visibility,
+ const struct bUUID *catalog_id);
void filelist_filter(struct FileList *filelist);
void filelist_setlibrary(struct FileList *filelist,
const struct AssetLibraryReference *asset_library_ref);
@@ -86,7 +91,10 @@ int filelist_geticon(struct FileList *filelist, const int index, const bool is_m
struct FileList *filelist_new(short type);
void filelist_settype(struct FileList *filelist, short type);
void filelist_clear(struct FileList *filelist);
-void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection);
+void filelist_clear_ex(struct FileList *filelist,
+ const bool do_asset_library,
+ const bool do_cache,
+ const bool do_selection);
void filelist_free(struct FileList *filelist);
const char *filelist_dir(struct FileList *filelist);
@@ -141,6 +149,8 @@ void filelist_entry_parent_select_set(struct FileList *filelist,
void filelist_setrecursion(struct FileList *filelist, const int recursion_level);
+struct AssetLibrary *filelist_asset_library(struct FileList *filelist);
+
struct BlendHandle *filelist_lib(struct FileList *filelist);
bool filelist_islibrary(struct FileList *filelist, char *dir, char **r_group);
void filelist_freelib(struct FileList *filelist);
diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c
index f7bdb4326a5..83b33fe8aa9 100644
--- a/source/blender/editors/space_file/filesel.c
+++ b/source/blender/editors/space_file/filesel.c
@@ -120,19 +120,16 @@ static void fileselect_ensure_updated_asset_params(SpaceFile *sfile)
asset_params->base_params.details_flags = U_default.file_space_data.details_flags;
asset_params->asset_library_ref.type = ASSET_LIBRARY_LOCAL;
asset_params->asset_library_ref.custom_library_index = -1;
- asset_params->import_type = FILE_ASSET_IMPORT_APPEND;
+ asset_params->import_type = FILE_ASSET_IMPORT_APPEND_REUSE;
}
FileSelectParams *base_params = &asset_params->base_params;
base_params->file[0] = '\0';
base_params->filter_glob[0] = '\0';
- /* TODO: this way of using filters to form categories is notably slower than specifying a
- * "group" to read. That's because all types are read and filtering is applied afterwards. Would
- * be nice if we could lazy-read individual groups. */
base_params->flag |= U_default.file_space_data.flag | FILE_ASSETS_ONLY | FILE_FILTER;
base_params->flag &= ~FILE_DIRSEL_ONLY;
base_params->filter |= FILE_TYPE_BLENDERLIB;
- base_params->filter_id = FILTER_ID_OB | FILTER_ID_GR;
+ base_params->filter_id = FILTER_ID_ALL;
base_params->display = FILE_IMGDISPLAY;
base_params->sort = FILE_SORT_ALPHA;
/* Asset libraries include all sub-directories, so enable maximal recursion. */
@@ -440,7 +437,8 @@ static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params)
BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir));
break;
}
- base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET : FILE_LOADLIB;
+ base_params->type = (library->type == ASSET_LIBRARY_LOCAL) ? FILE_MAIN_ASSET :
+ FILE_ASSET_LIBRARY;
}
void fileselect_refresh_params(SpaceFile *sfile)
@@ -461,6 +459,15 @@ bool ED_fileselect_is_asset_browser(const SpaceFile *sfile)
return (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS);
}
+struct AssetLibrary *ED_fileselect_active_asset_library_get(const SpaceFile *sfile)
+{
+ if (!ED_fileselect_is_asset_browser(sfile) || !sfile->files) {
+ return NULL;
+ }
+
+ return filelist_asset_library(sfile->files);
+}
+
struct ID *ED_fileselect_active_asset_get(const SpaceFile *sfile)
{
if (!ED_fileselect_is_asset_browser(sfile)) {
diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c
index 776bb0b3bb7..091c2d5f434 100644
--- a/source/blender/editors/space_file/fsmenu.c
+++ b/source/blender/editors/space_file/fsmenu.c
@@ -958,7 +958,7 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
found = 1;
}
if (endmntent(fp) == 0) {
- fprintf(stderr, "could not close the list of mounted filesystems\n");
+ fprintf(stderr, "could not close the list of mounted file-systems\n");
}
}
/* Check gvfs shares. */
diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c
index 42a9c4aa2d5..a563f24e24e 100644
--- a/source/blender/editors/space_file/space_file.c
+++ b/source/blender/editors/space_file/space_file.c
@@ -1,4 +1,4 @@
-/*
+/*
* 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
@@ -335,9 +335,9 @@ static void file_refresh(const bContext *C, ScrArea *area)
params->highlight_file = -1; /* added this so it opens nicer (ton) */
}
- if (!U.experimental.use_extended_asset_browser && ED_fileselect_is_asset_browser(sfile)) {
+ if (ED_fileselect_is_asset_browser(sfile)) {
/* Only poses supported as non-experimental right now. */
- params->filter_id = FILTER_ID_AC;
+ params->filter_id = U.experimental.use_extended_asset_browser ? FILTER_ID_ALL : FILTER_ID_AC;
}
filelist_settype(sfile->files, params->type);
@@ -355,6 +355,10 @@ static void file_refresh(const bContext *C, ScrArea *area)
(params->flag & FILE_ASSETS_ONLY) != 0,
params->filter_glob,
params->filter_search);
+ if (asset_params) {
+ filelist_set_asset_catalog_filter_options(
+ sfile->files, asset_params->asset_catalog_visibility, &asset_params->catalog_id);
+ }
/* Update the active indices of bookmarks & co. */
sfile->systemnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_SYSTEM, params->dir);
@@ -1050,6 +1054,7 @@ void ED_spacetype_file(void)
art->init = file_tools_region_init;
art->draw = file_tools_region_draw;
BLI_addhead(&st->regiontypes, art);
+ file_tools_region_panels_register(art);
/* regions: tool properties */
art = MEM_callocN(sizeof(ARegionType), "spacetype file operator region");
diff --git a/source/blender/editors/space_graph/space_graph.c b/source/blender/editors/space_graph/space_graph.c
index 720d69eaf4f..0e2c9b85bc6 100644
--- a/source/blender/editors/space_graph/space_graph.c
+++ b/source/blender/editors/space_graph/space_graph.c
@@ -237,29 +237,27 @@ static void graph_main_region_draw(const bContext *C, ARegion *region)
v2d->tot.xmax += 10.0f;
}
- if (((sipo->flag & SIPO_NODRAWCURSOR) == 0) || (sipo->mode == SIPO_MODE_DRIVERS)) {
+ if (((sipo->flag & SIPO_NODRAWCURSOR) == 0)) {
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
/* horizontal component of value-cursor (value line before the current frame line) */
- if ((sipo->flag & SIPO_NODRAWCURSOR) == 0) {
- float y = sipo->cursorVal;
+ float y = sipo->cursorVal;
- /* Draw a green line to indicate the cursor value */
- immUniformThemeColorShadeAlpha(TH_CFRAME, -10, -50);
- GPU_blend(GPU_BLEND_ALPHA);
- GPU_line_width(2.0);
+ /* Draw a line to indicate the cursor value. */
+ immUniformThemeColorShadeAlpha(TH_CFRAME, -10, -50);
+ GPU_blend(GPU_BLEND_ALPHA);
+ GPU_line_width(2.0);
- immBegin(GPU_PRIM_LINES, 2);
- immVertex2f(pos, v2d->cur.xmin, y);
- immVertex2f(pos, v2d->cur.xmax, y);
- immEnd();
+ immBegin(GPU_PRIM_LINES, 2);
+ immVertex2f(pos, v2d->cur.xmin, y);
+ immVertex2f(pos, v2d->cur.xmax, y);
+ immEnd();
- GPU_blend(GPU_BLEND_NONE);
- }
+ GPU_blend(GPU_BLEND_NONE);
- /* current frame or vertical component of vertical component of the cursor */
+ /* Vertical component of of the cursor. */
if (sipo->mode == SIPO_MODE_DRIVERS) {
/* cursor x-value */
float x = sipo->cursorTime;
@@ -311,12 +309,17 @@ static void graph_main_region_draw_overlay(const bContext *C, ARegion *region)
{
/* draw entirely, view changes should be handled here */
const SpaceGraph *sipo = CTX_wm_space_graph(C);
+
+ /* Driver Editor's X axis is not time. */
+ if (sipo->mode == SIPO_MODE_DRIVERS) {
+ return;
+ }
+
const Scene *scene = CTX_data_scene(C);
- const bool draw_vert_line = sipo->mode != SIPO_MODE_DRIVERS;
View2D *v2d = &region->v2d;
/* scrubbing region */
- ED_time_scrub_draw_current_frame(region, scene, sipo->flag & SIPO_DRAWTIME, draw_vert_line);
+ ED_time_scrub_draw_current_frame(region, scene, sipo->flag & SIPO_DRAWTIME);
/* scrollers */
/* FIXME: args for scrollers depend on the type of data being shown. */
diff --git a/source/blender/editors/space_image/image_draw.c b/source/blender/editors/space_image/image_draw.c
index d2af26aa1d7..fc04ec1fe02 100644
--- a/source/blender/editors/space_image/image_draw.c
+++ b/source/blender/editors/space_image/image_draw.c
@@ -34,6 +34,7 @@
#include "DNA_scene_types.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
+#include "DNA_view2d_types.h"
#include "PIL_time.h"
@@ -576,3 +577,62 @@ void draw_image_cache(const bContext *C, ARegion *region)
ED_mask_draw_frames(mask, region, cfra, sfra, efra);
}
}
+
+float ED_space_image_zoom_level(const View2D *v2d, const int grid_dimension)
+{
+ /* UV-space length per pixel */
+ float xzoom = (v2d->cur.xmax - v2d->cur.xmin) / ((float)(v2d->mask.xmax - v2d->mask.xmin));
+ float yzoom = (v2d->cur.ymax - v2d->cur.ymin) / ((float)(v2d->mask.ymax - v2d->mask.ymin));
+
+ /* Zoom_factor for UV/Image editor is calculated based on:
+ * - Default grid size on startup, which is 256x256 pixels
+ * - How blend factor for grid lines is set up in the fragment shader `grid_frag.glsl`. */
+ float zoom_factor;
+ zoom_factor = (xzoom + yzoom) / 2.0f; /* Average for accuracy. */
+ zoom_factor *= 256.0f / (powf(grid_dimension, 2));
+ return zoom_factor;
+}
+
+void ED_space_image_grid_steps(SpaceImage *sima,
+ float grid_steps[SI_GRID_STEPS_LEN],
+ const int grid_dimension)
+{
+ if (sima->flag & SI_CUSTOM_GRID) {
+ for (int step = 0; step < SI_GRID_STEPS_LEN; step++) {
+ grid_steps[step] = powf(1, step) * (1.0f / ((float)sima->custom_grid_subdiv));
+ }
+ }
+ else {
+ for (int step = 0; step < SI_GRID_STEPS_LEN; step++) {
+ grid_steps[step] = powf(grid_dimension, step) *
+ (1.0f / (powf(grid_dimension, SI_GRID_STEPS_LEN)));
+ }
+ }
+}
+
+/**
+ * Calculate the increment snapping value for UV/image editor based on the zoom factor
+ * The code in here (except the offset part) is used in `grid_frag.glsl` (see `grid_res`) for
+ * drawing the grid overlay for the UV/Image editor.
+ */
+float ED_space_image_increment_snap_value(const int grid_dimesnions,
+ const float grid_steps[SI_GRID_STEPS_LEN],
+ const float zoom_factor)
+{
+ /* Small offset on each grid_steps[] so that snapping value doesn't change until grid lines are
+ * significantly visible.
+ * `Offset = 3/4 * (grid_steps[i] - (grid_steps[i] / grid_dimesnsions))`
+ *
+ * Refer `grid_frag.glsl` to find out when grid lines actually start appearing */
+
+ for (int step = 0; step < SI_GRID_STEPS_LEN; step++) {
+ float offset = (3.0f / 4.0f) * (grid_steps[step] - (grid_steps[step] / grid_dimesnions));
+
+ if ((grid_steps[step] - offset) > zoom_factor) {
+ return grid_steps[step];
+ }
+ }
+
+ /* Fallback */
+ return grid_steps[0];
+}
diff --git a/source/blender/editors/space_image/space_image.c b/source/blender/editors/space_image/space_image.c
index de8e4684d45..f14a8266cdd 100644
--- a/source/blender/editors/space_image/space_image.c
+++ b/source/blender/editors/space_image/space_image.c
@@ -126,13 +126,7 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED(
simage->tile_grid_shape[0] = 1;
simage->tile_grid_shape[1] = 1;
- /* tool header */
- region = MEM_callocN(sizeof(ARegion), "tool header for image");
-
- BLI_addtail(&simage->regionbase, region);
- region->regiontype = RGN_TYPE_TOOL_HEADER;
- region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
- region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER;
+ simage->custom_grid_subdiv = 10;
/* header */
region = MEM_callocN(sizeof(ARegion), "header for image");
@@ -141,6 +135,14 @@ static SpaceLink *image_create(const ScrArea *UNUSED(area), const Scene *UNUSED(
region->regiontype = RGN_TYPE_HEADER;
region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
+ /* tool header */
+ region = MEM_callocN(sizeof(ARegion), "tool header for image");
+
+ BLI_addtail(&simage->regionbase, region);
+ region->regiontype = RGN_TYPE_TOOL_HEADER;
+ region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
+ region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER;
+
/* buttons/list view */
region = MEM_callocN(sizeof(ARegion), "buttons for image");
diff --git a/source/blender/editors/space_info/info_ops.c b/source/blender/editors/space_info/info_ops.c
index a99396ecdf0..8e37e5fe9a8 100644
--- a/source/blender/editors/space_info/info_ops.c
+++ b/source/blender/editors/space_info/info_ops.c
@@ -564,7 +564,7 @@ void FILE_OT_find_missing_files(wmOperatorType *ot)
/** \name Report Box Operator
* \{ */
-/* NOTE(matt): Hard to decide whether to keep this as an operator,
+/* NOTE(@broken): Hard to decide whether to keep this as an operator,
* or turn it into a hard_coded UI control feature,
* handling TIMER events for all regions in `interface_handlers.c`.
* Not sure how good that is to be accessing UI data from
diff --git a/source/blender/editors/space_nla/nla_draw.c b/source/blender/editors/space_nla/nla_draw.c
index c1b308d213f..4694d8652f6 100644
--- a/source/blender/editors/space_nla/nla_draw.c
+++ b/source/blender/editors/space_nla/nla_draw.c
@@ -152,7 +152,7 @@ static void nla_action_draw_keyframes(
format, "flags", GPU_COMP_U32, 1, GPU_FETCH_INT);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 1.0f);
immUniform2f("ViewportSize", BLI_rcti_size_x(&v2d->mask) + 1, BLI_rcti_size_y(&v2d->mask) + 1);
immBegin(GPU_PRIM_POINTS, key_len);
diff --git a/source/blender/editors/space_nla/space_nla.c b/source/blender/editors/space_nla/space_nla.c
index 987d06cfe5c..8b44c26f07c 100644
--- a/source/blender/editors/space_nla/space_nla.c
+++ b/source/blender/editors/space_nla/space_nla.c
@@ -289,7 +289,7 @@ static void nla_main_region_draw_overlay(const bContext *C, ARegion *region)
View2D *v2d = &region->v2d;
/* scrubbing region */
- ED_time_scrub_draw_current_frame(region, scene, snla->flag & SNLA_DRAWTIME, true);
+ ED_time_scrub_draw_current_frame(region, scene, snla->flag & SNLA_DRAWTIME);
/* scrollers */
UI_view2d_scrollers_draw(v2d, NULL);
diff --git a/source/blender/editors/space_node/drawnode.cc b/source/blender/editors/space_node/drawnode.cc
index 62f40152416..8d6d56fc383 100644
--- a/source/blender/editors/space_node/drawnode.cc
+++ b/source/blender/editors/space_node/drawnode.cc
@@ -164,6 +164,11 @@ static void node_buts_curvevec(uiLayout *layout, bContext *UNUSED(C), PointerRNA
uiTemplateCurveMapping(layout, ptr, "mapping", 'v', false, false, false, false);
}
+static void node_buts_curvefloat(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+ uiTemplateCurveMapping(layout, ptr, "mapping", 0, false, false, false, false);
+}
+
#define SAMPLE_FLT_ISNONE FLT_MAX
/* Bad bad, 2.5 will do better? ... no it won't! */
static float _sample_col[4] = {SAMPLE_FLT_ISNONE};
@@ -1183,6 +1188,9 @@ static void node_shader_set_butfunc(bNodeType *ntype)
case SH_NODE_CURVE_RGB:
ntype->draw_buttons = node_buts_curvecol;
break;
+ case SH_NODE_CURVE_FLOAT:
+ ntype->draw_buttons = node_buts_curvefloat;
+ break;
case SH_NODE_MAPPING:
ntype->draw_buttons = node_shader_buts_mapping;
break;
@@ -1563,7 +1571,7 @@ static void node_composit_buts_antialiasing(uiLayout *layout, bContext *UNUSED(C
uiItemR(col, ptr, "corner_rounding", 0, nullptr, ICON_NONE);
}
-/* qdn: glare node */
+/* glare node */
static void node_composit_buts_glare(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
uiItemR(layout, ptr, "glare_type", DEFAULT_FLAGS, "", ICON_NONE);
@@ -2079,7 +2087,7 @@ static void node_composit_buts_premulkey(uiLayout *layout, bContext *UNUSED(C),
static void node_composit_buts_view_levels(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
{
- uiItemR(layout, ptr, "channel", DEFAULT_FLAGS | UI_ITEM_R_EXPAND, nullptr, ICON_NONE);
+ uiItemR(layout, ptr, "channel", DEFAULT_FLAGS, "", ICON_NONE);
}
static void node_composit_buts_colorbalance(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
@@ -3933,9 +3941,13 @@ static struct {
uint p0_id, p1_id, p2_id, p3_id;
uint colid_id, muted_id;
uint dim_factor_id;
+ uint thickness_id;
+ uint dash_factor_id;
GPUVertBufRaw p0_step, p1_step, p2_step, p3_step;
GPUVertBufRaw colid_step, muted_step;
GPUVertBufRaw dim_factor_step;
+ GPUVertBufRaw thickness_step;
+ GPUVertBufRaw dash_factor_step;
uint count;
bool enabled;
} g_batch_link;
@@ -3952,6 +3964,10 @@ static void nodelink_batch_reset()
g_batch_link.inst_vbo, g_batch_link.muted_id, &g_batch_link.muted_step);
GPU_vertbuf_attr_get_raw_data(
g_batch_link.inst_vbo, g_batch_link.dim_factor_id, &g_batch_link.dim_factor_step);
+ GPU_vertbuf_attr_get_raw_data(
+ g_batch_link.inst_vbo, g_batch_link.thickness_id, &g_batch_link.thickness_step);
+ GPU_vertbuf_attr_get_raw_data(
+ g_batch_link.inst_vbo, g_batch_link.dash_factor_id, &g_batch_link.dash_factor_step);
g_batch_link.count = 0;
}
@@ -4071,6 +4087,10 @@ static void nodelink_batch_init()
&format_inst, "domuted", GPU_COMP_U8, 2, GPU_FETCH_INT);
g_batch_link.dim_factor_id = GPU_vertformat_attr_add(
&format_inst, "dim_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
+ g_batch_link.thickness_id = GPU_vertformat_attr_add(
+ &format_inst, "thickness", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
+ g_batch_link.dash_factor_id = GPU_vertformat_attr_add(
+ &format_inst, "dash_factor", GPU_COMP_F32, 1, GPU_FETCH_FLOAT);
g_batch_link.inst_vbo = GPU_vertbuf_create_with_format_ex(&format_inst, GPU_USAGE_STREAM);
/* Alloc max count but only draw the range we need. */
GPU_vertbuf_data_alloc(g_batch_link.inst_vbo, NODELINK_GROUP_SIZE);
@@ -4147,7 +4167,9 @@ static void nodelink_batch_add_link(const SpaceNode *snode,
int th_col3,
bool drawarrow,
bool drawmuted,
- float dim_factor)
+ float dim_factor,
+ float thickness,
+ float dash_factor)
{
/* Only allow these colors. If more is needed, you need to modify the shader accordingly. */
BLI_assert(ELEM(th_col1, TH_WIRE_INNER, TH_WIRE, TH_ACTIVE, TH_EDGE_SELECT, TH_REDALERT));
@@ -4167,6 +4189,8 @@ static void nodelink_batch_add_link(const SpaceNode *snode,
char *muted = (char *)GPU_vertbuf_raw_step(&g_batch_link.muted_step);
muted[0] = drawmuted;
*(float *)GPU_vertbuf_raw_step(&g_batch_link.dim_factor_step) = dim_factor;
+ *(float *)GPU_vertbuf_raw_step(&g_batch_link.thickness_step) = thickness;
+ *(float *)GPU_vertbuf_raw_step(&g_batch_link.dash_factor_step) = dash_factor;
if (g_batch_link.count == NODELINK_GROUP_SIZE) {
nodelink_batch_draw(snode);
@@ -4182,6 +4206,16 @@ void node_draw_link_bezier(const View2D *v2d,
int th_col3)
{
const float dim_factor = node_link_dim_factor(v2d, link);
+ float thickness = 1.5f;
+ float dash_factor = 1.0f;
+ if (snode->edittree->type == NTREE_GEOMETRY) {
+ if (link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) {
+ /* Make field links a bit thinner. */
+ thickness = 1.0f;
+ /* Draw field as dashes. */
+ dash_factor = 0.75f;
+ }
+ }
float vec[4][2];
const bool highlighted = link->flag & NODE_LINK_TEMP_HIGHLIGHT;
@@ -4205,7 +4239,9 @@ void node_draw_link_bezier(const View2D *v2d,
th_col3,
drawarrow,
drawmuted,
- dim_factor);
+ dim_factor,
+ thickness,
+ dash_factor);
}
else {
/* Draw single link. */
@@ -4231,6 +4267,8 @@ void node_draw_link_bezier(const View2D *v2d,
GPU_batch_uniform_1i(batch, "doArrow", drawarrow);
GPU_batch_uniform_1i(batch, "doMuted", drawmuted);
GPU_batch_uniform_1f(batch, "dim_factor", dim_factor);
+ GPU_batch_uniform_1f(batch, "thickness", thickness);
+ GPU_batch_uniform_1f(batch, "dash_factor", dash_factor);
GPU_batch_draw(batch);
}
}
@@ -4282,6 +4320,13 @@ void node_draw_link(View2D *v2d, SpaceNode *snode, bNodeLink *link)
// th_col3 = -1; /* no shadow */
}
}
+ /* Links from field to non-field sockets are not allowed. */
+ if (snode->edittree->type == NTREE_GEOMETRY && !(link->flag & NODE_LINK_DRAGGED)) {
+ if ((link->fromsock && link->fromsock->display_shape == SOCK_DISPLAY_SHAPE_DIAMOND) &&
+ (link->tosock && link->tosock->display_shape == SOCK_DISPLAY_SHAPE_CIRCLE)) {
+ th_col1 = th_col2 = th_col3 = TH_REDALERT;
+ }
+ }
node_draw_link_bezier(v2d, snode, link, th_col1, th_col2, th_col3);
}
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc
index 10a3285be8b..9b243290566 100644
--- a/source/blender/editors/space_node/node_draw.cc
+++ b/source/blender/editors/space_node/node_draw.cc
@@ -79,6 +79,7 @@
#include "RNA_access.h"
#include "NOD_geometry_nodes_eval_log.hh"
+#include "NOD_node_declaration.hh"
#include "FN_field_cpp_type.hh"
@@ -733,12 +734,6 @@ static void node_draw_mute_line(const View2D *v2d, const SpaceNode *snode, const
GPU_blend(GPU_BLEND_NONE);
}
-/* Flags used in gpu_shader_keyframe_diamond_frag.glsl. */
-#define MARKER_SHAPE_DIAMOND 0x1
-#define MARKER_SHAPE_SQUARE 0xC
-#define MARKER_SHAPE_CIRCLE 0x2
-#define MARKER_SHAPE_INNER_DOT 0x10
-
static void node_socket_draw(const bNodeSocket *sock,
const float color[4],
const float color_outline[4],
@@ -757,16 +752,16 @@ static void node_socket_draw(const bNodeSocket *sock,
switch (sock->display_shape) {
case SOCK_DISPLAY_SHAPE_DIAMOND:
case SOCK_DISPLAY_SHAPE_DIAMOND_DOT:
- flags = MARKER_SHAPE_DIAMOND;
+ flags = GPU_KEYFRAME_SHAPE_DIAMOND;
break;
case SOCK_DISPLAY_SHAPE_SQUARE:
case SOCK_DISPLAY_SHAPE_SQUARE_DOT:
- flags = MARKER_SHAPE_SQUARE;
+ flags = GPU_KEYFRAME_SHAPE_SQUARE;
break;
default:
case SOCK_DISPLAY_SHAPE_CIRCLE:
case SOCK_DISPLAY_SHAPE_CIRCLE_DOT:
- flags = MARKER_SHAPE_CIRCLE;
+ flags = GPU_KEYFRAME_SHAPE_CIRCLE;
break;
}
@@ -774,7 +769,7 @@ static void node_socket_draw(const bNodeSocket *sock,
SOCK_DISPLAY_SHAPE_DIAMOND_DOT,
SOCK_DISPLAY_SHAPE_SQUARE_DOT,
SOCK_DISPLAY_SHAPE_CIRCLE_DOT)) {
- flags |= MARKER_SHAPE_INNER_DOT;
+ flags |= GPU_KEYFRAME_SHAPE_INNER_DOT;
}
immAttr4fv(col_id, color);
@@ -1091,12 +1086,37 @@ static void node_socket_draw_nested(const bContext *C,
but,
[](bContext *C, void *argN, const char *UNUSED(tip)) {
SocketTooltipData *data = (SocketTooltipData *)argN;
- std::optional<std::string> str = create_socket_inspection_string(
+ std::optional<std::string> socket_inspection_str = create_socket_inspection_string(
C, *data->ntree, *data->node, *data->socket);
- if (str.has_value()) {
- return BLI_strdup(str->c_str());
+
+ std::stringstream output;
+ if (data->node->declaration != nullptr) {
+ ListBase *list;
+ Span<blender::nodes::SocketDeclarationPtr> decl_list;
+
+ if (data->socket->in_out == SOCK_IN) {
+ list = &data->node->inputs;
+ decl_list = data->node->declaration->inputs();
+ }
+ else {
+ list = &data->node->outputs;
+ decl_list = data->node->declaration->outputs();
+ }
+
+ const int socket_index = BLI_findindex(list, data->socket);
+ const blender::nodes::SocketDeclaration &socket_decl = *decl_list[socket_index];
+ blender::StringRef description = socket_decl.description();
+ if (!description.is_empty()) {
+ output << TIP_(description.data()) << ".\n\n";
+ }
+ }
+ if (socket_inspection_str.has_value()) {
+ output << *socket_inspection_str;
+ }
+ else {
+ output << TIP_("The socket value has not been computed yet");
}
- return BLI_strdup(TIP_("The socket value has not been computed yet"));
+ return BLI_strdup(output.str().c_str());
},
data,
MEM_freeN);
@@ -1132,7 +1152,7 @@ void ED_node_socket_draw(bNodeSocket *sock, const rcti *rect, const float color[
GPU_blend(GPU_BLEND_ALPHA);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 0.7f);
immUniform2f("ViewportSize", -1.0f, -1.0f);
@@ -1277,7 +1297,7 @@ void node_draw_sockets(const View2D *v2d,
GPU_blend(GPU_BLEND_ALPHA);
GPU_program_point_size(true);
- immBindBuiltinProgram(GPU_SHADER_KEYFRAME_DIAMOND);
+ immBindBuiltinProgram(GPU_SHADER_KEYFRAME_SHAPE);
immUniform1f("outline_scale", 0.7f);
immUniform2f("ViewportSize", -1.0f, -1.0f);
diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc
index 7d95659e403..b69e7e98bca 100644
--- a/source/blender/editors/space_node/node_relationships.cc
+++ b/source/blender/editors/space_node/node_relationships.cc
@@ -220,6 +220,7 @@ static LinkData *create_drag_link(Main *bmain, SpaceNode *snode, bNode *node, bN
if (node_connected_to_output(bmain, snode->edittree, node)) {
oplink->flag |= NODE_LINK_TEST;
}
+ oplink->flag |= NODE_LINK_DRAGGED;
return linkdata;
}
@@ -894,6 +895,8 @@ static void node_link_exit(bContext *C, wmOperator *op, bool apply_links)
*/
do_tag_update |= (link->flag & NODE_LINK_TEST) != 0;
+ link->flag &= ~NODE_LINK_DRAGGED;
+
if (apply_links && link->tosock && link->fromsock) {
/* before actually adding the link,
* let nodes perform special link insertion handling
@@ -1097,6 +1100,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor
*oplink = *link;
oplink->next = oplink->prev = nullptr;
oplink->flag |= NODE_LINK_VALID;
+ oplink->flag |= NODE_LINK_DRAGGED;
/* The link could be disconnected and in that case we
* wouldn't be able to check whether tag update is
@@ -1150,6 +1154,7 @@ static bNodeLinkDrag *node_link_init(Main *bmain, SpaceNode *snode, float cursor
*oplink = *link_to_pick;
oplink->next = oplink->prev = nullptr;
oplink->flag |= NODE_LINK_VALID;
+ oplink->flag |= NODE_LINK_DRAGGED;
oplink->flag &= ~NODE_LINK_TEST;
if (node_connected_to_output(bmain, snode->edittree, link_to_pick->tonode)) {
oplink->flag |= NODE_LINK_TEST;
diff --git a/source/blender/editors/space_outliner/outliner_select.c b/source/blender/editors/space_outliner/outliner_select.c
index 581892ebb3a..5e409db0059 100644
--- a/source/blender/editors/space_outliner/outliner_select.c
+++ b/source/blender/editors/space_outliner/outliner_select.c
@@ -34,6 +34,7 @@
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "DNA_shader_fx_types.h"
+#include "DNA_text_types.h"
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
@@ -63,6 +64,7 @@
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_sequencer.h"
+#include "ED_text.h"
#include "ED_undo.h"
#include "SEQ_select.h"
@@ -737,6 +739,12 @@ static void tree_element_layer_collection_activate(bContext *C, TreeElement *te)
WM_main_add_notifier(NC_SCENE | ND_LAYER | NS_LAYER_COLLECTION | NA_ACTIVATED, NULL);
}
+static void tree_element_text_activate(bContext *C, TreeElement *te)
+{
+ Text *text = (Text *)te->store_elem->id;
+ ED_text_activate_in_screen(C, text);
+}
+
/* ---------------------------------------------- */
/* generic call for ID data check or make/check active in UI */
@@ -764,6 +772,9 @@ void tree_element_activate(bContext *C,
case ID_CA:
tree_element_camera_activate(C, tvc->scene, te);
break;
+ case ID_TXT:
+ tree_element_text_activate(C, te);
+ break;
}
}
diff --git a/source/blender/editors/space_sequencer/sequencer_draw.c b/source/blender/editors/space_sequencer/sequencer_draw.c
index 53f1c35776c..dc5e11b6998 100644
--- a/source/blender/editors/space_sequencer/sequencer_draw.c
+++ b/source/blender/editors/space_sequencer/sequencer_draw.c
@@ -107,36 +107,53 @@
static Sequence *special_seq_update = NULL;
-void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3])
-{
+void color3ubv_from_seq(const Scene *curscene,
+ const Sequence *seq,
+ const bool show_strip_color_tag,
+ uchar r_col[3])
+{
+ if (show_strip_color_tag && (uint)seq->color_tag < SEQUENCE_COLOR_TOT &&
+ seq->color_tag != SEQUENCE_COLOR_NONE) {
+ bTheme *btheme = UI_GetTheme();
+ const ThemeStripColor *strip_color = &btheme->strip_color[seq->color_tag];
+ copy_v3_v3_uchar(r_col, strip_color->color);
+ return;
+ }
+
uchar blendcol[3];
+ /* Sometimes the active theme is not the sequencer theme, e.g. when an operator invokes the file
+ * browser. This makes sure we get the right color values for the theme. */
+ struct bThemeState theme_state;
+ UI_Theme_Store(&theme_state);
+ UI_SetTheme(SPACE_SEQ, RGN_TYPE_WINDOW);
+
switch (seq->type) {
case SEQ_TYPE_IMAGE:
- UI_GetThemeColor3ubv(TH_SEQ_IMAGE, col);
+ UI_GetThemeColor3ubv(TH_SEQ_IMAGE, r_col);
break;
case SEQ_TYPE_META:
- UI_GetThemeColor3ubv(TH_SEQ_META, col);
+ UI_GetThemeColor3ubv(TH_SEQ_META, r_col);
break;
case SEQ_TYPE_MOVIE:
- UI_GetThemeColor3ubv(TH_SEQ_MOVIE, col);
+ UI_GetThemeColor3ubv(TH_SEQ_MOVIE, r_col);
break;
case SEQ_TYPE_MOVIECLIP:
- UI_GetThemeColor3ubv(TH_SEQ_MOVIECLIP, col);
+ UI_GetThemeColor3ubv(TH_SEQ_MOVIECLIP, r_col);
break;
case SEQ_TYPE_MASK:
- UI_GetThemeColor3ubv(TH_SEQ_MASK, col);
+ UI_GetThemeColor3ubv(TH_SEQ_MASK, r_col);
break;
case SEQ_TYPE_SCENE:
- UI_GetThemeColor3ubv(TH_SEQ_SCENE, col);
+ UI_GetThemeColor3ubv(TH_SEQ_SCENE, r_col);
if (seq->scene == curscene) {
- UI_GetColorPtrShade3ubv(col, col, 20);
+ UI_GetColorPtrShade3ubv(r_col, r_col, 20);
}
break;
@@ -144,9 +161,9 @@ void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3])
case SEQ_TYPE_CROSS:
case SEQ_TYPE_GAMCROSS:
case SEQ_TYPE_WIPE:
- col[0] = 130;
- col[1] = 130;
- col[2] = 130;
+ r_col[0] = 130;
+ r_col[1] = 130;
+ r_col[2] = 130;
break;
/* Effects. */
@@ -163,72 +180,74 @@ void color3ubv_from_seq(Scene *curscene, Sequence *seq, uchar col[3])
case SEQ_TYPE_ADJUSTMENT:
case SEQ_TYPE_GAUSSIAN_BLUR:
case SEQ_TYPE_COLORMIX:
- UI_GetThemeColor3ubv(TH_SEQ_EFFECT, col);
+ UI_GetThemeColor3ubv(TH_SEQ_EFFECT, r_col);
/* Slightly offset hue to distinguish different effects. */
if (seq->type == SEQ_TYPE_ADD) {
- rgb_byte_set_hue_float_offset(col, 0.03);
+ rgb_byte_set_hue_float_offset(r_col, 0.03);
}
else if (seq->type == SEQ_TYPE_SUB) {
- rgb_byte_set_hue_float_offset(col, 0.06);
+ rgb_byte_set_hue_float_offset(r_col, 0.06);
}
else if (seq->type == SEQ_TYPE_MUL) {
- rgb_byte_set_hue_float_offset(col, 0.13);
+ rgb_byte_set_hue_float_offset(r_col, 0.13);
}
else if (seq->type == SEQ_TYPE_ALPHAOVER) {
- rgb_byte_set_hue_float_offset(col, 0.16);
+ rgb_byte_set_hue_float_offset(r_col, 0.16);
}
else if (seq->type == SEQ_TYPE_ALPHAUNDER) {
- rgb_byte_set_hue_float_offset(col, 0.23);
+ rgb_byte_set_hue_float_offset(r_col, 0.23);
}
else if (seq->type == SEQ_TYPE_OVERDROP) {
- rgb_byte_set_hue_float_offset(col, 0.26);
+ rgb_byte_set_hue_float_offset(r_col, 0.26);
}
else if (seq->type == SEQ_TYPE_COLORMIX) {
- rgb_byte_set_hue_float_offset(col, 0.33);
+ rgb_byte_set_hue_float_offset(r_col, 0.33);
}
else if (seq->type == SEQ_TYPE_GAUSSIAN_BLUR) {
- rgb_byte_set_hue_float_offset(col, 0.43);
+ rgb_byte_set_hue_float_offset(r_col, 0.43);
}
else if (seq->type == SEQ_TYPE_GLOW) {
- rgb_byte_set_hue_float_offset(col, 0.46);
+ rgb_byte_set_hue_float_offset(r_col, 0.46);
}
else if (seq->type == SEQ_TYPE_ADJUSTMENT) {
- rgb_byte_set_hue_float_offset(col, 0.55);
+ rgb_byte_set_hue_float_offset(r_col, 0.55);
}
else if (seq->type == SEQ_TYPE_SPEED) {
- rgb_byte_set_hue_float_offset(col, 0.65);
+ rgb_byte_set_hue_float_offset(r_col, 0.65);
}
else if (seq->type == SEQ_TYPE_TRANSFORM) {
- rgb_byte_set_hue_float_offset(col, 0.75);
+ rgb_byte_set_hue_float_offset(r_col, 0.75);
}
else if (seq->type == SEQ_TYPE_MULTICAM) {
- rgb_byte_set_hue_float_offset(col, 0.85);
+ rgb_byte_set_hue_float_offset(r_col, 0.85);
}
break;
case SEQ_TYPE_COLOR:
- UI_GetThemeColor3ubv(TH_SEQ_COLOR, col);
+ UI_GetThemeColor3ubv(TH_SEQ_COLOR, r_col);
break;
case SEQ_TYPE_SOUND_RAM:
- UI_GetThemeColor3ubv(TH_SEQ_AUDIO, col);
+ UI_GetThemeColor3ubv(TH_SEQ_AUDIO, r_col);
blendcol[0] = blendcol[1] = blendcol[2] = 128;
if (seq->flag & SEQ_MUTE) {
- UI_GetColorPtrBlendShade3ubv(col, blendcol, col, 0.5, 20);
+ UI_GetColorPtrBlendShade3ubv(r_col, blendcol, r_col, 0.5, 20);
}
break;
case SEQ_TYPE_TEXT:
- UI_GetThemeColor3ubv(TH_SEQ_TEXT, col);
+ UI_GetThemeColor3ubv(TH_SEQ_TEXT, r_col);
break;
default:
- col[0] = 10;
- col[1] = 255;
- col[2] = 40;
+ r_col[0] = 10;
+ r_col[1] = 255;
+ r_col[2] = 40;
break;
}
+
+ UI_Theme_Restore(&theme_state);
}
typedef struct WaveVizData {
@@ -558,7 +577,13 @@ static void draw_seq_waveform_overlay(View2D *v2d,
}
}
-static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, float y1, float x2, float y2)
+static void drawmeta_contents(Scene *scene,
+ Sequence *seqm,
+ float x1,
+ float y1,
+ float x2,
+ float y2,
+ const bool show_strip_color_tag)
{
Sequence *seq;
uchar col[4];
@@ -614,7 +639,7 @@ static void drawmeta_contents(Scene *scene, Sequence *seqm, float x1, float y1,
rgb_float_to_uchar(col, colvars->col);
}
else {
- color3ubv_from_seq(scene, seq, col);
+ color3ubv_from_seq(scene, seq, show_strip_color_tag, col);
}
if ((seqm->flag & SEQ_MUTE) || (seq->flag & SEQ_MUTE)) {
@@ -953,7 +978,8 @@ static void draw_seq_text_overlay(View2D *v2d,
UI_view2d_text_cache_add_rectf(v2d, &rect, overlay_string, overlay_string_len, col);
}
-static void draw_sequence_extensions_overlay(Scene *scene, Sequence *seq, uint pos, float pixely)
+static void draw_sequence_extensions_overlay(
+ Scene *scene, Sequence *seq, uint pos, float pixely, const bool show_strip_color_tag)
{
float x1, x2, y1, y2;
uchar col[4], blend_col[3];
@@ -966,7 +992,7 @@ static void draw_sequence_extensions_overlay(Scene *scene, Sequence *seq, uint p
GPU_blend(GPU_BLEND_ALPHA);
- color3ubv_from_seq(scene, seq, col);
+ color3ubv_from_seq(scene, seq, show_strip_color_tag, col);
if (seq->flag & SELECT) {
UI_GetColorPtrShade3ubv(col, col, 50);
}
@@ -1036,7 +1062,8 @@ static void draw_seq_background(Scene *scene,
float x2,
float y1,
float y2,
- bool is_single_image)
+ bool is_single_image,
+ bool show_strip_color_tag)
{
uchar col[4];
GPU_blend(GPU_BLEND_ALPHA);
@@ -1049,11 +1076,11 @@ static void draw_seq_background(Scene *scene,
rgb_float_to_uchar(col, colvars->col);
}
else {
- color3ubv_from_seq(scene, seq1, col);
+ color3ubv_from_seq(scene, seq1, show_strip_color_tag, col);
}
}
else {
- color3ubv_from_seq(scene, seq, col);
+ color3ubv_from_seq(scene, seq, show_strip_color_tag, col);
}
/* Draw muted strips semi-transparent. */
@@ -1108,7 +1135,7 @@ static void draw_seq_background(Scene *scene,
rgb_float_to_uchar(col, colvars->col);
}
else {
- color3ubv_from_seq(scene, seq2, col);
+ color3ubv_from_seq(scene, seq2, show_strip_color_tag, col);
/* If the transition inputs are of the same type, draw the right side slightly darker. */
if (seq1->type == seq2->type) {
UI_GetColorPtrShade3ubv(col, col, -15);
@@ -1822,6 +1849,10 @@ static void draw_seq_strip(const bContext *C,
/* Check if we are doing "solo preview". */
bool is_single_image = (char)SEQ_transform_single_image_check(seq);
+ /* Use the seq->color_tag to display the tag color. */
+ const bool show_strip_color_tag = (sseq->timeline_overlay.flag &
+ SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG);
+
/* Draw strip body. */
x1 = (seq->startstill) ? seq->start : seq->startdisp;
y1 = seq->machine + SEQ_STRIP_OFSBOTTOM;
@@ -1852,7 +1883,7 @@ static void draw_seq_strip(const bContext *C,
uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
- draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image);
+ draw_seq_background(scene, seq, pos, x1, x2, y1, y2, is_single_image, show_strip_color_tag);
/* Draw a color band inside color strip. */
if (seq->type == SEQ_TYPE_COLOR && y_threshold) {
@@ -1864,7 +1895,7 @@ static void draw_seq_strip(const bContext *C,
if (!is_single_image && (seq->startofs || seq->endofs) && pixely > 0) {
if ((sseq->timeline_overlay.flag & SEQ_TIMELINE_SHOW_STRIP_OFFSETS) ||
(seq == special_seq_update)) {
- draw_sequence_extensions_overlay(scene, seq, pos, pixely);
+ draw_sequence_extensions_overlay(scene, seq, pos, pixely, show_strip_color_tag);
}
}
}
@@ -1875,7 +1906,7 @@ static void draw_seq_strip(const bContext *C,
if ((seq->type == SEQ_TYPE_META) ||
((seq->type == SEQ_TYPE_SCENE) && (seq->flag & SEQ_SCENE_STRIPS))) {
- drawmeta_contents(scene, seq, x1, y1, x2, y2);
+ drawmeta_contents(scene, seq, x1, y1, x2, y2, show_strip_color_tag);
}
if ((sseq->flag & SEQ_SHOW_OVERLAY) &&
@@ -2585,7 +2616,7 @@ static int sequencer_draw_get_transform_preview_frame(Scene *scene)
return preview_frame;
}
-static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq)
+static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq, bool is_active_seq)
{
SpaceSeq *sseq = CTX_wm_space_seq(C);
if ((seq->flag & SELECT) == 0) {
@@ -2628,7 +2659,12 @@ static void seq_draw_image_origin_and_outline(const bContext *C, Sequence *seq)
immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
float col[3];
- UI_GetThemeColor3fv(TH_SEQ_SELECTED, col);
+ if (is_active_seq) {
+ UI_GetThemeColor3fv(TH_SEQ_ACTIVE, col);
+ }
+ else {
+ UI_GetThemeColor3fv(TH_SEQ_SELECTED, col);
+ }
immUniformColor3fv(col);
immUniform1f("lineWidth", U.pixelsize);
immBegin(GPU_PRIM_LINE_LOOP, 4);
@@ -2719,12 +2755,15 @@ void sequencer_draw_preview(const bContext *C,
sequencer_draw_borders_overlay(sseq, v2d, scene);
}
- SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0);
- Sequence *seq;
- SEQ_ITERATOR_FOREACH (seq, collection) {
- seq_draw_image_origin_and_outline(C, seq);
+ if (!draw_backdrop && scene->ed != NULL) {
+ SeqCollection *collection = SEQ_query_rendered_strips(&scene->ed->seqbase, timeline_frame, 0);
+ Sequence *seq;
+ Sequence *active_seq = SEQ_select_active_get(scene);
+ SEQ_ITERATOR_FOREACH (seq, collection) {
+ seq_draw_image_origin_and_outline(C, seq, seq == active_seq);
+ }
+ SEQ_collection_free(collection);
}
- SEQ_collection_free(collection);
if (draw_gpencil && show_imbuf && (sseq->flag & SEQ_SHOW_OVERLAY)) {
sequencer_draw_gpencil_overlay(C);
@@ -3292,6 +3331,6 @@ void draw_timeline_seq_display(const bContext *C, ARegion *region)
UI_view2d_view_restore(C);
}
- ED_time_scrub_draw_current_frame(region, scene, !(sseq->flag & SEQ_DRAWFRAMES), true);
+ ED_time_scrub_draw_current_frame(region, scene, !(sseq->flag & SEQ_DRAWFRAMES));
UI_view2d_scrollers_draw(v2d, NULL);
}
diff --git a/source/blender/editors/space_sequencer/sequencer_edit.c b/source/blender/editors/space_sequencer/sequencer_edit.c
index 9f21fc0676c..9be947b9112 100644
--- a/source/blender/editors/space_sequencer/sequencer_edit.c
+++ b/source/blender/editors/space_sequencer/sequencer_edit.c
@@ -63,6 +63,7 @@
#include "WM_types.h"
#include "RNA_define.h"
+#include "RNA_enum_types.h"
/* For menu, popup, icons, etc. */
#include "ED_numinput.h"
@@ -869,9 +870,9 @@ static int sequencer_slip_modal(bContext *C, wmOperator *op, const wmEvent *even
void SEQUENCER_OT_slip(struct wmOperatorType *ot)
{
/* Identifiers. */
- ot->name = "Trim Strips";
+ ot->name = "Slip Strips";
ot->idname = "SEQUENCER_OT_slip";
- ot->description = "Trim the contents of the active strip";
+ ot->description = "Slip the contents of selected strips";
/* Api callbacks. */
ot->invoke = sequencer_slip_invoke;
@@ -3322,4 +3323,38 @@ void SEQUENCER_OT_strip_transform_fit(struct wmOperatorType *ot)
"Scale fit fit_method");
}
+static int sequencer_strip_color_tag_set_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ const Editing *ed = SEQ_editing_get(scene);
+ const short color_tag = RNA_enum_get(op->ptr, "color");
+
+ LISTBASE_FOREACH (Sequence *, seq, &ed->seqbase) {
+ if (seq->flag & SELECT) {
+ seq->color_tag = color_tag;
+ }
+ }
+
+ WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER, scene);
+ return OPERATOR_FINISHED;
+}
+
+void SEQUENCER_OT_strip_color_tag_set(struct wmOperatorType *ot)
+{
+ /* Identifiers. */
+ ot->name = "Set Color Tag";
+ ot->idname = "SEQUENCER_OT_strip_color_tag_set";
+ ot->description = "Set a color tag for the selected strips";
+
+ /* Api callbacks. */
+ ot->exec = sequencer_strip_color_tag_set_exec;
+ ot->poll = sequencer_edit_poll;
+
+ /* Flags. */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_enum(
+ ot->srna, "color", rna_enum_strip_color_items, SEQUENCE_COLOR_NONE, "Color Tag", "");
+}
+
/** \} */
diff --git a/source/blender/editors/space_sequencer/sequencer_intern.h b/source/blender/editors/space_sequencer/sequencer_intern.h
index 5b5c381509f..202eda85dca 100644
--- a/source/blender/editors/space_sequencer/sequencer_intern.h
+++ b/source/blender/editors/space_sequencer/sequencer_intern.h
@@ -51,7 +51,10 @@ void sequencer_draw_preview(const struct bContext *C,
int offset,
bool draw_overlay,
bool draw_backdrop);
-void color3ubv_from_seq(struct Scene *curscene, struct Sequence *seq, unsigned char col[3]);
+void color3ubv_from_seq(const struct Scene *curscene,
+ const struct Sequence *seq,
+ const bool show_strip_color_tag,
+ uchar r_col[3]);
void sequencer_special_update_set(Sequence *seq);
float sequence_handle_size_get_clamped(struct Sequence *seq, const float pixelx);
@@ -148,6 +151,8 @@ void SEQUENCER_OT_set_range_to_strips(struct wmOperatorType *ot);
void SEQUENCER_OT_strip_transform_clear(struct wmOperatorType *ot);
void SEQUENCER_OT_strip_transform_fit(struct wmOperatorType *ot);
+void SEQUENCER_OT_strip_color_tag_set(struct wmOperatorType *ot);
+
/* sequencer_select.c */
void SEQUENCER_OT_select_all(struct wmOperatorType *ot);
void SEQUENCER_OT_select(struct wmOperatorType *ot);
diff --git a/source/blender/editors/space_sequencer/sequencer_ops.c b/source/blender/editors/space_sequencer/sequencer_ops.c
index 48e6cfcdcd0..95f7b44264c 100644
--- a/source/blender/editors/space_sequencer/sequencer_ops.c
+++ b/source/blender/editors/space_sequencer/sequencer_ops.c
@@ -79,6 +79,8 @@ void sequencer_operatortypes(void)
WM_operatortype_append(SEQUENCER_OT_strip_transform_clear);
WM_operatortype_append(SEQUENCER_OT_strip_transform_fit);
+ WM_operatortype_append(SEQUENCER_OT_strip_color_tag_set);
+
/* sequencer_select.c */
WM_operatortype_append(SEQUENCER_OT_select_all);
WM_operatortype_append(SEQUENCER_OT_select);
diff --git a/source/blender/editors/space_sequencer/space_sequencer.c b/source/blender/editors/space_sequencer/space_sequencer.c
index 99b75f82922..ad0ceb82709 100644
--- a/source/blender/editors/space_sequencer/space_sequencer.c
+++ b/source/blender/editors/space_sequencer/space_sequencer.c
@@ -104,25 +104,25 @@ static SpaceLink *sequencer_create(const ScrArea *UNUSED(area), const Scene *sce
sseq->preview_overlay.flag = SEQ_PREVIEW_SHOW_GPENCIL | SEQ_PREVIEW_SHOW_OUTLINE_SELECTED;
sseq->timeline_overlay.flag = SEQ_TIMELINE_SHOW_STRIP_NAME | SEQ_TIMELINE_SHOW_STRIP_SOURCE |
SEQ_TIMELINE_SHOW_STRIP_DURATION | SEQ_TIMELINE_SHOW_GRID |
- SEQ_TIMELINE_SHOW_FCURVES;
+ SEQ_TIMELINE_SHOW_FCURVES | SEQ_TIMELINE_SHOW_STRIP_COLOR_TAG;
BLI_rctf_init(&sseq->runtime.last_thumbnail_area, 0.0f, 0.0f, 0.0f, 0.0f);
sseq->runtime.last_displayed_thumbnails = NULL;
- /* Tool header. */
- region = MEM_callocN(sizeof(ARegion), "tool header for sequencer");
+ /* Header. */
+ region = MEM_callocN(sizeof(ARegion), "header for sequencer");
BLI_addtail(&sseq->regionbase, region);
- region->regiontype = RGN_TYPE_TOOL_HEADER;
+ region->regiontype = RGN_TYPE_HEADER;
region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
- region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER;
- /* Header. */
- region = MEM_callocN(sizeof(ARegion), "header for sequencer");
+ /* Tool header. */
+ region = MEM_callocN(sizeof(ARegion), "tool header for sequencer");
BLI_addtail(&sseq->regionbase, region);
- region->regiontype = RGN_TYPE_HEADER;
+ region->regiontype = RGN_TYPE_TOOL_HEADER;
region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
+ region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER;
/* Buttons/list view. */
region = MEM_callocN(sizeof(ARegion), "buttons for sequencer");
diff --git a/source/blender/editors/space_text/text_draw.c b/source/blender/editors/space_text/text_draw.c
index 99fcb2092c3..b541b65d676 100644
--- a/source/blender/editors/space_text/text_draw.c
+++ b/source/blender/editors/space_text/text_draw.c
@@ -48,6 +48,9 @@
#include "text_format.h"
#include "text_intern.h"
+#include "WM_api.h"
+#include "WM_types.h"
+
/******************** text font drawing ******************/
typedef struct TextDrawContext {
@@ -1734,6 +1737,23 @@ void text_update_character_width(SpaceText *st)
text_font_end(&tdc);
}
+bool ED_text_activate_in_screen(bContext *C, Text *text)
+{
+ ScrArea *area = BKE_screen_find_big_area(CTX_wm_screen(C), SPACE_TEXT, 0);
+ if (area) {
+ SpaceText *st = area->spacedata.first;
+ ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
+ st->text = text;
+ if (region) {
+ ED_text_scroll_to_cursor(st, region, true);
+ }
+ WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
+ return true;
+ }
+
+ return false;
+}
+
/* Moves the view to the cursor location,
* also used to make sure the view isn't outside the file */
void ED_text_scroll_to_cursor(SpaceText *st, ARegion *region, const bool center)
diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c
index cbc70fc7497..bedc24a6287 100644
--- a/source/blender/editors/space_view3d/space_view3d.c
+++ b/source/blender/editors/space_view3d/space_view3d.c
@@ -276,20 +276,20 @@ static SpaceLink *view3d_create(const ScrArea *UNUSED(area), const Scene *scene)
v3d->camera = scene->camera;
}
- /* tool header */
- region = MEM_callocN(sizeof(ARegion), "tool header for view3d");
+ /* header */
+ region = MEM_callocN(sizeof(ARegion), "header for view3d");
BLI_addtail(&v3d->regionbase, region);
- region->regiontype = RGN_TYPE_TOOL_HEADER;
+ region->regiontype = RGN_TYPE_HEADER;
region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
- region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER;
- /* header */
- region = MEM_callocN(sizeof(ARegion), "header for view3d");
+ /* tool header */
+ region = MEM_callocN(sizeof(ARegion), "tool header for view3d");
BLI_addtail(&v3d->regionbase, region);
- region->regiontype = RGN_TYPE_HEADER;
+ region->regiontype = RGN_TYPE_TOOL_HEADER;
region->alignment = (U.uiflag & USER_HEADER_BOTTOM) ? RGN_ALIGN_BOTTOM : RGN_ALIGN_TOP;
+ region->flag = RGN_FLAG_HIDDEN | RGN_FLAG_HIDDEN_BY_USER;
/* tool shelf */
region = MEM_callocN(sizeof(ARegion), "toolshelf for view3d");
@@ -539,6 +539,11 @@ static char *view3d_mat_drop_tooltip(bContext *C,
return ED_object_ot_drop_named_material_tooltip(C, drop->ptr, event);
}
+static bool view3d_world_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
+{
+ return view3d_drop_id_in_main_region_poll(C, drag, event, ID_WO);
+}
+
static bool view3d_object_data_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event)
{
ID_Type id_type = view3d_drop_id_in_main_region_poll_get_id_type(C, drag, event);
@@ -732,6 +737,12 @@ static void view3d_dropboxes(void)
view3d_id_drop_copy_with_type,
WM_drag_free_imported_drag_ID,
view3d_object_data_drop_tooltip);
+ WM_dropbox_add(lb,
+ "VIEW3D_OT_drop_world",
+ view3d_world_drop_poll,
+ view3d_id_drop_copy,
+ WM_drag_free_imported_drag_ID,
+ NULL);
}
static void view3d_widgets(void)
@@ -1555,11 +1566,16 @@ static void space_view3d_listener(const wmSpaceTypeListenerParams *params)
switch (wmn->category) {
case NC_SCENE:
switch (wmn->data) {
- case ND_WORLD:
- if (v3d->flag2 & V3D_HIDE_OVERLAYS) {
+ case ND_WORLD: {
+ const bool use_scene_world = ((v3d->shading.type == OB_MATERIAL) &&
+ (v3d->shading.flag & V3D_SHADING_SCENE_WORLD)) ||
+ ((v3d->shading.type == OB_RENDER) &&
+ (v3d->shading.flag & V3D_SHADING_SCENE_WORLD_RENDER));
+ if (v3d->flag2 & V3D_HIDE_OVERLAYS || use_scene_world) {
ED_area_tag_redraw_regiontype(area, RGN_TYPE_WINDOW);
}
break;
+ }
}
break;
case NC_WORLD:
diff --git a/source/blender/editors/space_view3d/view3d_edit.c b/source/blender/editors/space_view3d/view3d_edit.c
index 8ed134c7fd1..15ccf5891d4 100644
--- a/source/blender/editors/space_view3d/view3d_edit.c
+++ b/source/blender/editors/space_view3d/view3d_edit.c
@@ -34,10 +34,12 @@
#include "DNA_gpencil_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
+#include "DNA_world_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
+#include "BLI_dial_2d.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
@@ -210,6 +212,9 @@ typedef struct ViewOpsData {
* If we want the value before running the operator, add a separate member.
*/
char persp;
+
+ /** Used for roll */
+ Dial *dial;
} init;
/** Previous state (previous modal event handled). */
@@ -577,6 +582,10 @@ static void viewops_data_free(bContext *C, wmOperator *op)
WM_event_remove_timer(CTX_wm_manager(C), vod->timer->win, vod->timer);
}
+ if (vod->init.dial) {
+ MEM_freeN(vod->init.dial);
+ }
+
MEM_freeN(vod);
op->customdata = NULL;
}
@@ -4352,18 +4361,9 @@ static void view_roll_angle(
rv3d->view = RV3D_VIEW_USER;
}
-static void viewroll_apply(ViewOpsData *vod, int x, int UNUSED(y))
+static void viewroll_apply(ViewOpsData *vod, int x, int y)
{
- float angle = 0.0;
-
- {
- float len1, len2, tot;
-
- tot = vod->region->winrct.xmax - vod->region->winrct.xmin;
- len1 = (vod->region->winrct.xmax - x) / tot;
- len2 = (vod->region->winrct.xmax - vod->init.event_xy[0]) / tot;
- angle = (len1 - len2) * (float)M_PI * 4.0f;
- }
+ float angle = BLI_dial_angle(vod->init.dial, (const float[2]){x, y});
if (angle != 0.0f) {
view_roll_angle(vod->region, vod->rv3d->viewquat, vod->init.quat, vod->init.mousevec, angle);
@@ -4409,6 +4409,13 @@ static int viewroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
break;
}
}
+ else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
+ /* Note this does not remove auto-keys on locked cameras. */
+ copy_qt_qt(vod->rv3d->viewquat, vod->init.quat);
+ ED_view3d_camera_lock_sync(vod->depsgraph, vod->v3d, vod->rv3d);
+ viewops_data_free(C, op);
+ return OPERATOR_CANCELLED;
+ }
else if (event->type == vod->init.event_type && event->val == KM_RELEASE) {
event_code = VIEW_CONFIRM;
}
@@ -4517,6 +4524,9 @@ static int viewroll_invoke(bContext *C, wmOperator *op, const wmEvent *event)
viewops_data_alloc(C, op);
viewops_data_create(C, op, event, viewops_flag_from_prefs());
vod = op->customdata;
+ vod->init.dial = BLI_dial_init((const float[2]){BLI_rcti_cent_x(&vod->region->winrct),
+ BLI_rcti_cent_y(&vod->region->winrct)},
+ FLT_EPSILON);
ED_view3d_smooth_view_force_finish(C, vod->v3d, vod->region);
@@ -4865,6 +4875,59 @@ void VIEW3D_OT_background_image_remove(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name Drop World Operator
+ * \{ */
+
+static int drop_world_exec(bContext *C, wmOperator *op)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+
+ char name[MAX_ID_NAME - 2];
+
+ RNA_string_get(op->ptr, "name", name);
+ World *world = (World *)BKE_libblock_find_name(bmain, ID_WO, name);
+ if (world == NULL) {
+ return OPERATOR_CANCELLED;
+ }
+
+ id_us_plus(&world->id);
+ scene->world = world;
+
+ DEG_id_tag_update(&scene->id, 0);
+ DEG_relations_tag_update(bmain);
+
+ WM_event_add_notifier(C, NC_SCENE | ND_WORLD, scene);
+
+ return OPERATOR_FINISHED;
+}
+
+static bool drop_world_poll(bContext *C)
+{
+ return ED_operator_scene_editable(C);
+}
+
+void VIEW3D_OT_drop_world(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Drop World";
+ ot->description = "Drop a world into the scene";
+ ot->idname = "VIEW3D_OT_drop_world";
+
+ /* api callbacks */
+ ot->exec = drop_world_exec;
+ ot->poll = drop_world_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL;
+
+ /* properties */
+ RNA_def_string(ot->srna, "name", "World", MAX_ID_NAME - 2, "Name", "World to assign");
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name View Clipping Planes Operator
*
* Draw border or toggle off.
diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h
index ab80928e0c1..a21fc006b02 100644
--- a/source/blender/editors/space_view3d/view3d_intern.h
+++ b/source/blender/editors/space_view3d/view3d_intern.h
@@ -80,6 +80,7 @@ void VIEW3D_OT_view_persportho(struct wmOperatorType *ot);
void VIEW3D_OT_navigate(struct wmOperatorType *ot);
void VIEW3D_OT_background_image_add(struct wmOperatorType *ot);
void VIEW3D_OT_background_image_remove(struct wmOperatorType *ot);
+void VIEW3D_OT_drop_world(struct wmOperatorType *ot);
void VIEW3D_OT_view_orbit(struct wmOperatorType *ot);
void VIEW3D_OT_view_roll(struct wmOperatorType *ot);
void VIEW3D_OT_clip_border(struct wmOperatorType *ot);
diff --git a/source/blender/editors/space_view3d/view3d_ops.c b/source/blender/editors/space_view3d/view3d_ops.c
index 56dedbbdbb2..eb8c043319c 100644
--- a/source/blender/editors/space_view3d/view3d_ops.c
+++ b/source/blender/editors/space_view3d/view3d_ops.c
@@ -169,6 +169,7 @@ void view3d_operatortypes(void)
WM_operatortype_append(VIEW3D_OT_view_persportho);
WM_operatortype_append(VIEW3D_OT_background_image_add);
WM_operatortype_append(VIEW3D_OT_background_image_remove);
+ WM_operatortype_append(VIEW3D_OT_drop_world);
WM_operatortype_append(VIEW3D_OT_view_selected);
WM_operatortype_append(VIEW3D_OT_view_lock_clear);
WM_operatortype_append(VIEW3D_OT_view_lock_to_active);
diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c
index bca4f8b4857..ac5a06d22bb 100644
--- a/source/blender/editors/space_view3d/view3d_select.c
+++ b/source/blender/editors/space_view3d/view3d_select.c
@@ -2082,7 +2082,7 @@ static int mixed_bones_object_selectbuffer_extended(ViewContext *vc,
/**
* \param has_bones: When true, skip non-bone hits, also allow bases to be used
* that are visible but not select-able,
- * since you may be in pose mode with an unselect-able object.
+ * since you may be in pose mode with an un-selectable object.
*
* \return the active base or NULL.
*/
diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c
index e58e524e341..6ed2c28a7eb 100644
--- a/source/blender/editors/transform/transform.c
+++ b/source/blender/editors/transform/transform.c
@@ -28,6 +28,7 @@
#include "DNA_gpencil_types.h"
#include "DNA_mask_types.h"
#include "DNA_mesh_types.h"
+#include "DNA_screen_types.h"
#include "BLI_math.h"
#include "BLI_rect.h"
@@ -1609,8 +1610,16 @@ static void initSnapSpatial(TransInfo *t, float r_snap[2])
}
}
else if (t->spacetype == SPACE_IMAGE) {
- r_snap[0] = 0.0625f;
- r_snap[1] = 0.03125f;
+ SpaceImage *sima = t->area->spacedata.first;
+ View2D *v2d = &t->region->v2d;
+ int grid_size = SI_GRID_STEPS_LEN;
+ float zoom_factor = ED_space_image_zoom_level(v2d, grid_size);
+ float grid_steps[SI_GRID_STEPS_LEN];
+
+ ED_space_image_grid_steps(sima, grid_steps, grid_size);
+ /* Snapping value based on what type of grid is used (adaptive-subdividing or custom-grid). */
+ r_snap[0] = ED_space_image_increment_snap_value(grid_size, grid_steps, zoom_factor);
+ r_snap[1] = r_snap[0] / 2.0f;
}
else if (t->spacetype == SPACE_CLIP) {
r_snap[0] = 0.125f;
diff --git a/source/blender/editors/transform/transform_convert_armature.c b/source/blender/editors/transform/transform_convert_armature.c
index d3e0f55b127..d19ff123037 100644
--- a/source/blender/editors/transform/transform_convert_armature.c
+++ b/source/blender/editors/transform/transform_convert_armature.c
@@ -1496,8 +1496,10 @@ static void bone_children_clear_transflag(int mode, short around, ListBase *lb)
}
}
-/* Sets transform flags in the bones.
- * Returns total number of bones with `BONE_TRANSFORM`. */
+/**
+ * Sets transform flags in the bones.
+ * Returns total number of bones with #BONE_TRANSFORM.
+ */
int transform_convert_pose_transflags_update(Object *ob,
const int mode,
const short around,
@@ -1730,7 +1732,7 @@ void special_aftertrans_update__pose(bContext *C, TransInfo *t)
BKE_pose_where_is(t->depsgraph, t->scene, pose_ob);
}
- /* set BONE_TRANSFORM flags for autokey, gizmo draw might have changed them */
+ /* Set BONE_TRANSFORM flags for auto-key, gizmo draw might have changed them. */
if (!canceled && (t->mode != TFM_DUMMY)) {
transform_convert_pose_transflags_update(ob, t->mode, t->around, NULL);
}
diff --git a/source/blender/editors/transform/transform_convert_nla.c b/source/blender/editors/transform/transform_convert_nla.c
index 7e5b80c2453..acef8a666e3 100644
--- a/source/blender/editors/transform/transform_convert_nla.c
+++ b/source/blender/editors/transform/transform_convert_nla.c
@@ -208,30 +208,18 @@ void createTransNlaData(bContext *C, TransInfo *t)
/* just set tdn to assume that it only has one handle for now */
tdn->handle = -1;
- /* now, link the transform data up to this data */
- if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) {
- td->loc = tdn->h1;
- copy_v3_v3(td->iloc, tdn->h1);
+ /* Now, link the transform data up to this data. */
+ td->loc = tdn->h1;
+ copy_v3_v3(td->iloc, tdn->h1);
- /* store all the other gunk that is required by transform */
+ if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) {
+ /* Store all the other gunk that is required by transform. */
copy_v3_v3(td->center, center);
- memset(td->axismtx, 0, sizeof(td->axismtx));
td->axismtx[2][2] = 1.0f;
-
- td->ext = NULL;
- td->val = NULL;
-
td->flag |= TD_SELECTED;
- td->dist = 0.0f;
-
unit_m3(td->mtx);
unit_m3(td->smtx);
}
- else {
- /* time scaling only needs single value */
- td->val = &tdn->h1[0];
- td->ival = tdn->h1[0];
- }
td->extra = tdn;
td++;
@@ -241,30 +229,18 @@ void createTransNlaData(bContext *C, TransInfo *t)
* then we're doing both, otherwise, only end */
tdn->handle = (tdn->handle) ? 2 : 1;
- /* now, link the transform data up to this data */
- if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) {
- td->loc = tdn->h2;
- copy_v3_v3(td->iloc, tdn->h2);
+ /* Now, link the transform data up to this data. */
+ td->loc = tdn->h2;
+ copy_v3_v3(td->iloc, tdn->h2);
- /* store all the other gunk that is required by transform */
+ if (ELEM(t->mode, TFM_TRANSLATION, TFM_TIME_EXTEND)) {
+ /* Store all the other gunk that is required by transform. */
copy_v3_v3(td->center, center);
- memset(td->axismtx, 0, sizeof(td->axismtx));
td->axismtx[2][2] = 1.0f;
-
- td->ext = NULL;
- td->val = NULL;
-
td->flag |= TD_SELECTED;
- td->dist = 0.0f;
-
unit_m3(td->mtx);
unit_m3(td->smtx);
}
- else {
- /* time scaling only needs single value */
- td->val = &tdn->h2[0];
- td->ival = tdn->h2[0];
- }
td->extra = tdn;
td++;
diff --git a/source/blender/editors/transform/transform_convert_object.c b/source/blender/editors/transform/transform_convert_object.c
index ad22b0fc444..09aa4314b32 100644
--- a/source/blender/editors/transform/transform_convert_object.c
+++ b/source/blender/editors/transform/transform_convert_object.c
@@ -958,25 +958,25 @@ void special_aftertrans_update__object(bContext *C, TransInfo *t)
}
BLI_freelistN(&pidlist);
- /* pointcache refresh */
+ /* Point-cache refresh. */
if (BKE_ptcache_object_reset(t->scene, ob, PTCACHE_RESET_OUTDATED)) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
- /* Needed for proper updating of "quick cached" dynamics. */
- /* Creates troubles for moving animated objects without */
- /* autokey though, probably needed is an anim sys override? */
- /* Please remove if some other solution is found. -jahka */
+ /* Needed for proper updating of "quick cached" dynamics.
+ * Creates troubles for moving animated objects without
+ * auto-key though, probably needed is an animation-system override?
+ * NOTE(@jahka): Please remove if some other solution is found. */
DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM);
- /* Set autokey if necessary */
+ /* Set auto-key if necessary. */
if (!canceled) {
ED_transform_autokeyframe_object(C, t->scene, t->view_layer, ob, t->mode);
}
motionpath_update |= ED_transform_motionpath_need_update_object(t->scene, ob);
- /* restore rigid body transform */
+ /* Restore rigid body transform. */
if (ob->rigidbody_object && canceled) {
float ctime = BKE_scene_ctime_get(t->scene);
if (BKE_rigidbody_check_sim_running(t->scene->rigidbody_world, ctime)) {
diff --git a/source/blender/editors/transform/transform_convert_sequencer_image.c b/source/blender/editors/transform/transform_convert_sequencer_image.c
index 5db9a2e092f..6e3f12de472 100644
--- a/source/blender/editors/transform/transform_convert_sequencer_image.c
+++ b/source/blender/editors/transform/transform_convert_sequencer_image.c
@@ -113,12 +113,17 @@ static void freeSeqData(TransInfo *UNUSED(t),
void createTransSeqImageData(TransInfo *t)
{
Editing *ed = SEQ_editing_get(t->scene);
+
+ if (ed == NULL) {
+ return;
+ }
+
ListBase *seqbase = SEQ_active_seqbase_get(ed);
SeqCollection *strips = SEQ_query_rendered_strips(seqbase, t->scene->r.cfra, 0);
SEQ_filter_selected_strips(strips);
const int count = SEQ_collection_len(strips);
- if (ed == NULL || count == 0) {
+ if (count == 0) {
SEQ_collection_free(strips);
return;
}
diff --git a/source/blender/editors/transform/transform_gizmo_2d.c b/source/blender/editors/transform/transform_gizmo_2d.c
index 0d66db0d7e1..4f6556cd2a2 100644
--- a/source/blender/editors/transform/transform_gizmo_2d.c
+++ b/source/blender/editors/transform/transform_gizmo_2d.c
@@ -32,6 +32,7 @@
#include "DNA_view3d_types.h"
#include "BKE_context.h"
+#include "BKE_global.h"
#include "BKE_layer.h"
#include "RNA_access.h"
@@ -70,6 +71,10 @@ static bool gizmo2d_generic_poll(const bContext *C, wmGizmoGroupType *gzgt)
return false;
}
+ if (G.moving) {
+ return false;
+ }
+
ScrArea *area = CTX_wm_area(C);
switch (area->spacetype) {
case SPACE_IMAGE: {
diff --git a/source/blender/editors/transform/transform_mode_timescale.c b/source/blender/editors/transform/transform_mode_timescale.c
index 50fd714727b..0a7ae54982e 100644
--- a/source/blender/editors/transform/transform_mode_timescale.c
+++ b/source/blender/editors/transform/transform_mode_timescale.c
@@ -87,7 +87,7 @@ static void applyTimeScaleValue(TransInfo *t, float value)
}
/* now, calculate the new value */
- *(td->val) = ((td->ival - startx) * fac) + startx;
+ td->loc[0] = ((td->iloc[0] - startx) * fac) + startx;
}
}
}
diff --git a/source/blender/editors/transform/transform_snap.c b/source/blender/editors/transform/transform_snap.c
index 05a20a14477..39a70f5477e 100644
--- a/source/blender/editors/transform/transform_snap.c
+++ b/source/blender/editors/transform/transform_snap.c
@@ -590,6 +590,11 @@ static void initSnappingMode(TransInfo *t)
t->tsnap.project = 0;
t->tsnap.mode = ts->snap_uv_mode;
+ if ((t->tsnap.mode & SCE_SNAP_MODE_INCREMENT) && (ts->snap_uv_flag & SCE_SNAP_ABS_GRID) &&
+ (t->mode == TFM_TRANSLATION)) {
+ t->tsnap.mode &= ~SCE_SNAP_MODE_INCREMENT;
+ t->tsnap.mode |= SCE_SNAP_MODE_GRID;
+ }
}
else if (t->spacetype == SPACE_SEQ) {
t->tsnap.mode = SEQ_tool_settings_snap_mode_get(t->scene);
@@ -1502,7 +1507,8 @@ bool transform_snap_grid(TransInfo *t, float *val)
return false;
}
- if (t->spacetype != SPACE_VIEW3D) {
+ /* Don't do grid snapping if not in 3D viewport or UV editor */
+ if (!ELEM(t->spacetype, SPACE_VIEW3D, SPACE_IMAGE)) {
return false;
}
diff --git a/source/blender/editors/transform/transform_snap_object.c b/source/blender/editors/transform/transform_snap_object.c
index 891919fd46c..70297fad4ff 100644
--- a/source/blender/editors/transform/transform_snap_object.c
+++ b/source/blender/editors/transform/transform_snap_object.c
@@ -734,7 +734,7 @@ static bool raycastMesh(SnapObjectContext *sctx,
}
/* Test BoundBox */
- BoundBox *bb = BKE_mesh_boundbox_get(ob_eval);
+ BoundBox *bb = BKE_object_boundbox_get(ob_eval);
if (bb) {
/* was BKE_boundbox_ray_hit_check, see: cf6ca226fa58 */
if (!isect_ray_aabb_v3_simple(
diff --git a/source/blender/editors/util/CMakeLists.txt b/source/blender/editors/util/CMakeLists.txt
index b396e348845..b339bfbdc47 100644
--- a/source/blender/editors/util/CMakeLists.txt
+++ b/source/blender/editors/util/CMakeLists.txt
@@ -103,8 +103,10 @@ set(SRC
../include/ED_view3d_offscreen.h
../include/UI_icons.h
../include/UI_interface.h
+ ../include/UI_interface.hh
../include/UI_interface_icons.h
../include/UI_resources.h
+ ../include/UI_tree_view.hh
../include/UI_view2d.h
)
diff --git a/source/blender/editors/uvedit/uvedit_islands.c b/source/blender/editors/uvedit/uvedit_islands.c
index 56bcbc63de1..6159758dbcd 100644
--- a/source/blender/editors/uvedit/uvedit_islands.c
+++ b/source/blender/editors/uvedit/uvedit_islands.c
@@ -29,6 +29,7 @@
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
+#include "DNA_space_types.h"
#include "BLI_boxpack_2d.h"
#include "BLI_convexhull_2d.h"
@@ -38,6 +39,7 @@
#include "BKE_customdata.h"
#include "BKE_editmesh.h"
+#include "BKE_image.h"
#include "DEG_depsgraph.h"
@@ -232,6 +234,101 @@ static void bm_face_array_uv_scale_y(BMFace **faces,
/** \} */
/* -------------------------------------------------------------------- */
+/** \name UDIM packing helper functions
+ * \{ */
+
+/**
+ * Returns true if UV coordinates lie on a valid tile in UDIM grid or tiled image.
+ */
+bool uv_coords_isect_udim(const Image *image, const int udim_grid[2], const float coords[2])
+{
+ const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
+ const bool is_tiled_image = image && (image->source == IMA_SRC_TILED);
+
+ if (coords[0] < udim_grid[0] && coords[0] > 0 && coords[1] < udim_grid[1] && coords[1] > 0) {
+ return true;
+ }
+ /* Check if selection lies on a valid UDIM image tile. */
+ if (is_tiled_image) {
+ LISTBASE_FOREACH (const ImageTile *, tile, &image->tiles) {
+ const int tile_index = tile->tile_number - 1001;
+ const int target_x = (tile_index % 10);
+ const int target_y = (tile_index / 10);
+ if (coords_floor[0] == target_x && coords_floor[1] == target_y) {
+ return true;
+ }
+ }
+ }
+ /* Probably not required since UDIM grid checks for 1001. */
+ else if (image && !is_tiled_image) {
+ if (is_zero_v2(coords_floor)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Calculates distance to nearest UDIM image tile in UV space and its UDIM tile number.
+ */
+static float uv_nearest_image_tile_distance(const Image *image,
+ float coords[2],
+ float nearest_tile_co[2])
+{
+ int nearest_image_tile_index = BKE_image_find_nearest_tile(image, coords);
+ if (nearest_image_tile_index == -1) {
+ nearest_image_tile_index = 1001;
+ }
+
+ nearest_tile_co[0] = (nearest_image_tile_index - 1001) % 10;
+ nearest_tile_co[1] = (nearest_image_tile_index - 1001) / 10;
+ /* Add 0.5 to get tile center coordinates. */
+ float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
+ add_v2_fl(nearest_tile_center_co, 0.5f);
+
+ return len_squared_v2v2(coords, nearest_tile_center_co);
+}
+
+/**
+ * Calculates distance to nearest UDIM grid tile in UV space and its UDIM tile number.
+ */
+static float uv_nearest_grid_tile_distance(const int udim_grid[2],
+ float coords[2],
+ float nearest_tile_co[2])
+{
+ const float coords_floor[2] = {floorf(coords[0]), floorf(coords[1])};
+
+ if (coords[0] > udim_grid[0]) {
+ nearest_tile_co[0] = udim_grid[0] - 1;
+ }
+ else if (coords[0] < 0) {
+ nearest_tile_co[0] = 0;
+ }
+ else {
+ nearest_tile_co[0] = coords_floor[0];
+ }
+
+ if (coords[1] > udim_grid[1]) {
+ nearest_tile_co[1] = udim_grid[1] - 1;
+ }
+ else if (coords[1] < 0) {
+ nearest_tile_co[1] = 0;
+ }
+ else {
+ nearest_tile_co[1] = coords_floor[1];
+ }
+
+ /* Add 0.5 to get tile center coordinates. */
+ float nearest_tile_center_co[2] = {nearest_tile_co[0], nearest_tile_co[1]};
+ add_v2_fl(nearest_tile_center_co, 0.5f);
+
+ return len_squared_v2v2(coords, nearest_tile_center_co);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Calculate UV Islands
*
* \note Currently this is a private API/type, it could be made public.
@@ -359,6 +456,7 @@ static int bm_mesh_calc_uv_islands(const Scene *scene,
void ED_uvedit_pack_islands_multi(const Scene *scene,
Object **objects,
const uint objects_len,
+ const struct UVMapUDIM_Params *udim_params,
const struct UVPackIsland_Params *params)
{
/* Align to the Y axis, could make this configurable. */
@@ -407,8 +505,27 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
BoxPack *boxarray = MEM_mallocN(sizeof(*boxarray) * island_list_len, __func__);
int index;
+ /* Coordinates of bounding box containing all selected UVs. */
+ float selection_min_co[2], selection_max_co[2];
+ INIT_MINMAX2(selection_min_co, selection_max_co);
+
LISTBASE_FOREACH_INDEX (struct FaceIsland *, island, &island_list, index) {
+ /* Skip calculation if using specified UDIM option. */
+ if (udim_params && (udim_params->use_target_udim == false)) {
+ float bounds_min[2], bounds_max[2];
+ INIT_MINMAX2(bounds_min, bounds_max);
+ for (int i = 0; i < island->faces_len; i++) {
+ BMFace *f = island->faces[i];
+ BM_face_uv_minmax(f, bounds_min, bounds_max, island->cd_loop_uv_offset);
+ }
+
+ selection_min_co[0] = MIN2(bounds_min[0], selection_min_co[0]);
+ selection_min_co[1] = MIN2(bounds_min[1], selection_min_co[1]);
+ selection_max_co[0] = MAX2(bounds_max[0], selection_max_co[0]);
+ selection_max_co[1] = MAX2(bounds_max[1], selection_max_co[1]);
+ }
+
if (params->rotate) {
if (island->aspect_y != 1.0f) {
bm_face_array_uv_scale_y(
@@ -441,6 +558,13 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
}
}
+ /* Center of bounding box containing all selected UVs. */
+ float selection_center[2];
+ if (udim_params && (udim_params->use_target_udim == false)) {
+ selection_center[0] = (selection_min_co[0] + selection_max_co[0]) / 2.0f;
+ selection_center[1] = (selection_min_co[1] + selection_max_co[1]) / 2.0f;
+ }
+
if (margin > 0.0f) {
/* Logic matches behavior from #param_pack,
* use area so multiply the margin by the area to give
@@ -464,6 +588,53 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
const float scale[2] = {1.0f / boxarray_size[0], 1.0f / boxarray_size[1]};
+ /* Tile offset. */
+ float base_offset[2] = {0.0f, 0.0f};
+
+ /* CASE: ignore UDIM. */
+ if (udim_params == NULL) {
+ /* pass */
+ }
+ /* CASE: Active/specified(smart uv project) UDIM. */
+ else if (udim_params->use_target_udim) {
+
+ /* Calculate offset based on specified_tile_index. */
+ base_offset[0] = (udim_params->target_udim - 1001) % 10;
+ base_offset[1] = (udim_params->target_udim - 1001) / 10;
+ }
+
+ /* CASE: Closest UDIM. */
+ else {
+ const Image *image = udim_params->image;
+ const int *udim_grid = udim_params->grid_shape;
+ /* Check if selection lies on a valid UDIM grid tile. */
+ bool is_valid_udim = uv_coords_isect_udim(image, udim_grid, selection_center);
+ if (is_valid_udim) {
+ base_offset[0] = floorf(selection_center[0]);
+ base_offset[1] = floorf(selection_center[1]);
+ }
+ /* If selection doesn't lie on any UDIM then find the closest UDIM grid or image tile. */
+ else {
+ float nearest_image_tile_co[2] = {FLT_MAX, FLT_MAX};
+ float nearest_image_tile_dist = FLT_MAX, nearest_grid_tile_dist = FLT_MAX;
+ if (image) {
+ nearest_image_tile_dist = uv_nearest_image_tile_distance(
+ image, selection_center, nearest_image_tile_co);
+ }
+
+ float nearest_grid_tile_co[2] = {0.0f, 0.0f};
+ nearest_grid_tile_dist = uv_nearest_grid_tile_distance(
+ udim_grid, selection_center, nearest_grid_tile_co);
+
+ base_offset[0] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
+ nearest_image_tile_co[0] :
+ nearest_grid_tile_co[0];
+ base_offset[1] = (nearest_image_tile_dist < nearest_grid_tile_dist) ?
+ nearest_image_tile_co[1] :
+ nearest_grid_tile_co[1];
+ }
+ }
+
for (int i = 0; i < island_list_len; i++) {
struct FaceIsland *island = island_array[boxarray[i].index];
const float pivot[2] = {
@@ -471,8 +642,8 @@ void ED_uvedit_pack_islands_multi(const Scene *scene,
island->bounds_rect.ymin,
};
const float offset[2] = {
- (boxarray[i].x * scale[0]) - island->bounds_rect.xmin,
- (boxarray[i].y * scale[1]) - island->bounds_rect.ymin,
+ ((boxarray[i].x * scale[0]) - island->bounds_rect.xmin) + base_offset[0],
+ ((boxarray[i].y * scale[1]) - island->bounds_rect.ymin) + base_offset[1],
};
for (int j = 0; j < island->faces_len; j++) {
BMFace *efa = island->faces[j];
diff --git a/source/blender/editors/uvedit/uvedit_unwrap_ops.c b/source/blender/editors/uvedit/uvedit_unwrap_ops.c
index 3d5dabda23d..38233b55d15 100644
--- a/source/blender/editors/uvedit/uvedit_unwrap_ops.c
+++ b/source/blender/editors/uvedit/uvedit_unwrap_ops.c
@@ -37,6 +37,7 @@
#include "BLI_alloca.h"
#include "BLI_array.h"
#include "BLI_linklist.h"
+#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_memarena.h"
#include "BLI_string.h"
@@ -64,6 +65,7 @@
#include "PIL_time.h"
#include "UI_interface.h"
+#include "UI_view2d.h"
#include "ED_image.h"
#include "ED_mesh.h"
@@ -143,6 +145,61 @@ static bool ED_uvedit_ensure_uvs(Object *obedit)
/** \} */
/* -------------------------------------------------------------------- */
+/** \name UDIM Access
+ * \{ */
+
+bool ED_uvedit_udim_params_from_image_space(const SpaceImage *sima,
+ bool use_active,
+ struct UVMapUDIM_Params *udim_params)
+{
+ memset(udim_params, 0, sizeof(*udim_params));
+
+ udim_params->grid_shape[0] = 1;
+ udim_params->grid_shape[1] = 1;
+ udim_params->target_udim = 0;
+ udim_params->use_target_udim = false;
+
+ if (sima == NULL) {
+ return false;
+ }
+
+ udim_params->image = sima->image;
+ udim_params->grid_shape[0] = sima->tile_grid_shape[0];
+ udim_params->grid_shape[1] = sima->tile_grid_shape[1];
+
+ if (use_active) {
+ int active_udim = 1001;
+ /* NOTE: Presently, when UDIM grid and tiled image are present together, only active tile for
+ * the tiled image is considered. */
+ Image *image = sima->image;
+ if (image && image->source == IMA_SRC_TILED) {
+ ImageTile *active_tile = BLI_findlink(&image->tiles, image->active_tile_index);
+ if (active_tile) {
+ active_udim = active_tile->tile_number;
+ }
+ }
+ else {
+ /* TODO: Support storing an active UDIM when there are no tiles present.
+ * Until then, use 2D cursor to find the active tile index for the UDIM grid. */
+ const float cursor_loc[2] = {sima->cursor[0], sima->cursor[1]};
+ if (uv_coords_isect_udim(sima->image, sima->tile_grid_shape, cursor_loc)) {
+ int tile_number = 1001;
+ tile_number += floorf(cursor_loc[1]) * 10;
+ tile_number += floorf(cursor_loc[0]);
+ active_udim = tile_number;
+ }
+ }
+
+ udim_params->target_udim = active_udim;
+ udim_params->use_target_udim = true;
+ }
+
+ return true;
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
/** \name Parametrizer Conversion
* \{ */
@@ -1005,10 +1062,17 @@ static void uvedit_pack_islands_multi(const Scene *scene,
}
}
+/* Packing targets. */
+enum {
+ PACK_UDIM_SRC_CLOSEST = 0,
+ PACK_UDIM_SRC_ACTIVE = 1,
+};
+
static int pack_islands_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
const Scene *scene = CTX_data_scene(C);
+ const SpaceImage *sima = CTX_wm_space_image(C);
const UnwrapOptions options = {
.topology_from_uvs = true,
@@ -1018,17 +1082,19 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
.correct_aspect = true,
};
- bool rotate = RNA_boolean_get(op->ptr, "rotate");
-
uint objects_len = 0;
Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data_with_uvs(
view_layer, CTX_wm_view3d(C), &objects_len);
+ /* Early exit in case no UVs are selected. */
if (!uvedit_have_selection_multi(scene, objects, objects_len, &options)) {
MEM_freeN(objects);
return OPERATOR_CANCELLED;
}
+ /* RNA props */
+ const bool rotate = RNA_boolean_get(op->ptr, "rotate");
+ const int udim_source = RNA_enum_get(op->ptr, "udim_source");
if (RNA_struct_property_is_set(op->ptr, "margin")) {
scene->toolsettings->uvcalc_margin = RNA_float_get(op->ptr, "margin");
}
@@ -1036,9 +1102,15 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
RNA_float_set(op->ptr, "margin", scene->toolsettings->uvcalc_margin);
}
+ struct UVMapUDIM_Params udim_params;
+ const bool use_active = (udim_source == PACK_UDIM_SRC_ACTIVE);
+ const bool use_udim_params = ED_uvedit_udim_params_from_image_space(
+ sima, use_active, &udim_params);
+
ED_uvedit_pack_islands_multi(scene,
objects,
objects_len,
+ use_udim_params ? &udim_params : NULL,
&(struct UVPackIsland_Params){
.rotate = rotate,
.rotate_align_axis = -1,
@@ -1048,16 +1120,25 @@ static int pack_islands_exec(bContext *C, wmOperator *op)
});
MEM_freeN(objects);
-
return OPERATOR_FINISHED;
}
void UV_OT_pack_islands(wmOperatorType *ot)
{
+ static const EnumPropertyItem pack_target[] = {
+ {PACK_UDIM_SRC_CLOSEST, "CLOSEST_UDIM", 0, "Closest UDIM", "Pack islands to closest UDIM"},
+ {PACK_UDIM_SRC_ACTIVE,
+ "ACTIVE_UDIM",
+ 0,
+ "Active UDIM",
+ "Pack islands to active UDIM image tile or UDIM grid tile where 2D cursor is located"},
+ {0, NULL, 0, NULL, NULL},
+ };
/* identifiers */
ot->name = "Pack Islands";
ot->idname = "UV_OT_pack_islands";
- ot->description = "Transform all islands so that they fill up the UV space as much as possible";
+ ot->description =
+ "Transform all islands so that they fill up the UV/UDIM space as much as possible";
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@@ -1066,6 +1147,7 @@ void UV_OT_pack_islands(wmOperatorType *ot)
ot->poll = ED_operator_uvedit;
/* properties */
+ RNA_def_enum(ot->srna, "udim_source", pack_target, PACK_UDIM_SRC_CLOSEST, "Pack to", "");
RNA_def_boolean(ot->srna, "rotate", true, "Rotate", "Rotate islands for best fit");
RNA_def_float_factor(
ot->srna, "margin", 0.001f, 0.0f, 1.0f, "Margin", "Space between islands", 0.0f, 1.0f);
@@ -2206,6 +2288,7 @@ static int smart_project_exec(bContext *C, wmOperator *op)
ED_uvedit_pack_islands_multi(scene,
objects_changed,
object_changed_len,
+ NULL,
&(struct UVPackIsland_Params){
.rotate = true,
/* We could make this optional. */