Add support for constants
This commit is contained in:
		
					parent
					
						
							
								7d9d2a2d78
							
						
					
				
			
			
				commit
				
					
						96e2184a24
					
				
			
		
					 19 changed files with 1086 additions and 1004 deletions
				
			
		
							
								
								
									
										6
									
								
								build.js
									
										
									
									
									
								
							
							
						
						
									
										6
									
								
								build.js
									
										
									
									
									
								
							|  | @ -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() | ||||
|  |  | |||
|  | @ -3,6 +3,7 @@ | |||
|     "version": "1.0.0", | ||||
|     "type": "module", | ||||
|     "scripts": { | ||||
|         "dev": "ESBUILD_SERVE=1 node ./build.js", | ||||
|         "build": "node ./build.js", | ||||
|         "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 | ||||
|  */ | ||||
| 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) | ||||
| }) | ||||
|  |  | |||
|  | @ -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> | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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' | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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.` | ||||
|       ) | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										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 | ||||
|  */ | ||||
| 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
											
										
									
								
							|  | @ -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 | ||||
| ) | ||||
|  |  | |||
|  | @ -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[] | ||||
| } | ||||
|  |  | |||
|  | @ -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)` | ||||
| } | ||||
|  |  | |||
|  | @ -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)) | ||||
|  |  | |||
|  | @ -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() | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue