Add support for constants
This commit is contained in:
parent
7d9d2a2d78
commit
96e2184a24
6
build.js
6
build.js
|
@ -43,7 +43,11 @@ const ctx = await esbuild.context({
|
||||||
})
|
})
|
||||||
|
|
||||||
if (serve) {
|
if (serve) {
|
||||||
const { port, host } = await ctx.serve({ servedir: 'dist' })
|
await ctx.watch()
|
||||||
|
const { port, host } = await ctx.serve({
|
||||||
|
servedir: 'dist',
|
||||||
|
fallback: 'dist/index.html'
|
||||||
|
})
|
||||||
console.log(`Serving on ${host}:${port}`)
|
console.log(`Serving on ${host}:${port}`)
|
||||||
} else {
|
} else {
|
||||||
await ctx.rebuild()
|
await ctx.rebuild()
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"dev": "ESBUILD_SERVE=1 node ./build.js",
|
||||||
"build": "node ./build.js",
|
"build": "node ./build.js",
|
||||||
"check": "tsc"
|
"check": "tsc"
|
||||||
},
|
},
|
||||||
|
|
52
src/index.ts
52
src/index.ts
|
@ -4,37 +4,39 @@ import { Splash } from './modules/splash/classes/Splash'
|
||||||
* The function wich is run when the app is loaded
|
* The function wich is run when the app is loaded
|
||||||
*/
|
*/
|
||||||
async function main() {
|
async function main() {
|
||||||
// Create splash screen variable
|
// Create splash screen variable
|
||||||
let splash: Splash | undefined = undefined
|
let splash: Splash | undefined = undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// instantiate splash screen
|
// instantiate splash screen
|
||||||
splash = new Splash()
|
splash = new Splash()
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// import main app
|
// import main app
|
||||||
const app = await import('./main')
|
const app = await import('./main')
|
||||||
|
|
||||||
// wait for app to start
|
// wait for app to start
|
||||||
await app.start()
|
await app.start()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// show the error to the client
|
// show the error to the client
|
||||||
if (splash) splash.setError(error)
|
if (splash) splash.setError(error)
|
||||||
|
|
||||||
// log the error to the console
|
// log the error to the console
|
||||||
console.error(error.stack || error)
|
console.error(error.stack || error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide splash screen if it exists
|
// hide splash screen if it exists
|
||||||
if (splash) {
|
if (splash) {
|
||||||
splash.fade()
|
splash.fade()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
new EventSource('/esbuild').addEventListener('change', () => location.reload())
|
||||||
|
|
||||||
// Call entry
|
// Call entry
|
||||||
main().catch(error => {
|
main().catch((error) => {
|
||||||
// if the error handling error has an error, log that error
|
// if the error handling error has an error, log that error
|
||||||
console.error('Error loading app', error)
|
console.error('Error loading app', error)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation'
|
import { SimulationEnv } from '../../simulation/classes/Simulation'
|
||||||
import { PinState } from '../../simulation/classes/Pin'
|
import { PinState } from '../../simulation/classes/Pin'
|
||||||
|
|
||||||
export interface Context {
|
export interface Context {
|
||||||
getProperty: (name: string) => unknown
|
getProperty: (name: string) => unknown
|
||||||
setProperty: (name: string, value: unknown) => void
|
setProperty: (name: string, value: unknown) => void
|
||||||
get: (index: number) => PinState
|
get: (index: number) => PinState
|
||||||
set: (index: number, state: PinState) => void
|
set: (index: number, state: PinState) => void
|
||||||
getBinary: (index: number) => number
|
getOutput: (index: number) => PinState
|
||||||
setBinary: (index: number, value: number, bits: number) => void
|
getBinary: (index: number) => number
|
||||||
invertBinary: (value: number) => number
|
printBinary: (value: number, bits?: number) => string
|
||||||
color: (color: string) => void
|
printHex: (value: number, length?: number) => string
|
||||||
innerText: (value: string) => void
|
setBinary: (index: number, value: number, bits?: number) => void
|
||||||
update: () => void
|
getOutputBinary: (index: number) => number
|
||||||
toLength: (value: number | PinState, length: number) => string
|
invertBinary: (value: number) => number
|
||||||
maxLength: number
|
color: (color: string) => void
|
||||||
enviroment: SimulationEnv
|
innerText: (value: string) => void
|
||||||
colors: Record<string, string>
|
update: () => void
|
||||||
memory: Record<string, unknown>
|
toLength: (value: number | PinState, length: number) => string
|
||||||
|
maxLength: number
|
||||||
|
enviroment: SimulationEnv
|
||||||
|
colors: Record<string, string>
|
||||||
|
memory: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InitialisationContext {
|
export interface InitialisationContext {
|
||||||
memory: Record<string, unknown>
|
memory: Record<string, unknown>
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,60 +7,64 @@
|
||||||
$gate-props-margin: 1rem;
|
$gate-props-margin: 1rem;
|
||||||
|
|
||||||
#gate-properties-modal {
|
#gate-properties-modal {
|
||||||
@include modal-container();
|
@include modal-container();
|
||||||
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visible#gate-properties-modal {
|
.visible#gate-properties-modal {
|
||||||
@include visible();
|
@include visible();
|
||||||
}
|
}
|
||||||
|
|
||||||
div #gate-properties-container {
|
div #gate-properties-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
background-color: $grey;
|
background-color: $grey;
|
||||||
border-radius: 1em;
|
border-radius: 1em;
|
||||||
|
|
||||||
padding: $gate-props-margin * 4;
|
padding: $gate-props-margin * 4;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
div #gate-props-title {
|
div #gate-props-title {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
|
|
||||||
margin-bottom: 2 * $gate-props-margin;
|
margin-bottom: 2 * $gate-props-margin;
|
||||||
}
|
}
|
||||||
|
|
||||||
div .gate-prop-container {
|
div .gate-prop-container {
|
||||||
@include flex();
|
@include flex();
|
||||||
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
|
|
||||||
&.visible {
|
&.visible {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&>* {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div .gate-prop-group-container {
|
div .gate-prop-group-container {
|
||||||
@include flex;
|
@include flex;
|
||||||
|
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
div #save-props {
|
div #save-props {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin: $gate-props-margin * 2;
|
margin: $gate-props-margin * 2;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-label {
|
.checkbox-label {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import './GateProperties.scss'
|
import './GateProperties.scss'
|
||||||
import React, { ChangeEvent } from 'react'
|
import { ChangeEvent } from 'react'
|
||||||
import { getRendererSafely } from '../helpers/getRendererSafely'
|
import { getRendererSafely } from '../helpers/getRendererSafely'
|
||||||
import { Property, RawProp, isGroup } from '../../simulation/types/GateTemplate'
|
import { Property, RawProp, isGroup } from '../../simulation/types/GateTemplate'
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import Divider from '@material-ui/core/Divider'
|
|
||||||
import TextField from '@material-ui/core/TextField'
|
import TextField from '@material-ui/core/TextField'
|
||||||
import CheckBox from '@material-ui/core/Checkbox'
|
import CheckBox from '@material-ui/core/Checkbox'
|
||||||
import { open, id } from '../subjects/LogicGatePropsSubjects'
|
import { open, id } from '../subjects/LogicGatePropsSubjects'
|
||||||
|
@ -16,41 +15,41 @@ import Typography from '@material-ui/core/Typography'
|
||||||
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
|
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
|
||||||
import Icon from '@material-ui/core/Icon'
|
import Icon from '@material-ui/core/Icon'
|
||||||
interface GatePropertyProps<T extends Property = Property> {
|
interface GatePropertyProps<T extends Property = Property> {
|
||||||
raw: T
|
raw: T
|
||||||
gate: Gate
|
gate: Gate
|
||||||
props: GateProps
|
props: GateProps
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyInput = <></>
|
const emptyInput = <></>
|
||||||
|
|
||||||
const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
|
const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
|
||||||
if (isGroup(raw)) {
|
if (isGroup(raw)) {
|
||||||
return (
|
return (
|
||||||
<ExpansionPanel>
|
<ExpansionPanel>
|
||||||
<ExpansionPanelSummary
|
<ExpansionPanelSummary
|
||||||
expandIcon={<Icon> expand_more</Icon>}
|
expandIcon={<Icon> expand_more</Icon>}
|
||||||
aria-controls={raw.groupName}
|
aria-controls={raw.groupName}
|
||||||
id={raw.groupName}
|
id={raw.groupName}
|
||||||
>
|
>
|
||||||
<Typography>{raw.groupName}</Typography>
|
<Typography>{raw.groupName}</Typography>
|
||||||
</ExpansionPanelSummary>
|
</ExpansionPanelSummary>
|
||||||
<ExpansionPanelDetails>
|
<ExpansionPanelDetails>
|
||||||
<div className="gate-prop-group-container">
|
<div className="gate-prop-group-container">
|
||||||
{raw.props.map((propTemplate, index) => (
|
{raw.props.map((propTemplate, index) => (
|
||||||
<GateProperty
|
<GateProperty
|
||||||
key={index}
|
key={index}
|
||||||
raw={propTemplate}
|
raw={propTemplate}
|
||||||
gate={gate}
|
gate={gate}
|
||||||
props={props[raw.groupName] as GateProps}
|
props={props[raw.groupName] as GateProps}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</ExpansionPanelDetails>
|
</ExpansionPanelDetails>
|
||||||
</ExpansionPanel>
|
</ExpansionPanel>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return <GateRawProperty raw={raw} gate={gate} props={props} />
|
return <GateRawProperty raw={raw} gate={gate} props={props} />
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,99 +58,90 @@ const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
|
||||||
* @param param0 The props passed to the component
|
* @param param0 The props passed to the component
|
||||||
*/
|
*/
|
||||||
const GateRawProperty = ({
|
const GateRawProperty = ({
|
||||||
props,
|
props,
|
||||||
raw,
|
raw,
|
||||||
gate
|
gate
|
||||||
}: GatePropertyProps & { raw: RawProp }) => {
|
}: GatePropertyProps & { raw: RawProp }) => {
|
||||||
const { name } = raw
|
const { name } = raw
|
||||||
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
|
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
|
||||||
const outputSnapshot = useObservable(() => prop, '')
|
const outputSnapshot = useObservable(() => prop, '')
|
||||||
|
|
||||||
// rerender when the external checkbox changes
|
// rerender when the external checkbox changes
|
||||||
const external = useObservable(
|
const external = useObservable(
|
||||||
() =>
|
() =>
|
||||||
gate.props.external.pipe(
|
gate.props.external.pipe(map((value) => value && name !== 'external')),
|
||||||
map((value) => value && name !== 'external')
|
false
|
||||||
),
|
)
|
||||||
false
|
|
||||||
)
|
|
||||||
|
|
||||||
const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
|
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}${
|
||||||
external && name !== 'label' ? '(default value)' : ''
|
external && name !== 'label' ? ' (default value)' : ''
|
||||||
}:`
|
}${raw.description == undefined ? '' : ` ${raw.description}`}:`
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
const target = e.target as HTMLInputElement
|
const target = e.target as HTMLInputElement
|
||||||
let value: boolean | string | number = target.value
|
let value: boolean | string | number = target.value
|
||||||
|
|
||||||
if (raw.type === 'boolean') {
|
if (raw.type === 'boolean') {
|
||||||
value = target.checked
|
value = target.checked
|
||||||
} else if (raw.type === 'number') {
|
} else if (raw.type === 'number') {
|
||||||
value = Number(target.value)
|
value = Number(target.value)
|
||||||
}
|
|
||||||
|
|
||||||
if (raw.type !== 'boolean') {
|
|
||||||
prop.next(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let input = (() => {
|
if (raw.type !== 'boolean') {
|
||||||
const root = gate.props[name] === prop
|
prop.next(value)
|
||||||
const renderer = getRendererSafely()
|
}
|
||||||
const displayExternal = () =>
|
}
|
||||||
renderer.simulation.mode === 'ic' &&
|
|
||||||
root &&
|
|
||||||
!gate.template.properties.data.some(
|
|
||||||
(prop) => (prop as RawProp).needsUpdate
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
let input = (() => {
|
||||||
(raw.name === 'external' && !displayExternal()) ||
|
const root = gate.props[name] === prop
|
||||||
(raw.name === 'label' && (!external || !root))
|
const renderer = getRendererSafely()
|
||||||
) {
|
const displayExternal = () =>
|
||||||
return emptyInput
|
renderer.simulation.mode === 'ic' &&
|
||||||
}
|
root &&
|
||||||
|
!gate.template.properties.data.some(
|
||||||
|
(prop) => (prop as RawProp).needsUpdate
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
raw.type === 'number' ||
|
(raw.name === 'external' && !displayExternal()) ||
|
||||||
raw.type === 'text' ||
|
(raw.name === 'label' && (!external || !root))
|
||||||
raw.type === 'string'
|
) {
|
||||||
) {
|
return emptyInput
|
||||||
return (
|
}
|
||||||
<TextField
|
|
||||||
onChange={handleChange}
|
|
||||||
label={displayableName}
|
|
||||||
value={outputSnapshot}
|
|
||||||
type={raw.type}
|
|
||||||
multiline={raw.type === 'string'}
|
|
||||||
rowsMax={7}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
} else if (raw.type === 'boolean') {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<span className="checkbox-label">{displayableName}</span>
|
|
||||||
<CheckBox
|
|
||||||
onClick={() => {
|
|
||||||
prop.next(!outputSnapshot)
|
|
||||||
}}
|
|
||||||
onChange={handleChange}
|
|
||||||
checked={!!outputSnapshot}
|
|
||||||
/>{' '}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return (
|
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
|
||||||
<div
|
return (
|
||||||
className={`gate-prop-container ${
|
<TextField
|
||||||
input !== emptyInput ? 'visible' : ''
|
onChange={handleChange}
|
||||||
}`}
|
label={displayableName}
|
||||||
>
|
value={outputSnapshot}
|
||||||
{input}
|
type={raw.type}
|
||||||
</div>
|
multiline={raw.type === 'string'}
|
||||||
)
|
/>
|
||||||
|
)
|
||||||
|
} else if (raw.type === 'boolean') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<span className="checkbox-label">{displayableName}</span>
|
||||||
|
<CheckBox
|
||||||
|
onClick={() => {
|
||||||
|
prop.next(!outputSnapshot)
|
||||||
|
}}
|
||||||
|
onChange={handleChange}
|
||||||
|
checked={!!outputSnapshot}
|
||||||
|
/>{' '}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`gate-prop-container ${input !== emptyInput ? 'visible' : ''}`}
|
||||||
|
>
|
||||||
|
{input}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,47 +150,47 @@ const GateRawProperty = ({
|
||||||
* @param props The react props of the component
|
* @param props The react props of the component
|
||||||
*/
|
*/
|
||||||
const GateProperties = () => {
|
const GateProperties = () => {
|
||||||
const openSnapshot = useObservable(() => open, false)
|
const openSnapshot = useObservable(() => open, false)
|
||||||
const renderer = getRendererSafely()
|
const renderer = getRendererSafely()
|
||||||
|
|
||||||
const node = renderer.simulation.gates.get(id.value)
|
const node = renderer.simulation.gates.get(id.value)
|
||||||
|
|
||||||
if (!(node && node.data && node.data.template.properties.enabled)) {
|
if (!(node && node.data && node.data.template.properties.enabled)) {
|
||||||
|
open.next(false)
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const gate = node.data
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
id="gate-properties-modal"
|
||||||
|
className={openSnapshot ? 'visible' : ''}
|
||||||
|
onClick={() => {
|
||||||
open.next(false)
|
open.next(false)
|
||||||
return <></>
|
}}
|
||||||
}
|
>
|
||||||
|
<div
|
||||||
|
id="gate-properties-container"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div id="gate-props-title">Gate properties</div>
|
||||||
|
|
||||||
const gate = node.data
|
{gate.template.properties.data.map((prop, index) => {
|
||||||
|
return (
|
||||||
return (
|
<GateProperty
|
||||||
<div
|
props={gate.props}
|
||||||
id="gate-properties-modal"
|
raw={prop}
|
||||||
className={openSnapshot ? 'visible' : ''}
|
gate={gate}
|
||||||
onClick={() => {
|
key={`${index}-${id.value}`}
|
||||||
open.next(false)
|
/>
|
||||||
}}
|
)
|
||||||
>
|
})}
|
||||||
<div
|
</div>
|
||||||
id="gate-properties-container"
|
</div>
|
||||||
onClick={(e) => {
|
)
|
||||||
e.stopPropagation()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div id="gate-props-title">Gate properties</div>
|
|
||||||
|
|
||||||
{gate.template.properties.data.map((prop, index) => {
|
|
||||||
return (
|
|
||||||
<GateProperty
|
|
||||||
props={gate.props}
|
|
||||||
raw={prop}
|
|
||||||
gate={gate}
|
|
||||||
key={`${index}-${id.value}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GateProperties
|
export default GateProperties
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { GateTemplate } from '../../simulation/types/GateTemplate'
|
||||||
import { DefaultGateTemplate } from '../../simulation/constants'
|
import { DefaultGateTemplate } from '../../simulation/constants'
|
||||||
|
|
||||||
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
|
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
|
||||||
return merge(DefaultGateTemplate, template, {
|
return merge(DefaultGateTemplate, template, {
|
||||||
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
|
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
|
||||||
}) as GateTemplate
|
}) as GateTemplate
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,43 +20,45 @@ import comparatorTemplate from './templates/comparator'
|
||||||
import bitMergerTemplate from './templates/bitMerger'
|
import bitMergerTemplate from './templates/bitMerger'
|
||||||
import bitSplitterTemplate from './templates/bitSplitter'
|
import bitSplitterTemplate from './templates/bitSplitter'
|
||||||
import incrementorTemplate from './templates/incrementor'
|
import incrementorTemplate from './templates/incrementor'
|
||||||
|
import constantTemplate from './templates/constant'
|
||||||
|
|
||||||
export const defaultSimulationName = 'default'
|
export const defaultSimulationName = 'default'
|
||||||
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
andTemplate,
|
andTemplate,
|
||||||
buttonTemplate,
|
buttonTemplate,
|
||||||
lightTemplate,
|
lightTemplate,
|
||||||
nandTemplate,
|
nandTemplate,
|
||||||
norTemplate,
|
norTemplate,
|
||||||
notTemplate,
|
notTemplate,
|
||||||
orTemplate,
|
orTemplate,
|
||||||
parallelDelayerTemplate,
|
parallelDelayerTemplate,
|
||||||
rgbLightTemplate,
|
rgbLightTemplate,
|
||||||
sequentialDelayerTemplate,
|
sequentialDelayerTemplate,
|
||||||
xnorTemplate,
|
xnorTemplate,
|
||||||
xorTemplate,
|
xorTemplate,
|
||||||
halfAdderTemplate,
|
halfAdderTemplate,
|
||||||
fullAdderTemplate,
|
fullAdderTemplate,
|
||||||
_4bitEncoderTemplate,
|
_4bitEncoderTemplate,
|
||||||
_4bitDecoderTemplate,
|
_4bitDecoderTemplate,
|
||||||
comparatorTemplate,
|
comparatorTemplate,
|
||||||
bitMergerTemplate,
|
bitMergerTemplate,
|
||||||
bitSplitterTemplate,
|
bitSplitterTemplate,
|
||||||
incrementorTemplate
|
incrementorTemplate,
|
||||||
|
constantTemplate
|
||||||
]
|
]
|
||||||
|
|
||||||
export const baseSave: RendererState = {
|
export const baseSave: RendererState = {
|
||||||
camera: {
|
camera: {
|
||||||
transform: {
|
transform: {
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
scale: [1, 1],
|
scale: [1, 1],
|
||||||
rotation: 0
|
rotation: 0
|
||||||
}
|
|
||||||
},
|
|
||||||
simulation: {
|
|
||||||
gates: [],
|
|
||||||
mode: 'project',
|
|
||||||
wires: [],
|
|
||||||
name: 'default'
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
simulation: {
|
||||||
|
gates: [],
|
||||||
|
mode: 'project',
|
||||||
|
wires: [],
|
||||||
|
name: 'default'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
export const categories = {
|
export const categories = {
|
||||||
basic: 0,
|
basic: 0,
|
||||||
math: 1,
|
math: 1,
|
||||||
time: 2,
|
time: 2,
|
||||||
compressing: 3,
|
compressing: 3,
|
||||||
io: 4,
|
io: 4,
|
||||||
advancedIo: 5,
|
advancedIo: 5,
|
||||||
ic: 6
|
ic: 6
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@ import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
* @throws SimulationError if something is wrong with the template
|
* @throws SimulationError if something is wrong with the template
|
||||||
*/
|
*/
|
||||||
export const initBaseTemplates = () => {
|
export const initBaseTemplates = () => {
|
||||||
for (const template of baseTemplates) {
|
for (const template of baseTemplates) {
|
||||||
if (template.metadata && template.metadata.name) {
|
if (template.metadata && template.metadata.name) {
|
||||||
templateStore.set(template.metadata.name, template)
|
templateStore.set(template.metadata.name, template)
|
||||||
} else {
|
} else {
|
||||||
throw new SimulationError(
|
throw new SimulationError(
|
||||||
`Template ${JSON.stringify(template)} cannot be stored.`
|
`Template ${JSON.stringify(template)} cannot be stored.`
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,44 +4,44 @@ import { PartialTemplate } from '../types/PartialTemplate'
|
||||||
* The template of the comment gate
|
* The template of the comment gate
|
||||||
*/
|
*/
|
||||||
const commentTemplate: PartialTemplate = {
|
const commentTemplate: PartialTemplate = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'comment'
|
name: 'comment'
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 0
|
||||||
},
|
},
|
||||||
pins: {
|
outputs: {
|
||||||
inputs: {
|
count: 0
|
||||||
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'
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
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
|
export default commentTemplate
|
||||||
|
|
60
src/modules/saving/templates/constant.ts
Normal file
60
src/modules/saving/templates/constant.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import { PartialTemplate } from '../types/PartialTemplate'
|
||||||
|
import { categories } from '../data/categories'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template of the button gate
|
||||||
|
*/
|
||||||
|
const constTemplate: PartialTemplate = {
|
||||||
|
metadata: {
|
||||||
|
name: 'constant'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
fill: '#673AB7',
|
||||||
|
stroke: {
|
||||||
|
normal: '#EDC6FB'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `
|
||||||
|
const state = context.getProperty('value')
|
||||||
|
const bits = context.getProperty('output bits')
|
||||||
|
const length = state.toString(2).length
|
||||||
|
const text = length > 10
|
||||||
|
? "0x" + context.printHex(state, Math.ceil(length/4))
|
||||||
|
: context.printBinary(state, length)
|
||||||
|
|
||||||
|
context.setBinary(0, state, bits === 0 ? length : bits)
|
||||||
|
context.innerText(text)
|
||||||
|
`
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
integration: {
|
||||||
|
input: true
|
||||||
|
},
|
||||||
|
info: [],
|
||||||
|
properties: {
|
||||||
|
enabled: true,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
base: 0,
|
||||||
|
name: 'output bits',
|
||||||
|
description: '(0 for auto)',
|
||||||
|
type: 'number',
|
||||||
|
needsUpdate: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
base: 0,
|
||||||
|
name: 'value',
|
||||||
|
type: 'number',
|
||||||
|
needsUpdate: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
category: categories.io
|
||||||
|
}
|
||||||
|
|
||||||
|
export default constTemplate
|
|
@ -5,40 +5,40 @@ import { categories } from '../data/categories'
|
||||||
* The template of the light gate
|
* The template of the light gate
|
||||||
*/
|
*/
|
||||||
const lightTemplate: PartialTemplate = {
|
const lightTemplate: PartialTemplate = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'light bulb'
|
name: 'light bulb'
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
radius: 50
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
fill: '#1C1C1C',
|
||||||
|
stroke: {
|
||||||
|
normal: '#3C3C3C'
|
||||||
},
|
},
|
||||||
shape: {
|
colors: {
|
||||||
radius: 50
|
active: '#C6FF00'
|
||||||
},
|
}
|
||||||
material: {
|
},
|
||||||
fill: '#1C1C1C',
|
code: {
|
||||||
stroke: {
|
activation: `
|
||||||
normal: '#3C3C3C'
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
active: '#C6FF00'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
activation: `
|
|
||||||
const { main, active } = context.colors
|
const { main, active } = context.colors
|
||||||
|
|
||||||
const bits = context.get(0)
|
const bits = context.get(0)
|
||||||
|
|
||||||
context.color(parseInt(context.get(0),2) ? active : main)
|
context.color(parseInt(context.get(0),2) ? active : main)
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
integration: {
|
integration: {
|
||||||
output: true
|
output: true
|
||||||
},
|
},
|
||||||
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
info: ['https://en.wikipedia.org/wiki/Incandescent_light_bulb'],
|
||||||
pins: {
|
pins: {
|
||||||
outputs: {
|
outputs: {
|
||||||
count: 0
|
count: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
category: categories.io
|
category: categories.io
|
||||||
}
|
}
|
||||||
|
|
||||||
export default lightTemplate
|
export default lightTemplate
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,80 +3,80 @@ import { categories } from '../saving/data/categories'
|
||||||
import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
|
import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
|
||||||
|
|
||||||
export const DefaultGateTemplate: GateTemplate = {
|
export const DefaultGateTemplate: GateTemplate = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'Default template'
|
name: 'Default template'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
type: 'color',
|
||||||
|
fill: 'blue',
|
||||||
|
stroke: {
|
||||||
|
active: '#76FF02',
|
||||||
|
normal: '#3FC4FF'
|
||||||
},
|
},
|
||||||
material: {
|
colors: {}
|
||||||
type: 'color',
|
},
|
||||||
fill: 'blue',
|
pins: {
|
||||||
stroke: {
|
inputs: {
|
||||||
active: '#76FF02',
|
count: 1,
|
||||||
normal: '#3FC4FF'
|
variable: false
|
||||||
},
|
|
||||||
colors: {}
|
|
||||||
},
|
},
|
||||||
pins: {
|
outputs: {
|
||||||
inputs: {
|
count: 1,
|
||||||
count: 1,
|
variable: false
|
||||||
variable: false
|
}
|
||||||
},
|
},
|
||||||
outputs: {
|
shape: {
|
||||||
count: 1,
|
radius: 10,
|
||||||
variable: false
|
rounded: true,
|
||||||
}
|
scale: [100, 100]
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
async: false,
|
||||||
|
activation: '',
|
||||||
|
onClick: '',
|
||||||
|
initialisation: ''
|
||||||
|
},
|
||||||
|
simulation: {
|
||||||
|
debounce: {
|
||||||
|
enabled: true,
|
||||||
|
time: 1000 / 60
|
||||||
},
|
},
|
||||||
shape: {
|
throttle: {
|
||||||
radius: 10,
|
enabled: false
|
||||||
rounded: true,
|
}
|
||||||
scale: [100, 100]
|
},
|
||||||
},
|
integration: {
|
||||||
code: {
|
allowed: true,
|
||||||
async: false,
|
input: false,
|
||||||
activation: '',
|
output: false
|
||||||
onClick: '',
|
},
|
||||||
initialisation: ''
|
tags: ['base'],
|
||||||
},
|
properties: {
|
||||||
simulation: {
|
enabled: false,
|
||||||
debounce: {
|
data: [
|
||||||
enabled: true,
|
{
|
||||||
time: 1000 / 60
|
type: 'boolean',
|
||||||
},
|
base: false,
|
||||||
throttle: {
|
name: 'external'
|
||||||
enabled: false
|
},
|
||||||
}
|
{
|
||||||
},
|
type: 'string',
|
||||||
integration: {
|
base: 'my-logic-gate',
|
||||||
allowed: true,
|
name: 'label'
|
||||||
input: false,
|
}
|
||||||
output: false
|
]
|
||||||
},
|
},
|
||||||
tags: ['base'],
|
innerText: {
|
||||||
properties: {
|
enabled: false,
|
||||||
enabled: false,
|
color: 'white'
|
||||||
data: [
|
},
|
||||||
{
|
category: categories.basic,
|
||||||
type: 'boolean',
|
info: []
|
||||||
base: false,
|
|
||||||
name: 'external'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'string',
|
|
||||||
base: 'my-logic-gate',
|
|
||||||
name: 'label'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
innerText: {
|
|
||||||
enabled: false,
|
|
||||||
color: 'white'
|
|
||||||
},
|
|
||||||
category: categories.basic,
|
|
||||||
info: []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prop names which need to not be overriten
|
* Prop names which need to not be overriten
|
||||||
*/
|
*/
|
||||||
export const reservedPropNames = DefaultGateTemplate.properties.data.map(
|
export const reservedPropNames = DefaultGateTemplate.properties.data.map(
|
||||||
({ name }: RawProp) => name
|
({ name }: RawProp) => name
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,96 +1,97 @@
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
|
|
||||||
export interface PinCount {
|
export interface PinCount {
|
||||||
variable: boolean
|
variable: boolean
|
||||||
count: number
|
count: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PropGroup<
|
export type PropGroup<
|
||||||
T extends boolean | number | string = boolean | number | string
|
T extends boolean | number | string = boolean | number | string
|
||||||
> = {
|
> = {
|
||||||
groupName: string
|
groupName: string
|
||||||
props: Property<T>[]
|
props: Property<T>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isGroup = (prop: Property): prop is PropGroup =>
|
export const isGroup = (prop: Property): prop is PropGroup =>
|
||||||
(prop as PropGroup).groupName !== undefined
|
(prop as PropGroup).groupName !== undefined
|
||||||
|
|
||||||
export type RawProp<
|
export type RawProp<
|
||||||
T extends boolean | number | string = boolean | number | string
|
T extends boolean | number | string = boolean | number | string
|
||||||
> = {
|
> = {
|
||||||
type: 'number' | 'string' | 'text' | 'boolean'
|
type: 'number' | 'string' | 'text' | 'boolean'
|
||||||
base: T
|
base: T
|
||||||
name: string
|
name: string
|
||||||
needsUpdate?: boolean
|
description?: string
|
||||||
|
needsUpdate?: boolean
|
||||||
}
|
}
|
||||||
export type Property<
|
export type Property<
|
||||||
T extends boolean | number | string = boolean | number | string
|
T extends boolean | number | string = boolean | number | string
|
||||||
> = PropGroup<T> | RawProp<T>
|
> = PropGroup<T> | RawProp<T>
|
||||||
|
|
||||||
export interface Material {
|
export interface Material {
|
||||||
type: 'color' | 'image'
|
type: 'color' | 'image'
|
||||||
fill: string
|
fill: string
|
||||||
stroke: {
|
stroke: {
|
||||||
active: string
|
active: string
|
||||||
normal: string
|
normal: string
|
||||||
}
|
}
|
||||||
colors: Record<string, string>
|
colors: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Shape {
|
export interface Shape {
|
||||||
rounded: boolean
|
rounded: boolean
|
||||||
radius: number
|
radius: number
|
||||||
scale: vector2
|
scale: vector2
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Enabled<T> =
|
export type Enabled<T> =
|
||||||
| {
|
| {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
| ({
|
| ({
|
||||||
enabled: true
|
enabled: true
|
||||||
} & T)
|
} & T)
|
||||||
|
|
||||||
export type TimePipe = Enabled<{
|
export type TimePipe = Enabled<{
|
||||||
time: number
|
time: number
|
||||||
}>
|
}>
|
||||||
|
|
||||||
export type GateTag = 'base' | 'imported' | 'integrated'
|
export type GateTag = 'base' | 'imported' | 'integrated'
|
||||||
|
|
||||||
export interface GateTemplate {
|
export interface GateTemplate {
|
||||||
material: Material
|
material: Material
|
||||||
shape: Shape
|
shape: Shape
|
||||||
pins: {
|
pins: {
|
||||||
inputs: PinCount
|
inputs: PinCount
|
||||||
outputs: PinCount
|
outputs: PinCount
|
||||||
}
|
}
|
||||||
metadata: {
|
metadata: {
|
||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
code: {
|
code: {
|
||||||
async: boolean
|
async: boolean
|
||||||
initialisation: string
|
initialisation: string
|
||||||
activation: string
|
activation: string
|
||||||
onClick: string
|
onClick: string
|
||||||
}
|
}
|
||||||
simulation: {
|
simulation: {
|
||||||
throttle: TimePipe
|
throttle: TimePipe
|
||||||
debounce: TimePipe
|
debounce: TimePipe
|
||||||
}
|
}
|
||||||
integration: {
|
integration: {
|
||||||
allowed: boolean
|
allowed: boolean
|
||||||
input: boolean
|
input: boolean
|
||||||
output: boolean
|
output: boolean
|
||||||
}
|
}
|
||||||
tags: GateTag[]
|
tags: GateTag[]
|
||||||
properties: {
|
properties: {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
data: Property[]
|
data: Property[]
|
||||||
}
|
}
|
||||||
innerText: {
|
innerText: {
|
||||||
color: string
|
color: string
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
}
|
}
|
||||||
category: number // for better sorting
|
category: number // for better sorting
|
||||||
info: string[]
|
info: string[]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export const textSettings = {
|
export const textSettings = {
|
||||||
font: '30px Roboto',
|
font: '30px monospace',
|
||||||
offset: 35,
|
offset: 35,
|
||||||
fill: `rgba(256,256,256,0.75)`
|
fill: `rgba(256,256,256,0.75)`
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
|
||||||
.map((key) =>
|
.map((key) =>
|
||||||
chunked
|
chunked
|
||||||
.flat()
|
.flat()
|
||||||
.filter((v, index) => index % 3 === key)
|
.filter((_v, index) => index % 3 === key)
|
||||||
.reduce((acc, curr) => acc + curr, 0)
|
.reduce((acc, curr) => acc + curr, 0)
|
||||||
)
|
)
|
||||||
.map((value) => Math.floor(value / digits.length))
|
.map((value) => Math.floor(value / digits.length))
|
||||||
|
|
|
@ -10,64 +10,85 @@ import { idIsSelected } from './idIsSelected'
|
||||||
import { textSettings } from '../data/textSettings'
|
import { textSettings } from '../data/textSettings'
|
||||||
|
|
||||||
export const renderGate = (
|
export const renderGate = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
renderer: SimulationRenderer,
|
renderer: SimulationRenderer,
|
||||||
gate: Gate
|
gate: Gate
|
||||||
) => {
|
) => {
|
||||||
const { active, normal } = gate.template.material.stroke
|
const { active, normal } = gate.template.material.stroke
|
||||||
|
|
||||||
const selected =
|
const selected =
|
||||||
(renderer.mouseState >> 2 &&
|
(renderer.mouseState >> 2 &&
|
||||||
!!gatesInSelection(renderer.selectedArea, [gate]).length) ||
|
!!gatesInSelection(renderer.selectedArea, [gate]).length) ||
|
||||||
idIsSelected(renderer, gate.id)
|
idIsSelected(renderer, gate.id)
|
||||||
|
|
||||||
renderPins(ctx, renderer, gate, selected)
|
renderPins(ctx, renderer, gate, selected)
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
ctx.strokeStyle = active
|
ctx.strokeStyle = active
|
||||||
} else {
|
} else {
|
||||||
ctx.strokeStyle = normal
|
ctx.strokeStyle = normal
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.lineWidth = renderer.options.gates.gateStroke.width
|
||||||
|
|
||||||
|
ctx.save()
|
||||||
|
|
||||||
|
const relativeTransform = useTransform(ctx, gate.transform)
|
||||||
|
const renderingParameters = [
|
||||||
|
relativeTransform.x,
|
||||||
|
relativeTransform.y,
|
||||||
|
relativeTransform.width,
|
||||||
|
relativeTransform.height,
|
||||||
|
gate.template.shape.rounded ? gate.template.shape.radius : 0
|
||||||
|
]
|
||||||
|
|
||||||
|
if (gate.template.material.type === 'image') {
|
||||||
|
roundImage(
|
||||||
|
ctx,
|
||||||
|
ImageStore.get(gate.template.material.fill),
|
||||||
|
...renderingParameters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
roundRect(ctx, ...renderingParameters)
|
||||||
|
|
||||||
|
if (gate.template.material.type === 'color') {
|
||||||
|
ctx.fillStyle = gate.template.material.fill
|
||||||
|
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
if (gate.template.tags.includes('integrated')) {
|
||||||
|
ctx.textBaseline = 'top'
|
||||||
|
ctx.fillStyle = textSettings.fill
|
||||||
|
ctx.fillText(
|
||||||
|
gate.template.metadata.name,
|
||||||
|
relativeTransform.center[0],
|
||||||
|
relativeTransform.maxY + textSettings.offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = gate.text.inner.value
|
||||||
|
if (text !== null) {
|
||||||
|
ctx.textBaseline = 'middle'
|
||||||
|
ctx.fillStyle = textSettings.fill
|
||||||
|
|
||||||
|
let size = 30
|
||||||
|
if (text.length >= 8) {
|
||||||
|
size = 15
|
||||||
|
} else if (text.length >= 6) {
|
||||||
|
size = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.lineWidth = renderer.options.gates.gateStroke.width
|
ctx.font = `${size}px monospace`
|
||||||
|
ctx.fillText(
|
||||||
|
text,
|
||||||
|
relativeTransform.center[0],
|
||||||
|
relativeTransform.center[1] + 2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.save()
|
ctx.restore()
|
||||||
|
|
||||||
const relativeTransform = useTransform(ctx, gate.transform)
|
|
||||||
const renderingParameters = [
|
|
||||||
relativeTransform.x,
|
|
||||||
relativeTransform.y,
|
|
||||||
relativeTransform.width,
|
|
||||||
relativeTransform.height,
|
|
||||||
gate.template.shape.rounded ? gate.template.shape.radius : 0
|
|
||||||
]
|
|
||||||
|
|
||||||
if (gate.template.material.type === 'image') {
|
|
||||||
roundImage(
|
|
||||||
ctx,
|
|
||||||
ImageStore.get(gate.template.material.fill),
|
|
||||||
...renderingParameters
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
roundRect(ctx, ...renderingParameters)
|
|
||||||
|
|
||||||
if (gate.template.material.type === 'color') {
|
|
||||||
ctx.fillStyle = gate.template.material.fill
|
|
||||||
|
|
||||||
ctx.fill()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.stroke()
|
|
||||||
|
|
||||||
if (gate.template.tags.includes('integrated')) {
|
|
||||||
ctx.fillStyle = textSettings.fill
|
|
||||||
ctx.fillText(
|
|
||||||
gate.template.metadata.name,
|
|
||||||
relativeTransform.center[0],
|
|
||||||
relativeTransform.maxY + textSettings.offset
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.restore()
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue