diff --git a/src/layer/annotation/index.ts b/src/layer/annotation/index.ts index 663b76e052..eba7104c4e 100644 --- a/src/layer/annotation/index.ts +++ b/src/layer/annotation/index.ts @@ -15,6 +15,7 @@ */ import "#src/layer/annotation/style.css"; +import svgCode from "ikonate/icons/code-alt.svg?raw"; import type { AnnotationDisplayState } from "#src/annotation/annotation_layer_state.js"; import { AnnotationLayerState } from "#src/annotation/annotation_layer_state.js"; @@ -43,8 +44,11 @@ import { Overlay } from "#src/overlay.js"; import { getWatchableRenderLayerTransform } from "#src/render_coordinate_transform.js"; import { RenderLayerRole } from "#src/renderlayer.js"; import type { SegmentationDisplayState } from "#src/segmentation_display_state/frontend.js"; -import type { TrackableBoolean } from "#src/trackable_boolean.js"; -import { TrackableBooleanCheckbox } from "#src/trackable_boolean.js"; +import { + TrackableBoolean, + TrackableBooleanCheckbox, + ElementVisibilityFromTrackableBoolean, +} from "#src/trackable_boolean.js"; import { makeCachedLazyDerivedWatchableValue } from "#src/trackable_value.js"; import type { AnnotationLayerView, @@ -67,6 +71,7 @@ import { verifyStringArray, } from "#src/util/json.js"; import { NullarySignal } from "#src/util/signal.js"; +import { CheckboxIcon } from "#src/widget/checkbox_icon.js"; import { DependentViewWidget } from "#src/widget/dependent_view_widget.js"; import { makeHelpButton } from "#src/widget/help_button.js"; import { LayerReferenceWidget } from "#src/widget/layer_reference.js"; @@ -138,6 +143,7 @@ interface LinkedSegmentationLayer { const LINKED_SEGMENTATION_LAYER_JSON_KEY = "linkedSegmentationLayer"; const FILTER_BY_SEGMENTATION_JSON_KEY = "filterBySegmentation"; const IGNORE_NULL_SEGMENT_FILTER_JSON_KEY = "ignoreNullSegmentFilter"; +const CODE_VISIBLE_KEY = "codeVisible"; class LinkedSegmentationLayers extends RefCounted { changed = new NullarySignal(); @@ -382,6 +388,7 @@ class LinkedSegmentationLayersWidget extends RefCounted { const Base = UserLayerWithAnnotationsMixin(UserLayer); export class AnnotationUserLayer extends Base { localAnnotations: LocalAnnotationSource | undefined; + codeVisible = new TrackableBoolean(true); private localAnnotationProperties: AnnotationPropertySpec[] | undefined; private localAnnotationRelationships: string[]; private localAnnotationsJson: any = undefined; @@ -407,6 +414,7 @@ export class AnnotationUserLayer extends Base { this.linkedSegmentationLayers.changed.add( this.specificationChanged.dispatch, ); + this.codeVisible.changed.add(this.specificationChanged.dispatch); this.annotationDisplayState.ignoreNullSegmentFilter.changed.add( this.specificationChanged.dispatch, ); @@ -427,6 +435,7 @@ export class AnnotationUserLayer extends Base { restoreState(specification: any) { super.restoreState(specification); this.linkedSegmentationLayers.restoreState(specification); + this.codeVisible.restoreState(specification[CODE_VISIBLE_KEY]); this.localAnnotationsJson = specification[ANNOTATIONS_JSON_KEY]; this.localAnnotationProperties = verifyOptionalObjectProperty( specification, @@ -688,6 +697,7 @@ export class AnnotationUserLayer extends Base { const x = super.toJSON(); x[CROSS_SECTION_RENDER_SCALE_JSON_KEY] = this.annotationCrossSectionRenderScaleTarget.toJSON(); + x[CODE_VISIBLE_KEY] = this.codeVisible.toJSON(); x[PROJECTION_RENDER_SCALE_JSON_KEY] = this.annotationProjectionRenderScaleTarget.toJSON(); if (this.localAnnotations !== undefined) { @@ -742,42 +752,42 @@ class RenderingOptionsTab extends Tab { const { element } = this; this.codeWidget = this.registerDisposer(makeShaderCodeWidget(this.layer)); element.classList.add("neuroglancer-annotation-rendering-tab"); - element.appendChild( - this.registerDisposer( - new DependentViewWidget( - layer.annotationDisplayState.annotationProperties, - (properties, parent) => { - if (properties === undefined || properties.length === 0) return; - const propertyList = document.createElement("div"); - parent.appendChild(propertyList); - propertyList.classList.add( - "neuroglancer-annotation-shader-property-list", + const shaderProperties = this.registerDisposer( + new DependentViewWidget( + layer.annotationDisplayState.annotationProperties, + (properties, parent) => { + if (properties === undefined || properties.length === 0) return; + const propertyList = document.createElement("div"); + parent.appendChild(propertyList); + propertyList.classList.add( + "neuroglancer-annotation-shader-property-list", + ); + for (const property of properties) { + const div = document.createElement("div"); + div.classList.add("neuroglancer-annotation-shader-property"); + const typeElement = document.createElement("span"); + typeElement.classList.add( + "neuroglancer-annotation-shader-property-type", + ); + typeElement.textContent = property.type; + const nameElement = document.createElement("span"); + nameElement.classList.add( + "neuroglancer-annotation-shader-property-identifier", ); - for (const property of properties) { - const div = document.createElement("div"); - div.classList.add("neuroglancer-annotation-shader-property"); - const typeElement = document.createElement("span"); - typeElement.classList.add( - "neuroglancer-annotation-shader-property-type", - ); - typeElement.textContent = property.type; - const nameElement = document.createElement("span"); - nameElement.classList.add( - "neuroglancer-annotation-shader-property-identifier", - ); - nameElement.textContent = `prop_${property.identifier}`; - div.appendChild(typeElement); - div.appendChild(nameElement); - const { description } = property; - if (description !== undefined) { - div.title = description; - } - propertyList.appendChild(div); + nameElement.textContent = `prop_${property.identifier}`; + div.appendChild(typeElement); + div.appendChild(nameElement); + const { description } = property; + if (description !== undefined) { + div.title = description; } - }, - ), - ).element, - ); + propertyList.appendChild(div); + } + }, + ), + ).element; + + element.appendChild(shaderProperties); const topRow = document.createElement("div"); topRow.className = "neuroglancer-segmentation-dropdown-skeleton-shader-header"; @@ -785,6 +795,27 @@ class RenderingOptionsTab extends Tab { label.style.flex = "1"; label.textContent = "Annotation shader:"; topRow.appendChild(label); + + this.registerDisposer( + new ElementVisibilityFromTrackableBoolean( + this.layer.codeVisible, + this.codeWidget.element, + ), + ); + this.registerDisposer( + new ElementVisibilityFromTrackableBoolean( + this.layer.codeVisible, + shaderProperties, + ), + ); + const codeVisibilityControl = new CheckboxIcon(this.layer.codeVisible, { + enableTitle: "Show code", + disableTitle: "Hide code", + backgroundScheme: "dark", + svg: svgCode, + }); + topRow.appendChild(codeVisibilityControl.element); + topRow.appendChild( makeMaximizeButton({ title: "Show larger editor view", diff --git a/src/layer/image/index.ts b/src/layer/image/index.ts index 2d2830f6c3..189d7e1b14 100644 --- a/src/layer/image/index.ts +++ b/src/layer/image/index.ts @@ -15,6 +15,7 @@ */ import "#src/layer/image/style.css"; +import svgCode from "ikonate/icons/code-alt.svg?raw"; import type { CoordinateSpace } from "#src/coordinate_transform.js"; import { @@ -49,6 +50,10 @@ import { } from "#src/sliceview/volume/image_renderlayer.js"; import { trackableAlphaValue } from "#src/trackable_alpha.js"; import { trackableBlendModeValue } from "#src/trackable_blend.js"; +import { + TrackableBoolean, + ElementVisibilityFromTrackableBoolean, +} from "#src/trackable_boolean.js"; import { trackableFiniteFloat } from "#src/trackable_finite_float.js"; import type { WatchableValueInterface } from "#src/trackable_value.js"; import { @@ -79,6 +84,7 @@ import { ShaderControlState, } from "#src/webgl/shader_ui_controls.js"; import { ChannelDimensionsWidget } from "#src/widget/channel_dimensions_widget.js"; +import { CheckboxIcon } from "#src/widget/checkbox_icon.js"; import { makeCopyButton } from "#src/widget/copy_button.js"; import type { DependentViewContext } from "#src/widget/dependent_view_widget.js"; import { makeHelpButton } from "#src/widget/help_button.js"; @@ -105,6 +111,7 @@ import { Tab } from "#src/widget/tab_view.js"; const OPACITY_JSON_KEY = "opacity"; const BLEND_JSON_KEY = "blend"; const SHADER_JSON_KEY = "shader"; +const CODE_VISIBLE_KEY = "codeVisible"; const SHADER_CONTROLS_JSON_KEY = "shaderControls"; const CROSS_SECTION_RENDER_SCALE_JSON_KEY = "crossSectionRenderScale"; const CHANNEL_DIMENSIONS_JSON_KEY = "channelDimensions"; @@ -124,6 +131,7 @@ const [ export class ImageUserLayer extends Base { opacity = trackableAlphaValue(0.5); blendMode = trackableBlendModeValue(); + codeVisible = new TrackableBoolean(true); fragmentMain = getTrackableFragmentMain(); shaderError = makeWatchableShaderError(); dataType = new WatchableValue(undefined); @@ -204,6 +212,7 @@ export class ImageUserLayer extends Base { isLocalDimension; this.blendMode.changed.add(this.specificationChanged.dispatch); this.opacity.changed.add(this.specificationChanged.dispatch); + this.codeVisible.changed.add(this.specificationChanged.dispatch); this.volumeRenderingGain.changed.add(this.specificationChanged.dispatch); this.fragmentMain.changed.add(this.specificationChanged.dispatch); this.shaderControlState.changed.add(this.specificationChanged.dispatch); @@ -293,6 +302,7 @@ export class ImageUserLayer extends Base { restoreState(specification: any) { super.restoreState(specification); this.opacity.restoreState(specification[OPACITY_JSON_KEY]); + this.codeVisible.restoreState(specification[CODE_VISIBLE_KEY]); verifyOptionalObjectProperty(specification, BLEND_JSON_KEY, (blendValue) => this.blendMode.restoreState(blendValue), ); @@ -338,6 +348,7 @@ export class ImageUserLayer extends Base { const x = super.toJSON(); x[OPACITY_JSON_KEY] = this.opacity.toJSON(); x[BLEND_JSON_KEY] = this.blendMode.toJSON(); + x[CODE_VISIBLE_KEY] = this.codeVisible.toJSON(); x[SHADER_JSON_KEY] = this.fragmentMain.toJSON(); x[SHADER_CONTROLS_JSON_KEY] = this.shaderControlState.toJSON(); x[CROSS_SECTION_RENDER_SCALE_JSON_KEY] = @@ -541,6 +552,22 @@ class RenderingOptionsTab extends Tab { topRow.className = "neuroglancer-image-dropdown-top-row"; topRow.appendChild(document.createTextNode("Shader")); topRow.appendChild(spacer); + + this.registerDisposer( + new ElementVisibilityFromTrackableBoolean( + this.layer.codeVisible, + this.codeWidget.element, + ), + ); + + const codeVisibilityControl = new CheckboxIcon(this.layer.codeVisible, { + enableTitle: "Show code", + disableTitle: "Hide code", + backgroundScheme: "dark", + svg: svgCode, + }); + topRow.appendChild(codeVisibilityControl.element); + topRow.appendChild( makeMaximizeButton({ title: "Show larger editor view", @@ -562,6 +589,7 @@ class RenderingOptionsTab extends Tab { new ChannelDimensionsWidget(layer.channelCoordinateSpaceCombiner), ).element, ); + element.appendChild(this.codeWidget.element); element.appendChild( this.registerDisposer( diff --git a/src/layer/single_mesh/index.ts b/src/layer/single_mesh/index.ts index d0be944802..094dea43ab 100644 --- a/src/layer/single_mesh/index.ts +++ b/src/layer/single_mesh/index.ts @@ -15,6 +15,7 @@ */ import "#src/layer/single_mesh/style.css"; +import svgCode from "ikonate/icons/code-alt.svg?raw"; import type { ManagedUserLayer } from "#src/layer/index.js"; import { @@ -31,11 +32,16 @@ import { SingleMeshDisplayState, SingleMeshLayer, } from "#src/single_mesh/frontend.js"; +import { + ElementVisibilityFromTrackableBoolean, + TrackableBoolean, +} from "#src/trackable_boolean.js"; import type { WatchableValueInterface } from "#src/trackable_value.js"; import { WatchableValue } from "#src/trackable_value.js"; import type { Borrowed } from "#src/util/disposable.js"; import { RefCounted } from "#src/util/disposable.js"; import { removeChildren, removeFromParent } from "#src/util/dom.js"; +import { CheckboxIcon } from "#src/widget/checkbox_icon.js"; import { makeHelpButton } from "#src/widget/help_button.js"; import { makeMaximizeButton } from "#src/widget/maximize_button.js"; import { ShaderCodeWidget } from "#src/widget/shader_code_widget.js"; @@ -47,14 +53,18 @@ import { Tab } from "#src/widget/tab_view.js"; const SHADER_JSON_KEY = "shader"; const SHADER_CONTROLS_JSON_KEY = "shaderControls"; +const CODE_VISIBLE_KEY = "codeVisible"; export class SingleMeshUserLayer extends UserLayer { displayState = new SingleMeshDisplayState(); + codeVisible = new TrackableBoolean(true); + vertexAttributes = new WatchableValue( undefined, ); constructor(public managedLayer: Borrowed) { super(managedLayer); + this.codeVisible.changed.add(this.specificationChanged.dispatch); this.registerDisposer( this.displayState.shaderControlState.changed.add( this.specificationChanged.dispatch, @@ -75,6 +85,7 @@ export class SingleMeshUserLayer extends UserLayer { restoreState(specification: any) { super.restoreState(specification); + this.codeVisible.restoreState(specification[CODE_VISIBLE_KEY]); this.displayState.fragmentMain.restoreState(specification[SHADER_JSON_KEY]); this.displayState.shaderControlState.restoreState( specification[SHADER_CONTROLS_JSON_KEY], @@ -116,6 +127,7 @@ export class SingleMeshUserLayer extends UserLayer { const x = super.toJSON(); x[SHADER_JSON_KEY] = this.displayState.fragmentMain.toJSON(); x[SHADER_CONTROLS_JSON_KEY] = this.displayState.shaderControlState.toJSON(); + x[CODE_VISIBLE_KEY] = this.codeVisible.toJSON(); return x; } @@ -210,6 +222,21 @@ class DisplayOptionsTab extends Tab { spacer.style.flex = "1"; topRow.appendChild(spacer); + + this.registerDisposer( + new ElementVisibilityFromTrackableBoolean( + this.layer.codeVisible, + this.codeWidget.element, + ), + ); + const codeVisibilityControl = new CheckboxIcon(this.layer.codeVisible, { + enableTitle: "Show code", + disableTitle: "Hide code", + backgroundScheme: "dark", + svg: svgCode, + }); + topRow.appendChild(codeVisibilityControl.element); + topRow.appendChild( makeMaximizeButton({ title: "Show larger editor view",