From 48b967ead8af2e4cc8436b2397c90c6dea6b1680 Mon Sep 17 00:00:00 2001
From: Matei Adriel <rafaeladriel11@gmail.com>
Date: Thu, 25 Jul 2019 00:01:50 +0300
Subject: [PATCH] more advanced selection

---
 src/common/canvas/helpers/clearCanvas.ts      |   9 +-
 src/common/math/classes/Transform.ts          |  29 ++-
 src/common/math/helpers/pointInSquare.ts      |  16 +-
 src/modules/core/classes/Screen.ts            |  38 ----
 src/modules/core/components/FluidCanvas.tsx   |  12 +-
 .../internalisation/translations/english.ts   |   6 +-
 .../translations/nederlands.ts                |  13 +-
 .../internalisation/translations/romanian.ts  |  12 +-
 .../types/TranslationInterface.ts             |   1 -
 src/modules/saving/constants.ts               |  21 ++-
 src/modules/saving/helpers/dumpSimulation.ts  |   2 +-
 src/modules/screen/helpers/Screen.ts          |  27 +++
 src/modules/screen/helpers/getWidth.ts        |   6 +
 .../components/SimulationActions.tsx          |   7 +-
 src/modules/simulation-actions/constants.ts   |  23 +--
 .../helpers/createActionConfig.ts             |   2 +-
 .../helpers/createRendererActions.ts          |  22 +--
 .../helpers/deleteSelection.ts                |   8 +
 .../simulation-actions/helpers/selectAll.ts   |  13 ++
 .../types/possibleAction.ts                   |   8 +-
 src/modules/simulation/helpers/addGate.ts     |   4 +-
 .../simulationRenderer/classes/Camera.ts      |  11 --
 .../classes/SimulationRenderer.ts             | 176 ++++++++++--------
 src/modules/simulationRenderer/constants.ts   |  13 +-
 .../simulationRenderer/helpers/aabb.ts        |  10 +
 .../simulationRenderer/helpers/deleteGate.ts  |  29 +++
 .../helpers/gatesInSelection.ts               |  24 +++
 .../helpers/idIsSelected.ts                   |  36 ++++
 .../simulationRenderer/helpers/renderGate.ts  |   8 +-
 .../helpers/renderSelectedArea.ts             |  22 +++
 .../helpers/renderSimulation.ts               |   3 +-
 .../simulationRenderer/helpers/scaleCanvas.ts |  14 +-
 .../simulationRenderer/types/Selection.ts     |   4 -
 .../types/SimulationRendererOptions.ts        |   4 +
 .../simulationRenderer/types/selectionType.ts |   1 +
 35 files changed, 419 insertions(+), 215 deletions(-)
 delete mode 100644 src/modules/core/classes/Screen.ts
 create mode 100644 src/modules/screen/helpers/Screen.ts
 create mode 100644 src/modules/screen/helpers/getWidth.ts
 create mode 100644 src/modules/simulation-actions/helpers/deleteSelection.ts
 create mode 100644 src/modules/simulation-actions/helpers/selectAll.ts
 create mode 100644 src/modules/simulationRenderer/helpers/aabb.ts
 create mode 100644 src/modules/simulationRenderer/helpers/deleteGate.ts
 create mode 100644 src/modules/simulationRenderer/helpers/gatesInSelection.ts
 create mode 100644 src/modules/simulationRenderer/helpers/idIsSelected.ts
 create mode 100644 src/modules/simulationRenderer/helpers/renderSelectedArea.ts
 delete mode 100644 src/modules/simulationRenderer/types/Selection.ts
 create mode 100644 src/modules/simulationRenderer/types/selectionType.ts

diff --git a/src/common/canvas/helpers/clearCanvas.ts b/src/common/canvas/helpers/clearCanvas.ts
index fca0c01..7a84e00 100644
--- a/src/common/canvas/helpers/clearCanvas.ts
+++ b/src/common/canvas/helpers/clearCanvas.ts
@@ -1,9 +1,4 @@
-import { Screen } from '../../../modules/core/classes/Screen'
-
-/**
- * A screen instance used for the canvas clearing
- */
-const screen = new Screen()
+import { Screen } from '../../../modules/screen/helpers/Screen'
 
 /**
  * Clears the used portion of the canvas
@@ -11,5 +6,5 @@ const screen = new Screen()
  * @param ctx the context to clear
  */
 export const clearCanvas = (ctx: CanvasRenderingContext2D) => {
-    ctx.clearRect(0, 0, screen.x, screen.y)
+    ctx.clearRect(0, 0, Screen.width, Screen.height)
 }
diff --git a/src/common/math/classes/Transform.ts b/src/common/math/classes/Transform.ts
index cf2dcba..c501983 100644
--- a/src/common/math/classes/Transform.ts
+++ b/src/common/math/classes/Transform.ts
@@ -9,7 +9,9 @@ export class Transform {
     ) {}
 
     public getBoundingBox() {
-        return [...this.position, ...this.scale] as vector4
+        const result = [...this.position, ...this.scale] as vector4
+
+        return result
     }
 
     public getPoints() {
@@ -21,18 +23,7 @@ export class Transform {
             this.y + this.width * combination[1]
         ])
 
-        const pointsInTheRightOrder = [
-            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
+        return points as vector2[]
     }
 
     public getEdges() {
@@ -64,12 +55,20 @@ export class Transform {
         return this.scale[1]
     }
 
+    get minX() {
+        return Math.min(this.x, this.x + this.width)
+    }
+
     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() {
-        return this.y + this.height
+        return Math.max(this.y, this.y + this.height)
     }
 
     get center() {
diff --git a/src/common/math/helpers/pointInSquare.ts b/src/common/math/helpers/pointInSquare.ts
index 516bfe8..5eb3f76 100644
--- a/src/common/math/helpers/pointInSquare.ts
+++ b/src/common/math/helpers/pointInSquare.ts
@@ -3,9 +3,21 @@ import { vector2 } from '../types/vector2'
 
 export const pointInSquare = (point: vector2, square: Transform) => {
     return (
-        point[0] >= square.x &&
+        point[0] >= square.minX &&
         point[0] <= square.maxX &&
-        point[1] >= square.y &&
+        point[1] >= square.minY &&
         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
+    )
+}
diff --git a/src/modules/core/classes/Screen.ts b/src/modules/core/classes/Screen.ts
deleted file mode 100644
index d3e86dc..0000000
--- a/src/modules/core/classes/Screen.ts
+++ /dev/null
@@ -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)
-    }
-}
diff --git a/src/modules/core/components/FluidCanvas.tsx b/src/modules/core/components/FluidCanvas.tsx
index 589c75f..b04f90a 100644
--- a/src/modules/core/components/FluidCanvas.tsx
+++ b/src/modules/core/components/FluidCanvas.tsx
@@ -1,11 +1,9 @@
 import React, { RefObject, forwardRef, MouseEvent, WheelEvent } from 'react'
 import { useObservable } from 'rxjs-hooks'
-import { Screen } from '../classes/Screen'
 import { Subject } from 'rxjs'
 import { mouseButton } from '../types/mouseButton'
 import { MouseEventInfo } from './MouseEventInfo'
-
-const screen = new Screen()
+import { width, height } from '../../screen/helpers/Screen'
 
 export interface FluidCanvasProps {
     mouseDownOuput: Subject<MouseEventInfo>
@@ -30,14 +28,14 @@ export const mouseEventHandler = (output: Subject<MouseEventInfo>) => (
 
 const FluidCanvas = forwardRef(
     (props: FluidCanvasProps, ref: RefObject<HTMLCanvasElement>) => {
-        const width = useObservable(() => screen.width, 0)
-        const height = useObservable(() => screen.height, 0)
+        const currentWidth = useObservable(() => width, 0)
+        const currentHeight = useObservable(() => height, 0)
 
         return (
             <canvas
                 ref={ref}
-                width={width}
-                height={height}
+                width={currentWidth}
+                height={currentHeight}
                 onMouseDown={mouseEventHandler(props.mouseDownOuput)}
                 onMouseUp={mouseEventHandler(props.mouseUpOutput)}
                 onMouseMove={mouseEventHandler(props.mouseMoveOutput)}
diff --git a/src/modules/internalisation/translations/english.ts b/src/modules/internalisation/translations/english.ts
index a8574a0..060078b 100644
--- a/src/modules/internalisation/translations/english.ts
+++ b/src/modules/internalisation/translations/english.ts
@@ -27,9 +27,10 @@ export const EnglishTranslation: Translation = {
     actions: {
         save: 'Save',
         clean: 'Clean',
-        clear: 'Clear',
         refresh: 'Refresh',
-        undo: 'Undo'
+        undo: 'Undo',
+        'select all': 'Select all',
+        'delete selection': 'Delete selection'
     },
     messages: {
         createdSimulation: name => `Succesfully created simulation '${name}'`,
@@ -38,7 +39,6 @@ export const EnglishTranslation: Translation = {
         savedSimulation: name => `Succesfully saved simulation '${name}'`,
         compiledIc: name => `Succesfully compiled circuit '${name}'`,
         cleaned: name => `Succesfully cleaned simulation '${name}'`,
-        cleared: name => `Succesfully cleared simulation '${name}'`,
         refreshed: name => `Succesfully refreshed simulation '${name}'`,
         undone: name => `Succesfully undone simulation '${name}'`
     }
diff --git a/src/modules/internalisation/translations/nederlands.ts b/src/modules/internalisation/translations/nederlands.ts
index 9981bfa..e3d9454 100644
--- a/src/modules/internalisation/translations/nederlands.ts
+++ b/src/modules/internalisation/translations/nederlands.ts
@@ -12,6 +12,14 @@ export const DutchTranslation: Translation = {
         simulation: 'Todo',
         language: 'Taal'
     },
+    actions: {
+        'delete selection': 'Todo',
+        'select all': 'Todo',
+        clean: 'Todo',
+        refresh: 'Todo',
+        save: 'Todo',
+        undo: 'Todo'
+    },
     createSimulation: {
         mode: {
             question: 'Wat voor simulatie wil je maken?',
@@ -30,6 +38,9 @@ export const DutchTranslation: Translation = {
         switchedToSimulation: name =>
             `Succesvol veranderd naar simulatie '${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}`
     }
 }
diff --git a/src/modules/internalisation/translations/romanian.ts b/src/modules/internalisation/translations/romanian.ts
index 5d33cc0..1a61d63 100644
--- a/src/modules/internalisation/translations/romanian.ts
+++ b/src/modules/internalisation/translations/romanian.ts
@@ -25,7 +25,12 @@ export const RomanianTranslation: Translation = {
         }
     },
     actions: {
-        save: 'Salvează'
+        save: 'Salvează',
+        'delete selection': 'Șterge selecția',
+        'select all': 'Selectează totul',
+        clean: 'Curăță',
+        refresh: 'Reîncarcă',
+        undo: 'Întoarce'
     },
     messages: {
         createdSimulation: name =>
@@ -33,6 +38,9 @@ export const RomanianTranslation: Translation = {
         switchedToSimulation: name =>
             `Simulația '${name}' a fost deschisă 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ă`
     }
 }
diff --git a/src/modules/internalisation/types/TranslationInterface.ts b/src/modules/internalisation/types/TranslationInterface.ts
index 9fb95d8..69e8381 100644
--- a/src/modules/internalisation/types/TranslationInterface.ts
+++ b/src/modules/internalisation/types/TranslationInterface.ts
@@ -32,7 +32,6 @@ export interface Translation {
         savedSimulation: NameSentence
         compiledIc: NameSentence
         refreshed: NameSentence
-        cleared: NameSentence
         cleaned: NameSentence
         undone: NameSentence
     }
diff --git a/src/modules/saving/constants.ts b/src/modules/saving/constants.ts
index d748cb1..af1ecc4 100644
--- a/src/modules/saving/constants.ts
+++ b/src/modules/saving/constants.ts
@@ -26,7 +26,8 @@ export const baseTemplates: DeepPartial<GateTemplate>[] = [
             name: 'or'
         },
         material: {
-            value: 'yellow'
+            type: 'image',
+            value: require('../../assets/or_gate.png')
         },
         code: {
             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']
     },
+    {
+        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: {
             name: 'xor'
diff --git a/src/modules/saving/helpers/dumpSimulation.ts b/src/modules/saving/helpers/dumpSimulation.ts
index 7156681..40ea32f 100644
--- a/src/modules/saving/helpers/dumpSimulation.ts
+++ b/src/modules/saving/helpers/dumpSimulation.ts
@@ -8,7 +8,7 @@ import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationR
 export const dumpSimulation = (renderer: SimulationRenderer) => {
     renderer.simulation.dispose()
     renderer.lastMousePosition = [0, 0]
-    renderer.selectedGates = new Set()
+    renderer.clearSelection()
     renderer.selectedPins = {
         end: null,
         start: null
diff --git a/src/modules/screen/helpers/Screen.ts b/src/modules/screen/helpers/Screen.ts
new file mode 100644
index 0000000..66c8711
--- /dev/null
+++ b/src/modules/screen/helpers/Screen.ts
@@ -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 }
diff --git a/src/modules/screen/helpers/getWidth.ts b/src/modules/screen/helpers/getWidth.ts
new file mode 100644
index 0000000..7019456
--- /dev/null
+++ b/src/modules/screen/helpers/getWidth.ts
@@ -0,0 +1,6 @@
+import { sidebarWidth } from '../../core/components/Sidebar'
+
+/**
+ * Helper to get the width of the canvas
+ */
+export const getWidth = () => window.innerWidth - sidebarWidth
diff --git a/src/modules/simulation-actions/components/SimulationActions.tsx b/src/modules/simulation-actions/components/SimulationActions.tsx
index 8366915..b72be83 100644
--- a/src/modules/simulation-actions/components/SimulationActions.tsx
+++ b/src/modules/simulation-actions/components/SimulationActions.tsx
@@ -7,6 +7,7 @@ import MenuItem from '@material-ui/core/MenuItem'
 import Icon from '@material-ui/core/Icon'
 import { useTranslation } from '../../internalisation/helpers/useLanguage'
 import { SidebarActions } from '../constants'
+import { possibleAction } from '../types/possibleAction'
 
 /**
  * Component wich contains the sidebar 'Simulation' button
@@ -49,7 +50,11 @@ const SimulationActions = () => {
                                 </ListItemIcon>
 
                                 <ListItemText
-                                    primary={name}
+                                    primary={
+                                        translation.actions[
+                                            name as possibleAction
+                                        ]
+                                    }
                                     secondary={(keybinding || []).join(' + ')}
                                 />
                             </MenuItem>
diff --git a/src/modules/simulation-actions/constants.ts b/src/modules/simulation-actions/constants.ts
index cf0d5d9..57f7268 100644
--- a/src/modules/simulation-actions/constants.ts
+++ b/src/modules/simulation-actions/constants.ts
@@ -4,14 +4,16 @@ import { save } from '../saving/helpers/save'
 import { refresh } from './helpers/refresh'
 import { undo } from './helpers/undo'
 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> = {
-    clean: 'layers_clear',
-    clear: 'clear',
+    clean: 'clear',
     refresh: 'refresh',
     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']
     ),
-    ...createActionConfig(
-        'clear',
-        {
-            run: clear
-        },
-        ['ctrl', 'delete']
-    ),
     ...createActionConfig(
         'clean',
         {
@@ -47,6 +42,8 @@ export const SidebarActions: Record<possibleAction, SidebarAction> = {
                 console.log('Cleaning')
             }
         },
-        ['ctrl', 'shift', 'delete']
-    )
+        ['ctrl', 'delete']
+    ),
+    ...createActionConfig('select all', selectAll, ['ctrl', 'a']),
+    ...createActionConfig('delete selection', deleteSelection, ['delete'])
 }
diff --git a/src/modules/simulation-actions/helpers/createActionConfig.ts b/src/modules/simulation-actions/helpers/createActionConfig.ts
index 91d6bd6..dafac9b 100644
--- a/src/modules/simulation-actions/helpers/createActionConfig.ts
+++ b/src/modules/simulation-actions/helpers/createActionConfig.ts
@@ -5,7 +5,7 @@ import { createRendererAction } from './createRendererActions'
 import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
 import { getRendererSafely } from '../../logic-gates/helpers/getRendererSafely'
 
-export type ActionConfigFunction = (renderer?: SimulationRenderer) => void
+export type ActionConfigFunction = (renderer: SimulationRenderer) => void
 
 export type ActionConfigCallback =
     | {
diff --git a/src/modules/simulation-actions/helpers/createRendererActions.ts b/src/modules/simulation-actions/helpers/createRendererActions.ts
index 4e95340..1aaaa47 100644
--- a/src/modules/simulation-actions/helpers/createRendererActions.ts
+++ b/src/modules/simulation-actions/helpers/createRendererActions.ts
@@ -10,12 +10,10 @@ import { Translation } from '../../internalisation/types/TranslationInterface'
 /**
  * Map used to get the correct message from any action name
  */
-export const actionToMessageMap: Record<
-    possibleAction,
-    keyof Translation['messages']
+export const actionToMessageMap: Partial<
+    Record<possibleAction, keyof Translation['messages']>
 > = {
     clean: 'cleaned',
-    clear: 'cleared',
     refresh: 'refreshed',
     undo: 'undone',
     save: 'savedSimulation'
@@ -30,12 +28,14 @@ export const createRendererAction = (
 
     callback(renderer)
 
-    toast(
-        ...createToastArguments(
-            translation.messages[actionToMessageMap[action]](
-                renderer.simulation.name
-            ),
-            actionIcons[action]
+    const messageName = actionToMessageMap[action]
+
+    if (messageName) {
+        toast(
+            ...createToastArguments(
+                translation.messages[messageName](renderer.simulation.name),
+                actionIcons[action]
+            )
         )
-    )
+    }
 }
diff --git a/src/modules/simulation-actions/helpers/deleteSelection.ts b/src/modules/simulation-actions/helpers/deleteSelection.ts
new file mode 100644
index 0000000..1721eda
--- /dev/null
+++ b/src/modules/simulation-actions/helpers/deleteSelection.ts
@@ -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)
+    }
+}
diff --git a/src/modules/simulation-actions/helpers/selectAll.ts b/src/modules/simulation-actions/helpers/selectAll.ts
new file mode 100644
index 0000000..560bfa5
--- /dev/null
+++ b/src/modules/simulation-actions/helpers/selectAll.ts
@@ -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)
+    }
+}
diff --git a/src/modules/simulation-actions/types/possibleAction.ts b/src/modules/simulation-actions/types/possibleAction.ts
index 7efe140..447e0b8 100644
--- a/src/modules/simulation-actions/types/possibleAction.ts
+++ b/src/modules/simulation-actions/types/possibleAction.ts
@@ -1,4 +1,10 @@
 /**
  * Type repressenting al lpossible actions
  */
-export type possibleAction = 'save' | 'clear' | 'clean' | 'refresh' | 'undo'
+export type possibleAction =
+    | 'save'
+    | 'clean'
+    | 'refresh'
+    | 'undo'
+    | 'select all'
+    | 'delete selection'
diff --git a/src/modules/simulation/helpers/addGate.ts b/src/modules/simulation/helpers/addGate.ts
index 9ccafc0..90833a4 100644
--- a/src/modules/simulation/helpers/addGate.ts
+++ b/src/modules/simulation/helpers/addGate.ts
@@ -1,11 +1,11 @@
 import { templateStore } from '../../saving/stores/templateStore'
 import { SimulationError } from '../../errors/classes/SimulationError'
-import { Simulation } from '../classes/Simulation'
 import { Gate } from '../classes/Gate'
 import { add, relativeTo, multiply } from '../../vector2/helpers/basic'
 import { SimulationRenderer } from '../../simulationRenderer/classes/SimulationRenderer'
 import { DefaultGateTemplate } from '../constants'
 import { vector2 } from '../../../common/math/classes/Transform'
+import { Screen } from '../../screen/helpers/Screen'
 
 export const addGate = (renderer: SimulationRenderer, templateName: string) => {
     const template = templateStore.get(templateName)
@@ -22,7 +22,7 @@ export const addGate = (renderer: SimulationRenderer, templateName: string) => {
 
     const origin = relativeTo(
         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
diff --git a/src/modules/simulationRenderer/classes/Camera.ts b/src/modules/simulationRenderer/classes/Camera.ts
index 5e0c8d7..05c07bd 100644
--- a/src/modules/simulationRenderer/classes/Camera.ts
+++ b/src/modules/simulationRenderer/classes/Camera.ts
@@ -1,20 +1,9 @@
 import { Transform } from '../../../common/math/classes/Transform'
 import { vector2 } from '../../../common/math/types/vector2'
-import { Screen } from '../../core/classes/Screen'
-import { relativeTo } from '../../vector2/helpers/basic'
 
 export class Camera {
     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) {
         return [
             (position[0] - this.transform.position[0]) /
diff --git a/src/modules/simulationRenderer/classes/SimulationRenderer.ts b/src/modules/simulationRenderer/classes/SimulationRenderer.ts
index 268e0ef..3c65033 100644
--- a/src/modules/simulationRenderer/classes/SimulationRenderer.ts
+++ b/src/modules/simulationRenderer/classes/SimulationRenderer.ts
@@ -2,9 +2,11 @@ import { Camera } from './Camera'
 import { Simulation } from '../../simulation/classes/Simulation'
 import { Subject } from 'rxjs'
 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 { Screen } from '../../core/classes/Screen'
 import { relativeTo, add, invert } from '../../vector2/helpers/basic'
 import { SimulationRendererOptions } from '../types/SimulationRendererOptions'
 import { defaultSimulationRendererOptions, mouseButtons } from '../constants'
@@ -12,8 +14,6 @@ import { getPinPosition } from '../helpers/pinPosition'
 import { pointInCircle } from '../../../common/math/helpers/pointInCircle'
 import { SelectedPins } from '../types/SelectedPins'
 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 { saveStore } from '../../saving/stores/saveStore'
 import {
@@ -21,7 +21,6 @@ import {
     fromCameraState
 } from '../../saving/helpers/fromState'
 import merge from 'deepmerge'
-import { wireConnectedToGate } from '../helpers/wireConnectedToGate'
 import { updateMouse, handleScroll } from '../helpers/scaleCanvas'
 import { RefObject } from 'react'
 import { dumpSimulation } from '../../saving/helpers/dumpSimulation'
@@ -30,7 +29,10 @@ import { SimulationError } from '../../errors/classes/SimulationError'
 import { deleteWire } from '../../simulation/helpers/deleteWire'
 import { RendererState } from '../../saving/types/SimulationSave'
 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 {
     public mouseDownOutput = new Subject<MouseEventInfo>()
@@ -38,17 +40,22 @@ export class SimulationRenderer {
     public mouseMoveOutput = new Subject<MouseEventInfo>()
     public wheelOutput = new Subject<unknown>()
 
-    public selectedGates = new Set<Selection>()
-    public lastMousePosition: vector2 = [0, 0]
+    public selectedGates: Record<selectionType, Set<number>> = {
+        temporary: new Set<number>(),
+        permanent: new Set<number>()
+    }
+
     public options: SimulationRendererOptions
-    public screen = new Screen()
     public camera = new Camera()
 
+    public selectedArea = new Transform()
+
     // first bit = dragging
     // second bit = panning around
     // third bit = selecting
-    private mouseState = 0b000
-    private gateSelectionOffset: vector2 = [0, 0]
+    public mouseState = 0b000
+
+    public lastMousePosition: vector2 = [0, 0]
 
     // this is used for spawning gates
     public spawnCount = 0
@@ -89,14 +96,7 @@ export class SimulationRenderer {
 
                     this.mouseState |= 1
 
-                    this.selectedGates.add({
-                        id,
-                        permanent: false
-                    })
-                    this.gateSelectionOffset = worldPosition.map(
-                        (position, index) =>
-                            position - transform.position[index]
-                    ) as vector2
+                    addIdToSelection(this, 'temporary', id)
 
                     const gateNode = this.simulation.gates.get(id)
 
@@ -132,9 +132,9 @@ export class SimulationRenderer {
                                         `Cannot find wire to remove`
                                     )
                                 }
-                            }
 
-                            return
+                                return
+                            }
                         }
 
                         if (
@@ -177,29 +177,59 @@ export class SimulationRenderer {
                 }
             }
 
+            if (event.button === mouseButtons.unselect) {
+                this.clearSelection()
+            }
+
             if (event.button === mouseButtons.pan) {
+                // the second bit = pannning
                 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(() => {
-            if (this.selectedGates.size) {
+        this.mouseUpOutput.subscribe(event => {
+            if (event.button === mouseButtons.drag) {
                 const selected = this.getSelected()
 
                 for (const gate of selected) {
                     gate.transform.rotation = 0
                 }
 
-                for (const selection of this.selectedGates.values()) {
-                    if (!selection.permanent) {
-                        this.selectedGates.delete(selection)
-                    }
-                }
+                this.selectedGates.temporary.clear()
 
-                this.mouseState &= 0
+                // 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 &= 0b00
+            if (
+                event.button === mouseButtons.select &&
+                (this.mouseState >> 2) & 1
+            ) {
+                // turn the third bit to 0
+                this.mouseState &= (1 << 2) - 1
+
+                const selectedGates = gatesInSelection(
+                    this.selectedArea,
+                    Array.from(this.simulation.gates)
+                )
+
+                for (const { id } of selectedGates) {
+                    addIdToSelection(this, 'permanent', id)
+                }
+            }
         })
 
         this.mouseMoveOutput.subscribe(event => {
@@ -207,22 +237,22 @@ export class SimulationRenderer {
 
             const worldPosition = this.camera.toWordPostition(event.position)
 
-            if (this.mouseState & 1 && this.selectedGates.size) {
+            const offset = invert(
+                relativeTo(this.lastMousePosition, worldPosition)
+            ).map(
+                (value, index) => value * this.camera.transform.scale[index]
+            ) as vector2
+
+            if (this.mouseState & 1) {
                 for (const gate of this.getSelected()) {
                     const { transform } = gate
 
-                    transform.x = worldPosition[0] - this.gateSelectionOffset[0]
-                    transform.y = worldPosition[1] - this.gateSelectionOffset[1]
+                    transform.x -= offset[0]
+                    transform.y -= offset[1]
                 }
             }
 
             if ((this.mouseState >> 1) & 1) {
-                const offset = invert(
-                    relativeTo(this.lastMousePosition, worldPosition)
-                ).map(
-                    (value, index) => value * this.camera.transform.scale[index]
-                ) as vector2
-
                 this.camera.transform.position = add(
                     this.camera.transform.position,
                     invert(offset)
@@ -231,13 +261,17 @@ export class SimulationRenderer {
                 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)
         })
 
-        dumpSimulation(this)
-
         this.reloadSave()
-        this.initKeyBindings()
     }
 
     public updateWheelListener() {
@@ -259,6 +293,8 @@ export class SimulationRenderer {
 
     public reloadSave() {
         try {
+            dumpSimulation(this)
+
             const current = currentStore.get()
             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
      *
@@ -318,7 +318,7 @@ export class SimulationRenderer {
      * @throws SimulationError if the id doesnt have a data prop
      */
     public getSelected() {
-        return setToArray(this.selectedGates).map(({ id }) => {
+        return setToArray(this.allSelectedIds()).map(id => {
             const gate = this.simulation.gates.get(id)
 
             if (!gate) {
@@ -332,4 +332,22 @@ export class SimulationRenderer {
             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()
+    }
 }
diff --git a/src/modules/simulationRenderer/constants.ts b/src/modules/simulationRenderer/constants.ts
index 08bb3ac..03b9857 100644
--- a/src/modules/simulationRenderer/constants.ts
+++ b/src/modules/simulationRenderer/constants.ts
@@ -27,17 +27,22 @@ export const defaultSimulationRendererOptions: SimulationRendererOptions = {
     },
     spawning: {
         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 mouseButtons: Record<
-    'zoom' | 'pan' | 'drag' | 'select',
+    'zoom' | 'pan' | 'drag' | 'select' | 'unselect',
     mouseButton
 > = {
     zoom: 1,
-    drag: 0,
-    pan: 0,
-    select: 2
+    drag: 2,
+    pan: 2,
+    select: 0,
+    unselect: 0
 }
diff --git a/src/modules/simulationRenderer/helpers/aabb.ts b/src/modules/simulationRenderer/helpers/aabb.ts
new file mode 100644
index 0000000..b23fc78
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/aabb.ts
@@ -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
+    )
+}
diff --git a/src/modules/simulationRenderer/helpers/deleteGate.ts b/src/modules/simulationRenderer/helpers/deleteGate.ts
new file mode 100644
index 0000000..d42097c
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/deleteGate.ts
@@ -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)
+}
diff --git a/src/modules/simulationRenderer/helpers/gatesInSelection.ts b/src/modules/simulationRenderer/helpers/gatesInSelection.ts
new file mode 100644
index 0000000..c82d8fa
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/gatesInSelection.ts
@@ -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
+    })
+}
diff --git a/src/modules/simulationRenderer/helpers/idIsSelected.ts b/src/modules/simulationRenderer/helpers/idIsSelected.ts
new file mode 100644
index 0000000..1f02b3f
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/idIsSelected.ts
@@ -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)
+    }
+}
diff --git a/src/modules/simulationRenderer/helpers/renderGate.ts b/src/modules/simulationRenderer/helpers/renderGate.ts
index aad12c9..5f535b2 100644
--- a/src/modules/simulationRenderer/helpers/renderGate.ts
+++ b/src/modules/simulationRenderer/helpers/renderGate.ts
@@ -5,6 +5,8 @@ import { useTransform } from '../../../common/canvas/helpers/useTransform'
 import { roundRect } from '../../../common/canvas/helpers/drawRoundedSquare'
 import { roundImage } from '../../../common/canvas/helpers/drawRoundedImage'
 import { ImageStore } from '../stores/imageStore'
+import { gatesInSelection } from './gatesInSelection'
+import { idIsSelected } from './idIsSelected'
 
 export const renderGate = (
     ctx: CanvasRenderingContext2D,
@@ -13,7 +15,11 @@ export const renderGate = (
 ) => {
     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
     } else {
         ctx.strokeStyle = renderer.options.gates.gateStroke.normal
diff --git a/src/modules/simulationRenderer/helpers/renderSelectedArea.ts b/src/modules/simulationRenderer/helpers/renderSelectedArea.ts
new file mode 100644
index 0000000..947f9d4
--- /dev/null
+++ b/src/modules/simulationRenderer/helpers/renderSelectedArea.ts
@@ -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()
+    }
+}
diff --git a/src/modules/simulationRenderer/helpers/renderSimulation.ts b/src/modules/simulationRenderer/helpers/renderSimulation.ts
index b77ae65..e55e60e 100644
--- a/src/modules/simulationRenderer/helpers/renderSimulation.ts
+++ b/src/modules/simulationRenderer/helpers/renderSimulation.ts
@@ -4,7 +4,7 @@ import { renderGate } from './renderGate'
 import { clearCanvas } from '../../../common/canvas/helpers/clearCanvas'
 import { renderClickedPins } from './renderClickedPins'
 import { renderWires } from './renderWires'
-import { vector2 } from '../../../common/math/classes/Transform'
+import { renderSelectedArea } from './renderSelectedArea'
 
 export const renderSimulation = (
     ctx: CanvasRenderingContext2D,
@@ -26,6 +26,7 @@ export const renderSimulation = (
     }
 
     renderClickedPins(ctx, renderer)
+    renderSelectedArea(ctx, renderer)
 
     ctx.scale(...inverse(transform.scale))
     ctx.translate(...invert(transform.position))
diff --git a/src/modules/simulationRenderer/helpers/scaleCanvas.ts b/src/modules/simulationRenderer/helpers/scaleCanvas.ts
index 5c1e08e..b1976bd 100644
--- a/src/modules/simulationRenderer/helpers/scaleCanvas.ts
+++ b/src/modules/simulationRenderer/helpers/scaleCanvas.ts
@@ -1,16 +1,13 @@
-import { Screen } from '../../core/classes/Screen'
 import { clamp } from '../../simulation/helpers/clamp'
 import { Camera } from '../classes/Camera'
 import { vector2 } from '../../../common/math/classes/Transform'
 import { MouseEventInfo } from '../../core/components/MouseEventInfo'
-// import { WheelEvent } from 'react'
-
-const screen = new Screen()
+import { Screen } from '../../screen/helpers/Screen'
 
 const scrollStep = 1.3
 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) => {
     absoluteMousePosition = e.position
@@ -20,8 +17,7 @@ export const handleScroll = (e: WheelEvent, camera: Camera) => {
     const sign = e.deltaY / Math.abs(e.deltaY)
     const zoom = scrollStep ** sign
 
-    const size = [screen.width.value, screen.height.value]
-    const mouseFraction = size.map(
+    const mouseFraction = Screen.scale.map(
         (value, index) => absoluteMousePosition[index] / 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(
         (value, index) =>
-            size[index] * (newScale[index] - value) * mouseFraction[index]
+            Screen.scale[index] *
+            (newScale[index] - value) *
+            mouseFraction[index]
     )
 
     camera.transform.scale = newScale as vector2
diff --git a/src/modules/simulationRenderer/types/Selection.ts b/src/modules/simulationRenderer/types/Selection.ts
deleted file mode 100644
index 19e1dbc..0000000
--- a/src/modules/simulationRenderer/types/Selection.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface Selection {
-    id: number
-    permanent: boolean
-}
diff --git a/src/modules/simulationRenderer/types/SimulationRendererOptions.ts b/src/modules/simulationRenderer/types/SimulationRendererOptions.ts
index 222d204..98c18fe 100644
--- a/src/modules/simulationRenderer/types/SimulationRendererOptions.ts
+++ b/src/modules/simulationRenderer/types/SimulationRendererOptions.ts
@@ -24,4 +24,8 @@ export interface SimulationRendererOptions {
     spawning: {
         spawnOffset: number
     }
+    selecting: {
+        stroke: string
+        fill: string
+    }
 }
diff --git a/src/modules/simulationRenderer/types/selectionType.ts b/src/modules/simulationRenderer/types/selectionType.ts
new file mode 100644
index 0000000..d105b72
--- /dev/null
+++ b/src/modules/simulationRenderer/types/selectionType.ts
@@ -0,0 +1 @@
+export type selectionType = 'permanent' | 'temporary'