| /* |
| * 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(); |
| } |
| }; |