diff --git a/src/ui/panes/pid_drawing_tool.rs b/src/ui/panes/pid_drawing_tool.rs
index a2354bee5a9e231f0881f58cdee8896439c5dc9a..160b421e9ed35651d097cc330bf5d7af03401b9a 100644
--- a/src/ui/panes/pid_drawing_tool.rs
+++ b/src/ui/panes/pid_drawing_tool.rs
@@ -11,7 +11,7 @@ use egui::{
     Sense, Stroke, Theme, Ui, Vec2,
 };
 use elements::Element;
-use grid::GridInfo;
+use grid::{GridInfo, LINE_THICKNESS};
 use pos::Pos;
 use serde::{Deserialize, Serialize};
 use std::f32::consts::PI;
@@ -32,7 +32,7 @@ enum Action {
 }
 
 /// Piping and instrumentation diagram
-#[derive(Clone, Serialize, Deserialize, PartialEq)]
+#[derive(Clone, Serialize, Deserialize, PartialEq, Default)]
 pub struct PidPane {
     elements: Vec<Element>,
     connections: Vec<Connection>,
@@ -48,22 +48,6 @@ pub struct PidPane {
     center_content: bool,
 }
 
-impl Default for PidPane {
-    fn default() -> Self {
-        Self {
-            elements: Vec::default(),
-            connections: Vec::default(),
-            grid: GridInfo {
-                zero_pos: Pos2::ZERO,
-                size: 10.0,
-            },
-            action: None,
-            editable: false,
-            center_content: false,
-        }
-    }
-}
-
 impl PaneBehavior for PidPane {
     fn ui(&mut self, ui: &mut egui::Ui) -> PaneResponse {
         let theme = PidPane::find_theme(ui.ctx());
@@ -83,6 +67,12 @@ impl PaneBehavior for PidPane {
         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 self.editable {
+                self.handle_zoom(ui, theme, &pointer_pos);
+            }
+        }
+
         // Set grab icon when hovering an element
         if let Some(pointer_pos) = &pointer_pos {
             if self.editable
@@ -98,26 +88,24 @@ impl PaneBehavior for PidPane {
         if let Some(pointer_pos) = &pointer_pos {
             if response.clicked_by(PointerButton::Secondary) {
                 println!("Context menu opened");
-                self.action = Some(Action::ContextMenu(pointer_pos.clone()));
+                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(|idx| Action::DragElement(idx))
-                        {
-                            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 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();
@@ -172,20 +160,15 @@ impl PidPane {
     fn is_hovering_element(&self, pointer_pos: &Pos2) -> bool {
         self.elements
             .iter()
-            .find(|element| element.contains(&self.grid, pointer_pos))
-            .is_some()
+            .any(|element| element.contains(&self.grid, pointer_pos))
     }
 
     fn is_hovering_connection_point(&self, pointer_pos: &Pos2) -> bool {
-        self.connections
-            .iter()
-            .find(|conn| {
-                conn.middle_points
-                    .iter()
-                    .find(|p| p.distance(&self.grid, pointer_pos) < 10.0)
-                    .is_some()
-            })
-            .is_some()
+        self.connections.iter().any(|conn| {
+            conn.middle_points
+                .iter()
+                .any(|p| p.distance(&self.grid, pointer_pos) < 10.0)
+        })
     }
 
     /// Returns the currently used theme
@@ -227,7 +210,7 @@ impl PidPane {
         self.connections
             .iter()
             .enumerate()
-            .find_map(|(idx, conn)| Some(idx).zip(conn.contains(&self, pos)))
+            .find_map(|(idx, conn)| Some(idx).zip(conn.contains(self, pos)))
     }
 
     fn find_hovered_connection_point(&self, pos: &Pos2) -> Option<(usize, usize)> {
@@ -248,13 +231,17 @@ impl PidPane {
         let window_rect = ui.max_rect();
         let dot_color = PidPane::dots_color(theme);
 
-        let start_x = (window_rect.min.x / self.grid.size) as i32 * self.grid.size as i32;
-        let end_x = (window_rect.max.x / self.grid.size + 1.0) as i32 * self.grid.size as i32;
-        let start_y = (window_rect.min.y / self.grid.size) as i32 * self.grid.size as i32;
-        let end_y = (window_rect.max.y / self.grid.size + 1.0) as i32 * self.grid.size as i32;
-
-        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 start_x =
+            (window_rect.min.x / self.grid.get_size()) as i32 * self.grid.get_size() as i32;
+        let end_x =
+            (window_rect.max.x / self.grid.get_size() + 1.0) as i32 * self.grid.get_size() as i32;
+        let start_y =
+            (window_rect.min.y / self.grid.get_size()) as i32 * self.grid.get_size() as i32;
+        let end_y =
+            (window_rect.max.y / self.grid.get_size() + 1.0) as i32 * self.grid.get_size() as i32;
+
+        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) {
                 let rect = egui::Rect::from_min_size(
                     egui::Pos2::new(x as f32, y as f32),
                     egui::Vec2::new(1.0, 1.0),
@@ -284,13 +271,13 @@ impl PidPane {
 
             // Draw line segments
             if points.is_empty() {
-                PidPane::draw_connection_segment(painter, color, start, end);
+                self.draw_connection_segment(painter, color, start, end);
             } else {
-                PidPane::draw_connection_segment(painter, color, start, *points.first().unwrap());
+                self.draw_connection_segment(painter, color, start, *points.first().unwrap());
                 for i in 0..(points.len() - 1) {
-                    PidPane::draw_connection_segment(painter, color, points[i], points[i + 1]);
+                    self.draw_connection_segment(painter, color, points[i], points[i + 1]);
                 }
-                PidPane::draw_connection_segment(painter, color, *points.last().unwrap(), end);
+                self.draw_connection_segment(painter, color, *points.last().unwrap(), end);
             }
 
             // Draw handles (dragging boxes)
@@ -299,7 +286,7 @@ impl PidPane {
                     painter.rect(
                         egui::Rect::from_center_size(
                             point,
-                            Vec2::new(self.grid.size, self.grid.size),
+                            Vec2::new(self.grid.get_size(), self.grid.get_size()),
                         ),
                         Rounding::ZERO,
                         Color32::DARK_GRAY,
@@ -310,15 +297,18 @@ impl PidPane {
         }
     }
 
-    fn draw_connection_segment(painter: &Painter, color: Color32, a: Pos2, b: Pos2) {
-        painter.line_segment([a, b], PathStroke::new(2.0, color));
+    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.size),
+                Vec2::splat(element.size as f32 * self.grid.get_size()),
             );
 
             egui::Image::new(element.symbol.get_image(theme))
@@ -339,8 +329,8 @@ impl PidPane {
             return;
         }
 
-        if self.is_hovering_element(&pointer_pos) {
-            let hovered_element = self.find_hovered_element_idx(&pointer_pos);
+        if self.is_hovering_element(pointer_pos) {
+            let hovered_element = self.find_hovered_element_idx(pointer_pos);
             if ui.button("Connect").clicked() {
                 if let Some(idx) = hovered_element {
                     println!("Connect action started");
@@ -351,7 +341,7 @@ impl PidPane {
                 ui.close_menu();
             }
             if ui.button("Rotate 90° ⟲").clicked() {
-                if let Some(elem) = self.find_hovered_element_mut(&pointer_pos) {
+                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");
@@ -359,7 +349,7 @@ impl PidPane {
                 ui.close_menu();
             }
             if ui.button("Rotate 90° ⟳").clicked() {
-                if let Some(elem) = self.find_hovered_element_mut(&pointer_pos) {
+                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");
@@ -367,14 +357,14 @@ impl PidPane {
                 ui.close_menu();
             }
             if ui.button("Delete").clicked() {
-                if let Some(idx) = self.find_hovered_element_idx(&pointer_pos) {
+                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();
             }
-        } else if let Some((conn_idx, segm_idx)) = self.find_hovered_connection_idx(&pointer_pos) {
+        } else if let Some((conn_idx, segm_idx)) = self.find_hovered_connection_idx(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));
@@ -397,7 +387,7 @@ impl PidPane {
                 for symbol in Symbol::iter() {
                     if ui.button(symbol.to_string()).clicked() {
                         self.elements.push(Element::new(
-                            Pos::from_pos2(&self.grid, &pointer_pos),
+                            Pos::from_pos2(&self.grid, pointer_pos),
                             symbol,
                         ));
                         ui.close_menu();
@@ -445,4 +435,17 @@ impl PidPane {
 
         self.grid.zero_pos = ui_center - pid_center.to_vec2();
     }
+
+    fn handle_zoom(&mut self, ui: &Ui, theme: Theme, pointer_pos: &Pos2) {
+        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);
+
+            // Invalidate the cache to redraw the images
+            for symbol in Symbol::iter() {
+                let img: egui::ImageSource = symbol.get_image(theme);
+                ui.ctx().forget_image(img.uri().unwrap());
+            }
+        }
+    }
 }
diff --git a/src/ui/panes/pid_drawing_tool/elements.rs b/src/ui/panes/pid_drawing_tool/elements.rs
index 4fbc3f8349ec6ee90306ce418a160aa02efd579e..446c444e03cda8d3ab653a1f2b66552912108f5a 100644
--- a/src/ui/panes/pid_drawing_tool/elements.rs
+++ b/src/ui/panes/pid_drawing_tool/elements.rs
@@ -37,7 +37,7 @@ impl Element {
 
     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.size;
+        let anchor = Vec2::from(anchor) * self.size as f32 * grid.get_size();
 
         self.position.to_pos2(grid) + anchor
     }
diff --git a/src/ui/panes/pid_drawing_tool/grid.rs b/src/ui/panes/pid_drawing_tool/grid.rs
index 1e04422391b3cc98427f08834b76c75064240f1e..9977c69ca1aea025d095866d51e335208c70c371 100644
--- a/src/ui/panes/pid_drawing_tool/grid.rs
+++ b/src/ui/panes/pid_drawing_tool/grid.rs
@@ -1,10 +1,52 @@
 use egui::Pos2;
 use serde::{Deserialize, Serialize};
 
-pub const LINE_DISTANCE_THRESHOLD: f32 = 5.0; // Pixels
+const DEFAULT_SIZE: f32 = 10.0;
+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;
 
 #[derive(Clone, Serialize, Deserialize, PartialEq)]
 pub struct GridInfo {
     pub zero_pos: Pos2,
-    pub size: f32,
+    size: f32,
+}
+
+impl Default for GridInfo {
+    fn default() -> Self {
+        Self {
+            zero_pos: Pos2::ZERO,
+            size: DEFAULT_SIZE,
+        }
+    }
+}
+
+impl GridInfo {
+    pub fn get_size(&self) -> f32 {
+        self.size
+    }
+
+    pub fn apply_scroll_delta(&mut self, delta: f32, pos: &Pos2) {
+        if delta == 0.0 {
+            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);
+
+        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();
+        }
+    }
 }
diff --git a/src/ui/panes/pid_drawing_tool/pos.rs b/src/ui/panes/pid_drawing_tool/pos.rs
index 88178616c6a12ac969c3c89fbeb8642009026d33..bb91852492a61f3f3885b98cb2b846cfae0f641f 100644
--- a/src/ui/panes/pid_drawing_tool/pos.rs
+++ b/src/ui/panes/pid_drawing_tool/pos.rs
@@ -23,22 +23,22 @@ impl Pos {
 
     pub fn to_pos2(&self, grid: &GridInfo) -> Pos2 {
         Pos2 {
-            x: self.x as f32 * grid.size + grid.zero_pos.x,
-            y: self.y as f32 * grid.size + grid.zero_pos.y,
+            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.size,
-            y: self.y as f32 * grid.size,
+            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.size) as i32,
-            y: ((pos.y - grid.zero_pos.y) / grid.size) as i32,
+            x: ((pos.x - grid.zero_pos.x) / grid.get_size()) as i32,
+            y: ((pos.y - grid.zero_pos.y) / grid.get_size()) as i32,
         }
     }