diff --git a/editor/i18n/en/assets.js b/editor/i18n/en/assets.js index e439e3879b6..41ca76762dd 100644 --- a/editor/i18n/en/assets.js +++ b/editor/i18n/en/assets.js @@ -521,6 +521,10 @@ module.exports = { node: 'Node Menu', component: 'Component Menu', + copy_property_path: 'Copy Property Path', + copy_property_value: 'Copy Property Value', + paste_property_value: 'Paste Property Value', + remove_component: 'Remove', reset_component: 'Reset', move_up_component: 'Move Up', diff --git a/editor/i18n/zh/assets.js b/editor/i18n/zh/assets.js index b070b4dce6d..ba4edd7a6d5 100644 --- a/editor/i18n/zh/assets.js +++ b/editor/i18n/zh/assets.js @@ -506,6 +506,10 @@ module.exports = { node: '节点菜单', component: '组件菜单', + copy_property_path: '复制属性路径', + copy_property_value: '复制值', + paste_property_value: '粘贴值', + remove_component: '删除组件', reset_component: '重置组件', move_up_component: '向上移动', diff --git a/editor/inspector/contributions/node.js b/editor/inspector/contributions/node.js index c6bc17952bd..904df7f90e9 100644 --- a/editor/inspector/contributions/node.js +++ b/editor/inspector/contributions/node.js @@ -2,6 +2,7 @@ const fs = require('fs'); const path = require('path'); module.paths.push(path.join(Editor.App.path, 'node_modules')); +const { clipboard } = require('electron'); const Profile = require('@base/electron-profile'); const { throttle } = require('lodash'); const utils = require('./utils'); @@ -1169,6 +1170,59 @@ const Elements = { panel.i18nChangeBind = Elements.node.i18nChange.bind(panel); Editor.Message.addBroadcastListener('i18n:change', panel.i18nChangeBind); + + // 针对layer节点属性的右键菜单 + panel.$.nodeLayer && panel.$.nodeLayer.addEventListener('contextmenu', (event) => { + event.stopPropagation(); + event.preventDefault(); + + if (!panel.dump || !panel.dump.layer) { return; } + const layer = panel.dump.layer; + + const store = Elements.node.getAndParseClipboard(); + const pasteEnable = Elements.node.validatePasteEnable(layer, store); + + Editor.Menu.popup({ + menu: [ + { + label: Editor.I18n.t('ENGINE.menu.copy_property_path'), + async click() { + if (layer.path) { clipboard.writeText(layer.path); } + }, + }, + { type: 'separator' }, + { + label: Editor.I18n.t('ENGINE.menu.copy_property_value'), + click() { + const { type = '', value, enumList } = layer; + const storeData = { + type, + value, + enumList, + }; + + clipboard.writeText(JSON.stringify(storeData)); + }, + }, + { + label: Editor.I18n.t('ENGINE.menu.paste_property_value'), + enabled: pasteEnable, + click() { + const select = panel.$.nodeLayerSelect.querySelector('ui-select'); + if (select) { + select.value = store.value; + layer.value = store.value; + if (layer.values) { + layer.values.forEach((val, index) => dump.values[index] = store.value); + } + select.dispatch('change'); + select.dispatch('confirm'); + } + }, + }, + ], + }); + }); }, async update() { const panel = this; @@ -1422,6 +1476,39 @@ const Elements = { const $links = panel.$.container.querySelectorAll('ui-link'); $links.forEach($link => panel.setHelpUrl($link)); }, + getAndParseClipboard() { + const store = clipboard.readText(); + if (!store) { return; } + + try { + return JSON.parse(store); + } catch (err) { + return; + } + }, + validatePasteEnable(dump, store) { + if (!store) { return false; } + + const { type, value, enumList = [], bitmaskList = [] } = store; + + if (typeof type === 'undefined' || typeof value === 'undefined') { return false; } + + if (type !== dump.type || Boolean(dump.isArray) !== Array.isArray(value) || dump.readonly) { return false; } + + switch (type) { + case 'BitMask': { + return bitmaskList.length === dump.bitmaskList?.length && bitmaskList.every((item, index) => { + return item.name === dump.bitmaskList?.[index].name && item.value === dump.bitmaskList?.[index].value; + }); + } + case 'Enum': { + return enumList.length === dump.enumList?.length && enumList.every((item, index) => { + return item.name === dump.enumList?.[index].name && item.value === dump.enumList?.[index].value; + }) && enumList.some(item => item.value === value); + } + default: return true; + } + }, }, missingComponent: { ready() {