gate props and fixed the pins not beenig set to false bug

This commit is contained in:
Matei Adriel 2019-07-29 22:51:17 +03:00
parent f06fe88df5
commit 285248435a
33 changed files with 739 additions and 341 deletions

15
.vscode/template.code-snippets vendored Normal file
View file

@ -0,0 +1,15 @@
{
"Gate template": {
"prefix": "template",
"body": [
"import { PartialTemplate } from '../types/PartialTemplate'",
"",
"/**",
" * The template of the ${1:and} gate",
" */",
"const ${1}Template: PartialTemplate = ${2:[]}",
"",
"export default ${1}Template"
]
}
}

View file

@ -4,9 +4,12 @@ import { fromSimulationState } from '../../saving/helpers/fromState'
export interface Context { export interface Context {
memory: Record<string, unknown> memory: Record<string, unknown>
getProperty: (name: string) => unknown
setProperty: (name: string, value: unknown) => void
get: (index: number) => boolean get: (index: number) => boolean
set: (index: number, state: boolean) => void set: (index: number, state: boolean) => void
color: (color: string) => void color: (color: string) => void
update: () => void
enviroment: SimulationEnv enviroment: SimulationEnv
colors: Record<string, string> colors: Record<string, string>
} }

View file

@ -5,7 +5,7 @@ import 'react-toastify/dist/ReactToastify.css'
import { ToastContainer } from 'react-toastify' import { ToastContainer } from 'react-toastify'
import { theme as muiTheme } from '../constants' import { theme as muiTheme } from '../constants'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom' import { Route } from 'react-router-dom'
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import CssBaseline from '@material-ui/core/CssBaseline' import CssBaseline from '@material-ui/core/CssBaseline'
@ -15,6 +15,7 @@ import Head from './Head'
import Root from './Root' import Root from './Root'
import LogicGatePage from '../../logic-gates/components/LogicGatesPage' import LogicGatePage from '../../logic-gates/components/LogicGatesPage'
import { loadSubject } from '../subjects/loadedSubject' import { loadSubject } from '../subjects/loadedSubject'
import { CustomRouter } from './CustomRouter'
const App = () => { const App = () => {
useEffect(() => { useEffect(() => {
@ -27,12 +28,12 @@ const App = () => {
<CssBaseline /> <CssBaseline />
<Theme theme={muiTheme}> <Theme theme={muiTheme}>
<Router> <CustomRouter>
<Sidebar /> <Sidebar />
<Route path="/" component={Root} exact /> <Route path="/" component={Root} exact />
<Route path="/gates" component={LogicGatePage} /> <Route path="/gates" component={LogicGatePage} />
</Router> </CustomRouter>
</Theme> </Theme>
<ToastContainer <ToastContainer

View file

@ -0,0 +1,6 @@
import { BrowserRouter } from 'react-router-dom'
import { history } from '../constants'
export class CustomRouter extends BrowserRouter {
public history = history
}

View file

@ -2,12 +2,14 @@ import React from 'react'
import Canvas from './Canvas' import Canvas from './Canvas'
import CreateSimulation from '../../create-simulation/components/CreateSimulation' import CreateSimulation from '../../create-simulation/components/CreateSimulation'
import Input from '../../input/components/Input' import Input from '../../input/components/Input'
import GateProperties from '../../logic-gates/components/GatePropertiesModal'
const Root = () => { const Root = () => {
return ( return (
<> <>
<Canvas /> <Canvas />
<CreateSimulation /> <CreateSimulation />
<GateProperties />
<Input /> <Input />
</> </>
) )

View file

@ -1,6 +1,7 @@
import { createMuiTheme } from '@material-ui/core/styles' import { createMuiTheme } from '@material-ui/core/styles'
import { red, deepPurple } from '@material-ui/core/colors' import { red, deepPurple } from '@material-ui/core/colors'
import { simulationMode } from '../saving/types/SimulationSave' import { simulationMode } from '../saving/types/SimulationSave'
import { createBrowserHistory } from 'history'
export const theme = createMuiTheme({ export const theme = createMuiTheme({
palette: { palette: {
@ -31,3 +32,8 @@ export const icons: IconInterface = {
* The width of the sidebar * The width of the sidebar
*/ */
export const sidebarWidth = 240 export const sidebarWidth = 240
/**
* The history to be used by the router
*/
export const history = createBrowserHistory()

View file

@ -1,6 +1,6 @@
@import '../colors.scss'; @import '../colors.scss';
.MuiListItem-root.contained { .contained {
// i spent hours trying to find a better solution // i spent hours trying to find a better solution
background-color: $primary !important; background-color: $primary !important;
} }

View file

@ -0,0 +1,53 @@
@import '../../core/styles/mixins/flex.scss';
@import '../../core/styles/colors.scss';
@import '../../core/styles/mixins/visibility.scss';
@import '../../modals/styles/mixins/modal-container.scss';
@import '../../modals/styles/mixins/modal-title.scss';
$gate-props-margin: 1rem;
#gate-properties-modal {
@include modal-container();
justify-content: center;
}
.visible#gate-properties-modal {
@include visible();
}
div #gate-properties-container {
@include flex;
background-color: $grey;
padding: $gate-props-margin * 4;
border-radius: 1em;
}
#gate-props-divider {
margin-top: $gate-props-margin;
margin-bottom: $gate-props-margin;
}
div #gate-props-title {
color: white;
font-size: 3em;
}
div .gate-prop-container {
@include flex();
flex-direction: row;
justify-content: start;
}
div #save-props {
width: 50%;
margin: $gate-props-margin * 2;
margin-bottom: 0;
}
.checkbox-label {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

View file

@ -0,0 +1,138 @@
import './GateProperties.scss'
import React, { ChangeEvent } from 'react'
import { getRendererSafely } from '../helpers/getRendererSafely'
import { Property } from '../../simulation/types/GateTemplate'
import { BehaviorSubject } from 'rxjs'
import { useObservable } from 'rxjs-hooks'
import Divider from '@material-ui/core/Divider'
import TextField from '@material-ui/core/TextField'
import CheckBox from '@material-ui/core/Checkbox'
import { open, id } from '../subjects/LogicGatePropsSubjects'
import { Gate } from '../../simulation/classes/Gate'
export interface GatePropertyProps {
raw: Property
name: string
output: BehaviorSubject<string | number | boolean>
gate: Gate
}
/**
* Renders a single props of the gate
*
* @param param0 The props passed to the component
*/
export const GatePropery = ({ name, raw, output, gate }: GatePropertyProps) => {
const outputSnapshot = useObservable(() => output, '')
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}:`
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const target = e.target as HTMLInputElement
let value: boolean | string | number = target.value
if (raw.type === 'boolean') {
value = target.checked
} else if (raw.type === 'number') {
value = Number(target.value)
}
gate.props[name] = value
if (raw.type !== 'boolean') {
output.next(target.value)
}
if (raw.needsUpdate === true) {
gate.update()
}
}
let input = <></>
if (raw.type === 'number' || raw.type === 'text') {
input = (
<TextField
onChange={handleChange}
label={displayableName}
value={outputSnapshot}
type={raw.type}
/>
)
} else if (raw.type === 'boolean') {
input = (
<>
<span className="checkbox-label">{displayableName}</span>
<CheckBox
onClick={() => {
output.next(!outputSnapshot)
}}
onChange={handleChange}
checked={!!outputSnapshot}
/>{' '}
</>
)
}
return <div className="gate-prop-container">{input}</div>
}
/**
* The props page of any gate wich has props enabled
*
* @param props The react props of the component
*/
const GateProperties = () => {
const openSnapshot = useObservable(() => open, false)
const renderer = getRendererSafely()
const node = renderer.simulation.gates.get(id.value)
if (!(node && node.data && node.data.template.properties.enabled)) {
open.next(false)
return <></>
}
const gate = node.data
const gateProps = gate.props
return (
<div
id="gate-properties-modal"
className={openSnapshot ? 'visible' : ''}
onClick={() => {
open.next(false)
}}
>
<div
id="gate-properties-container"
onClick={e => {
e.stopPropagation()
}}
>
<div id="gate-props-title">Gate properties</div>
<Divider id="gate-props-divider" />
{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 (
<GatePropery
output={output}
raw={raw}
name={name}
gate={gate}
key={`${index}-${id.value}-${output.value}`}
/>
)
})}
</div>
</div>
)
}
export default GateProperties

View file

@ -0,0 +1,4 @@
import { BehaviorSubject, Subject } from 'rxjs'
export const open = new Subject<boolean>()
export const id = new BehaviorSubject<number>(0)

View file

@ -1,292 +1,32 @@
import { GateTemplate } from '../simulation/types/GateTemplate' import { GateTemplate } from '../simulation/types/GateTemplate'
import { RendererState } from './types/SimulationSave' import { RendererState } from './types/SimulationSave'
import andTemplate from './templates/and'
import buttonTemplate from './templates/button'
import lightTemplate from './templates/light'
import nandTemplate from './templates/nand'
import norTemplate from './templates/nor'
import notTemplate from './templates/not'
import orTemplate from './templates/or'
import parallelDelayerTemplate from './templates/parallelDelayer'
import rgbLightTemplate from './templates/rgb'
import sequentialDelayerTemplate from './templates/sequentialDelayer'
import xnorTemplate from './templates/xnor'
import xorTemplate from './templates/xor'
export const defaultSimulationName = 'default' export const defaultSimulationName = 'default'
export const baseTemplates: DeepPartial<GateTemplate>[] = [ export const baseTemplates: DeepPartial<GateTemplate>[] = [
{ andTemplate,
metadata: { buttonTemplate,
name: 'and' lightTemplate,
}, nandTemplate,
material: { norTemplate,
type: 'image', notTemplate,
fill: require('../../assets/and') orTemplate,
}, parallelDelayerTemplate,
code: { rgbLightTemplate,
activation: `context.set(0, context.get(0) && context.get(1))` sequentialDelayerTemplate,
}, xnorTemplate,
pins: { xorTemplate
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/AND_gate']
},
{
metadata: {
name: 'nand'
},
material: {
type: 'image',
fill: require('../../assets/nand')
},
code: {
activation: `context.set(0, !context.get(0) || !context.get(1))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/NAND_gate']
},
{
metadata: {
name: 'or'
},
material: {
type: 'image',
fill: require('../../assets/or')
},
code: {
activation: `context.set(0, context.get(0) || context.get(1))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/OR_gate']
},
{
metadata: {
name: 'nor'
},
material: {
type: 'image',
fill: require('../../assets/nor')
},
code: {
activation: `context.set(0, !(context.get(0) || context.get(1)))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/NOR_gate']
},
{
metadata: {
name: 'xor'
},
material: {
type: 'image',
fill: require('../../assets/xor')
},
code: {
activation: `
const a = context.get(0)
const b = context.get(1)
const c = (a || b) && (!a || !b)
context.set(0, c)`
},
info: ['https://en.wikipedia.org/wiki/XOR_gate'],
pins: {
inputs: {
count: 2
}
}
},
{
metadata: {
name: 'xnor'
},
material: {
type: 'image',
fill: require('../../assets/xnor')
},
code: {
activation: `
const a = context.get(0)
const b = context.get(1)
const c = (a && b) || !(a || b)
context.set(0, c)`
},
info: ['https://en.wikipedia.org/wiki/XNOR_gate'],
pins: {
inputs: {
count: 2
}
}
},
{
metadata: {
name: 'not'
},
material: {
type: 'image',
fill: require('../../assets/not')
},
code: {
activation: `context.set(0, !context.get(0))`
},
info: ['https://en.wikipedia.org/wiki/Inverter_(logic_gate)']
},
{
metadata: {
name: 'button'
},
material: {
fill: '#D32F2E',
stroke: {
normal: '#AB8C31',
active: '#7EF813'
},
colors: {
pressed: '#7D1313'
}
},
code: {
onClick: `
const old = context.memory.state
const state = !old
context.set(0, state)
context.color(old ? context.colors.main : context.colors.pressed)
context.memory.state = state
`
},
pins: {
inputs: {
count: 0
}
},
integration: {
input: true
},
info: ['https://en.wikipedia.org/wiki/Push-button']
},
{
metadata: {
name: 'light bulb'
},
shape: {
radius: 50
},
material: {
fill: '#1C1C1C',
stroke: {
normal: '#3C3C3C'
},
colors: {
active: '#C6FF00'
}
},
code: {
activation: `
const { main, active } = context.colors
context.color(context.get(0) ? active : main)
`
},
integration: {
output: true
},
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
pins: {
outputs: {
count: 0
}
}
},
{
metadata: {
name: 'rgb light'
},
material: {
fill: '#1C1C1C',
colors: {
1: '#00f',
2: `#0f0`,
3: `#0ff`,
4: `#f00`,
5: `#f0f`,
6: `#ff0`,
7: `#fff`
}
},
code: {
activation: `
const color = (context.get(0) << 2) + (context.get(1) << 1) + context.get(2)
if (color === 0){
context.color(context.colors.main)
}
else{
context.color(context.colors[color])
}
`
},
integration: {
output: true
},
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
pins: {
outputs: {
count: 0
},
inputs: {
count: 3
}
}
},
{
metadata: {
name: 'Sequential delayer'
},
material: {
type: 'image',
fill: require('../../assets/sequential')
},
code: {
activation: `
const i = context.get(0)
return new Promise((res, rej) => {
setTimeout(() => {
res()
},1000)
}).then(() => {
context.set(0,i)
})
`,
async: true
}
},
{
metadata: {
name: 'Parallel delayer'
},
material: {
type: 'image',
fill: require('../../assets/parallel')
},
code: {
activation: `
const i = context.get(0)
return new Promise((res, rej) => {
setTimeout(() => {
res()
},1000)
}).then(() => {
context.set(0,i)
})
`
}
}
] ]
export const baseSave: RendererState = { export const baseSave: RendererState = {

View file

@ -0,0 +1,12 @@
import { GateTemplate } from '../../simulation/types/GateTemplate'
export const delayProperties: GateTemplate['properties'] = {
enabled: true,
data: [
{
base: 1000,
type: 'number',
name: 'delay'
}
]
}

View file

@ -35,7 +35,8 @@ export const fromSimulationState = (
for (const gateState of state.gates) { for (const gateState of state.gates) {
const gate = new Gate( const gate = new Gate(
templateStore.get(gateState.template), templateStore.get(gateState.template),
gateState.id gateState.id,
gateState.props
) )
gate.transform = fromTransformState(gateState.transform) gate.transform = fromTransformState(gateState.transform)

View file

@ -0,0 +1,25 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the and gate
*/
const andTemplate: PartialTemplate = {
metadata: {
name: 'and'
},
material: {
type: 'image',
fill: require('../../../assets/and')
},
code: {
activation: `context.set(0, context.get(0) && context.get(1))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/AND_gate']
}
export default andTemplate

View file

@ -0,0 +1,56 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the button gate
*/
const buttonTemplate: PartialTemplate = {
metadata: {
name: 'button'
},
material: {
fill: '#D32F2E',
stroke: {
normal: '#AB8C31',
active: '#7EF813'
},
colors: {
pressed: '#7D1313'
}
},
code: {
onClick: `
const active = context.getProperty('active')
context.setProperty('active',!active)
context.update()
`,
activation: `
const state = context.getProperty('active')
context.set(0, state)
context.color(!state ? context.colors.main : context.colors.pressed)
`
},
pins: {
inputs: {
count: 0
}
},
integration: {
input: true
},
info: ['https://en.wikipedia.org/wiki/Push-button'],
properties: {
enabled: true,
data: [
{
base: false,
name: 'active',
type: 'boolean',
needsUpdate: true
}
]
}
}
export default buttonTemplate

View file

@ -0,0 +1,40 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the light gate
*/
const lightTemplate: PartialTemplate = {
metadata: {
name: 'light bulb'
},
shape: {
radius: 50
},
material: {
fill: '#1C1C1C',
stroke: {
normal: '#3C3C3C'
},
colors: {
active: '#C6FF00'
}
},
code: {
activation: `
const { main, active } = context.colors
context.color(context.get(0) ? active : main)
`
},
integration: {
output: true
},
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
pins: {
outputs: {
count: 0
}
}
}
export default lightTemplate

View file

@ -0,0 +1,25 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the nand gate
*/
const nandTemplate: PartialTemplate = {
metadata: {
name: 'nand'
},
material: {
type: 'image',
fill: require('../../../assets/nand')
},
code: {
activation: `context.set(0, !context.get(0) || !context.get(1))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/NAND_gate']
}
export default nandTemplate

View file

@ -0,0 +1,25 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the nor gate
*/
const norTemplate: PartialTemplate = {
metadata: {
name: 'nor'
},
material: {
type: 'image',
fill: require('../../../assets/nor')
},
code: {
activation: `context.set(0, !(context.get(0) || context.get(1)))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/NOR_gate']
}
export default norTemplate

View file

@ -0,0 +1,20 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the not gate
*/
const notTemplate: PartialTemplate = {
metadata: {
name: 'not'
},
material: {
type: 'image',
fill: require('../../../assets/not')
},
code: {
activation: `context.set(0, !context.get(0))`
},
info: ['https://en.wikipedia.org/wiki/Inverter_(logic_gate)']
}
export default notTemplate

View file

@ -0,0 +1,25 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the or gate
*/
const orTemplate: PartialTemplate = {
metadata: {
name: 'or'
},
material: {
type: 'image',
fill: require('../../../assets/or')
},
code: {
activation: `context.set(0, context.get(0) || context.get(1))`
},
pins: {
inputs: {
count: 2
}
},
info: ['https://en.wikipedia.org/wiki/OR_gate']
}
export default orTemplate

View file

@ -0,0 +1,30 @@
import { PartialTemplate } from '../types/PartialTemplate'
import { delayProperties } from '../data/delayProperties'
/**
* The template of the parallelDelayer gate
*/
const parallelDelayerTemplate: PartialTemplate = {
metadata: {
name: 'Parallel delayer'
},
material: {
type: 'image',
fill: require('../../../assets/parallel')
},
code: {
activation: `
const i = context.get(0)
return new Promise((res, rej) => {
setTimeout(() => {
res()
}, context.getProperty('delay'))
}).then(() => {
context.set(0,i)
})
`
},
properties: delayProperties
}
export default parallelDelayerTemplate

View file

@ -0,0 +1,49 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the rgbLight gate
*/
const rgbLightTemplate: PartialTemplate = {
metadata: {
name: 'rgb light'
},
material: {
fill: '#1C1C1C',
colors: {
1: '#00f',
2: `#0f0`,
3: `#0ff`,
4: `#f00`,
5: `#f0f`,
6: `#ff0`,
7: `#fff`
}
},
code: {
activation: `
const color = (context.get(0) << 2) + (context.get(1) << 1) + context.get(2)
if (color === 0){
context.color(context.colors.main)
}
else{
context.color(context.colors[color])
}
`
},
integration: {
output: true
},
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
pins: {
outputs: {
count: 0
},
inputs: {
count: 3
}
}
}
export default rgbLightTemplate

View file

@ -0,0 +1,31 @@
import { PartialTemplate } from '../types/PartialTemplate'
import { delayProperties } from '../data/delayProperties'
/**
* The template of the sequentialDelayer gate
*/
const sequentialDelayerTemplate: PartialTemplate = {
metadata: {
name: 'Sequential delayer'
},
material: {
type: 'image',
fill: require('../../../assets/sequential')
},
code: {
activation: `
const i = context.get(0)
return new Promise((res, rej) => {
setTimeout(() => {
res()
},context.getProperty('delay'))
}).then(() => {
context.set(0,i)
})
`,
async: true
},
properties: delayProperties
}
export default sequentialDelayerTemplate

View file

@ -0,0 +1,30 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the xnor gate
*/
const xnorTemplate: PartialTemplate = {
metadata: {
name: 'xnor'
},
material: {
type: 'image',
fill: require('../../../assets/xnor')
},
code: {
activation: `
const a = context.get(0)
const b = context.get(1)
const c = (a && b) || !(a || b)
context.set(0, c)`
},
info: ['https://en.wikipedia.org/wiki/XNOR_gate'],
pins: {
inputs: {
count: 2
}
}
}
export default xnorTemplate

View file

@ -0,0 +1,30 @@
import { PartialTemplate } from '../types/PartialTemplate'
/**
* The template of the xor gate
*/
const xorTemplate: PartialTemplate = {
metadata: {
name: 'xor'
},
material: {
type: 'image',
fill: require('../../../assets/xor')
},
code: {
activation: `
const a = context.get(0)
const b = context.get(1)
const c = (a || b) && (!a || !b)
context.set(0, c)`
},
info: ['https://en.wikipedia.org/wiki/XOR_gate'],
pins: {
inputs: {
count: 2
}
}
}
export default xorTemplate

View file

@ -0,0 +1,6 @@
import { GateTemplate } from '../../simulation/types/GateTemplate'
/**
* Deep partial of a gate template
*/
export type PartialTemplate = DeepPartial<GateTemplate>

View file

@ -12,7 +12,7 @@ export interface GateState {
transform: TransformState transform: TransformState
id: number id: number
template: string template: string
props: Record<string, unknown> props: Record<string, string | number | boolean>
} }
export interface CameraState { export interface CameraState {

View file

@ -118,7 +118,7 @@ export class Gate {
/** /**
* The props used by the activation function (the same as memory but presists) * The props used by the activation function (the same as memory but presists)
*/ */
public props: Record<string, unknown> = {} public props: Record<string, string | number | boolean> = {}
/** /**
* The main logic gate class * The main logic gate class
@ -129,7 +129,7 @@ export class Gate {
public constructor( public constructor(
template: DeepPartial<GateTemplate> = {}, template: DeepPartial<GateTemplate> = {},
id?: number, id?: number,
props: Record<string, unknown> = {} props: Record<string, string> = {}
) { ) {
this.template = completeTemplate(template) this.template = completeTemplate(template)
@ -259,17 +259,25 @@ export class Gate {
/** /**
* Assign the props passed to the gate and mere them with the base ones * Assign the props passed to the gate and mere them with the base ones
*/ */
private assignProps(props: Record<string, unknown>) { private assignProps(props: Record<string, string>) {
let shouldUpdate = false
if (this.template.properties.enabled) { if (this.template.properties.enabled) {
for (const prop in this.template.properties) { for (const { base, name, needsUpdate } of this.template.properties
if (prop !== 'enabled') { .data) {
this.props[prop] = this.props[name] = props.hasOwnProperty(name)
props[prop] !== undefined ? props[name]
? props[prop] : base
: this.template.properties[prop].base
if (!shouldUpdate && needsUpdate) {
shouldUpdate = true
} }
} }
} }
if (shouldUpdate) {
this.update()
}
} }
/** /**
@ -335,13 +343,22 @@ export class Gate {
set: (index: number, state: boolean = false) => { set: (index: number, state: boolean = false) => {
return this._pins.outputs[index].state.next(state) return this._pins.outputs[index].state.next(state)
}, },
memory: this.memory,
color: (color: string) => { color: (color: string) => {
if (this.template.material.type === 'color') { if (this.template.material.type === 'color') {
this.template.material.fill = color this.template.material.fill = color
} }
}, },
getProperty: (name: string) => {
return this.props[name]
},
setProperty: (name: string, value: string | number | boolean) => {
this.props[name] = value
},
update: () => {
this.update()
},
enviroment: this.env, enviroment: this.env,
memory: this.memory,
colors: { colors: {
...this.template.material.colors ...this.template.material.colors
} }

View file

@ -30,6 +30,7 @@ export class Wire {
public dispose() { public dispose() {
this.end.value.removePair(this.start.value) this.end.value.removePair(this.start.value)
this.start.value.removePair(this.end.value) this.start.value.removePair(this.end.value)
this.end.value.state.next(false)
this.active = false this.active = false
} }

View file

@ -51,6 +51,7 @@ export const DefaultGateTemplate: GateTemplate = {
info: [], info: [],
tags: ['base'], tags: ['base'],
properties: { properties: {
enabled: false enabled: false,
data: []
} }
} }

View file

@ -1,15 +1,17 @@
import { vector2 } from '../../../common/math/classes/Transform' import { vector2 } from '../../../common/math/classes/Transform'
import { InputHTMLAttributes } from 'react'
export interface PinCount { export interface PinCount {
variable: boolean variable: boolean
count: number count: number
} }
export interface Property<T> { export interface Property<
type: HTMLInputElement['type'] T extends boolean | number | string = boolean | number | string
encode: (value: string) => T > {
type: 'number' | 'string' | 'text' | 'boolean'
base: T base: T
name: string
needsUpdate?: boolean
} }
export interface Material { export interface Material {
@ -69,5 +71,8 @@ export interface GateTemplate {
} }
info: string[] info: string[]
tags: GateTag[] tags: GateTag[]
properties: Enabled<Record<Exclude<string, 'enabled'>, Property<unknown>>> properties: {
enabled: boolean
data: Property[]
}
} }

View file

@ -35,6 +35,10 @@ import { gatesInSelection } from '../helpers/gatesInSelection'
import { selectionType } from '../types/selectionType' import { selectionType } from '../types/selectionType'
import { addIdToSelection, idIsSelected } from '../helpers/idIsSelected' import { addIdToSelection, idIsSelected } from '../helpers/idIsSelected'
import { GateInitter } from '../types/GateInitter' import { GateInitter } from '../types/GateInitter'
import {
open as propsAreOpen,
id as propsModalId
} from '../../logic-gates/subjects/LogicGatePropsSubjects'
export class SimulationRenderer { export class SimulationRenderer {
public mouseDownOutput = new Subject<MouseEventInfo>() public mouseDownOutput = new Subject<MouseEventInfo>()
@ -89,29 +93,34 @@ export class SimulationRenderer {
// because if we have 2 overlapping gates, // because if we have 2 overlapping gates,
// we want to select the one on top // we want to select the one on top
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, template } = gates[index]
if ( if (pointInSquare(worldPosition, transform)) {
event.button === mouseButtons.drag && if (event.button === mouseButtons.drag) {
pointInSquare(worldPosition, transform) gates[index].onClick()
) {
gates[index].onClick()
this.mouseState |= 1 this.mouseState |= 1
if (!idIsSelected(this, id)) { if (!idIsSelected(this, id)) {
this.clearSelection() this.clearSelection()
addIdToSelection(this, 'temporary', id) addIdToSelection(this, 'temporary', id)
} }
const gateNode = this.simulation.gates.get(id) const gateNode = this.simulation.gates.get(id)
if (gateNode) { if (gateNode) {
return this.simulation.gates.moveOnTop(gateNode) return this.simulation.gates.moveOnTop(gateNode)
} else { } else {
throw new SimulationError( throw new SimulationError(
`Cannot find gate with id ${id}` `Cannot find gate with id ${id}`
) )
}
} else if (
event.button === mouseButtons.properties &&
template.properties.enabled
) {
propsModalId.next(id)
return propsAreOpen.next(true)
} }
} }
@ -313,21 +322,15 @@ export class SimulationRenderer {
} }
public reloadSave() { public reloadSave() {
try { dumpSimulation(this)
dumpSimulation(this)
const current = currentStore.get() const current = currentStore.get()
const save = saveStore.get(current) const save = saveStore.get(current)
if (!save) return if (!save) return
if (!(save.simulation || save.camera)) return if (!(save.simulation || save.camera)) return
this.loadSave(save) this.loadSave(save)
} catch (e) {
throw new Error(
`An error occured while loading the save: ${e as Error}`
)
}
} }
/** /**

View file

@ -37,15 +37,13 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
export const imageQuality: vector2 = [100, 100] export const imageQuality: vector2 = [100, 100]
export const mouseButtons: Record< export const mouseButtons = {
'zoom' | 'pan' | 'drag' | 'select' | 'unselect',
mouseButton
> = {
zoom: 1, zoom: 1,
drag: 0, drag: 0,
pan: 2, pan: 2,
select: 0, select: 0,
unselect: 0 unselect: 0,
properties: 2
} }
export const shiftInput = new KeyboardInput('shift') export const shiftInput = new KeyboardInput('shift')