Skip to content

Commit

Permalink
feat: Support unified customizable keyboard and mouse event handling.
Browse files Browse the repository at this point in the history
Keyboard and mouse bindings are now both specified using the new EventActionMap class, that maps an
event identifier to an action specification.  Mouse bindings are no longer hardcoded in
RenderedDataPanel, PerspectivePanel, and SliceViewPanel.  Actions are now dispatched using the DOM
event mechanism rather than the previous ad-hoc mechanism.
  • Loading branch information
jbms committed Oct 12, 2017
1 parent bd511bb commit cde2a51
Show file tree
Hide file tree
Showing 25 changed files with 1,195 additions and 754 deletions.
22 changes: 4 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,16 @@ Neuroglancer itself is purely a client-side program, but it depends on data bein
- Chrome >= 51
- Firefox >= 46

# Key bindings
# Keyboard and mouse bindings

See [src/neuroglancer/default_key_bindings.ts](src/neuroglancer/default_key_bindings.ts).

# Mouse bindings
For the complete set of bindings, see
[src/neuroglancer/ui/default_input_event_bindings.ts](src/neuroglancer/default_input_event_bindings.ts),
or within Neuroglancer, press `h` or click on the button labeled `?` in the upper right corner.

- Click on a layer name to toggle its visibility.

- Double-click on a layer name to edit its properties.

- Left-drag within a slice view to move within that plane.

- Shift-left-drag within a slice view to change the orientation of the slice views. The projection of the point where the drag started will remain fixed.

- Rotate the mouse wheel to move forward or backward in the local z axis of the 3-d or cross-sectional view under the mouse pointer. Hold down shift to move 10x faster.

- Control-mouse wheel zooms in or out. When used in the cross-sectional view, the projection of the point under the mouse pointer will remain fixed.

- Left-drag within the 3-d view to change the orientation.

- Right click to move to the position under the mouse pointer.

- Double click to toggle showing the object under the mouse pointer.

- Hover over a segmentation layer name to see the current list of objects shown and to access the opacity sliders.

- Hover over an image layer name to access the opacity slider and the text editor for modifying the [rendering code](src/neuroglancer/sliceview/image_layer_rendering.md).
Expand Down
5 changes: 3 additions & 2 deletions examples/dependent-project/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@
import {makeExtraKeyBindings} from 'my-neuroglancer-project/extra_key_bindings';
import {navigateToOrigin} from 'my-neuroglancer-project/navigate_to_origin';
import {setupDefaultViewer} from 'neuroglancer/ui/default_viewer_setup';
import {registerActionListener} from 'neuroglancer/util/event_action_map';

window.addEventListener('DOMContentLoaded', () => {
const viewer = setupDefaultViewer();
makeExtraKeyBindings(viewer.keyMap);
viewer.keyCommands.set('navigate-to-origin', navigateToOrigin);
makeExtraKeyBindings(viewer.inputEventMap);
registerActionListener(viewer.element, 'navigate-to-origin', () => navigateToOrigin(viewer));
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* limitations under the License.
*/

import {KeySequenceMap} from 'neuroglancer/util/keyboard_shortcut_handler';
import {EventActionMap} from 'neuroglancer/util/event_action_map';

export function makeExtraKeyBindings(keyMap: KeySequenceMap) {
keyMap.bind('keyo', 'navigate-to-origin');
export function makeExtraKeyBindings(keyMap: EventActionMap) {
keyMap.set('keyo', 'navigate-to-origin');
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import {kZeroVec} from 'neuroglancer/util/geom';
import {Viewer} from 'neuroglancer/viewer';

export function navigateToOrigin(this: Viewer) {
let {position} = this.navigationState.pose;
export function navigateToOrigin(viewer: Viewer) {
let {position} = viewer.navigationState.pose;
if (position.valid) {
position.setVoxelCoordinates(kZeroVec);
}
Expand Down
53 changes: 0 additions & 53 deletions src/neuroglancer/default_key_bindings.ts

This file was deleted.

25 changes: 0 additions & 25 deletions src/neuroglancer/display_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ export abstract class RenderedPanel extends RefCounted {
public visibility: WatchableVisibilityPriority) {
super();
this.gl = context.gl;
this.registerEventListener(element, 'mouseenter', (_event: MouseEvent) => {
this.context.setActivePanel(this);
});
context.addPanel(this);
}

Expand All @@ -52,10 +49,6 @@ export abstract class RenderedPanel extends RefCounted {

abstract onResize(): void;

onKeyCommand(_action: string) {
return false;
}

abstract draw(): void;

disposed() {
Expand All @@ -74,7 +67,6 @@ export class DisplayContext extends RefCounted {
updateStarted = new NullarySignal();
updateFinished = new NullarySignal();
panels = new Set<RenderedPanel>();
activePanel: RenderedPanel|null = null;
private updatePending: number|null = null;
private needsRedraw = false;

Expand All @@ -96,27 +88,10 @@ export class DisplayContext extends RefCounted {

addPanel(panel: RenderedPanel) {
this.panels.add(panel);
if (this.activePanel == null) {
this.setActivePanel(panel);
}
}

setActivePanel(panel: RenderedPanel|null) {
let existingPanel = this.activePanel;
if (existingPanel != null) {
existingPanel.element.attributes.removeNamedItem('isActivePanel');
}
if (panel != null) {
panel.element.setAttribute('isActivePanel', 'true');
}
this.activePanel = panel;
}

removePanel(panel: RenderedPanel) {
this.panels.delete(panel);
if (panel === this.activePanel) {
this.setActivePanel(null);
}
panel.dispose();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
overflow-x: hidden;
}

.describe-key-bindings .dl {
.describe-key-bindings-container {
overflow-y: scroll;
max-height: 80vh;
overflow-x: hidden;
Expand Down
118 changes: 118 additions & 0 deletions src/neuroglancer/help/input_event_bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* @license
* Copyright 2016 Google Inc.
* 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 {Overlay} from 'neuroglancer/overlay';
import {EventActionMap} from 'neuroglancer/util/event_action_map';

require('./input_event_bindings.css');

export function formatKeyName(name: string) {
if (name.startsWith('key')) {
return name.substring(3);
}
if (name.startsWith('digit')) {
return name.substring(5);
}
if (name.startsWith('arrow')) {
return name.substring(5);
}
return name;
}

export function formatKeyStroke(stroke: string) {
let parts = stroke.split('+');
return parts.map(formatKeyName).join('+');
}

export class InputEventBindingHelpDialog extends Overlay {
/**
* @param keyMap Key map to list.
*/
constructor(bindings: Iterable<[string, EventActionMap]>) {
super();

let {content} = this;
content.classList.add('describe-key-bindings');

let scroll = document.createElement('div');
scroll.classList.add('describe-key-bindings-container');

interface BindingList {
label: string;
entries: Map<string, string>;
}

const uniqueMaps = new Map<EventActionMap, BindingList>();
function addEntries(eventMap: EventActionMap, entries: Map<string, string>) {
for (const parent of eventMap.parents) {
if (parent.label !== undefined) {
addMap(parent.label, parent);
} else {
addEntries(parent, entries);
}
}
for (const [event, eventAction] of eventMap.bindings.entries()) {
const firstColon = event.indexOf(':');
const suffix = event.substring(firstColon + 1);
entries.set(suffix, eventAction.action);
}
}

function addMap(label: string, map: EventActionMap) {
if (uniqueMaps.has(map)) {
return;
}
const list: BindingList = {
label,
entries: new Map(),
};
addEntries(map, list.entries);
uniqueMaps.set(map, list);
}

for (const [label, eventMap] of bindings) {
addMap(label, eventMap);
}

for (const list of uniqueMaps.values()) {
let header = document.createElement('h2');
header.textContent = list.label;
scroll.appendChild(header);
let dl = document.createElement('div');
dl.className = 'dl';

for (const [event, action] of list.entries) {
let container = document.createElement('div');
let container2 = document.createElement('div');
container2.className = 'definition-outer-container';
container.className = 'definition-container';
let dt = document.createElement('div');
dt.className = 'dt';
dt.textContent = formatKeyStroke(event);
let dd = document.createElement('div');
dd.className = 'dd';
dd.textContent = action;
container.appendChild(dt);
container.appendChild(dd);
dl.appendChild(container2);
container2.appendChild(container);
}
scroll.appendChild(dl);
}
content.appendChild(scroll);
}
}

74 changes: 0 additions & 74 deletions src/neuroglancer/help/key_bindings.ts

This file was deleted.

Loading

0 comments on commit cde2a51

Please sign in to comment.