'use strict';
const { readFileSync, existsSync } = require('fs');
const { updateElementReadonly } = require('../utils/assets');
exports.template = /* html */`
`;
exports.style = /* css */`
.asset-effect {
padding-right: 4px;
}
.asset-effect[multiple-invalid] > *:not(.multiple-warn-tip) {
display: none!important;
}
.asset-effect[multiple-invalid] > .multiple-warn-tip {
display: block;
}
.asset-effect .multiple-warn-tip {
display: none;
text-align: center;
color: var(--color-focus-contrast-weakest);
margin-top: 8px;
}
.asset-effect > .section > .description {
text-align: center;
color: var(--color-normal-fill-weakest);
}
.asset-effect > .section > .combinations .tab {
margin-left: 4px;
min-width: 60px;
width: calc(50% - 4px);
}
.asset-effect > .section > .combinations .tab[checked="true"] {
background-color: var(--color-info-fill-important);
border-color: var(--color-info-fill-important);
color: var(--color-info-contrast-important);
}
.asset-effect > .codes .tabs {
margin: 4px auto 6px auto;
}
.asset-effect > .codes .tabs > .tab {
padding: 0;
width: 110px;
height: 20px;
box-sizing: border-box;
text-align: center;
cursor: pointer;
display: inline-block;
color: var(--color-normal-contrast-emphasis);
border: calc(var(--size-normal-border) * 1px) solid var(--color-default-border);
}
.asset-effect > .codes .tabs > .tab:first-child {
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
border-right: 1px solid var(--color-default-border);
}
.asset-effect > .codes .tabs > .tab:last-child {
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
border-left: 1px solid var(--color-default-border);
}
.asset-effect > .codes .tabs > .tab:hover,
.asset-effect > .codes .tabs > .tab[active="true"] {
background-color: var(--color-default-fill-normal);
background-color: var(--color-default-fill-important);
color: var(--color-normal-contrast);
}
.asset-effect > .codes ui-code {
max-height: 400px;
border: none;
border-radius: 0;
background-color: var(--color-normal-fill-emphasis);
}
`;
exports.$ = {
container: '.asset-effect',
shaderSelect: '.shader-select',
combinations: '.combinations',
codes: '.codes',
};
/**
* Property corresponds to the edit element
*/
const Elements = {
shaders: {
ready() {
const panel = this;
panel.shadersIndex = 0;
panel.$.shaderSelect.addEventListener('change', (event) => {
panel.shadersIndex = event.target.value;
// There are other properties that are updated depending on its change
Elements.combinations.update.call(panel);
Elements.codes.update.call(panel);
});
panel.$.shaderSelect.addEventListener('confirm', () => {
panel.dispatch('snapshot');
});
},
update() {
const panel = this;
let optionsHtml = '';
panel.shaders.forEach((shader, index) => {
optionsHtml += ``;
});
panel.$.shaderSelect.innerHTML = optionsHtml;
if (panel.shadersIndex > panel.shaders.length - 1) {
panel.shadersIndex = 0;
}
panel.$.shaderSelect.value = panel.shadersIndex;
if (panel.shaders[panel.shadersIndex]) {
panel.$.shaderSelect.setAttribute('tooltip', panel.shaders[panel.shadersIndex].name);
}
updateElementReadonly.call(this, panel.$.shaderSelect);
},
},
combinations: {
update() {
const panel = this;
panel.$.combinations.innerText = '';
panel.shaders[panel.shadersIndex].defines.forEach((define) => {
if (!define._enabled) {
return;
}
const prop = document.createElement('ui-prop');
panel.$.combinations.appendChild(prop);
const label = document.createElement('ui-label');
label.setAttribute('slot', 'label');
label.setAttribute('value', define.name);
prop.appendChild(label);
const content = document.createElement('div');
content.setAttribute('slot', 'content');
prop.appendChild(content);
define._values.forEach((value) => {
const userCombinations = panel.combinations[panel.shadersIndex][define.name];
const checked = userCombinations && userCombinations.includes(value) ? 'true' : 'false';
const name = typeof value === 'boolean' ? (value ? 'on' : 'off') : value.toString();
const button = document.createElement('ui-button');
updateElementReadonly.call(panel, button);
button.setAttribute('class', 'tab');
button.setAttribute('checked', checked);
button.innerText = name;
button.addEventListener('click', () => {
if (!panel.combinations[panel.shadersIndex][define.name]) {
panel.combinations[panel.shadersIndex][define.name] = [];
}
const userCombinations = panel.combinations[panel.shadersIndex][define.name];
if (userCombinations.indexOf(value) !== -1) {
// Eliminate existing, can choose more
userCombinations.splice(userCombinations.indexOf(value), 1);
button.setAttribute('checked', 'false');
} else {
userCombinations.push(value);
button.setAttribute('checked', 'true');
}
panel.change();
});
content.appendChild(button);
});
});
if (panel.$.combinations.children.length) {
panel.$.combinations.parentElement.style.display = 'block';
} else {
panel.$.combinations.parentElement.style.display = 'none';
}
},
},
codes: {
ready() {
const panel = this;
panel.glslNames = {
glsl3: 'GLSL 300 ES Output',
glsl1: 'GLSL 100 Output',
};
panel.shaderNames = {
vert: 'Vertex Shader',
frag: 'Fragment Shader',
};
},
update() {
const panel = this;
panel.$.codes.innerText = '';
for (const glslKey in panel.glslNames) {
const section = document.createElement('ui-section');
panel.$.codes.appendChild(section);
section.setAttribute('class', 'section');
section.setAttribute('expand', '');
section.setAttribute('cache-expand', `effect-${glslKey}`);
const glslName = panel.glslNames[glslKey];
const header = document.createElement('div');
section.appendChild(header);
header.setAttribute('slot', 'header');
header.innerHTML = `${glslName}`;
const tabs = document.createElement('div');
section.appendChild(tabs);
tabs.setAttribute('class', 'tabs');
const code = document.createElement('ui-code');
section.appendChild(code);
code.setAttribute('language', 'glsl');
code.innerHTML = panel.shaders[panel.shadersIndex][glslKey][panel.shaders[panel.shadersIndex][glslKey].activeKey];
for (const shaderKey in panel.shaderNames) {
const shaderName = panel.shaderNames[shaderKey];
const active = panel.shaders[panel.shadersIndex][glslKey].activeKey === shaderKey;
const tab = document.createElement('div');
tabs.appendChild(tab);
tab.setAttribute('class', 'tab');
tab.setAttribute('active', active);
tab.innerText = shaderName;
tab.addEventListener('click', () => {
if (tab.getAttribute('active') === 'true') {
return;
}
for (const child of tab.parentElement.children) {
if (child === tab) {
child.setAttribute('active', 'true');
} else {
child.setAttribute('active', 'false');
}
}
panel.shaders[panel.shadersIndex][glslKey].activeKey = shaderKey;
code.innerHTML = panel.shaders[panel.shadersIndex][glslKey][panel.shaders[panel.shadersIndex][glslKey].activeKey];
});
}
}
},
},
};
exports.methods = {
record() {
return JSON.stringify({ shadersIndex: this.shadersIndex });
},
restore(record) {
record = JSON.parse(record);
this.$.shaderSelect.value = record.shadersIndex;
this.$.shaderSelect.dispatch('change');
return true;
},
refresh() {
const panel = this;
// notice : The data displayed uses both the library and the meta, so you need to keep both consistent
if (panel.asset.uuid !== panel.meta.uuid) {
return false;
}
const fileSource = panel.asset.library['.json'];
if (!fileSource || !existsSync(fileSource)) {
console.error('Read effect json file in library failed.');
return false;
}
const dataSource = JSON.parse(readFileSync(fileSource, 'utf8'));
if (!dataSource) {
console.error('Read effect json file in library failed.');
return false;
}
panel.shaders = dataSource.shaders;
// The edited value of defines in each shader
panel.combinations = [];
if (Array.isArray(panel.meta.userData.combinations)) {
panel.combinations = panel.meta.userData.combinations;
}
// Adjusting some data for display
panel.shaders.forEach((shader, index) => {
for (const glslKey in panel.glslNames) {
shader[glslKey].activeKey = 'vert';
}
if (!panel.combinations[index]) {
panel.combinations[index] = {};
}
// Configurable definition with injection of temporary data
shader.defines.forEach((define) => {
const { name, type } = define;
if (name.startsWith('CC_')) {
// Prefixed with "CC_" are not processed
define._enabled = false;
return;
} else {
define._enabled = true;
}
// The following data is used for display editing
define._values = [];
if (type === 'number' && define.range) {
const [min, max] = define.range;
for (let i = min; i <= max; i++) {
define._values.push(i);
}
}
if (type === 'boolean') {
define._values = [false, true];
}
if (type === 'string') {
define._values = define.options;
}
});
});
return true;
},
change() {
const panel = this;
// Need to exclude empty arrays, otherwise scene will report an error
const submitData = [];
panel.combinations.forEach((combination, index) => {
submitData[index] = {};
const names = Object.keys(combination);
names.forEach((name) => {
if (combination[name].length !== 0) {
submitData[index][name] = combination[name];
}
});
});
panel.meta.userData.combinations = submitData;
panel.dispatch('change');
panel.dispatch('snapshot');
},
};
exports.ready = function() {
for (const prop in Elements) {
const element = Elements[prop];
if (element.ready) {
element.ready.call(this);
}
}
};
exports.update = function(assetList, metaList) {
this.assetList = assetList;
this.metaList = metaList;
this.asset = assetList[0];
this.meta = metaList[0];
if (assetList.length > 1) {
this.$.container.setAttribute('multiple-invalid', '');
return;
} else {
this.$.container.removeAttribute('multiple-invalid');
}
const isLegal = this.refresh();
if (!isLegal) {
return;
}
for (const prop in Elements) {
const element = Elements[prop];
if (element.update) {
element.update.call(this);
}
}
};