355 lines
12 KiB
JavaScript
355 lines
12 KiB
JavaScript
/*
|
|
* Copyright (C) 2017 Apple Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
|
* THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
WI.ShaderProgramContentView = class ShaderProgramContentView extends WI.ContentView
|
|
{
|
|
constructor(shaderProgram)
|
|
{
|
|
console.assert(shaderProgram instanceof WI.ShaderProgram);
|
|
|
|
super(shaderProgram);
|
|
|
|
let isWebGPU = this.representedObject.canvas.contextType === WI.Canvas.ContextType.WebGPU;
|
|
let sharesVertexFragmentShader = isWebGPU && this.representedObject.sharesVertexFragmentShader;
|
|
|
|
this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("refresh", WI.UIString("Refresh"), "Images/ReloadFull.svg", 13, 13);
|
|
this._refreshButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
|
|
this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshContent, this);
|
|
|
|
let contentDidChangeDebouncer = new Debouncer((event) => {
|
|
this._contentDidChange(event);
|
|
});
|
|
|
|
this.element.classList.add("shader-program", this.representedObject.programType);
|
|
|
|
let createEditor = (shaderType) => {
|
|
let container = this.element.appendChild(document.createElement("div"));
|
|
|
|
let header = container.appendChild(document.createElement("header"));
|
|
|
|
let shaderTypeContainer = header.appendChild(document.createElement("div"));
|
|
shaderTypeContainer.classList.add("shader-type");
|
|
|
|
let textEditor = new WI.TextEditor;
|
|
textEditor.readOnly = false;
|
|
textEditor.addEventListener(WI.TextEditor.Event.Focused, this._editorFocused, this);
|
|
textEditor.addEventListener(WI.TextEditor.Event.NumberOfSearchResultsDidChange, this._numberOfSearchResultsDidChange, this);
|
|
textEditor.addEventListener(WI.TextEditor.Event.ContentDidChange, function(event) {
|
|
contentDidChangeDebouncer.delayForTime(250, event);
|
|
}, textEditor);
|
|
|
|
switch (shaderType) {
|
|
case WI.ShaderProgram.ShaderType.Compute:
|
|
shaderTypeContainer.textContent = WI.UIString("Compute Shader");
|
|
textEditor.mimeType = isWebGPU ? "x-pipeline/x-compute" : "x-shader/x-compute";
|
|
break;
|
|
|
|
case WI.ShaderProgram.ShaderType.Fragment:
|
|
shaderTypeContainer.textContent = WI.UIString("Fragment Shader");
|
|
textEditor.mimeType = isWebGPU ? "x-pipeline/x-render" : "x-shader/x-fragment";
|
|
break;
|
|
|
|
case WI.ShaderProgram.ShaderType.Vertex:
|
|
if (sharesVertexFragmentShader)
|
|
shaderTypeContainer.textContent = WI.UIString("Vertex/Fragment Shader");
|
|
else
|
|
shaderTypeContainer.textContent = WI.UIString("Vertex Shader");
|
|
textEditor.mimeType = isWebGPU ? "x-pipeline/x-render" : "x-shader/x-vertex";
|
|
break;
|
|
}
|
|
|
|
this.addSubview(textEditor);
|
|
container.appendChild(textEditor.element);
|
|
container.classList.add("shader", shaderType);
|
|
container.classList.toggle("shares-vertex-fragment-shader", sharesVertexFragmentShader);
|
|
|
|
return textEditor;
|
|
};
|
|
|
|
switch (this.representedObject.programType) {
|
|
case WI.ShaderProgram.ProgramType.Compute: {
|
|
this._computeEditor = createEditor(WI.ShaderProgram.ShaderType.Compute);
|
|
|
|
this._lastActiveEditor = this._computeEditor;
|
|
break;
|
|
}
|
|
|
|
case WI.ShaderProgram.ProgramType.Render: {
|
|
this._vertexEditor = createEditor(WI.ShaderProgram.ShaderType.Vertex);
|
|
|
|
if (!sharesVertexFragmentShader) {
|
|
this._fragmentEditor = createEditor(WI.ShaderProgram.ShaderType.Fragment);
|
|
}
|
|
|
|
this._lastActiveEditor = this._vertexEditor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (WI.FileUtilities.canSave(WI.FileUtilities.SaveMode.FileVariants))
|
|
this._saveMode = WI.FileUtilities.SaveMode.FileVariants;
|
|
else if (WI.FileUtilities.canSave(WI.FileUtilities.SaveMode.SingleFile))
|
|
this._saveMode = WI.FileUtilities.SaveMode.SingleFile;
|
|
else
|
|
this._saveMode = null;
|
|
}
|
|
|
|
// Public
|
|
|
|
get navigationItems()
|
|
{
|
|
return [this._refreshButtonNavigationItem];
|
|
}
|
|
|
|
// Protected
|
|
|
|
attached()
|
|
{
|
|
super.attached();
|
|
|
|
this._refreshContent();
|
|
}
|
|
|
|
get supportsSave()
|
|
{
|
|
return !!this._saveMode;
|
|
}
|
|
|
|
get saveMode()
|
|
{
|
|
return this._saveMode;
|
|
}
|
|
|
|
get saveData()
|
|
{
|
|
let data = [];
|
|
let addDataForEditor = (editor) => {
|
|
if (!editor || (editor !== this._lastActiveEditor && this._saveMode === WI.FileUtilities.SaveMode.SingleFile))
|
|
return;
|
|
|
|
let filename = "";
|
|
let displayType = "";
|
|
switch (editor) {
|
|
case this._computeEditor:
|
|
filename = WI.UIString("Compute");
|
|
displayType = WI.UIString("Compute Shader");
|
|
break;
|
|
case this._fragmentEditor:
|
|
filename = WI.UIString("Fragment");
|
|
displayType = WI.UIString("Fragment Shader");
|
|
break;
|
|
case this._vertexEditor:
|
|
filename = WI.UIString("Vertex");
|
|
displayType = WI.UIString("Vertex Shader");
|
|
break;
|
|
}
|
|
console.assert(filename);
|
|
console.assert(displayType);
|
|
|
|
let extension = "";
|
|
switch (this.representedObject.canvas.contextType) {
|
|
case WI.Canvas.ContextType.WebGL:
|
|
case WI.Canvas.ContextType.WebGL2:
|
|
extension = WI.unlocalizedString(".glsl");
|
|
break;
|
|
case WI.Canvas.ContextType.WebGPU:
|
|
extension = WI.unlocalizedString(".wsl");
|
|
break;
|
|
}
|
|
console.assert(extension);
|
|
|
|
data.push({
|
|
displayType,
|
|
content: editor.string,
|
|
suggestedName: filename + extension,
|
|
forceSaveAs: true,
|
|
});
|
|
};
|
|
addDataForEditor(this._computeEditor);
|
|
addDataForEditor(this._fragmentEditor);
|
|
addDataForEditor(this._vertexEditor);
|
|
return data;
|
|
}
|
|
|
|
get supportsSearch()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
get numberOfSearchResults()
|
|
{
|
|
return this._lastActiveEditor.numberOfSearchResults;
|
|
}
|
|
|
|
get hasPerformedSearch()
|
|
{
|
|
return this._lastActiveEditor.currentSearchQuery !== null;
|
|
}
|
|
|
|
set automaticallyRevealFirstSearchResult(reveal)
|
|
{
|
|
this._lastActiveEditor.automaticallyRevealFirstSearchResult = reveal;
|
|
}
|
|
|
|
performSearch(query)
|
|
{
|
|
this._lastActiveEditor.performSearch(query);
|
|
}
|
|
|
|
searchCleared()
|
|
{
|
|
this._lastActiveEditor.searchCleared();
|
|
}
|
|
|
|
searchQueryWithSelection()
|
|
{
|
|
return this._lastActiveEditor.searchQueryWithSelection();
|
|
}
|
|
|
|
revealPreviousSearchResult(changeFocus)
|
|
{
|
|
this._lastActiveEditor.revealPreviousSearchResult(changeFocus);
|
|
}
|
|
|
|
revealNextSearchResult(changeFocus)
|
|
{
|
|
this._lastActiveEditor.revealNextSearchResult(changeFocus);
|
|
}
|
|
|
|
revealPosition(position, options = {})
|
|
{
|
|
this._lastActiveEditor.revealPosition(position, options);
|
|
}
|
|
|
|
// Private
|
|
|
|
_refreshContent()
|
|
{
|
|
let spinnerContainer = null;
|
|
|
|
if (!this.didInitialLayout) {
|
|
spinnerContainer = this.element.appendChild(document.createElement("div"));
|
|
spinnerContainer.className = "spinner-container";
|
|
spinnerContainer.appendChild((new WI.IndeterminateProgressSpinner).element);
|
|
|
|
this._contentErrorMessageElement?.remove();
|
|
}
|
|
|
|
let createCallback = (textEditor) => {
|
|
return (source) => {
|
|
spinnerContainer?.remove();
|
|
|
|
if (source === null) {
|
|
if (!this._contentErrorMessageElement) {
|
|
const isError = true;
|
|
this._contentErrorMessageElement = WI.createMessageTextView(WI.UIString("An error occurred trying to load the resource."), isError);
|
|
}
|
|
if (!this._contentErrorMessageElement.parentNode)
|
|
this.element.appendChild(this._contentErrorMessageElement);
|
|
return;
|
|
}
|
|
|
|
textEditor.string = source || "";
|
|
};
|
|
};
|
|
|
|
switch (this.representedObject.programType) {
|
|
case WI.ShaderProgram.ProgramType.Compute:
|
|
this.representedObject.requestShaderSource(WI.ShaderProgram.ShaderType.Compute, createCallback(this._computeEditor));
|
|
return;
|
|
|
|
case WI.ShaderProgram.ProgramType.Render:
|
|
this.representedObject.requestShaderSource(WI.ShaderProgram.ShaderType.Vertex, createCallback(this._vertexEditor));
|
|
if (!this.representedObject.sharesVertexFragmentShader)
|
|
this.representedObject.requestShaderSource(WI.ShaderProgram.ShaderType.Fragment, createCallback(this._fragmentEditor));
|
|
return;
|
|
}
|
|
|
|
console.assert();
|
|
}
|
|
|
|
_updateShader(shaderType)
|
|
{
|
|
switch (shaderType) {
|
|
case WI.ShaderProgram.ShaderType.Compute:
|
|
this.representedObject.updateShader(shaderType, this._computeEditor.string);
|
|
return;
|
|
|
|
case WI.ShaderProgram.ShaderType.Fragment:
|
|
this.representedObject.updateShader(shaderType, this._fragmentEditor.string);
|
|
return;
|
|
|
|
case WI.ShaderProgram.ShaderType.Vertex:
|
|
this.representedObject.updateShader(shaderType, this._vertexEditor.string);
|
|
return;
|
|
}
|
|
|
|
console.assert();
|
|
}
|
|
|
|
_editorFocused(event)
|
|
{
|
|
if (this._lastActiveEditor === event.target)
|
|
return;
|
|
|
|
let currentSearchQuery = null;
|
|
|
|
if (this._lastActiveEditor) {
|
|
currentSearchQuery = this._lastActiveEditor.currentSearchQuery;
|
|
|
|
this._lastActiveEditor.searchCleared();
|
|
}
|
|
|
|
this._lastActiveEditor = event.target;
|
|
|
|
if (currentSearchQuery)
|
|
this._lastActiveEditor.performSearch(currentSearchQuery);
|
|
}
|
|
|
|
_numberOfSearchResultsDidChange(event)
|
|
{
|
|
this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange);
|
|
}
|
|
|
|
_contentDidChange(event)
|
|
{
|
|
switch (event.target) {
|
|
case this._computeEditor:
|
|
this._updateShader(WI.ShaderProgram.ShaderType.Compute);
|
|
return;
|
|
|
|
case this._fragmentEditor:
|
|
this._updateShader(WI.ShaderProgram.ShaderType.Fragment);
|
|
return;
|
|
|
|
case this._vertexEditor:
|
|
this._updateShader(WI.ShaderProgram.ShaderType.Vertex);
|
|
return;
|
|
}
|
|
|
|
console.assert();
|
|
}
|
|
};
|