animation.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229
  1. 'use strict';
  2. const { updateElementReadonly, updateElementInvalid, setPropValue, getPropValue } = require('../../utils/assets');
  3. exports.template = /* html */`
  4. <div class="container">
  5. <div class="animator-config">
  6. <ui-prop class="animator-config-import-all-animator">
  7. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.GlTFUserData.mountAllAnimationsOnPrefab.name"></ui-label>
  8. <ui-checkbox slot="content"></ui-checkbox>
  9. </ui-prop>
  10. </div>
  11. <div class="show-type-wrap">
  12. <ui-tab class="show-type" value="0">
  13. <ui-button value="time">Time</ui-button>
  14. <ui-button value="frame">Frame</ui-button>
  15. </ui-tab>
  16. </div>
  17. <div class="clips"></div>
  18. <div class="editor">
  19. <div class="anim-name">
  20. <ui-icon value="video"></ui-icon>
  21. <ui-input class="clip-name"></ui-input>
  22. </div>
  23. <div class="clip-info">
  24. <div class="left">Time: <span class="clip-duration"></span> (s)</div>
  25. <div class="right">
  26. <ui-num-input class="clip-fps" min="1" max="120" step="1"></ui-num-input>
  27. FPS
  28. </div>
  29. </div>
  30. <div class="edit-ruler">
  31. <div class="grid ruler-making"></div>
  32. <div class="grid ruler-gear"></div>
  33. <div class="control-wrap">
  34. <div class="control-duration"></div>
  35. <div class="control control-left">
  36. <div class="box"></div>
  37. <div class="direction"></div>
  38. </div>
  39. <div class="control control-right">
  40. <div class="box"></div>
  41. <div class="direction"></div>
  42. </div>
  43. <div class="control control-virtual">
  44. <div class="box"></div>
  45. <div class="direction"></div>
  46. <div class="number control-virtual-number"></div>
  47. </div>
  48. </div>
  49. </div>
  50. <div class="cut-info">
  51. <div class="left">
  52. <span>Start: </span>
  53. <ui-num-input path="from" step="1" min="0" class="clip-from"></ui-num-input>
  54. </div>
  55. <div class="frames-info">
  56. <div class="left">Frames: <span class="clip-frames"></span></div>
  57. </div>
  58. <div class="right">
  59. <span>End: </span>
  60. <ui-num-input path="to" step="1" min="0" class="clip-to"></ui-num-input>
  61. </div>
  62. </div>
  63. <ui-prop>
  64. <span slot="label">WrapMode</span>
  65. <ui-select slot="content" class="wrap-mode">
  66. <option value="0">Default</option>
  67. <option value="1">Normal</option>
  68. <option value="2">Loop</option>
  69. <option value="22">PingPong</option>
  70. <option value="36">Reverse</option>
  71. <option value="38">LoopReverse</option>
  72. </ui-select>
  73. </ui-prop>
  74. <ui-prop>
  75. <span slot="label">Speed</span>
  76. <ui-num-input slot="content" class="speed"></ui-num-input>
  77. </ui-prop>
  78. <ui-section expand cache-expand="inspector-asset-fbx-animation-additive">
  79. <ui-label slot="header" value="i18n:ENGINE.assets.fbx.animationSetting.additive.header"></ui-label>
  80. <ui-prop>
  81. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.animationSetting.additive.enabled.label"></ui-label>
  82. <ui-checkbox slot="content" class="additive-enabled"
  83. tooltip="i18n:ENGINE.assets.fbx.animationSetting.additive.enabled.tooltip"></ui-checkbox>
  84. </ui-prop>
  85. <ui-prop ui="asset">
  86. <ui-label slot="label" value="i18n:ENGINE.assets.fbx.animationSetting.additive.refClip.label"></ui-label>
  87. <ui-asset slot="content" droppable="cc.AnimationClip" class="ref-clip"
  88. tooltip="i18n:ENGINE.assets.fbx.animationSetting.additive.refClip.tooltip"></ui-asset>
  89. </ui-prop>
  90. </ui-section>
  91. </div>
  92. <ui-label class="multiple-warn-tip" value="i18n:ENGINE.assets.multipleWarning"></ui-label>
  93. </div>
  94. `;
  95. exports.style = /* css */`
  96. .container {
  97. padding: 4px;
  98. }
  99. .container[multiple-invalid] > *:not(.multiple-warn-tip) {
  100. display: none!important;
  101. }
  102. .container[multiple-invalid] > .multiple-warn-tip {
  103. display: block;
  104. }
  105. .container .multiple-warn-tip {
  106. display: none;
  107. text-align: center;
  108. color: var(--color-focus-contrast-weakest);
  109. margin-top: 8px;
  110. }
  111. .container > .show-type-wrap {
  112. text-align: center;
  113. margin-top: 8px;
  114. }
  115. .container > .clips {
  116. padding: 4px;
  117. border-radius: calc(var(--size-normal-radius) * 1px);
  118. overflow-y: auto;
  119. max-height: 250px;
  120. background: var(--color-normal-fill-emphasis);
  121. margin-bottom: 8px;
  122. }
  123. .container > .clips > .clip {}
  124. .container > .clips > .clip > .table {
  125. border-radius: 4px;
  126. border-bottom-right-radius: 0;
  127. background: var(--color-normal-fill-emphasis);
  128. }
  129. .container > .clips > .clip > .table > .header {
  130. display: flex;
  131. padding: 0 4px;
  132. margin-bottom: 4px;
  133. border-bottom: 1px solid var(--color-default-border);
  134. color: var(--color-default-fill-weakest);
  135. }
  136. .container > .clips > .clip > .table > .line {
  137. display: flex;
  138. padding: 0 4px;
  139. cursor: pointer;
  140. }
  141. .container > .clips > .clip > .table > .line[active] {
  142. background-color: var(--color-info-fill-important);
  143. }
  144. .container > .clips > .clip > .table > .line > .name,
  145. .container > .clips > .clip > .table > .header > .name {
  146. flex: 1;
  147. }
  148. .container > .clips > .clip > .table > .line > .time,
  149. .container > .clips > .clip > .table > .header > .time {
  150. width: 40px;
  151. text-align: right;
  152. }
  153. .container > .clips > .clip > .add-clip {
  154. display: flex;
  155. justify-content: flex-end;
  156. }
  157. .container > .clips > .clip > .add-clip > .button > ui-icon {
  158. padding: 0 4px;
  159. border-radius: 2px;
  160. line-height: 16px;
  161. margin-left: 10px;
  162. margin-right: 2px;
  163. cursor: pointer;
  164. }
  165. .container > .clips > .clip > .add-clip > .button > ui-icon:hover {
  166. background: var(--color-normal-fill);
  167. }
  168. .container > .clips > .clip > .add-clip > .button > ui-icon[disabled] {
  169. opacity: 0.55;
  170. pointer-events: none;
  171. }
  172. .container > .editor[disabled] {
  173. opacity: 0.55;
  174. pointer-events: none;
  175. }
  176. .container > .editor > .anim-name {
  177. display: flex;
  178. justify-content: space-between;
  179. margin-top: 10px;
  180. margin-bottom: 5px;
  181. }
  182. .container > .editor > .anim-name {
  183. display: flex;
  184. justify-content: space-between;
  185. margin-top: 10px;
  186. margin-bottom: 5px;
  187. }
  188. .container > .editor > .anim-name > ui-icon {
  189. font-size: 18px;
  190. margin: auto 10px;
  191. margin-left: 0;
  192. }
  193. .container > .editor > .anim-name > ui-input {
  194. flex: 1;
  195. }
  196. .container > .editor > .clip-info {
  197. font-size: 11px;
  198. color: var(--color-normal-fill-weakest);
  199. display: flex;
  200. justify-content: space-between;
  201. margin-bottom: 5px;
  202. }
  203. .container > .editor ui-num-input {
  204. color: var(--color-normal-contrast);
  205. width: 50px;
  206. }
  207. .container > .editor > .cut-info {
  208. margin: 5px 0 10px;
  209. line-height: 20px;
  210. display: flex;
  211. justify-content: space-between;
  212. }
  213. .container > .editor > .cut-info > .frames-info {
  214. font-size: 11px;
  215. color: var(--color-normal-fill-weakest);
  216. }
  217. .container > .editor > .edit-ruler {
  218. border: 1px solid var(--color-normal-contrast-important);
  219. border-top: transparent;
  220. padding: 0 20px;
  221. display: flex;
  222. flex-direction: column;
  223. position: relative;
  224. user-select: none;
  225. }
  226. .container > .editor > .edit-ruler > .grid {
  227. position: relative;
  228. display: flex;
  229. font-size: 10px;
  230. box-sizing: border-box;
  231. height: 18px;
  232. }
  233. .container > .editor > .edit-ruler > .grid > .label-item {
  234. position: absolute;
  235. }
  236. .container > .editor > .edit-ruler > .ruler-gear > .start {
  237. position: absolute;
  238. top: 3px;
  239. }
  240. .container > .editor > .edit-ruler > .ruler-gear > .grid-item {
  241. display: flex;
  242. justify-content: space-between;
  243. align-items: flex-end;
  244. }
  245. .container > .editor > .edit-ruler > .grid .sm-grid {
  246. display: inline-block;
  247. width: 20%;
  248. height: 4px;
  249. border-left: 1px solid var(--color-normal-contrast-important);
  250. }
  251. .container > .editor > .edit-ruler > .grid .mid-grid {
  252. display: inline-block;
  253. width: 20%;
  254. height: 8px;
  255. border-left: 1px solid var(--color-normal-contrast-important);
  256. }
  257. .container > .editor > .edit-ruler > .control-wrap {
  258. position: relative;
  259. width: 100%;
  260. left: 0;
  261. top: -16px;
  262. }
  263. .container > .editor > .edit-ruler > .control-wrap > .control-duration {
  264. background: var(--color-focus-fill);
  265. position: absolute;
  266. height: 16px;
  267. opacity: 0.2;
  268. }
  269. .container > .editor > .edit-ruler > .control-wrap > .control {
  270. user-select: none;
  271. width: 6px;
  272. position: absolute;
  273. cursor: pointer;
  274. opacity: 0.7;
  275. }
  276. .container > .editor > .edit-ruler > .control-wrap > .control:hover {
  277. opacity: 1;
  278. }
  279. .container > .editor > .edit-ruler > .control-wrap > .control > .box {
  280. background: var(--color-focus-fill);
  281. width: 100%;
  282. height: 10px;
  283. }
  284. .container > .editor > .edit-ruler > .control-wrap > .control > .direction {
  285. border: 3px solid var(--color-focus-fill);
  286. width: 0;
  287. height: 0;
  288. border-bottom-color: transparent;
  289. }
  290. .container > .editor > .edit-ruler > .control-wrap > .control-left > .direction {
  291. border-left-color: transparent;
  292. }
  293. .container > .editor > .edit-ruler > .control-wrap > .control-right > .direction {
  294. border-right-color: transparent;
  295. }
  296. .container > .editor > .edit-ruler > .control-wrap > .control-virtual {
  297. --color-focus-fill: var(--color-success-fill);
  298. display: none;
  299. }
  300. .container > .editor > .edit-ruler > .control-wrap > .control-virtual > .box {
  301. background: var(--color-focus-fill);
  302. width: 100%;
  303. height: 10px;
  304. }
  305. .container > .editor > .edit-ruler > .control-wrap > .control-virtual[direction="right"] > .direction {
  306. border-right-color: transparent;
  307. border-left-color: var(--color-focus-fill);
  308. }
  309. .container > .editor > .edit-ruler > .control-wrap > .control-virtual > .direction {
  310. border-left-color: transparent;
  311. }
  312. .container > .editor > .edit-ruler > .control-wrap > .control-virtual > .number {
  313. margin-top: -47px;
  314. font-size: 10px;
  315. color: var(--color-success-fill-weaker);
  316. }
  317. `;
  318. exports.$ = {
  319. container: '.container',
  320. importAllAnimationsCheckbox: '.animator-config-import-all-animator ui-checkbox',
  321. importAllAnimatorWrap: '.animator-config-import-all-animator',
  322. clips: '.clips',
  323. editor: '.editor',
  324. clipName: '.clip-name',
  325. clipDuration: '.clip-duration',
  326. clipFPS: '.clip-fps',
  327. clipFrom: '.clip-from',
  328. clipTo: '.clip-to',
  329. clipFrames: '.clip-frames',
  330. wrapMode: '.wrap-mode',
  331. speed: '.speed',
  332. additiveEnabled: '.additive-enabled',
  333. refClip: '.ref-clip',
  334. rulerMaking: '.ruler-making',
  335. rulerGear: '.ruler-gear',
  336. controlWrap: '.control-wrap',
  337. controlDuration: '.control-duration',
  338. controlLeft: '.control-left',
  339. controlRight: '.control-right',
  340. controlVirtual: '.control-virtual',
  341. controlVirtualNumber: '.control-virtual-number',
  342. showTypeWrap: '.show-type-wrap',
  343. showType: '.show-type',
  344. };
  345. /**
  346. * attribute corresponds to the edit element
  347. */
  348. const Elements = {
  349. // infos put first
  350. infos: {
  351. ready() {
  352. const panel = this;
  353. Object.assign(panel, {
  354. animationInfos: null,
  355. });
  356. },
  357. update() {
  358. const panel = this;
  359. if (panel.meta && panel.meta.userData.animationImportSettings) {
  360. panel.animationInfos = panel.meta.userData.animationImportSettings;
  361. // Support multiple selection when the list display, limit the number of display clip name collection for renaming and new to determine whether the same name
  362. panel.clipNames = new Set();
  363. for (const animationInfo of panel.animationInfos) {
  364. panel.clipNames.add(animationInfo.name);
  365. for (const subAnimInfo of animationInfo.splits) {
  366. panel.clipNames.add(subAnimInfo.name);
  367. }
  368. }
  369. } else {
  370. panel.animationInfos = null;
  371. }
  372. },
  373. },
  374. showType: {
  375. ready() {
  376. const panel = this;
  377. panel.animationTimeShowType = panel.$.showType.value === 0 ? 'time' : 'frame';
  378. panel.$.showType.addEventListener('change', (event) => {
  379. panel.animationTimeShowType = event.target.value === 0 ? 'time' : 'frame';
  380. Elements.clips.update.call(panel);
  381. });
  382. },
  383. update() {
  384. const panel = this;
  385. if (!panel.animationInfos) {
  386. panel.$.showTypeWrap.style.display = 'none';
  387. return;
  388. } else {
  389. panel.$.showTypeWrap.style.display = 'block';
  390. }
  391. panel.animationTimeShowType = panel.$.showType.value === 0 ? 'time' : 'frame';
  392. },
  393. },
  394. clips: {
  395. ready() {
  396. const panel = this;
  397. Object.assign(panel, {
  398. splitClipIndex: 0,
  399. rawClipIndex: 0,
  400. currentClipInfo: null,
  401. });
  402. },
  403. update() {
  404. const panel = this;
  405. panel.$.clips.innerText = '';
  406. if (!panel.animationInfos) {
  407. panel.$.clips.style.display = 'none';
  408. return;
  409. } else {
  410. panel.$.clips.style.display = 'block';
  411. }
  412. panel.updateRawClipInfo();
  413. panel.updateCurrentClipInfo();
  414. panel.animationInfos.forEach((animInfo, rawClipIndex) => {
  415. const clip = document.createElement('div');
  416. clip.setAttribute('class', 'clip');
  417. panel.$.clips.appendChild(clip);
  418. if (!animInfo.duration) {
  419. clip.setAttribute('disabled', 'true');
  420. }
  421. const table = document.createElement('div');
  422. table.setAttribute('class', 'table');
  423. clip.appendChild(table);
  424. const header = document.createElement('div');
  425. header.setAttribute('class', 'header');
  426. table.appendChild(header);
  427. const name = document.createElement('div');
  428. name.setAttribute('class', 'name');
  429. name.innerHTML = `Clips <i>( ${animInfo.name} )</i> `;
  430. header.appendChild(name);
  431. const time = document.createElement('div');
  432. time.setAttribute('class', 'time');
  433. time.innerHTML = 'Start';
  434. header.appendChild(time);
  435. const timeEnd = document.createElement('div');
  436. timeEnd.setAttribute('class', 'time end');
  437. timeEnd.innerHTML = 'End';
  438. header.appendChild(timeEnd);
  439. animInfo.splits.forEach((subAnim, splitClipIndex) => {
  440. const line = document.createElement('div');
  441. line.setAttribute('class', 'line');
  442. if (panel.rawClipIndex === rawClipIndex && panel.splitClipIndex === splitClipIndex) {
  443. line.setAttribute('active', true);
  444. }
  445. table.appendChild(line);
  446. line.setAttribute('rawCLipIndex', rawClipIndex);
  447. line.setAttribute('splitClipIndex', splitClipIndex);
  448. line.addEventListener('click', () => {
  449. panel.onSelect(rawClipIndex, splitClipIndex);
  450. });
  451. const name = document.createElement('div');
  452. name.setAttribute('class', 'name');
  453. name.innerHTML = subAnim.name;
  454. line.appendChild(name);
  455. const time = document.createElement('div');
  456. time.setAttribute('class', 'time');
  457. time.innerHTML = panel.animationTimeShowType === 'time' ? subAnim.from.toFixed(3) : Math.round(subAnim.from * (subAnim.fps || panel.rawClipInfo.fps));
  458. line.appendChild(time);
  459. const timeEnd = document.createElement('div');
  460. timeEnd.setAttribute('class', 'time end');
  461. timeEnd.innerHTML = panel.animationTimeShowType === 'time' ? subAnim.to.toFixed(3) : Math.round(subAnim.to * (subAnim.fps || panel.rawClipInfo.fps));
  462. line.appendChild(timeEnd);
  463. });
  464. // Button area
  465. const addClip = document.createElement('div');
  466. addClip.setAttribute('class', 'add-clip');
  467. clip.appendChild(addClip);
  468. const button = document.createElement('div');
  469. button.setAttribute('class', 'button');
  470. addClip.appendChild(button);
  471. const addIcon = document.createElement('ui-icon');
  472. addIcon.setAttribute('value', 'add');
  473. addIcon.setAttribute('tooltip', 'Duplicate Selected');
  474. updateElementReadonly.call(panel, addIcon);
  475. button.appendChild(addIcon);
  476. addIcon.addEventListener('click', () => {
  477. const newInfo = panel.newClipTemplate();
  478. panel.clipNames.add(newInfo.name);
  479. panel.animationInfos[panel.rawClipIndex].splits.push(newInfo);
  480. Elements.clips.update.call(panel);
  481. Elements.editor.update.call(panel);
  482. panel.dispatch('change');
  483. panel.dispatch('snapshot');
  484. });
  485. const miniIcon = document.createElement('ui-icon');
  486. miniIcon.setAttribute('value', 'mini');
  487. miniIcon.setAttribute('tooltip', 'Remove Selected');
  488. updateElementReadonly.call(panel, miniIcon);
  489. button.prepend(miniIcon);
  490. miniIcon.addEventListener('click', () => {
  491. panel.updateCurrentClipInfo();
  492. if (!panel.currentClipInfo) {
  493. return;
  494. }
  495. panel.clipNames.delete(panel.currentClipInfo.name);
  496. panel.animationInfos[panel.rawClipIndex].splits.splice(panel.splitClipIndex, 1);
  497. const length = panel.animationInfos[panel.rawClipIndex].splits.length;
  498. if (length > 0 && panel.splitClipIndex > 0 && panel.splitClipIndex >= length) {
  499. panel.splitClipIndex = length - 1;
  500. }
  501. Elements.clips.update.call(panel);
  502. Elements.editor.update.call(panel);
  503. panel.dispatch('change');
  504. panel.dispatch('snapshot');
  505. });
  506. });
  507. },
  508. },
  509. editor: {
  510. ready() {
  511. const panel = this;
  512. Object.assign(panel, {
  513. gridTableWith: panel.$.container.getBoundingClientRect().width,
  514. virtualControl: null,
  515. clipNames: [],
  516. });
  517. panel.onClipNameBind = panel.onClipName.bind(panel);
  518. panel.$.clipName.addEventListener('confirm', panel.onClipNameBind);
  519. panel.onMouseDownBindLeft = panel.onMouseDown.bind(panel, 'left');
  520. panel.onMouseDownBindRight = panel.onMouseDown.bind(panel, 'right');
  521. panel.$.controlLeft.addEventListener('mousedown', panel.onMouseDownBindLeft);
  522. panel.$.controlRight.addEventListener('mousedown', panel.onMouseDownBindRight);
  523. panel.onCutClipBind = panel.onCutClip.bind(panel);
  524. panel.$.clipFrom.addEventListener('confirm', panel.onCutClipBind);
  525. panel.$.clipTo.addEventListener('confirm', panel.onCutClipBind);
  526. panel.onFpsChangeBind = panel.onFpsChange.bind(panel);
  527. panel.$.clipFPS.addEventListener('confirm', panel.onFpsChangeBind);
  528. panel.onWrapModeChangeBind = panel.onWrapModeChange.bind(panel);
  529. panel.$.wrapMode.addEventListener('confirm', panel.onWrapModeChangeBind);
  530. panel.onSpeedChangeBind = panel.onSpeedChange.bind(panel);
  531. panel.$.speed.addEventListener('confirm', panel.onSpeedChangeBind);
  532. panel.onAdditiveEnabledChangedBind = panel.onAdditiveEnabledChanged.bind(panel);
  533. panel.$.additiveEnabled.addEventListener('confirm', panel.onAdditiveEnabledChangedBind);
  534. panel.onRefClipChangedBind = panel.onRefClipChanged.bind(panel);
  535. panel.$.refClip.addEventListener('confirm', panel.onRefClipChangedBind);
  536. function observer() {
  537. const rect = panel.$.editor.getBoundingClientRect();
  538. panel.gridTableWith = rect.width - 60;
  539. if (panel.gridTableWith < 0) {
  540. panel.gridTableWith = panel.$.container.getBoundingClientRect().width;
  541. }
  542. Elements.clips.update.call(panel);
  543. Elements.editor.update.call(panel);
  544. }
  545. panel.resizeObserver = new window.ResizeObserver(observer);
  546. panel.resizeObserver.observe(panel.$.editor);
  547. observer();
  548. },
  549. close() {
  550. const panel = this;
  551. panel.resizeObserver.unobserve(panel.$.editor);
  552. panel.$.clipName.removeEventListener('confirm', panel.onClipNameBind);
  553. panel.$.controlLeft.removeEventListener('mousedown', panel.onMouseDownBindLeft);
  554. panel.$.controlRight.removeEventListener('mousedown', panel.onMouseDownBindRight);
  555. panel.$.clipFrom.removeEventListener('confirm', panel.onCutClipBind);
  556. panel.$.clipTo.removeEventListener('confirm', panel.onCutClipBind);
  557. panel.$.clipFPS.removeEventListener('confirm', panel.onFpsChangeBind);
  558. panel.$.wrapMode.removeEventListener('confirm', panel.onWrapModeChangeBind);
  559. panel.$.speed.removeEventListener('confirm', panel.onSpeedChangeBind);
  560. panel.$.additiveEnabled.removeEventListener('confirm', panel.onAdditiveEnabledChangedBind);
  561. panel.$.refClip.removeEventListener('confirm', panel.onRefClipChangedBind);
  562. },
  563. update() {
  564. const panel = this;
  565. panel.updateRawClipInfo();
  566. panel.updateCurrentClipInfo();
  567. panel.updateGridConfig();
  568. if (!panel.currentClipInfo) {
  569. panel.$.editor.style.display = 'none';
  570. return;
  571. } else {
  572. panel.$.editor.style.display = 'block';
  573. }
  574. updateElementReadonly.call(panel, panel.$.editor);
  575. panel.$.clipName.value = panel.currentClipInfo.name;
  576. // ruler making
  577. panel.$.rulerMaking.innerText = '';
  578. const maxNum = panel.gridConfig.mod + 1;
  579. for (let minNum = 1; minNum <= maxNum; minNum++) {
  580. // If the remaining cells are less than 1.5, hide the label
  581. if (((panel.gridConfig.width / panel.gridConfig.spacing) % 5) >= 1.5 || minNum !== maxNum) {
  582. const label = document.createElement('div');
  583. label.setAttribute('class', 'label-item');
  584. label.style.left = `${panel.gridConfig.spacing * 5 * (minNum - 1) - 6}px`;
  585. panel.$.rulerMaking.appendChild(label);
  586. const span = document.createElement('span');
  587. span.setAttribute('class', 'mid-label');
  588. span.innerText = (panel.gridConfig.labelStep * (minNum - 1)).toFixed(3);
  589. label.appendChild(span);
  590. }
  591. }
  592. const lastMakingLabel = document.createElement('div');
  593. lastMakingLabel.setAttribute('class', 'label-item');
  594. lastMakingLabel.style.left = `${panel.gridConfig.width}px`;
  595. panel.$.rulerMaking.appendChild(lastMakingLabel);
  596. lastMakingLabel.innerText = panel.rawClipInfo.duration.toFixed(3);
  597. // ruler gear
  598. panel.$.rulerGear.innerText = '';
  599. Object.assign(panel.$.rulerGear.style, {
  600. 'margin-left': `${panel.gridConfig.spacing}px`,
  601. });
  602. const firstRulerGear = document.createElement('div');
  603. firstRulerGear.setAttribute('class', 'start');
  604. firstRulerGear.style.left = `${0 - panel.gridConfig.spacing}px`;
  605. firstRulerGear.innerHTML = '<span class="mid-grid"></span>';
  606. panel.$.rulerGear.appendChild(firstRulerGear);
  607. for (let minNum = 0; minNum < panel.gridConfig.mod; minNum++) {
  608. const item = document.createElement('div');
  609. item.setAttribute('class', 'grid-item');
  610. item.style.width = `${panel.gridConfig.spacing * 5}px`;
  611. const lines = Array(4).fill('<span class="sm-grid"></span>');
  612. lines.push('<span class="mid-grid"></span>');
  613. item.innerHTML = lines.join('');
  614. panel.$.rulerGear.appendChild(item);
  615. }
  616. const lastGearItem = document.createElement('div');
  617. lastGearItem.setAttribute('class', 'grid-item');
  618. lastGearItem.style.width = `${panel.gridConfig.rest * panel.gridConfig.spacing}px`;
  619. lastGearItem.innerHTML = Array(panel.gridConfig.rest).fill('<span class="sm-grid"></span>').join('');
  620. panel.$.rulerGear.appendChild(lastGearItem);
  621. // control-wrap
  622. Object.assign(panel.$.controlDuration.style, panel.currentClipInfo.durationStyle);
  623. Object.assign(panel.$.controlLeft.style, panel.currentClipInfo.ctrlStartStyle);
  624. Object.assign(panel.$.controlRight.style, panel.currentClipInfo.ctrlEndStyle);
  625. },
  626. },
  627. mountAllAnimationsOnPrefab: {
  628. ready() {
  629. const panel = this;
  630. panel.$.importAllAnimationsCheckbox.addEventListener('change', panel.setProp.bind(panel, 'mountAllAnimationsOnPrefab', 'boolean'));
  631. panel.$.importAllAnimationsCheckbox.addEventListener('confirm', () => {
  632. panel.dispatch('snapshot');
  633. });
  634. },
  635. update() {
  636. const panel = this;
  637. if (!panel.animationInfos) {
  638. panel.$.importAllAnimatorWrap.style.display = 'none';
  639. return;
  640. } else {
  641. panel.$.importAllAnimatorWrap.style.display = 'block';
  642. }
  643. panel.$.importAllAnimationsCheckbox.value = getPropValue.call(panel, panel.meta.userData.mountAllAnimationsOnPrefab, true);
  644. updateElementInvalid.call(panel, panel.$.importAllAnimationsCheckbox, 'mountAllAnimationsOnPrefab');
  645. updateElementReadonly.call(panel, panel.$.importAllAnimationsCheckbox);
  646. },
  647. },
  648. };
  649. async function callModelPreviewFunction(funcName, ...args) {
  650. return await Editor.Message.request('scene', 'call-preview-function', 'scene:model-preview', funcName, ...args);
  651. }
  652. exports.methods = {
  653. /** animation name -> uuid */
  654. initAnimationNameToUUIDMap() {
  655. if (this.meta && this.meta.subMetas) {
  656. const animationNameToUUIDMap = new Map();
  657. Object.keys(this.meta.subMetas).forEach((id) => {
  658. const subMeta = this.meta.subMetas[id];
  659. if (subMeta.importer === 'gltf-animation') {
  660. const sourceName = subMeta.name;
  661. const animName = sourceName.slice(0, sourceName.lastIndexOf('.'));
  662. animationNameToUUIDMap.set(animName, subMeta.uuid);
  663. }
  664. });
  665. this.animationNameToUUIDMap = animationNameToUUIDMap;
  666. }
  667. },
  668. initAnimationInfos() {
  669. if (this.meta && this.meta.userData.animationImportSettings) {
  670. this.animationInfos = this.meta.userData.animationImportSettings;
  671. // Collect clip names for renaming and creating to determine whether the name is repeated
  672. this.clipNames = new Set();
  673. for (const animationInfo of this.animationInfos) {
  674. this.clipNames.add(animationInfo.name);
  675. for (const subAnimInfo of animationInfo.splits) {
  676. this.clipNames.add(subAnimInfo.name);
  677. }
  678. }
  679. } else {
  680. this.animationInfos = null;
  681. }
  682. },
  683. onSelect(rawClipIndex, splitClipIndex) {
  684. this.rawClipIndex = rawClipIndex;
  685. this.splitClipIndex = splitClipIndex;
  686. const isElementSelect = (element) => element.getAttribute('rawClipIndex') == rawClipIndex && element.getAttribute('splitClipIndex') == splitClipIndex;
  687. Elements.editor.update.call(this);
  688. this.$.clips.querySelectorAll('.line').forEach((child) => {
  689. if (isElementSelect(child)) {
  690. child.setAttribute('active', true);
  691. } else {
  692. child.removeAttribute('active');
  693. }
  694. });
  695. const curClipInfo = this.getCurClipInfo();
  696. Editor.Message.broadcast('fbx-inspector:animation-change', curClipInfo);
  697. },
  698. getCurClipInfo() {
  699. if (!this.animationInfos) {
  700. return null;
  701. }
  702. const animInfo = this.animationInfos[this.rawClipIndex];
  703. const splitInfo = animInfo.splits[this.splitClipIndex];
  704. if (!animInfo) {
  705. return null;
  706. }
  707. if (!splitInfo) {
  708. return null;
  709. }
  710. const rawClipUUID = this.animationNameToUUIDMap.get(animInfo.name);
  711. const clipUUID = this.animationNameToUUIDMap.get(splitInfo.name);
  712. let duration = animInfo.duration;
  713. let fps = animInfo.fps;
  714. let from = 0;
  715. let to = duration;
  716. if (splitInfo) {
  717. from = splitInfo.from;
  718. to = splitInfo.to;
  719. duration = to - from;
  720. if (splitInfo.fps !== undefined) {
  721. fps = splitInfo.fps;
  722. }
  723. // if (this.animationNameToUUIDMap.has(splitInfo.name)) {
  724. // clipUUID = this.animationNameToUUIDMap.get(splitInfo.name);
  725. // }
  726. }
  727. return {
  728. rawClipUUID,
  729. rawClipIndex: this.rawClipIndex,
  730. clipUUID,
  731. duration,
  732. fps,
  733. from,
  734. to,
  735. wrapMode: splitInfo.wrapMode,
  736. speed: splitInfo.speed || 1,
  737. additiveEnabled: splitInfo.additive?.enabled ?? false,
  738. refClip: splitInfo.additive?.refClip || '',
  739. };
  740. },
  741. getRightName(name) {
  742. if (!name) {
  743. return null;
  744. }
  745. const panel = this;
  746. do {
  747. const result = name.match(/(.*)_(\d{0,3})/);
  748. if (result) {
  749. name = `${result[1]}_${Number(result[2]) + 1}`;
  750. } else {
  751. name += '_1';
  752. }
  753. } while (panel.clipNames.has(name));
  754. return name;
  755. },
  756. newClipTemplate() {
  757. const panel = this;
  758. // Verify the name
  759. return {
  760. name: panel.getRightName(panel.rawClipInfo.name),
  761. from: 0,
  762. to: panel.rawClipInfo.duration,
  763. wrapMode: 2 /* Loop */,
  764. speed: 1,
  765. };
  766. },
  767. updateCurrentClipInfo() {
  768. const panel = this;
  769. if (!panel.animationInfos) {
  770. panel.currentClipInfo = null;
  771. return;
  772. }
  773. const info = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex];
  774. if (!info || !panel.gridTableWith) {
  775. panel.currentClipInfo = null;
  776. return;
  777. }
  778. const duration = info.to - info.from;
  779. const ctrlStart = (info.from / panel.rawClipInfo.duration) * panel.gridTableWith;
  780. const ctrlEnd = (info.to / panel.rawClipInfo.duration) * panel.gridTableWith;
  781. const durationWidth = (duration / panel.rawClipInfo.duration) * panel.gridTableWith;
  782. const fps = info.fps !== undefined ? info.fps : panel.rawClipInfo.fps;
  783. const wrapMode = info.wrapMode ?? panel.rawClipInfo.wrapMode;
  784. const speed = info.speed ?? panel.rawClipInfo.speed;
  785. const additiveEnabled = (info.additive?.enabled) ?? panel.rawClipInfo.additiveEnabled;
  786. const refClip = (info.additive?.refClip) ?? panel.rawClipInfo.refClip;
  787. panel.currentClipInfo = {
  788. name: info.name,
  789. from: info.from * fps,
  790. to: info.to * fps,
  791. ctrlStart,
  792. ctrlEnd,
  793. ctrlStartStyle: {
  794. transform: `translateX(${ctrlStart - 6}px)`,
  795. },
  796. ctrlEndStyle: {
  797. transform: `translateX(${ctrlEnd}px)`,
  798. },
  799. durationStyle: {
  800. width: `${durationWidth}px`,
  801. transform: `translateX(${ctrlStart}px)`,
  802. },
  803. duration,
  804. fps,
  805. wrapMode,
  806. speed,
  807. additiveEnabled,
  808. refClip,
  809. };
  810. const maxFrames = (panel.rawClipInfo.duration * panel.currentClipInfo.fps).toFixed(0);
  811. const startFrames = panel.currentClipInfo.from.toFixed(0);
  812. const endFrames = panel.currentClipInfo.to.toFixed(0);
  813. panel.$.clipFrames.innerText = maxFrames;
  814. panel.$.clipFPS.value = fps;
  815. // TODO: hack for bug at 3d-tasks#10113. Because the new value would be limited in min and max, should firstly remove min and max.
  816. panel.$.clipFrom.max = null;
  817. panel.$.clipTo.min = null;
  818. panel.$.clipTo.max = null;
  819. panel.$.clipFrom.value = startFrames;
  820. panel.$.clipFrom.setAttribute('max', endFrames);
  821. panel.$.clipTo.value = endFrames;
  822. panel.$.clipTo.setAttribute('min', startFrames);
  823. panel.$.clipTo.setAttribute('max', maxFrames);
  824. panel.$.wrapMode.value = panel.currentClipInfo.wrapMode;
  825. panel.$.speed.value = panel.currentClipInfo.speed || 1;
  826. panel.$.additiveEnabled.value = panel.currentClipInfo.additiveEnabled || false;
  827. panel.$.refClip.value = panel.currentClipInfo.refClip || '';
  828. },
  829. updateRawClipInfo() {
  830. const panel = this;
  831. if (!panel.animationInfos) {
  832. panel.rawClipInfo = null;
  833. return;
  834. }
  835. if (!panel.animationInfos[panel.rawClipIndex]) {
  836. panel.rawClipInfo = null;
  837. return;
  838. }
  839. const { name, duration, fps } = panel.animationInfos[panel.rawClipIndex];
  840. panel.rawClipInfo = { name, duration, fps };
  841. panel.$.clipDuration.innerText = duration.toFixed(3);
  842. },
  843. updateGridConfig() {
  844. const panel = this;
  845. if (!panel.currentClipInfo) {
  846. return null;
  847. }
  848. let width = panel.gridTableWith;
  849. const info = panel.rawClipInfo;
  850. const { step, spacing } = panel.getStepAndSpacing(width, panel.currentClipInfo.fps * info.duration);
  851. width = (panel.currentClipInfo.fps * info.duration * spacing) / step;
  852. const mod = Math.floor(panel.gridTableWith / (spacing * 5));
  853. const rest = Math.floor((panel.gridTableWith % (spacing * 5)) / spacing);
  854. const labelStep = info.duration * ((5 * step) / (panel.currentClipInfo.fps * info.duration));
  855. panel.gridConfig = {
  856. step,
  857. spacing,
  858. mod,
  859. rest,
  860. width,
  861. labelStep,
  862. };
  863. },
  864. getStepAndSpacing(width, frames) {
  865. const config = {
  866. minSpacing: 10,
  867. maxSpacing: 20,
  868. };
  869. const rawMinSpacing = width / frames;
  870. let spacing = rawMinSpacing;
  871. let step = 1;
  872. if (rawMinSpacing < config.minSpacing) {
  873. // Calculates a minimum spacing value that is a multiple of maxSpacing
  874. step = Math.ceil(config.minSpacing / rawMinSpacing);
  875. spacing = rawMinSpacing * step;
  876. }
  877. return {
  878. step,
  879. spacing,
  880. };
  881. },
  882. onMouseDown(type) {
  883. const panel = this;
  884. const info = panel.currentClipInfo;
  885. if (!info) {
  886. return;
  887. }
  888. panel.virtualControl = { type };
  889. if (type === 'right') {
  890. panel.virtualControl.style = info.ctrlEndStyle;
  891. panel.virtualControl.value = info.to / panel.currentClipInfo.fps;
  892. } else {
  893. panel.virtualControl.style = info.ctrlStartStyle;
  894. panel.virtualControl.value = info.from / panel.currentClipInfo.fps;
  895. }
  896. panel.$.controlVirtual.setAttribute('direction', panel.virtualControl.type);
  897. panel.$.controlVirtual.style.display = 'block';
  898. panel.updateVirtualControl();
  899. panel.onMouseMoveBind = panel.onMouseMove.bind(panel);
  900. panel.onMouseUpBind = panel.onMouseUp.bind(panel);
  901. document.addEventListener('mousemove', panel.onMouseMoveBind);
  902. document.addEventListener('mouseup', panel.onMouseUpBind);
  903. },
  904. onMouseMove(event) {
  905. const panel = this;
  906. event.preventDefault();
  907. if (!panel.virtualControl) {
  908. return;
  909. }
  910. // beyond border
  911. const { type } = panel.virtualControl;
  912. let x = event.x - panel.$.rulerMaking.getBoundingClientRect().x;
  913. if (
  914. x > panel.gridConfig.width
  915. || x < 0
  916. || (type === 'left' && x > panel.currentClipInfo.ctrlEnd)
  917. || (type === 'right' && x < panel.currentClipInfo.ctrlStart)
  918. ) {
  919. return;
  920. }
  921. const { duration } = panel.rawClipInfo;
  922. const value = (x / panel.gridConfig.width) * duration;
  923. const currentTime = Editor.Utils.Math.clamp(value, 0, duration);
  924. const currentFrame = parseInt((currentTime * panel.currentClipInfo.fps).toFixed(0));
  925. panel.virtualControl.value = currentTime;
  926. if (type === 'left') {
  927. panel.virtualControl.startFrame = currentFrame;
  928. x -= 6;
  929. } else {
  930. panel.virtualControl.endFrame = currentFrame;
  931. }
  932. panel.virtualControl.style = {
  933. transform: `translateX(${x}px)`,
  934. };
  935. cancelAnimationFrame(panel.animationId);
  936. panel.animationId = requestAnimationFrame(() => {
  937. panel.updateVirtualControl();
  938. });
  939. },
  940. onMouseUp() {
  941. const panel = this;
  942. if (!panel.virtualControl) {
  943. return;
  944. }
  945. document.removeEventListener('mousemove', panel.onMouseMoveBind);
  946. document.removeEventListener('mouseup', panel.onMouseUpBind);
  947. const { value } = panel.virtualControl;
  948. let { type } = panel.virtualControl;
  949. type = type === 'right' ? 'to' : 'from';
  950. // refresh data
  951. const splitInfo = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex];
  952. if (splitInfo[type] !== value) {
  953. const { duration } = panel.rawClipInfo;
  954. splitInfo[type] = Editor.Utils.Math.clamp(value, 0, duration);
  955. }
  956. Elements.clips.update.call(panel);
  957. Object.assign(panel.$.controlDuration.style, panel.currentClipInfo.durationStyle);
  958. Object.assign(panel.$.controlLeft.style, panel.currentClipInfo.ctrlStartStyle);
  959. Object.assign(panel.$.controlRight.style, panel.currentClipInfo.ctrlEndStyle);
  960. panel.$.controlVirtual.style.display = 'none';
  961. const curClipInfo = panel.getCurClipInfo();
  962. Editor.Message.broadcast('fbx-inspector:animation-change', curClipInfo);
  963. panel.dispatch('change');
  964. panel.dispatch('snapshot');
  965. },
  966. updateVirtualControl() {
  967. const panel = this;
  968. Object.assign(panel.$.controlVirtual.style, panel.virtualControl.style);
  969. panel.$.controlVirtualNumber.innerText = panel.virtualControl.value.toFixed(3);
  970. if (panel.virtualControl.startFrame || panel.virtualControl.endFrame) {
  971. if (panel.virtualControl.type === 'left') {
  972. panel.$.clipFrom.value = panel.virtualControl.startFrame;
  973. } else {
  974. panel.$.clipTo.value = panel.virtualControl.endFrame;
  975. }
  976. }
  977. },
  978. onClipName(event) {
  979. const panel = this;
  980. if (!panel.currentClipInfo) {
  981. return;
  982. }
  983. let name = event.target.value;
  984. if (!name) {
  985. return;
  986. }
  987. if (panel.clipNames.has(name)) {
  988. name = panel.getRightName(name);
  989. }
  990. const info = panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex];
  991. if (info.name === name) {
  992. return;
  993. }
  994. panel.clipNames.delete(info.name);
  995. info.name = name;
  996. panel.clipNames.add(name);
  997. panel.dispatch('change');
  998. panel.dispatch('snapshot');
  999. Elements.clips.update.call(panel);
  1000. },
  1001. onCutClip(event) {
  1002. const panel = this;
  1003. const path = event.target.getAttribute('path');
  1004. panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex][path] = event.target.value / panel.currentClipInfo.fps;
  1005. Elements.clips.update.call(panel);
  1006. Elements.editor.update.call(panel);
  1007. panel.dispatch('change');
  1008. panel.dispatch('snapshot');
  1009. },
  1010. onFpsChange(event) {
  1011. const panel = this;
  1012. panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].fps = Number(event.target.value);
  1013. Elements.editor.update.call(panel);
  1014. panel.dispatch('change');
  1015. panel.dispatch('snapshot');
  1016. },
  1017. onWrapModeChange(event) {
  1018. const panel = this;
  1019. const wrapMode = Number(event.target.value);
  1020. panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].wrapMode = wrapMode;
  1021. callModelPreviewFunction(
  1022. 'setClipConfig',
  1023. {
  1024. wrapMode,
  1025. }
  1026. );
  1027. Elements.editor.update.call(panel);
  1028. panel.dispatch('change');
  1029. panel.dispatch('snapshot');
  1030. },
  1031. onSpeedChange(event) {
  1032. const panel = this;
  1033. const speed = Number(event.target.value);
  1034. panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].speed = speed;
  1035. callModelPreviewFunction(
  1036. 'setClipConfig',
  1037. {
  1038. speed,
  1039. }
  1040. );
  1041. Elements.editor.update.call(panel);
  1042. panel.dispatch('change');
  1043. panel.dispatch('snapshot');
  1044. },
  1045. onAdditiveEnabledChanged(event) {
  1046. const panel = this;
  1047. const enabled = Boolean(event.target.value);
  1048. (panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].additive ??= { enabled: false }).enabled = enabled;
  1049. Elements.editor.update.call(panel);
  1050. panel.dispatch('change');
  1051. panel.dispatch('snapshot');
  1052. },
  1053. onRefClipChanged(event) {
  1054. const panel = this;
  1055. const refClipUUID = String(event.target.value);
  1056. (panel.animationInfos[panel.rawClipIndex].splits[panel.splitClipIndex].additive ??= { enabled: false }).refClip = refClipUUID;
  1057. Elements.editor.update.call(panel);
  1058. panel.dispatch('change');
  1059. panel.dispatch('snapshot');
  1060. },
  1061. setProp(prop, type, event) {
  1062. setPropValue.call(this, prop, type, event);
  1063. this.dispatch('change');
  1064. this.dispatch('track', { tab: 'animation', prop, value: event.target.value });
  1065. },
  1066. };
  1067. exports.ready = function() {
  1068. for (const prop in Elements) {
  1069. const element = Elements[prop];
  1070. if (element.ready) {
  1071. element.ready.call(this);
  1072. }
  1073. }
  1074. };
  1075. exports.update = function(assetList, metaList) {
  1076. this.assetList = assetList;
  1077. this.metaList = metaList;
  1078. this.asset = assetList[0];
  1079. this.meta = metaList[0];
  1080. if (assetList.length > 1) {
  1081. this.$.container.setAttribute('multiple-invalid', '');
  1082. return;
  1083. } else {
  1084. this.$.container.removeAttribute('multiple-invalid');
  1085. }
  1086. for (const prop in Elements) {
  1087. const element = Elements[prop];
  1088. if (element.update) {
  1089. element.update.call(this);
  1090. }
  1091. }
  1092. this.initAnimationNameToUUIDMap();
  1093. this.initAnimationInfos();
  1094. if (this.animationInfos) {
  1095. this.onSelect(this.rawClipIndex, this.splitClipIndex);
  1096. }
  1097. };
  1098. exports.close = function() {
  1099. for (const prop in Elements) {
  1100. const element = Elements[prop];
  1101. if (element.close) {
  1102. element.close.call(this);
  1103. }
  1104. }
  1105. };