/** * 3D Foundation Project * Copyright 2025 Smithsonian Institution * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import CLight from "@ff/scene/components/CLight"; import { IComponentEvent } from "@ff/graph/Component"; import "../ui/properties/PropertyBoolean"; import "../ui/properties/PropertyOptions"; import "../ui/properties/PropertySlider"; import "../ui/properties/PropertyColor"; import "../ui/properties/PropertyString"; import "../ui/properties/PropertyDateTime"; import "../ui/properties/PropertyNumber"; import CVDocument from "./CVDocument"; import CVTool, { types, customElement, html, ToolView } from "./CVTool"; import CVEnvironmentLight from "./lights/CVEnvironmentLight"; import NVNode from "client/nodes/NVNode"; import CSunLight from "@ff/scene/components/CSunLight"; //////////////////////////////////////////////////////////////////////////////// export default class CVLightTool extends CVTool { static readonly typeName: string = "CVLightTool"; static readonly text = "Lights"; static readonly icon = "bulb"; lights: Readonly = []; protected static readonly ins = { light: types.Option("Tool.Light", []), }; protected static readonly outs = { light: types.Object("Tool.SelectedLight", CLight), }; ins = this.addInputs(CVLightTool.ins); outs = this.addOutputs(CVLightTool.outs); update(context) { this.outs.light.setValue(this.lights[this.ins.light.getValidatedValue()]); return true; } createView() { return new LightToolView(this); } protected onActiveDocument(previous: CVDocument, next: CVDocument) { this.lights = next ? next.getInnerComponents(CLight).filter((light) => light.ins.enabled.value) : []; this.ins.light.setOptions(this.lights.map(light => light.ins.name.value)); this.outs.light.setValue(this.lights[this.ins.light.getValidatedValue()] ?? null); super.onActiveDocument(previous, next); } } //////////////////////////////////////////////////////////////////////////////// @customElement("sv-light-tool-view") export class LightToolView extends ToolView { protected lights: CLight[] = null; protected firstConnected() { super.firstConnected(); this.classList.add("sv-group", "sv-light-tool-view"); } protected connected() { super.connected(); this.tool.outs.light.on("value", this.onUpdate, this); } protected disconnected() { this.tool.outs.light.off("value", this.onUpdate, this); super.disconnected(); } private renderSunLightProperties(light: CSunLight, language): unknown { return html` `; } private renderCommonLightProperties(light: CLight, language): unknown { return html` `; } private renderColorInput(light: CLight, language): unknown { return html``; } protected render() { const tool = this.tool; const lights = tool.lights; const document = this.activeDocument; if (!lights || !document || lights.length == 0) { return html`
No editable lights in this scene.
`; } const activeLight = tool.outs.light.value; const navigation = document.setup.navigation; const language = document.setup.language; var lightDetails = null; if (activeLight) { var lightControls = null; if (activeLight.is(CSunLight)) { lightControls = this.renderSunLightProperties(activeLight as CSunLight, language); } else if (activeLight.is(CVEnvironmentLight)) { lightControls = this.renderCommonLightProperties(activeLight, language); } else { lightControls = html` ${this.renderCommonLightProperties(activeLight, language)} ${this.renderColorInput(activeLight, language)} `; } lightDetails = html`
${lightControls}
`; } return html`${lightDetails}
`; } protected onActiveDocument(previous: CVDocument, next: CVDocument) { if (previous) { previous.setup.navigation.ins.lightsFollowCamera.off("value", this.onUpdate, this); previous.innerGraph.components.off(CLight, this.refreshLights, this); } if (next) { next.innerGraph.components.on(CLight, this.refreshLights, this); next.setup.navigation.ins.lightsFollowCamera.on("value", this.onUpdate, this); } this.requestUpdate(); } protected onActiveNode(previous: NVNode, next: NVNode) { if (previous && previous.light) { previous.light.ins.name.off("value", this.refreshLightList, this); previous.light.ins.enabled.off("value", this.refreshLights, this); } if (next && next.light) { next.light.ins.enabled.on("value", this.refreshLights, this); next.light.ins.name.on("value", this.refreshLightList, this); } } protected refreshLights() { this.tool.lights = this.activeDocument.getInnerComponents(CLight).filter((light) => light.ins.enabled.value); this.refreshLightList(); } protected refreshLightList() { this.tool.ins.light.setOptions(this.tool.lights.map(light => light.ins.name.value || light.node.name)); this.requestUpdate(); } protected async setFocus() { await this.updateComplete; const idx = this.tool.lights.findIndex(light => light === this.tool.outs.light.value); const focusElement = this.getElementsByTagName("sv-property-options")[0] ?.getElementsByTagName("ff-button")[idx >= 0 ? idx : 0] as HTMLElement; focusElement?.focus(); } protected onClose(event: MouseEvent) { this.parentElement.dispatchEvent(new CustomEvent("close")); event.stopPropagation(); } }