11import { Circuit } from "./circuit/circuit.js"
22import { minXY } from "./circuit/layer.js"
3- import { pitch } from "./draw/config.js"
3+ import { MAX_QUBIT_COORDINATE , MAX_ZOOM , MIN_ZOOM , pitch } from "./draw/config.js"
44import { GATE_MAP } from "./gates/gateset.js"
55import { EditorState } from "./editor/editor_state.js" ;
66import { initUrlCircuitSync } from "./editor/sync_url_to_state.js" ;
77import { draw } from "./draw/main_draw.js" ;
88import { drawToolbox } from "./keyboard/toolbox.js" ;
99import { Operation } from "./circuit/operation.js" ;
1010import { make_mpp_gate } from './gates/gateset_mpp.js' ;
11- import { PropagatedPauliFrames } from './circuit/propagated_pauli_frames.js' ;
1211
1312const OFFSET_X = - pitch + Math . floor ( pitch / 4 ) + 0.5 ;
1413const OFFSET_Y = - pitch + Math . floor ( pitch / 4 ) + 0.5 ;
@@ -39,6 +38,14 @@ txtStimCircuit.addEventListener('keydown', ev => ev.stopPropagation());
3938
4039let editorState = /** @type {!EditorState } */ new EditorState ( document . getElementById ( 'cvn' ) ) ;
4140
41+ function toWorldMouseX ( screenX ) {
42+ return ( screenX - editorState . viewportX ) / editorState . viewportZoom + OFFSET_X ;
43+ }
44+
45+ function toWorldMouseY ( screenY ) {
46+ return ( screenY - editorState . viewportY ) / editorState . viewportZoom + OFFSET_Y ;
47+ }
48+
4249btnExport . addEventListener ( 'click' , _ev => {
4350 exportCurrentState ( ) ;
4451} ) ;
@@ -144,8 +151,10 @@ function exportCurrentState() {
144151}
145152
146153editorState . canvas . addEventListener ( 'mousemove' , ev => {
147- editorState . curMouseX = ev . offsetX + OFFSET_X ;
148- editorState . curMouseY = ev . offsetY + OFFSET_Y ;
154+ editorState . curMouseScreenX = ev . offsetX ;
155+ editorState . curMouseScreenY = ev . offsetY ;
156+ editorState . curMouseX = toWorldMouseX ( ev . offsetX ) ;
157+ editorState . curMouseY = toWorldMouseY ( ev . offsetY ) ;
149158
150159 // Scrubber.
151160 let w = editorState . canvas . width / 2 ;
@@ -159,10 +168,12 @@ editorState.canvas.addEventListener('mousemove', ev => {
159168
160169let isInScrubber = false ;
161170editorState . canvas . addEventListener ( 'mousedown' , ev => {
162- editorState . curMouseX = ev . offsetX + OFFSET_X ;
163- editorState . curMouseY = ev . offsetY + OFFSET_Y ;
164- editorState . mouseDownX = ev . offsetX + OFFSET_X ;
165- editorState . mouseDownY = ev . offsetY + OFFSET_Y ;
171+ editorState . curMouseScreenX = ev . offsetX ;
172+ editorState . curMouseScreenY = ev . offsetY ;
173+ editorState . curMouseX = toWorldMouseX ( ev . offsetX ) ;
174+ editorState . curMouseY = toWorldMouseY ( ev . offsetY ) ;
175+ editorState . mouseDownX = toWorldMouseX ( ev . offsetX ) ;
176+ editorState . mouseDownY = toWorldMouseY ( ev . offsetY ) ;
166177
167178 // Scrubber.
168179 let w = editorState . canvas . width / 2 ;
@@ -179,14 +190,80 @@ editorState.canvas.addEventListener('mouseup', ev => {
179190 let highlightedArea = editorState . currentPositionsBoxesByMouseDrag ( ev . altKey ) ;
180191 editorState . mouseDownX = undefined ;
181192 editorState . mouseDownY = undefined ;
182- editorState . curMouseX = ev . offsetX + OFFSET_X ;
183- editorState . curMouseY = ev . offsetY + OFFSET_Y ;
193+ editorState . curMouseScreenX = ev . offsetX ;
194+ editorState . curMouseScreenY = ev . offsetY ;
195+ editorState . curMouseX = toWorldMouseX ( ev . offsetX ) ;
196+ editorState . curMouseY = toWorldMouseY ( ev . offsetY ) ;
184197 editorState . changeFocus ( highlightedArea , ev . shiftKey , ev . ctrlKey ) ;
185198 if ( ev . buttons === 1 ) {
186199 isInScrubber = false ;
187200 }
188201} ) ;
189202
203+ // Make sure qubit grid and timeline don't deviate from the area of interest.
204+ function restrictQubitGridAndTimeline ( ) {
205+ const width = editorState . canvas . width / 2 ;
206+ const height = editorState . canvas . height ;
207+ const zoom = editorState . viewportZoom ;
208+ const gridMin = - 1 * pitch - OFFSET_X ;
209+ const gridMax = MAX_QUBIT_COORDINATE * pitch - OFFSET_X ;
210+
211+ editorState . viewportX = Math . max (
212+ width - gridMax * zoom ,
213+ Math . min ( - gridMin * zoom , editorState . viewportX )
214+ ) ;
215+ editorState . viewportY = Math . max (
216+ height - gridMax * zoom ,
217+ Math . min ( - gridMin * zoom , editorState . viewportY )
218+ ) ;
219+
220+ editorState . timelineScrollY = Math . max (
221+ 0 ,
222+ editorState . timelineScrollY
223+ ) ;
224+ }
225+
226+ function handleTimelineVerticalScroll ( ev ) {
227+ editorState . timelineScrollY += ev . deltaY ;
228+ restrictQubitGridAndTimeline ( ) ;
229+ editorState . force_redraw ( ) ;
230+ return ;
231+ }
232+
233+ function handleQubitGridZoomPan ( ev ) {
234+ if ( ev . ctrlKey || ev . metaKey ) {
235+ // Handle zoom.
236+ const zoomMultiplier = ev . deltaY < 0 ? 1.05 : ( 1 / 1.05 ) ;
237+ const newZoom = Math . max ( MIN_ZOOM , Math . min ( MAX_ZOOM , editorState . viewportZoom * zoomMultiplier ) ) ;
238+ const ratio = newZoom / editorState . viewportZoom ;
239+ editorState . viewportZoom = newZoom ;
240+
241+ // Center zoom around mouse.
242+ editorState . viewportX = ev . offsetX - ( ev . offsetX - editorState . viewportX ) * ratio ;
243+ editorState . viewportY = ev . offsetY - ( ev . offsetY - editorState . viewportY ) * ratio ;
244+ } else {
245+ // Handle pan.
246+ editorState . viewportX -= ev . deltaX ;
247+ editorState . viewportY -= ev . deltaY ;
248+ }
249+
250+ editorState . curMouseX = toWorldMouseX ( ev . offsetX ) ;
251+ editorState . curMouseY = toWorldMouseY ( ev . offsetY ) ;
252+ restrictQubitGridAndTimeline ( ) ;
253+ editorState . force_redraw ( ) ;
254+ }
255+
256+ editorState . canvas . addEventListener ( 'wheel' , ev => {
257+ ev . preventDefault ( ) ;
258+ const width = editorState . canvas . width / 2 ;
259+
260+ if ( ev . offsetX > width ) {
261+ handleTimelineVerticalScroll ( ev ) ;
262+ } else {
263+ handleQubitGridZoomPan ( ev ) ;
264+ }
265+ } , { passive : false } ) ;
266+
190267/**
191268 * @return {!Map<!string, !function(preview: !boolean) : void> }
192269 */
@@ -504,7 +581,13 @@ editorState.rev.changes().subscribe(() => {
504581 drawToolbox ( editorState . chorder . toEvent ( false ) ) ;
505582} ) ;
506583initUrlCircuitSync ( editorState . rev ) ;
507- editorState . obs_val_draw_state . observable ( ) . subscribe ( ds => requestAnimationFrame ( ( ) => draw ( editorState . canvas . getContext ( '2d' ) , ds ) ) ) ;
584+ editorState . obs_val_draw_state . observable ( ) . subscribe ( ds => requestAnimationFrame ( ( ) => {
585+ const maxTimelineScrollY = draw ( editorState . canvas . getContext ( '2d' ) , ds ) ;
586+ // Prevent over-scrolling.
587+ if ( editorState . timelineScrollY > maxTimelineScrollY ) {
588+ editorState . timelineScrollY = maxTimelineScrollY ;
589+ }
590+ } ) ) ;
508591window . addEventListener ( 'focus' , ( ) => {
509592 editorState . chorder . handleFocusChanged ( ) ;
510593} ) ;
0 commit comments