diff --git a/Cargo.lock b/Cargo.lock
index 16d58e4d9d04a2f92c5a47d836623cc0f916c8d3..cfcee9b60b6a6ca085468b80076080ff18249e31 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1071,6 +1071,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b"
 dependencies = [
  "bytemuck",
+ "mint",
  "serde",
 ]
 
@@ -1461,6 +1462,16 @@ dependencies = [
  "xml-rs",
 ]
 
+[[package]]
+name = "glam"
+version = "0.29.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677"
+dependencies = [
+ "mint",
+ "serde",
+]
+
 [[package]]
 name = "glow"
 version = "0.16.0"
@@ -2128,6 +2139,12 @@ dependencies = [
  "simd-adler32",
 ]
 
+[[package]]
+name = "mint"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
+
 [[package]]
 name = "mio"
 version = "1.0.3"
@@ -3081,7 +3098,9 @@ dependencies = [
  "egui_plot",
  "egui_tiles",
  "enum_dispatch",
+ "glam",
  "mavlink-bindgen",
+ "mint",
  "profiling",
  "rand 0.9.0",
  "ring-channel",
@@ -3097,6 +3116,7 @@ dependencies = [
  "tracing-appender",
  "tracing-subscriber",
  "tracing-tracy",
+ "uuid",
 ]
 
 [[package]]
@@ -3921,6 +3941,16 @@ version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
 
+[[package]]
+name = "uuid"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
+dependencies = [
+ "getrandom 0.3.1",
+ "serde",
+]
+
 [[package]]
 name = "valuable"
 version = "0.1.1"
diff --git a/Cargo.toml b/Cargo.toml
index 5aa24050b589ef3324aa5fefdda46da754dda764..819e571068083cb0aa8e10b32407d5a394cda88c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,7 +11,7 @@ license = "MIT"
 # ======= GUI & Rendering =======
 egui_tiles = "0.12"
 eframe = { version = "0.31", features = ["persistence"] }
-egui = { version = "0.31", features = ["log"] }
+egui = { version = "0.31", features = ["log", "mint"] }
 egui_extras = { version = "0.31", features = ["svg"] }
 egui_plot = "0.31"
 egui_file = "0.22"
@@ -36,6 +36,9 @@ strum_macros = "0.26"
 anyhow = "1.0"
 ring-channel = "0.12.0"
 thiserror = "2.0.7"
+uuid = { version = "1.12.1", features = ["serde", "v7"] }
+glam = { version = "0.29", features = ["serde", "mint"] }
+mint = "0.5.9"
 
 [dependencies.skyward_mavlink]
 git = "https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git"
diff --git a/icons/check_valve_dark.svg b/icons/check_valve_dark.svg
deleted file mode 100644
index 6f957f16703f9e9cd8fb7f5ca1c33677deddca85..0000000000000000000000000000000000000000
--- a/icons/check_valve_dark.svg
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   width="50"
-   height="50"
-   version="1.1"
-   viewBox="0 0 50 50"
-   xml:space="preserve"
-   id="svg4"
-   sodipodi:docname="check_valve_dark.svg"
-   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
-     id="namedview4"
-     pagecolor="#000000"
-     bordercolor="#000000"
-     borderopacity="0.25"
-     inkscape:showpageshadow="2"
-     inkscape:pageopacity="0.0"
-     inkscape:pagecheckerboard="true"
-     inkscape:deskcolor="#d1d1d1"
-     inkscape:document-units="px"
-     inkscape:zoom="11.313709"
-     inkscape:cx="3.0935922"
-     inkscape:cy="30.714951"
-     inkscape:window-width="1920"
-     inkscape:window-height="1131"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="svg4" /><defs
-     id="defs1" /><g
-     stroke="#000000"
-     id="g3"
-     transform="matrix(3.779528,0,0,3.77953,-106.2768,168.61347)"
-     style="stroke:#ffffff"><circle
-       cx="29.749052"
-       cy="-40.397133"
-       r="1.1778001"
-       fill-opacity="0.98911"
-       stroke-linecap="round"
-       stroke-width="0.3"
-       id="circle2"
-       style="stroke:#ffffff;fill:#ffffff" /><path
-       d="m 29.754599,-40.469906 4.979,2.4722 -4.979,2.4722 z m 9.9581,0 -4.979,2.4722 4.979,2.4722 z"
-       fill="none"
-       pointer-events="all"
-       stroke-linejoin="round"
-       stroke-miterlimit="10"
-       stroke-width="0.3"
-       id="path3"
-       sodipodi:nodetypes="cccccccc"
-       style="stroke:#ffffff" /></g></svg>
diff --git a/icons/check_valve_light.svg b/icons/check_valve_light.svg
deleted file mode 100644
index edbdcbe89ecaa955a91b59154cd7b108595a913a..0000000000000000000000000000000000000000
--- a/icons/check_valve_light.svg
+++ /dev/null
@@ -1,53 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   width="50"
-   height="50"
-   version="1.1"
-   viewBox="0 0 50 50"
-   xml:space="preserve"
-   id="svg4"
-   sodipodi:docname="check_valve.svg"
-   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
-     id="namedview4"
-     pagecolor="#000000"
-     bordercolor="#000000"
-     borderopacity="0.25"
-     inkscape:showpageshadow="2"
-     inkscape:pageopacity="0.0"
-     inkscape:pagecheckerboard="true"
-     inkscape:deskcolor="#d1d1d1"
-     inkscape:document-units="px"
-     inkscape:zoom="11.313709"
-     inkscape:cx="3.0935922"
-     inkscape:cy="30.714951"
-     inkscape:window-width="1920"
-     inkscape:window-height="1131"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
-     inkscape:window-maximized="1"
-     inkscape:current-layer="svg4" /><defs
-     id="defs1" /><g
-     stroke="#000000"
-     id="g3"
-     transform="matrix(3.779528,0,0,3.77953,-106.2768,168.61347)"><circle
-       cx="29.749052"
-       cy="-40.397133"
-       r="1.1778001"
-       fill-opacity="0.98911"
-       stroke-linecap="round"
-       stroke-width="0.3"
-       id="circle2" /><path
-       d="m 29.754599,-40.469906 4.979,2.4722 -4.979,2.4722 z m 9.9581,0 -4.979,2.4722 4.979,2.4722 z"
-       fill="none"
-       pointer-events="all"
-       stroke-linejoin="round"
-       stroke-miterlimit="10"
-       stroke-width="0.3"
-       id="path3"
-       sodipodi:nodetypes="cccccccc" /></g></svg>
diff --git a/icons/pid_symbols/light/check_valve.svg b/icons/pid_symbols/light/check_valve.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f3694e49a605ff5d430e3a971380953739e558c2
--- /dev/null
+++ b/icons/pid_symbols/light/check_valve.svg
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="10"
+   height="5"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="2_check_valve.svg"
+   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs1">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="10 : 0 : 1"
+       inkscape:persp3d-origin="5 : -1.6666667 : 1"
+       id="perspective3" />
+  </defs>
+  <sodipodi:namedview
+     id="namedview1"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:zoom="64"
+     inkscape:cx="3.7109375"
+     inkscape:cy="5.2421875"
+     inkscape:window-width="1996"
+     inkscape:window-height="1371"
+     inkscape:window-x="20"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1"
+     showgrid="true">
+    <inkscape:grid
+       id="grid3"
+       units="px"
+       originx="0"
+       originy="0"
+       spacingx="0.1"
+       spacingy="0.1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" />
+  </sodipodi:namedview>
+  <circle
+     style="fill:#000000;stroke:none"
+     id="path2"
+     inkscape:label="path4"
+     cx="1"
+     cy="4.5"
+     r="0.5" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="m 9,2.5 h 1 z"
+     id="path1-5-2"
+     style="stroke-width:0.2;stroke-dasharray:none"
+     sodipodi:nodetypes="ccccc"
+     inkscape:label="path3" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="M 0,2.5 H 1 Z"
+     id="path1-5"
+     style="stroke-width:0.2;stroke-dasharray:none"
+     sodipodi:nodetypes="ccccc"
+     inkscape:label="path2" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="m 1,0.5 v 4 l 8,-4 v 4"
+     id="path1"
+     style="stroke-width:0.2;stroke-dasharray:none"
+     sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/icons/pid_symbols/light/empty.svg b/icons/pid_symbols/light/empty.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7abdd75191ecfb26c8cc61827f94d06ae1b9ea65
--- /dev/null
+++ b/icons/pid_symbols/light/empty.svg
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="10"
+   height="10"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="0_empty.svg"
+   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs1" />
+  <sodipodi:namedview
+     id="namedview1"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:zoom="93.176471"
+     inkscape:cx="4.3787879"
+     inkscape:cy="3.2143308"
+     inkscape:window-width="1996"
+     inkscape:window-height="1371"
+     inkscape:window-x="20"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1"
+     showgrid="true">
+    <inkscape:grid
+       id="grid3"
+       units="px"
+       originx="0"
+       originy="0"
+       spacingx="0.1"
+       spacingy="0.1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" />
+  </sodipodi:namedview>
+</svg>
diff --git a/icons/pid_symbols/light/manual_valve.svg b/icons/pid_symbols/light/manual_valve.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b66100b8b557235ed136cedef7c62d6491378f53
--- /dev/null
+++ b/icons/pid_symbols/light/manual_valve.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="10"
+   height="5"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="1_manual_valve.svg"
+   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs1">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 0 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="10 : 0 : 1"
+       inkscape:persp3d-origin="5 : -1.6666667 : 1"
+       id="perspective3" />
+  </defs>
+  <sodipodi:namedview
+     id="namedview1"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:zoom="64"
+     inkscape:cx="3.7109375"
+     inkscape:cy="5.2421875"
+     inkscape:window-width="1996"
+     inkscape:window-height="1371"
+     inkscape:window-x="20"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1"
+     showgrid="true">
+    <inkscape:grid
+       id="grid3"
+       units="px"
+       originx="0"
+       originy="0"
+       spacingx="0.1"
+       spacingy="0.1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" />
+  </sodipodi:namedview>
+  <path
+     fill="none"
+     stroke="#000000"
+     d="m 9,2.5 h 1 z"
+     id="path1-5-2"
+     style="stroke-width:0.2;stroke-dasharray:none"
+     sodipodi:nodetypes="ccccc"
+     inkscape:label="path3" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="M 0,2.5 H 1 Z"
+     id="path1-5"
+     style="stroke-width:0.2;stroke-dasharray:none"
+     sodipodi:nodetypes="ccccc"
+     inkscape:label="path2" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="m 1,0.5 v 4 l 8,-4 v 4 z"
+     id="path1"
+     style="stroke-width:0.2;stroke-dasharray:none"
+     sodipodi:nodetypes="cccc" />
+</svg>
diff --git a/icons/pid_symbols/light/motor_valve.svg b/icons/pid_symbols/light/motor_valve.svg
new file mode 100644
index 0000000000000000000000000000000000000000..eae812d1adaf9afedcaf6b36abb28677d8f50b7d
--- /dev/null
+++ b/icons/pid_symbols/light/motor_valve.svg
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="10"
+   height="7.5"
+   version="1.1"
+   id="svg1"
+   sodipodi:docname="5_motor_valve.svg"
+   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs1" />
+  <sodipodi:namedview
+     id="namedview1"
+     pagecolor="#ffffff"
+     bordercolor="#000000"
+     borderopacity="0.25"
+     inkscape:showpageshadow="2"
+     inkscape:pageopacity="0.0"
+     inkscape:pagecheckerboard="false"
+     inkscape:deskcolor="#d1d1d1"
+     inkscape:zoom="90.509668"
+     inkscape:cx="4.3752232"
+     inkscape:cy="2.4362038"
+     inkscape:window-width="1996"
+     inkscape:window-height="1371"
+     inkscape:window-x="20"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg1"
+     showgrid="true">
+    <inkscape:grid
+       id="grid3"
+       units="px"
+       originx="0"
+       originy="0"
+       spacingx="0.1"
+       spacingy="0.1"
+       empcolor="#0099e5"
+       empopacity="0.30196078"
+       color="#0099e5"
+       opacity="0.14901961"
+       empspacing="5"
+       enabled="true"
+       visible="true" />
+  </sodipodi:namedview>
+  <path
+     fill="none"
+     stroke="#000000"
+     d="M 1,3 V 7 L 9,3 v 4 z"
+     id="path1"
+     style="stroke-width:0.225"
+     sodipodi:nodetypes="ccccc" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="M 0,5 H 1 Z"
+     id="path2"
+     style="stroke-width:0.225"
+     sodipodi:nodetypes="ccccc" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="M 5,2.5 L 5,5 Z"
+     id="path3"
+     style="stroke-width:0.225"
+     sodipodi:nodetypes="ccccc" />
+  <path
+     fill="none"
+     stroke="#000000"
+     d="m 9,5 h 1 z"
+     id="path4"
+     style="stroke-width:0.225"
+     sodipodi:nodetypes="ccccc" />
+  <circle
+     style="fill:none;stroke:#000000;stroke-width:0.2;stroke-opacity:1"
+     id="circle1"
+     cx="5"
+     cy="1.5"
+     r="1" />
+  <path
+     style="font-size:1.33333px;-inkscape-font-specification:'sans-serif, Normal';fill:#000000;stroke:none;stroke-width:0.2;fill-opacity:1"
+     d="M 4.9446681,1.9759989 4.6313347,1.1413322 h -0.00533 q 0.00267,0.026667 0.004,0.068 0.00267,0.041333 0.004,0.090667 0.00133,0.048 0.00133,0.098667 V 1.9759989 H 4.5246681 v -0.952 h 0.1773333 l 0.2933333,0.78 h 0.00533 l 0.2986666,-0.78 h 0.1760001 v 0.952 H 5.3566681 V 1.3906655 q 0,-0.046667 0.00133,-0.092 0.00133,-0.046667 0.004,-0.086667 0.00267,-0.041333 0.004,-0.069333 h -0.00533 L 5.0433347,1.9759989 Z"
+     id="text1"
+     inkscape:transform-center-x="0.034572172"
+     inkscape:transform-center-y="-0.11235956"
+     aria-label="M" />
+</svg>
diff --git a/icons/pressurized_vessel_dark.svg b/icons/pressurized_vessel_dark.svg
deleted file mode 100644
index 4b5d69bd5b4ce2091ae06120f15ab57991e68423..0000000000000000000000000000000000000000
--- a/icons/pressurized_vessel_dark.svg
+++ /dev/null
@@ -1,111 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   width="50mm"
-   height="50mm"
-   id="svg2"
-   sodipodi:version="0.32"
-   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
-   version="1.0"
-   sodipodi:docname="pressurized_vessel_dark.svg"
-   inkscape:output_extension="org.inkscape.output.svg.inkscape"
-   viewBox="0 0 50 50"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:dc="http://purl.org/dc/elements/1.1/">
-  <defs
-     id="defs4" />
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#000000"
-     bordercolor="#000000"
-     borderopacity="0.10196078"
-     gridtolerance="10000"
-     guidetolerance="10"
-     objecttolerance="10"
-     inkscape:pageopacity="0.0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="2.8"
-     inkscape:cx="54.821429"
-     inkscape:cy="74.464286"
-     inkscape:document-units="mm"
-     inkscape:current-layer="layer1"
-     width="20mm"
-     height="20mm"
-     units="mm"
-     inkscape:window-width="1920"
-     inkscape:window-height="1131"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
-     inkscape:showpageshadow="2"
-     inkscape:pagecheckerboard="true"
-     inkscape:deskcolor="#d1d1d1"
-     inkscape:window-maximized="1" />
-  <metadata
-     id="metadata7">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer"
-     id="layer1"
-     style="stroke:#ffffff">
-    <g
-       id="g2774"
-       transform="matrix(0.78244262,0,0,0.78244262,-2.7241867,-2.3262084)"
-       style="fill:none;stroke:#ffffff">
-      <path
-         sodipodi:open="true"
-         sodipodi:end="3.1415927"
-         sodipodi:start="0.017453293"
-         transform="matrix(1.4090631,0,0,1.7600641,-3.2596449,30.294269)"
-         d="m 37.203241,13.364701 a 9.7440948,4.4291339 0 0 1 -9.827643,4.351666 9.7440948,4.4291339 0 0 1 -9.659062,-4.428965"
-         sodipodi:ry="4.4291339"
-         sodipodi:rx="9.7440948"
-         sodipodi:cy="13.287402"
-         sodipodi:cx="27.46063"
-         id="path2865"
-         style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.674995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
-         sodipodi:type="arc"
-         sodipodi:arc-type="arc" />
-      <path
-         sodipodi:open="true"
-         sodipodi:end="3.1415927"
-         sodipodi:start="0.017453293"
-         transform="matrix(1.4089528,0,0,-1.3269204,-3.2566161,31.802164)"
-         d="m 37.203241,13.364701 a 9.7440948,4.4291339 0 0 1 -9.827643,4.351666 9.7440948,4.4291339 0 0 1 -9.659062,-4.428965"
-         sodipodi:ry="4.4291339"
-         sodipodi:rx="9.7440948"
-         sodipodi:cy="13.287402"
-         sodipodi:cx="27.46063"
-         id="path3248"
-         style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.777427;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
-         sodipodi:type="arc"
-         sodipodi:arc-type="arc" />
-      <path
-         id="path3250"
-         d="m 21.711705,13.722069 c 0,40.782784 0,40.782784 0,40.782784"
-         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.06299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;enable-background:accumulate" />
-      <path
-         id="path3252"
-         d="m 49.163386,13.713031 c 0,40.79173 0,40.79173 0,40.79173"
-         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.06299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;enable-background:accumulate" />
-      <path
-         id="path2772"
-         d="m 33.467862,60.827788 c -3.577648,-0.413125 -6.10002,-1.215482 -8.214142,-2.612883 -1.024074,-0.676902 -2.204101,-1.968578 -2.580352,-2.8245 -0.378258,-0.860483 -0.364238,-0.03958 -0.365254,-21.38872 l -9.66e-4,-20.317289 0.233492,-0.468565 c 1.031623,-2.070193 4.817372,-3.6605684 10.034365,-4.2153907 1.367601,-0.1454429 4.725174,-0.1186857 6.160715,0.049095 4.897705,0.5724277 8.598366,2.1804967 9.587937,4.1662967 l 0.233491,0.468564 -9.55e-4,20.317289 c -0.0011,22.298611 0.04107,20.607328 -0.540759,21.698958 -1.275447,2.393011 -4.759848,4.264225 -9.235072,4.959511 -0.934233,0.145142 -4.525825,0.258473 -5.3125,0.167634 z"
-         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.09491;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
-    </g>
-  </g>
-</svg>
diff --git a/icons/pressurized_vessel_light.svg b/icons/pressurized_vessel_light.svg
deleted file mode 100644
index 6cb7b41184dbe266427102c6e3e27042a32000c5..0000000000000000000000000000000000000000
--- a/icons/pressurized_vessel_light.svg
+++ /dev/null
@@ -1,110 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
-   width="50mm"
-   height="50mm"
-   id="svg2"
-   sodipodi:version="0.32"
-   inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
-   version="1.0"
-   sodipodi:docname="pressurized_vessel.svg"
-   inkscape:output_extension="org.inkscape.output.svg.inkscape"
-   viewBox="0 0 50 50"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:dc="http://purl.org/dc/elements/1.1/">
-  <defs
-     id="defs4" />
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#000000"
-     bordercolor="#000000"
-     borderopacity="0.10196078"
-     gridtolerance="10000"
-     guidetolerance="10"
-     objecttolerance="10"
-     inkscape:pageopacity="0.0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="2.8"
-     inkscape:cx="54.821429"
-     inkscape:cy="74.464286"
-     inkscape:document-units="mm"
-     inkscape:current-layer="layer1"
-     width="20mm"
-     height="20mm"
-     units="mm"
-     inkscape:window-width="1920"
-     inkscape:window-height="1131"
-     inkscape:window-x="0"
-     inkscape:window-y="0"
-     inkscape:showpageshadow="2"
-     inkscape:pagecheckerboard="true"
-     inkscape:deskcolor="#d1d1d1"
-     inkscape:window-maximized="1" />
-  <metadata
-     id="metadata7">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-      </cc:Work>
-    </rdf:RDF>
-  </metadata>
-  <g
-     inkscape:label="Layer 1"
-     inkscape:groupmode="layer"
-     id="layer1">
-    <g
-       id="g2774"
-       transform="matrix(0.78244262,0,0,0.78244262,-2.7241867,-2.3262084)"
-       style="fill:none">
-      <path
-         sodipodi:open="true"
-         sodipodi:end="3.1415927"
-         sodipodi:start="0.017453293"
-         transform="matrix(1.4090631,0,0,1.7600641,-3.2596449,30.294269)"
-         d="m 37.203241,13.364701 a 9.7440948,4.4291339 0 0 1 -9.827643,4.351666 9.7440948,4.4291339 0 0 1 -9.659062,-4.428965"
-         sodipodi:ry="4.4291339"
-         sodipodi:rx="9.7440948"
-         sodipodi:cy="13.287402"
-         sodipodi:cx="27.46063"
-         id="path2865"
-         style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.674995;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
-         sodipodi:type="arc"
-         sodipodi:arc-type="arc" />
-      <path
-         sodipodi:open="true"
-         sodipodi:end="3.1415927"
-         sodipodi:start="0.017453293"
-         transform="matrix(1.4089528,0,0,-1.3269204,-3.2566161,31.802164)"
-         d="m 37.203241,13.364701 a 9.7440948,4.4291339 0 0 1 -9.827643,4.351666 9.7440948,4.4291339 0 0 1 -9.659062,-4.428965"
-         sodipodi:ry="4.4291339"
-         sodipodi:rx="9.7440948"
-         sodipodi:cy="13.287402"
-         sodipodi:cx="27.46063"
-         id="path3248"
-         style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.777427;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
-         sodipodi:type="arc"
-         sodipodi:arc-type="arc" />
-      <path
-         id="path3250"
-         d="m 21.711705,13.722069 c 0,40.782784 0,40.782784 0,40.782784"
-         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.06299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;enable-background:accumulate" />
-      <path
-         id="path3252"
-         d="m 49.163386,13.713031 c 0,40.79173 0,40.79173 0,40.79173"
-         style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.06299;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;enable-background:accumulate" />
-      <path
-         id="path2772"
-         d="m 33.467862,60.827788 c -3.577648,-0.413125 -6.10002,-1.215482 -8.214142,-2.612883 -1.024074,-0.676902 -2.204101,-1.968578 -2.580352,-2.8245 -0.378258,-0.860483 -0.364238,-0.03958 -0.365254,-21.38872 l -9.66e-4,-20.317289 0.233492,-0.468565 c 1.031623,-2.070193 4.817372,-3.6605684 10.034365,-4.2153907 1.367601,-0.1454429 4.725174,-0.1186857 6.160715,0.049095 4.897705,0.5724277 8.598366,2.1804967 9.587937,4.1662967 l 0.233491,0.468564 -9.55e-4,20.317289 c -0.0011,22.298611 0.04107,20.607328 -0.540759,21.698958 -1.275447,2.393011 -4.759848,4.264225 -9.235072,4.959511 -0.934233,0.145142 -4.525825,0.258473 -5.3125,0.167634 z"
-         style="opacity:1;fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.09491;stroke-linecap:square;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
-    </g>
-  </g>
-</svg>
diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs
index a512ce418ae140ab8525720b3e472c22dcb4ea5a..c2ee75c6ec9392fba42e23116bdf2bc219d03777 100644
--- a/src/ui/panes/pid_drawing_tool.rs
+++ b/src/ui/panes/pid_drawing_tool.rs
@@ -1,31 +1,27 @@
 mod connections;
 mod elements;
 mod grid;
-mod pos;
 mod symbols;
 
 use connections::Connection;
 use core::f32;
-use egui::{
-    epaint::PathStroke, Color32, Context, CursorIcon, Painter, PointerButton, Pos2, Rounding,
-    Sense, Stroke, Theme, Ui, Vec2,
-};
+use egui::{Color32, Context, CursorIcon, PointerButton, Response, Sense, Theme, Ui};
 use elements::Element;
-use grid::{GridInfo, LINE_THICKNESS};
-use pos::Pos;
+use glam::Vec2;
+use grid::GridInfo;
 use serde::{Deserialize, Serialize};
 use std::f32::consts::PI;
 use strum::IntoEnumIterator;
 use symbols::Symbol;
 
-use crate::ui::composable_view::PaneResponse;
+use crate::ui::{composable_view::PaneResponse, utils::egui_to_glam};
 
 use super::PaneBehavior;
 
 #[derive(Clone, Serialize, Deserialize, PartialEq)]
 enum Action {
     Connect(usize),
-    ContextMenu(Pos2),
+    ContextMenu(Vec2),
     DragElement(usize),
     DragConnection(usize, usize),
     DragGrid,
@@ -59,93 +55,32 @@ impl PaneBehavior for PidPane {
         if self.editable {
             self.draw_grid(ui, theme);
         }
-
-        self.draw_connections(ui, theme, self.editable);
+        self.draw_connections(ui, theme);
         self.draw_elements(ui, theme);
 
-        // Allocate the space to sense inputs
+        // Handle things that require knowing the position of the pointer
         let (_, response) = ui.allocate_at_least(ui.max_rect().size(), Sense::click_and_drag());
-        let pointer_pos = response.hover_pos();
-
-        if let Some(pointer_pos) = pointer_pos {
+        if let Some(pointer_pos) = response.hover_pos().map(|p| egui_to_glam(p.to_vec2())) {
             if self.editable {
-                self.handle_zoom(ui, theme, &pointer_pos);
+                self.handle_zoom(ui, theme, pointer_pos);
             }
-        }
 
-        // Set grab icon when hovering an element
-        if let Some(pointer_pos) = &pointer_pos {
-            if self.editable
-                && (self.is_hovering_element(pointer_pos)
-                    || self.is_hovering_connection_point(pointer_pos))
-            {
+            // Set grab icon when hovering something
+            let hovers_element = self.hovers_element(pointer_pos).is_some();
+            let hovers_connection_point = self.hovers_connection_point(pointer_pos).is_some();
+            if self.editable && (hovers_element || hovers_connection_point) {
                 ui.ctx()
                     .output_mut(|output| output.cursor_icon = CursorIcon::Grab);
             }
-        }
 
-        // Detect the action
-        if let Some(pointer_pos) = &pointer_pos {
-            if response.clicked_by(PointerButton::Secondary) {
-                println!("Context menu opened");
-                self.action = Some(Action::ContextMenu(*pointer_pos));
-            } else if self.editable {
-                if response.drag_started() {
-                    if response.dragged_by(PointerButton::Middle) {
-                        self.action = Some(Action::DragGrid);
-                        println!("Grid drag started");
-                    } else if let Some(drag_element_action) = self
-                        .find_hovered_element_idx(pointer_pos)
-                        .map(Action::DragElement)
-                    {
-                        self.action = Some(drag_element_action);
-                        println!("Element drag started");
-                    } else if let Some(drag_connection_point) = self
-                        .find_hovered_connection_point(pointer_pos)
-                        .map(|(idx1, idx2)| Action::DragConnection(idx1, idx2))
-                    {
-                        self.action = Some(drag_connection_point);
-                        println!("Connection point drag started");
-                    }
-                } else if response.drag_stopped() {
-                    self.action.take();
-                    println!("Drag stopped");
-                }
-            }
+            self.detect_action(&response, pointer_pos);
+            self.handle_actions(&response, pointer_pos);
         }
 
-        // Context menu
+        // The context menu does not need the pointer's position.
+        // If active it has to be shown even if the pointer goes off screen.
         if let Some(Action::ContextMenu(pointer_pos)) = self.action.clone() {
-            response.context_menu(|ui| self.draw_context_menu(ui, &pointer_pos));
-        }
-
-        // Connect action
-        if let Some(pointer_pos) = pointer_pos {
-            match self.action {
-                Some(Action::Connect(start)) => {
-                    if let Some(end) = self.find_hovered_element_idx(&pointer_pos) {
-                        if response.clicked() {
-                            if start != end {
-                                self.connections.push(Connection::new(start, 0, end, 0));
-                                println!("Added connection from {} to {}", start, end);
-                            }
-                            self.action.take();
-                            println!("Connect action ended");
-                        }
-                    }
-                }
-                Some(Action::DragElement(idx)) => {
-                    self.elements[idx].position = Pos::from_pos2(&self.grid, &pointer_pos)
-                }
-                Some(Action::DragConnection(conn_idx, midpoint_idx)) => {
-                    self.connections[conn_idx].middle_points[midpoint_idx] =
-                        Pos::from_pos2(&self.grid, &pointer_pos);
-                }
-                Some(Action::DragGrid) => {
-                    self.grid.zero_pos += response.drag_delta();
-                }
-                _ => {}
-            }
+            response.context_menu(|ui| self.draw_context_menu(ui, pointer_pos));
         }
 
         PaneResponse::default()
@@ -157,20 +92,6 @@ impl PaneBehavior for PidPane {
 }
 
 impl PidPane {
-    fn is_hovering_element(&self, pointer_pos: &Pos2) -> bool {
-        self.elements
-            .iter()
-            .any(|element| element.contains(&self.grid, pointer_pos))
-    }
-
-    fn is_hovering_connection_point(&self, pointer_pos: &Pos2) -> bool {
-        self.connections.iter().any(|conn| {
-            conn.middle_points
-                .iter()
-                .any(|p| p.distance(&self.grid, pointer_pos) < 10.0)
-        })
-    }
-
     /// Returns the currently used theme
     fn find_theme(ctx: &Context) -> Theme {
         // In Egui you can either decide a theme or use the system one.
@@ -193,37 +114,32 @@ impl PidPane {
         }
     }
 
-    fn find_hovered_element_idx(&self, pos: &Pos2) -> Option<usize> {
+    /// Returns the index of the element the point is on, if any
+    fn hovers_element(&self, p_s: Vec2) -> Option<usize> {
         self.elements
             .iter()
-            .position(|elem| elem.contains(&self.grid, pos))
-    }
-
-    fn find_hovered_element_mut(&mut self, pos: &Pos2) -> Option<&mut Element> {
-        self.elements
-            .iter_mut()
-            .find(|element| element.contains(&self.grid, pos))
+            .position(|elem| elem.contains(self.grid.screen_to_grid(p_s)))
     }
 
     /// Return the connection and segment indexes where the position is on, if any
-    fn find_hovered_connection_idx(&self, pos: &Pos2) -> Option<(usize, usize)> {
+    fn hovers_connection(&self, p_s: Vec2) -> Option<(usize, usize)> {
         self.connections
             .iter()
             .enumerate()
-            .find_map(|(idx, conn)| Some(idx).zip(conn.contains(self, pos)))
+            .find_map(|(conn_idx, conn)| {
+                let segm_idx = conn.contains(self, p_s);
+                Some(conn_idx).zip(segm_idx)
+            })
     }
 
-    fn find_hovered_connection_point(&self, pos: &Pos2) -> Option<(usize, usize)> {
-        let mut midpoint_idx = Some(0);
-        let connection_idx = self.connections.iter().position(|conn| {
-            midpoint_idx = conn
-                .middle_points
-                .iter()
-                .position(|p| p.distance(&self.grid, pos) < 12.0);
-            midpoint_idx.is_some()
-        });
-
-        connection_idx.zip(midpoint_idx)
+    fn hovers_connection_point(&self, p_s: Vec2) -> Option<(usize, usize)> {
+        self.connections
+            .iter()
+            .enumerate()
+            .find_map(|(conn_idx, conn)| {
+                let p_idx = conn.hovers_point(self.grid.screen_to_grid(p_s));
+                Some(conn_idx).zip(p_idx)
+            })
     }
 
     fn draw_grid(&self, ui: &Ui, theme: Theme) {
@@ -231,24 +147,20 @@ impl PidPane {
         let window_rect = ui.max_rect();
         let dot_color = PidPane::dots_color(theme);
 
-        let offset_x = (self.grid.zero_pos.x % self.grid.get_size()) as i32;
-        let offset_y = (self.grid.zero_pos.y % self.grid.get_size()) as i32;
+        let offset_x = (self.grid.zero_pos.x % self.grid.size()) as i32;
+        let offset_y = (self.grid.zero_pos.y % self.grid.size()) as i32;
 
-        let start_x = (window_rect.min.x / self.grid.get_size()) as i32
-            * self.grid.get_size() as i32
+        let start_x =
+            (window_rect.min.x / self.grid.size()) as i32 * self.grid.size() as i32 + offset_x;
+        let end_x = (window_rect.max.x / self.grid.size() + 2.0) as i32 * self.grid.size() as i32
             + offset_x;
-        let end_x = (window_rect.max.x / self.grid.get_size() + 2.0) as i32
-            * self.grid.get_size() as i32
-            + offset_x;
-        let start_y = (window_rect.min.y / self.grid.get_size()) as i32
-            * self.grid.get_size() as i32
-            + offset_y;
-        let end_y = (window_rect.max.y / self.grid.get_size() + 2.0) as i32
-            * self.grid.get_size() as i32
+        let start_y =
+            (window_rect.min.y / self.grid.size()) as i32 * self.grid.size() as i32 + offset_y;
+        let end_y = (window_rect.max.y / self.grid.size() + 2.0) as i32 * self.grid.size() as i32
             + offset_y;
 
-        for x in (start_x..end_x).step_by(self.grid.get_size() as usize) {
-            for y in (start_y..end_y).step_by(self.grid.get_size() as usize) {
+        for x in (start_x..end_x).step_by(self.grid.size() as usize) {
+            for y in (start_y..end_y).step_by(self.grid.size() as usize) {
                 let rect = egui::Rect::from_min_size(
                     egui::Pos2::new(x as f32, y as f32),
                     egui::Vec2::new(1.0, 1.0),
@@ -258,146 +170,75 @@ impl PidPane {
         }
     }
 
-    fn draw_connections(&self, ui: &Ui, theme: Theme, draw_handles: bool) {
+    fn draw_connections(&self, ui: &Ui, theme: Theme) {
         let painter = ui.painter();
-        let color = match theme {
-            Theme::Light => Color32::BLACK,
-            Theme::Dark => Color32::WHITE,
-        };
 
-        // Each connection is composed from multiple lines
         for conn in &self.connections {
-            let start = self.elements[conn.start].get_anchor(&self.grid, conn.start_anchor);
-            let end = self.elements[conn.end].get_anchor(&self.grid, conn.end_anchor);
-
-            let points: Vec<Pos2> = conn
-                .middle_points
-                .iter()
-                .map(|p| p.to_pos2(&self.grid))
-                .collect();
-
-            // Draw line segments
-            if points.is_empty() {
-                self.draw_connection_segment(painter, color, start, end);
-            } else {
-                self.draw_connection_segment(painter, color, start, *points.first().unwrap());
-                for i in 0..(points.len() - 1) {
-                    self.draw_connection_segment(painter, color, points[i], points[i + 1]);
-                }
-                self.draw_connection_segment(painter, color, *points.last().unwrap(), end);
-            }
-
-            // Draw handles (dragging boxes)
-            if draw_handles {
-                for point in points {
-                    painter.rect(
-                        egui::Rect::from_center_size(
-                            point,
-                            Vec2::new(self.grid.get_size(), self.grid.get_size()),
-                        ),
-                        Rounding::ZERO,
-                        Color32::DARK_GRAY,
-                        Stroke::NONE,
-                    );
-                }
-            }
+            conn.draw(self, painter, theme);
         }
     }
 
-    fn draw_connection_segment(&self, painter: &Painter, color: Color32, a: Pos2, b: Pos2) {
-        painter.line_segment(
-            [a, b],
-            PathStroke::new(LINE_THICKNESS * self.grid.get_size(), color),
-        );
-    }
-
     fn draw_elements(&self, ui: &Ui, theme: Theme) {
         for element in &self.elements {
-            let image_rect = egui::Rect::from_center_size(
-                element.position.to_pos2(&self.grid),
-                Vec2::splat(element.size as f32 * self.grid.get_size()),
-            );
-
-            egui::Image::new(element.symbol.get_image(theme))
-                .rotate(element.rotation, Vec2::new(0.5, 0.5))
-                .paint_at(ui, image_rect);
+            element.draw(&self.grid, ui, theme);
         }
     }
 
-    fn draw_context_menu(&mut self, ui: &mut Ui, pointer_pos: &Pos2) {
+    fn draw_context_menu(&mut self, ui: &mut Ui, pointer_pos: Vec2) {
         ui.set_max_width(120.0); // To make sure we wrap long text
 
         if !self.editable {
             if ui.button("Enable editing").clicked() {
                 self.editable = true;
-                ui.close_menu();
+                self.close_context_menu(ui);
             }
             ui.checkbox(&mut self.center_content, "Center");
             return;
         }
 
-        if self.is_hovering_element(pointer_pos) {
-            let hovered_element = self.find_hovered_element_idx(pointer_pos);
+        let elem_idx = self.hovers_element(pointer_pos);
+        if let Some(elem_idx) = elem_idx {
             if ui.button("Connect").clicked() {
-                if let Some(idx) = hovered_element {
-                    println!("Connect action started");
-                    self.action = Some(Action::Connect(idx));
-                } else {
-                    panic!("No element found where the \"Connect\" action was issued");
-                }
-                ui.close_menu();
+                self.action = Some(Action::Connect(elem_idx));
+                self.close_context_menu(ui);
+                println!("Connect action started on {}", elem_idx);
             }
             if ui.button("Rotate 90° ⟲").clicked() {
-                if let Some(elem) = self.find_hovered_element_mut(pointer_pos) {
-                    elem.rotation += PI / 2.0;
-                } else {
-                    panic!("No element found where the \"Rotate 90° ⟲\" action was issued");
-                }
-                ui.close_menu();
+                self.elements[elem_idx].rotate(-PI / 2.0);
+                self.close_context_menu(ui);
             }
             if ui.button("Rotate 90° ⟳").clicked() {
-                if let Some(elem) = self.find_hovered_element_mut(pointer_pos) {
-                    elem.rotation -= PI / 2.0;
-                } else {
-                    panic!("No element found where the \"Rotate 90° ⟳\" action was issued");
-                }
-                ui.close_menu();
+                self.elements[elem_idx].rotate(PI / 2.0);
+                self.close_context_menu(ui);
             }
             if ui.button("Delete").clicked() {
-                if let Some(idx) = self.find_hovered_element_idx(pointer_pos) {
-                    self.delete_element(idx);
-                } else {
-                    panic!("No element found where the \"Delete\" action was issued");
-                }
-                ui.close_menu();
+                self.delete_element(elem_idx);
+                self.close_context_menu(ui);
             }
-        } else if let Some((conn_idx, segm_idx)) = self.find_hovered_connection_idx(pointer_pos) {
+        } else if let Some((conn_idx, segm_idx)) = self.hovers_connection(pointer_pos) {
             if ui.button("Split").clicked() {
-                println!("Splitting connection line");
-                self.connections[conn_idx].split(segm_idx, Pos::from_pos2(&self.grid, pointer_pos));
-                ui.close_menu();
+                self.connections[conn_idx].split(segm_idx, self.grid.screen_to_grid(pointer_pos));
+                self.close_context_menu(ui);
             }
             if ui.button("Change start anchor").clicked() {
                 let conn = &mut self.connections[conn_idx];
-                conn.start_anchor = (conn.start_anchor + 1)
-                    % self.elements[conn.start].symbol.get_anchor_points().len();
-                ui.close_menu();
+                conn.start_anchor =
+                    (conn.start_anchor + 1) % self.elements[conn.start].anchor_points_len();
+                self.close_context_menu(ui);
             }
             if ui.button("Change end anchor").clicked() {
                 let conn = &mut self.connections[conn_idx];
-                conn.end_anchor = (conn.end_anchor + 1)
-                    % self.elements[conn.end].symbol.get_anchor_points().len();
-                ui.close_menu();
+                conn.end_anchor =
+                    (conn.end_anchor + 1) % self.elements[conn.end].anchor_points_len();
+                self.close_context_menu(ui);
             }
         } else {
             ui.menu_button("Symbols", |ui| {
                 for symbol in Symbol::iter() {
                     if ui.button(symbol.to_string()).clicked() {
-                        self.elements.push(Element::new(
-                            Pos::from_pos2(&self.grid, pointer_pos),
-                            symbol,
-                        ));
-                        ui.close_menu();
+                        self.elements
+                            .push(Element::new(self.grid.screen_to_grid(pointer_pos), symbol));
+                        self.close_context_menu(ui);
                     }
                 }
             });
@@ -409,41 +250,60 @@ impl PidPane {
         }
     }
 
-    fn delete_element(&mut self, idx: usize) {
+    fn close_context_menu(&mut self, ui: &mut Ui) {
+        ui.close_menu();
+        self.action.take();
+    }
+
+    /// Removes an element from the diagram
+    fn delete_element(&mut self, elem_idx: usize) {
         // First delete connection referencing this element
-        self.connections
-            .retain(|elem| elem.start != idx && elem.end != idx);
+        self.connections.retain(|elem| !elem.connected(elem_idx));
 
         // Then the element
-        self.elements.remove(idx);
+        self.elements.remove(elem_idx);
     }
 
     fn center(&mut self, ui: &Ui) {
-        let ui_center = ui.max_rect().center();
+        let ui_center = egui_to_glam(ui.max_rect().center().to_vec2());
 
-        let points: Vec<Pos> = self
+        // Chain elements positions and connection mid points
+        let points: Vec<Vec2> = self
             .elements
             .iter()
-            .map(|e| e.position.clone())
-            .chain(
-                self.connections
-                    .iter()
-                    .flat_map(|conn| conn.middle_points.clone()),
-            )
+            .map(|e| e.position())
+            .chain(self.connections.iter().flat_map(|conn| conn.points()))
             .collect();
 
-        let min_x = points.iter().map(|p| p.x).min().unwrap();
-        let max_x = points.iter().map(|p| p.x).max().unwrap();
-        let min_y = points.iter().map(|p| p.y).min().unwrap();
-        let max_y = points.iter().map(|p| p.y).max().unwrap();
+        let min_x = points
+            .iter()
+            .map(|p| p.x)
+            .min_by(|a, b| a.total_cmp(b))
+            .unwrap();
+        let min_y = points
+            .iter()
+            .map(|p| p.y)
+            .min_by(|a, b| a.total_cmp(b))
+            .unwrap();
+        let min = Vec2::new(min_x, min_y);
 
-        let pid_center =
-            Pos::new((max_x + min_x) / 2, (max_y + min_y) / 2).to_relative_pos2(&self.grid);
+        let max_x = points
+            .iter()
+            .map(|p| p.x)
+            .max_by(|a, b| a.total_cmp(b))
+            .unwrap();
+        let max_y = points
+            .iter()
+            .map(|p| p.y)
+            .max_by(|a, b| a.total_cmp(b))
+            .unwrap();
+        let max = Vec2::new(max_x, max_y);
 
-        self.grid.zero_pos = ui_center - pid_center.to_vec2();
+        let center_g = (min + max) / 2.0;
+        self.grid.zero_pos = ui_center - center_g * self.grid.size();
     }
 
-    fn handle_zoom(&mut self, ui: &Ui, theme: Theme, pointer_pos: &Pos2) {
+    fn handle_zoom(&mut self, ui: &Ui, theme: Theme, pointer_pos: Vec2) {
         let scroll_delta = ui.input(|i| i.raw_scroll_delta).y;
         if scroll_delta != 0.0 {
             self.grid.apply_scroll_delta(scroll_delta, pointer_pos);
@@ -455,4 +315,63 @@ impl PidPane {
             }
         }
     }
+
+    fn detect_action(&mut self, response: &Response, pointer_pos: Vec2) {
+        if response.clicked_by(PointerButton::Secondary) {
+            self.action = Some(Action::ContextMenu(pointer_pos));
+        } else if self.editable {
+            if response.drag_started() {
+                if response.dragged_by(PointerButton::Middle) {
+                    self.action = Some(Action::DragGrid);
+                } else if let Some(drag_element_action) =
+                    self.hovers_element(pointer_pos).map(Action::DragElement)
+                {
+                    self.action = Some(drag_element_action);
+                } else if let Some(drag_connection_point) = self
+                    .hovers_connection_point(pointer_pos)
+                    .map(|(idx1, idx2)| Action::DragConnection(idx1, idx2))
+                {
+                    self.action = Some(drag_connection_point);
+                }
+            } else if response.drag_stopped() {
+                self.action.take();
+            }
+        }
+    }
+
+    fn handle_actions(&mut self, response: &Response, pointer_pos: Vec2) {
+        println!("Handling actions");
+        match self.action {
+            Some(Action::Connect(start)) => {
+                println!("Handling connect action");
+                if response.clicked() {
+                    println!("A click occurred");
+                    if let Some(end) = self.hovers_element(pointer_pos) {
+                        println!("The pointer was hovering the element {}", end);
+                        if start != end {
+                            self.connections.push(Connection::new(start, 0, end, 0));
+                            println!("Connect action ended on {}", end);
+                        } else {
+                            println!("Connect action onded on the same element")
+                        }
+                        self.action.take();
+                    }
+                }
+            }
+            Some(Action::DragElement(idx)) => {
+                let pointer_pos_g = self.grid.screen_to_grid(pointer_pos).round();
+                self.elements[idx].set_center_at(pointer_pos_g);
+            }
+            Some(Action::DragConnection(conn_idx, point_idx)) => {
+                let pointer_pos_g = self.grid.screen_to_grid(pointer_pos).round();
+                self.connections[conn_idx].set_point(point_idx, pointer_pos_g);
+            }
+            Some(Action::DragGrid) => {
+                self.grid.zero_pos += egui_to_glam(response.drag_delta());
+            }
+            // Context menu has to be handled outside since it does not reuquire the pointer's position
+            Some(Action::ContextMenu(_)) => {}
+            None => {}
+        }
+    }
 }
diff --git a/src/ui/panes/pid_drawing_tool/connections.rs b/src/ui/panes/pid_drawing_tool/connections.rs
index 48ea80775472b991a0674493d16f300f3e98b59a..f64ec3ea5a3e00bfda8f7ebf38d33cdd04de1ae2 100644
--- a/src/ui/panes/pid_drawing_tool/connections.rs
+++ b/src/ui/panes/pid_drawing_tool/connections.rs
@@ -1,7 +1,13 @@
-use egui::Pos2;
+use egui::{epaint::PathStroke, Color32, Painter, Rect, Rounding, Stroke, Theme};
+use glam::Vec2;
 use serde::{Deserialize, Serialize};
 
-use super::{grid::LINE_DISTANCE_THRESHOLD, pos::Pos, PidPane};
+use crate::ui::utils::glam_to_egui;
+
+use super::{
+    grid::{GridInfo, CONNECTION_LINE_THICKNESS, CONNECTION_LINE_THRESHOLD, CONNECTION_POINT_SIZE},
+    PidPane,
+};
 
 #[derive(Clone, Serialize, Deserialize, PartialEq)]
 pub struct Connection {
@@ -13,8 +19,8 @@ pub struct Connection {
     pub end: usize,
     pub end_anchor: usize,
 
-    /// Coordinates of middle points
-    pub middle_points: Vec<Pos>,
+    /// Mid points in grid coordinates
+    points_g: Vec<Vec2>,
 }
 
 impl Connection {
@@ -24,31 +30,37 @@ impl Connection {
             start_anchor,
             end,
             end_anchor,
-            middle_points: Vec::new(),
+            points_g: Vec::new(),
         }
     }
 
-    /// Return the index of the segment the position is on, if any
-    pub fn contains(&self, pid: &PidPane, pos: &Pos2) -> Option<usize> {
+    /// Mid points in grid coordinates
+    pub fn points(&self) -> Vec<Vec2> {
+        self.points_g.clone()
+    }
+
+    /// Return the index of the segment the point is on, if any
+    pub fn contains(&self, pid: &PidPane, p_s: Vec2) -> Option<usize> {
+        let p_g = pid.grid.screen_to_grid(p_s);
         let mut points = Vec::new();
 
         // Append start point
-        points.push(pid.elements[self.start].get_anchor(&pid.grid, self.start_anchor));
+        points.push(pid.elements[self.start].anchor_point(self.start_anchor));
 
         // Append all midpoints
-        self.middle_points
+        self.points_g
             .iter()
-            .map(|p| p.to_pos2(&pid.grid))
+            .map(|p| pid.grid.grid_to_screen(*p))
             .for_each(|p| points.push(p));
 
         // Append end point
-        points.push(pid.elements[self.end].get_anchor(&pid.grid, self.end_anchor));
+        points.push(pid.elements[self.end].anchor_point(self.end_anchor));
 
         // Check each segment
         for i in 0..(points.len() - 1) {
             let a = points[i];
             let b = points[i + 1];
-            if is_hovering_segment(pos, &a, &b) {
+            if is_hovering_segment(p_g, a, b) {
                 return Some(i);
             }
         }
@@ -56,27 +68,95 @@ impl Connection {
         None
     }
 
-    pub fn split(&mut self, idx: usize, pos: Pos) {
-        self.middle_points.insert(idx, pos.clone());
+    /// Checks if the connection references the given element index
+    pub fn connected(&self, elem_idx: usize) -> bool {
+        self.start == elem_idx || self.end == elem_idx
+    }
+
+    /// Returns the index of the point the point is on, if any
+    pub fn hovers_point(&self, p_g: Vec2) -> Option<usize> {
+        self.points_g
+            .iter()
+            .position(|p| p.distance(p_g) < CONNECTION_POINT_SIZE)
+    }
+
+    /// Splits a segment of the connection with a new point
+    pub fn split(&mut self, idx: usize, p_g: Vec2) {
+        self.points_g.insert(idx, p_g);
+    }
+
+    /// Sets the poisition of one of the path points
+    pub fn set_point(&mut self, idx: usize, p_g: Vec2) {
+        self.points_g[idx] = p_g;
+    }
+
+    fn line_color(theme: Theme) -> Color32 {
+        match theme {
+            Theme::Light => Color32::BLACK,
+            Theme::Dark => Color32::WHITE,
+        }
+    }
+
+    pub fn draw(&self, pid: &PidPane, painter: &Painter, theme: Theme) {
+        let color = Connection::line_color(theme);
+
+        let start = pid.elements[self.start].anchor_point(self.start_anchor);
+        let start = pid.grid.grid_to_screen(start);
+        let end = pid.elements[self.end].anchor_point(self.end_anchor);
+        let end = pid.grid.grid_to_screen(end);
+
+        // Draw line segments
+        if self.points_g.is_empty() {
+            Connection::draw_segment(&pid.grid, painter, color, start, end);
+        } else {
+            let points: Vec<Vec2> = self
+                .points_g
+                .iter()
+                .map(|p| pid.grid.grid_to_screen(*p))
+                .collect();
+            Connection::draw_segment(&pid.grid, painter, color, start, *points.first().unwrap());
+            for i in 0..(points.len() - 1) {
+                Connection::draw_segment(&pid.grid, painter, color, points[i], points[i + 1]);
+            }
+            Connection::draw_segment(&pid.grid, painter, color, *points.last().unwrap(), end);
+
+            if pid.editable {
+                for point in points {
+                    painter.rect(
+                        Rect::from_center_size(
+                            glam_to_egui(point).to_pos2(),
+                            egui::Vec2::splat(CONNECTION_POINT_SIZE * pid.grid.size()),
+                        ),
+                        Rounding::ZERO,
+                        Color32::DARK_GRAY,
+                        Stroke::NONE,
+                    );
+                }
+            }
+        }
+    }
+
+    fn draw_segment(grid: &GridInfo, painter: &Painter, color: Color32, a: Vec2, b: Vec2) {
+        painter.line_segment(
+            [glam_to_egui(a).to_pos2(), glam_to_egui(b).to_pos2()],
+            PathStroke::new(CONNECTION_LINE_THICKNESS * grid.size(), color),
+        );
     }
 }
 
-fn distance(a: &Pos2, b: &Pos2) -> f32 {
+fn distance(a: Vec2, b: Vec2) -> f32 {
     ((a.x - b.x).powi(2) + (a.y - b.y).powi(2)).sqrt()
 }
 
 /// Distance of a from the line defined by b and c
-fn distance_from_line(p: &Pos2, m: f32, q: f32) -> f32 {
+fn distance_from_line(p: Vec2, m: f32, q: f32) -> f32 {
     (p.y - m * p.x - q).abs() / (1.0 + m * m).sqrt()
 }
 
 /// True if p hovers the segment defined by a and b
-fn is_hovering_segment(p: &Pos2, a: &Pos2, b: &Pos2) -> bool {
+fn is_hovering_segment(p: Vec2, a: Vec2, b: Vec2) -> bool {
     if a != b {
-        let midpoint = Pos2 {
-            x: (a.x + b.x) / 2.0,
-            y: (a.y + b.y) / 2.0,
-        };
+        let midpoint = (a + b) / 2.0;
         let m = (a.y - b.y) / (a.x - b.x);
 
         let (d1, d2) = if m == 0.0 {
@@ -97,7 +177,7 @@ fn is_hovering_segment(p: &Pos2, a: &Pos2, b: &Pos2) -> bool {
 
         let length = distance(a, b);
 
-        d1 <= LINE_DISTANCE_THRESHOLD && d2 <= length
+        d1 <= CONNECTION_LINE_THRESHOLD && d2 <= length
     } else {
         false
     }
diff --git a/src/ui/panes/pid_drawing_tool/elements.rs b/src/ui/panes/pid_drawing_tool/elements.rs
index 446c444e03cda8d3ab653a1f2b66552912108f5a..49a6f6f76f5acc6a10ae8259e6c19e26cff9c490 100644
--- a/src/ui/panes/pid_drawing_tool/elements.rs
+++ b/src/ui/panes/pid_drawing_tool/elements.rs
@@ -1,44 +1,124 @@
+use crate::ui::utils::glam_to_egui;
+
+use super::grid::GridInfo;
 use super::symbols::Symbol;
-use super::{grid::GridInfo, pos::Pos};
-use egui::{Pos2, Vec2};
+use egui::{Rect, Theme, Ui};
+use glam::{Mat2, Vec2};
 use serde::{Deserialize, Serialize};
 
 #[derive(Clone, Serialize, Deserialize, PartialEq)]
 pub struct Element {
-    /// Anchor postion in the grid, symbol center
-    pub position: Pos,
+    /// Anchor postion in grid coordinates, top-left corner
+    position: glam::Vec2,
 
-    /// Size in grid units
-    pub size: i32,
+    /// Symbol to be displayed
+    symbol: Symbol,
 
     /// Rotation in radiants
-    pub rotation: f32,
+    rotation: f32,
 
-    /// Symbol to be displayed
-    pub symbol: Symbol,
+    /// Anchor point in grid coordinates relative to the element's center
+    ///
+    /// These vectors include the current rotation of the element.
+    /// They are cached to avoid recomputing the rotation.
+    anchor_points: Vec<Vec2>,
 }
 
 impl Element {
-    pub fn new(pos: Pos, symbol: Symbol) -> Self {
+    pub fn new(position: Vec2, symbol: Symbol) -> Self {
         Self {
-            position: pos,
-            size: 10,
+            position,
             rotation: 0.0,
+            anchor_points: symbol.anchor_points(),
             symbol,
         }
     }
 
-    pub fn contains(&self, grid: &GridInfo, pos: &Pos2) -> bool {
-        let start = self.position.add_size(-self.size / 2).to_pos2(grid);
-        let end = self.position.add_size(self.size / 2).to_pos2(grid);
+    /// Check if the given position is inside the element
+    pub fn contains(&self, p_g: Vec2) -> bool {
+        // First we need to do a rotostranslation from the grid's frame to the element's frame
+        let rotm = Mat2::from_angle(-self.rotation);
+        let p_e = rotm * (p_g - self.position);
+
+        // The bounding box is just the size
+        let min_e = Vec2::ZERO;
+        let max_e = self.symbol.size();
+
+        // Check if the point is in the bounding box
+        min_e.x <= p_e.x && p_e.x <= max_e.x && min_e.y <= p_e.y && p_e.y <= max_e.y
+    }
+
+    /// Moves the element such that its center is at the given position
+    pub fn set_center_at(&mut self, p_g: Vec2) {
+        // Rotation matrix from element's frame to grid's frame
+        let rotm_e_to_g = Mat2::from_angle(self.rotation);
+
+        // Center in grid's frame
+        let center_g = rotm_e_to_g * self.symbol.size() / 2.0;
+
+        self.position = p_g - center_g;
+    }
+
+    pub fn change_symbol(&mut self, symbol: Symbol) {
+        self.symbol = symbol;
+
+        // Anchor points can be different between symbols, realod the cache
+        self.reload_anchor_points();
+    }
+
+    /// Rotate the element by its center
+    pub fn rotate(&mut self, rotation: f32) {
+        // Current center position relative to the top-left point in the grid reference frame
+        let center_g = Mat2::from_angle(self.rotation) * self.symbol.size() / 2.0;
+
+        // Rotate the position by the element's center
+        self.position += (Mat2::IDENTITY - Mat2::from_angle(rotation)) * center_g;
+
+        // Update absolute rotation
+        self.rotation += rotation;
+
+        // Recompute anchor points cache
+        self.reload_anchor_points();
+    }
+
+    fn reload_anchor_points(&mut self) {
+        // Rotation matrix from element's frame to grid's frame
+        let rotm_e_to_g = Mat2::from_angle(self.rotation);
+
+        // Then rotate the anchor points
+        self.anchor_points = self
+            .symbol
+            .anchor_points()
+            .iter()
+            .map(|&p| rotm_e_to_g * p)
+            .collect();
+    }
+
+    /// Returns the position of one anchor point in grid coordinates
+    pub fn anchor_point(&self, idx: usize) -> Vec2 {
+        self.anchor_points[idx] + self.position
+    }
+
+    pub fn anchor_points_len(&self) -> usize {
+        self.anchor_points.len()
+    }
+
+    /// Size in grid units
+    pub fn size(&self) -> Vec2 {
+        self.symbol.size()
+    }
 
-        (start.x <= pos.x && pos.x < end.x) && (start.y <= pos.y && pos.y < end.y)
+    /// Position of the element's top-left corner
+    pub fn position(&self) -> Vec2 {
+        self.position
     }
 
-    pub fn get_anchor(&self, grid: &GridInfo, idx: usize) -> Pos2 {
-        let anchor = self.symbol.get_anchor_points()[idx];
-        let anchor = Vec2::from(anchor) * self.size as f32 * grid.get_size();
+    pub fn draw(&self, grid: &GridInfo, ui: &Ui, theme: Theme) {
+        let center = glam_to_egui(grid.grid_to_screen(self.position)).to_pos2();
+        let image_rect = Rect::from_min_size(center, glam_to_egui(self.size() * grid.size()));
 
-        self.position.to_pos2(grid) + anchor
+        egui::Image::new(self.symbol.get_image(theme))
+            .rotate(self.rotation, egui::Vec2::splat(0.0))
+            .paint_at(ui, image_rect);
     }
 }
diff --git a/src/ui/panes/pid_drawing_tool/grid.rs b/src/ui/panes/pid_drawing_tool/grid.rs
index 9977c69ca1aea025d095866d51e335208c70c371..735218a87e9408ccba0df9895addd7714a785e21 100644
--- a/src/ui/panes/pid_drawing_tool/grid.rs
+++ b/src/ui/panes/pid_drawing_tool/grid.rs
@@ -1,4 +1,6 @@
-use egui::Pos2;
+use core::f32;
+
+use glam::Vec2;
 use serde::{Deserialize, Serialize};
 
 const DEFAULT_SIZE: f32 = 10.0;
@@ -6,47 +8,54 @@ const MIN_SIZE: f32 = 5.0;
 const MAX_SIZE: f32 = 50.0;
 const SCROLL_DELTA: f32 = 1.0;
 
-pub const LINE_DISTANCE_THRESHOLD: f32 = 5.0;
-pub const LINE_THICKNESS: f32 = 0.2;
+pub const CONNECTION_LINE_THRESHOLD: f32 = 5.0; // Pixels
+pub const CONNECTION_LINE_THICKNESS: f32 = 0.2; // Grid units
+pub const CONNECTION_POINT_SIZE: f32 = 1.0; // Grid units
 
 #[derive(Clone, Serialize, Deserialize, PartialEq)]
 pub struct GridInfo {
-    pub zero_pos: Pos2,
+    /// Grid's zero position on screen
+    pub zero_pos: Vec2,
     size: f32,
 }
 
 impl Default for GridInfo {
     fn default() -> Self {
         Self {
-            zero_pos: Pos2::ZERO,
+            zero_pos: Vec2::ZERO,
             size: DEFAULT_SIZE,
         }
     }
 }
 
 impl GridInfo {
-    pub fn get_size(&self) -> f32 {
+    /// Returns the grid size
+    pub fn size(&self) -> f32 {
         self.size
     }
 
-    pub fn apply_scroll_delta(&mut self, delta: f32, pos: &Pos2) {
-        if delta == 0.0 {
+    /// Applies the scroll delta at the given position (in screen coordinates)
+    pub fn apply_scroll_delta(&mut self, delta: f32, pos_s: Vec2) {
+        if delta == 0.0 || delta == f32::NAN {
             return;
         }
 
         let old_size = self.size;
-        if delta > 0.0 {
-            self.size += SCROLL_DELTA;
-        } else {
-            self.size -= SCROLL_DELTA;
-        };
-
-        self.size = self.size.clamp(MIN_SIZE, MAX_SIZE);
+        let delta = delta.signum() * SCROLL_DELTA;
+        self.size = (self.size + delta).clamp(MIN_SIZE, MAX_SIZE);
 
         if self.size != old_size {
-            let delta_prop = self.size / old_size - 1.0;
-            let pos_delta = delta_prop * (self.zero_pos - pos.to_vec2());
-            self.zero_pos += pos_delta.to_vec2();
+            self.zero_pos += (delta / old_size) * (self.zero_pos - pos_s);
         }
     }
+
+    /// Grid to screen coordinates transformation
+    pub fn grid_to_screen(&self, p_g: Vec2) -> Vec2 {
+        p_g * self.size + self.zero_pos
+    }
+
+    /// Screen to grid coordinates transformation
+    pub fn screen_to_grid(&self, p_s: Vec2) -> Vec2 {
+        (p_s - self.zero_pos) / self.size
+    }
 }
diff --git a/src/ui/panes/pid_drawing_tool/pos.rs b/src/ui/panes/pid_drawing_tool/pos.rs
deleted file mode 100644
index bb91852492a61f3f3885b98cb2b846cfae0f641f..0000000000000000000000000000000000000000
--- a/src/ui/panes/pid_drawing_tool/pos.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-use egui::Pos2;
-use serde::{Deserialize, Serialize};
-
-use super::grid::GridInfo;
-
-#[derive(Clone, Serialize, Deserialize, PartialEq, Default)]
-pub struct Pos {
-    pub x: i32,
-    pub y: i32,
-}
-
-impl Pos {
-    pub fn new(x: i32, y: i32) -> Self {
-        Self { x, y }
-    }
-
-    pub fn add_size(&self, size: i32) -> Self {
-        Self {
-            x: self.x + size,
-            y: self.y + size,
-        }
-    }
-
-    pub fn to_pos2(&self, grid: &GridInfo) -> Pos2 {
-        Pos2 {
-            x: self.x as f32 * grid.get_size() + grid.zero_pos.x,
-            y: self.y as f32 * grid.get_size() + grid.zero_pos.y,
-        }
-    }
-
-    pub fn to_relative_pos2(&self, grid: &GridInfo) -> Pos2 {
-        Pos2 {
-            x: self.x as f32 * grid.get_size(),
-            y: self.y as f32 * grid.get_size(),
-        }
-    }
-
-    pub fn from_pos2(grid: &GridInfo, pos: &Pos2) -> Self {
-        Self {
-            x: ((pos.x - grid.zero_pos.x) / grid.get_size()) as i32,
-            y: ((pos.y - grid.zero_pos.y) / grid.get_size()) as i32,
-        }
-    }
-
-    pub fn distance(&self, grid: &GridInfo, pos: &Pos2) -> f32 {
-        let me = self.to_pos2(grid);
-
-        ((me.x - pos.x).powi(2) + (me.y - pos.y).powi(2)).sqrt()
-    }
-}
diff --git a/src/ui/panes/pid_drawing_tool/symbols.rs b/src/ui/panes/pid_drawing_tool/symbols.rs
index c0bd2123d2af4bd827623e2c5f53421f90e040ba..7f092870e57a333379ad862b838eddae5cd944b7 100644
--- a/src/ui/panes/pid_drawing_tool/symbols.rs
+++ b/src/ui/panes/pid_drawing_tool/symbols.rs
@@ -1,4 +1,5 @@
 use egui::{ImageSource, Theme};
+use glam::Vec2;
 use serde::{Deserialize, Serialize};
 use strum_macros::{Display, EnumIter};
 
@@ -7,46 +8,56 @@ pub enum Symbol {
     ManualValve,
     CheckValve,
     // ReliefValve,
-    // ControlValve,
+    MotorValve,
+    // ThreeWayValve,
     // PressureRegulator,
     // BurstDisk,
     // QuickConnector,
     // PressureTransducer,
     // PressureGauge,
     // FlexibleConnection,
-    // ThreeWayValve,
-    PressurizedVessel,
+    // PressurizedVessel,
 }
 
 impl Symbol {
     pub fn get_image(&self, theme: Theme) -> ImageSource {
         match (&self, theme) {
             (Symbol::ManualValve, Theme::Light) => {
-                egui::include_image!("../../../../icons/ball_valve_light.svg")
+                egui::include_image!("../../../../icons/pid_symbols/light/manual_valve.svg")
             }
             (Symbol::ManualValve, Theme::Dark) => {
-                egui::include_image!("../../../../icons/ball_valve_dark.svg")
+                egui::include_image!("../../../../icons/pid_symbols/light/manual_valve.svg")
             }
             (Symbol::CheckValve, Theme::Light) => {
-                egui::include_image!("../../../../icons/check_valve_light.svg")
+                egui::include_image!("../../../../icons/pid_symbols/light/check_valve.svg")
             }
             (Symbol::CheckValve, Theme::Dark) => {
-                egui::include_image!("../../../../icons/check_valve_dark.svg")
+                egui::include_image!("../../../../icons/pid_symbols/light/check_valve.svg")
             }
-            (Symbol::PressurizedVessel, Theme::Light) => {
-                egui::include_image!("../../../../icons/pressurized_vessel_light.svg")
+            (Symbol::MotorValve, Theme::Light) => {
+                egui::include_image!("../../../../icons/pid_symbols/light/motor_valve.svg")
             }
-            (Symbol::PressurizedVessel, Theme::Dark) => {
-                egui::include_image!("../../../../icons/pressurized_vessel_dark.svg")
+            (Symbol::MotorValve, Theme::Dark) => {
+                egui::include_image!("../../../../icons/pid_symbols/light/motor_valve.svg")
             }
         }
     }
 
-    pub fn get_anchor_points(&self) -> Vec<(f32, f32)> {
+    /// Symbol size in grid coordinates
+    pub fn size(&self) -> Vec2 {
+        match self {
+            Symbol::ManualValve => Vec2::new(10.0, 5.0),
+            Symbol::CheckValve => Vec2::new(10.0, 5.0),
+            Symbol::MotorValve => Vec2::new(10.0, 7.5),
+        }
+    }
+
+    /// Anchor point position relative to top right corner in grid units
+    pub fn anchor_points(&self) -> Vec<Vec2> {
         match self {
-            Symbol::ManualValve => [(-0.5, 0.0), (0.5, 0.0)].into(),
-            Symbol::CheckValve => [(-0.5, 0.0), (0.5, 0.0)].into(),
-            Symbol::PressurizedVessel => [(0.0, -0.5), (0.0, 0.5)].into(),
+            Symbol::ManualValve => [Vec2::new(0.0, 2.5), Vec2::new(10.0, 2.5)].into(),
+            Symbol::CheckValve => [Vec2::new(0.0, 2.5), Vec2::new(10.0, 2.5)].into(),
+            Symbol::MotorValve => [Vec2::new(0.0, 5.0), Vec2::new(10.0, 5.0)].into(),
         }
     }
 }
diff --git a/src/ui/utils.rs b/src/ui/utils.rs
index 7ad863175181d3b06766858c500a07707bcefc79..0c2ae3f7257254b414d9974507003ee1443a30bf 100644
--- a/src/ui/utils.rs
+++ b/src/ui/utils.rs
@@ -42,3 +42,15 @@ pub fn vertically_centered(
         .response
     }
 }
+
+#[inline(always)]
+pub fn egui_to_glam(p: egui::Vec2) -> glam::Vec2 {
+    let p: mint::Vector2<f32> = p.into();
+    glam::Vec2::from(p)
+}
+
+#[inline(always)]
+pub fn glam_to_egui(p: glam::Vec2) -> egui::Vec2 {
+    let p: mint::Vector2<f32> = p.into();
+    egui::Vec2::from(p)
+}