diff --git a/frontend/src/components/download/DownloadButton.js b/frontend/src/components/download/DownloadButton.js index 57eabd4b..caa01b93 100644 --- a/frontend/src/components/download/DownloadButton.js +++ b/frontend/src/components/download/DownloadButton.js @@ -1,6 +1,6 @@ import axios from 'axios'; import Button from '../Button'; -import Template from '../../plaquettes/Template'; +import Footprint from '../../plaquettes/Footprint'; import config from './config'; import notification from '../notification'; @@ -29,7 +29,7 @@ export default class DownloadButton extends Button { onClick = () => { const payload = { plaquettes: [] }; this.workspace.children - .filter((child) => child instanceof Template) + .filter((child) => child instanceof Footprint) .forEach((tile) => { tile.getPlaquettes().forEach((plaquette) => { const marshalledPlaquette = { diff --git a/frontend/src/control-flow.js b/frontend/src/control-flow.js index 7c6fef34..e1fab811 100644 --- a/frontend/src/control-flow.js +++ b/frontend/src/control-flow.js @@ -6,7 +6,7 @@ import { Container, Point } from 'pixi.js'; import { AdjustmentFilter } from 'pixi-filters'; import notification from './components/notification'; import Grid from './graphics/Grid'; -import Template from './plaquettes/Template'; +import Footprint from './plaquettes/Footprint'; import Qubit from './qubits/Qubit'; import QubitLattice from './qubits/QubitLattice'; import Button from './components/Button'; @@ -69,7 +69,7 @@ export default function InitializeControlFlow() { workspace.removeChild(currentControlPanel); } workspace.children - .filter((child) => child instanceof Template) + .filter((child) => child instanceof Footprint) .forEach((template) => { if (template.getPlaquettes().includes(plaquette)) { template.removeChild(plaquette); @@ -172,25 +172,14 @@ export default function InitializeControlFlow() { qubit.visible = false; }); - // Initialize button to make plaquettes - let selectedQubits = []; - const plaquetteButton = new Button('Create plaquette', x, y + 50); - const template = new Template( - selectedQubits, + // Initialize Template + const template = new Footprint( workspace, - plaquetteButton, - app + app, + x, + y ); - - // Create the plaquettes and template - plaquetteButton.on('click', () => { - template.createPlaquette(); - workspace.addChild(template.container); - // Clear the selected qubits - selectedQubits = []; - plaquetteButton.visible = false; - }); - workspace.addChild(plaquetteButton); + workspace.addChild(template.container); workspace.removeChild(finalizeBoundingQuadButton); const downloadStimButton = new DownloadButton( diff --git a/frontend/src/plaquettes/Template.js b/frontend/src/plaquettes/Footprint.js similarity index 51% rename from frontend/src/plaquettes/Template.js rename to frontend/src/plaquettes/Footprint.js index 93acdbc1..80fb62ac 100644 --- a/frontend/src/plaquettes/Template.js +++ b/frontend/src/plaquettes/Footprint.js @@ -1,10 +1,20 @@ import { Container, Graphics, Color } from 'pixi.js'; import Plaquette from './Plaquette'; +import Qubit from '../qubits/Qubit'; import notification from '../components/notification'; import Button from '../components/Button'; -export default class Template { - constructor(selectedQubits, workspace, plaquetteButton, app) { +/** + * Footprint class to create the footprint for the plaquettes + * @class footprint + * @param {Container} workspace - The workspace container + * @param {PIXI.Application} app - The PIXI application + * @param {number} x - The x coordinate + * @param {number} y - The y coordinate + * @returns {void} + */ +export default class Footprint { + constructor(workspace, app, x, y) { // UI Properties this.app = app; this.container = new Container(); @@ -12,114 +22,168 @@ export default class Template { this.cursor = 'pointer'; this.mode = 'static'; this.isDragging = false; + this.x = x; + this.y = y; this.startX = 0; this.startY = 0; this.plaquette = null; - this.plaquetteButton = plaquetteButton; - this.templateQubits = selectedQubits || []; + this.unselectQubitsButton = new Button('Unselect Qubits', x, y + 100); + this.createPlaquetteButton = new Button('Make Plaquette', x, y + 50); this.selectedQubits = []; this.rectangle = new Graphics(); this.workspace = workspace; - const { x, y } = workspace.mainButtonPosition; - this.templateButton = new Button('Step 1: Define a Template', x, y + 50); - this.templateButton.on('click', () => { - // Create the template - this.renderTemplateControlButtons(); - this.defineTemplateArea(); + this.container.name = 'footprint'; + this.footprintQubits = []; + this.initializeFootprint(); + } + + /** + * Initialize the footprint + * @function initializefootprint + * @returns {void} + */ + initializeFootprint = () => { + // Create the footprint + notification(this.app, 'Select 3+ qubits to create a plaquette'); + this.app.view.addEventListener('click', this.selectQubit); + this.unselectQubitsButton.visible = true; + this.createPlaquetteButton.visible = false; + this.unselectQubitsButton.on('click', () => { + // Unselect the qubits + this.selectedQubits.forEach((qubit) => { + qubit.deselect(); + // For each qubit, remove the text + qubit.removeChildren(); + }); // Clear the selected qubits this.selectedQubits = []; - // Hide the button - this.templateButton.visible = false; + this.unselectQubitsButton.visible = false; + this.createPlaquetteButton.visible = false; + }); - // Show a notification to now select qubits within the template to make a plaquette + this.createPlaquetteButton.on('click', () => { + this.createPlaquette(); }); - this.container.addChild(this.templateButton); - this.container.addChild(this.plaquetteButton); - this.container.name = 'template'; - // this.workspace.addChild(this.container); - } - // Render the template control buttons - renderTemplateControlButtons() { + // Initialize the buttons + this.unselectQubitsButton.visible = false; + this.createPlaquetteButton.visible = false; + this.workspace.addChild(this.unselectQubitsButton); + this.workspace.addChild(this.createPlaquetteButton); + }; + + /** + * Select a qubit from the footprint + * @function selectQubit + * @param {Event} e - The click event + * @returns {void} + */ + selectQubit = (e) => { + // Check if the click was on a qubit + const canvasRect = this.app.view.getBoundingClientRect(); // Get canvas position + + // Calculate the relative click position within the canvas + const relativeX = e.clientX - canvasRect.left; + const relativeY = e.clientY - canvasRect.top; + // Get all the qubits + const qubits = this.workspace.children.filter( + (child) => child instanceof Qubit + ); + const qubit = qubits.find( + // Find the qubit that was clicked + (q) => q.checkHitArea(relativeX, relativeY) === true + ); + if (!qubit && !(qubit instanceof Qubit)) return; // Check that the qubit exists + // Check that the qubit is not already selected + if (this.selectedQubits.includes(qubit)) { + // Remove the qubit from the selected qubits + this.selectedQubits = this.selectedQubits.filter((q) => q !== qubit); + return; + } + qubit.changeColor('red'); + this.selectedQubits.push(qubit); + // Change the color of the qubit + // Check if the selected qubits are 3 or more + if (this.selectedQubits.length > 2) { + // Show the button + this.createPlaquetteButton.visible = true; + } + }; + + /** + * Create the plaquette from the selected qubits and assign it to the footprint + * @function createPlaquette + * @returns {void} + */ + createPlaquette = () => { + // Check that the selected qubits are part of the footprint area + if (this.selectedQubits.length < 3) { + notification(this.app, 'Plaquette requires 3+ qubits'); + return; + } + // Render the plaquette + const plaquette = new Plaquette(this.selectedQubits, this.workspace, this.app); + // Add the plaquette to the tile container + this.container.addChild(plaquette); + // Remove seleected qubits from the footprint qubits, so they can be used again + // For each qubit, remove the text + this.selectedQubits.forEach((qubit) => { + qubit.removeChildren(); + }); + // Clear the selected qubits + this.selectedQubits = []; + // Notify the user that the plaquette has been created + notification(this.app, 'Plaquette created'); + this.createPlaquetteButton.visible = false; + this.unselectQubitsButton.visible = false; + }; + + /** + * Update the visibility and event listeners + * @function updateVisiblityAndEventListeners + * @returns {void} + */ + updateVisiblityAndEventListeners() { this.isDragging = true; - this.container.name = 'template'; + this.container.name = 'footprint'; // Create the buttons const { x, y } = this.workspace.mainButtonPosition; this.clearButton = new Button('Clear', x, y + 50); this.clearButton.on('click', () => { - // Clear the template + // Clear the footprint this.clearButton.visible = false; - this.templateButton.visible = true; + this.footprintButton.visible = true; this.makeTileButton.visible = false; this.isDragging = false; - this.templateQubits.forEach((qubit) => { + this.footprintQubits.forEach((qubit) => { qubit.changeColor('black'); }); - // Clear the template qubits - this.templateQubits = []; + // Clear the footprint qubits + this.footprintQubits = []; // Remove listeners this.app.view.removeEventListener( 'mousedown', - this.mouseDownCreateTemplateArea + this.mouseDownCreateFootprintArea ); this.app.view.removeEventListener( 'mousemove', - this.mouseDragResizeTemplateArea + this.mouseDragResizeFootprintArea ); this.app.view.removeEventListener( 'mouseup', - this.mouseUpFinishTemplateArea + this.mouseUpFinishFootprintArea ); this.rectangle.visible = false; - notification(this.app, 'Step 1: Drag to define a template area'); - }); - - this.makeTileButton = new Button( - 'Step 2: Confirm Template', - 100, - 170, - 'darkgreen' - ); - this.makeTileButton.on('click', () => { - if (this.templateQubits.length === 0) { - notification(this.app, 'Template requires +3 qubits'); - return; - } - this.makeTileButton.visible = false; - this.clearButton.visible = false; - this.isDragging = false; - // Remove listeners - this.app.view.removeEventListener( - 'mousedown', - this.mouseDownCreateTemplateArea - ); - this.app.view.removeEventListener( - 'mousemove', - this.mouseDragResizeTemplateArea - ); - this.app.view.removeEventListener( - 'mouseup', - this.mouseUpFinishTemplateArea - ); - notification(this.app, 'Step 3: Click on 3+ qubits to make a plaquette'); - this.app.view.addEventListener('click', this.selectQubit); - this.plaquetteButton.visible = true; - this.plaquetteButton.on('click', () => { - // Create the plaquettes and tile - this.createPlaquette(); - this.workspace.addChild(this.container); - // Clear the selected qubits - this.selectedQubits = []; - notification(this.app, 'Step 4: Define the circuit'); - }); + notification(this.app, 'Step 1: Drag to define a footprint area'); }); - - // Add the buttons to the container - this.container.addChild(this.clearButton); - this.container.addChild(this.makeTileButton); } - mouseDownCreateTemplateArea = (event) => { + /** + * Create the footprint area + * @param {*} event + * @returns {void} + */ + mouseDownCreateFootprintArea = (event) => { // Get the canvas position const canvasRect = this.app.view.getBoundingClientRect(); // Get canvas position // Calculate the relative click position within the canvas @@ -133,7 +197,7 @@ export default class Template { this.isDragging = true; // Untint the qubits - this.templateQubits.forEach((qubit) => { + this.footprintQubits.forEach((qubit) => { qubit.changeColor('black'); }); @@ -144,7 +208,12 @@ export default class Template { this.rectangle.visible = true; }; - mouseDragResizeTemplateArea = (event) => { + /** + * mouseDragResizeFootprintArea + * @param {*} event + * @returns {void} + */ + mouseDragResizeFootprintArea = (event) => { if (this.isDragging) { // Get the canvas position const width = event.clientX - this.startX; @@ -160,7 +229,7 @@ export default class Template { const relativeX = this.startX - canvasRect.left; const relativeY = this.startY - canvasRect.top; - if (child.isQubit) { + if (child instanceof Qubit) { const qubitX = child.globalX; const qubitY = child.globalY; if ( @@ -171,12 +240,12 @@ export default class Template { ) { // Change the color of the qubits child.changeColor('red'); - this.templateQubits.push(child); + this.footprintQubits.push(child); return child; - } if (this.templateQubits.includes(child)) { - // If the qubit is no longer in the template area, - // remove it from the template - this.templateQubits = this.templateQubits.filter( + } if (this.footprintQubits.includes(child)) { + // If the qubit is no longer in the footprint area, + // remove it from the footprint + this.footprintQubits = this.footprintQubits.filter( (qubit) => qubit !== child ); child.changeColor('black'); @@ -186,98 +255,12 @@ export default class Template { } }; - mouseUpFinishTemplateArea = () => { + /** + * mouseUpFinishFootprintArea + * @returns {void} + */ + mouseUpFinishFootprintArea = () => { this.isDragging = false; this.rectangle.visible = false; }; - - defineTemplateArea() { - this.rectangle.lineStyle(2, 0xff0000); - this.rectangle.drawRect(0, 0, 0, 0); // Initialize with zero dimensions - this.rectangle.visible = false; - this.rectangle.name = 'templateArea'; - this.app.stage.addChild(this.rectangle); - - this.app.renderer.view.addEventListener( - 'mousedown', - this.mouseDownCreateTemplateArea - ); - - this.app.renderer.view.addEventListener( - 'mousemove', - this.mouseDragResizeTemplateArea - ); - - this.app.renderer.view.addEventListener( - 'mouseup', - this.mouseUpFinishTemplateArea - ); - return this.templateQubits; - } - - makeTile() { - // Get the plaquette - if (this.plaquette) { - this.container.addChild(this.plaquette.onDragMove()); - } else { - notification(this.container, this.app); - } - } - - // Select qubits - selectQubit = (e) => { - // Check if the click was on a qubit - const canvasRect = this.app.view.getBoundingClientRect(); // Get canvas position - - // Calculate the relative click position within the canvas - const relativeX = e.clientX - canvasRect.left; - const relativeY = e.clientY - canvasRect.top; - // Get all the qubits - const qubits = this.templateQubits.filter( - (child) => child.isQubit === true - ); - const qubit = qubits.find( - // Find the qubit that was clicked - (q) => q.checkHitArea(relativeX, relativeY) === true - ); - if (!qubit && !(qubit?.isQubit === true)) return; // Check that the qubit exists - // Check that the qubit is not already selected - if (this.selectedQubits.includes(qubit)) { - // Remove the qubit from the selected qubits - this.selectedQubits = this.selectedQubits.filter((q) => q !== qubit); - return; - } - this.selectedQubits.push(qubit); - if (this.selectedQubits.length > 2) { - // Show the button - this.plaquetteButton.visible = true; - } - }; - - // Create the plaquette from the selected qubits and assign it to the template - createPlaquette = () => { - // Check that the selected qubits are part of the template area - if (this.selectedQubits.length < 3) { - notification(this.app, 'Plaquette requires 3+ qubits'); - return; - } - // Render the plaquette - const plaquette = new Plaquette(this.selectedQubits, this.workspace, this.app); - // Remove seleected qubits from the template qubits, so they can be - // selected for circuit construction. - this.templateQubits = this.templateQubits.filter( - (qubit) => !this.selectedQubits.includes(qubit) - ); - // Add the plaquette to the tile container - this.container.addChild(plaquette); - // For each qubit, remove the text - this.selectedQubits.forEach((qubit) => { - qubit.removeChildren(); - }); - // Clear the selected qubits - this.selectedQubits = []; - // Notify the user that the plaquette has been created - notification(this.app, 'Plaquette created'); - this.plaquetteButton.visible = true; - }; } diff --git a/frontend/src/qubits/Qubit.js b/frontend/src/qubits/Qubit.js index b657a104..f5207ed9 100644 --- a/frontend/src/qubits/Qubit.js +++ b/frontend/src/qubits/Qubit.js @@ -40,7 +40,6 @@ export default class Qubit extends Graphics { this.name = `Qubit(${x}, ${y})`; // Adjacent (degree 1) qubits - this.isQubit = true; this.visible = true; this.isSelected = false; } diff --git a/tutorial/src/workspace/position.js b/tutorial/src/workspace/position.js index 0b03523c..72e2db86 100644 --- a/tutorial/src/workspace/position.js +++ b/tutorial/src/workspace/position.js @@ -26,7 +26,6 @@ export default class Position extends Graphics { this.radius = radius; this.factor = 3; // TO expand the hitarea. this._createCircle(x, y, radius, Position.color); - this.isQubit = false; } _onPointerOver = () => { diff --git a/tutorial/src/workspace/qubit.js b/tutorial/src/workspace/qubit.js index 38466b88..c143191e 100644 --- a/tutorial/src/workspace/qubit.js +++ b/tutorial/src/workspace/qubit.js @@ -34,7 +34,6 @@ export default class Qubit extends Position { this.factor = 2; // To expand the hitarea. this._createCircle(x, y, radius, Qubit.color_none); // QC properties - this.isQubit = true; this.role = 'none'; }