From a77e943c9fc09dcc6ca4a0434b32b5c7927a06f9 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Fri, 24 Apr 2026 10:35:52 +0200 Subject: [PATCH 1/2] refactor: remove mouseState listener in edit tab --- src/layer/segmentation/index.ts | 16 +++++++++++++++- src/ui/spatial_skeleton_edit_tab.ts | 6 ++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/layer/segmentation/index.ts b/src/layer/segmentation/index.ts index 1a22308b1..8a9cea9a6 100644 --- a/src/layer/segmentation/index.ts +++ b/src/layer/segmentation/index.ts @@ -31,7 +31,6 @@ import { import type { LayerActionContext, ManagedUserLayer, - MouseSelectionState, UserLayerSelectionState, } from "#src/layer/index.js"; import { @@ -1255,6 +1254,9 @@ export class SegmentationUserLayer extends Base { readonly selectedSpatialSkeletonNodeId = new WatchableValue< number | undefined >(undefined); + readonly hoveredSpatialSkeletonNodeId = new WatchableValue< + number | undefined + >(undefined); readonly spatialSkeletonVisibleChunksNeeded = new WatchableValue(0); readonly spatialSkeletonVisibleChunksAvailable = new WatchableValue(0); readonly spatialSkeletonVisibleChunksLoaded = new WatchableValue(false); @@ -1569,6 +1571,18 @@ export class SegmentationUserLayer extends Base { ), ); syncSelectedSpatialSkeletonNodeIdFromGlobalSelection(); + const syncHoveredSpatialSkeletonNodeId = () => { + this.hoveredSpatialSkeletonNodeId.value = + getSpatialSkeletonNodeIdFromViewerHover( + this.manager.layerSelectedValues.mouseState, + this, + ); + }; + this.registerDisposer( + this.manager.layerSelectedValues.changed.add( + syncHoveredSpatialSkeletonNodeId, + ), + ); this.displayState.selectedAlpha.changed.add( this.specificationChanged.dispatch, ); diff --git a/src/ui/spatial_skeleton_edit_tab.ts b/src/ui/spatial_skeleton_edit_tab.ts index 56ae48525..20c37a4ea 100644 --- a/src/ui/spatial_skeleton_edit_tab.ts +++ b/src/ui/spatial_skeleton_edit_tab.ts @@ -31,7 +31,6 @@ import svg_retweet from "ikonate/icons/retweet.svg?raw"; import svg_share_android from "ikonate/icons/share-android.svg?raw"; import svg_undo from "ikonate/icons/undo.svg?raw"; import type { SegmentationUserLayer } from "#src/layer/segmentation/index.js"; -import { getSpatialSkeletonNodeIdFromViewerHover } from "#src/layer/segmentation/selection.js"; import { executeSpatialSkeletonDeleteNode, executeSpatialSkeletonNodeTrueEndUpdate, @@ -316,7 +315,6 @@ export class SpatialSkeletonEditTab extends Tab { const renderedRowsByNodeId = new Map(); const renderedEntriesByNodeId = new Map(); const skeletonState = layer.spatialSkeletonState; - const mouseState = layer.manager.root.layerSelectedValues.mouseState; const navigationGraphCache = new Map< number, { @@ -528,7 +526,7 @@ export class SpatialSkeletonEditTab extends Tab { layer.getSpatialSkeletonNodeDisplayDescription(node); const getHoveredNodeIdFromViewer = () => { - return getSpatialSkeletonNodeIdFromViewerHover(mouseState, layer); + return layer.hoveredSpatialSkeletonNodeId.value; }; const applyRowInteractionState = ( @@ -1742,7 +1740,7 @@ export class SpatialSkeletonEditTab extends Tab { }), ); this.registerDisposer( - mouseState.changed.add(() => { + layer.hoveredSpatialSkeletonNodeId.changed.add(() => { updateHoveredViewerNode(); }), ); From 5082f0dbedb9709491748c2f5d593b39e98940f7 Mon Sep 17 00:00:00 2001 From: Sean Martin Date: Fri, 24 Apr 2026 11:09:30 +0200 Subject: [PATCH 2/2] feat: explore message on non-spatial node this isn't quite right yes, but I had the feeling that hovering over nodes from the spatially indexed skeletons and seeing nothing is confusing. Was exploring here the idea that we could show a message. In this case it would show the loading message, which doesn't make much sense, but gives the idea. --- src/layer/segmentation/index.ts | 15 +++++++++++++++ src/skeleton/frontend.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/layer/segmentation/index.ts b/src/layer/segmentation/index.ts index 8a9cea9a6..a6f9e0b1a 100644 --- a/src/layer/segmentation/index.ts +++ b/src/layer/segmentation/index.ts @@ -31,6 +31,7 @@ import { import type { LayerActionContext, ManagedUserLayer, + MouseSelectionState, UserLayerSelectionState, } from "#src/layer/index.js"; import { @@ -1495,6 +1496,20 @@ export class SegmentationUserLayer extends Base { }, pin); }; + captureSelectionState( + state: this["selectionState"], + mouseState: MouseSelectionState, + ) { + super.captureSelectionState(state, mouseState); + const nodeId = getSpatialSkeletonNodeIdFromViewerHover(mouseState, this); + if (nodeId === undefined) return; + state.spatialSkeletonNodeId = String(nodeId); + const segmentId = mouseState.pickedSpatialSkeletonSegmentId; + if (segmentId !== undefined && Number.isSafeInteger(segmentId) && segmentId > 0) { + state.spatialSkeletonSegmentId = String(segmentId); + } + } + filterBySegmentLabel = (id: bigint) => { const augmented = augmentSegmentId(this.displayState, id); const { label } = augmented; diff --git a/src/skeleton/frontend.ts b/src/skeleton/frontend.ts index 6beb66180..3ee52f454 100644 --- a/src/skeleton/frontend.ts +++ b/src/skeleton/frontend.ts @@ -2865,6 +2865,16 @@ export class SpatiallyIndexedSkeletonLayer ); } + resolveNodePickFromChunk( + chunk: SpatiallyIndexedSkeletonChunk, + pickedOffset: number, + ): number | undefined { + for (const [nodeId, vertexIndex] of chunk.nodeMap) { + if (vertexIndex === pickedOffset) return nodeId; + } + return undefined; + } + updateVisibleChunksForView( view: SpatiallyIndexedSkeletonView, transformedSources: readonly TransformedSource[][], @@ -3560,6 +3570,15 @@ export class PerspectiveViewSpatiallyIndexedSkeletonLayer extends PerspectiveVie if (segmentId !== undefined) { mouseState.pickedSpatialSkeletonSegmentId = segmentId; } + if (pickData.kind === "segment-node") { + const nodeId = this.base.resolveNodePickFromChunk( + pickData.chunk, + pickedOffset, + ); + if (nodeId !== undefined) { + mouseState.pickedSpatialSkeletonNodeId = nodeId; + } + } } } @@ -3865,6 +3884,15 @@ export class SliceViewPanelSpatiallyIndexedSkeletonLayer extends SliceViewPanelR if (segmentId !== undefined) { mouseState.pickedSpatialSkeletonSegmentId = segmentId; } + if (pickData.kind === "segment-node") { + const nodeId = this.base.resolveNodePickFromChunk( + pickData.chunk, + pickedOffset, + ); + if (nodeId !== undefined) { + mouseState.pickedSpatialSkeletonNodeId = nodeId; + } + } } }