diff --git a/glue/crumble/README.md b/glue/crumble/README.md
index 1b402a6f8..f649a4ced 100644
--- a/glue/crumble/README.md
+++ b/glue/crumble/README.md
@@ -11,11 +11,13 @@ Crumble is not polished.**
## Index
+- [Crumble](#crumble)
+ - [Index](#index)
- [Accessing Crumble](#accessing-crumble)
- [Using Crumble](#using-crumble)
- - [Loading and Saving Circuits](#loading-saving)
- - [Keyboard Controls](#keyboard-commands)
- - [Mouse Controls](#mouse-commands)
+ - [Loading and Saving Circuits](#loading-and-saving-circuits)
+ - [Keyboard Controls](#keyboard-controls)
+ - [Mouse Controls](#mouse-controls)
- [Building Crumble](#building-crumble)
- [Testing Crumble](#testing-crumble)
@@ -222,8 +224,15 @@ of the **two qubit variant** (c)
of the **square root** (s)
of the **Y gate** (y) (i.e. the gate `SQRT_YY_DAG 1 2`).
+**Interaction**
+
+- `Arrow Keys (Up/Down/Left/Right)`: Pan the Planar Layout view
+- `ctrl+0`: Reset View (reset both the planar view and the timeline scroll)
+- `ctrl+-`: Zoom out
+- `ctrl++` or `ctrl+=`: Zoom in
+
-### Mouse Controls
+## Mouse Controls
Note: to `BoxSelect` means to press down the left mouse button, drag the mouse
while holding the button down to outline a rectangular region, and then release
@@ -245,6 +254,12 @@ box selection action. The specific parity being used depends on context. For
example, when selecting a column of qubits, the row parity is used. When
selecting a 2d region, the subgrid parity is used.
+- `shift+MMB` (Middle Mouse Button):
+ - Drag on the left (planar layout) to pan the planar layout view
+ - Drag on the right to scroll vertically through the wires
+- `ctrl+MMB`:
+ - Drag vertically on the left (planar layout) to zoom in/out
+
# Building Crumble
diff --git a/glue/crumble/crumble.html b/glue/crumble/crumble.html
index 4dff02ecc..0192b72f4 100644
--- a/glue/crumble/crumble.html
+++ b/glue/crumble/crumble.html
@@ -227,6 +227,11 @@
+
+
+
+
+
diff --git a/glue/crumble/draw/config.js b/glue/crumble/draw/config.js
index 25c6c739b..9baa796f1 100644
--- a/glue/crumble/draw/config.js
+++ b/glue/crumble/draw/config.js
@@ -2,5 +2,5 @@ const pitch = 50;
const rad = 10;
const OFFSET_X = -pitch + Math.floor(pitch / 4) + 0.5;
const OFFSET_Y = -pitch + Math.floor(pitch / 4) + 0.5;
-
-export {pitch, rad, OFFSET_X, OFFSET_Y};
+const step = pitch / 2;
+export {pitch, rad, OFFSET_X, OFFSET_Y, step};
diff --git a/glue/crumble/draw/main_draw.js b/glue/crumble/draw/main_draw.js
index 4636133e2..40aed4961 100644
--- a/glue/crumble/draw/main_draw.js
+++ b/glue/crumble/draw/main_draw.js
@@ -10,17 +10,18 @@ import {beginPathPolygon} from './draw_util.js';
* @param {!number|undefined} y
* @return {![undefined, undefined]|![!number, !number]}
*/
-function xyToPos(x, y) {
+function xyToPos(x, y, scrollX = 0, scrollY = 0, zoomScale = 1.0) {
if (x === undefined || y === undefined) {
return [undefined, undefined];
}
- let focusX = x / pitch;
- let focusY = y / pitch;
+ let focusX = (x - scrollX) / (pitch * zoomScale);
+ let focusY = (y - scrollY) / (pitch * zoomScale);
let roundedX = Math.floor(focusX * 2 + 0.5) / 2;
let roundedY = Math.floor(focusY * 2 + 0.5) / 2;
let centerX = roundedX*pitch;
let centerY = roundedY*pitch;
- if (Math.abs(centerX - x) <= rad && Math.abs(centerY - y) <= rad && roundedX % 1 === roundedY % 1) {
+ let scaledRad = rad * zoomScale;
+ if (Math.abs(centerX - x) <= scaledRad && Math.abs(centerY - y) <= scaledRad && roundedX % 1 === roundedY % 1) {
return [roundedX, roundedY];
}
return [undefined, undefined];
@@ -198,6 +199,7 @@ function defensiveDraw(ctx, body) {
*/
function draw(ctx, snap) {
let circuit = snap.circuit;
+ let zoom = snap.zoomScale || 1.0;
let numPropagatedLayers = 0;
for (let layer of circuit.layers) {
@@ -209,12 +211,17 @@ function draw(ctx, snap) {
}
}
- let c2dCoordTransform = (x, y) => [x*pitch - OFFSET_X, y*pitch - OFFSET_Y];
+ let c2dCoordTransform = (x, y) => [x * pitch * snap.zoomScale - OFFSET_X + snap.scrollX, y * pitch * snap.zoomScale - OFFSET_Y + snap.scrollY];
let qubitDrawCoords = q => {
let x = circuit.qubitCoordData[2 * q];
let y = circuit.qubitCoordData[2 * q + 1];
return c2dCoordTransform(x, y);
};
+ let logicCoords = q => {
+ let x = circuit.qubitCoordData[2 * q];
+ let y = circuit.qubitCoordData[2 * q + 1];
+ return [x * pitch, y * pitch];
+ }
let propagatedMarkerLayers = /** @type {!Map} */ new Map();
for (let mi = 0; mi < numPropagatedLayers; mi++) {
propagatedMarkerLayers.set(mi, PropagatedPauliFrames.fromCircuit(circuit, mi));
@@ -253,10 +260,12 @@ function draw(ctx, snap) {
}
}
+ let scaledRad = rad * snap.zoomScale;
+
defensiveDraw(ctx, () => {
ctx.fillStyle = 'white';
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
- let [focusX, focusY] = xyToPos(snap.curMouseX, snap.curMouseY);
+ let [focusX, focusY] = xyToPos(snap.curMouseX, snap.curMouseY, snap.scrollX, snap.scrollY);
// Draw the background polygons.
let lastPolygonLayer = snap.curLayer;
@@ -268,35 +277,47 @@ function draw(ctx, snap) {
}
}
}
+
+
+
+ ctx.save();
+ ctx.translate(- OFFSET_X + snap.scrollX, - OFFSET_Y + snap.scrollY);
+ ctx.scale(zoom, zoom);
+
let polygonMarkers = [...circuit.layers[lastPolygonLayer].markers];
polygonMarkers.sort((a, b) => b.id_targets.length - a.id_targets.length);
for (let op of polygonMarkers) {
if (op.gate.name === 'POLYGON') {
- op.id_draw(qubitDrawCoords, ctx);
+ op.id_draw(logicCoords, ctx);
}
}
+ ctx.restore();
// Draw the grid of qubits.
defensiveDraw(ctx, () => {
- for (let qx = 0; qx < 100; qx += 0.5) {
+ let qStep = 0.5;
+ if (snap.zoomScale < 0.2) {
+ qStep = 5.0;
+ } else if (snap.zoomScale < 0.5) {
+ qStep = 2.0;
+ } else if (snap.zoomScale < 0.8) {
+ qStep = 1.0;
+ }
+ for (let qx = 0; qx < 100; qx += qStep) {
let [x, _] = c2dCoordTransform(qx, 0);
let s = `${qx}`;
ctx.fillStyle = 'black';
ctx.fillText(s, x - ctx.measureText(s).width / 2, 15);
}
- for (let qy = 0; qy < 100; qy += 0.5) {
+ for (let qy = 0; qy < 100; qy += qStep) {
let [_, y] = c2dCoordTransform(0, qy);
let s = `${qy}`;
ctx.fillStyle = 'black';
ctx.fillText(s, 18 - ctx.measureText(s).width, y);
}
-
+
ctx.strokeStyle = 'black';
for (let qx = 0; qx < 100; qx += 0.5) {
- let [x, _] = c2dCoordTransform(qx, 0);
- let s = `${qx}`;
- ctx.fillStyle = 'black';
- ctx.fillText(s, x - ctx.measureText(s).width / 2, 15);
for (let qy = qx % 1; qy < 100; qy += 1) {
let [x, y] = c2dCoordTransform(qx, qy);
ctx.fillStyle = 'white';
@@ -308,8 +329,8 @@ function draw(ctx, snap) {
if (isVeryUnused) {
ctx.globalAlpha *= 0.25;
}
- ctx.fillRect(x - rad, y - rad, 2*rad, 2*rad);
- ctx.strokeRect(x - rad, y - rad, 2*rad, 2*rad);
+ ctx.fillRect(x - scaledRad, y - scaledRad, 2 * scaledRad, 2 * scaledRad);
+ ctx.strokeRect(x - scaledRad, y - scaledRad, 2 * scaledRad, 2 * scaledRad);
if (isUnused) {
ctx.globalAlpha *= 4;
}
@@ -320,42 +341,48 @@ function draw(ctx, snap) {
}
});
+ ctx.save();
+ ctx.translate(- OFFSET_X + snap.scrollX, - OFFSET_Y + snap.scrollY);
+ ctx.scale(zoom, zoom);
+
for (let [mi, p] of propagatedMarkerLayers.entries()) {
- drawCrossMarkers(ctx, snap, qubitDrawCoords, p, mi);
+ drawCrossMarkers(ctx, snap, logicCoords, p, mi);
}
for (let op of circuit.layers[snap.curLayer].iter_gates_and_markers()) {
if (op.gate.name !== 'POLYGON') {
- op.id_draw(qubitDrawCoords, ctx);
+ op.id_draw(logicCoords, ctx);
}
}
-
+
defensiveDraw(ctx, () => {
ctx.globalAlpha *= 0.25
for (let [qx, qy] of snap.timelineSet.values()) {
let [x, y] = c2dCoordTransform(qx, qy);
ctx.fillStyle = 'yellow';
- ctx.fillRect(x - rad * 1.25, y - rad * 1.25, 2.5*rad, 2.5*rad);
+ ctx.fillRect(x - scaledRad * 1.25, y - scaledRad * 1.25, 2.5 * scaledRad, 2.5 * scaledRad);
}
});
+ drawMarkers(ctx, snap, logicCoords, propagatedMarkerLayers);
+
+ ctx.restore();
+
defensiveDraw(ctx, () => {
ctx.globalAlpha *= 0.5
for (let [qx, qy] of snap.focusedSet.values()) {
let [x, y] = c2dCoordTransform(qx, qy);
ctx.fillStyle = 'blue';
- ctx.fillRect(x - rad * 1.25, y - rad * 1.25, 2.5*rad, 2.5*rad);
+ ctx.fillRect(x - scaledRad * 1.25, y - scaledRad * 1.25, 2.5 * scaledRad, 2.5 * scaledRad);
}
});
- drawMarkers(ctx, snap, qubitDrawCoords, propagatedMarkerLayers);
-
if (focusX !== undefined) {
ctx.save();
ctx.globalAlpha *= 0.5;
let [x, y] = c2dCoordTransform(focusX, focusY);
ctx.fillStyle = 'red';
- ctx.fillRect(x - rad, y - rad, 2*rad, 2*rad);
+ ctx.fillRect(x - scaledRad, y - scaledRad, 2 * scaledRad, 2 * scaledRad);
ctx.restore();
}
@@ -379,11 +406,13 @@ function draw(ctx, snap) {
}
for (let [qx, qy] of snap.boxHighlightPreview) {
let [x, y] = c2dCoordTransform(qx, qy);
- ctx.fillRect(x - rad, y - rad, rad*2, rad*2);
+ ctx.fillRect(x - scaledRad, y - scaledRad, 2 * scaledRad, 2 * scaledRad);
}
});
- });
+ ctx.font = `10px sans-serif`;
+ });
+
drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords, circuit.layers.length);
// Draw scrubber.
diff --git a/glue/crumble/draw/state_snapshot.js b/glue/crumble/draw/state_snapshot.js
index 3c31cb3d1..1c9ed8187 100644
--- a/glue/crumble/draw/state_snapshot.js
+++ b/glue/crumble/draw/state_snapshot.js
@@ -18,7 +18,7 @@ class StateSnapshot {
* @param {!number} mouseDownY
* @param {!Array} boxHighlightPreview
*/
- constructor(circuit, curLayer, focusedSet, timelineSet, curMouseX, curMouseY, mouseDownX, mouseDownY, boxHighlightPreview) {
+ constructor(circuit, curLayer, focusedSet, timelineSet, curMouseX, curMouseY, mouseDownX, mouseDownY, scrollX, scrollY, timelineScrollY, zoomScale, boxHighlightPreview) {
this.circuit = circuit.copy();
this.curLayer = curLayer;
this.focusedSet = new Map(focusedSet.entries());
@@ -27,6 +27,10 @@ class StateSnapshot {
this.curMouseY = curMouseY;
this.mouseDownX = mouseDownX;
this.mouseDownY = mouseDownY;
+ this.scrollX = scrollX || 0;
+ this.scrollY = scrollY || 0;
+ this.timelineScrollY = timelineScrollY || 0;
+ this.zoomScale = zoomScale || 1.0;
this.boxHighlightPreview = [...boxHighlightPreview];
while (this.circuit.layers.length <= this.curLayer) {
diff --git a/glue/crumble/draw/timeline_viewer.js b/glue/crumble/draw/timeline_viewer.js
index 2bab08cfb..2ca848198 100644
--- a/glue/crumble/draw/timeline_viewer.js
+++ b/glue/crumble/draw/timeline_viewer.js
@@ -129,7 +129,7 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
cur_x += rad * 0.25;
cur_run++;
}
- base_y2xy.set(`${x},${y}`, [Math.round(cur_x) + 0.5, Math.round(cur_y) + 0.5]);
+ base_y2xy.set(`${x},${y}`, [Math.round(cur_x) + 0.5, Math.round(cur_y + snap.timelineScrollY) + 0.5]);
}
let x_pitch = TIMELINE_PITCH + Math.ceil(rad*max_run*0.25);
diff --git a/glue/crumble/editor/editor_state.js b/glue/crumble/editor/editor_state.js
index 9feef8227..47aa7334c 100644
--- a/glue/crumble/editor/editor_state.js
+++ b/glue/crumble/editor/editor_state.js
@@ -3,7 +3,7 @@ import {Chorder} from "../keyboard/chord.js";
import {Layer, minXY} from "../circuit/layer.js";
import {Revision} from "../base/revision.js";
import {ObservableValue} from "../base/obs.js";
-import {pitch, rad} from "../draw/config.js";
+import {OFFSET_X, pitch, rad} from "../draw/config.js";
import {xyToPos} from "../draw/main_draw.js";
import {StateSnapshot} from "../draw/state_snapshot.js";
import {Operation} from "../circuit/operation.js";
@@ -46,6 +46,10 @@ class EditorState {
this.timelineSet = /** @type {!Map} */ new Map();
this.mouseDownX = /** @type {undefined|!number} */ undefined;
this.mouseDownY = /** @type {undefined|!number} */ undefined;
+ this.scrollX = 0;
+ this.scrollY = 0;
+ this.timelineScrollY = 0;
+ this.zoomScale = 1.0;
this.obs_val_draw_state = /** @type {!ObservableValue} */ new ObservableValue(this.toSnapshot(undefined));
}
@@ -210,6 +214,10 @@ class EditorState {
this.curMouseY,
this.mouseDownX,
this.mouseDownY,
+ this.scrollX,
+ this.scrollY,
+ this.timelineScrollY,
+ this.zoomScale,
this.currentPositionsBoxesByMouseDrag(this.chorder.curModifiers.has("alt")),
);
}
@@ -240,28 +248,37 @@ class EditorState {
let curMouseY = this.curMouseY;
let mouseDownX = this.mouseDownX;
let mouseDownY = this.mouseDownY;
+ let scrollX = this.scrollX || 0;
+ let scrollY = this.scrollY || 0;
+ let timelineScrollY = this.timelineScrollY || 0;
+ let zoomScale = this.zoomScale || 1.0;
let result = [];
if (curMouseX !== undefined && mouseDownX !== undefined) {
- let [sx, sy] = xyToPos(mouseDownX, mouseDownY);
- let x1 = Math.min(curMouseX, mouseDownX);
- let x2 = Math.max(curMouseX, mouseDownX);
- let y1 = Math.min(curMouseY, mouseDownY);
- let y2 = Math.max(curMouseY, mouseDownY);
- let gap = pitch/4 - rad;
+ let [sx, sy] = xyToPos(mouseDownX, mouseDownY, scrollX, scrollY, zoomScale);
+
+ let scaledPitch = pitch * zoomScale;
+
+ let x1 = Math.min(curMouseX, mouseDownX) - scrollX;
+ let x2 = Math.max(curMouseX, mouseDownX) - scrollX;
+ let y1 = Math.min(curMouseY, mouseDownY) - scrollY;
+ let y2 = Math.max(curMouseY, mouseDownY) - scrollY;
+ let gap = (pitch/4 - rad) * zoomScale;
x1 += gap;
x2 -= gap;
y1 += gap;
y2 -= gap;
- x1 = Math.floor(x1 * 2 / pitch + 0.5) / 2;
- x2 = Math.floor(x2 * 2 / pitch + 0.5) / 2;
- y1 = Math.floor(y1 * 2 / pitch + 0.5) / 2;
- y2 = Math.floor(y2 * 2 / pitch + 0.5) / 2;
+ x1 = Math.floor(x1 * 2 / scaledPitch + 0.5) / 2;
+ x2 = Math.floor(x2 * 2 / scaledPitch + 0.5) / 2;
+ y1 = Math.floor(y1 * 2 / scaledPitch + 0.5) / 2;
+ y2 = Math.floor(y2 * 2 / scaledPitch + 0.5) / 2;
let b = 1;
if (x1 === x2 || y1 === y2) {
b = 2;
}
for (let x = x1; x <= x2; x += 0.5) {
+ if (x < 0) continue;
for (let y = y1; y <= y2; y += 0.5) {
+ if (y < 0) continue;
if (x % 1 === y % 1) {
if (!parityLock || (sx % b === x % b && sy % b === y % b)) {
result.push([x, y]);
@@ -309,6 +326,47 @@ class EditorState {
}, preview, true);
}
+ /**
+ * @param {!number} dx
+ * @param {!number} dy
+ */
+ pan(dx, dy) {
+ this.scrollX += dx;
+ this.scrollY += dy;
+ this.force_redraw();
+ }
+ /**
+ * @param {!number} dy
+ */
+ timelinePan(dy) {
+ if( this.timelineScrollY + dy > 0) {
+ return;
+ }
+ this.timelineScrollY += dy;
+ this.force_redraw();
+ }
+ /**
+ * @param {!number} factor
+ * @returns
+ */
+ zoom(factor) {
+ if( this.zoomScale * factor < 0.1 || this.zoomScale * factor > 10) {
+ return;
+ }
+ this.zoomScale *= factor;
+ let mouseX = this.curMouseX || 0;
+ let mouseY = this.curMouseY || 0;
+ this.scrollX = mouseX - (mouseX - this.scrollX) * factor;
+ this.scrollY = mouseY - (mouseY - this.scrollY) * factor;
+ this.force_redraw();
+ }
+ resetView() {
+ this.scrollX = 0;
+ this.scrollY = 0;
+ this.timelineScrollY = 0;
+ this.zoomScale = 1.0;
+ this.force_redraw();
+ }
/**
* @param {!int} newLayer
*/
diff --git a/glue/crumble/main.js b/glue/crumble/main.js
index 7888b9d32..6d335ebdb 100644
--- a/glue/crumble/main.js
+++ b/glue/crumble/main.js
@@ -1,6 +1,6 @@
import {Circuit} from "./circuit/circuit.js"
import {minXY} from "./circuit/layer.js"
-import {pitch} from "./draw/config.js"
+import {pitch, step} from "./draw/config.js"
import {GATE_MAP} from "./gates/gateset.js"
import {EditorState} from "./editor/editor_state.js";
import {initUrlCircuitSync} from "./editor/sync_url_to_state.js";
@@ -31,6 +31,7 @@ const btnTimelineFocus = /** @type{!HTMLButtonElement} */ document.getElementByI
const btnClearTimelineFocus = /** @type{!HTMLButtonElement} */ document.getElementById('btnClearTimelineFocus');
const btnClearSelectedMarkers = /** @type{!HTMLButtonElement} */ document.getElementById('btnClearSelectedMarkers');
const btnShowExamples = /** @type {!HTMLButtonElement} */ document.getElementById('btnShowExamples');
+const btnResetView = /** @type {!HTMLButtonElement} */ document.getElementById('btnResetView');
const divExamples = /** @type{!HTMLDivElement} */ document.getElementById('examples-div');
// Prevent typing in the import/export text editor from causing changes in the main circuit editor.
@@ -125,6 +126,10 @@ btnNextLayer.addEventListener('click', _ev => {
btnPrevLayer.addEventListener('click', _ev => {
editorState.changeCurLayerTo(editorState.curLayer - 1);
});
+btnResetView.addEventListener('click', _ev => {
+ editorState.resetView();
+});
+
window.addEventListener('resize', _ev => {
editorState.canvas.width = editorState.canvas.scrollWidth;
@@ -144,6 +149,28 @@ function exportCurrentState() {
}
editorState.canvas.addEventListener('mousemove', ev => {
+ if (isPanning) {
+ const dx = ev.clientX - lastPanX;
+ const dy = ev.clientY - lastPanY;
+ editorState.pan(dx, dy);
+ lastPanX = ev.clientX;
+ lastPanY = ev.clientY;
+ return;
+ }
+ if (isTimelinePanning) {
+ const dy = ev.clientY - lastPanY;
+ editorState.timelinePan(dy);
+ lastPanY = ev.clientY;
+ editorState.force_redraw();
+ return;
+ }
+ if (isZooming) {
+ const dy = ev.clientY - lastZoomY;
+ editorState.zoom(1.0 + dy * 0.01);
+ lastZoomY = ev.clientY;
+ return;
+ }
+
editorState.curMouseX = ev.offsetX + OFFSET_X;
editorState.curMouseY = ev.offsetY + OFFSET_Y;
@@ -158,6 +185,12 @@ editorState.canvas.addEventListener('mousemove', ev => {
});
let isInScrubber = false;
+let isPanning = false; // Add scrolling by dragging with shift+MMB, no touchpad support yet
+let isTimelinePanning = false; // Add vertical scrolling of the timeline by dragging with shift+MMB in the timeline area, no touchpad support yet
+let lastPanX = 0;
+let lastPanY = 0;
+let isZooming = false; // Add zooming by dragging with ctrl+MMB, no touchpad support yet
+let lastZoomY = 0;
editorState.canvas.addEventListener('mousedown', ev => {
editorState.curMouseX = ev.offsetX + OFFSET_X;
editorState.curMouseY = ev.offsetY + OFFSET_Y;
@@ -172,10 +205,49 @@ editorState.canvas.addEventListener('mousedown', ev => {
return;
}
+
+ if (ev.button === 1 && ev.shiftKey) {
+ if(ev.offsetX <= w){ // Panning for the planar view
+ isPanning = true;
+ lastPanX = ev.clientX;
+ lastPanY = ev.clientY;
+ ev.preventDefault();
+ return;
+ }
+ else{ // Panning for the timeline view
+ isTimelinePanning = true;
+ lastPanY = ev.clientY;
+ ev.preventDefault();
+ return;
+ }
+ }
+
+ if (ev.button === 1 && ev.ctrlKey && ev.offsetX <= w) {
+ isZooming = true;
+ lastZoomY = ev.clientY;
+ ev.preventDefault();
+ return;
+ }
+
+
+
editorState.force_redraw();
});
+function clearDragState(ev) {
+ isPanning = false;
+ isTimelinePanning = false;
+ isZooming = false;
+
+ editorState.mouseDownX = undefined;
+ editorState.mouseDownY = undefined;
+}
+
editorState.canvas.addEventListener('mouseup', ev => {
+ if ((isPanning || isTimelinePanning || isZooming) && ev.button === 1) {
+ clearDragState(ev);
+ return;
+ }
let highlightedArea = editorState.currentPositionsBoxesByMouseDrag(ev.altKey);
editorState.mouseDownX = undefined;
editorState.mouseDownY = undefined;
@@ -187,6 +259,12 @@ editorState.canvas.addEventListener('mouseup', ev => {
}
});
+editorState.canvas.addEventListener('mouseenter', ev => {
+ if ((isPanning || isTimelinePanning || isZooming) && (ev.buttons & 4) === 0) {
+ clearDragState(ev);
+ }
+});
+
/**
* @return {!Map}
*/
@@ -224,6 +302,10 @@ function makeChordHandlers() {
}
});
res.set(' ', preview => editorState.unmarkFocusInferBasis(preview));
+ res.set('ctrl+0', () => editorState.resetView());
+ res.set('ctrl++', () => editorState.zoom(1.25));
+ res.set('ctrl+=', () => editorState.zoom(1.25));
+ res.set('ctrl+-', () => editorState.zoom(0.8));
for (let [key, val] of [
['1', 0],
@@ -432,6 +514,22 @@ const CHORD_HANDLERS = makeChordHandlers();
function handleKeyboardEvent(ev) {
editorState.chorder.handleKeyEvent(ev);
if (ev.type === 'keydown') {
+ if( ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(ev.key)) {
+ ev.preventDefault();
+ if (ev.key === 'ArrowUp') {
+ editorState.pan(0, -step);
+ }
+ if (ev.key === 'ArrowDown') {
+ editorState.pan(0, step);
+ }
+ if (ev.key === 'ArrowLeft') {
+ editorState.pan(-step, 0);
+ }
+ if (ev.key === 'ArrowRight') {
+ editorState.pan(step, 0);
+ }
+ return;
+ }
if (ev.key.toLowerCase() === 'q') {
let d = ev.shiftKey ? 5 : 1;
editorState.changeCurLayerTo(editorState.curLayer - d);