texture.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. 'use strict';
  2. const { updateElementReadonly } = require('../../utils/assets');
  3. const { extname } = require('path');
  4. const { ParseAtlasFile } = require('./parse-atlas');
  5. exports.template = /* html */ `
  6. <div class="asset-texture">
  7. <!-- dont delete, for insert -->
  8. <div class="content">
  9. <ui-prop>
  10. <ui-label slot="label" value="i18n:ENGINE.assets.texture.anisotropy" tooltip="i18n:ENGINE.assets.texture.anisotropyTip"></ui-label>
  11. <ui-num-input slot="content" class="anisotropy-input"></ui-num-input>
  12. </ui-prop>
  13. <ui-prop>
  14. <ui-label slot="label" value="i18n:ENGINE.assets.texture.filterMode" tooltip="i18n:ENGINE.assets.texture.filterModeTip"></ui-label>
  15. <ui-select slot="content" class="filterMode-select"></ui-select>
  16. </ui-prop>
  17. <section class="filter-advanced-section">
  18. <ui-prop>
  19. <ui-label slot="label" value="i18n:ENGINE.assets.texture.minfilter" tooltip="i18n:ENGINE.assets.texture.minfilterTip"></ui-label>
  20. <ui-select slot="content" class="minfilter-select"></ui-select>
  21. </ui-prop>
  22. <ui-prop>
  23. <ui-label slot="label" value="i18n:ENGINE.assets.texture.magfilter" tooltip="i18n:ENGINE.assets.texture.magfilterTip"></ui-label>
  24. <ui-select slot="content" class="magfilter-select"></ui-select>
  25. </ui-prop>
  26. <ui-prop>
  27. <ui-label slot="label" value="i18n:ENGINE.assets.texture.generateMipmaps" tooltip="i18n:ENGINE.assets.texture.generateMipmapsTip"></ui-label>
  28. <ui-checkbox slot="content" class="generate-mipmaps-checkbox"></ui-checkbox>
  29. </ui-prop>
  30. <section class="generate-mipmaps-section">
  31. <ui-prop>
  32. <ui-label slot="label" value="i18n:ENGINE.assets.texture.mipfilter" tooltip="i18n:ENGINE.assets.texture.mipfilterTip"></ui-label>
  33. <ui-select slot="content" class="mipfilter-select"></ui-select>
  34. </ui-prop>
  35. </section>
  36. </section>
  37. <ui-prop class="filter-different">
  38. <div slot="content">
  39. <div class="atlas-file-name"></div>
  40. </div>
  41. </ui-prop>
  42. <ui-prop class="wrapMode-prop">
  43. <ui-label slot="label" value="i18n:ENGINE.assets.texture.wrapMode" tooltip="i18n:ENGINE.assets.texture.wrapModeTip"></ui-label>
  44. <ui-select slot="content" class="wrapMode-select"></ui-select>
  45. </ui-prop>
  46. <section class="wrap-advanced-section">
  47. <ui-prop class="wrapModeS-prop">
  48. <ui-label slot="label" value="i18n:ENGINE.assets.texture.wrapModeS" tooltip="i18n:ENGINE.assets.texture.wrapModeSTip"></ui-label>
  49. <ui-select slot="content" class="wrapModeS-select"></ui-select>
  50. </ui-prop>
  51. <ui-prop class="wrapModeT-prop">
  52. <ui-label slot="label" value="i18n:ENGINE.assets.texture.wrapModeT" tooltip="i18n:ENGINE.assets.texture.wrapModeTTip"></ui-label>
  53. <ui-select slot="content" class="wrapModeT-select"></ui-select>
  54. </ui-prop>
  55. </section>
  56. <ui-prop class="warn-words">
  57. <ui-label value="i18n:ENGINE.assets.texture.modeWarn"></ui-label>
  58. </ui-prop>
  59. </div>
  60. </div>
  61. `;
  62. exports.style = /* css */ `
  63. .asset-texture {
  64. display: flex;
  65. flex: 1;
  66. flex-direction: column;
  67. padding-right: 4px;
  68. }
  69. .asset-texture > .content {
  70. flex: 1;
  71. }
  72. .asset-texture > .content .filter-advanced-section,
  73. .asset-texture > .content .wrap-advanced-section,
  74. .asset-texture > .content .generate-mipmaps-section {
  75. margin-left: 1.2em;
  76. display: none;
  77. }
  78. .asset-texture > .content ui-prop.warn {
  79. color: var(--color-warn-fill);
  80. }
  81. .asset-texture > .content > ui-prop.warn ui-select {
  82. border-color: var(--color-warn-fill);
  83. }
  84. .asset-texture > .content > .warn-words {
  85. display: none;
  86. margin-top: 4px;
  87. color: var(--color-warn-fill);
  88. }
  89. .asset-texture > .preview {
  90. position: relative;
  91. height: 200px;
  92. overflow: hidden;
  93. display: flex;
  94. justify-content: center;
  95. align-items: center;
  96. padding: 10px;
  97. background: var(--color-normal-fill-emphasis);
  98. border: 1px solid var(--color-normal-border-emphasis);
  99. }
  100. .asset-texture > .preview:hover {
  101. border-color: var(--color-warn-fill);
  102. }
  103. .filter-different {
  104. color: var(--color-warn-fill);
  105. display: none;
  106. }
  107. .filter-different .atlas-file-name span {
  108. cursor: pointer;
  109. text-decoration: underline;
  110. }
  111. `;
  112. exports.$ = {
  113. container: '.asset-texture',
  114. anisotropyInput: '.anisotropy-input',
  115. filterModeSelect: '.filterMode-select',
  116. filterAdvancedSection: '.filter-advanced-section',
  117. minfilterSelect: '.minfilter-select',
  118. magfilterSelect: '.magfilter-select',
  119. generateMipmapsSection: '.generate-mipmaps-section',
  120. generateMipmapsCheckbox: '.generate-mipmaps-checkbox',
  121. mipfilterSelect: '.mipfilter-select',
  122. wrapModeProp: '.wrapMode-prop',
  123. wrapModeSelect: '.wrapMode-select',
  124. wrapAdvancedSection: '.wrap-advanced-section',
  125. wrapModeSProp: '.wrapModeS-prop',
  126. wrapModeSSelect: '.wrapModeS-select',
  127. wrapModeTProp: '.wrapModeT-prop',
  128. wrapModeTSelect: '.wrapModeT-select',
  129. warnWords: '.warn-words',
  130. filterDifferent: '.filter-different',
  131. atlasFileName: '.filter-different .atlas-file-name',
  132. };
  133. const ModeMap = {
  134. filter: {
  135. 'Nearest (None)': {
  136. minfilter: 'nearest',
  137. magfilter: 'nearest',
  138. mipfilter: 'none',
  139. },
  140. Bilinear: {
  141. minfilter: 'linear',
  142. magfilter: 'linear',
  143. mipfilter: 'none',
  144. },
  145. 'Bilinear with Mipmaps': {
  146. minfilter: 'linear',
  147. magfilter: 'linear',
  148. mipfilter: 'nearest',
  149. },
  150. 'Trilinear with Mipmaps': {
  151. minfilter: 'linear',
  152. magfilter: 'linear',
  153. mipfilter: 'linear',
  154. },
  155. },
  156. wrap: {
  157. Repeat: {
  158. wrapModeS: 'repeat',
  159. wrapModeT: 'repeat',
  160. },
  161. Clamp: {
  162. wrapModeS: 'clamp-to-edge',
  163. wrapModeT: 'clamp-to-edge',
  164. },
  165. Mirror: {
  166. wrapModeS: 'mirrored-repeat',
  167. wrapModeT: 'mirrored-repeat',
  168. },
  169. },
  170. };
  171. exports.ModeMap = ModeMap;
  172. const Elements = {
  173. anisotropy: {
  174. ready() {
  175. const panel = this;
  176. panel.$.anisotropyInput.addEventListener('change', (event) => {
  177. panel.userDataList.forEach((userData) => {
  178. userData.anisotropy = event.target.value;
  179. });
  180. panel.dispatch('change');
  181. });
  182. panel.$.anisotropyInput.addEventListener('confirm', () => {
  183. panel.dispatch('snapshot');
  184. });
  185. },
  186. update() {
  187. const panel = this;
  188. panel.$.anisotropyInput.value = panel.userData.anisotropy;
  189. panel.updateInvalid(panel.$.anisotropyInput, 'anisotropy');
  190. updateElementReadonly.call(panel, panel.$.anisotropyInput);
  191. },
  192. },
  193. filterMode: {
  194. ready() {
  195. const panel = this;
  196. panel.$.filterModeSelect.addEventListener('change', (event) => {
  197. // 根据 filterModeSelect 组合值同步相应的 min/mag/mip 到 userData
  198. const value = event.target.value;
  199. if (ModeMap.filter[value]) {
  200. panel.userDataList.forEach((userData) => {
  201. const data = ModeMap.filter[value];
  202. for (const key of Object.keys(data)) {
  203. userData[key] = data[key];
  204. }
  205. });
  206. // 选择 filterMode 组合选项,不显示自定义项
  207. panel.$.filterAdvancedSection.style.display = 'none';
  208. } else {
  209. // 选择 advanced 显示自定义项
  210. panel.$.filterAdvancedSection.style.display = 'block';
  211. }
  212. panel.dispatch('change');
  213. });
  214. panel.$.filterModeSelect.addEventListener('confirm', () => {
  215. panel.dispatch('snapshot');
  216. });
  217. },
  218. update() {
  219. const panel = this;
  220. let optionsHtml = '';
  221. // FilterMode 选项
  222. const types = Object.keys(ModeMap.filter).concat('Advanced');
  223. types.forEach((type) => {
  224. optionsHtml += `<option value="${type}">${type}</option>`;
  225. });
  226. panel.$.filterModeSelect.innerHTML = optionsHtml;
  227. // 匹配 filterModeSelect 值,没有匹配到组合,则为自定义 Advanced
  228. let value = 'Advanced';
  229. for (const filterKey of Object.keys(ModeMap.filter)) {
  230. const filterItem = ModeMap.filter[filterKey];
  231. let flag = true;
  232. for (const key of Object.keys(filterItem)) {
  233. if (panel.userData[key] !== filterItem[key]) {
  234. flag = false;
  235. break;
  236. }
  237. }
  238. if (flag) {
  239. value = filterKey;
  240. break;
  241. }
  242. }
  243. panel.$.filterModeSelect.value = value;
  244. // 更新时判断是否显示自定义项
  245. value === 'Advanced'
  246. ? (panel.$.filterAdvancedSection.style.display = 'block')
  247. : (panel.$.filterAdvancedSection.style.display = 'none');
  248. panel.updateInvalid(panel.$.filterModeSelect, 'filterMode');
  249. updateElementReadonly.call(panel, panel.$.filterModeSelect);
  250. },
  251. },
  252. minfilter: {
  253. ready() {
  254. const panel = this;
  255. panel.$.minfilterSelect.addEventListener('change', (event) => {
  256. panel.userDataList.forEach((userData) => {
  257. userData.minfilter = event.target.value;
  258. });
  259. panel.dispatch('change');
  260. });
  261. panel.$.minfilterSelect.addEventListener('confirm', () => {
  262. panel.dispatch('snapshot');
  263. });
  264. },
  265. update() {
  266. const panel = this;
  267. let optionsHtml = '';
  268. const types = ['nearest', 'linear'];
  269. types.forEach((type) => {
  270. optionsHtml += `<option value="${type}">${type.toUpperCase()}</option>`;
  271. });
  272. panel.$.minfilterSelect.innerHTML = optionsHtml;
  273. panel.$.minfilterSelect.value = panel.userData.minfilter || 'nearest';
  274. panel.updateInvalid(panel.$.minfilterSelect, 'minfilter');
  275. updateElementReadonly.call(panel, panel.$.minfilterSelect);
  276. },
  277. },
  278. magfilter: {
  279. ready() {
  280. const panel = this;
  281. panel.$.magfilterSelect.addEventListener('change', (event) => {
  282. panel.userDataList.forEach((userData) => {
  283. userData.magfilter = event.target.value;
  284. });
  285. panel.dispatch('change');
  286. });
  287. panel.$.magfilterSelect.addEventListener('confirm', () => {
  288. panel.dispatch('snapshot');
  289. });
  290. },
  291. update() {
  292. const panel = this;
  293. let optionsHtml = '';
  294. const types = ['nearest', 'linear'];
  295. types.forEach((type) => {
  296. optionsHtml += `<option value="${type}">${type.toUpperCase()}</option>`;
  297. });
  298. panel.$.magfilterSelect.innerHTML = optionsHtml;
  299. panel.$.magfilterSelect.value = panel.userData.magfilter || 'nearest';
  300. panel.updateInvalid(panel.$.magfilterSelect, 'magfilter');
  301. updateElementReadonly.call(panel, panel.$.magfilterSelect);
  302. },
  303. },
  304. generateMipmaps: {
  305. ready() {
  306. const panel = this;
  307. panel.$.generateMipmapsCheckbox.addEventListener('change', (event) => {
  308. panel.userDataList.forEach((userData) => {
  309. const value = event.target.value;
  310. if (!value) {
  311. // 没勾选 生成 mipmaps,不显示 mipfilter 选项
  312. userData.mipfilter = 'none';
  313. panel.$.generateMipmapsSection.style.display = 'none';
  314. } else {
  315. panel.$.generateMipmapsSection.style.display = 'block';
  316. // 为空的话默认 nearest
  317. if (panel.$.mipfilterSelect.value === 'none') {
  318. panel.$.mipfilterSelect.value = 'nearest';
  319. // TODO: 目前 ui-select 通过 .value 修改组件值后没有触发 change 事件,需要手动提交
  320. panel.$.mipfilterSelect.dispatch('change');
  321. }
  322. }
  323. });
  324. panel.dispatch('change');
  325. });
  326. panel.$.generateMipmapsCheckbox.addEventListener('confirm', () => {
  327. panel.dispatch('snapshot');
  328. });
  329. },
  330. update() {
  331. const panel = this;
  332. panel.$.generateMipmapsCheckbox.value = panel.userData.mipfilter ? panel.userData.mipfilter !== 'none' : false;
  333. // 更新时判断是否显示 mipfilter 选项
  334. panel.$.generateMipmapsCheckbox.value
  335. ? (panel.$.generateMipmapsSection.style.display = 'block')
  336. : (panel.$.generateMipmapsSection.style.display = 'none');
  337. panel.updateInvalid(panel.$.generateMipmapsCheckbox, 'generateMipmaps');
  338. updateElementReadonly.call(panel, panel.$.generateMipmapsCheckbox);
  339. },
  340. },
  341. mipfilter: {
  342. ready() {
  343. const panel = this;
  344. panel.$.mipfilterSelect.addEventListener('change', (event) => {
  345. panel.userDataList.forEach((userData) => {
  346. userData.mipfilter = event.target.value;
  347. });
  348. panel.dispatch('change');
  349. });
  350. panel.$.mipfilterSelect.addEventListener('confirm', () => {
  351. panel.dispatch('snapshot');
  352. });
  353. },
  354. update() {
  355. const panel = this;
  356. let optionsHtml = '';
  357. const types = ['nearest', 'linear'];
  358. types.forEach((type) => {
  359. optionsHtml += `<option value="${type}">${type.toUpperCase()}</option>`;
  360. });
  361. panel.$.mipfilterSelect.innerHTML = optionsHtml;
  362. panel.$.mipfilterSelect.value = panel.userData.mipfilter || 'nearest';
  363. panel.metaList &&
  364. panel.metaList.forEach((meta) => {
  365. Editor.Profile.setTemp('inspector', `${meta.uuid}.texture.mipfilter`, panel.userData.mipfilter);
  366. });
  367. panel.updateInvalid(panel.$.mipfilterSelect, 'mipfilter');
  368. updateElementReadonly.call(panel, panel.$.mipfilterSelect);
  369. },
  370. },
  371. wrapMode: {
  372. ready() {
  373. const panel = this;
  374. panel.$.wrapModeSelect.addEventListener('change', (event) => {
  375. // 根据 wrapModeSelect 组合值同步相应的 wrapModeS/wrapModeT 到 userData
  376. const value = event.target.value;
  377. // 临时记录用户的修改配置
  378. Editor.Profile.setTemp('inspector', `${this.meta.uuid}.texture.wrapMode`, value, 'default');
  379. if (ModeMap.wrap[value]) {
  380. panel.userDataList.forEach((userData) => {
  381. const data = ModeMap.wrap[value];
  382. for (const key of Object.keys(data)) {
  383. userData[key] = data[key];
  384. }
  385. });
  386. panel.$.wrapAdvancedSection.style.display = 'none';
  387. } else {
  388. // 选择 advanced 显示自定义项
  389. panel.$.wrapAdvancedSection.style.display = 'block';
  390. }
  391. // 校验是否显示警告提示
  392. Elements.warnWords.update.call(panel);
  393. panel.dispatch('change');
  394. });
  395. panel.$.wrapModeSelect.addEventListener('confirm', () => {
  396. panel.dispatch('snapshot');
  397. });
  398. },
  399. update() {
  400. const panel = this;
  401. let optionsHtml = '';
  402. // WrapMode 选项
  403. const types = Object.keys(ModeMap.wrap).concat('Advanced');
  404. types.forEach((type) => {
  405. optionsHtml += `<option value="${type}">${type}</option>`;
  406. });
  407. panel.$.wrapModeSelect.innerHTML = optionsHtml;
  408. // 匹配 wrapModeSelect 值,没有匹配到组合,则为自定义 Advanced
  409. let value = 'Advanced';
  410. for (const wrapKey of Object.keys(ModeMap.wrap)) {
  411. const wrapItem = ModeMap.wrap[wrapKey];
  412. let flag = true;
  413. for (const key of Object.keys(wrapItem)) {
  414. if (panel.userData[key] !== wrapItem[key]) {
  415. flag = false;
  416. break;
  417. }
  418. }
  419. if (flag) {
  420. value = wrapKey;
  421. break;
  422. }
  423. }
  424. panel.$.wrapModeSelect.value = value;
  425. // 更新时需要判断是否显示自定义项
  426. value === 'Advanced'
  427. ? (panel.$.wrapAdvancedSection.style.display = 'block')
  428. : (panel.$.wrapAdvancedSection.style.display = 'none');
  429. // 校验是否显示警告提示
  430. panel.updateInvalid(panel.$.wrapModeSelect, 'wrapMode');
  431. updateElementReadonly.call(panel, panel.$.wrapModeSelect);
  432. },
  433. },
  434. wrapModeS: {
  435. ready() {
  436. const panel = this;
  437. panel.$.wrapModeSSelect.addEventListener('change', (event) => {
  438. panel.userDataList.forEach((userData) => {
  439. userData.wrapModeS = event.target.value;
  440. });
  441. Elements.warnWords.update.call(panel);
  442. panel.dispatch('change');
  443. });
  444. panel.$.wrapModeSSelect.addEventListener('confirm', () => {
  445. panel.dispatch('snapshot');
  446. });
  447. },
  448. update() {
  449. const panel = this;
  450. let optionsHtml = '';
  451. const types = {
  452. Repeat: 'repeat',
  453. Clamp: 'clamp-to-edge',
  454. Mirror: 'mirrored-repeat',
  455. };
  456. for (const type in types) {
  457. optionsHtml += `<option value="${types[type]}">${type}</option>`;
  458. }
  459. panel.$.wrapModeSSelect.innerHTML = optionsHtml;
  460. panel.$.wrapModeSSelect.value = panel.userData.wrapModeS || 'repeat';
  461. panel.updateInvalid(panel.$.wrapModeSSelect, 'wrapModeS');
  462. updateElementReadonly.call(panel, panel.$.wrapModeSSelect);
  463. },
  464. },
  465. wrapModeT: {
  466. ready() {
  467. const panel = this;
  468. panel.$.wrapModeTSelect.addEventListener('change', (event) => {
  469. panel.userDataList.forEach((userData) => {
  470. userData.wrapModeT = event.target.value;
  471. });
  472. Elements.warnWords.update.call(panel);
  473. panel.dispatch('change');
  474. });
  475. panel.$.wrapModeTSelect.addEventListener('confirm', () => {
  476. panel.dispatch('snapshot');
  477. });
  478. },
  479. update() {
  480. const panel = this;
  481. let optionsHtml = '';
  482. const types = {
  483. Repeat: 'repeat',
  484. Clamp: 'clamp-to-edge',
  485. Mirror: 'mirrored-repeat',
  486. };
  487. for (const type in types) {
  488. optionsHtml += `<option value="${types[type]}">${type}</option>`;
  489. }
  490. panel.$.wrapModeTSelect.innerHTML = optionsHtml;
  491. panel.$.wrapModeTSelect.value = panel.userData.wrapModeT || 'repeat';
  492. panel.updateInvalid(panel.$.wrapModeTSelect, 'wrapModeT');
  493. updateElementReadonly.call(panel, panel.$.wrapModeTSelect);
  494. },
  495. },
  496. /**
  497. * Condition check: whether the width and height of the image is a power of 2
  498. * A warning message is required if the wrap mode value is repeat.
  499. */
  500. warnWords: {
  501. ready() {
  502. this.$.image = document.createElement('ui-image');
  503. this.$.image.$img.addEventListener('load', () => {
  504. Elements.warnWords.update.call(this);
  505. });
  506. },
  507. update() {
  508. const panel = this;
  509. this.$.image.value = panel.asset.uuid;
  510. let isUnlegalWrapModeT = false;
  511. let isUnlegalWrapModeS = false;
  512. if (panel.$.image.$img.src) {
  513. const { naturalWidth, naturalHeight } = panel.$.image.$img;
  514. const { wrapModeT, wrapModeS } = panel.userData;
  515. // Determine the power of 2 algorithm: (number & number - 1) === 0
  516. const isUnlegal = naturalWidth & (naturalWidth - 1) || naturalHeight & (naturalHeight - 1);
  517. isUnlegalWrapModeT = isUnlegal && wrapModeT === 'repeat';
  518. isUnlegalWrapModeS = isUnlegal && wrapModeS === 'repeat';
  519. }
  520. if (isUnlegalWrapModeT || isUnlegalWrapModeS) {
  521. this.$.warnWords.style.display = 'block';
  522. this.$.wrapModeSProp.classList.add('warn');
  523. this.$.wrapModeTProp.classList.add('warn');
  524. this.$.wrapModeProp.classList.add('warn');
  525. } else {
  526. this.$.warnWords.style.display = 'none';
  527. this.$.wrapModeSProp.classList.remove('warn');
  528. this.$.wrapModeTProp.classList.remove('warn');
  529. this.$.wrapModeProp.classList.remove('warn');
  530. }
  531. },
  532. },
  533. checkAtlasFileConfig: {
  534. async ready() {
  535. this.$.atlasFileName.addEventListener(
  536. 'click',
  537. () => {
  538. Editor.Message.send('assets', 'twinkle', this.$.atlasFileName.getAttribute('data-uuid'));
  539. },
  540. false,
  541. );
  542. },
  543. async update() {
  544. try {
  545. if (!Array.isArray(this.parentAssetList)) {
  546. this.$.filterDifferent.style.display = 'none';
  547. return;
  548. }
  549. const parentPath = this.parentAssetList[0].path.replace(/\/[^/]+$/, '/*');
  550. let assets = await Editor.Message.request('asset-db', 'query-assets', { pattern: parentPath, ccType: 'cc.Asset' });
  551. assets = assets.filter((v) => extname(v.file) === '.atlas');
  552. let matchedAsset;
  553. let matchedAtlasJson;
  554. for (const asset of assets) {
  555. const json = await new ParseAtlasFile().parse(asset.file);
  556. // the asset.displayName is not a full name, miss the extname
  557. // so, we should use RegExp to get the extname
  558. const regStr = `${this.asset.displayName}(.*?)/`;
  559. const match = this.asset.url.match(new RegExp(regStr));
  560. const imageExtname = match ? match[1] : '.png';
  561. const imageFullName = this.asset.displayName + imageExtname;
  562. if (json[imageFullName]) {
  563. matchedAsset = asset;
  564. matchedAtlasJson = json[imageFullName];
  565. break;
  566. }
  567. }
  568. if (matchedAsset) {
  569. let atlasFileFilter = matchedAtlasJson.filter;
  570. if (Array.isArray(atlasFileFilter)) {
  571. atlasFileFilter = atlasFileFilter.join();
  572. } else if (atlasFileFilter) {
  573. atlasFileFilter = `${atlasFileFilter},${atlasFileFilter}`;
  574. }
  575. const userDataFilter = `${this.meta.userData.minfilter},${this.meta.userData.magfilter}`;
  576. if (atlasFileFilter && atlasFileFilter.toLowerCase() !== userDataFilter.toLowerCase()) {
  577. this.$.filterDifferent.style.display = 'block';
  578. const tipHtml = Editor.I18n.t('ENGINE.assets.texture.filterDiffenent').replace(
  579. '{atlasFile}',
  580. `<span>${matchedAsset.name}</span>`,
  581. );
  582. this.$.atlasFileName.innerHTML = tipHtml;
  583. this.$.atlasFileName.setAttribute('data-uuid', matchedAsset.uuid);
  584. } else {
  585. this.$.filterDifferent.style.display = 'none';
  586. }
  587. }
  588. } catch (error) {
  589. this.$.filterDifferent.style.display = 'none';
  590. console.warn('parse atlas file error:', error);
  591. }
  592. },
  593. },
  594. };
  595. exports.Elements = Elements;
  596. exports.methods = {
  597. /**
  598. * Update whether a data is editable in multi-select state
  599. * 多选时候,以选中的第一个作为标准,如果选中其中有一个数据不一致,则该选项无效,需要重新选择
  600. */
  601. updateInvalid(element, prop) {
  602. // filterMode、 wrapMode 和 generateMipmaps 需要拆解进行判断
  603. let invalid;
  604. switch (prop) {
  605. case 'filterMode':
  606. invalid = this.userDataList.some((userData) => {
  607. for (const key of Object.keys(ModeMap.filter.Bilinear)) {
  608. if (userData[key] !== this.userData[key]) {
  609. return true;
  610. }
  611. }
  612. return false;
  613. });
  614. break;
  615. case 'wrapMode':
  616. invalid = this.userDataList.some((userData) => {
  617. for (const key of Object.keys(ModeMap.wrap.Repeat)) {
  618. if (userData[key] !== this.userData[key]) {
  619. return true;
  620. }
  621. }
  622. return false;
  623. });
  624. break;
  625. case 'generateMipmaps':
  626. invalid = this.userDataList.some((userData) => {
  627. return userData['mipfilter'] !== this.userData['mipfilter'];
  628. });
  629. break;
  630. default:
  631. invalid = this.userDataList.some((userData) => {
  632. return userData[prop] !== this.userData[prop];
  633. });
  634. break;
  635. }
  636. element.invalid = invalid;
  637. },
  638. };
  639. exports.ready = function () {
  640. for (const prop in Elements) {
  641. const element = Elements[prop];
  642. if (element.ready) {
  643. element.ready.call(this);
  644. }
  645. }
  646. };