more advanced selection
This commit is contained in:
parent
a9034559a3
commit
48b967ead8
|
@ -1,9 +1,4 @@
|
||||||
import { Screen } from '../../../modules/core/classes/Screen'
|
import { Screen } from '../../../modules/screen/helpers/Screen'
|
||||||
|
|
||||||
/**
|
|
||||||
* A screen instance used for the canvas clearing
|
|
||||||
*/
|
|
||||||
const screen = new Screen()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the used portion of the canvas
|
* Clears the used portion of the canvas
|
||||||
|
@ -11,5 +6,5 @@ const screen = new Screen()
|
||||||
* @param ctx the context to clear
|
* @param ctx the context to clear
|
||||||
*/
|
*/
|
||||||
export const clearCanvas = (ctx: CanvasRenderingContext2D) => {
|
export const clearCanvas = (ctx: CanvasRenderingContext2D) => {
|
||||||
ctx.clearRect(0, 0, screen.x, screen.y)
|
ctx.clearRect(0, 0, Screen.width, Screen.height)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,9 @@ export class Transform {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public getBoundingBox() {
|
public getBoundingBox() {
|
||||||
return [...this.position, ...this.scale] as vector4
|
const result = [...this.position, ...this.scale] as vector4
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPoints() {
|
public getPoints() {
|
||||||
|
@ -21,18 +23,7 @@ export class Transform {
|
||||||
this.y + this.width * combination[1]
|
this.y + this.width * combination[1]
|
||||||
])
|
])
|
||||||
|
|
||||||
const pointsInTheRightOrder = [
|
return points as vector2[]
|
||||||
points[0],
|
|
||||||
points[1],
|
|
||||||
points[3],
|
|
||||||
points[2]
|
|
||||||
] as vector2[]
|
|
||||||
|
|
||||||
const result = pointsInTheRightOrder.map(point =>
|
|
||||||
rotateAroundVector(point, this.center, this.rotation)
|
|
||||||
) as vector2[]
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEdges() {
|
public getEdges() {
|
||||||
|
@ -64,12 +55,20 @@ export class Transform {
|
||||||
return this.scale[1]
|
return this.scale[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get minX() {
|
||||||
|
return Math.min(this.x, this.x + this.width)
|
||||||
|
}
|
||||||
|
|
||||||
get maxX() {
|
get maxX() {
|
||||||
return this.x + this.width
|
return Math.max(this.x, this.x + this.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
get minY() {
|
||||||
|
return Math.min(this.y, this.y + this.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
get maxY() {
|
get maxY() {
|
||||||
return this.y + this.height
|
return Math.max(this.y, this.y + this.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
get center() {
|
get center() {
|
||||||
|
|
|
@ -3,9 +3,21 @@ import { vector2 } from '../types/vector2'
|
||||||
|
|
||||||
export const pointInSquare = (point: vector2, square: Transform) => {
|
export const pointInSquare = (point: vector2, square: Transform) => {
|
||||||
return (
|
return (
|
||||||
point[0] >= square.x &&
|
point[0] >= square.minX &&
|
||||||
point[0] <= square.maxX &&
|
point[0] <= square.maxX &&
|
||||||
point[1] >= square.y &&
|
point[1] >= square.minY &&
|
||||||
point[1] <= square.maxY
|
point[1] <= square.maxY
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The old version of pontInSquare
|
||||||
|
*/
|
||||||
|
export const oldPointInSquare = (point: vector2, square: Transform) => {
|
||||||
|
return (
|
||||||
|
point[0] >= square.x &&
|
||||||
|
point[0] <= square.x + square.width &&
|
||||||
|
point[1] >= square.y &&
|
||||||
|
point[1] <= square.y + square.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
import { Singleton } from '@eix-js/utils'
|
|
||||||
import { Observable, fromEvent, BehaviorSubject } from 'rxjs'
|
|
||||||
import { map } from 'rxjs/operators'
|
|
||||||
import { multiply } from '../../vector2/helpers/basic'
|
|
||||||
import { sidebarWidth } from '../components/Sidebar'
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
export class Screen {
|
|
||||||
private getWidth() {
|
|
||||||
return window.innerWidth - sidebarWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
public width = new BehaviorSubject<number>(this.getWidth())
|
|
||||||
public height = new BehaviorSubject<number>(window.innerHeight)
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
const resize = fromEvent(window, 'resize')
|
|
||||||
|
|
||||||
resize
|
|
||||||
.pipe(map(() => this.getWidth()))
|
|
||||||
.subscribe(val => this.width.next(val))
|
|
||||||
resize
|
|
||||||
.pipe(map(() => window.innerHeight))
|
|
||||||
.subscribe(val => this.height.next(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
public get x() {
|
|
||||||
return this.width.value
|
|
||||||
}
|
|
||||||
|
|
||||||
public get y() {
|
|
||||||
return this.height.value
|
|
||||||
}
|
|
||||||
|
|
||||||
public get center() {
|
|
||||||
return multiply([this.x, this.y], 0.5)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +1,9 @@
|
||||||
import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
|
import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
|
||||||
import { useObservable } from 'rxjs-hooks'
|
import { useObservable } from 'rxjs-hooks'
|
||||||
import { Screen } from '../classes/Screen'
|
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { mouseButton } from '../types/mouseButton'
|
import { mouseButton } from '../types/mouseButton'
|
||||||
import { MouseEventInfo } from './MouseEventInfo'
|
import { MouseEventInfo } from './MouseEventInfo'
|
||||||
|
import { width, height } from '../../screen/helpers/Screen'
|
||||||
const screen = new Screen()
|
|
||||||
|
|
||||||
export interface FluidCanvasProps {
|
export interface FluidCanvasProps {
|
||||||
mouseDownOuput: Subject<MouseEventInfo>
|
mouseDownOuput: Subject<MouseEventInfo>
|
||||||
|
@ -30,14 +28,14 @@ export const mouseEventHandler = (output: Subject<MouseEventInfo>) => (
|
||||||
|
|
||||||
const FluidCanvas = forwardRef(
|
const FluidCanvas = forwardRef(
|
||||||
(props: FluidCanvasProps, ref: RefObject<HTMLCanvasElement>) => {
|
(props: FluidCanvasProps, ref: RefObject<HTMLCanvasElement>) => {
|
||||||
const width = useObservable(() => screen.width, 0)
|
const currentWidth = useObservable(() => width, 0)
|
||||||
const height = useObservable(() => screen.height, 0)
|
const currentHeight = useObservable(() => height, 0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
ref={ref}
|
ref={ref}
|
||||||
width={width}
|
width={currentWidth}
|
||||||
height={height}
|
height={currentHeight}
|
||||||
onMouseDown={mouseEventHandler(props.mouseDownOuput)}
|
onMouseDown={mouseEventHandler(props.mouseDownOuput)}
|
||||||
onMouseUp={mouseEventHandler(props.mouseUpOutput)}
|
onMouseUp={mouseEventHandler(props.mouseUpOutput)}
|
||||||
onMouseMove={mouseEventHandler(props.mouseMoveOutput)}
|
onMouseMove={mouseEventHandler(props.mouseMoveOutput)}
|
||||||
|
|
|
@ -27,9 +27,10 @@ export const EnglishTranslation: Translation = {
|
||||||
actions: {
|
actions: {
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
clean: 'Clean',
|
clean: 'Clean',
|
||||||
clear: 'Clear',
|
|
||||||
refresh: 'Refresh',
|
refresh: 'Refresh',
|
||||||
undo: 'Undo'
|
undo: 'Undo',
|
||||||
|
'select all': 'Select all',
|
||||||
|
'delete selection': 'Delete selection'
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
createdSimulation: name => `Succesfully created simulation '${name}'`,
|
||||||
|
@ -38,7 +39,6 @@ export const EnglishTranslation: Translation = {
|
||||||
savedSimulation: name => `Succesfully saved simulation '${name}'`,
|
savedSimulation: name => `Succesfully saved simulation '${name}'`,
|
||||||
compiledIc: name => `Succesfully compiled circuit '${name}'`,
|
compiledIc: name => `Succesfully compiled circuit '${name}'`,
|
||||||
cleaned: name => `Succesfully cleaned simulation '${name}'`,
|
cleaned: name => `Succesfully cleaned simulation '${name}'`,
|
||||||
cleared: name => `Succesfully cleared simulation '${name}'`,
|
|
||||||
refreshed: name => `Succesfully refreshed simulation '${name}'`,
|
refreshed: name => `Succesfully refreshed simulation '${name}'`,
|
||||||
undone: name => `Succesfully undone simulation '${name}'`
|
undone: name => `Succesfully undone simulation '${name}'`
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,14 @@ export const DutchTranslation: Translation = {
|
||||||
simulation: 'Todo',
|
simulation: 'Todo',
|
||||||
language: 'Taal'
|
language: 'Taal'
|
||||||
},
|
},
|
||||||
|
actions: {
|
||||||
|
'delete selection': 'Todo',
|
||||||
|
'select all': 'Todo',
|
||||||
|
clean: 'Todo',
|
||||||
|
refresh: 'Todo',
|
||||||
|
save: 'Todo',
|
||||||
|
undo: 'Todo'
|
||||||
|
},
|
||||||
createSimulation: {
|
createSimulation: {
|
||||||
mode: {
|
mode: {
|
||||||
question: 'Wat voor simulatie wil je maken?',
|
question: 'Wat voor simulatie wil je maken?',
|
||||||
|
@ -30,6 +38,9 @@ export const DutchTranslation: Translation = {
|
||||||
switchedToSimulation: name =>
|
switchedToSimulation: name =>
|
||||||
`Succesvol veranderd naar simulatie '${name}'`,
|
`Succesvol veranderd naar simulatie '${name}'`,
|
||||||
savedSimulation: name => `Simulatie succesvol opgeslagen '${name}'`,
|
savedSimulation: name => `Simulatie succesvol opgeslagen '${name}'`,
|
||||||
compiledIc: name => `Todo: ${name}`
|
compiledIc: name => `Todo: ${name}`,
|
||||||
|
cleaned: name => `Todo ${name}`,
|
||||||
|
refreshed: name => `Todo ${name}`,
|
||||||
|
undone: name => `Todo ${name}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,12 @@ export const RomanianTranslation: Translation = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
save: 'Salvează'
|
save: 'Salvează',
|
||||||
|
'delete selection': 'Șterge selecția',
|
||||||
|
'select all': 'Selectează totul',
|
||||||
|
clean: 'Curăță',
|
||||||
|
refresh: 'Reîncarcă',
|
||||||
|
undo: 'Întoarce'
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
createdSimulation: name =>
|
createdSimulation: name =>
|
||||||
|
@ -33,6 +38,9 @@ export const RomanianTranslation: Translation = {
|
||||||
switchedToSimulation: name =>
|
switchedToSimulation: name =>
|
||||||
`Simulația '${name}' a fost deschisă cu succes`,
|
`Simulația '${name}' a fost deschisă cu succes`,
|
||||||
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`,
|
savedSimulation: name => `Simulația '${name}' a fost salvată cu succes`,
|
||||||
compiledIc: name => `Simulația '${name}' a fost compilată cu succes`
|
compiledIc: name => `Simulația '${name}' a fost compilată cu succes`,
|
||||||
|
cleaned: name => `Simulația '${name}' a fost curățată cu succes`,
|
||||||
|
refreshed: name => `Simulația '${name}' a fost reîncărcată cu succes`,
|
||||||
|
undone: name => `Acțiunea a fost întoarsă`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ export interface Translation {
|
||||||
savedSimulation: NameSentence
|
savedSimulation: NameSentence
|
||||||
compiledIc: NameSentence
|
compiledIc: NameSentence
|
||||||
refreshed: NameSentence
|
refreshed: NameSentence
|
||||||
cleared: NameSentence
|
|
||||||
cleaned: NameSentence
|
cleaned: NameSentence
|
||||||
undone: NameSentence
|
undone: NameSentence
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,8 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
name: 'or'
|
name: 'or'
|
||||||
},
|
},
|
||||||
material: {
|
material: {
|
||||||
value: 'yellow'
|
type: 'image',
|
||||||
|
value: require('../../assets/or_gate.png')
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
activation: `context.set(0, context.get(0) || context.get(1))`
|
activation: `context.set(0, context.get(0) || context.get(1))`
|
||||||
|
@ -38,6 +39,24 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
|
||||||
},
|
},
|
||||||
info: ['https://en.wikipedia.org/wiki/OR_gate']
|
info: ['https://en.wikipedia.org/wiki/OR_gate']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: 'nor'
|
||||||
|
},
|
||||||
|
material: {
|
||||||
|
type: 'image',
|
||||||
|
value: require('../../assets/nor_gate.png')
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
activation: `context.set(0, !(context.get(0) || context.get(1)))`
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
inputs: {
|
||||||
|
count: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
info: ['https://en.wikipedia.org/wiki/NOR_gate']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
metadata: {
|
metadata: {
|
||||||
name: 'xor'
|
name: 'xor'
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationR
|
||||||
export const dumpSimulation = (renderer: SimulationRenderer) => {
|
export const dumpSimulation = (renderer: SimulationRenderer) => {
|
||||||
renderer.simulation.dispose()
|
renderer.simulation.dispose()
|
||||||
renderer.lastMousePosition = [0, 0]
|
renderer.lastMousePosition = [0, 0]
|
||||||
renderer.selectedGates = new Set()
|
renderer.clearSelection()
|
||||||
renderer.selectedPins = {
|
renderer.selectedPins = {
|
||||||
end: null,
|
end: null,
|
||||||
start: null
|
start: null
|
||||||
|
|
27
src/modules/screen/helpers/Screen.ts
Normal file
27
src/modules/screen/helpers/Screen.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { BehaviorSubject, fromEvent } from 'rxjs'
|
||||||
|
import { map } from 'rxjs/operators'
|
||||||
|
import { getWidth } from '../helpers/getWidth'
|
||||||
|
|
||||||
|
const width = new BehaviorSubject(getWidth())
|
||||||
|
const height = new BehaviorSubject(window.innerHeight)
|
||||||
|
|
||||||
|
const resize = fromEvent(window, 'resize')
|
||||||
|
|
||||||
|
resize.pipe(map(getWidth)).subscribe(val => width.next(val))
|
||||||
|
resize.pipe(map(() => window.innerHeight)).subscribe(val => height.next(val))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main screen transform
|
||||||
|
*/
|
||||||
|
const Screen = new Transform()
|
||||||
|
|
||||||
|
width.subscribe(currentWidth => {
|
||||||
|
Screen.width = currentWidth
|
||||||
|
})
|
||||||
|
|
||||||
|
height.subscribe(currentHeight => {
|
||||||
|
Screen.height = currentHeight
|
||||||
|
})
|
||||||
|
|
||||||
|
export { Screen, height, width }
|
6
src/modules/screen/helpers/getWidth.ts
Normal file
6
src/modules/screen/helpers/getWidth.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { sidebarWidth } from '../../core/components/Sidebar'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the width of the canvas
|
||||||
|
*/
|
||||||
|
export const getWidth = () => window.innerWidth - sidebarWidth
|
|
@ -7,6 +7,7 @@ import MenuItem from '@material-ui/core/MenuItem'
|
||||||
import Icon from '@material-ui/core/Icon'
|
import Icon from '@material-ui/core/Icon'
|
||||||
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
import { useTranslation } from '../../internalisation/helpers/useLanguage'
|
||||||
import { SidebarActions } from '../constants'
|
import { SidebarActions } from '../constants'
|
||||||
|
import { possibleAction } from '../types/possibleAction'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component wich contains the sidebar 'Simulation' button
|
* Component wich contains the sidebar 'Simulation' button
|
||||||
|
@ -49,7 +50,11 @@ const SimulationActions = () => {
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={name}
|
primary={
|
||||||
|
translation.actions[
|
||||||
|
name as possibleAction
|
||||||
|
]
|
||||||
|
}
|
||||||
secondary={(keybinding || []).join(' + ')}
|
secondary={(keybinding || []).join(' + ')}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
|
@ -4,14 +4,16 @@ import { save } from '../saving/helpers/save'
|
||||||
import { refresh } from './helpers/refresh'
|
import { refresh } from './helpers/refresh'
|
||||||
import { undo } from './helpers/undo'
|
import { undo } from './helpers/undo'
|
||||||
import { createActionConfig } from './helpers/createActionConfig'
|
import { createActionConfig } from './helpers/createActionConfig'
|
||||||
import { clear } from './helpers/clear'
|
import { selectAll } from './helpers/selectAll'
|
||||||
|
import { deleteSelection } from './helpers/deleteSelection'
|
||||||
|
|
||||||
export const actionIcons: Record<possibleAction, string> = {
|
export const actionIcons: Record<possibleAction, string> = {
|
||||||
clean: 'layers_clear',
|
clean: 'clear',
|
||||||
clear: 'clear',
|
|
||||||
refresh: 'refresh',
|
refresh: 'refresh',
|
||||||
save: 'save',
|
save: 'save',
|
||||||
undo: 'undo'
|
undo: 'undo',
|
||||||
|
'select all': 'select_all',
|
||||||
|
'delete selection': 'delete'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,13 +35,6 @@ export const SidebarActions: Record<possibleAction, SidebarAction> = {
|
||||||
},
|
},
|
||||||
['ctrl', 'z']
|
['ctrl', 'z']
|
||||||
),
|
),
|
||||||
...createActionConfig(
|
|
||||||
'clear',
|
|
||||||
{
|
|
||||||
run: clear
|
|
||||||
},
|
|
||||||
['ctrl', 'delete']
|
|
||||||
),
|
|
||||||
...createActionConfig(
|
...createActionConfig(
|
||||||
'clean',
|
'clean',
|
||||||
{
|
{
|
||||||
|
@ -47,6 +42,8 @@ export const SidebarActions: Record<possibleAction, SidebarAction> = {
|
||||||
console.log('Cleaning')
|
console.log('Cleaning')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
['ctrl', 'shift', 'delete']
|
['ctrl', 'delete']
|
||||||
)
|
),
|
||||||
|
...createActionConfig('select all', selectAll, ['ctrl', 'a']),
|
||||||
|
...createActionConfig('delete selection', deleteSelection, ['delete'])
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { createRendererAction } from './createRendererActions'
|
||||||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
|
import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
|
||||||
|
|
||||||
export type ActionConfigFunction = (renderer?: SimulationRenderer) => void
|
export type ActionConfigFunction = (renderer: SimulationRenderer) => void
|
||||||
|
|
||||||
export type ActionConfigCallback =
|
export type ActionConfigCallback =
|
||||||
| {
|
| {
|
||||||
|
|
|
@ -10,12 +10,10 @@ import { Translation } from '../../internalisation/types/TranslationInterface'
|
||||||
/**
|
/**
|
||||||
* Map used to get the correct message from any action name
|
* Map used to get the correct message from any action name
|
||||||
*/
|
*/
|
||||||
export const actionToMessageMap: Record<
|
export const actionToMessageMap: Partial<
|
||||||
possibleAction,
|
Record<possibleAction, keyof Translation['messages']>
|
||||||
keyof Translation['messages']
|
|
||||||
> = {
|
> = {
|
||||||
clean: 'cleaned',
|
clean: 'cleaned',
|
||||||
clear: 'cleared',
|
|
||||||
refresh: 'refreshed',
|
refresh: 'refreshed',
|
||||||
undo: 'undone',
|
undo: 'undone',
|
||||||
save: 'savedSimulation'
|
save: 'savedSimulation'
|
||||||
|
@ -30,12 +28,14 @@ export const createRendererAction = (
|
||||||
|
|
||||||
callback(renderer)
|
callback(renderer)
|
||||||
|
|
||||||
|
const messageName = actionToMessageMap[action]
|
||||||
|
|
||||||
|
if (messageName) {
|
||||||
toast(
|
toast(
|
||||||
...createToastArguments(
|
...createToastArguments(
|
||||||
translation.messages[actionToMessageMap[action]](
|
translation.messages[messageName](renderer.simulation.name),
|
||||||
renderer.simulation.name
|
|
||||||
),
|
|
||||||
actionIcons[action]
|
actionIcons[action]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
import { deleteGate } from '../../simulationRenderer/helpers/deleteGate'
|
||||||
|
|
||||||
|
export const deleteSelection = (renderer: SimulationRenderer) => {
|
||||||
|
for (const gate of renderer.getSelected()) {
|
||||||
|
deleteGate(renderer.simulation, gate)
|
||||||
|
}
|
||||||
|
}
|
13
src/modules/simulation-actions/helpers/selectAll.ts
Normal file
13
src/modules/simulation-actions/helpers/selectAll.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
|
import { addIdToSelection } from '../../simulationRenderer/helpers/idIsSelected'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects all the gates of an renderer
|
||||||
|
*
|
||||||
|
* @param renderer The renderer to selet all the gates of
|
||||||
|
*/
|
||||||
|
export const selectAll = (renderer: SimulationRenderer) => {
|
||||||
|
for (const { id } of renderer.simulation.gates) {
|
||||||
|
addIdToSelection(renderer, 'permanent', id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,10 @@
|
||||||
/**
|
/**
|
||||||
* Type repressenting al lpossible actions
|
* Type repressenting al lpossible actions
|
||||||
*/
|
*/
|
||||||
export type possibleAction = 'save' | 'clear' | 'clean' | 'refresh' | 'undo'
|
export type possibleAction =
|
||||||
|
| 'save'
|
||||||
|
| 'clean'
|
||||||
|
| 'refresh'
|
||||||
|
| 'undo'
|
||||||
|
| 'select all'
|
||||||
|
| 'delete selection'
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { templateStore } from '../../saving/stores/templateStore'
|
import { templateStore } from '../../saving/stores/templateStore'
|
||||||
import { SimulationError } from '../../errors/classes/SimulationError'
|
import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { Simulation } from '../classes/Simulation'
|
|
||||||
import { Gate } from '../classes/Gate'
|
import { Gate } from '../classes/Gate'
|
||||||
import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
|
import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
|
||||||
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
|
||||||
import { DefaultGateTemplate } from '../constants'
|
import { DefaultGateTemplate } from '../constants'
|
||||||
import { vector2 } from '../../../common/math/classes/Transform'
|
import { vector2 } from '../../../common/math/classes/Transform'
|
||||||
|
import { Screen } from '../../screen/helpers/Screen'
|
||||||
|
|
||||||
export const addGate = (renderer: SimulationRenderer, templateName: string) => {
|
export const addGate = (renderer: SimulationRenderer, templateName: string) => {
|
||||||
const template = templateStore.get(templateName)
|
const template = templateStore.get(templateName)
|
||||||
|
@ -22,7 +22,7 @@ export const addGate = (renderer: SimulationRenderer, templateName: string) => {
|
||||||
|
|
||||||
const origin = relativeTo(
|
const origin = relativeTo(
|
||||||
multiply(gateScale, 0.5),
|
multiply(gateScale, 0.5),
|
||||||
relativeTo(renderer.camera.transform.position, renderer.screen.center)
|
relativeTo(renderer.camera.transform.position, Screen.center)
|
||||||
)
|
)
|
||||||
|
|
||||||
const scalarOffset = renderer.options.spawning.spawnOffset
|
const scalarOffset = renderer.options.spawning.spawnOffset
|
||||||
|
|
|
@ -1,20 +1,9 @@
|
||||||
import { Transform } from '../../../common/math/classes/Transform'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { Screen } from '../../core/classes/Screen'
|
|
||||||
import { relativeTo } from '../../vector2/helpers/basic'
|
|
||||||
|
|
||||||
export class Camera {
|
export class Camera {
|
||||||
public transform = new Transform([0, 0])
|
public transform = new Transform([0, 0])
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
// this.screen.height.subscribe(value => {
|
|
||||||
// this.transform.height = value
|
|
||||||
// })
|
|
||||||
// this.screen.width.subscribe(value => {
|
|
||||||
// this.transform.width = value
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
|
|
||||||
public toWordPostition(position: vector2) {
|
public toWordPostition(position: vector2) {
|
||||||
return [
|
return [
|
||||||
(position[0] - this.transform.position[0]) /
|
(position[0] - this.transform.position[0]) /
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { Camera } from './Camera'
|
||||||
import { Simulation } from '../../simulation/classes/Simulation'
|
import { Simulation } from '../../simulation/classes/Simulation'
|
||||||
import { Subject } from 'rxjs'
|
import { Subject } from 'rxjs'
|
||||||
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
||||||
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
import {
|
||||||
|
oldPointInSquare,
|
||||||
|
pointInSquare
|
||||||
|
} from '../../../common/math/helpers/pointInSquare'
|
||||||
import { vector2 } from '../../../common/math/types/vector2'
|
import { vector2 } from '../../../common/math/types/vector2'
|
||||||
import { Screen } from '../../core/classes/Screen'
|
|
||||||
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
import { relativeTo, add, invert } from '../../vector2/helpers/basic'
|
||||||
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
|
||||||
import { defaultSimulationRendererOptions, mouseButtons } from '../constants'
|
import { defaultSimulationRendererOptions, mouseButtons } from '../constants'
|
||||||
|
@ -12,8 +14,6 @@ import { getPinPosition } from '../helpers/pinPosition'
|
||||||
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
|
||||||
import { SelectedPins } from '../types/SelectedPins'
|
import { SelectedPins } from '../types/SelectedPins'
|
||||||
import { Wire } from '../../simulation/classes/Wire'
|
import { Wire } from '../../simulation/classes/Wire'
|
||||||
import { KeyBindingMap } from '../../keybindings/types/KeyBindingMap'
|
|
||||||
import { initKeyBindings } from '../../keybindings/helpers/initialiseKeyBindings'
|
|
||||||
import { currentStore } from '../../saving/stores/currentStore'
|
import { currentStore } from '../../saving/stores/currentStore'
|
||||||
import { saveStore } from '../../saving/stores/saveStore'
|
import { saveStore } from '../../saving/stores/saveStore'
|
||||||
import {
|
import {
|
||||||
|
@ -21,7 +21,6 @@ import {
|
||||||
fromCameraState
|
fromCameraState
|
||||||
} from '../../saving/helpers/fromState'
|
} from '../../saving/helpers/fromState'
|
||||||
import merge from 'deepmerge'
|
import merge from 'deepmerge'
|
||||||
import { wireConnectedToGate } from '../helpers/wireConnectedToGate'
|
|
||||||
import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
|
import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
|
||||||
import { RefObject } from 'react'
|
import { RefObject } from 'react'
|
||||||
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
|
||||||
|
@ -30,7 +29,10 @@ import { SimulationError } from '../../errors/classes/SimulationError'
|
||||||
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
import { deleteWire } from '../../simulation/helpers/deleteWire'
|
||||||
import { RendererState } from '../../saving/types/SimulationSave'
|
import { RendererState } from '../../saving/types/SimulationSave'
|
||||||
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
|
import { setToArray } from '../../../common/lang/arrays/helpers/setToArray'
|
||||||
import { Selection } from '../types/Selection'
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { gatesInSelection } from '../helpers/gatesInSelection'
|
||||||
|
import { selectionType } from '../types/selectionType'
|
||||||
|
import { addIdToSelection } from '../helpers/idIsSelected'
|
||||||
|
|
||||||
export class SimulationRenderer {
|
export class SimulationRenderer {
|
||||||
public mouseDownOutput = new Subject<MouseEventInfo>()
|
public mouseDownOutput = new Subject<MouseEventInfo>()
|
||||||
|
@ -38,17 +40,22 @@ export class SimulationRenderer {
|
||||||
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
public mouseMoveOutput = new Subject<MouseEventInfo>()
|
||||||
public wheelOutput = new Subject<unknown>()
|
public wheelOutput = new Subject<unknown>()
|
||||||
|
|
||||||
public selectedGates = new Set<Selection>()
|
public selectedGates: Record<selectionType, Set<number>> = {
|
||||||
public lastMousePosition: vector2 = [0, 0]
|
temporary: new Set<number>(),
|
||||||
|
permanent: new Set<number>()
|
||||||
|
}
|
||||||
|
|
||||||
public options: SimulationRendererOptions
|
public options: SimulationRendererOptions
|
||||||
public screen = new Screen()
|
|
||||||
public camera = new Camera()
|
public camera = new Camera()
|
||||||
|
|
||||||
|
public selectedArea = new Transform()
|
||||||
|
|
||||||
// first bit = dragging
|
// first bit = dragging
|
||||||
// second bit = panning around
|
// second bit = panning around
|
||||||
// third bit = selecting
|
// third bit = selecting
|
||||||
private mouseState = 0b000
|
public mouseState = 0b000
|
||||||
private gateSelectionOffset: vector2 = [0, 0]
|
|
||||||
|
public lastMousePosition: vector2 = [0, 0]
|
||||||
|
|
||||||
// this is used for spawning gates
|
// this is used for spawning gates
|
||||||
public spawnCount = 0
|
public spawnCount = 0
|
||||||
|
@ -89,14 +96,7 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
this.mouseState |= 1
|
this.mouseState |= 1
|
||||||
|
|
||||||
this.selectedGates.add({
|
addIdToSelection(this, 'temporary', id)
|
||||||
id,
|
|
||||||
permanent: false
|
|
||||||
})
|
|
||||||
this.gateSelectionOffset = worldPosition.map(
|
|
||||||
(position, index) =>
|
|
||||||
position - transform.position[index]
|
|
||||||
) as vector2
|
|
||||||
|
|
||||||
const gateNode = this.simulation.gates.get(id)
|
const gateNode = this.simulation.gates.get(id)
|
||||||
|
|
||||||
|
@ -132,10 +132,10 @@ export class SimulationRenderer {
|
||||||
`Cannot find wire to remove`
|
`Cannot find wire to remove`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.selectedPins.start &&
|
this.selectedPins.start &&
|
||||||
|
@ -177,29 +177,59 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.button === mouseButtons.unselect) {
|
||||||
|
this.clearSelection()
|
||||||
|
}
|
||||||
|
|
||||||
if (event.button === mouseButtons.pan) {
|
if (event.button === mouseButtons.pan) {
|
||||||
|
// the second bit = pannning
|
||||||
this.mouseState |= 0b10
|
this.mouseState |= 0b10
|
||||||
|
} else if (event.button === mouseButtons.select) {
|
||||||
|
this.selectedArea.position = this.lastMousePosition
|
||||||
|
this.selectedArea.scale = [0, 0]
|
||||||
|
|
||||||
|
// the third bit = selecting
|
||||||
|
this.mouseState |= 0b100
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseUpOutput.subscribe(() => {
|
this.mouseUpOutput.subscribe(event => {
|
||||||
if (this.selectedGates.size) {
|
if (event.button === mouseButtons.drag) {
|
||||||
const selected = this.getSelected()
|
const selected = this.getSelected()
|
||||||
|
|
||||||
for (const gate of selected) {
|
for (const gate of selected) {
|
||||||
gate.transform.rotation = 0
|
gate.transform.rotation = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const selection of this.selectedGates.values()) {
|
this.selectedGates.temporary.clear()
|
||||||
if (!selection.permanent) {
|
|
||||||
this.selectedGates.delete(selection)
|
// turn first 2 bits to 0
|
||||||
|
this.mouseState &= 1 << 2
|
||||||
|
|
||||||
|
// for debugging
|
||||||
|
if ((this.mouseState >> 1) & 1 || this.mouseState & 1) {
|
||||||
|
throw new SimulationError(
|
||||||
|
'First 2 bits of mouseState need to be set to 0'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mouseState &= 0
|
if (
|
||||||
}
|
event.button === mouseButtons.select &&
|
||||||
|
(this.mouseState >> 2) & 1
|
||||||
|
) {
|
||||||
|
// turn the third bit to 0
|
||||||
|
this.mouseState &= (1 << 2) - 1
|
||||||
|
|
||||||
this.mouseState &= 0b00
|
const selectedGates = gatesInSelection(
|
||||||
|
this.selectedArea,
|
||||||
|
Array.from(this.simulation.gates)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const { id } of selectedGates) {
|
||||||
|
addIdToSelection(this, 'permanent', id)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mouseMoveOutput.subscribe(event => {
|
this.mouseMoveOutput.subscribe(event => {
|
||||||
|
@ -207,22 +237,22 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
const worldPosition = this.camera.toWordPostition(event.position)
|
const worldPosition = this.camera.toWordPostition(event.position)
|
||||||
|
|
||||||
if (this.mouseState & 1 && this.selectedGates.size) {
|
|
||||||
for (const gate of this.getSelected()) {
|
|
||||||
const { transform } = gate
|
|
||||||
|
|
||||||
transform.x = worldPosition[0] - this.gateSelectionOffset[0]
|
|
||||||
transform.y = worldPosition[1] - this.gateSelectionOffset[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((this.mouseState >> 1) & 1) {
|
|
||||||
const offset = invert(
|
const offset = invert(
|
||||||
relativeTo(this.lastMousePosition, worldPosition)
|
relativeTo(this.lastMousePosition, worldPosition)
|
||||||
).map(
|
).map(
|
||||||
(value, index) => value * this.camera.transform.scale[index]
|
(value, index) => value * this.camera.transform.scale[index]
|
||||||
) as vector2
|
) as vector2
|
||||||
|
|
||||||
|
if (this.mouseState & 1) {
|
||||||
|
for (const gate of this.getSelected()) {
|
||||||
|
const { transform } = gate
|
||||||
|
|
||||||
|
transform.x -= offset[0]
|
||||||
|
transform.y -= offset[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.mouseState >> 1) & 1) {
|
||||||
this.camera.transform.position = add(
|
this.camera.transform.position = add(
|
||||||
this.camera.transform.position,
|
this.camera.transform.position,
|
||||||
invert(offset)
|
invert(offset)
|
||||||
|
@ -231,13 +261,17 @@ export class SimulationRenderer {
|
||||||
this.spawnCount = 0
|
this.spawnCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((this.mouseState >> 2) & 1) {
|
||||||
|
this.selectedArea.scale = relativeTo(
|
||||||
|
this.selectedArea.position,
|
||||||
|
this.camera.toWordPostition(event.position)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this.lastMousePosition = this.camera.toWordPostition(event.position)
|
this.lastMousePosition = this.camera.toWordPostition(event.position)
|
||||||
})
|
})
|
||||||
|
|
||||||
dumpSimulation(this)
|
|
||||||
|
|
||||||
this.reloadSave()
|
this.reloadSave()
|
||||||
this.initKeyBindings()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateWheelListener() {
|
public updateWheelListener() {
|
||||||
|
@ -259,6 +293,8 @@ export class SimulationRenderer {
|
||||||
|
|
||||||
public reloadSave() {
|
public reloadSave() {
|
||||||
try {
|
try {
|
||||||
|
dumpSimulation(this)
|
||||||
|
|
||||||
const current = currentStore.get()
|
const current = currentStore.get()
|
||||||
const save = saveStore.get(current)
|
const save = saveStore.get(current)
|
||||||
|
|
||||||
|
@ -275,42 +311,6 @@ export class SimulationRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initKeyBindings() {
|
|
||||||
const bindings: KeyBindingMap = [
|
|
||||||
{
|
|
||||||
keys: ['delete'],
|
|
||||||
actions: [
|
|
||||||
() => {
|
|
||||||
for (const gate of this.getSelected()) {
|
|
||||||
const node = this.simulation.gates.get(gate.id)
|
|
||||||
|
|
||||||
if (!node) continue
|
|
||||||
|
|
||||||
for (const wire of this.simulation.wires) {
|
|
||||||
if (wireConnectedToGate(gate, wire)) {
|
|
||||||
wire.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.simulation.wires = this.simulation.wires.filter(
|
|
||||||
wire => wire.active
|
|
||||||
)
|
|
||||||
|
|
||||||
gate.dispose()
|
|
||||||
this.simulation.gates.delete(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
initKeyBindings(bindings)
|
|
||||||
}
|
|
||||||
|
|
||||||
public getGateById(id: number) {
|
|
||||||
return this.simulation.gates.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all selected gates in the simulation
|
* Gets all selected gates in the simulation
|
||||||
*
|
*
|
||||||
|
@ -318,7 +318,7 @@ export class SimulationRenderer {
|
||||||
* @throws SimulationError if the id doesnt have a data prop
|
* @throws SimulationError if the id doesnt have a data prop
|
||||||
*/
|
*/
|
||||||
public getSelected() {
|
public getSelected() {
|
||||||
return setToArray(this.selectedGates).map(({ id }) => {
|
return setToArray(this.allSelectedIds()).map(id => {
|
||||||
const gate = this.simulation.gates.get(id)
|
const gate = this.simulation.gates.get(id)
|
||||||
|
|
||||||
if (!gate) {
|
if (!gate) {
|
||||||
|
@ -332,4 +332,22 @@ export class SimulationRenderer {
|
||||||
return gate.data
|
return gate.data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* helper to merge the temporary and permanent selection
|
||||||
|
*/
|
||||||
|
public allSelectedIds() {
|
||||||
|
return new Set([
|
||||||
|
...setToArray(this.selectedGates.permanent),
|
||||||
|
...setToArray(this.selectedGates.temporary)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to clear all selected sets
|
||||||
|
*/
|
||||||
|
public clearSelection() {
|
||||||
|
this.selectedGates.permanent.clear()
|
||||||
|
this.selectedGates.temporary.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,17 +27,22 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
|
||||||
},
|
},
|
||||||
spawning: {
|
spawning: {
|
||||||
spawnOffset: 30
|
spawnOffset: 30
|
||||||
|
},
|
||||||
|
selecting: {
|
||||||
|
fill: 'rgba(128,128,128,0.3)',
|
||||||
|
stroke: `rgba(128,128,128,0.7)`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const imageQuality: vector2 = [100, 100]
|
export const imageQuality: vector2 = [100, 100]
|
||||||
|
|
||||||
export const mouseButtons: Record<
|
export const mouseButtons: Record<
|
||||||
'zoom' | 'pan' | 'drag' | 'select',
|
'zoom' | 'pan' | 'drag' | 'select' | 'unselect',
|
||||||
mouseButton
|
mouseButton
|
||||||
> = {
|
> = {
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
drag: 0,
|
drag: 2,
|
||||||
pan: 0,
|
pan: 2,
|
||||||
select: 2
|
select: 0,
|
||||||
|
unselect: 0
|
||||||
}
|
}
|
||||||
|
|
10
src/modules/simulationRenderer/helpers/aabb.ts
Normal file
10
src/modules/simulationRenderer/helpers/aabb.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
|
||||||
|
export const aabbCollisionDetection = (rect1: Transform, rect2: Transform) => {
|
||||||
|
return !(
|
||||||
|
rect1.maxX < rect2.minX ||
|
||||||
|
rect1.maxY < rect2.minY ||
|
||||||
|
rect1.minX > rect2.maxX ||
|
||||||
|
rect1.minY > rect2.maxY
|
||||||
|
)
|
||||||
|
}
|
29
src/modules/simulationRenderer/helpers/deleteGate.ts
Normal file
29
src/modules/simulationRenderer/helpers/deleteGate.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
import { Gate } from '../../simulation/classes/Gate'
|
||||||
|
import { wireConnectedToGate } from './wireConnectedToGate'
|
||||||
|
import { Simulation } from '../../simulation/classes/Simulation'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to delete a gate from a simulation
|
||||||
|
*
|
||||||
|
* @param simulation The simulation to remove the gate from
|
||||||
|
* @param gate The gate to remove
|
||||||
|
*/
|
||||||
|
export const deleteGate = (simulation: Simulation, gate: Gate) => {
|
||||||
|
const node = simulation.gates.get(gate.id)
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wire of simulation.wires) {
|
||||||
|
if (wireConnectedToGate(gate, wire)) {
|
||||||
|
wire.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
simulation.wires = simulation.wires.filter(wire => wire.active)
|
||||||
|
|
||||||
|
gate.dispose()
|
||||||
|
simulation.gates.delete(node)
|
||||||
|
}
|
24
src/modules/simulationRenderer/helpers/gatesInSelection.ts
Normal file
24
src/modules/simulationRenderer/helpers/gatesInSelection.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { Gate } from '../../simulation/classes/Gate'
|
||||||
|
import { aabbCollisionDetection } from './aabb'
|
||||||
|
import { Transform } from '../../../common/math/classes/Transform'
|
||||||
|
import { pointInSquare } from '../../../common/math/helpers/pointInSquare'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds all selections in the selected area
|
||||||
|
*
|
||||||
|
* @param renderer The renderer to find the selected gates of
|
||||||
|
*/
|
||||||
|
export const gatesInSelection = (
|
||||||
|
selectedArea: Transform,
|
||||||
|
gates: Gate[] = []
|
||||||
|
) => {
|
||||||
|
return gates.filter(({ transform }) => {
|
||||||
|
for (const point of transform.getPoints()) {
|
||||||
|
if (pointInSquare(point, selectedArea)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
36
src/modules/simulationRenderer/helpers/idIsSelected.ts
Normal file
36
src/modules/simulationRenderer/helpers/idIsSelected.ts
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
import { selectionType } from '../types/selectionType'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an id is selected inside a renderer
|
||||||
|
*
|
||||||
|
* @param renderer The renderer to check for the id
|
||||||
|
* @param gateId The id of the gate
|
||||||
|
*/
|
||||||
|
export const idIsSelected = (renderer: SimulationRenderer, gateId: number) => {
|
||||||
|
return renderer.allSelectedIds().has(gateId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an id to a selection set
|
||||||
|
*
|
||||||
|
* @param renderer The renderer to add the id to the selection set of
|
||||||
|
* @param type The selection type
|
||||||
|
* @param id The id to select
|
||||||
|
*/
|
||||||
|
export const addIdToSelection = (
|
||||||
|
renderer: SimulationRenderer,
|
||||||
|
type: selectionType = 'temporary',
|
||||||
|
id: number
|
||||||
|
) => {
|
||||||
|
if (idIsSelected(renderer, id)) {
|
||||||
|
if (renderer.selectedGates.permanent.has(id)) {
|
||||||
|
return
|
||||||
|
} else if (type === 'temporary') {
|
||||||
|
renderer.selectedGates.temporary.delete(id)
|
||||||
|
renderer.selectedGates.permanent.add(id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
renderer.selectedGates[type].add(id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ import { useTransform } from '../../../common/canvas/helpers/useTransform'
|
||||||
import { roundRect } from '../../../common/canvas/helpers/drawRoundedSquare'
|
import { roundRect } from '../../../common/canvas/helpers/drawRoundedSquare'
|
||||||
import { roundImage } from '../../../common/canvas/helpers/drawRoundedImage'
|
import { roundImage } from '../../../common/canvas/helpers/drawRoundedImage'
|
||||||
import { ImageStore } from '../stores/imageStore'
|
import { ImageStore } from '../stores/imageStore'
|
||||||
|
import { gatesInSelection } from './gatesInSelection'
|
||||||
|
import { idIsSelected } from './idIsSelected'
|
||||||
|
|
||||||
export const renderGate = (
|
export const renderGate = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -13,7 +15,11 @@ export const renderGate = (
|
||||||
) => {
|
) => {
|
||||||
renderPins(ctx, renderer, gate)
|
renderPins(ctx, renderer, gate)
|
||||||
|
|
||||||
if (renderer.selectedGates.has(gate.id)) {
|
if (
|
||||||
|
((renderer.mouseState >> 2) & 1 &&
|
||||||
|
gatesInSelection(renderer.selectedArea, [gate]).length) ||
|
||||||
|
idIsSelected(renderer, gate.id)
|
||||||
|
) {
|
||||||
ctx.strokeStyle = renderer.options.gates.gateStroke.active
|
ctx.strokeStyle = renderer.options.gates.gateStroke.active
|
||||||
} else {
|
} else {
|
||||||
ctx.strokeStyle = renderer.options.gates.gateStroke.normal
|
ctx.strokeStyle = renderer.options.gates.gateStroke.normal
|
||||||
|
|
22
src/modules/simulationRenderer/helpers/renderSelectedArea.ts
Normal file
22
src/modules/simulationRenderer/helpers/renderSelectedArea.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { SimulationRenderer } from '../classes/SimulationRenderer'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the selected area of a renderer
|
||||||
|
*
|
||||||
|
* @param ctx The context to draw on
|
||||||
|
* @param renderer The renderer to draw the selected area of
|
||||||
|
*/
|
||||||
|
export const renderSelectedArea = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
renderer: SimulationRenderer
|
||||||
|
) => {
|
||||||
|
if (renderer.mouseState >> 2) {
|
||||||
|
ctx.fillStyle = renderer.options.selecting.fill
|
||||||
|
ctx.strokeStyle = renderer.options.selecting.stroke
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.rect(...renderer.selectedArea.getBoundingBox())
|
||||||
|
ctx.fill()
|
||||||
|
ctx.stroke()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import { renderGate } from './renderGate'
|
||||||
import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
|
import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
|
||||||
import { renderClickedPins } from './renderClickedPins'
|
import { renderClickedPins } from './renderClickedPins'
|
||||||
import { renderWires } from './renderWires'
|
import { renderWires } from './renderWires'
|
||||||
import { vector2 } from '../../../common/math/classes/Transform'
|
import { renderSelectedArea } from './renderSelectedArea'
|
||||||
|
|
||||||
export const renderSimulation = (
|
export const renderSimulation = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
|
@ -26,6 +26,7 @@ export const renderSimulation = (
|
||||||
}
|
}
|
||||||
|
|
||||||
renderClickedPins(ctx, renderer)
|
renderClickedPins(ctx, renderer)
|
||||||
|
renderSelectedArea(ctx, renderer)
|
||||||
|
|
||||||
ctx.scale(...inverse(transform.scale))
|
ctx.scale(...inverse(transform.scale))
|
||||||
ctx.translate(...invert(transform.position))
|
ctx.translate(...invert(transform.position))
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
import { Screen } from '../../core/classes/Screen'
|
|
||||||
import { clamp } from '../../simulation/helpers/clamp'
|
import { clamp } from '../../simulation/helpers/clamp'
|
||||||
import { Camera } from '../classes/Camera'
|
import { Camera } from '../classes/Camera'
|
||||||
import { vector2 } from '../../../common/math/classes/Transform'
|
import { vector2 } from '../../../common/math/classes/Transform'
|
||||||
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
import { MouseEventInfo } from '../../core/components/MouseEventInfo'
|
||||||
// import { WheelEvent } from 'react'
|
import { Screen } from '../../screen/helpers/Screen'
|
||||||
|
|
||||||
const screen = new Screen()
|
|
||||||
|
|
||||||
const scrollStep = 1.3
|
const scrollStep = 1.3
|
||||||
const zoomLimits = [0.1, 10]
|
const zoomLimits = [0.1, 10]
|
||||||
|
|
||||||
let absoluteMousePosition = [screen.x / 2, screen.y / 2]
|
let absoluteMousePosition = [Screen.width / 2, Screen.height]
|
||||||
|
|
||||||
export const updateMouse = (e: MouseEventInfo) => {
|
export const updateMouse = (e: MouseEventInfo) => {
|
||||||
absoluteMousePosition = e.position
|
absoluteMousePosition = e.position
|
||||||
|
@ -20,8 +17,7 @@ export const handleScroll = (e: WheelEvent, camera: Camera) => {
|
||||||
const sign = e.deltaY / Math.abs(e.deltaY)
|
const sign = e.deltaY / Math.abs(e.deltaY)
|
||||||
const zoom = scrollStep ** sign
|
const zoom = scrollStep ** sign
|
||||||
|
|
||||||
const size = [screen.width.value, screen.height.value]
|
const mouseFraction = Screen.scale.map(
|
||||||
const mouseFraction = size.map(
|
|
||||||
(value, index) => absoluteMousePosition[index] / value
|
(value, index) => absoluteMousePosition[index] / value
|
||||||
)
|
)
|
||||||
const newScale = camera.transform.scale.map(value =>
|
const newScale = camera.transform.scale.map(value =>
|
||||||
|
@ -29,7 +25,9 @@ export const handleScroll = (e: WheelEvent, camera: Camera) => {
|
||||||
)
|
)
|
||||||
const delta = camera.transform.scale.map(
|
const delta = camera.transform.scale.map(
|
||||||
(value, index) =>
|
(value, index) =>
|
||||||
size[index] * (newScale[index] - value) * mouseFraction[index]
|
Screen.scale[index] *
|
||||||
|
(newScale[index] - value) *
|
||||||
|
mouseFraction[index]
|
||||||
)
|
)
|
||||||
|
|
||||||
camera.transform.scale = newScale as vector2
|
camera.transform.scale = newScale as vector2
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export interface Selection {
|
|
||||||
id: number
|
|
||||||
permanent: boolean
|
|
||||||
}
|
|
|
@ -24,4 +24,8 @@ export interface SimulationRendererOptions {
|
||||||
spawning: {
|
spawning: {
|
||||||
spawnOffset: number
|
spawnOffset: number
|
||||||
}
|
}
|
||||||
|
selecting: {
|
||||||
|
stroke: string
|
||||||
|
fill: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
1
src/modules/simulationRenderer/types/selectionType.ts
Normal file
1
src/modules/simulationRenderer/types/selectionType.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export type selectionType = 'permanent' | 'temporary'
|
Loading…
Reference in a new issue