diff --git a/src/main.js b/src/main.js index 9fe9bbc5..945f7b71 100644 --- a/src/main.js +++ b/src/main.js @@ -163,7 +163,7 @@ let clickDownGateButtonKey = undefined; canvasDiv.addEventListener('click', ev => { let pt = eventPosRelativeTo(ev, canvasDiv); let curInspector = displayed.get(); - if (curInspector.isHandOverButtonKey() !== clickDownGateButtonKey) { + if (curInspector.tryGetHandOverButtonKey() !== clickDownGateButtonKey) { return; } let clicked = syncArea(curInspector.withHand(curInspector.hand.withPos(pt))).tryClick(); @@ -182,7 +182,7 @@ watchDrags(canvasDiv, let oldInspector = displayed.get(); let newHand = oldInspector.hand.withPos(pt); let newInspector = syncArea(oldInspector.withHand(newHand)); - clickDownGateButtonKey = newInspector.isHandOverButtonKey(); + clickDownGateButtonKey = ev.ctrlKey ? undefined : newInspector.tryGetHandOverButtonKey(); if (clickDownGateButtonKey !== undefined) { displayed.set(newInspector); return; diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index 2c0a57e1..e50a03c4 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -354,6 +354,8 @@ class DisplayedCircuit { this._drawOutputDisplays(painter, stats, hand); this._drawHintLabels(painter, stats); } + + this._drawRowDragHighlight(painter); } /** @@ -606,14 +608,31 @@ class DisplayedCircuit { _drawColumnDragHighlight(painter, col) { if (this._highlightedSlot !== undefined && - this._highlightedSlot.col === col && - this._highlightedSlot.row === undefined) { + this._highlightedSlot.col === col && + this._highlightedSlot.row === undefined) { let rect = this.gateRect(0, col, 1, this._groundedWireCount()).paddedBy(3); painter.fillRect(rect, 'rgba(255, 196, 112, 0.7)'); painter.strokeRect(rect, 'black'); } } + /** + * @param {!Painter} painter + * @private + */ + _drawRowDragHighlight(painter) { + if (this._highlightedSlot !== undefined && + this._highlightedSlot.col === undefined && + this._highlightedSlot.row !== undefined) { + + let row = this._highlightedSlot.row; + let w = this.gateRect(row, this.clampedCircuitColCount() + 1).x; + let rect = this.wireRect(row).takeLeft(w); + painter.fillRect(rect, 'rgba(255, 196, 112, 0.7)'); + painter.strokeRect(rect, 'black'); + } + } + /** * @param {!Painter} painter * @param {!int} columnIndex @@ -649,11 +668,44 @@ class DisplayedCircuit { * @returns {!DisplayedCircuit} */ previewDrop(hand) { - return hand.heldColumn !== undefined ? this._previewDropMovedGateColumn(hand) : + return hand.heldRow !== undefined ? this._previewDropMovedRow(hand) : + hand.heldColumn !== undefined ? this._previewDropMovedGateColumn(hand) : hand.heldGate !== undefined ? this._previewDropMovedGate(hand) : this._previewResizedGate(hand); } + /** + * @param {!Hand} hand + * @returns {!DisplayedCircuit} + * @private + */ + _previewDropMovedRow(hand) { + if (hand.pos === undefined) { + return this; + } + let handWire = this.wireIndexAt(hand.pos.y); + if (handWire < 0 || handWire >= this.circuitDefinition.numWires) { + // Dragged the row out of the circuit. + return this; + } + + let heldRowHeight = seq(hand.heldRow.gates).map(g => g === undefined ? 1 : g.height).max(1); + handWire = Math.min(handWire, this.circuitDefinition.numWires - heldRowHeight); + + let newCols = []; + for (let c = 0; c < this.circuitDefinition.columns.length; c++) { + let gates = [...this.circuitDefinition.columns[c].gates]; + gates.splice(handWire, 0, hand.heldRow.gates[c]); + gates.pop(); + newCols.push(new GateColumn(gates)); + } + + let newCircuitDef = this.circuitDefinition.withColumns(newCols); + + return this.withCircuit(newCircuitDef). + _withHighlightedSlot({row: handWire, col: undefined, resizeStyle: false}); + } + /** * @param {!Hand} hand * @returns {!DisplayedCircuit} @@ -840,7 +892,7 @@ class DisplayedCircuit { } /** - * @param {undefined|!{col: !int, row: undefined|!int, resizeStyle: !boolean}} slot + * @param {undefined|!{col: undefined|!int, row: undefined|!int, resizeStyle: !boolean}} slot * @returns {!DisplayedCircuit} * @private */ @@ -1017,6 +1069,10 @@ class DisplayedCircuit { */ tryGrab(hand, duplicate=false, wholeColumn=false, ignoreResizeTabs=false) { if (wholeColumn) { + let grabRowResult = this._tryGrabRow(hand); + if (grabRowResult !== undefined) { + return grabRowResult; + } return this._tryGrabWholeColumn(hand, duplicate) || {newCircuit: this, newHand: hand}; } @@ -1033,6 +1089,56 @@ class DisplayedCircuit { return newCircuit._tryGrabGate(newHand, duplicate) || {newCircuit, newHand}; } + /** + * @param {!Hand} hand + * @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}} + */ + _tryGrabRow(hand) { + if (hand.pos === undefined) { + return undefined; + } + + // Which wire is it? Is it one that's actually in the circuit? + let wire = this.wireIndexAt(hand.pos.y); + if (wire < 0 || wire >= this.circuitDefinition.numWires) { + return undefined; + } + + // Is it inside the intended click area, instead of just off to the side? + let r = this._wireInitialStateClickableRect(wire); + if (!r.containsPoint(hand.pos)) { + return undefined; + } + + let {new_circuit, row_gates} = this._cutRow(wire); + let holdOffset = new Point(0, hand.pos.y - r.y); + return { + newCircuit: this.withCircuit(new_circuit), + newHand: hand.withHeldRow(row_gates, holdOffset) + }; + } + + /** + * @param {!int} row + * @returns {!{new_circuit: !CircuitDefinition, row_gates: !GateColumn}} + * @private + */ + _cutRow(row) { + let row_gates = []; + let cols = []; + for (let i = 0; i < this.circuitDefinition.columns.length; i++) { + let col_gates = [...this.circuitDefinition.columns[i].gates]; + row_gates.push(col_gates[row]); + col_gates.splice(row, 1); + col_gates.push(undefined); + cols.push(new GateColumn(col_gates)); + } + return { + new_circuit: this.circuitDefinition.withColumns(cols), + row_gates: new GateColumn(row_gates) + }; + } + /** * @param {!Hand} hand * @param {!boolean=} duplicate diff --git a/src/ui/DisplayedInspector.js b/src/ui/DisplayedInspector.js index c244ed9d..5dde7dec 100644 --- a/src/ui/DisplayedInspector.js +++ b/src/ui/DisplayedInspector.js @@ -127,7 +127,7 @@ class DisplayedInspector { /** * @returns {undefined|!string} */ - isHandOverButtonKey() { + tryGetHandOverButtonKey() { if (this.hand.pos === undefined) { return undefined; } diff --git a/src/ui/Hand.js b/src/ui/Hand.js index ecb38579..aae53a61 100644 --- a/src/ui/Hand.js +++ b/src/ui/Hand.js @@ -25,26 +25,31 @@ class Hand { * @param {undefined|!Gate} heldGate * @param {undefined|!Point} holdOffset * @param {undefined|!GateColumn} heldColumn + * @param {undefined|!GateColumn} heldRow * @param {undefined|!Point} resizingGateSlot */ - constructor(pos, heldGate, holdOffset, heldColumn, resizingGateSlot) { + constructor(pos, heldGate, holdOffset, heldColumn, heldRow, resizingGateSlot) { + let args = {pos, heldGate, holdOffset, heldColumn, heldRow, resizingGateSlot}; if (pos !== undefined && !(pos instanceof Point)) { - throw new DetailedError("Bad pos", {pos, heldGate, holdOffset, heldColumn, resizingGateSlot}); + throw new DetailedError("Bad pos", args); } if (heldGate !== undefined && !(heldGate instanceof Gate)) { - throw new DetailedError("Bad heldGate", {pos, heldGate, holdOffset, heldColumn, resizingGateSlot}); + throw new DetailedError("Bad heldGate", args); } if (holdOffset !== undefined && !(holdOffset instanceof Point)) { - throw new DetailedError("Bad holdOffset", {pos, heldGate, holdOffset, heldColumn, resizingGateSlot}); + throw new DetailedError("Bad holdOffset", args); } if (resizingGateSlot !== undefined && !(resizingGateSlot instanceof Point)) { - throw new DetailedError("Bad resizingGateSlot", {pos, heldGate, holdOffset, heldColumn, resizingGateSlot}); + throw new DetailedError("Bad resizingGateSlot", args); } if (heldColumn !== undefined && !(heldColumn instanceof GateColumn)) { - throw new DetailedError("Bad heldColumn", {pos, heldGate, holdOffset, heldColumn, resizingGateSlot}); + throw new DetailedError("Bad heldColumn", args); + } + if (heldRow !== undefined && !(heldRow instanceof GateColumn)) { + throw new DetailedError("Bad heldRow", args); } if (heldGate !== undefined && this.resizingGateSlot !== undefined) { - throw new DetailedError("Holding AND resizing", {pos, heldGate, holdOffset, heldColumn, resizingGateSlot}); + throw new DetailedError("Holding AND resizing", args); } /** @type {undefined|!Point} */ @@ -55,6 +60,8 @@ class Hand { this.holdOffset = holdOffset; /** @type {undefined|!GateColumn} */ this.heldColumn = heldColumn; + /** @type {undefined|!GateColumn} */ + this.heldRow = heldRow; /** @type {undefined|!Point} */ this.resizingGateSlot = resizingGateSlot; } @@ -74,7 +81,10 @@ class Hand { * @returns {!boolean} */ isBusy() { - return this.heldGate !== undefined || this.heldColumn !== undefined || this.resizingGateSlot !== undefined; + return (this.heldGate !== undefined || + this.heldColumn !== undefined || + this.resizingGateSlot !== undefined || + this.heldRow !== undefined); } /** @@ -97,6 +107,7 @@ class Hand { Util.CUSTOM_IS_EQUAL_TO_EQUALITY(this.holdOffset, other.holdOffset) && Util.CUSTOM_IS_EQUAL_TO_EQUALITY(this.heldGate, other.heldGate) && Util.CUSTOM_IS_EQUAL_TO_EQUALITY(this.heldColumn, other.heldColumn) && + Util.CUSTOM_IS_EQUAL_TO_EQUALITY(this.heldRow, other.heldRow) && Util.CUSTOM_IS_EQUAL_TO_EQUALITY(this.resizingGateSlot, other.resizingGateSlot); } @@ -109,6 +120,7 @@ class Hand { heldGate: this.heldGate, holdOffset: this.holdOffset, heldColumn: this.heldColumn, + heldRow: this.heldRow, resizingGateSlot: this.resizingGateSlot })}`; } @@ -118,14 +130,14 @@ class Hand { * @returns {!Hand} */ withPos(newPos) { - return new Hand(newPos, this.heldGate, this.holdOffset, this.heldColumn, this.resizingGateSlot); + return new Hand(newPos, this.heldGate, this.holdOffset, this.heldColumn, this.heldRow, this.resizingGateSlot); } /** * @returns {!Hand} */ withDrop() { - return new Hand(this.pos, undefined, undefined, undefined, undefined); + return new Hand(this.pos, undefined, undefined, undefined, undefined, undefined); } /** @@ -134,7 +146,7 @@ class Hand { * @returns {!Hand} */ withHeldGate(heldGate, heldGateOffset) { - return new Hand(this.pos, heldGate, heldGateOffset, undefined, undefined); + return new Hand(this.pos, heldGate, heldGateOffset, undefined, undefined, undefined); } /** @@ -143,7 +155,16 @@ class Hand { * @returns {!Hand} */ withHeldGateColumn(heldGateColumn, heldGateOffset) { - return new Hand(this.pos, undefined, heldGateOffset, heldGateColumn, undefined); + return new Hand(this.pos, undefined, heldGateOffset, heldGateColumn, undefined, undefined); + } + + /** + * @param {!GateColumn} heldRow + * @param {!Point} heldGateOffset + * @returns {!Hand} + */ + withHeldRow(heldRow, heldGateOffset) { + return new Hand(this.pos, undefined, heldGateOffset, undefined, heldRow, undefined); } /** @@ -152,7 +173,7 @@ class Hand { * @returns {!Hand} */ withResizeSlot(resizeSlot, resizeTabOffset) { - return new Hand(this.pos, undefined, resizeTabOffset, undefined, resizeSlot); + return new Hand(this.pos, undefined, resizeTabOffset, undefined, undefined, resizeSlot); } /** @@ -161,10 +182,11 @@ class Hand { stableDuration() { return this.heldGate !== undefined ? this.heldGate.stableDuration() : this.heldColumn !== undefined ? this.heldColumn.stableDuration() : + this.heldRow !== undefined ? this.heldRow.stableDuration() : Infinity; } } -Hand.EMPTY = new Hand(undefined, undefined, undefined, undefined, undefined); +Hand.EMPTY = new Hand(undefined, undefined, undefined, undefined, undefined, undefined); export {Hand}