Add support for constants

This commit is contained in:
prescientmoon 2024-11-27 10:41:36 +01:00
parent 7d9d2a2d78
commit 96e2184a24
Signed by: prescientmoon
SSH key fingerprint: SHA256:UUF9JT2s8Xfyv76b8ZuVL7XrmimH4o49p4b+iexbVH4
19 changed files with 1086 additions and 1004 deletions

View file

@ -43,7 +43,11 @@ const ctx = await esbuild.context({
})
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}`)
} else {
await ctx.rebuild()

View file

@ -3,6 +3,7 @@
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "ESBUILD_SERVE=1 node ./build.js",
"build": "node ./build.js",
"check": "tsc"
},

View file

@ -4,37 +4,39 @@ import { Splash } from './modules/splash/classes/Splash'
* The function wich is run when the app is loaded
*/
async function main() {
// Create splash screen variable
let splash: Splash | undefined = undefined
// Create splash screen variable
let splash: Splash | undefined = undefined
try {
// instantiate splash screen
splash = new Splash()
} catch {}
try {
// instantiate splash screen
splash = new Splash()
} catch {}
try {
// import main app
const app = await import('./main')
try {
// import main app
const app = await import('./main')
// wait for app to start
await app.start()
} catch (error) {
// show the error to the client
if (splash) splash.setError(error)
// wait for app to start
await app.start()
} catch (error) {
// show the error to the client
if (splash) splash.setError(error)
// log the error to the console
console.error(error.stack || error)
return
}
// log the error to the console
console.error(error.stack || error)
return
}
// hide splash screen if it exists
if (splash) {
splash.fade()
}
// hide splash screen if it exists
if (splash) {
splash.fade()
}
}
new EventSource('/esbuild').addEventListener('change', () => location.reload())
// Call entry
main().catch(error => {
// if the error handling error has an error, log that error
console.error('Error loading app', error)
main().catch((error) => {
// if the error handling error has an error, log that error
console.error('Error loading app', error)
})

View file

@ -1,24 +1,28 @@
import { Simulation, SimulationEnv } from '../../simulation/classes/Simulation'
import { SimulationEnv } from '../../simulation/classes/Simulation'
import { PinState } from '../../simulation/classes/Pin'
export interface Context {
getProperty: (name: string) => unknown
setProperty: (name: string, value: unknown) => void
get: (index: number) => PinState
set: (index: number, state: PinState) => void
getBinary: (index: number) => number
setBinary: (index: number, value: number, bits: number) => void
invertBinary: (value: number) => number
color: (color: string) => void
innerText: (value: string) => void
update: () => void
toLength: (value: number | PinState, length: number) => string
maxLength: number
enviroment: SimulationEnv
colors: Record<string, string>
memory: Record<string, unknown>
getProperty: (name: string) => unknown
setProperty: (name: string, value: unknown) => void
get: (index: number) => PinState
set: (index: number, state: PinState) => void
getOutput: (index: number) => PinState
getBinary: (index: number) => number
printBinary: (value: number, bits?: number) => string
printHex: (value: number, length?: number) => string
setBinary: (index: number, value: number, bits?: number) => void
getOutputBinary: (index: number) => number
invertBinary: (value: number) => number
color: (color: string) => void
innerText: (value: string) => void
update: () => void
toLength: (value: number | PinState, length: number) => string
maxLength: number
enviroment: SimulationEnv
colors: Record<string, string>
memory: Record<string, unknown>
}
export interface InitialisationContext {
memory: Record<string, unknown>
memory: Record<string, unknown>
}

View file

@ -7,60 +7,64 @@
$gate-props-margin: 1rem;
#gate-properties-modal {
@include modal-container();
@include modal-container();
justify-content: center;
justify-content: center;
}
.visible#gate-properties-modal {
@include visible();
@include visible();
}
div #gate-properties-container {
display: flex;
flex-direction: column;
display: flex;
flex-direction: column;
background-color: $grey;
border-radius: 1em;
background-color: $grey;
border-radius: 1em;
padding: $gate-props-margin * 4;
box-sizing: border-box;
padding: $gate-props-margin * 4;
box-sizing: border-box;
max-height: 80vh;
overflow: auto;
max-height: 80vh;
overflow: auto;
}
div #gate-props-title {
color: white;
font-size: 3em;
color: white;
font-size: 3em;
margin-bottom: 2 * $gate-props-margin;
margin-bottom: 2 * $gate-props-margin;
}
div .gate-prop-container {
@include flex();
@include flex();
flex-direction: row;
justify-content: start;
flex-direction: row;
justify-content: start;
&.visible {
margin: 1rem;
}
&.visible {
margin: 1rem;
}
&>* {
width: 100%;
}
}
div .gate-prop-group-container {
@include flex;
@include flex;
margin-left: 1rem;
margin-left: 1rem;
}
div #save-props {
width: 50%;
margin: $gate-props-margin * 2;
margin-bottom: 0;
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;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

View file

@ -1,9 +1,8 @@
import './GateProperties.scss'
import React, { ChangeEvent } from 'react'
import { ChangeEvent } from 'react'
import { getRendererSafely } from '../helpers/getRendererSafely'
import { Property, RawProp, isGroup } from '../../simulation/types/GateTemplate'
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'
@ -16,41 +15,41 @@ import Typography from '@material-ui/core/Typography'
import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'
import Icon from '@material-ui/core/Icon'
interface GatePropertyProps<T extends Property = Property> {
raw: T
gate: Gate
props: GateProps
raw: T
gate: Gate
props: GateProps
}
const emptyInput = <></>
const GateProperty = ({ raw, props, gate }: GatePropertyProps) => {
if (isGroup(raw)) {
return (
<ExpansionPanel>
<ExpansionPanelSummary
expandIcon={<Icon> expand_more</Icon>}
aria-controls={raw.groupName}
id={raw.groupName}
>
<Typography>{raw.groupName}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<div className="gate-prop-group-container">
{raw.props.map((propTemplate, index) => (
<GateProperty
key={index}
raw={propTemplate}
gate={gate}
props={props[raw.groupName] as GateProps}
/>
))}
</div>
</ExpansionPanelDetails>
</ExpansionPanel>
)
}
if (isGroup(raw)) {
return (
<ExpansionPanel>
<ExpansionPanelSummary
expandIcon={<Icon> expand_more</Icon>}
aria-controls={raw.groupName}
id={raw.groupName}
>
<Typography>{raw.groupName}</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<div className="gate-prop-group-container">
{raw.props.map((propTemplate, index) => (
<GateProperty
key={index}
raw={propTemplate}
gate={gate}
props={props[raw.groupName] as GateProps}
/>
))}
</div>
</ExpansionPanelDetails>
</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
*/
const GateRawProperty = ({
props,
raw,
gate
props,
raw,
gate
}: GatePropertyProps & { raw: RawProp }) => {
const { name } = raw
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
const outputSnapshot = useObservable(() => prop, '')
const { name } = raw
const prop = props[raw.name] as BehaviorSubject<string | number | boolean>
const outputSnapshot = useObservable(() => prop, '')
// rerender when the external checkbox changes
const external = useObservable(
() =>
gate.props.external.pipe(
map((value) => value && name !== 'external')
),
false
)
// rerender when the external checkbox changes
const external = useObservable(
() =>
gate.props.external.pipe(map((value) => value && name !== 'external')),
false
)
const displayableName = `${name[0].toUpperCase()}${name.slice(1)} ${
external && name !== 'label' ? '(default value)' : ''
}:`
const displayableName = `${name[0].toUpperCase()}${name.slice(1)}${
external && name !== 'label' ? ' (default value)' : ''
}${raw.description == undefined ? '' : ` ${raw.description}`}:`
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
const target = e.target as HTMLInputElement
let value: boolean | string | number = target.value
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)
}
if (raw.type !== 'boolean') {
prop.next(value)
}
if (raw.type === 'boolean') {
value = target.checked
} else if (raw.type === 'number') {
value = Number(target.value)
}
let input = (() => {
const root = gate.props[name] === prop
const renderer = getRendererSafely()
const displayExternal = () =>
renderer.simulation.mode === 'ic' &&
root &&
!gate.template.properties.data.some(
(prop) => (prop as RawProp).needsUpdate
)
if (raw.type !== 'boolean') {
prop.next(value)
}
}
if (
(raw.name === 'external' && !displayExternal()) ||
(raw.name === 'label' && (!external || !root))
) {
return emptyInput
}
let input = (() => {
const root = gate.props[name] === prop
const renderer = getRendererSafely()
const displayExternal = () =>
renderer.simulation.mode === 'ic' &&
root &&
!gate.template.properties.data.some(
(prop) => (prop as RawProp).needsUpdate
)
if (
raw.type === 'number' ||
raw.type === 'text' ||
raw.type === 'string'
) {
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}
/>{' '}
</>
)
}
})()
if (
(raw.name === 'external' && !displayExternal()) ||
(raw.name === 'label' && (!external || !root))
) {
return emptyInput
}
return (
<div
className={`gate-prop-container ${
input !== emptyInput ? 'visible' : ''
}`}
>
{input}
</div>
)
if (raw.type === 'number' || raw.type === 'text' || raw.type === 'string') {
return (
<TextField
onChange={handleChange}
label={displayableName}
value={outputSnapshot}
type={raw.type}
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
*/
const GateProperties = () => {
const openSnapshot = useObservable(() => open, false)
const renderer = getRendererSafely()
const openSnapshot = useObservable(() => open, false)
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)
return <></>
}
}}
>
<div
id="gate-properties-container"
onClick={(e) => {
e.stopPropagation()
}}
>
<div id="gate-props-title">Gate properties</div>
const gate = node.data
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>
{gate.template.properties.data.map((prop, index) => {
return (
<GateProperty
props={gate.props}
raw={prop}
gate={gate}
key={`${index}-${id.value}`}
/>
)
})}
</div>
</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

View file

@ -3,7 +3,7 @@ import { GateTemplate } from '../../simulation/types/GateTemplate'
import { DefaultGateTemplate } from '../../simulation/constants'
export const completeTemplate = (template: DeepPartial<GateTemplate>) => {
return merge(DefaultGateTemplate, template, {
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
}) as GateTemplate
return merge(DefaultGateTemplate, template, {
arrayMerge: (a: unknown[], b: unknown[]) => a.concat(b)
}) as GateTemplate
}

View file

@ -20,43 +20,45 @@ import comparatorTemplate from './templates/comparator'
import bitMergerTemplate from './templates/bitMerger'
import bitSplitterTemplate from './templates/bitSplitter'
import incrementorTemplate from './templates/incrementor'
import constantTemplate from './templates/constant'
export const defaultSimulationName = 'default'
export const baseTemplates: DeepPartial<GateTemplate>[] = [
andTemplate,
buttonTemplate,
lightTemplate,
nandTemplate,
norTemplate,
notTemplate,
orTemplate,
parallelDelayerTemplate,
rgbLightTemplate,
sequentialDelayerTemplate,
xnorTemplate,
xorTemplate,
halfAdderTemplate,
fullAdderTemplate,
_4bitEncoderTemplate,
_4bitDecoderTemplate,
comparatorTemplate,
bitMergerTemplate,
bitSplitterTemplate,
incrementorTemplate
andTemplate,
buttonTemplate,
lightTemplate,
nandTemplate,
norTemplate,
notTemplate,
orTemplate,
parallelDelayerTemplate,
rgbLightTemplate,
sequentialDelayerTemplate,
xnorTemplate,
xorTemplate,
halfAdderTemplate,
fullAdderTemplate,
_4bitEncoderTemplate,
_4bitDecoderTemplate,
comparatorTemplate,
bitMergerTemplate,
bitSplitterTemplate,
incrementorTemplate,
constantTemplate
]
export const baseSave: RendererState = {
camera: {
transform: {
position: [0, 0],
scale: [1, 1],
rotation: 0
}
},
simulation: {
gates: [],
mode: 'project',
wires: [],
name: 'default'
camera: {
transform: {
position: [0, 0],
scale: [1, 1],
rotation: 0
}
},
simulation: {
gates: [],
mode: 'project',
wires: [],
name: 'default'
}
}

View file

@ -1,9 +1,9 @@
export const categories = {
basic: 0,
math: 1,
time: 2,
compressing: 3,
io: 4,
advancedIo: 5,
ic: 6
basic: 0,
math: 1,
time: 2,
compressing: 3,
io: 4,
advancedIo: 5,
ic: 6
}

View file

@ -8,13 +8,13 @@ import { SimulationError } from '../../errors/classes/SimulationError'
* @throws SimulationError if something is wrong with the template
*/
export const initBaseTemplates = () => {
for (const template of baseTemplates) {
if (template.metadata && template.metadata.name) {
templateStore.set(template.metadata.name, template)
} else {
throw new SimulationError(
`Template ${JSON.stringify(template)} cannot be stored.`
)
}
for (const template of baseTemplates) {
if (template.metadata && template.metadata.name) {
templateStore.set(template.metadata.name, template)
} else {
throw new SimulationError(
`Template ${JSON.stringify(template)} cannot be stored.`
)
}
}
}

View file

@ -4,44 +4,44 @@ import { PartialTemplate } from '../types/PartialTemplate'
* The template of the comment gate
*/
const commentTemplate: PartialTemplate = {
metadata: {
name: 'comment'
metadata: {
name: 'comment'
},
pins: {
inputs: {
count: 0
},
pins: {
inputs: {
count: 0
},
outputs: {
count: 0
}
},
material: {
fill: '#007A72'
},
shape: {
scale: [300, 100]
},
code: {
activation: `
context.innerText(context.getProperty('content'))
`
},
info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
properties: {
enabled: true,
data: [
{
needsUpdate: true,
base: 'Your comment here',
name: 'content',
type: 'string'
}
]
},
innerText: {
enabled: true,
color: '#ADFFFA'
outputs: {
count: 0
}
},
material: {
fill: '#007A72'
},
shape: {
scale: [300, 100]
},
code: {
activation: `
context.innerText(context.getProperty('content'))
`
},
info: ['https://en.wikipedia.org/wiki/Comment_(computer_programming)'],
properties: {
enabled: true,
data: [
{
needsUpdate: true,
base: 'Your comment here',
name: 'content',
type: 'string'
}
]
},
innerText: {
enabled: true,
color: '#ADFFFA'
}
}
export default commentTemplate

View 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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -3,80 +3,80 @@ import { categories } from '../saving/data/categories'
import { getRendererSafely } from '../logic-gates/helpers/getRendererSafely'
export const DefaultGateTemplate: GateTemplate = {
metadata: {
name: 'Default template'
metadata: {
name: 'Default template'
},
material: {
type: 'color',
fill: 'blue',
stroke: {
active: '#76FF02',
normal: '#3FC4FF'
},
material: {
type: 'color',
fill: 'blue',
stroke: {
active: '#76FF02',
normal: '#3FC4FF'
},
colors: {}
colors: {}
},
pins: {
inputs: {
count: 1,
variable: false
},
pins: {
inputs: {
count: 1,
variable: false
},
outputs: {
count: 1,
variable: false
}
outputs: {
count: 1,
variable: false
}
},
shape: {
radius: 10,
rounded: true,
scale: [100, 100]
},
code: {
async: false,
activation: '',
onClick: '',
initialisation: ''
},
simulation: {
debounce: {
enabled: true,
time: 1000 / 60
},
shape: {
radius: 10,
rounded: true,
scale: [100, 100]
},
code: {
async: false,
activation: '',
onClick: '',
initialisation: ''
},
simulation: {
debounce: {
enabled: true,
time: 1000 / 60
},
throttle: {
enabled: false
}
},
integration: {
allowed: true,
input: false,
output: false
},
tags: ['base'],
properties: {
enabled: false,
data: [
{
type: 'boolean',
base: false,
name: 'external'
},
{
type: 'string',
base: 'my-logic-gate',
name: 'label'
}
]
},
innerText: {
enabled: false,
color: 'white'
},
category: categories.basic,
info: []
throttle: {
enabled: false
}
},
integration: {
allowed: true,
input: false,
output: false
},
tags: ['base'],
properties: {
enabled: false,
data: [
{
type: 'boolean',
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
*/
export const reservedPropNames = DefaultGateTemplate.properties.data.map(
({ name }: RawProp) => name
({ name }: RawProp) => name
)

View file

@ -1,96 +1,97 @@
import { vector2 } from '../../../common/math/types/vector2'
export interface PinCount {
variable: boolean
count: number
variable: boolean
count: number
}
export type PropGroup<
T extends boolean | number | string = boolean | number | string
T extends boolean | number | string = boolean | number | string
> = {
groupName: string
props: Property<T>[]
groupName: string
props: Property<T>[]
}
export const isGroup = (prop: Property): prop is PropGroup =>
(prop as PropGroup).groupName !== undefined
(prop as PropGroup).groupName !== undefined
export type RawProp<
T extends boolean | number | string = boolean | number | string
T extends boolean | number | string = boolean | number | string
> = {
type: 'number' | 'string' | 'text' | 'boolean'
base: T
name: string
needsUpdate?: boolean
type: 'number' | 'string' | 'text' | 'boolean'
base: T
name: string
description?: string
needsUpdate?: boolean
}
export type Property<
T extends boolean | number | string = boolean | number | string
T extends boolean | number | string = boolean | number | string
> = PropGroup<T> | RawProp<T>
export interface Material {
type: 'color' | 'image'
fill: string
stroke: {
active: string
normal: string
}
colors: Record<string, string>
type: 'color' | 'image'
fill: string
stroke: {
active: string
normal: string
}
colors: Record<string, string>
}
export interface Shape {
rounded: boolean
radius: number
scale: vector2
rounded: boolean
radius: number
scale: vector2
}
export type Enabled<T> =
| {
enabled: false
}
| ({
enabled: true
} & T)
| {
enabled: false
}
| ({
enabled: true
} & T)
export type TimePipe = Enabled<{
time: number
time: number
}>
export type GateTag = 'base' | 'imported' | 'integrated'
export interface GateTemplate {
material: Material
shape: Shape
pins: {
inputs: PinCount
outputs: PinCount
}
metadata: {
name: string
}
code: {
async: boolean
initialisation: string
activation: string
onClick: string
}
simulation: {
throttle: TimePipe
debounce: TimePipe
}
integration: {
allowed: boolean
input: boolean
output: boolean
}
tags: GateTag[]
properties: {
enabled: boolean
data: Property[]
}
innerText: {
color: string
enabled: boolean
}
category: number // for better sorting
info: string[]
material: Material
shape: Shape
pins: {
inputs: PinCount
outputs: PinCount
}
metadata: {
name: string
}
code: {
async: boolean
initialisation: string
activation: string
onClick: string
}
simulation: {
throttle: TimePipe
debounce: TimePipe
}
integration: {
allowed: boolean
input: boolean
output: boolean
}
tags: GateTag[]
properties: {
enabled: boolean
data: Property[]
}
innerText: {
color: string
enabled: boolean
}
category: number // for better sorting
info: string[]
}

View file

@ -1,5 +1,5 @@
export const textSettings = {
font: '30px Roboto',
offset: 35,
fill: `rgba(256,256,256,0.75)`
font: '30px monospace',
offset: 35,
fill: `rgba(256,256,256,0.75)`
}

View file

@ -16,7 +16,7 @@ export const pinFill = (renderer: SimulationRenderer, pin: Pin) => {
.map((key) =>
chunked
.flat()
.filter((v, index) => index % 3 === key)
.filter((_v, index) => index % 3 === key)
.reduce((acc, curr) => acc + curr, 0)
)
.map((value) => Math.floor(value / digits.length))

View file

@ -10,64 +10,85 @@ import { idIsSelected } from './idIsSelected'
import { textSettings } from '../data/textSettings'
export const renderGate = (
ctx: CanvasRenderingContext2D,
renderer: SimulationRenderer,
gate: Gate
ctx: CanvasRenderingContext2D,
renderer: SimulationRenderer,
gate: Gate
) => {
const { active, normal } = gate.template.material.stroke
const { active, normal } = gate.template.material.stroke
const selected =
(renderer.mouseState >> 2 &&
!!gatesInSelection(renderer.selectedArea, [gate]).length) ||
idIsSelected(renderer, gate.id)
const selected =
(renderer.mouseState >> 2 &&
!!gatesInSelection(renderer.selectedArea, [gate]).length) ||
idIsSelected(renderer, gate.id)
renderPins(ctx, renderer, gate, selected)
renderPins(ctx, renderer, gate, selected)
if (selected) {
ctx.strokeStyle = active
} else {
ctx.strokeStyle = normal
if (selected) {
ctx.strokeStyle = active
} else {
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()
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()
ctx.restore()
}