diff options
-rw-r--r-- | release/scripts/modules/bpy_extras/__init__.py | 1 | ||||
-rw-r--r-- | release/scripts/modules/bpy_extras/asset_utils.py | 63 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/__init__.py | 1 | ||||
-rw-r--r-- | release/scripts/startup/bl_operators/assets.py | 74 | ||||
-rw-r--r-- | release/scripts/startup/bl_ui/space_filebrowser.py | 196 | ||||
-rw-r--r-- | source/blender/editors/interface/interface_style.c | 8 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_draw.c | 186 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_intern.h | 3 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_ops.c | 28 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_utils.c | 17 | ||||
-rw-r--r-- | source/blender/editors/space_file/space_file.c | 87 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_screen.c | 2 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_space.c | 62 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_userdef.c | 5 |
14 files changed, 677 insertions, 56 deletions
diff --git a/release/scripts/modules/bpy_extras/__init__.py b/release/scripts/modules/bpy_extras/__init__.py index 1caef074d43..cb990b014a1 100644 --- a/release/scripts/modules/bpy_extras/__init__.py +++ b/release/scripts/modules/bpy_extras/__init__.py @@ -24,6 +24,7 @@ Utility modules associated with the bpy module. __all__ = ( "anim_utils", + "asset_utils", "object_utils", "io_utils", "image_utils", diff --git a/release/scripts/modules/bpy_extras/asset_utils.py b/release/scripts/modules/bpy_extras/asset_utils.py new file mode 100644 index 00000000000..db982e119d4 --- /dev/null +++ b/release/scripts/modules/bpy_extras/asset_utils.py @@ -0,0 +1,63 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +""" +Helpers for asset management tasks. +""" + +import bpy +from bpy.types import ( + Context, +) + +__all__ = ( + "SpaceAssetInfo", +) + +class SpaceAssetInfo: + @classmethod + def is_asset_browser(cls, space_data: bpy.types.Space): + return space_data.type == 'FILE_BROWSER' and space_data.browse_mode == 'ASSETS' + + @classmethod + def is_asset_browser_poll(cls, context: Context): + return cls.is_asset_browser(context.space_data) + + @classmethod + def get_active_asset(cls, context: Context): + if hasattr(context, "active_file"): + active_file = context.active_file + return active_file.asset_data if active_file else None + +class AssetBrowserPanel: + bl_space_type = 'FILE_BROWSER' + + @classmethod + def poll(cls, context): + return SpaceAssetInfo.is_asset_browser_poll(context) + +class AssetMetaDataPanel: + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + + @classmethod + def poll(cls, context): + active_file = context.active_file + return SpaceAssetInfo.is_asset_browser_poll(context) and active_file and active_file.asset_data diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py index 71b2de41d9e..e91d3b3ce60 100644 --- a/release/scripts/startup/bl_operators/__init__.py +++ b/release/scripts/startup/bl_operators/__init__.py @@ -27,6 +27,7 @@ if "bpy" in locals(): _modules = [ "add_mesh_torus", "anim", + "assets", "clip", "console", "constraint", diff --git a/release/scripts/startup/bl_operators/assets.py b/release/scripts/startup/bl_operators/assets.py new file mode 100644 index 00000000000..c317df78aa5 --- /dev/null +++ b/release/scripts/startup/bl_operators/assets.py @@ -0,0 +1,74 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# 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. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy + +from bpy_extras.asset_utils import ( + SpaceAssetInfo, +) + +class ASSET_OT_tag_add(bpy.types.Operator): + """Add a new keyword tag to the active asset""" + + bl_idname = "asset.tag_add" + bl_label = "Add Asset Tag" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return SpaceAssetInfo.is_asset_browser_poll(context) and SpaceAssetInfo.get_active_asset(context) + + def execute(self, context): + active_asset = SpaceAssetInfo.get_active_asset(context) + active_asset.tags.new("Unnamed Tag") + + return {'FINISHED'} + + +class ASSET_OT_tag_remove(bpy.types.Operator): + """Remove an existing keyword tag from the active asset""" + + bl_idname = "asset.tag_remove" + bl_label = "Remove Asset Tag" + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + if not SpaceAssetInfo.is_asset_browser_poll(context): + return False + + active_asset = SpaceAssetInfo.get_active_asset(context) + if not active_asset: + return False + + return active_asset.active_tag in range(len(active_asset.tags)) + + def execute(self, context): + active_asset = SpaceAssetInfo.get_active_asset(context) + tag = active_asset.tags[active_asset.active_tag] + + active_asset.tags.remove(tag) + active_asset.active_tag -= 1 + + return {'FINISHED'} + + +classes = ( + ASSET_OT_tag_add, + ASSET_OT_tag_remove, +) diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index a9bb2e79762..527a5bc623e 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -17,27 +17,79 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> + +import bpy + from bpy.types import Header, Panel, Menu, UIList +from bpy_extras import ( + asset_utils, +) + class FILEBROWSER_HT_header(Header): bl_space_type = 'FILE_BROWSER' + def draw_asset_browser_buttons(self, context): + layout = self.layout + + space_data = context.space_data + params = space_data.params + + row = layout.row(align=True) + row.prop(params, "asset_library", text="") + # External libraries don't auto-refresh, add refresh button. + if params.asset_library != 'LOCAL': + row.operator("file.refresh", text="", icon="FILE_REFRESH") + + layout.separator_spacer() + + # Uses prop_with_popover() as popover() only adds the triangle icon in headers. + layout.prop_with_popover( + params, + "display_type", + panel="FILEBROWSER_PT_display", + text="", + icon_only=True, + ) + layout.prop_with_popover( + params, + "display_type", + panel="FILEBROWSER_PT_filter", + text="", + icon='FILTER', + icon_only=True, + ) + + layout.prop(params, "filter_search", text="", icon='VIEWZOOM') + + layout.operator( + "screen.region_toggle", + text="", + icon='PREFERENCES', + depress=is_option_region_visible(context, space_data) + ).region_type = 'TOOL_PROPS' + def draw(self, context): + from bpy_extras.asset_utils import SpaceAssetInfo + layout = self.layout - st = context.space_data + space_data = context.space_data - if st.active_operator is None: + if space_data.active_operator is None: layout.template_header() FILEBROWSER_MT_editor_menus.draw_collapsible(context, layout) - # can be None when save/reload with a file selector open - - layout.separator_spacer() + if SpaceAssetInfo.is_asset_browser(space_data): + layout.separator() + self.draw_asset_browser_buttons(context) + else: + layout.separator_spacer() - layout.template_running_jobs() + if not context.screen.show_statusbar: + layout.template_running_jobs() class FILEBROWSER_PT_display(Panel): @@ -144,6 +196,9 @@ class FILEBROWSER_PT_filter(Panel): row.label(icon='BLANK1') # Indentation sub = row.column(align=True) + + sub.prop(params, "use_filter_asset_only") + filter_id = params.filter_id for identifier in dir(filter_id): if identifier.startswith("category_"): @@ -160,6 +215,11 @@ def panel_poll_is_upper_region(region): return region.alignment in {'LEFT', 'RIGHT'} +def panel_poll_is_asset_browsing(context): + from bpy_extras.asset_utils import SpaceAssetInfo + return SpaceAssetInfo.is_asset_browser_poll(context) + + class FILEBROWSER_UL_dir(UIList): def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): direntry = item @@ -187,7 +247,7 @@ class FILEBROWSER_PT_bookmarks_volumes(Panel): @classmethod def poll(cls, context): - return panel_poll_is_upper_region(context.region) + return panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) def draw(self, context): layout = self.layout @@ -207,7 +267,7 @@ class FILEBROWSER_PT_bookmarks_system(Panel): @classmethod def poll(cls, context): - return not context.preferences.filepaths.hide_system_bookmarks and panel_poll_is_upper_region(context.region) + return not context.preferences.filepaths.hide_system_bookmarks and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) def draw(self, context): layout = self.layout @@ -241,7 +301,7 @@ class FILEBROWSER_PT_bookmarks_favorites(Panel): @classmethod def poll(cls, context): - return panel_poll_is_upper_region(context.region) + return panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) def draw(self, context): layout = self.layout @@ -278,7 +338,7 @@ class FILEBROWSER_PT_bookmarks_recents(Panel): @classmethod def poll(cls, context): - return not context.preferences.filepaths.hide_recent_locations and panel_poll_is_upper_region(context.region) + return not context.preferences.filepaths.hide_recent_locations and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) def draw(self, context): layout = self.layout @@ -302,7 +362,7 @@ class FILEBROWSER_PT_advanced_filter(Panel): @classmethod def poll(cls, context): # only useful in append/link (library) context currently... - return context.space_data.params.use_library_browsing and panel_poll_is_upper_region(context.region) + return context.space_data.params.use_library_browsing and panel_poll_is_upper_region(context.region) and not panel_poll_is_asset_browsing(context) def draw(self, context): layout = self.layout @@ -314,12 +374,26 @@ class FILEBROWSER_PT_advanced_filter(Panel): if params.use_filter_blendid: layout.separator() col = layout.column(align=True) + + col.prop(params, "use_filter_asset_only") + filter_id = params.filter_id for identifier in dir(filter_id): if identifier.startswith("filter_"): col.prop(filter_id, identifier, toggle=True) +def is_option_region_visible(context, space): + if not space.active_operator: + return False + + for region in context.area.regions: + if region.type == 'TOOL_PROPS' and region.width <= 1: + return False + + return True + + class FILEBROWSER_PT_directory_path(Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'UI' @@ -334,16 +408,6 @@ class FILEBROWSER_PT_directory_path(Panel): return True - def is_option_region_visible(self, context, space): - if not space.active_operator: - return False - - for region in context.area.regions: - if region.type == 'TOOL_PROPS' and region.width <= 1: - return False - - return True - def draw(self, context): layout = self.layout space = context.space_data @@ -388,7 +452,7 @@ class FILEBROWSER_PT_directory_path(Panel): "screen.region_toggle", text="", icon='PREFERENCES', - depress=self.is_option_region_visible(context, space) + depress=is_option_region_visible(context, space) ).region_type = 'TOOL_PROPS' @@ -482,6 +546,89 @@ class FILEBROWSER_MT_context_menu(Menu): layout.prop_menu_enum(params, "sort_method") +class ASSETBROWSER_PT_navigation_bar(asset_utils.AssetBrowserPanel, Panel): + bl_label = "Asset Navigation" + bl_region_type = 'TOOLS' + bl_options = {'HIDE_HEADER'} + + def draw(self, context): + layout = self.layout + + space_file = context.space_data + + col = layout.column() + + col.scale_x = 1.3 + col.scale_y = 1.3 + col.prop(space_file.params, "asset_category", expand=True) + + +class ASSETBROWSER_PT_metadata(asset_utils.AssetBrowserPanel, Panel): + bl_region_type = 'TOOL_PROPS' + bl_label = "Asset Metadata" + bl_options = {'HIDE_HEADER'} + + def draw(self, context): + layout = self.layout + active_file = context.active_file + active_asset = asset_utils.SpaceAssetInfo.get_active_asset(context) + + layout.use_property_split = True + + if not active_file or not active_asset: + layout.label(text="No asset selected.") + return + + box = layout.box() + box.template_icon(icon_value=active_file.preview_icon_id, scale=5.0) + if bpy.ops.ed.lib_id_load_custom_preview.poll(): + box.operator("ed.lib_id_load_custom_preview", icon='FILEBROWSER', text="Load Custom") + layout.prop(active_file, "name") + + +class ASSETBROWSER_PT_metadata_details(asset_utils.AssetBrowserPanel, Panel): + bl_region_type = 'TOOL_PROPS' + bl_label = "Details" + bl_parent_id = "ASSETBROWSER_PT_metadata" + + def draw(self, context): + layout = self.layout + active_asset = asset_utils.SpaceAssetInfo.get_active_asset(context) + + layout.use_property_split = True + + if active_asset: + layout.prop(active_asset, "description") + + +class ASSETBROWSER_PT_metadata_tags(asset_utils.AssetMetaDataPanel, Panel): + bl_label = "Tags" + + def draw(self, context): + layout = self.layout + asset_data = asset_utils.SpaceAssetInfo.get_active_asset(context) + + row = layout.row() + row.template_list("ASSETBROWSER_UL_metadata_tags", "asset_tags", asset_data, "tags", + asset_data, "active_tag", rows=4) + + col = row.column(align=True) + col.operator("asset.tag_add", icon='ADD', text="") + col.operator("asset.tag_remove", icon='REMOVE', text="") + + +class ASSETBROWSER_UL_metadata_tags(UIList): + def draw_item(self, _context, layout, _data, item, icon, _active_data, _active_propname, _index): + tag = item + + row = layout.row(align=True) + # Non-editable entries would show grayed-out, which is bad in this specific case, so switch to mere label. + if tag.is_property_readonly("name"): + row.label(text=tag.name, icon_value=icon) + else: + row.prop(tag, "name", text="", emboss=False, icon_value=icon) + + classes = ( FILEBROWSER_HT_header, FILEBROWSER_PT_display, @@ -498,6 +645,11 @@ classes = ( FILEBROWSER_MT_view, FILEBROWSER_MT_select, FILEBROWSER_MT_context_menu, + ASSETBROWSER_PT_navigation_bar, + ASSETBROWSER_PT_metadata, + ASSETBROWSER_PT_metadata_details, + ASSETBROWSER_PT_metadata_tags, + ASSETBROWSER_UL_metadata_tags, ) if __name__ == "__main__": # only for live edit. diff --git a/source/blender/editors/interface/interface_style.c b/source/blender/editors/interface/interface_style.c index c3d528ad5c5..a37fb0dfde1 100644 --- a/source/blender/editors/interface/interface_style.c +++ b/source/blender/editors/interface/interface_style.c @@ -206,8 +206,12 @@ void UI_fontstyle_draw_ex(const uiFontStyle *fs, BLF_disable(fs->uifont_id, font_flag); - *r_xofs = xofs; - *r_yofs = yofs; + if (r_xofs) { + *r_xofs = xofs; + } + if (r_yofs) { + *r_yofs = yofs; + } } void UI_fontstyle_draw(const uiFontStyle *fs, diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index e3bdda7c480..f2f7f9d82f9 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -25,6 +25,7 @@ #include <math.h> #include <string.h> +#include "BLI_alloca.h" #include "BLI_blenlib.h" #include "BLI_fileops_types.h" #include "BLI_math.h" @@ -134,6 +135,7 @@ static void draw_tile(int sx, int sy, int width, int height, int colorid, int sh } static void file_draw_icon(uiBlock *block, + const FileDirEntry *file, const char *path, int sx, int sy, @@ -157,8 +159,29 @@ static void file_draw_icon(uiBlock *block, UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path)); if (drag) { - /* path is no more static, cannot give it directly to but... */ - UI_but_drag_set_path(but, BLI_strdup(path), true); + /* TODO duplicated from file_draw_preview(). */ + ID *id; + + if ((id = filelist_file_get_id(file))) { + UI_but_drag_set_id(but, id); + } + else if (file->typeflag & FILE_TYPE_ASSET) { + ImBuf *preview_image = filelist_file_getimage(file); + char blend_path[FILE_MAX_LIBEXTRA]; + if (BLO_library_path_explode(path, blend_path, NULL, NULL)) { + UI_but_drag_set_asset(but, + file->name, + BLI_strdup(blend_path), + file->blentype, + icon, + preview_image, + UI_DPI_FAC); + } + } + else { + /* path is no more static, cannot give it directly to but... */ + UI_but_drag_set_path(but, BLI_strdup(path), true); + } } } @@ -200,6 +223,65 @@ static void file_draw_string(int sx, }); } +/** + * \param r_sx, r_sy: The lower right corner of the last line drawn. AKA the cursor position on + * completion. + */ +static void file_draw_string_multiline(int sx, + int sy, + const char *string, + int wrap_width, + int line_height, + const uchar text_col[4], + int *r_sx, + int *r_sy) +{ + rcti rect; + + if (string[0] == '\0' || wrap_width < 1) { + return; + } + + const uiStyle *style = UI_style_get(); + int font_id = style->widgetlabel.uifont_id; + int len = strlen(string); + + rctf textbox; + BLF_wordwrap(font_id, wrap_width); + BLF_enable(font_id, BLF_WORD_WRAP); + BLF_boundbox(font_id, string, len, &textbox); + BLF_disable(font_id, BLF_WORD_WRAP); + + /* no text clipping needed, UI_fontstyle_draw does it but is a bit too strict + * (for buttons it works) */ + rect.xmin = sx; + rect.xmax = sx + wrap_width; + /* Need to increase the clipping rect by one more line, since the #UI_fontstyle_draw_ex() will + * actually start drawing at (ymax - line-height). */ + rect.ymin = sy - round_fl_to_int(BLI_rctf_size_y(&textbox)) - line_height; + rect.ymax = sy; + + struct ResultBLF result; + UI_fontstyle_draw_ex(&style->widget, + &rect, + string, + text_col, + &(struct uiFontStyleDraw_Params){ + .align = UI_STYLE_TEXT_LEFT, + .word_wrap = true, + }, + len, + NULL, + NULL, + &result); + if (r_sx) { + *r_sx = result.width; + } + if (r_sy) { + *r_sy = rect.ymin + line_height; + } +} + void file_calc_previews(const bContext *C, ARegion *region) { SpaceFile *sfile = CTX_wm_space_file(C); @@ -210,6 +292,7 @@ void file_calc_previews(const bContext *C, ARegion *region) } static void file_draw_preview(uiBlock *block, + const FileDirEntry *file, const char *path, int sx, int sy, @@ -218,7 +301,6 @@ static void file_draw_preview(uiBlock *block, const int icon, FileLayout *layout, const bool is_icon, - const int typeflags, const bool drag, const bool dimmed, const bool is_link) @@ -232,7 +314,7 @@ static void file_draw_preview(uiBlock *block, float scale; int ex, ey; bool show_outline = !is_icon && - (typeflags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_BLENDER)); + (file->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_BLENDER)); BLI_assert(imb != NULL); @@ -273,14 +355,14 @@ static void file_draw_preview(uiBlock *block, float col[4] = {1.0f, 1.0f, 1.0f, 1.0f}; if (is_icon) { - if (typeflags & FILE_TYPE_DIR) { + if (file->typeflag & FILE_TYPE_DIR) { UI_GetThemeColor4fv(TH_ICON_FOLDER, col); } else { UI_GetThemeColor4fv(TH_TEXT, col); } } - else if (typeflags & FILE_TYPE_FTFONT) { + else if (file->typeflag & FILE_TYPE_FTFONT) { UI_GetThemeColor4fv(TH_TEXT, col); } @@ -288,7 +370,7 @@ static void file_draw_preview(uiBlock *block, col[3] *= 0.3f; } - if (!is_icon && typeflags & FILE_TYPE_BLENDERLIB) { + if (!is_icon && file->typeflag & FILE_TYPE_BLENDERLIB) { /* Datablock preview images use premultiplied alpha. */ GPU_blend(GPU_BLEND_ALPHA_PREMULT); } @@ -324,7 +406,7 @@ static void file_draw_preview(uiBlock *block, icon_color[2] = 255; } icon_x = xco + (ex / 2.0f) - (icon_size / 2.0f); - icon_y = yco + (ey / 2.0f) - (icon_size * ((typeflags & FILE_TYPE_DIR) ? 0.78f : 0.75f)); + icon_y = yco + (ey / 2.0f) - (icon_size * ((file->typeflag & FILE_TYPE_DIR) ? 0.78f : 0.75f)); UI_icon_draw_ex( icon_x, icon_y, icon, icon_aspect / U.dpi_fac, icon_opacity, 0.0f, icon_color, false); } @@ -346,13 +428,13 @@ static void file_draw_preview(uiBlock *block, /* Link to folder or non-previewed file. */ uchar icon_color[4]; UI_GetThemeColor4ubv(TH_BACK, icon_color); - icon_x = xco + ((typeflags & FILE_TYPE_DIR) ? 0.14f : 0.23f) * scaledx; - icon_y = yco + ((typeflags & FILE_TYPE_DIR) ? 0.24f : 0.14f) * scaledy; + icon_x = xco + ((file->typeflag & FILE_TYPE_DIR) ? 0.14f : 0.23f) * scaledx; + icon_y = yco + ((file->typeflag & FILE_TYPE_DIR) ? 0.24f : 0.14f) * scaledy; UI_icon_draw_ex( icon_x, icon_y, arrow, icon_aspect / U.dpi_fac * 1.8, 0.3f, 0.0f, icon_color, false); } } - else if (icon && !is_icon && !(typeflags & FILE_TYPE_FTFONT)) { + else if (icon && !is_icon && !(file->typeflag & FILE_TYPE_FTFONT)) { /* Smaller, fainter icon at bottom-left for preview image thumbnail, but not for fonts. */ float icon_x, icon_y; const uchar dark[4] = {0, 0, 0, 255}; @@ -385,8 +467,22 @@ static void file_draw_preview(uiBlock *block, /* dragregion */ if (drag) { + ID *id; + + if ((id = filelist_file_get_id(file))) { + UI_but_drag_set_id(but, id); + } /* path is no more static, cannot give it directly to but... */ - UI_but_drag_set_image(but, BLI_strdup(path), icon, imb, scale, true); + else if (file->typeflag & FILE_TYPE_ASSET) { + char blend_path[FILE_MAX_LIBEXTRA]; + if (BLO_library_path_explode(path, blend_path, NULL, NULL)) { + UI_but_drag_set_asset( + but, file->name, BLI_strdup(blend_path), file->blentype, icon, imb, scale); + } + } + else { + UI_but_drag_set_image(but, BLI_strdup(path), icon, imb, scale, true); + } } GPU_blend(GPU_BLEND_NONE); @@ -821,6 +917,7 @@ void file_draw_list(const bContext *C, ARegion *region) } file_draw_preview(block, + file, path, sx, sy, @@ -829,13 +926,13 @@ void file_draw_list(const bContext *C, ARegion *region) icon, layout, is_icon, - file->typeflag, do_drag, is_hidden, is_link); } else { file_draw_icon(block, + file, path, sx, sy - layout->tile_border_y, @@ -906,3 +1003,66 @@ void file_draw_list(const bContext *C, ARegion *region) layout->curr_size = params->thumbnail_size; } + +static void file_draw_invalid_library_hint(const SpaceFile *sfile, const ARegion *region) +{ + const FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); + + char library_ui_path[PATH_MAX]; + file_path_to_ui_path(asset_params->base_params.dir, library_ui_path, sizeof(library_ui_path)); + + uchar text_col[4]; + uchar text_alert_col[4]; + UI_GetThemeColor4ubv(TH_TEXT, text_col); + UI_GetThemeColor4ubv(TH_REDALERT, text_alert_col); + + const View2D *v2d = ®ion->v2d; + const int pad = sfile->layout->tile_border_x; + const int width = BLI_rctf_size_x(&v2d->tot) - (2 * pad); + const int line_height = sfile->layout->textheight; + int sx = v2d->tot.xmin + pad; + /* For some reason no padding needed. */ + int sy = v2d->tot.ymax; + + { + const char *message = TIP_("Library not found"); + const int draw_string_str_len = strlen(message) + 2 + sizeof(library_ui_path); + char *draw_string = alloca(draw_string_str_len); + BLI_snprintf(draw_string, draw_string_str_len, "%s: %s", message, library_ui_path); + file_draw_string_multiline(sx, sy, draw_string, width, line_height, text_alert_col, NULL, &sy); + } + + /* Next line, but separate it a bit further. */ + sy -= line_height; + + { + UI_icon_draw(sx, sy - UI_UNIT_Y, ICON_INFO); + + const char *suggestion = TIP_( + "Set up the library or edit libraries in the Preferences, File Paths section."); + file_draw_string_multiline( + sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, NULL, NULL); + } +} + +/** + * Draw a string hint if the file list is invalid. + * \return true if the list is invalid and a hint was drawn. + */ +bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region) +{ + FileAssetSelectParams *asset_params = ED_fileselect_get_asset_params(sfile); + /* Only for asset browser. */ + if (!ED_fileselect_is_asset_browser(sfile)) { + return false; + } + /* Check if the library exists. */ + if ((asset_params->asset_library.type == FILE_ASSET_LIBRARY_LOCAL) || + filelist_is_dir(sfile->files, asset_params->base_params.dir)) { + return false; + } + + file_draw_invalid_library_hint(sfile, region); + + return true; +} diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index a0e02681e0e..56fb588776e 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -38,6 +38,7 @@ struct View2D; void file_calc_previews(const bContext *C, ARegion *region); void file_draw_list(const bContext *C, ARegion *region); +bool file_draw_hint_if_invalid(const SpaceFile *sfile, const ARegion *region); void file_draw_check_ex(bContext *C, struct ScrArea *area); void file_draw_check(bContext *C); @@ -117,3 +118,5 @@ void file_execute_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); diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 8af84f65ced..be4577bcba7 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -40,6 +40,7 @@ # include "BLI_winstuff.h" #endif +#include "ED_asset.h" #include "ED_fileselect.h" #include "ED_screen.h" #include "ED_select_utils.h" @@ -2695,6 +2696,29 @@ static bool file_delete_poll(bContext *C) return poll; } +static bool file_delete_single(const FileSelectParams *params, + FileDirEntry *file, + const char **r_error_message) +{ + if (file->typeflag & FILE_TYPE_ASSET) { + ID *id = filelist_file_get_id(file); + if (!id) { + *r_error_message = "File is not a local data-block asset."; + return false; + } + ED_asset_clear_id(id); + } + else { + char str[FILE_MAX]; + BLI_join_dirfile(str, sizeof(str), params->dir, file->relpath); + if (BLI_delete_soft(str, r_error_message) != 0 || BLI_exists(str)) { + return false; + } + } + + return true; +} + static int file_delete_exec(bContext *C, wmOperator *op) { wmWindowManager *wm = CTX_wm_manager(C); @@ -2708,9 +2732,7 @@ static int file_delete_exec(bContext *C, wmOperator *op) for (int i = 0; i < numfiles; i++) { if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { FileDirEntry *file = filelist_file(sfile->files, i); - char str[FILE_MAX]; - BLI_join_dirfile(str, sizeof(str), params->dir, file->relpath); - if (BLI_delete_soft(str, &error_message) != 0 || BLI_exists(str)) { + if (!file_delete_single(params, file, &error_message)) { report_error = true; } } diff --git a/source/blender/editors/space_file/file_utils.c b/source/blender/editors/space_file/file_utils.c index 452f2f704cf..9d85996c559 100644 --- a/source/blender/editors/space_file/file_utils.c +++ b/source/blender/editors/space_file/file_utils.c @@ -18,12 +18,14 @@ * \ingroup spfile */ +#include "BLI_fileops.h" #include "BLI_listbase.h" +#include "BLI_path_util.h" #include "BLI_rect.h" - -#include "BLO_readfile.h" +#include "BLI_string.h" #include "BKE_context.h" +#include "BLO_readfile.h" #include "ED_fileselect.h" #include "ED_screen.h" @@ -44,3 +46,14 @@ void file_tile_boundbox(const ARegion *region, FileLayout *layout, const int fil ymax - layout->tile_h - layout->tile_border_y, ymax); } + +/** + * If \a path leads to a .blend, remove the trailing slash (if needed). + */ +void file_path_to_ui_path(const char *path, char *r_path, int max_size) +{ + char tmp_path[PATH_MAX]; + BLI_strncpy(tmp_path, path, sizeof(tmp_path)); + BLI_path_slash_rstrip(tmp_path); + BLI_strncpy(r_path, BLO_has_bfile_extension(tmp_path) ? tmp_path : path, max_size); +} diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index f1d72387791..46ae52fd4cf 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -57,6 +57,23 @@ #include "filelist.h" #include "fsmenu.h" +static ARegion *file_ui_region_ensure(ScrArea *area, ARegion *region_prev) +{ + ARegion *region; + + if ((region = BKE_area_find_region_type(area, RGN_TYPE_UI)) != NULL) { + return region; + } + + region = MEM_callocN(sizeof(ARegion), "execute region for file"); + BLI_insertlinkafter(&area->regionbase, region_prev, region); + region->regiontype = RGN_TYPE_UI; + region->alignment = RGN_ALIGN_TOP; + region->flag = RGN_FLAG_DYNAMIC_SIZE; + + return region; +} + static ARegion *file_execute_region_ensure(ScrArea *area, ARegion *region_prev) { ARegion *region; @@ -223,15 +240,30 @@ static void file_ensure_valid_region_state(bContext *C, SpaceFile *sfile, FileSelectParams *params) { - ARegion *region_ui = BKE_area_find_region_type(area, RGN_TYPE_UI); - ARegion *region_props = BKE_area_find_region_type(area, RGN_TYPE_TOOL_PROPS); - ARegion *region_execute = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE); + ARegion *region_tools = BKE_area_find_region_type(area, RGN_TYPE_TOOLS); bool needs_init = false; /* To avoid multiple ED_area_init() calls. */ + BLI_assert(region_tools); + + if (sfile->browse_mode == FILE_BROWSE_MODE_ASSETS) { + ARegion *region_execute = file_execute_region_ensure(area, region_tools); + ARegion *region_props = file_tool_props_region_ensure(area, region_execute); + + /* Hide specific regions by default. */ + region_props->flag |= RGN_FLAG_HIDDEN; + region_execute->flag |= RGN_FLAG_HIDDEN; + + ARegion *region_ui = BKE_area_find_region_type(area, RGN_TYPE_UI); + if (region_ui) { + ED_region_remove(C, area, region_ui); + needs_init = true; + } + } /* If there's an file-operation, ensure we have the option and execute region */ - if (sfile->op && (region_props == NULL)) { - region_execute = file_execute_region_ensure(area, region_ui); - region_props = file_tool_props_region_ensure(area, region_execute); + else if (sfile->op) { + ARegion *region_ui = file_ui_region_ensure(area, region_tools); + ARegion *region_execute = file_execute_region_ensure(area, region_ui); + ARegion *region_props = file_tool_props_region_ensure(area, region_execute); if (params->flag & FILE_HIDE_TOOL_PROPS) { region_props->flag |= RGN_FLAG_HIDDEN; @@ -243,12 +275,19 @@ static void file_ensure_valid_region_state(bContext *C, needs_init = true; } /* If there's _no_ file-operation, ensure we _don't_ have the option and execute region */ - else if ((sfile->op == NULL) && (region_props != NULL)) { - BLI_assert(region_execute != NULL); + else { + ARegion *region_props = BKE_area_find_region_type(area, RGN_TYPE_TOOL_PROPS); + ARegion *region_execute = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE); + ARegion *region_ui = file_ui_region_ensure(area, region_tools); + UNUSED_VARS(region_ui); - ED_region_remove(C, area, region_props); - ED_region_remove(C, area, region_execute); - needs_init = true; + if (region_props) { + BLI_assert(region_execute); + + ED_region_remove(C, area, region_props); + ED_region_remove(C, area, region_execute); + needs_init = true; + } } if (needs_init) { @@ -530,7 +569,9 @@ static void file_main_region_draw(const bContext *C, ARegion *region) file_highlight_set(sfile, region, event->x, event->y); } - file_draw_list(C, region); + if (!file_draw_hint_if_invalid(sfile, region)) { + file_draw_list(C, region); + } /* reset view matrix */ UI_view2d_view_restore(C); @@ -714,6 +755,25 @@ static void file_dropboxes(void) WM_dropbox_add(lb, "FILE_OT_filepath_drop", filepath_drop_poll, filepath_drop_copy); } +static int file_space_subtype_get(ScrArea *area) +{ + SpaceFile *sfile = area->spacedata.first; + return sfile->browse_mode; +} + +static void file_space_subtype_set(ScrArea *area, int value) +{ + SpaceFile *sfile = area->spacedata.first; + sfile->browse_mode = value; +} + +static void file_space_subtype_item_extend(bContext *UNUSED(C), + EnumPropertyItem **item, + int *totitem) +{ + RNA_enum_items_add(item, totitem, rna_enum_space_file_browse_mode_items); +} + const char *file_context_dir[] = {"active_file", "active_id", NULL}; static int /*eContextResult*/ file_context(const bContext *C, @@ -779,6 +839,9 @@ void ED_spacetype_file(void) st->operatortypes = file_operatortypes; st->keymap = file_keymap; st->dropboxes = file_dropboxes; + st->space_subtype_item_extend = file_space_subtype_item_extend; + st->space_subtype_get = file_space_subtype_get; + st->space_subtype_set = file_space_subtype_set; st->context = file_context; st->id_remap = file_id_remap; diff --git a/source/blender/makesrna/intern/rna_screen.c b/source/blender/makesrna/intern/rna_screen.c index e300fb1b31b..784172b3ac9 100644 --- a/source/blender/makesrna/intern/rna_screen.c +++ b/source/blender/makesrna/intern/rna_screen.c @@ -270,6 +270,8 @@ static void rna_Area_ui_type_update(bContext *C, PointerRNA *ptr) st->space_subtype_set(area, area->butspacetype_subtype); } area->butspacetype_subtype = 0; + + ED_area_tag_refresh(area); } static void rna_View2D_region_to_view(struct View2D *v2d, float x, float y, float result[2]) diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 4b39858026c..59012ce4528 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -2574,6 +2574,18 @@ static const EnumPropertyItem *rna_FileAssetSelectParams_asset_library_itemf( return item; } +static void rna_FileAssetSelectParams_asset_category_set(PointerRNA *ptr, uint64_t value) +{ + FileSelectParams *params = ptr->data; + params->filter_id = value; +} + +static uint64_t rna_FileAssetSelectParams_asset_category_get(PointerRNA *ptr) +{ + FileSelectParams *params = ptr->data; + return params->filter_id; +} + static void rna_FileBrowser_FileSelectEntry_name_get(PointerRNA *ptr, char *value) { const FileDirEntry *entry = ptr->data; @@ -6207,6 +6219,47 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) StructRNA *srna; PropertyRNA *prop; + /* XXX copied from rna_def_fileselect_idfilter. */ + static const EnumPropertyItem asset_category_items[] = { + {FILTER_ID_SCE, "SCENES", ICON_SCENE_DATA, "Scenes", "Show scenes"}, + {FILTER_ID_AC, "ANIMATIONS", ICON_ANIM_DATA, "Animations", "Show animation data"}, + {FILTER_ID_OB | FILTER_ID_GR, + "OBJECTS_AND_COLLECTIONS", + ICON_GROUP, + "Objects & Collections", + "Show objects and collections"}, + {FILTER_ID_AR | FILTER_ID_CU | FILTER_ID_LT | FILTER_ID_MB | FILTER_ID_ME + /* XXX avoid warning */ + // | FILTER_ID_HA | FILTER_ID_PT | FILTER_ID_VO + , + "GEOMETRY", + ICON_MESH_DATA, + "Geometry", + "Show meshes, curves, lattice, armatures and metaballs data"}, + {FILTER_ID_LS | FILTER_ID_MA | FILTER_ID_NT | FILTER_ID_TE, + "SHADING", + ICON_MATERIAL_DATA, + "Shading", + "Show materials, nodetrees, textures and Freestyle's linestyles"}, + {FILTER_ID_IM | FILTER_ID_MC | FILTER_ID_MSK | FILTER_ID_SO, + "IMAGES_AND_SOUNDS", + ICON_IMAGE_DATA, + "Images & Sounds", + "Show images, movie clips, sounds and masks"}, + {FILTER_ID_CA | FILTER_ID_LA | FILTER_ID_LP | FILTER_ID_SPK | FILTER_ID_WO | FILTER_ID_WS, + "ENVIRONMENTS", + ICON_WORLD_DATA, + "Environment", + "Show worlds, lights, cameras and speakers"}, + {FILTER_ID_BR | FILTER_ID_GD | FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_TXT | + FILTER_ID_VF | FILTER_ID_CF, + "MISC", + ICON_GREASEPENCIL, + "Miscellaneous", + "Show other data types"}, + {0, NULL, 0, NULL, NULL}, + }; + srna = RNA_def_struct(brna, "FileAssetSelectParams", "FileSelectParams"); RNA_def_struct_ui_text( srna, "Asset Select Parameters", "Settings for the file selection in Asset Browser mode"); @@ -6219,6 +6272,15 @@ static void rna_def_fileselect_asset_params(BlenderRNA *brna) "rna_FileAssetSelectParams_asset_library_itemf"); RNA_def_property_ui_text(prop, "Asset Library", ""); RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); + + prop = RNA_def_property(srna, "asset_category", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, asset_category_items); + RNA_def_property_enum_funcs(prop, + "rna_FileAssetSelectParams_asset_category_get", + "rna_FileAssetSelectParams_asset_category_set", + NULL); + RNA_def_property_ui_text(prop, "Asset Category", "Determine which kind of assets to display"); + RNA_def_property_update(prop, NC_SPACE | ND_SPACE_FILE_LIST, NULL); } static void rna_def_filemenu_entry(BlenderRNA *brna) diff --git a/source/blender/makesrna/intern/rna_userdef.c b/source/blender/makesrna/intern/rna_userdef.c index 2175cddcd7f..7a285df235a 100644 --- a/source/blender/makesrna/intern/rna_userdef.c +++ b/source/blender/makesrna/intern/rna_userdef.c @@ -5993,8 +5993,9 @@ static void rna_def_userdef_filepaths_asset_library(BlenderRNA *brna) RNA_def_struct_name_property(srna, prop); RNA_def_property_update(prop, 0, "rna_userdef_update"); - prop = RNA_def_property(srna, "path", PROP_STRING, PROP_FILEPATH); - RNA_def_property_ui_text(prop, "Path", "Path to a .blend file to use as an asset library"); + prop = RNA_def_property(srna, "path", PROP_STRING, PROP_DIRPATH); + RNA_def_property_ui_text( + prop, "Path", "Path to a directory with .blend files to use as an asset library"); RNA_def_property_update(prop, 0, "rna_userdef_update"); } |