149 lines
4.1 KiB
Python
149 lines
4.1 KiB
Python
"""Video SequencerサイドバーのSE Palette UI。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import bpy
|
|
import bpy.utils.previews as previews
|
|
from bpy.types import Panel
|
|
|
|
from .operators import populate_items
|
|
|
|
|
|
_preview_collection = None
|
|
|
|
|
|
def clear_previews():
|
|
global _preview_collection
|
|
if _preview_collection is not None:
|
|
try:
|
|
previews.remove(_preview_collection)
|
|
except Exception:
|
|
pass
|
|
_preview_collection = None
|
|
|
|
|
|
def _custom_icon_value(item) -> int:
|
|
global _preview_collection
|
|
raw_path = item.icon_path.strip()
|
|
if not raw_path:
|
|
return 0
|
|
filepath = os.path.abspath(bpy.path.abspath(raw_path))
|
|
if not os.path.isfile(filepath):
|
|
return 0
|
|
try:
|
|
if _preview_collection is None:
|
|
_preview_collection = previews.new()
|
|
key = f"{filepath}:{os.path.getmtime(filepath)}"
|
|
if key not in _preview_collection:
|
|
_preview_collection.load(key, filepath, "IMAGE")
|
|
return _preview_collection[key].icon_id
|
|
except Exception:
|
|
return 0
|
|
|
|
|
|
def _draw_insert_button(row, item):
|
|
icon_value = _custom_icon_value(item)
|
|
kwargs = {"text": item.label}
|
|
if icon_value:
|
|
kwargs["icon_value"] = icon_value
|
|
else:
|
|
kwargs["icon"] = item.icon or "PLAY_SOUND"
|
|
operator = row.operator("se_palette.insert", **kwargs)
|
|
operator.filepath = item.filepath
|
|
|
|
|
|
def draw_palette(layout, context):
|
|
"""ポップアップなど任意のUILayoutへSE Paletteを描画する。"""
|
|
props = context.scene.se_palette
|
|
|
|
folder_box = layout.box()
|
|
row = folder_box.row(align=True)
|
|
row.prop(props, "folder", text="")
|
|
row.operator("se_palette.scan", text="", icon="FILE_REFRESH")
|
|
|
|
if not props.items and os.path.isdir(os.path.abspath(bpy.path.abspath(props.folder))):
|
|
try:
|
|
populate_items(props)
|
|
except Exception:
|
|
pass
|
|
|
|
settings = layout.box()
|
|
row = settings.row(align=True)
|
|
row.prop(props, "channel")
|
|
row.prop(props, "volume")
|
|
row = settings.row(align=True)
|
|
row.prop(props, "advance_playhead")
|
|
row.prop(props, "compact")
|
|
settings.prop(props, "grid_columns", slider=True)
|
|
|
|
row = layout.row(align=True)
|
|
row.prop(props, "search", text="", icon="VIEWZOOM")
|
|
row.operator("se_palette.stop_preview", text="", icon="PAUSE")
|
|
|
|
folder = os.path.abspath(bpy.path.abspath(props.folder))
|
|
if not os.path.isdir(folder):
|
|
warning = layout.row()
|
|
warning.alert = True
|
|
warning.label(text="Select an SE folder", icon="ERROR")
|
|
return
|
|
|
|
query = props.search.strip().casefold()
|
|
visible_items = [
|
|
item
|
|
for item in props.items
|
|
if not query
|
|
or query in item.label.casefold()
|
|
or query in item.filename.casefold()
|
|
]
|
|
|
|
if not visible_items:
|
|
layout.label(
|
|
text="No matching sound effects" if query else "No sound effects found",
|
|
icon="INFO",
|
|
)
|
|
return
|
|
|
|
grid = layout.grid_flow(
|
|
row_major=True,
|
|
columns=props.grid_columns,
|
|
even_columns=True,
|
|
even_rows=True,
|
|
align=True,
|
|
)
|
|
for item in visible_items:
|
|
card = grid.box()
|
|
insert_row = card.row(align=True)
|
|
insert_row.scale_y = 1.0 if props.compact else 1.5
|
|
_draw_insert_button(insert_row, item)
|
|
|
|
controls = card.row(align=True)
|
|
preview = controls.operator(
|
|
"se_palette.preview",
|
|
text="" if props.compact else "Preview",
|
|
icon="PLAY",
|
|
)
|
|
preview.filepath = item.filepath
|
|
edit = controls.operator(
|
|
"se_palette.edit_item",
|
|
text="" if props.compact else "Edit",
|
|
icon="PREFERENCES",
|
|
)
|
|
edit.filepath = item.filepath
|
|
|
|
layout.label(text=f"{len(visible_items)} / {len(props.items)} sounds", icon="SOUND")
|
|
|
|
|
|
class SEPALETTE_PT_main(Panel):
|
|
"""VSEサイドバーに表示するSE Palette。"""
|
|
|
|
bl_label = "SE Palette"
|
|
bl_idname = "SEPALETTE_PT_main"
|
|
bl_space_type = "SEQUENCE_EDITOR"
|
|
bl_region_type = "UI"
|
|
bl_category = "SE Palette"
|
|
|
|
def draw(self, context):
|
|
draw_palette(self.layout, context)
|