-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(tools): custom keybinds and layer independent tool keybinds #464
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,26 +14,124 @@ | |
* limitations under the License. | ||
*/ | ||
|
||
import type { UserLayer, UserLayerConstructor } from "#src/layer/index.js"; | ||
import { layerTypes } from "#src/layer/index.js"; | ||
import { StatusMessage } from "#src/status.js"; | ||
import { | ||
bindDefaultCopyHandler, | ||
bindDefaultPasteHandler, | ||
} from "#src/ui/default_clipboard_handling.js"; | ||
import { setDefaultInputEventBindings } from "#src/ui/default_input_event_bindings.js"; | ||
import { makeDefaultViewer } from "#src/ui/default_viewer.js"; | ||
import type { MinimalViewerOptions } from "#src/ui/minimal_viewer.js"; | ||
import { bindTitle } from "#src/ui/title.js"; | ||
import type { Tool } from "#src/ui/tool.js"; | ||
import { restoreTool } from "#src/ui/tool.js"; | ||
import { UrlHashBinding } from "#src/ui/url_hash_binding.js"; | ||
import { | ||
verifyObject, | ||
verifyObjectProperty, | ||
verifyString, | ||
} from "#src/util/json.js"; | ||
|
||
declare let NEUROGLANCER_DEFAULT_STATE_FRAGMENT: string | undefined; | ||
|
||
type CustomToolBinding = { | ||
layer: string; | ||
tool: unknown; | ||
provider?: string; | ||
}; | ||
|
||
type CustomBindings = { | ||
[key: string]: CustomToolBinding | string | boolean; | ||
}; | ||
|
||
declare const CUSTOM_BINDINGS: CustomBindings | undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please rename to NEUROGLANCER_CUSTOM_INPUT_BINDINGS |
||
export const hasCustomBindings = | ||
typeof CUSTOM_BINDINGS !== "undefined" && | ||
Object.keys(CUSTOM_BINDINGS).length > 0; | ||
|
||
/** | ||
* Sets up the default neuroglancer viewer. | ||
*/ | ||
export function setupDefaultViewer(options?: Partial<MinimalViewerOptions>) { | ||
const viewer = ((<any>window).viewer = makeDefaultViewer(options)); | ||
export function setupDefaultViewer() { | ||
const viewer = ((<any>window).viewer = makeDefaultViewer()); | ||
setDefaultInputEventBindings(viewer.inputEventBindings); | ||
|
||
const bindNonLayerSpecificTool = ( | ||
obj: unknown, | ||
toolKey: string, | ||
desiredLayerType: UserLayerConstructor, | ||
desiredProvider?: string, | ||
) => { | ||
let previousTool: Tool<object> | undefined; | ||
let previousLayer: UserLayer | undefined; | ||
if (typeof obj === "string") { | ||
obj = { type: obj }; | ||
} | ||
verifyObject(obj); | ||
const type = verifyObjectProperty(obj, "type", verifyString); | ||
viewer.bindAction(`tool-${type}`, () => { | ||
const acceptableLayers = viewer.layerManager.managedLayers.filter( | ||
(managedLayer) => { | ||
const correctLayerType = | ||
managedLayer.layer instanceof desiredLayerType; | ||
if (desiredProvider && correctLayerType) { | ||
for (const dataSource of managedLayer.layer?.dataSources || []) { | ||
const protocol = viewer.dataSourceProvider.getProvider( | ||
dataSource.spec.url, | ||
)[2]; | ||
if (protocol === desiredProvider) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the kvstore changes getProvider is not exactly the right method anymore. Perhaps just have a regexp that matches on the URL? |
||
return true; | ||
} | ||
} | ||
return false; | ||
} else { | ||
return correctLayerType; | ||
} | ||
}, | ||
); | ||
if (acceptableLayers.length > 0) { | ||
const firstLayer = acceptableLayers[0].layer; | ||
if (firstLayer) { | ||
if (firstLayer !== previousLayer) { | ||
previousTool = restoreTool(firstLayer, obj); | ||
previousLayer = firstLayer; | ||
} | ||
if (previousTool) { | ||
viewer.activateTool(toolKey, previousTool); | ||
} | ||
} | ||
} | ||
}); | ||
}; | ||
|
||
if (hasCustomBindings) { | ||
for (const [key, val] of Object.entries(CUSTOM_BINDINGS!)) { | ||
if (typeof val === "string") { | ||
viewer.inputEventBindings.global.set(key, val); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that in addition to the global map there are several others that are relevant in default_input_event_bindings.ts --- in particular the default slice view and perspective view bindings. Perhaps this should account for that? |
||
} else if (typeof val === "boolean") { | ||
if (!val) { | ||
viewer.inputEventBindings.global.delete(key); | ||
viewer.inputEventBindings.global.parents.map((parent) => | ||
parent.delete(key), | ||
); | ||
} | ||
} else { | ||
viewer.inputEventBindings.global.set(key, `tool-${val.tool}`); | ||
const layerConstructor = layerTypes.get(val.layer); | ||
if (layerConstructor) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably should throw or log an error if layerConstructor not found to avoid silently ignoring invalid input. Since this is a build time option better to ensure it is correct. |
||
const toolKey = key.charAt(key.length - 1).toUpperCase(); | ||
bindNonLayerSpecificTool( | ||
val.tool, | ||
toolKey, | ||
layerConstructor, | ||
val.provider, | ||
); | ||
} | ||
} | ||
} | ||
} | ||
|
||
const hashBinding = viewer.registerDisposer( | ||
new UrlHashBinding( | ||
viewer.state, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -318,15 +318,15 @@ export class GlobalToolBinder extends RefCounted { | |
this.changed.dispatch(); | ||
} | ||
|
||
activate(key: string): Borrowed<Tool> | undefined { | ||
const tool = this.get(key); | ||
activate(key: string, tool?: Tool<object>): Borrowed<Tool> | undefined { | ||
tool = tool || this.get(key); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
if (tool === undefined) { | ||
this.deactivate_(); | ||
return; | ||
} | ||
this.debounceDeactivate.cancel(); | ||
const activeTool = this.activeTool_; | ||
if (tool === activeTool?.tool) { | ||
if (tool.toJSON() === activeTool?.tool.toJSON()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't compare the json like this because it can be an object rather than a plain string. Instead we need to compare the serialized json. However, it would be better if we can avoid having to insert the json comparison here, and instead just ensure that the same |
||
if (tool.toggle) { | ||
this.deactivate_(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename to
layerType
for consistency with the tool palette queries.