Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion glue/crumble/draw/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,9 @@ 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 MIN_ZOOM = 0.25;
const MAX_ZOOM = 4;

const MAX_QUBIT_COORDINATE = 100;

export {pitch, rad, OFFSET_X, OFFSET_Y, MIN_ZOOM, MAX_ZOOM, MAX_QUBIT_COORDINATE};
27 changes: 20 additions & 7 deletions glue/crumble/draw/main_draw.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {pitch, rad, OFFSET_X, OFFSET_Y} from "./config.js"
import {pitch, rad, OFFSET_X, OFFSET_Y, MAX_QUBIT_COORDINATE} from "./config.js"
import {marker_placement} from "../gates/gateset_markers.js";
import {drawTimeline} from "./timeline_viewer.js";
import {PropagatedPauliFrames} from "../circuit/propagated_pauli_frames.js";
Expand Down Expand Up @@ -192,6 +192,15 @@ function defensiveDraw(ctx, body) {
}
}

function switchToScreenCoordinates(ctx) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
}

function switchToTransformationCoordinates(ctx, snap) {
const zoom = snap.viewportZoom;
ctx.setTransform(zoom, 0, 0, zoom, snap.viewportX, snap.viewportY);
}

/**
* @param {!CanvasRenderingContext2D} ctx
* @param {!StateSnapshot} snap
Expand Down Expand Up @@ -254,8 +263,10 @@ function draw(ctx, snap) {
}

defensiveDraw(ctx, () => {
ctx.fillStyle = 'white';
switchToScreenCoordinates(ctx);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
switchToTransformationCoordinates(ctx, snap);

let [focusX, focusY] = xyToPos(snap.curMouseX, snap.curMouseY);

// Draw the background polygons.
Expand All @@ -278,26 +289,26 @@ function draw(ctx, snap) {

// Draw the grid of qubits.
defensiveDraw(ctx, () => {
for (let qx = 0; qx < 100; qx += 0.5) {
for (let qx = 0; qx < MAX_QUBIT_COORDINATE; 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 = 0; qy < 100; qy += 0.5) {
for (let qy = 0; qy < MAX_QUBIT_COORDINATE; qy += 0.5) {
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) {
for (let qx = 0; qx < MAX_QUBIT_COORDINATE; 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) {
for (let qy = qx % 1; qy < MAX_QUBIT_COORDINATE; qy += 1) {
let [x, y] = c2dCoordTransform(qx, qy);
ctx.fillStyle = 'white';
let isUnused = !usedQubitCoordSet.has(`${qx},${qy}`);
Expand Down Expand Up @@ -384,7 +395,8 @@ function draw(ctx, snap) {
});
});

drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords, circuit.layers.length);
switchToScreenCoordinates(ctx);
const maxTimelineScrollY = drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords, circuit.layers.length);

// Draw scrubber.
ctx.save();
Expand Down Expand Up @@ -485,6 +497,7 @@ function draw(ctx, snap) {
} finally {
ctx.restore();
}
return maxTimelineScrollY;
}

export {xyToPos, draw, setDefensiveDrawEnabled, OFFSET_X, OFFSET_Y}
14 changes: 13 additions & 1 deletion glue/crumble/draw/state_snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ class StateSnapshot {
* @param {!number} mouseDownX
* @param {!number} mouseDownY
* @param {!Array<![!number, !number]>} boxHighlightPreview
* @param {!number} viewportX
* @param {!number} viewportY
* @param {!number} viewportZoom
* @param {!number} timelineScrollY
* @param {!number} curMouseScreenX
* @param {!number} curMouseScreenY
*/
constructor(circuit, curLayer, focusedSet, timelineSet, curMouseX, curMouseY, mouseDownX, mouseDownY, boxHighlightPreview) {
constructor(circuit, curLayer, focusedSet, timelineSet, curMouseX, curMouseY, mouseDownX, mouseDownY, boxHighlightPreview, viewportX=0, viewportY=0, viewportZoom=1, timelineScrollY=0, curMouseScreenX=undefined, curMouseScreenY=undefined) {
this.circuit = circuit.copy();
this.curLayer = curLayer;
this.focusedSet = new Map(focusedSet.entries());
Expand All @@ -28,6 +34,12 @@ class StateSnapshot {
this.mouseDownX = mouseDownX;
this.mouseDownY = mouseDownY;
this.boxHighlightPreview = [...boxHighlightPreview];
this.viewportX = viewportX;
this.viewportY = viewportY;
this.viewportZoom = viewportZoom;
this.timelineScrollY = timelineScrollY;
this.curMouseScreenX = curMouseScreenX;
this.curMouseScreenY = curMouseScreenY;

while (this.circuit.layers.length <= this.curLayer) {
this.circuit.layers.push(new Layer());
Expand Down
33 changes: 27 additions & 6 deletions glue/crumble/draw/timeline_viewer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {OFFSET_Y, rad} from "./config.js";
import {rad} from "./config.js";
import {stroke_connector_to} from "../gates/gate_draw_util.js"
import {marker_placement} from '../gates/gateset_markers.js';

let TIMELINE_PITCH = 32;
const TIMELINE_PITCH = 32;
const QUBIT_HIGHLIGHT_SIZE = 40;

/**
* @param {!CanvasRenderingContext2D} ctx
Expand Down Expand Up @@ -110,6 +111,7 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
return x1 - x2;
});

// Calculate base coordinates.
let base_y2xy = new Map();
let prev_y = undefined;
let cur_x = 0;
Expand All @@ -132,6 +134,17 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
base_y2xy.set(`${x},${y}`, [Math.round(cur_x) + 0.5, Math.round(cur_y) + 0.5]);
}


// Apply vertical scroll offset.
const maxScrollY = Math.max(0, cur_y - ctx.canvas.height + TIMELINE_PITCH); // Restrict scroll based on qubits drawn
const scrollY = Math.max(0, Math.min(snap.timelineScrollY, maxScrollY));

if (scrollY !== 0) {
for (let [key, [x, y]] of base_y2xy) {
base_y2xy.set(key, [x, y - scrollY]);
}
}

let x_pitch = TIMELINE_PITCH + Math.ceil(rad*max_run*0.25);
let num_cols_half = Math.floor(ctx.canvas.width / 4 / x_pitch);
let min_t_free = snap.curLayer - num_cols_half + 1;
Expand Down Expand Up @@ -211,22 +224,30 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun

// Draw links to timeslice viewer.
ctx.globalAlpha = 0.5;
const mouseScreenX = snap.curMouseScreenX;
const mouseScreenY = snap.curMouseScreenY;
const zoom = snap.viewportZoom;

for (let q of qubits) {
let [x0, y0] = qubitTimeCoords(q, min_t_clamp - 1);
let [x1, y1] = timesliceQubitCoordsFunc(q);
if (snap.curMouseX > ctx.canvas.width / 2 && snap.curMouseY >= y0 + OFFSET_Y - TIMELINE_PITCH * 0.55 && snap.curMouseY <= y0 + TIMELINE_PITCH * 0.55 + OFFSET_Y) {
const [wx1, wy1] = timesliceQubitCoordsFunc(q);
// Convert from world to screen coordinates for qubit highlight.
const x1 = wx1 * zoom + snap.viewportX;
const y1 = wy1 * zoom + snap.viewportY;
if (mouseScreenX > ctx.canvas.width / 2 && mouseScreenY >= y0 - TIMELINE_PITCH * 0.55 && mouseScreenY <= y0 + TIMELINE_PITCH * 0.55) {
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
ctx.fillStyle = 'black';
ctx.fillRect(x1 - 20, y1 - 20, 40, 40);
ctx.fillRect(x1 - (QUBIT_HIGHLIGHT_SIZE/2) * zoom, y1 - (QUBIT_HIGHLIGHT_SIZE/2) * zoom, QUBIT_HIGHLIGHT_SIZE * zoom, QUBIT_HIGHLIGHT_SIZE * zoom);
ctx.fillRect(ctx.canvas.width / 2, y0 - TIMELINE_PITCH / 3, ctx.canvas.width / 2, TIMELINE_PITCH * 2 / 3);
}
}
} finally {
ctx.restore();
}
return maxScrollY;
}

export {drawTimeline}
export {drawTimeline}
18 changes: 7 additions & 11 deletions glue/crumble/editor/editor_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class EditorState {
this.mouseDownX = /** @type {undefined|!number} */ undefined;
this.mouseDownY = /** @type {undefined|!number} */ undefined;
this.obs_val_draw_state = /** @type {!ObservableValue<StateSnapshot>} */ new ObservableValue(this.toSnapshot(undefined));
this.curMouseScreenX = /** @type {undefined|!number} */ undefined;
this.curMouseScreenY = /** @type {undefined|!number} */ undefined;
this.viewportX = 0;
this.viewportY = 0;
this.viewportZoom = 1;
this.timelineScrollY = 0;
}

flipTwoQubitGateOrderAtFocus(preview) {
Expand Down Expand Up @@ -201,17 +207,7 @@ class EditorState {
if (previewCircuit === undefined) {
previewCircuit = this.copyOfCurCircuit();
}
return new StateSnapshot(
previewCircuit,
this.curLayer,
this.focusedSet,
this.timelineSet,
this.curMouseX,
this.curMouseY,
this.mouseDownX,
this.mouseDownY,
this.currentPositionsBoxesByMouseDrag(this.chorder.curModifiers.has("alt")),
);
return new StateSnapshot(previewCircuit, this.curLayer, this.focusedSet, this.timelineSet, this.curMouseX, this.curMouseY, this.mouseDownX, this.mouseDownY, this.currentPositionsBoxesByMouseDrag(this.chorder.curModifiers.has("alt")), this.viewportX, this.viewportY, this.viewportZoom, this.timelineScrollY, this.curMouseScreenX, this.curMouseScreenY);
}

force_redraw() {
Expand Down
105 changes: 94 additions & 11 deletions glue/crumble/main.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import {Circuit} from "./circuit/circuit.js"
import {minXY} from "./circuit/layer.js"
import {pitch} from "./draw/config.js"
import {MAX_QUBIT_COORDINATE, MAX_ZOOM, MIN_ZOOM, pitch} 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";
import {draw} from "./draw/main_draw.js";
import {drawToolbox} from "./keyboard/toolbox.js";
import {Operation} from "./circuit/operation.js";
import {make_mpp_gate} from './gates/gateset_mpp.js';
import {PropagatedPauliFrames} from './circuit/propagated_pauli_frames.js';

const OFFSET_X = -pitch + Math.floor(pitch / 4) + 0.5;
const OFFSET_Y = -pitch + Math.floor(pitch / 4) + 0.5;
Expand Down Expand Up @@ -39,6 +38,14 @@ txtStimCircuit.addEventListener('keydown', ev => ev.stopPropagation());

let editorState = /** @type {!EditorState} */ new EditorState(document.getElementById('cvn'));

function toWorldMouseX(screenX) {
return (screenX - editorState.viewportX) / editorState.viewportZoom + OFFSET_X;
}

function toWorldMouseY(screenY) {
return (screenY - editorState.viewportY) / editorState.viewportZoom + OFFSET_Y;
}

btnExport.addEventListener('click', _ev => {
exportCurrentState();
});
Expand Down Expand Up @@ -144,8 +151,10 @@ function exportCurrentState() {
}

editorState.canvas.addEventListener('mousemove', ev => {
editorState.curMouseX = ev.offsetX + OFFSET_X;
editorState.curMouseY = ev.offsetY + OFFSET_Y;
editorState.curMouseScreenX = ev.offsetX;
editorState.curMouseScreenY = ev.offsetY;
editorState.curMouseX = toWorldMouseX(ev.offsetX);
editorState.curMouseY = toWorldMouseY(ev.offsetY);

// Scrubber.
let w = editorState.canvas.width / 2;
Expand All @@ -159,10 +168,12 @@ editorState.canvas.addEventListener('mousemove', ev => {

let isInScrubber = false;
editorState.canvas.addEventListener('mousedown', ev => {
editorState.curMouseX = ev.offsetX + OFFSET_X;
editorState.curMouseY = ev.offsetY + OFFSET_Y;
editorState.mouseDownX = ev.offsetX + OFFSET_X;
editorState.mouseDownY = ev.offsetY + OFFSET_Y;
editorState.curMouseScreenX = ev.offsetX;
editorState.curMouseScreenY = ev.offsetY;
editorState.curMouseX = toWorldMouseX(ev.offsetX);
editorState.curMouseY = toWorldMouseY(ev.offsetY);
editorState.mouseDownX = toWorldMouseX(ev.offsetX);
editorState.mouseDownY = toWorldMouseY(ev.offsetY);

// Scrubber.
let w = editorState.canvas.width / 2;
Expand All @@ -179,14 +190,80 @@ editorState.canvas.addEventListener('mouseup', ev => {
let highlightedArea = editorState.currentPositionsBoxesByMouseDrag(ev.altKey);
editorState.mouseDownX = undefined;
editorState.mouseDownY = undefined;
editorState.curMouseX = ev.offsetX + OFFSET_X;
editorState.curMouseY = ev.offsetY + OFFSET_Y;
editorState.curMouseScreenX = ev.offsetX;
editorState.curMouseScreenY = ev.offsetY;
editorState.curMouseX = toWorldMouseX(ev.offsetX);
editorState.curMouseY = toWorldMouseY(ev.offsetY);
editorState.changeFocus(highlightedArea, ev.shiftKey, ev.ctrlKey);
if (ev.buttons === 1) {
isInScrubber = false;
}
});

// Make sure qubit grid and timeline don't deviate from the area of interest.
function restrictQubitGridAndTimeline() {
const width = editorState.canvas.width / 2;
const height = editorState.canvas.height;
const zoom = editorState.viewportZoom;
const gridMin = -1 * pitch - OFFSET_X;
const gridMax = MAX_QUBIT_COORDINATE * pitch - OFFSET_X;

editorState.viewportX = Math.max(
width - gridMax * zoom,
Math.min(-gridMin * zoom, editorState.viewportX)
);
editorState.viewportY = Math.max(
height - gridMax * zoom,
Math.min(-gridMin * zoom, editorState.viewportY)
);

editorState.timelineScrollY = Math.max(
0,
editorState.timelineScrollY
);
}

function handleTimelineVerticalScroll(ev) {
editorState.timelineScrollY += ev.deltaY;
restrictQubitGridAndTimeline();
editorState.force_redraw();
return;
}

function handleQubitGridZoomPan(ev) {
if (ev.ctrlKey || ev.metaKey) {
// Handle zoom.
const zoomMultiplier = ev.deltaY < 0 ? 1.05 : (1 / 1.05);
const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, editorState.viewportZoom * zoomMultiplier));
const ratio = newZoom / editorState.viewportZoom;
editorState.viewportZoom = newZoom;

// Center zoom around mouse.
editorState.viewportX = ev.offsetX - (ev.offsetX - editorState.viewportX) * ratio;
editorState.viewportY = ev.offsetY - (ev.offsetY - editorState.viewportY) * ratio;
} else {
// Handle pan.
editorState.viewportX -= ev.deltaX;
editorState.viewportY -= ev.deltaY;
}

editorState.curMouseX = toWorldMouseX(ev.offsetX);
editorState.curMouseY = toWorldMouseY(ev.offsetY);
restrictQubitGridAndTimeline();
editorState.force_redraw();
}

editorState.canvas.addEventListener('wheel', ev => {
ev.preventDefault();
const width = editorState.canvas.width / 2;

if (ev.offsetX > width) {
handleTimelineVerticalScroll(ev);
} else {
handleQubitGridZoomPan(ev);
}
}, { passive: false });

/**
* @return {!Map<!string, !function(preview: !boolean) : void>}
*/
Expand Down Expand Up @@ -504,7 +581,13 @@ editorState.rev.changes().subscribe(() => {
drawToolbox(editorState.chorder.toEvent(false));
});
initUrlCircuitSync(editorState.rev);
editorState.obs_val_draw_state.observable().subscribe(ds => requestAnimationFrame(() => draw(editorState.canvas.getContext('2d'), ds)));
editorState.obs_val_draw_state.observable().subscribe(ds => requestAnimationFrame(() => {
const maxTimelineScrollY = draw(editorState.canvas.getContext('2d'), ds);
// Prevent over-scrolling.
if (editorState.timelineScrollY > maxTimelineScrollY) {
editorState.timelineScrollY = maxTimelineScrollY;
}
}));
window.addEventListener('focus', () => {
editorState.chorder.handleFocusChanged();
});
Expand Down
Loading
Loading