blob: 99c5402325b8fbd774b43c048509cb5f97c67d01 [file] [log] [blame]
/*
* 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();
}
};