{
{gate.template.properties.data.map((raw, index) => {
- const { name, base } = raw
-
- const prop = gateProps[name]
- const output = new BehaviorSubject(
- gateProps.hasOwnProperty(name) ? prop : base
- )
-
return (
)
})}
diff --git a/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts b/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts
index a75af4a..b822ea3 100644
--- a/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts
+++ b/src/modules/logic-gates/subjects/LogicGatePropsSubjects.ts
@@ -1,4 +1,4 @@
import { BehaviorSubject, Subject } from 'rxjs'
-export const open = new Subject
()
+export const open = new BehaviorSubject(false)
export const id = new BehaviorSubject(0)
diff --git a/src/modules/modals/helpers/modalIsOpen.ts b/src/modules/modals/helpers/modalIsOpen.ts
index 10a6d23..0c17e97 100644
--- a/src/modules/modals/helpers/modalIsOpen.ts
+++ b/src/modules/modals/helpers/modalIsOpen.ts
@@ -1,6 +1,14 @@
import { InputStore } from '../../input/stores/InputStore'
import { CreateSimulationStore } from '../../create-simulation/stores/CreateSimulationStore'
+import { open as propModalIsOpen } from '../../logic-gates/subjects/LogicGatePropsSubjects'
+/**
+ * Returns true if any modal is open
+ */
export const modalIsOpen = () => {
- return InputStore.data.open.value || CreateSimulationStore.data.open.value
+ return (
+ InputStore.data.open.value ||
+ CreateSimulationStore.data.open.value ||
+ propModalIsOpen.value
+ )
}
diff --git a/src/modules/saving/constants.ts b/src/modules/saving/constants.ts
index 92c452f..3758345 100644
--- a/src/modules/saving/constants.ts
+++ b/src/modules/saving/constants.ts
@@ -12,6 +12,8 @@ import rgbLightTemplate from './templates/rgb'
import sequentialDelayerTemplate from './templates/sequentialDelayer'
import xnorTemplate from './templates/xnor'
import xorTemplate from './templates/xor'
+import halfAdderTemplate from './templates/halfAdder'
+import fullAdderTemplate from './templates/fullAdder'
export const defaultSimulationName = 'default'
export const baseTemplates: DeepPartial[] = [
@@ -26,7 +28,10 @@ export const baseTemplates: DeepPartial[] = [
rgbLightTemplate,
sequentialDelayerTemplate,
xnorTemplate,
- xorTemplate
+ xorTemplate,
+ halfAdderTemplate,
+ fullAdderTemplate
+ // commentTemplate
]
export const baseSave: RendererState = {
diff --git a/src/modules/saving/helpers/getState.ts b/src/modules/saving/helpers/getState.ts
index 22af919..30b02d1 100644
--- a/src/modules/saving/helpers/getState.ts
+++ b/src/modules/saving/helpers/getState.ts
@@ -62,7 +62,7 @@ export const getGateState = (gate: Gate): GateState => {
id: gate.id,
template: gate.template.metadata.name,
transform: getTransformState(gate.transform),
- props: gate.props
+ props: gate.getProps()
}
}
diff --git a/src/modules/saving/templates/comment.ts b/src/modules/saving/templates/comment.ts
new file mode 100644
index 0000000..48decbd
--- /dev/null
+++ b/src/modules/saving/templates/comment.ts
@@ -0,0 +1,47 @@
+import { PartialTemplate } from '../types/PartialTemplate'
+
+/**
+ * The template of the comment gate
+ */
+const commentTemplate: PartialTemplate = {
+ metadata: {
+ name: 'comment'
+ },
+ pins: {
+ inputs: {
+ count: 0
+ },
+ outputs: {
+ count: 0
+ }
+ },
+ material: {
+ fill: '#007A72'
+ },
+ shape: {
+ scale: [300, 100]
+ },
+ code: {
+ activation: `
+ context.innerText(context.getProperty('content'))
+ `
+ },
+ info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
+ properties: {
+ enabled: true,
+ data: [
+ {
+ needsUpdate: true,
+ base: 'Your comment here',
+ name: 'content',
+ type: 'string'
+ }
+ ]
+ },
+ innerText: {
+ enabled: true,
+ color: '#ADFFFA'
+ }
+}
+
+export default commentTemplate
diff --git a/src/modules/saving/templates/fullAdder.ts b/src/modules/saving/templates/fullAdder.ts
new file mode 100644
index 0000000..2c1099e
--- /dev/null
+++ b/src/modules/saving/templates/fullAdder.ts
@@ -0,0 +1,32 @@
+import { PartialTemplate } from '../types/PartialTemplate'
+
+/**
+ * The template of the fullAdder gate
+ */
+const fullAdderTemplate: PartialTemplate = {
+ metadata: {
+ name: 'full adder'
+ },
+ material: {
+ type: 'image',
+ fill: require('../../../assets/full-adder')
+ },
+ code: {
+ activation: `
+ const result = context.get(0) + context.get(1) + context.get(2)
+
+ context.set(0, result & 1)
+ context.set(1, result >> 1)
+ `
+ },
+ pins: {
+ inputs: {
+ count: 3
+ },
+ outputs: {
+ count: 2
+ }
+ }
+}
+
+export default fullAdderTemplate
diff --git a/src/modules/saving/templates/halfAdder.ts b/src/modules/saving/templates/halfAdder.ts
new file mode 100644
index 0000000..6a3ccd8
--- /dev/null
+++ b/src/modules/saving/templates/halfAdder.ts
@@ -0,0 +1,32 @@
+import { PartialTemplate } from '../types/PartialTemplate'
+
+/**
+ * The template of the halfAdder gate
+ */
+const halfAdderTemplate: PartialTemplate = {
+ metadata: {
+ name: 'half adder'
+ },
+ material: {
+ type: 'image',
+ fill: require('../../../assets/half-adder')
+ },
+ code: {
+ activation: `
+ const result = context.get(0) + context.get(1)
+
+ context.set(0, result & 1)
+ context.set(1, result >> 1)
+ `
+ },
+ pins: {
+ inputs: {
+ count: 2
+ },
+ outputs: {
+ count: 2
+ }
+ }
+}
+
+export default halfAdderTemplate
diff --git a/src/modules/saving/types/SimulationSave.ts b/src/modules/saving/types/SimulationSave.ts
index 1d58e7a..d607667 100644
--- a/src/modules/saving/types/SimulationSave.ts
+++ b/src/modules/saving/types/SimulationSave.ts
@@ -1,4 +1,4 @@
-import { vector2 } from '../../../common/math/classes/Transform'
+import { vector2 } from '../../../common/math/types/vector2'
export type simulationMode = 'ic' | 'project'
diff --git a/src/modules/simulation/classes/Gate.ts b/src/modules/simulation/classes/Gate.ts
index fe2b284..3c9455c 100644
--- a/src/modules/simulation/classes/Gate.ts
+++ b/src/modules/simulation/classes/Gate.ts
@@ -4,7 +4,7 @@ import { GateTemplate, PinCount } from '../types/GateTemplate'
import { idStore } from '../stores/idStore'
import { Context, InitialisationContext } from '../../activation/types/Context'
import { toFunction } from '../../activation/helpers/toFunction'
-import { Subscription } from 'rxjs'
+import { Subscription, BehaviorSubject } from 'rxjs'
import { SimulationError } from '../../errors/classes/SimulationError'
import { getGateTimePipes } from '../helpers/getGateTimePipes'
import { ImageStore } from '../../simulationRenderer/stores/imageStore'
@@ -15,6 +15,7 @@ import { saveStore } from '../../saving/stores/saveStore'
import { Wire } from './Wire'
import { cleanSimulation } from '../../simulation-actions/helpers/clean'
import { ExecutionQueue } from '../../activation/classes/ExecutionQueue'
+import { tap } from 'rxjs/operators'
/**
* The interface for the pins of a gate
@@ -115,10 +116,20 @@ export class Gate {
*/
public env: SimulationEnv = 'global'
+ /**
+ * Holds all the gate-related text
+ */
+ public text = {
+ inner: new BehaviorSubject('text goes here')
+ }
+
/**
* The props used by the activation function (the same as memory but presists)
*/
- public props: Record = {}
+ public props: Record<
+ string,
+ BehaviorSubject
+ > = {}
/**
* The main logic gate class
@@ -129,7 +140,7 @@ export class Gate {
public constructor(
template: DeepPartial = {},
id?: number,
- props: Record = {}
+ props: Record = {}
) {
this.template = completeTemplate(template)
@@ -259,15 +270,15 @@ export class Gate {
/**
* Assign the props passed to the gate and mere them with the base ones
*/
- private assignProps(props: Record) {
+ private assignProps(props: Record) {
let shouldUpdate = false
if (this.template.properties.enabled) {
for (const { base, name, needsUpdate } of this.template.properties
.data) {
- this.props[name] = props.hasOwnProperty(name)
- ? props[name]
- : base
+ this.props[name] = new BehaviorSubject(
+ props.hasOwnProperty(name) ? props[name] : base
+ )
if (!shouldUpdate && needsUpdate) {
shouldUpdate = true
@@ -301,6 +312,19 @@ export class Gate {
}
}
+ /**
+ * Used to get the props as an object
+ */
+ public getProps() {
+ const props: Record = {}
+
+ for (const key in this.props) {
+ props[key] = this.props[key].value
+ }
+
+ return props
+ }
+
/**
* Clears subscriptions to prevent memory leaks
*/
@@ -349,10 +373,13 @@ export class Gate {
}
},
getProperty: (name: string) => {
- return this.props[name]
+ return this.props[name].value
},
setProperty: (name: string, value: string | number | boolean) => {
- this.props[name] = value
+ this.props[name].next(value)
+ },
+ innerText: (value: string) => {
+ this.text.inner.next(value)
},
update: () => {
this.update()
@@ -369,7 +396,8 @@ export class Gate {
* Generates pin wrappers from an array of pins
*
* @param pins The pins to wwap
- */ private wrapPins(pins: Pin[]) {
+ */
+ private wrapPins(pins: Pin[]) {
const result: PinWrapper[] = []
const length = pins.length
diff --git a/src/modules/simulation/classes/Simulation.ts b/src/modules/simulation/classes/Simulation.ts
index 90a9550..bd022d1 100644
--- a/src/modules/simulation/classes/Simulation.ts
+++ b/src/modules/simulation/classes/Simulation.ts
@@ -3,6 +3,7 @@ import { GateStorage } from './GateStorage'
import { LruCacheNode } from '@eix-js/utils'
import { Wire } from './Wire'
import { simulationMode } from '../../saving/types/SimulationSave'
+import { BehaviorSubject } from 'rxjs'
/**
* The env a simulation can run in
diff --git a/src/modules/simulation/constants.ts b/src/modules/simulation/constants.ts
index 77b66be..fd73458 100644
--- a/src/modules/simulation/constants.ts
+++ b/src/modules/simulation/constants.ts
@@ -53,5 +53,9 @@ export const DefaultGateTemplate: GateTemplate = {
properties: {
enabled: false,
data: []
+ },
+ innerText: {
+ enabled: false,
+ color: 'white'
}
}
diff --git a/src/modules/simulation/helpers/addGate.ts b/src/modules/simulation/helpers/addGate.ts
index 775a041..610fbff 100644
--- a/src/modules/simulation/helpers/addGate.ts
+++ b/src/modules/simulation/helpers/addGate.ts
@@ -4,7 +4,7 @@ import { Gate } from '../classes/Gate'
import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
import { DefaultGateTemplate } from '../constants'
-import { vector2 } from '../../../common/math/classes/Transform'
+import { vector2 } from '../../../common/math/types/vector2'
import { Screen } from '../../screen/helpers/Screen'
import { toast } from 'react-toastify'
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
diff --git a/src/modules/simulation/types/GateTemplate.ts b/src/modules/simulation/types/GateTemplate.ts
index ab089da..dfe8fa6 100644
--- a/src/modules/simulation/types/GateTemplate.ts
+++ b/src/modules/simulation/types/GateTemplate.ts
@@ -1,4 +1,4 @@
-import { vector2 } from '../../../common/math/classes/Transform'
+import { vector2 } from '../../../common/math/types/vector2'
export interface PinCount {
variable: boolean
@@ -75,4 +75,8 @@ export interface GateTemplate {
enabled: boolean
data: Property[]
}
+ innerText: {
+ color: string
+ enabled: boolean
+ }
}
diff --git a/src/modules/simulationRenderer/classes/Camera.ts b/src/modules/simulationRenderer/classes/Camera.ts
index 05c07bd..d4c028a 100644
--- a/src/modules/simulationRenderer/classes/Camera.ts
+++ b/src/modules/simulationRenderer/classes/Camera.ts
@@ -1,14 +1,42 @@
import { Transform } from '../../../common/math/classes/Transform'
import { vector2 } from '../../../common/math/types/vector2'
+import {
+ substract,
+ multiplyVectors,
+ add,
+ inverse
+} from '../../vector2/helpers/basic'
+
+/**
+ * worldPosition(x) = (x - p) / s
+ * s * wp = x - p
+ * screenPosition(x) = s * x + p
+ */
export class Camera {
public transform = new Transform([0, 0])
+ /**
+ * Gets the world position of a vector
+ *
+ * @param position The position to transform
+ */
public toWordPostition(position: vector2) {
- return [
- (position[0] - this.transform.position[0]) /
- this.transform.scale[0],
- (position[1] - this.transform.position[1]) / this.transform.scale[1]
- ] as vector2
+ return multiplyVectors(
+ substract(position, this.transform.position),
+ inverse(this.transform.scale)
+ )
+ }
+
+ /**
+ * Gets the screen position of a vector
+ *
+ * @param position The position to transform
+ */
+ public toScreenPosition(position: vector2) {
+ return add(
+ multiplyVectors(position, this.transform.scale),
+ this.transform.position
+ )
}
}
diff --git a/src/modules/simulationRenderer/classes/SimulationRenderer.ts b/src/modules/simulationRenderer/classes/SimulationRenderer.ts
index 332b95c..ef88e6d 100644
--- a/src/modules/simulationRenderer/classes/SimulationRenderer.ts
+++ b/src/modules/simulationRenderer/classes/SimulationRenderer.ts
@@ -1,20 +1,11 @@
import { Camera } from './Camera'
import { Simulation } from '../../simulation/classes/Simulation'
import { Subject } from 'rxjs'
-import { MouseEventInfo } from '../../core/components/MouseEventInfo'
-import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
+import { MouseEventInfo } from '../../core/types/MouseEventInfo'
import { vector2 } from '../../../common/math/types/vector2'
-import { relativeTo, add, invert } from '../../vector2/helpers/basic'
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
-import {
- defaultSimulationRendererOptions,
- mouseButtons,
- shiftInput
-} from '../constants'
-import { getPinPosition } from '../helpers/pinPosition'
-import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
+import { defaultSimulationRendererOptions } from '../constants'
import { SelectedPins } from '../types/SelectedPins'
-import { Wire } from '../../simulation/classes/Wire'
import { currentStore } from '../../saving/stores/currentStore'
import { saveStore } from '../../saving/stores/saveStore'
import {
@@ -27,18 +18,14 @@ import { RefObject } from 'react'
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
import { SimulationError } from '../../errors/classes/SimulationError'
-import { deleteWire } from '../../simulation/helpers/deleteWire'
import { RendererState, WireState } from '../../saving/types/SimulationSave'
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
import { Transform } from '../../../common/math/classes/Transform'
-import { gatesInSelection } from '../helpers/gatesInSelection'
import { selectionType } from '../types/selectionType'
-import { addIdToSelection, idIsSelected } from '../helpers/idIsSelected'
import { GateInitter } from '../types/GateInitter'
-import {
- open as propsAreOpen,
- id as propsModalId
-} from '../../logic-gates/subjects/LogicGatePropsSubjects'
+import { handleMouseDown } from '../helpers/handleMouseDown'
+import { handleMouseUp } from '../helpers/handleMouseUp'
+import { handleMouseMove } from '../helpers/handleMouseMove'
export class SimulationRenderer {
public mouseDownOutput = new Subject()
@@ -83,223 +70,9 @@ export class SimulationRenderer {
}
public init() {
- this.mouseDownOutput.subscribe(event => {
- const worldPosition = this.camera.toWordPostition(event.position)
- const gates = Array.from(this.simulation.gates)
-
- this.lastMousePosition = worldPosition
-
- // We need to iterate from the last to the first
- // because if we have 2 overlapping gates,
- // we want to select the one on top
- for (let index = gates.length - 1; index >= 0; index--) {
- const { transform, id, pins, template } = gates[index]
-
- if (pointInSquare(worldPosition, transform)) {
- if (event.button === mouseButtons.drag) {
- gates[index].onClick()
-
- this.mouseState |= 1
-
- if (!idIsSelected(this, id)) {
- this.clearSelection()
- addIdToSelection(this, 'temporary', id)
- }
-
- const gateNode = this.simulation.gates.get(id)
-
- if (gateNode) {
- return this.simulation.gates.moveOnTop(gateNode)
- } else {
- throw new SimulationError(
- `Cannot find gate with id ${id}`
- )
- }
- } else if (
- event.button === mouseButtons.properties &&
- template.properties.enabled
- ) {
- propsModalId.next(id)
- return propsAreOpen.next(true)
- }
- }
-
- for (const pin of pins) {
- const position = getPinPosition(this, transform, pin)
-
- if (
- pointInCircle(
- worldPosition,
- position,
- this.options.gates.pinRadius
- )
- ) {
- if (pin.value.pairs.size) {
- if (pin.value.type & 1) {
- const wire = this.simulation.wires.find(
- wire => wire.end.value === pin.value
- )
-
- if (wire) {
- deleteWire(this.simulation, wire)
- } else {
- throw new SimulationError(
- `Cannot find wire to remove`
- )
- }
-
- return
- }
- }
-
- if (
- this.selectedPins.start &&
- pin.value === this.selectedPins.start.wrapper.value
- ) {
- this.selectedPins.start = null
- this.selectedPins.end = null
- } else if (
- this.selectedPins.end &&
- pin.value === this.selectedPins.end.wrapper.value
- ) {
- this.selectedPins.start = null
- this.selectedPins.end = null
- } else if ((pin.value.type & 2) >> 1) {
- this.selectedPins.start = {
- wrapper: pin,
- transform
- }
- } else if (pin.value.type & 1) {
- this.selectedPins.end = {
- wrapper: pin,
- transform
- }
- }
-
- if (this.selectedPins.start && this.selectedPins.end) {
- this.simulation.wires.push(
- new Wire(
- this.selectedPins.start.wrapper,
- this.selectedPins.end.wrapper
- )
- )
- this.selectedPins.start = null
- this.selectedPins.end = null
- }
-
- return
- }
- }
- }
-
- if (!shiftInput.value && event.button === mouseButtons.unselect) {
- this.clearSelection()
- }
-
- if (event.button === mouseButtons.pan) {
- // the second bit = pannning
- this.mouseState |= 0b10
- } else if (event.button === mouseButtons.select) {
- this.selectedArea.position = this.lastMousePosition
- this.selectedArea.scale = [0, 0]
-
- // the third bit = selecting
- this.mouseState |= 0b100
- }
- })
-
- this.mouseUpOutput.subscribe(event => {
- if (event.button === mouseButtons.drag && this.mouseState & 1) {
- const selected = this.getSelected()
-
- for (const gate of selected) {
- gate.transform.rotation = 0
- }
-
- this.selectedGates.temporary.clear()
-
- // turn first bit to 0
- this.mouseState &= 6
-
- // for debugging
- if ((this.mouseState >> 1) & 1 || this.mouseState & 1) {
- throw new SimulationError(
- 'First 2 bits of mouseState need to be set to 0'
- )
- }
- }
-
- if (
- event.button === mouseButtons.pan &&
- (this.mouseState >> 1) & 1
- ) {
- // turn second bit to 0
- this.mouseState &= 5
- }
-
- if (event.button === mouseButtons.select && this.mouseState >> 2) {
- // turn the third bit to 0
- this.mouseState &= 3
-
- const selectedGates = gatesInSelection(
- this.selectedArea,
- Array.from(this.simulation.gates)
- )
-
- for (const { id } of selectedGates) {
- addIdToSelection(this, 'permanent', id)
-
- const node = this.simulation.gates.get(id)
-
- if (node) {
- this.simulation.gates.moveOnTop(node)
- } else {
- throw new SimulationError(
- `Cannot find node in gate storage with id ${id}`
- )
- }
- }
- }
- })
-
- this.mouseMoveOutput.subscribe(event => {
- const worldPosition = this.camera.toWordPostition(event.position)
-
- const offset = invert(
- relativeTo(this.lastMousePosition, worldPosition)
- )
-
- const scaledOffset = offset.map(
- (value, index) => value * this.camera.transform.scale[index]
- ) as vector2
-
- if (this.mouseState & 1) {
- for (const gate of this.getSelected()) {
- const { transform } = gate
-
- transform.x -= offset[0]
- transform.y -= offset[1]
- }
- }
-
- if ((this.mouseState >> 1) & 1) {
- this.camera.transform.position = add(
- this.camera.transform.position,
- invert(scaledOffset)
- )
-
- this.spawnCount = 0
- }
-
- if ((this.mouseState >> 2) & 1) {
- this.selectedArea.scale = relativeTo(
- this.selectedArea.position,
- this.camera.toWordPostition(event.position)
- )
- }
-
- this.lastMousePosition = this.camera.toWordPostition(event.position)
- })
+ this.mouseDownOutput.subscribe(handleMouseDown(this))
+ this.mouseUpOutput.subscribe(handleMouseUp(this))
+ this.mouseMoveOutput.subscribe(handleMouseMove(this))
this.reloadSave()
}
@@ -316,6 +89,11 @@ export class SimulationRenderer {
}
}
+ /**
+ * Loads a simulation state
+ *
+ * @param save LThe state to load
+ */
public loadSave(save: RendererState) {
this.simulation = fromSimulationState(save.simulation)
this.camera = fromCameraState(save.camera)
diff --git a/src/modules/simulationRenderer/constants.ts b/src/modules/simulationRenderer/constants.ts
index 342c716..d283aed 100644
--- a/src/modules/simulationRenderer/constants.ts
+++ b/src/modules/simulationRenderer/constants.ts
@@ -1,5 +1,5 @@
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
-import { vector2 } from '../../common/math/classes/Transform'
+import { vector2 } from '../../common/math/types/vector2'
import { mouseButton } from '../core/types/mouseButton'
import { KeyboardInput } from '../keybindings/classes/KeyboardInput'
diff --git a/src/modules/simulationRenderer/helpers/getCurrentContextSafely.ts b/src/modules/simulationRenderer/helpers/getCurrentContextSafely.ts
new file mode 100644
index 0000000..29ed5cd
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/getCurrentContextSafely.ts
@@ -0,0 +1,15 @@
+import { currentContext } from '../subjects'
+import { SimulationError } from '../../errors/classes/SimulationError'
+
+/**
+ * Gets the current context and throw an error if it doesnt exist
+ *
+ * @throws SimulationError if no context can be found
+ */
+export const getCurrentContextSafely = () => {
+ if (currentContext.value) {
+ return currentContext.value
+ } else {
+ throw new SimulationError(`Cannot find a rendering context`)
+ }
+}
diff --git a/src/modules/simulationRenderer/helpers/handleMouseDown.ts b/src/modules/simulationRenderer/helpers/handleMouseDown.ts
new file mode 100644
index 0000000..eb5281a
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/handleMouseDown.ts
@@ -0,0 +1,135 @@
+import { MouseEventInfo } from '../../core/types/MouseEventInfo'
+import { SimulationRenderer } from '../classes/SimulationRenderer'
+import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
+import { idIsSelected, addIdToSelection } from './idIsSelected'
+import { mouseButtons, shiftInput } from '../constants'
+import { SimulationError } from '../../errors/classes/SimulationError'
+import { openGateProps } from './openGateProps'
+import { getPinPosition } from './pinPosition'
+import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
+import { deleteWire } from '../../simulation/helpers/deleteWire'
+import { Wire } from '../../simulation/classes/Wire'
+
+export const handleMouseDown = (renderer: SimulationRenderer) => (
+ event: MouseEventInfo
+) => {
+ const worldPosition = renderer.camera.toWordPostition(event.position)
+ const gates = Array.from(renderer.simulation.gates)
+
+ renderer.lastMousePosition = worldPosition
+
+ // We need to iterate from the last to the first
+ // because if we have 2 overlapping gates,
+ // we want to select the one on top
+ for (let index = gates.length - 1; index >= 0; index--) {
+ const { transform, id, pins, template } = gates[index]
+
+ if (pointInSquare(worldPosition, transform)) {
+ if (event.button === mouseButtons.drag) {
+ gates[index].onClick()
+
+ renderer.mouseState |= 1
+
+ if (!idIsSelected(renderer, id)) {
+ renderer.clearSelection()
+ addIdToSelection(renderer, 'temporary', id)
+ }
+
+ const gateNode = renderer.simulation.gates.get(id)
+
+ if (gateNode) {
+ return renderer.simulation.gates.moveOnTop(gateNode)
+ } else {
+ throw new SimulationError(`Cannot find gate with id ${id}`)
+ }
+ } else if (
+ event.button === mouseButtons.properties &&
+ template.properties.enabled
+ ) {
+ return openGateProps(id)
+ }
+ }
+
+ for (const pin of pins) {
+ const position = getPinPosition(renderer, transform, pin)
+
+ if (
+ pointInCircle(
+ worldPosition,
+ position,
+ renderer.options.gates.pinRadius
+ )
+ ) {
+ if (pin.value.pairs.size) {
+ if (pin.value.type & 1) {
+ const wire = renderer.simulation.wires.find(
+ wire => wire.end.value === pin.value
+ )
+
+ if (wire) {
+ deleteWire(renderer.simulation, wire)
+ } else {
+ throw new SimulationError(
+ `Cannot find wire to remove`
+ )
+ }
+
+ return
+ }
+ }
+
+ if (
+ renderer.selectedPins.start &&
+ pin.value === renderer.selectedPins.start.wrapper.value
+ ) {
+ renderer.selectedPins.start = null
+ renderer.selectedPins.end = null
+ } else if (
+ renderer.selectedPins.end &&
+ pin.value === renderer.selectedPins.end.wrapper.value
+ ) {
+ renderer.selectedPins.start = null
+ renderer.selectedPins.end = null
+ } else if ((pin.value.type & 2) >> 1) {
+ renderer.selectedPins.start = {
+ wrapper: pin,
+ transform
+ }
+ } else if (pin.value.type & 1) {
+ renderer.selectedPins.end = {
+ wrapper: pin,
+ transform
+ }
+ }
+
+ if (renderer.selectedPins.start && renderer.selectedPins.end) {
+ renderer.simulation.wires.push(
+ new Wire(
+ renderer.selectedPins.start.wrapper,
+ renderer.selectedPins.end.wrapper
+ )
+ )
+ renderer.selectedPins.start = null
+ renderer.selectedPins.end = null
+ }
+
+ return
+ }
+ }
+ }
+
+ if (!shiftInput.value && event.button === mouseButtons.unselect) {
+ renderer.clearSelection()
+ }
+
+ if (event.button === mouseButtons.pan) {
+ // the second bit = pannning
+ renderer.mouseState |= 0b10
+ } else if (event.button === mouseButtons.select) {
+ renderer.selectedArea.position = renderer.lastMousePosition
+ renderer.selectedArea.scale = [0, 0]
+
+ // the third bit = selecting
+ renderer.mouseState |= 0b100
+ }
+}
diff --git a/src/modules/simulationRenderer/helpers/handleMouseMove.ts b/src/modules/simulationRenderer/helpers/handleMouseMove.ts
new file mode 100644
index 0000000..c40f888
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/handleMouseMove.ts
@@ -0,0 +1,47 @@
+import { SimulationRenderer } from '../classes/SimulationRenderer'
+import { MouseEventInfo } from '../../core/types/MouseEventInfo'
+import {
+ substract,
+ multiplyVectors,
+ relativeTo
+} from '../../vector2/helpers/basic'
+
+export const handleMouseMove = (renderer: SimulationRenderer) => (
+ event: MouseEventInfo
+) => {
+ const worldPosition = renderer.camera.toWordPostition(event.position)
+
+ const offset = substract(renderer.lastMousePosition, worldPosition)
+
+ const scaledOffset = multiplyVectors(
+ offset,
+ renderer.camera.transform.scale
+ )
+
+ if (renderer.mouseState & 1) {
+ for (const gate of renderer.getSelected()) {
+ const { transform } = gate
+
+ transform.x -= offset[0]
+ transform.y -= offset[1]
+ }
+ }
+
+ if ((renderer.mouseState >> 1) & 1) {
+ renderer.camera.transform.position = substract(
+ renderer.camera.transform.position,
+ scaledOffset
+ )
+
+ renderer.spawnCount = 0
+ }
+
+ if ((renderer.mouseState >> 2) & 1) {
+ renderer.selectedArea.scale = relativeTo(
+ renderer.selectedArea.position,
+ renderer.camera.toWordPostition(event.position)
+ )
+ }
+
+ renderer.lastMousePosition = renderer.camera.toWordPostition(event.position)
+}
diff --git a/src/modules/simulationRenderer/helpers/handleMouseUp.ts b/src/modules/simulationRenderer/helpers/handleMouseUp.ts
new file mode 100644
index 0000000..4b9c75b
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/handleMouseUp.ts
@@ -0,0 +1,59 @@
+import { SimulationRenderer } from '../classes/SimulationRenderer'
+import { MouseEventInfo } from '../../core/types/MouseEventInfo'
+import { mouseButtons } from '../constants'
+import { SimulationError } from '../../errors/classes/SimulationError'
+import { addIdToSelection } from './idIsSelected'
+import { gatesInSelection } from './gatesInSelection'
+
+export const handleMouseUp = (renderer: SimulationRenderer) => (
+ event: MouseEventInfo
+) => {
+ if (event.button === mouseButtons.drag && renderer.mouseState & 1) {
+ const selected = renderer.getSelected()
+
+ for (const gate of selected) {
+ gate.transform.rotation = 0
+ }
+
+ renderer.selectedGates.temporary.clear()
+
+ // turn first bit to 0
+ renderer.mouseState &= 6
+
+ // for debugging
+ if ((renderer.mouseState >> 1) & 1 || renderer.mouseState & 1) {
+ throw new SimulationError(
+ 'First 2 bits of mouseState need to be set to 0'
+ )
+ }
+ }
+
+ if (event.button === mouseButtons.pan && (renderer.mouseState >> 1) & 1) {
+ // turn second bit to 0
+ renderer.mouseState &= 5
+ }
+
+ if (event.button === mouseButtons.select && renderer.mouseState >> 2) {
+ // turn the third bit to 0
+ renderer.mouseState &= 3
+
+ const selectedGates = gatesInSelection(
+ renderer.selectedArea,
+ Array.from(renderer.simulation.gates)
+ )
+
+ for (const { id } of selectedGates) {
+ addIdToSelection(renderer, 'permanent', id)
+
+ const node = renderer.simulation.gates.get(id)
+
+ if (node) {
+ renderer.simulation.gates.moveOnTop(node)
+ } else {
+ throw new SimulationError(
+ `Cannot find node in gate storage with id ${id}`
+ )
+ }
+ }
+ }
+}
diff --git a/src/modules/simulationRenderer/helpers/openGateProps.ts b/src/modules/simulationRenderer/helpers/openGateProps.ts
new file mode 100644
index 0000000..56a663f
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/openGateProps.ts
@@ -0,0 +1,14 @@
+import {
+ open as propsAreOpen,
+ id as propsModalId
+} from '../../logic-gates/subjects/LogicGatePropsSubjects'
+
+/**
+ * Opens the properties modal for a gate
+ *
+ * @param id The id of the gate
+ */
+export const openGateProps = (id: number) => {
+ propsModalId.next(id)
+ return propsAreOpen.next(true)
+}
diff --git a/src/modules/simulationRenderer/helpers/projectPoint.ts b/src/modules/simulationRenderer/helpers/projectPoint.ts
deleted file mode 100644
index cbe1f70..0000000
--- a/src/modules/simulationRenderer/helpers/projectPoint.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { vector3 } from '../../../common/math/types/vector3'
-import { vector2 } from '../../../common/math/types/vector2'
-
-export const projectPointOnPlane = (point: vector3, light: vector3) =>
- point.slice(0, 2).map((position, index) => {
- const delta = light[index] - position
-
- return light[index] - (delta + (point[2] * delta) / light[2])
- }) as vector2
diff --git a/src/modules/simulationRenderer/helpers/renderSimulation.ts b/src/modules/simulationRenderer/helpers/renderSimulation.ts
index e55e60e..379e570 100644
--- a/src/modules/simulationRenderer/helpers/renderSimulation.ts
+++ b/src/modules/simulationRenderer/helpers/renderSimulation.ts
@@ -5,6 +5,7 @@ import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
import { renderClickedPins } from './renderClickedPins'
import { renderWires } from './renderWires'
import { renderSelectedArea } from './renderSelectedArea'
+import { currentContext } from '../subjects'
export const renderSimulation = (
ctx: CanvasRenderingContext2D,
@@ -12,6 +13,11 @@ export const renderSimulation = (
) => {
clearCanvas(ctx)
+ // push current context if it changed
+ if (currentContext.value !== ctx) {
+ currentContext.next(ctx)
+ }
+
const transform = renderer.camera.transform
ctx.translate(...transform.position)
diff --git a/src/modules/simulationRenderer/helpers/scaleCanvas.ts b/src/modules/simulationRenderer/helpers/scaleCanvas.ts
index 902e21b..663e709 100644
--- a/src/modules/simulationRenderer/helpers/scaleCanvas.ts
+++ b/src/modules/simulationRenderer/helpers/scaleCanvas.ts
@@ -1,6 +1,6 @@
import { clamp } from '../../simulation/helpers/clamp'
import { Camera } from '../classes/Camera'
-import { vector2 } from '../../../common/math/classes/Transform'
+import { vector2 } from '../../../common/math/types/vector2'
import { multiply, substract } from '../../vector2/helpers/basic'
import { repeat } from '../../vector2/helpers/repeat'
diff --git a/src/modules/simulationRenderer/helpers/wireRadius.ts b/src/modules/simulationRenderer/helpers/wireRadius.ts
index cbaa565..3a1a9be 100644
--- a/src/modules/simulationRenderer/helpers/wireRadius.ts
+++ b/src/modules/simulationRenderer/helpers/wireRadius.ts
@@ -1,5 +1,8 @@
import { SimulationRenderer } from '../classes/SimulationRenderer'
+/**
+ * Gets the stroke width for wires
+ */
export const wireRadius = (renderer: SimulationRenderer) => {
return (
2 *
diff --git a/src/modules/simulationRenderer/subjects.ts b/src/modules/simulationRenderer/subjects.ts
new file mode 100644
index 0000000..2ca67bc
--- /dev/null
+++ b/src/modules/simulationRenderer/subjects.ts
@@ -0,0 +1,8 @@
+import { BehaviorSubject } from 'rxjs'
+
+/**
+ * Behavior subject holding the curentCnvasRenderingContext2d
+ */
+export const currentContext = new BehaviorSubject(
+ null
+)
diff --git a/src/modules/simulationRenderer/types/GateInitter.ts b/src/modules/simulationRenderer/types/GateInitter.ts
index c58e888..34e262e 100644
--- a/src/modules/simulationRenderer/types/GateInitter.ts
+++ b/src/modules/simulationRenderer/types/GateInitter.ts
@@ -1,4 +1,4 @@
-import { vector2 } from '../../../common/math/classes/Transform'
+import { vector2 } from '../../../common/math/types/vector2'
/**
* Used to init a gate at a certain position