removed rotations and added the base for multi-gate selections
This commit is contained in:
parent
116c0d5e01
commit
a9034559a3
|
@ -1,8 +1,6 @@
|
||||||
const { publish } = require('gh-pages')
|
const { publish } = require('gh-pages')
|
||||||
const { exec } = require('child_process')
|
const { exec } = require('child_process')
|
||||||
|
|
||||||
// const { publish } = require("gh-pages")
|
|
||||||
|
|
||||||
const args = process.argv.splice(2)
|
const args = process.argv.splice(2)
|
||||||
|
|
||||||
const mFlag = (args.indexOf('--message') + 1 || args.indexOf('-m') + 1) - 1
|
const mFlag = (args.indexOf('--message') + 1 || args.indexOf('-m') + 1) - 1
|
||||||
|
|
6
src/common/lang/arrays/helpers/setToArray.ts
Normal file
6
src/common/lang/arrays/helpers/setToArray.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Transforms a set into an array
|
||||||
|
*
|
||||||
|
* @param set The set to convert
|
||||||
|
*/
|
||||||
|
export const setToArray = <T>(set: Set<T>) => Array.from(set.values())
|
|
@ -3,7 +3,6 @@ import FluidCanvas from './FluidCanvas'
|
||||||
import loop from 'mainloop.js'
|
import loop from 'mainloop.js'
|
||||||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
|
import { renderSimulation } from '../../simulationRenderer/helpers/renderSimulation'
|
||||||
import { updateSimulation } from '../../simulationRenderer/helpers/updateSimulation'
|
|
||||||
import { rendererSubject } from '../subjects/rendererSubject'
|
import { rendererSubject } from '../subjects/rendererSubject'
|
||||||
import { loadSubject } from '../subjects/loadedSubject'
|
import { loadSubject } from '../subjects/loadedSubject'
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ class Canvas extends Component {
|
||||||
if (this.renderingContext) {
|
if (this.renderingContext) {
|
||||||
renderSimulation(this.renderingContext, this.renderer)
|
renderSimulation(this.renderingContext, this.renderer)
|
||||||
}
|
}
|
||||||
}).setUpdate(delta => updateSimulation(this.renderer, delta))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
|
|
|
@ -2,15 +2,11 @@ import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import { Screen } from '../classes/Screen'
|
import { Screen } from '../classes/Screen'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { mouseButton } from '../types/mouseButton'
|
||||||
|
import { MouseEventInfo } from './MouseEventInfo'
|
||||||
|
|
||||||
const screen = new Screen()
|
const screen = new Screen()
|
||||||
|
|
||||||
export interface MouseEventInfo {
|
|
||||||
position: vector2
|
|
||||||
button: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FluidCanvasProps {
|
export interface FluidCanvasProps {
|
||||||
mouseDownOuput: Subject<MouseEventInfo>
|
mouseDownOuput: Subject<MouseEventInfo>
|
||||||
mouseUpOutput: Subject<MouseEventInfo>
|
mouseUpOutput: Subject<MouseEventInfo>
|
||||||
|
@ -21,7 +17,7 @@ export const getEventInfo = (
|
||||||
e: MouseEvent<HTMLCanvasElement>
|
e: MouseEvent<HTMLCanvasElement>
|
||||||
): MouseEventInfo => {
|
): MouseEventInfo => {
|
||||||
return {
|
return {
|
||||||
button: e.button,
|
button: e.button as mouseButton,
|
||||||
position: [e.clientX, e.clientY]
|
position: [e.clientX, e.clientY]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/modules/core/components/MouseEventInfo.tsx
Normal file
10
src/modules/core/components/MouseEventInfo.tsx
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
import { mouseButton } from '../types/mouseButton'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The info about the mouse passed to mouse subjects
|
||||||
|
*/
|
||||||
|
export interface MouseEventInfo {
|
||||||
|
position: vector2
|
||||||
|
button: mouseButton
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import CreateSimulationButton from './CreateSimulationButton'
|
||||||
import LogicGates from './LogicGates'
|
import LogicGates from './LogicGates'
|
||||||
import { makeStyles, createStyles } from '@material-ui/core/styles'
|
import { makeStyles, createStyles } from '@material-ui/core/styles'
|
||||||
import Language from './Language'
|
import Language from './Language'
|
||||||
|
import SimulationActions from '../../simulation-actions/components/SimulationActions'
|
||||||
/**
|
/**
|
||||||
* The width of the sidebar
|
* The width of the sidebar
|
||||||
*/
|
*/
|
||||||
|
@ -70,6 +71,7 @@ const Sidebar = () => {
|
||||||
<CreateSimulationButton />
|
<CreateSimulationButton />
|
||||||
<OpenSimulation />
|
<OpenSimulation />
|
||||||
<LogicGates />
|
<LogicGates />
|
||||||
|
<SimulationActions />
|
||||||
</List>
|
</List>
|
||||||
|
|
||||||
<Language />
|
<Language />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { MouseEventInfo } from '../components/FluidCanvas'
|
import { MouseEventInfo } from '../components/MouseEventInfo'
|
||||||
|
|
||||||
export type MouseSubject = Subject<MouseEventInfo>
|
export type MouseSubject = Subject<MouseEventInfo>
|
||||||
|
|
4
src/modules/core/types/mouseButton.ts
Normal file
4
src/modules/core/types/mouseButton.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* All possibl values for the mouse button
|
||||||
|
*/
|
||||||
|
export type mouseButton = 0 | 1 | 2
|
|
@ -9,6 +9,7 @@ export const EnglishTranslation: Translation = {
|
||||||
createSimulation: 'Create simulation',
|
createSimulation: 'Create simulation',
|
||||||
logicGates: 'Logic gates',
|
logicGates: 'Logic gates',
|
||||||
openSimulation: 'Open simulations',
|
openSimulation: 'Open simulations',
|
||||||
|
simulation: 'Simulation',
|
||||||
language: 'Language'
|
language: 'Language'
|
||||||
},
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
|
@ -23,11 +24,22 @@ export const EnglishTranslation: Translation = {
|
||||||
question: 'What do you want your simulation to be called?'
|
question: 'What do you want your simulation to be called?'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
save: 'Save',
|
||||||
|
clean: 'Clean',
|
||||||
|
clear: 'Clear',
|
||||||
|
refresh: 'Refresh',
|
||||||
|
undo: 'Undo'
|
||||||
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
||||||
switchedToSimulation: name =>
|
switchedToSimulation: name =>
|
||||||
`Succesfully switched to simulation '${name}'`,
|
`Succesfully switched to simulation '${name}'`,
|
||||||
savedSimulation: name => `Succesfully saved simulation '${name}'`,
|
savedSimulation: name => `Succesfully saved simulation '${name}'`,
|
||||||
compiledIc: name => `Succesfully compiled circuit '${name}'`
|
compiledIc: name => `Succesfully compiled circuit '${name}'`,
|
||||||
|
cleaned: name => `Succesfully cleaned simulation '${name}'`,
|
||||||
|
cleared: name => `Succesfully cleared simulation '${name}'`,
|
||||||
|
refreshed: name => `Succesfully refreshed simulation '${name}'`,
|
||||||
|
undone: name => `Succesfully undone simulation '${name}'`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const DutchTranslation: Translation = {
|
||||||
createSimulation: 'Maak simulatie',
|
createSimulation: 'Maak simulatie',
|
||||||
logicGates: 'Logische poorten',
|
logicGates: 'Logische poorten',
|
||||||
openSimulation: 'Open simulatie',
|
openSimulation: 'Open simulatie',
|
||||||
|
simulation: 'Todo',
|
||||||
language: 'Taal'
|
language: 'Taal'
|
||||||
},
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const RomanianTranslation: Translation = {
|
||||||
createSimulation: 'Creează o simulație',
|
createSimulation: 'Creează o simulație',
|
||||||
openSimulation: 'Deschide o simulație',
|
openSimulation: 'Deschide o simulație',
|
||||||
logicGates: 'Porți logice',
|
logicGates: 'Porți logice',
|
||||||
|
simulation: 'Simulație',
|
||||||
language: 'Limba'
|
language: 'Limba'
|
||||||
},
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
|
@ -23,6 +24,9 @@ export const RomanianTranslation: Translation = {
|
||||||
question: 'Cum vrei să numești simulația?'
|
question: 'Cum vrei să numești simulația?'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
save: 'Salvează'
|
||||||
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name =>
|
createdSimulation: name =>
|
||||||
`Simulația '${name}' a fost creeată cu succes`,
|
`Simulația '${name}' a fost creeată cu succes`,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { supportedLanguage } from './supportedLanguages'
|
import { supportedLanguage } from './supportedLanguages'
|
||||||
import { simulationMode } from '../../saving/types/SimulationSave'
|
import { simulationMode } from '../../saving/types/SimulationSave'
|
||||||
|
import { possibleAction } from '../../simulation-actions/types/possibleAction'
|
||||||
|
|
||||||
export type SentenceFactory<T extends string[]> = (...names: T) => string
|
export type SentenceFactory<T extends string[]> = (...names: T) => string
|
||||||
export type NameSentence = SentenceFactory<[string]>
|
export type NameSentence = SentenceFactory<[string]>
|
||||||
|
@ -13,6 +14,7 @@ export interface Translation {
|
||||||
createSimulation: string
|
createSimulation: string
|
||||||
openSimulation: string
|
openSimulation: string
|
||||||
logicGates: string
|
logicGates: string
|
||||||
|
simulation: string
|
||||||
language: string
|
language: string
|
||||||
}
|
}
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
|
@ -29,5 +31,10 @@ export interface Translation {
|
||||||
switchedToSimulation: NameSentence
|
switchedToSimulation: NameSentence
|
||||||
savedSimulation: NameSentence
|
savedSimulation: NameSentence
|
||||||
compiledIc: NameSentence
|
compiledIc: NameSentence
|
||||||
|
refreshed: NameSentence
|
||||||
|
cleared: NameSentence
|
||||||
|
cleaned: NameSentence
|
||||||
|
undone: NameSentence
|
||||||
}
|
}
|
||||||
|
actions: Record<possibleAction, string>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
import { KeyBindingMap } from './types/KeyBindingMap'
|
|
||||||
import { save } from '../saving/helpers/save'
|
|
||||||
|
|
||||||
export const keyBindings: KeyBindingMap = []
|
|
|
@ -1,9 +1,22 @@
|
||||||
import { keyBindings } from '../constants'
|
|
||||||
import { KeyboardInput } from '../classes/KeyboardInput'
|
import { KeyboardInput } from '../classes/KeyboardInput'
|
||||||
import { KeyBindingMap } from '../types/KeyBindingMap'
|
import { KeyBindingMap, KeyBinding } from '../types/KeyBindingMap'
|
||||||
|
import { SidebarActions } from '../../simulation-actions/constants'
|
||||||
|
import { SidebarAction } from '../../simulation-actions/types/SidebarAction'
|
||||||
|
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
|
||||||
|
|
||||||
export const listeners: Record<string, KeyboardInput> = {}
|
export const listeners: Record<string, KeyboardInput> = {}
|
||||||
|
|
||||||
|
const keyBindings = Object.values(SidebarActions)
|
||||||
|
.filter(action => action.keybinding)
|
||||||
|
.map(
|
||||||
|
(action): KeyBinding => {
|
||||||
|
return {
|
||||||
|
actions: [action.action],
|
||||||
|
keys: action.keybinding || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export const initKeyBindings = (bindings: KeyBindingMap = keyBindings) => {
|
export const initKeyBindings = (bindings: KeyBindingMap = keyBindings) => {
|
||||||
const allKeys = new Set<string>()
|
const allKeys = new Set<string>()
|
||||||
|
|
||||||
|
@ -18,26 +31,54 @@ export const initKeyBindings = (bindings: KeyBindingMap = keyBindings) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', e => {
|
window.addEventListener('keydown', e => {
|
||||||
for (const keyBinding of bindings) {
|
if (!modalIsOpen()) {
|
||||||
let done = false
|
const current: {
|
||||||
|
keys: string[]
|
||||||
|
callback: Function
|
||||||
|
}[] = []
|
||||||
|
|
||||||
for (const key of keyBinding.keys) {
|
for (const keyBinding of bindings) {
|
||||||
if (!(done || listeners[key].value)) {
|
let done = false
|
||||||
done = true
|
|
||||||
break
|
for (const key of keyBinding.keys) {
|
||||||
|
if (!(done || listeners[key].value)) {
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
current.push({
|
||||||
|
keys: keyBinding.keys,
|
||||||
|
callback: () => {
|
||||||
|
for (const action of keyBinding.actions) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (done) {
|
if (current.length) {
|
||||||
continue
|
let maxIndex = 0
|
||||||
}
|
let max = current[0].keys.length
|
||||||
|
|
||||||
for (const action of keyBinding.actions) {
|
for (let index = 1; index < current.length; index++) {
|
||||||
action()
|
const element = current[index]
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault()
|
if (element.keys.length > max) {
|
||||||
e.stopPropagation()
|
max = element.keys.length
|
||||||
|
maxIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
current[maxIndex].callback()
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationR
|
||||||
export const dumpSimulation = (renderer: SimulationRenderer) => {
|
export const dumpSimulation = (renderer: SimulationRenderer) => {
|
||||||
renderer.simulation.dispose()
|
renderer.simulation.dispose()
|
||||||
renderer.lastMousePosition = [0, 0]
|
renderer.lastMousePosition = [0, 0]
|
||||||
renderer.selectedGate = null
|
renderer.selectedGates = new Set()
|
||||||
renderer.selectedPins = {
|
renderer.selectedPins = {
|
||||||
end: null,
|
end: null,
|
||||||
start: null
|
start: null
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import ListItem from '@material-ui/core/ListItem'
|
||||||
|
import ListItemIcon from '@material-ui/core/ListItemIcon'
|
||||||
|
import ListItemText from '@material-ui/core/ListItemText'
|
||||||
|
import Menu from '@material-ui/core/Menu'
|
||||||
|
import MenuItem from '@material-ui/core/MenuItem'
|
||||||
|
import Icon from '@material-ui/core/Icon'
|
||||||
|
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||||
|
import { SidebarActions } from '../constants'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component wich contains the sidebar 'Simulation' button
|
||||||
|
*/
|
||||||
|
const SimulationActions = () => {
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
|
||||||
|
|
||||||
|
const translation = useTranslation()
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setAnchorEl(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ListItem
|
||||||
|
button
|
||||||
|
onClick={event => {
|
||||||
|
setAnchorEl(event.currentTarget)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon>insert_drive_file</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{translation.sidebar.simulation}</ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
keepMounted
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
{Object.values(SidebarActions).map(
|
||||||
|
({ icon, name, keybinding, action }, index) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={index} onClick={action}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<Icon>{icon}</Icon>
|
||||||
|
</ListItemIcon>
|
||||||
|
|
||||||
|
<ListItemText
|
||||||
|
primary={name}
|
||||||
|
secondary={(keybinding || []).join(' + ')}
|
||||||
|
/>
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SimulationActions
|
52
src/modules/simulation-actions/constants.ts
Normal file
52
src/modules/simulation-actions/constants.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import { SidebarAction } from './types/SidebarAction'
|
||||||
|
import { possibleAction } from './types/possibleAction'
|
||||||
|
import { save } from '../saving/helpers/save'
|
||||||
|
import { refresh } from './helpers/refresh'
|
||||||
|
import { undo } from './helpers/undo'
|
||||||
|
import { createActionConfig } from './helpers/createActionConfig'
|
||||||
|
import { clear } from './helpers/clear'
|
||||||
|
|
||||||
|
export const actionIcons: Record<possibleAction, string> = {
|
||||||
|
clean: 'layers_clear',
|
||||||
|
clear: 'clear',
|
||||||
|
refresh: 'refresh',
|
||||||
|
save: 'save',
|
||||||
|
undo: 'undo'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array with all the actions for the SimulationAction component to render
|
||||||
|
*/
|
||||||
|
export const SidebarActions: Record<possibleAction, SidebarAction> = {
|
||||||
|
...createActionConfig('save', save, ['ctrl', 's']),
|
||||||
|
...createActionConfig(
|
||||||
|
'refresh',
|
||||||
|
{
|
||||||
|
run: refresh
|
||||||
|
},
|
||||||
|
['ctrl', 'r']
|
||||||
|
),
|
||||||
|
...createActionConfig(
|
||||||
|
'undo',
|
||||||
|
{
|
||||||
|
run: undo
|
||||||
|
},
|
||||||
|
['ctrl', 'z']
|
||||||
|
),
|
||||||
|
...createActionConfig(
|
||||||
|
'clear',
|
||||||
|
{
|
||||||
|
run: clear
|
||||||
|
},
|
||||||
|
['ctrl', 'delete']
|
||||||
|
),
|
||||||
|
...createActionConfig(
|
||||||
|
'clean',
|
||||||
|
{
|
||||||
|
run: () => {
|
||||||
|
console.log('Cleaning')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
['ctrl', 'shift', 'delete']
|
||||||
|
)
|
||||||
|
}
|
12
src/modules/simulation-actions/helpers/clear.ts
Normal file
12
src/modules/simulation-actions/helpers/clear.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the simuolation of a renderer
|
||||||
|
*
|
||||||
|
* @param renderer The renderer to clear the simulation of
|
||||||
|
*/
|
||||||
|
export const clear = (renderer: SimulationRenderer) => {
|
||||||
|
renderer.simulation.dispose()
|
||||||
|
renderer.simulation.wires = []
|
||||||
|
renderer.simulation.gates.clear()
|
||||||
|
}
|
44
src/modules/simulation-actions/helpers/createActionConfig.ts
Normal file
44
src/modules/simulation-actions/helpers/createActionConfig.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { possibleAction } from '../types/possibleAction'
|
||||||
|
import { SidebarAction } from '../types/SidebarAction'
|
||||||
|
import { actionIcons } from '../constants'
|
||||||
|
import { createRendererAction } from './createRendererActions'
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
|
||||||
|
|
||||||
|
export type ActionConfigFunction = (renderer?: SimulationRenderer) => void
|
||||||
|
|
||||||
|
export type ActionConfigCallback =
|
||||||
|
| {
|
||||||
|
renderer?: boolean
|
||||||
|
run: ActionConfigFunction
|
||||||
|
}
|
||||||
|
| ActionConfigFunction
|
||||||
|
|
||||||
|
export const createActionConfig = <T extends possibleAction>(
|
||||||
|
name: T,
|
||||||
|
callback: ActionConfigCallback,
|
||||||
|
keybinding: string[] = []
|
||||||
|
): Record<T, SidebarAction> => {
|
||||||
|
let action: ActionConfigFunction
|
||||||
|
|
||||||
|
if (callback instanceof Function) {
|
||||||
|
action = callback
|
||||||
|
} else {
|
||||||
|
if (callback.renderer !== false) {
|
||||||
|
action = createRendererAction(name, callback.run)
|
||||||
|
} else {
|
||||||
|
action = callback.run
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
[name]: {
|
||||||
|
name,
|
||||||
|
action: () => {
|
||||||
|
action(getRendererSafely())
|
||||||
|
},
|
||||||
|
icon: actionIcons[name],
|
||||||
|
keybinding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { possibleAction } from '../types/possibleAction'
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
import { CurrentLanguage } from '../../internalisation/stores/currentLanguage'
|
||||||
|
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
|
||||||
|
import { createToastArguments } from '../../toasts/helpers/createToastArguments'
|
||||||
|
import { actionIcons } from '../constants'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
import { Translation } from '../../internalisation/types/TranslationInterface'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map used to get the correct message from any action name
|
||||||
|
*/
|
||||||
|
export const actionToMessageMap: Record<
|
||||||
|
possibleAction,
|
||||||
|
keyof Translation['messages']
|
||||||
|
> = {
|
||||||
|
clean: 'cleaned',
|
||||||
|
clear: 'cleared',
|
||||||
|
refresh: 'refreshed',
|
||||||
|
undo: 'undone',
|
||||||
|
save: 'savedSimulation'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createRendererAction = (
|
||||||
|
action: possibleAction,
|
||||||
|
callback: (renderer: SimulationRenderer) => void
|
||||||
|
) => () => {
|
||||||
|
const translation = CurrentLanguage.getTranslation()
|
||||||
|
const renderer = getRendererSafely()
|
||||||
|
|
||||||
|
callback(renderer)
|
||||||
|
|
||||||
|
toast(
|
||||||
|
...createToastArguments(
|
||||||
|
translation.messages[actionToMessageMap[action]](
|
||||||
|
renderer.simulation.name
|
||||||
|
),
|
||||||
|
actionIcons[action]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
11
src/modules/simulation-actions/helpers/refresh.ts
Normal file
11
src/modules/simulation-actions/helpers/refresh.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { getRendererState } from '../../saving/helpers/getState'
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes a simulations
|
||||||
|
*
|
||||||
|
* @param renderer - the renderer to refresh
|
||||||
|
*/
|
||||||
|
export const refresh = (renderer: SimulationRenderer) => {
|
||||||
|
renderer.loadSave(getRendererState(renderer))
|
||||||
|
}
|
10
src/modules/simulation-actions/helpers/undo.ts
Normal file
10
src/modules/simulation-actions/helpers/undo.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Undoes a simulation
|
||||||
|
*
|
||||||
|
* @param renderer - the renderer to undo the simulation of
|
||||||
|
*/
|
||||||
|
export const undo = (renderer: SimulationRenderer) => {
|
||||||
|
renderer.reloadSave()
|
||||||
|
}
|
9
src/modules/simulation-actions/types/SidebarAction.ts
Normal file
9
src/modules/simulation-actions/types/SidebarAction.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* The interface for all the actions wvound to the Simulation button on the sidebar
|
||||||
|
*/
|
||||||
|
export interface SidebarAction {
|
||||||
|
name: string
|
||||||
|
icon: string
|
||||||
|
keybinding?: string[]
|
||||||
|
action: () => void
|
||||||
|
}
|
4
src/modules/simulation-actions/types/possibleAction.ts
Normal file
4
src/modules/simulation-actions/types/possibleAction.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Type repressenting al lpossible actions
|
||||||
|
*/
|
||||||
|
export type possibleAction = 'save' | 'clear' | 'clean' | 'refresh' | 'undo'
|
|
@ -1,18 +1,48 @@
|
||||||
import { LruCacheNode } from '@eix-js/utils'
|
import { LruCacheNode } from '@eix-js/utils'
|
||||||
import { Gate } from './Gate'
|
import { Gate } from './Gate'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The nodes used inside the gat storage
|
||||||
|
*/
|
||||||
export type GateNode = LruCacheNode<Gate>
|
export type GateNode = LruCacheNode<Gate>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dequeue implementation with iteration support.
|
||||||
|
* Used to store the logic gates
|
||||||
|
*/
|
||||||
export class GateStorage {
|
export class GateStorage {
|
||||||
|
/**
|
||||||
|
* The map containing all the hash - node pairs
|
||||||
|
*/
|
||||||
private hashMap = new Map<number, GateNode>()
|
private hashMap = new Map<number, GateNode>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The head of the dequeue
|
||||||
|
*/
|
||||||
private head = new LruCacheNode<Gate>(0, null)
|
private head = new LruCacheNode<Gate>(0, null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tail of the dequue
|
||||||
|
*/
|
||||||
private tail = new LruCacheNode<Gate>(0, null)
|
private tail = new LruCacheNode<Gate>(0, null)
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Links the head to the tail
|
||||||
|
*/
|
||||||
|
private init() {
|
||||||
this.head.next = this.tail
|
this.head.next = this.tail
|
||||||
this.tail.previous = this.head
|
this.tail.previous = this.head
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a gate
|
||||||
|
*
|
||||||
|
* @param node The node to delete
|
||||||
|
*/
|
||||||
public delete(node: GateNode) {
|
public delete(node: GateNode) {
|
||||||
node.previous.next = node.next
|
node.previous.next = node.next
|
||||||
node.next.previous = node.previous
|
node.next.previous = node.previous
|
||||||
|
@ -20,11 +50,22 @@ export class GateStorage {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new hash - gate pair to the dequeue
|
||||||
|
*
|
||||||
|
* @param key The ket of the gate
|
||||||
|
* @param node The gate itself
|
||||||
|
*/
|
||||||
public set(key: number, node: GateNode) {
|
public set(key: number, node: GateNode) {
|
||||||
this.hashMap.set(key, node)
|
this.hashMap.set(key, node)
|
||||||
this.addToHead(node)
|
this.addToHead(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a node directly after head
|
||||||
|
*
|
||||||
|
* @param node The node to add
|
||||||
|
*/
|
||||||
public addToHead(node: GateNode) {
|
public addToHead(node: GateNode) {
|
||||||
node.next = this.head.next
|
node.next = this.head.next
|
||||||
node.next.previous = node
|
node.next.previous = node
|
||||||
|
@ -34,24 +75,40 @@ export class GateStorage {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a node directly after the head
|
||||||
|
*
|
||||||
|
* @param node The node to move
|
||||||
|
*/
|
||||||
public moveOnTop(node: GateNode) {
|
public moveOnTop(node: GateNode) {
|
||||||
this.delete(node).addToHead(node)
|
this.delete(node).addToHead(node)
|
||||||
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a gate by its key
|
||||||
|
*
|
||||||
|
* @param key The key of the gate
|
||||||
|
*/
|
||||||
public get(key: number) {
|
public get(key: number) {
|
||||||
const node = this.hashMap.get(key)
|
const node = this.hashMap.get(key)
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the first gate in the dequeue
|
||||||
|
*/
|
||||||
public first() {
|
public first() {
|
||||||
const first = this.head.next
|
const first = this.head.next
|
||||||
|
|
||||||
return
|
return first
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for iterating over the dequeue
|
||||||
|
*/
|
||||||
public *[Symbol.iterator](): Iterator<Gate> {
|
public *[Symbol.iterator](): Iterator<Gate> {
|
||||||
let last = this.tail
|
let last = this.tail
|
||||||
|
|
||||||
|
@ -67,4 +124,13 @@ export class GateStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes every gate in the dequeue
|
||||||
|
*/
|
||||||
|
public clear() {
|
||||||
|
this.hashMap = new Map()
|
||||||
|
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +0,0 @@
|
||||||
import { Singleton } from '@eix-js/utils'
|
|
||||||
import { MouseSubject } from '../../core/types/MouseSubject'
|
|
||||||
import { clamp } from '../../simulation/helpers/clamp'
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
export class MouseVelocityManager {
|
|
||||||
private history: number[] = []
|
|
||||||
private total = 0
|
|
||||||
private limit = 10
|
|
||||||
private lastPosition = 0
|
|
||||||
private lastDirection = 0
|
|
||||||
private minimumDifference = 10
|
|
||||||
|
|
||||||
private lastMove = performance.now()
|
|
||||||
private resetLimit = 500
|
|
||||||
|
|
||||||
// mouseMoveInput is optional because we want to be able to get
|
|
||||||
// the instance even if we don't have a subject
|
|
||||||
// (as long as we passed one the first time)
|
|
||||||
public constructor(public mouseMoveInput?: MouseSubject) {
|
|
||||||
if (this.mouseMoveInput) {
|
|
||||||
this.mouseMoveInput.subscribe(event => {
|
|
||||||
this.lastMove = performance.now()
|
|
||||||
|
|
||||||
const position = event.position[0]
|
|
||||||
const dx = position - this.lastPosition
|
|
||||||
|
|
||||||
if (Math.abs(dx) < this.minimumDifference) {
|
|
||||||
this.lastPosition = position
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dx === 0) {
|
|
||||||
this.lastDirection = 0
|
|
||||||
} else {
|
|
||||||
this.lastDirection = Math.abs(dx) / dx
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastPosition = event.position[0]
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'You need to pass a MouseMoveInput the first time you instantiate this class!'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getDirection() {
|
|
||||||
if (this.history.length) {
|
|
||||||
return clamp(-1, 1, this.total / this.history.length)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
public update(maybeAgain = true) {
|
|
||||||
if (this.lastDirection === 0) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
this.lastDirection !== 0 &&
|
|
||||||
performance.now() - this.lastMove > this.resetLimit
|
|
||||||
) {
|
|
||||||
this.lastDirection = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
this.history.push(this.lastDirection)
|
|
||||||
|
|
||||||
this.total += this.lastDirection
|
|
||||||
|
|
||||||
if (this.history.length > this.limit) {
|
|
||||||
const removed = this.history.shift()
|
|
||||||
|
|
||||||
if (removed !== undefined) {
|
|
||||||
this.total -= removed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public clear(value?: number) {
|
|
||||||
if (value) {
|
|
||||||
this.lastPosition = value
|
|
||||||
}
|
|
||||||
this.history = []
|
|
||||||
this.total = 0
|
|
||||||
this.lastMove = performance.now()
|
|
||||||
this.lastDirection = 0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +1,18 @@
|
||||||
import { Camera } from './Camera'
|
import { Camera } from './Camera'
|
||||||
import { Simulation } from '../../simulation/classes/Simulation'
|
import { Simulation } from '../../simulation/classes/Simulation'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { MouseEventInfo } from '../../core/components/FluidCanvas'
|
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
||||||
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { MouseVelocityManager } from './MouseVelocityManager'
|
|
||||||
import { Screen } from '../../core/classes/Screen'
|
import { Screen } from '../../core/classes/Screen'
|
||||||
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
||||||
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
||||||
import { defaultSimulationRendererOptions } from '../constants'
|
import { defaultSimulationRendererOptions, mouseButtons } from '../constants'
|
||||||
import { getPinPosition } from '../helpers/pinPosition'
|
import { getPinPosition } from '../helpers/pinPosition'
|
||||||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||||
import { SelectedPins } from '../types/SelectedPins'
|
import { SelectedPins } from '../types/SelectedPins'
|
||||||
import { Wire } from '../../simulation/classes/Wire'
|
import { Wire } from '../../simulation/classes/Wire'
|
||||||
import { KeyBindingMap } from '../../keybindings/types/KeyBindingMap'
|
import { KeyBindingMap } from '../../keybindings/types/KeyBindingMap'
|
||||||
import { save } from '../../saving/helpers/save'
|
|
||||||
import { initKeyBindings } from '../../keybindings/helpers/initialiseKeyBindings'
|
import { initKeyBindings } from '../../keybindings/helpers/initialiseKeyBindings'
|
||||||
import { currentStore } from '../../saving/stores/currentStore'
|
import { currentStore } from '../../saving/stores/currentStore'
|
||||||
import { saveStore } from '../../saving/stores/saveStore'
|
import { saveStore } from '../../saving/stores/saveStore'
|
||||||
|
@ -30,6 +28,9 @@ import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
||||||
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
|
import { modalIsOpen } from '../../modals/helpers/modalIsOpen'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
||||||
|
import { RendererState } from '../../saving/types/SimulationSave'
|
||||||
|
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
|
||||||
|
import { Selection } from '../types/Selection'
|
||||||
|
|
||||||
export class SimulationRenderer {
|
export class SimulationRenderer {
|
||||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||||
|
@ -37,17 +38,16 @@ export class SimulationRenderer {
|
||||||
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
||||||
public wheelOutput = new Subject<unknown>()
|
public wheelOutput = new Subject<unknown>()
|
||||||
|
|
||||||
public selectedGate: number | null = null
|
public selectedGates = new Set<Selection>()
|
||||||
public lastMousePosition: vector2 = [0, 0]
|
public lastMousePosition: vector2 = [0, 0]
|
||||||
public movedSelection = false
|
|
||||||
public options: SimulationRendererOptions
|
public options: SimulationRendererOptions
|
||||||
public mouseManager = new MouseVelocityManager(this.mouseMoveOutput)
|
|
||||||
public screen = new Screen()
|
public screen = new Screen()
|
||||||
public camera = new Camera()
|
public camera = new Camera()
|
||||||
|
|
||||||
// first bit = dragging
|
// first bit = dragging
|
||||||
// second bit = moving around
|
// second bit = panning around
|
||||||
private mouseState = 0b00
|
// third bit = selecting
|
||||||
|
private mouseState = 0b000
|
||||||
private gateSelectionOffset: vector2 = [0, 0]
|
private gateSelectionOffset: vector2 = [0, 0]
|
||||||
|
|
||||||
// this is used for spawning gates
|
// this is used for spawning gates
|
||||||
|
@ -81,16 +81,18 @@ export class SimulationRenderer {
|
||||||
for (let index = gates.length - 1; index >= 0; index--) {
|
for (let index = gates.length - 1; index >= 0; index--) {
|
||||||
const { transform, id, pins } = gates[index]
|
const { transform, id, pins } = gates[index]
|
||||||
|
|
||||||
if (pointInSquare(worldPosition, transform)) {
|
if (
|
||||||
// run function
|
event.button === mouseButtons.drag &&
|
||||||
|
pointInSquare(worldPosition, transform)
|
||||||
|
) {
|
||||||
gates[index].onClick()
|
gates[index].onClick()
|
||||||
|
|
||||||
this.mouseManager.clear(worldPosition[0])
|
|
||||||
|
|
||||||
this.mouseState |= 1
|
this.mouseState |= 1
|
||||||
this.movedSelection = false
|
|
||||||
|
|
||||||
this.selectedGate = id
|
this.selectedGates.add({
|
||||||
|
id,
|
||||||
|
permanent: false
|
||||||
|
})
|
||||||
this.gateSelectionOffset = worldPosition.map(
|
this.gateSelectionOffset = worldPosition.map(
|
||||||
(position, index) =>
|
(position, index) =>
|
||||||
position - transform.position[index]
|
position - transform.position[index]
|
||||||
|
@ -100,9 +102,11 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
if (gateNode) {
|
if (gateNode) {
|
||||||
return this.simulation.gates.moveOnTop(gateNode)
|
return this.simulation.gates.moveOnTop(gateNode)
|
||||||
|
} else {
|
||||||
|
throw new SimulationError(
|
||||||
|
`Cannot find gate with id ${id}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pin of pins) {
|
for (const pin of pins) {
|
||||||
|
@ -145,7 +149,7 @@ export class SimulationRenderer {
|
||||||
) {
|
) {
|
||||||
this.selectedPins.start = null
|
this.selectedPins.start = null
|
||||||
this.selectedPins.end = null
|
this.selectedPins.end = null
|
||||||
} else if ((pin.value.type & 0b10) >> 1) {
|
} else if ((pin.value.type & 2) >> 1) {
|
||||||
this.selectedPins.start = {
|
this.selectedPins.start = {
|
||||||
wrapper: pin,
|
wrapper: pin,
|
||||||
transform
|
transform
|
||||||
|
@ -167,22 +171,31 @@ export class SimulationRenderer {
|
||||||
this.selectedPins.start = null
|
this.selectedPins.start = null
|
||||||
this.selectedPins.end = null
|
this.selectedPins.end = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mouseState |= 0b10
|
if (event.button === mouseButtons.pan) {
|
||||||
|
this.mouseState |= 0b10
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseUpOutput.subscribe(event => {
|
this.mouseUpOutput.subscribe(() => {
|
||||||
if (this.selectedGate !== null) {
|
if (this.selectedGates.size) {
|
||||||
const selected = this.getSelected()
|
const selected = this.getSelected()
|
||||||
|
|
||||||
if (selected) {
|
for (const gate of selected) {
|
||||||
selected.transform.rotation = 0
|
gate.transform.rotation = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const selection of this.selectedGates.values()) {
|
||||||
|
if (!selection.permanent) {
|
||||||
|
this.selectedGates.delete(selection)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedGate = null
|
|
||||||
this.mouseState &= 0
|
this.mouseState &= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,18 +207,12 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
const worldPosition = this.camera.toWordPostition(event.position)
|
const worldPosition = this.camera.toWordPostition(event.position)
|
||||||
|
|
||||||
if (this.mouseState & 1 && this.selectedGate !== null) {
|
if (this.mouseState & 1 && this.selectedGates.size) {
|
||||||
const gate = this.getGateById(this.selectedGate)
|
for (const gate of this.getSelected()) {
|
||||||
|
const { transform } = gate
|
||||||
|
|
||||||
if (!gate || !gate.data) return
|
transform.x = worldPosition[0] - this.gateSelectionOffset[0]
|
||||||
|
transform.y = worldPosition[1] - this.gateSelectionOffset[1]
|
||||||
const transform = gate.data.transform
|
|
||||||
|
|
||||||
transform.x = worldPosition[0] - this.gateSelectionOffset[0]
|
|
||||||
transform.y = worldPosition[1] - this.gateSelectionOffset[1]
|
|
||||||
|
|
||||||
if (!this.movedSelection) {
|
|
||||||
this.movedSelection = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +252,11 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public loadSave(save: RendererState) {
|
||||||
|
this.simulation = fromSimulationState(save.simulation)
|
||||||
|
this.camera = fromCameraState(save.camera)
|
||||||
|
}
|
||||||
|
|
||||||
public reloadSave() {
|
public reloadSave() {
|
||||||
try {
|
try {
|
||||||
const current = currentStore.get()
|
const current = currentStore.get()
|
||||||
|
@ -253,8 +265,7 @@ export class SimulationRenderer {
|
||||||
if (!save) return
|
if (!save) return
|
||||||
if (!(save.simulation || save.camera)) return
|
if (!(save.simulation || save.camera)) return
|
||||||
|
|
||||||
this.simulation = fromSimulationState(save.simulation)
|
this.loadSave(save)
|
||||||
this.camera = fromCameraState(save.camera)
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`An error occured while loading the save: ${
|
`An error occured while loading the save: ${
|
||||||
|
@ -266,38 +277,28 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
private initKeyBindings() {
|
private initKeyBindings() {
|
||||||
const bindings: KeyBindingMap = [
|
const bindings: KeyBindingMap = [
|
||||||
{
|
|
||||||
keys: ['ctrl', 's'],
|
|
||||||
actions: [() => save(this)]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
keys: ['delete'],
|
keys: ['delete'],
|
||||||
actions: [
|
actions: [
|
||||||
() => {
|
() => {
|
||||||
const selected = this.getSelected()
|
for (const gate of this.getSelected()) {
|
||||||
|
const node = this.simulation.gates.get(gate.id)
|
||||||
|
|
||||||
if (!selected) {
|
if (!node) continue
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const node = this.simulation.gates.get(selected.id)
|
for (const wire of this.simulation.wires) {
|
||||||
|
if (wireConnectedToGate(gate, wire)) {
|
||||||
if (!node) {
|
wire.dispose()
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const wire of this.simulation.wires) {
|
|
||||||
if (wireConnectedToGate(selected, wire)) {
|
|
||||||
wire.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.simulation.wires = this.simulation.wires.filter(
|
||||||
|
wire => wire.active
|
||||||
|
)
|
||||||
|
|
||||||
|
gate.dispose()
|
||||||
|
this.simulation.gates.delete(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.simulation.wires = this.simulation.wires.filter(
|
|
||||||
wire => wire.active
|
|
||||||
)
|
|
||||||
|
|
||||||
selected.dispose()
|
|
||||||
this.simulation.gates.delete(node)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -310,13 +311,25 @@ export class SimulationRenderer {
|
||||||
return this.simulation.gates.get(id)
|
return this.simulation.gates.get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all selected gates in the simulation
|
||||||
|
*
|
||||||
|
* @throws SimulationError if an id isnt valid
|
||||||
|
* @throws SimulationError if the id doesnt have a data prop
|
||||||
|
*/
|
||||||
public getSelected() {
|
public getSelected() {
|
||||||
if (this.selectedGate === null) return null
|
return setToArray(this.selectedGates).map(({ id }) => {
|
||||||
|
const gate = this.simulation.gates.get(id)
|
||||||
|
|
||||||
const gate = this.getGateById(this.selectedGate)
|
if (!gate) {
|
||||||
|
throw new SimulationError(`Cannot find gate with id ${id}`)
|
||||||
|
} else if (!gate.data) {
|
||||||
|
throw new SimulationError(
|
||||||
|
`Cannot find data of gate with id ${id}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!gate || !gate.data) return null
|
return gate.data
|
||||||
|
})
|
||||||
return gate.data
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from './types/SimulationRendererOptions'
|
||||||
import { vector2 } from '../../common/math/classes/Transform'
|
import { vector2 } from '../../common/math/classes/Transform'
|
||||||
|
import { mouseButton } from '../core/types/mouseButton'
|
||||||
|
|
||||||
export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
dnd: {
|
dnd: {
|
||||||
|
@ -30,3 +31,13 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const imageQuality: vector2 = [100, 100]
|
export const imageQuality: vector2 = [100, 100]
|
||||||
|
|
||||||
|
export const mouseButtons: Record<
|
||||||
|
'zoom' | 'pan' | 'drag' | 'select',
|
||||||
|
mouseButton
|
||||||
|
> = {
|
||||||
|
zoom: 1,
|
||||||
|
drag: 0,
|
||||||
|
pan: 0,
|
||||||
|
select: 2
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export const renderGate = (
|
||||||
) => {
|
) => {
|
||||||
renderPins(ctx, renderer, gate)
|
renderPins(ctx, renderer, gate)
|
||||||
|
|
||||||
if (renderer.selectedGate === gate.id) {
|
if (renderer.selectedGates.has(gate.id)) {
|
||||||
ctx.strokeStyle = renderer.options.gates.gateStroke.active
|
ctx.strokeStyle = renderer.options.gates.gateStroke.active
|
||||||
} else {
|
} else {
|
||||||
ctx.strokeStyle = renderer.options.gates.gateStroke.normal
|
ctx.strokeStyle = renderer.options.gates.gateStroke.normal
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Screen } from '../../core/classes/Screen'
|
||||||
import { clamp } from '../../simulation/helpers/clamp'
|
import { clamp } from '../../simulation/helpers/clamp'
|
||||||
import { Camera } from '../classes/Camera'
|
import { Camera } from '../classes/Camera'
|
||||||
import { vector2 } from '../../../common/math/classes/Transform'
|
import { vector2 } from '../../../common/math/classes/Transform'
|
||||||
import { MouseEventInfo } from '../../core/components/FluidCanvas'
|
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
||||||
// import { WheelEvent } from 'react'
|
// import { WheelEvent } from 'react'
|
||||||
|
|
||||||
const screen = new Screen()
|
const screen = new Screen()
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
|
||||||
|
|
||||||
export const updateSimulation = (
|
|
||||||
renderer: SimulationRenderer,
|
|
||||||
delta: number
|
|
||||||
) => {
|
|
||||||
const selected = renderer.getSelected()
|
|
||||||
|
|
||||||
if (selected && renderer.movedSelection) {
|
|
||||||
renderer.mouseManager.update()
|
|
||||||
selected.transform.rotation =
|
|
||||||
renderer.mouseManager.getDirection() * renderer.options.dnd.rotation
|
|
||||||
} else {
|
|
||||||
if (selected) {
|
|
||||||
selected.transform.rotation = 0
|
|
||||||
}
|
|
||||||
renderer.mouseManager.update()
|
|
||||||
}
|
|
||||||
}
|
|
4
src/modules/simulationRenderer/types/Selection.ts
Normal file
4
src/modules/simulationRenderer/types/Selection.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface Selection {
|
||||||
|
id: number
|
||||||
|
permanent: boolean
|
||||||
|
}
|
Loading…
Reference in a new issue