=== modified file 'data/ui/preferences.ui'
--- data/ui/preferences.ui	2015-07-12 17:08:16 +0000
+++ data/ui/preferences.ui	2015-07-16 08:37:41 +0000
@@ -42,6 +42,13 @@
     <property name="upper">2500</property>
     <property name="step_increment">50</property>
   </object>
+  <object class="GtkAdjustment" id="adj_zoom_percent">
+    <property name="lower">100</property>
+    <property name="upper">200</property>
+    <property name="value">150</property>
+    <property name="step_increment">5</property>
+    <property name="page_increment">10</property>
+  </object>
   <object class="GtkStack" id="dock_preferences">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
@@ -266,6 +273,44 @@
             <property name="width">2</property>
           </packing>
         </child>
+        <child>
+          <object class="GtkScale" id="s_zoom_percent">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="adjustment">adj_zoom_percent</property>
+            <property name="digits">0</property>
+            <property name="value_pos">right</property>
+          </object>
+          <packing>
+            <property name="left_attach">3</property>
+            <property name="top_attach">6</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkSwitch" id="sw_zoom_enabled">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="halign">start</property>
+            <property name="valign">center</property>
+          </object>
+          <packing>
+            <property name="left_attach">2</property>
+            <property name="top_attach">6</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="l_iconzoom">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="halign">end</property>
+            <property name="label" translatable="yes">Icon Zoom:</property>
+            <property name="justify">right</property>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">6</property>
+          </packing>
+        </child>
       </object>
       <packing>
         <property name="name">grid_appearance</property>

=== modified file 'docs/Makefile.am'
--- docs/Makefile.am	2015-06-04 20:24:37 +0000
+++ docs/Makefile.am	2015-07-16 08:37:41 +0000
@@ -33,6 +33,7 @@
 	$(top_srcdir)/lib/Drawing/DockSurface.vala \
 	$(top_srcdir)/lib/Drawing/DockTheme.vala \
 	$(top_srcdir)/lib/Drawing/Easing.vala \
+	$(top_srcdir)/lib/Drawing/SurfaceCache.vala \
 	$(top_srcdir)/lib/Drawing/Theme.vala \
 	$(top_srcdir)/lib/Factories/AbstractMain.vala \
 	$(top_srcdir)/lib/Factories/Factory.vala \

=== modified file 'lib/DockController.vala'
--- lib/DockController.vala	2015-06-11 05:36:16 +0000
+++ lib/DockController.vala	2015-07-16 08:37:41 +0000
@@ -348,7 +348,6 @@
 				&& added.size != removed.size) {
 				position_manager.update (renderer.theme);
 			} else {
-				position_manager.reset_item_caches ();
 				position_manager.update_regions ();
 			}
 			window.update_icon_regions ();
@@ -362,7 +361,6 @@
 			update_visible_elements ();
 			
 			foreach (unowned DockElement item in moved_items) {
-				position_manager.reset_item_cache (item);
 				unowned ApplicationDockItem? app_item = (item as ApplicationDockItem);
 				if (app_item != null)
 					window.update_icon_region (app_item);

=== modified file 'lib/DockPreferences.vala'
--- lib/DockPreferences.vala	2015-05-07 12:46:58 +0000
+++ lib/DockPreferences.vala	2015-07-16 08:37:41 +0000
@@ -29,6 +29,9 @@
 		public const int MIN_ICON_SIZE = 24;
 		public const int MAX_ICON_SIZE = 128;
 		
+		public const int MIN_ICON_ZOOM = 100;
+		public const int MAX_ICON_ZOOM = 200;
+		
 		[Description(nick = "current-workspace-only", blurb = "Whether to show only windows of the current workspace.")]
 		public bool CurrentWorkspaceOnly { get; set; }
 		
@@ -80,6 +83,12 @@
 		[Description(nick = "show-dock-item", blurb = "Whether to show the item for the dock itself.")]
 		public bool ShowDockItem { get; set; }
 		
+		[Description(nick = "zoom-enabled", blurb = "Whether the dock will zoom when hovered.")]
+		public bool ZoomEnabled { get; set; }
+		
+		[Description(nick = "zoom-percent", blurb = "The dock's icon-zoom (in percent).")]
+		public uint ZoomPercent { get; set; }
+		
 		/**
 		 * {@inheritDoc}
 		 */
@@ -132,6 +141,8 @@
 			PinnedOnly = false;
 			AutoPinning = true;
 			ShowDockItem = true;
+			ZoomEnabled = false;
+			ZoomPercent = 150;
 		}
 		
 		/**
@@ -235,6 +246,16 @@
 			
 			case "ShowDockItem":
 				break;
+			
+			case "ZoomEnabled":
+				break;
+			
+			case "ZoomPercent":
+				if (ZoomPercent < MIN_ICON_ZOOM)
+					ZoomPercent = MIN_ICON_ZOOM;
+				else if (ZoomPercent > MAX_ICON_ZOOM)
+					ZoomPercent = MAX_ICON_ZOOM;
+				break;
 			}
 		}
 	}

=== modified file 'lib/DockRenderer.vala'
--- lib/DockRenderer.vala	2015-07-03 20:25:11 +0000
+++ lib/DockRenderer.vala	2015-07-16 08:37:41 +0000
@@ -44,6 +44,18 @@
 		 */
 		[CCode (notify = false)]
 		double opacity { get; private set; }
+		
+		/**
+		 * The current progress [0.0..1.0] of the zoom-in-animation of the dock.
+		 */
+		[CCode (notify = false)]
+		public double zoom_in_progress { get; private set; }
+		
+		/**
+		 * The current local cursor-position on the dock if hovered.
+		 */
+		[CCode (notify = false)]
+		public Gdk.Point local_cursor { get; private set; }
 
 		DockSurface? main_buffer = null;
 		DockSurface? fade_buffer = null;
@@ -57,16 +69,19 @@
 		DockSurface? urgent_glow_buffer = null;
 		
 		int64 last_hide = 0;
+		int64 last_hovered_changed = 0;
 		
 		bool screen_is_composited = false;
 		uint reset_position_manager_timer = 0;
 		int window_scale_factor = 1;
 		bool is_first_frame = true;
+		bool zoom_changed = false;
 		
 		ulong gtk_theme_name_changed_id = 0;
 		
 		double dynamic_animation_offset = 0.0;
 		
+		Gee.ArrayList<unowned DockItem> current_items;
 		Gee.HashSet<DockItem> transient_items;
 #if BENCHMARK
 		Gee.ArrayList<string> benchmark;
@@ -86,6 +101,7 @@
 		construct
 		{
 			transient_items = new Gee.HashSet<DockItem> ();
+			current_items = new Gee.ArrayList<unowned DockItem> ();
 #if BENCHMARK
 			benchmark = new Gee.ArrayList<string> ();
 #endif
@@ -104,6 +120,7 @@
 			
 			controller.window.notify["HoveredItem"].connect (animated_draw);
 			controller.hide_manager.notify["Hidden"].connect (hidden_changed);
+			controller.hide_manager.notify["Hovered"].connect (hovered_changed);
 		}
 		
 		~DockRenderer ()
@@ -112,6 +129,7 @@
 			theme.notify.disconnect (theme_changed);
 			
 			controller.hide_manager.notify["Hidden"].disconnect (hidden_changed);
+			controller.hide_manager.notify["Hovered"].disconnect (hovered_changed);
 			controller.window.notify["HoveredItem"].disconnect (animated_draw);
 		}
 		
@@ -221,7 +239,9 @@
 		{
 			return_if_fail (theme != null);
 			
-			screen_is_composited = controller.position_manager.screen_is_composited;
+			unowned PositionManager position_manager = controller.position_manager;
+			
+			screen_is_composited = position_manager.screen_is_composited;
 			dynamic_animation_offset = 0.0;
 			
 			var fade_opacity = theme.FadeOpacity;
@@ -237,14 +257,78 @@
 				} else {
 					hide_progress = (controller.hide_manager.Hidden ? 1.0 : 0.0);
 				}
+				
+				var zoom_duration = 150 * 1000;
+				var zoom_time = int64.max (0LL, frame_time - last_hovered_changed);
+				double zoom_progress;
+				if (zoom_time < zoom_duration)
+					zoom_progress = Drawing.easing_for_mode (AnimationMode.LINEAR, zoom_time, zoom_duration);
+				else
+					zoom_progress = 1.0;
+				if (!controller.hide_manager.Hovered)
+					zoom_progress = 1.0 - zoom_progress;
+				zoom_progress *= 1.0 - hide_progress;
+				zoom_in_progress = zoom_progress;
 			} else {
 				hide_progress = 0.0;
+				zoom_in_progress = 0.0;
 			}
 			
 			if (fade_opacity < 1.0)
 				opacity = 1.0 - (1.0 - fade_opacity) * hide_progress;
 			else
 				opacity = 1.0;
+			
+			// Update *ordered* list of items
+			current_items.clear ();
+			current_items.add_all (controller.VisibleItems);
+			
+			if (screen_is_composited) {
+				var add_time = 0LL;
+				var remove_time = 0LL;
+				var move_time = 0LL;
+				var move_duration = theme.ItemMoveTime * 1000;
+				
+				var transient_items_it = transient_items.iterator ();
+				while (transient_items_it.next ()) {
+					var item = transient_items_it.get ();
+					add_time = item.AddTime;
+					remove_time = item.RemoveTime;
+					
+					if (add_time > remove_time) {
+						move_time = frame_time - add_time;
+						if (move_time < move_duration) {
+							if (!current_items.contains (item))
+								current_items.add (item);
+						} else {
+							transient_items_it.remove ();
+						}
+					} else if (remove_time > 0) {
+						move_time = frame_time - remove_time;
+						if (move_time < move_duration) {
+							if (!current_items.contains (item))
+								current_items.add (item);
+						} else {
+							transient_items_it.remove ();
+						}
+					}
+				}
+			} else {
+				transient_items.clear ();
+			}
+			
+#if HAVE_GEE_0_8
+			current_items.sort ((CompareDataFunc) compare_dock_item_position);
+#else
+			current_items.sort ((CompareFunc) compare_dock_item_position);
+#endif
+			
+			// Calculate positions for given ordered list of items
+			position_manager.update_draw_values (current_items,
+				(PositionManager.DockItemDrawValueFunc) animate_draw_value_for_item,
+				(PositionManager.DrawValuesFunc) post_process_draw_values);
+			
+			background_rect = position_manager.get_background_region ();
 		}
 		
 		/**
@@ -262,7 +346,6 @@
 			unowned PositionManager position_manager = controller.position_manager;
 			unowned DockItem dragged_item = controller.drag_manager.DragItem;
 			var win_rect = position_manager.get_dock_window_region ();
-			var items = controller.VisibleItems;
 			
 			if (main_buffer == null) {
 				main_buffer = new DockSurface.with_surface (win_rect.width, win_rect.height, cr.get_target ());
@@ -295,7 +378,7 @@
 				cr.paint ();
 				cr.restore ();
 				
-				foreach (var item in items)
+				foreach (unowned DockItem item in current_items)
 					draw_urgent_glow (item, cr, frame_time);
 				
 				return;
@@ -320,118 +403,11 @@
 			unowned Cairo.Context item_cr = item_buffer.Context;
 			unowned Cairo.Context shadow_cr = shadow_buffer.Context;
 			
-			// draw transient items onto the dock buffer and calculate the resulting
-			// dynamic-animation-offset used to animate the background-resize
-			if (screen_is_composited) {
-				var add_time = 0LL;
-				var remove_time = 0LL;
-				var move_time = 0LL;
-				var move_duration = theme.ItemMoveTime * 1000;
-				
-				var transient_items_it = transient_items.iterator ();
-				while (transient_items_it.next ()) {
-					var item = transient_items_it.get ();
-					add_time = item.AddTime;
-					remove_time = item.RemoveTime;
-					
-					if (add_time > remove_time) {
-						move_time = int64.max (0LL, frame_time - add_time);
-						if (move_time < move_duration) {
-							var move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_OUT_QUINT, move_time, move_duration);
-							dynamic_animation_offset -= move_animation_progress * (position_manager.IconSize + position_manager.ItemPadding);
-						} else {
-							transient_items_it.remove ();
-						}
-					} else if (remove_time > 0) {
-						move_time = int64.max (0LL, frame_time - remove_time);
-						if (move_time < move_duration) {
-							var move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_IN_QUINT, move_time, move_duration);
-							dynamic_animation_offset += move_animation_progress * (position_manager.IconSize + position_manager.ItemPadding);
-						} else {
-							transient_items_it.remove ();
-						}
-					} else {
-						continue;
-					}
-#if BENCHMARK
-					start2 = new DateTime.now_local ();
-#endif
-					// Do not draw the currently dragged item or items which are suppose to be drawn later
-					if (move_time < move_duration && item.IsVisible && dragged_item != item && !items.contains (item)) {
-						var draw_value = get_animated_draw_value_for_item (item, frame_time);
-						draw_item (item_cr, item, ref draw_value, frame_time);
-						draw_item_shadow (shadow_cr, item, ref draw_value);
-					}
-#if BENCHMARK
-					end2 = new DateTime.now_local ();
-					benchmark.add ("item render time - %f ms".printf (end2.difference (start2) / 1000.0));
-#endif
-				}
-			} else {
-				transient_items.clear ();
-			}
-
-			background_rect = position_manager.get_background_region ();
-			
 			// calculate drawing offset
 			var x_offset = 0, y_offset = 0;
 			if (opacity == 1.0)
 				position_manager.get_dock_draw_position (out x_offset, out y_offset);
 			
-			// calculate drawing animation-offset
-			var x_animation_offset = 0, y_animation_offset = 0;
-			switch (controller.prefs.Alignment) {
-			default:
-			case Gtk.Align.CENTER:
-				if (position_manager.is_horizontal_dock ())
-					x_animation_offset -= (int) Math.round (dynamic_animation_offset / 2.0);
-				else
-					y_animation_offset -= (int) Math.round (dynamic_animation_offset / 2.0);
-				background_rect = { background_rect.x + x_offset + x_animation_offset, background_rect.y + y_offset + y_animation_offset,
-					background_rect.width -2 * x_animation_offset, background_rect.height -2 * y_animation_offset };
-				break;
-			case Gtk.Align.START:
-				if (position_manager.is_horizontal_dock ())
-					background_rect = { background_rect.x + x_offset, background_rect.y + y_offset,
-						background_rect.width + (int) Math.round (dynamic_animation_offset), background_rect.height };
-				else
-					background_rect = { background_rect.x + x_offset, background_rect.y + y_offset,
-						background_rect.width, background_rect.height + (int) Math.round (dynamic_animation_offset) };
-				break;
-			case Gtk.Align.END:
-				if (position_manager.is_horizontal_dock ())
-					x_animation_offset -= (int) Math.round (dynamic_animation_offset);
-				else
-					y_animation_offset -= (int) Math.round (dynamic_animation_offset);
-				background_rect = { background_rect.x + x_offset + x_animation_offset, background_rect.y + y_offset + y_animation_offset,
-					background_rect.width - x_animation_offset, background_rect.height - y_animation_offset };
-				break;
-			case Gtk.Align.FILL:
-				switch (controller.prefs.ItemsAlignment) {
-				default:
-				case Gtk.Align.FILL:
-				case Gtk.Align.CENTER:
-					if (position_manager.is_horizontal_dock ())
-						x_animation_offset -= (int) Math.round (dynamic_animation_offset / 2.0);
-					else
-						y_animation_offset -= (int) Math.round (dynamic_animation_offset / 2.0);
-					break;
-				case Gtk.Align.START:
-					break;
-				case Gtk.Align.END:
-					if (position_manager.is_horizontal_dock ())
-						x_animation_offset -= (int) Math.round (dynamic_animation_offset);
-					else
-						y_animation_offset -= (int) Math.round (dynamic_animation_offset);
-					break;
-				}
-				background_rect = { background_rect.x + x_offset, background_rect.y + y_offset,	background_rect.width, background_rect.height };
-				break;
-			}
-			
-			x_offset += x_animation_offset;
-			y_offset += y_animation_offset;
-
 			// composite dock layers and make sure to draw onto the window's context with one operation
 			main_buffer.clear ();
 			unowned Cairo.Context main_cr = main_buffer.Context;
@@ -441,22 +417,22 @@
 			start2 = new DateTime.now_local ();
 #endif
 			// draw background-layer
-			draw_dock_background (main_cr, background_rect);
+			draw_dock_background (main_cr, background_rect, x_offset, y_offset);
 #if BENCHMARK
 			end2 = new DateTime.now_local ();
 			benchmark.add ("background render time - %f ms".printf (end2.difference (start2) / 1000.0));
 #endif
 			
 			// draw each item onto the dock buffer
-			foreach (var item in items) {
+			foreach (unowned DockItem item in current_items) {
 #if BENCHMARK
 				start2 = new DateTime.now_local ();
 #endif
 				// Do not draw the currently dragged item
 				if (item.IsVisible && dragged_item != item) {
-					var draw_value = get_animated_draw_value_for_item (item, frame_time);
-					draw_item (item_cr, item, ref draw_value, frame_time);
-					draw_item_shadow (shadow_cr, item, ref draw_value);
+					var draw_value = position_manager.get_draw_value_for_item (item);
+					draw_item (item_cr, item, draw_value, frame_time);
+					draw_item_shadow (shadow_cr, item, draw_value);
 				}
 #if BENCHMARK
 				end2 = new DateTime.now_local ();
@@ -489,7 +465,7 @@
 			
 			// draw urgent-glow if dock is completely hidden
 			if (hide_progress == 1.0) {
-				foreach (var item in items)
+				foreach (unowned DockItem item in current_items)
 					draw_urgent_glow (item, cr, frame_time);
 			}
 			
@@ -520,7 +496,7 @@
 			}
 		}
 		
-		void draw_dock_background (Cairo.Context cr, Gdk.Rectangle background_rect)
+		void draw_dock_background (Cairo.Context cr, Gdk.Rectangle background_rect, int x_offset, int y_offset)
 		{
 			unowned PositionManager position_manager = controller.position_manager;
 			
@@ -534,21 +510,20 @@
 				background_buffer = theme.create_background (background_rect.width, background_rect.height,
 					position_manager.Position, main_buffer);
 			
-			cr.set_source_surface (background_buffer.Internal, background_rect.x, background_rect.y);
+			cr.set_source_surface (background_buffer.Internal, background_rect.x + x_offset, background_rect.y + y_offset);
 			cr.paint ();
 		}
 		
-		PositionManager.DockItemDrawValue get_animated_draw_value_for_item (DockItem item, int64 frame_time)
+		[CCode (instance_pos = -1)]
+		void animate_draw_value_for_item (DockItem item, PositionManager.DockItemDrawValue draw_value)
 		{
 			unowned PositionManager position_manager = controller.position_manager;
 			unowned DockItem hovered_item = controller.window.HoveredItem;
 			unowned DragManager drag_manager = controller.drag_manager;
 			
-			var icon_size = position_manager.IconSize;
+			var icon_size = (int) draw_value.icon_size;
 			var position = position_manager.Position;
-			
-			// get item's draw-value
-			var draw_value = position_manager.get_draw_value_for_item (item);
+			var x_offset = 0.0, y_offset = 0.0;
 			
 			// check for and calculate click-animation
 			var max_click_time = item.ClickedAnimation == Animation.BOUNCE ? theme.LaunchBounceTime : theme.ClickTime;
@@ -564,8 +539,7 @@
 				case Animation.BOUNCE:
 					if (!screen_is_composited)
 						break;
-					var change = Math.fabs (Math.sin (2 * Math.PI * click_animation_progress) * position_manager.LaunchBounceHeight * double.min (1.0, 1.3333 * (1.0 - click_animation_progress)));
-					draw_value.move_in (position, change);
+					y_offset += Math.fabs (Math.sin (2 * Math.PI * click_animation_progress) * position_manager.LaunchBounceHeight * double.min (1.0, 1.3333 * (1.0 - click_animation_progress)));
 					break;
 				case Animation.DARKEN:
 					draw_value.darken = double.max (0, Math.sin (Math.PI * click_animation_progress)) * 0.5;
@@ -630,8 +604,40 @@
 				var urgent_time = int64.max (0LL, frame_time - item.LastUrgent);
 				var bounce_animation_progress = urgent_time / (double) (theme.UrgentBounceTime * 1000);
 				if (bounce_animation_progress < 1.0) {
-					var change = Math.fabs (Math.sin (Math.PI * bounce_animation_progress) * position_manager.UrgentBounceHeight * double.min (1.0, 2.0 * (1.0 - bounce_animation_progress)));
-					draw_value.move_in (position, change);
+					y_offset += Math.fabs (Math.sin (Math.PI * bounce_animation_progress) * position_manager.UrgentBounceHeight * double.min (1.0, 2.0 * (1.0 - bounce_animation_progress)));
+				}
+			}
+			
+			// animate addition/removal
+			unowned DockContainer? container = item.Container;
+			var allow_animation = (screen_is_composited && (container == null || container.AddTime < item.AddTime));
+			if (allow_animation && item.AddTime > item.RemoveTime) {
+				var move_duration = theme.ItemMoveTime * 1000;
+				var move_time = int64.max (0LL, frame_time - item.AddTime);
+				if (move_time < move_duration) {
+					var move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.LINEAR, move_time, move_duration);
+					draw_value.opacity = Drawing.easing_for_mode (AnimationMode.EASE_IN_EXPO, move_time, move_duration);
+					y_offset -= move_animation_progress * (icon_size + position_manager.BottomPadding);
+					draw_value.show_indicator = false;
+					
+					// calculate the resulting incremental dynamic-animation-offset used to animate the background-resize and icon-offset
+					move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_OUT_QUINT, move_time, move_duration);
+					dynamic_animation_offset -= move_animation_progress * (icon_size + position_manager.ItemPadding);
+					x_offset += dynamic_animation_offset;
+				}
+			} else if (allow_animation && item.RemoveTime > 0) {
+				var move_duration = theme.ItemMoveTime * 1000;
+				var move_time = int64.max (0LL, frame_time - item.RemoveTime);
+				if (move_time < move_duration) {
+					var move_animation_progress = Drawing.easing_for_mode (AnimationMode.LINEAR, move_time, move_duration);
+					draw_value.opacity = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_OUT_EXPO, move_time, move_duration);
+					y_offset -= move_animation_progress * (icon_size + position_manager.BottomPadding);
+					draw_value.show_indicator = false;
+					
+					// calculate the resulting incremental dynamic-animation-offset used to animate the background-resize and icon-offset
+					move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_IN_QUINT, move_time, move_duration);
+					dynamic_animation_offset += move_animation_progress * (icon_size + position_manager.ItemPadding);
+					x_offset += dynamic_animation_offset - (icon_size + position_manager.ItemPadding);
 				}
 			}
 			
@@ -650,37 +656,12 @@
 						move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_OUT_CIRC, move_time, move_duration);
 					}
 					var change = move_animation_progress * (icon_size + position_manager.ItemPadding);
-					draw_value.move_right (position, (item.Position < item.LastPosition ? change : -change));
+					x_offset += (item.Position < item.LastPosition ? change : -change);
 				} else {
 					item.unset_move_state ();
 				}
 			}
 			
-			// animate addition/removal
-			unowned DockContainer? container = item.Container;
-			var allow_animation = (screen_is_composited && (container == null || container.AddTime < item.AddTime));
-			if (allow_animation && item.AddTime > item.RemoveTime) {
-				var move_duration = theme.ItemMoveTime * 1000;
-				var move_time = int64.max (0LL, frame_time - item.AddTime);
-				if (move_time < move_duration) {
-					var move_animation_progress = 1.0 - Drawing.easing_for_mode (AnimationMode.LINEAR, move_time, move_duration);
-					draw_value.opacity = Drawing.easing_for_mode (AnimationMode.EASE_IN_EXPO, move_time, move_duration);
-					var change = move_animation_progress * (icon_size + position_manager.BottomPadding);
-					draw_value.move_in (position, -change);
-					draw_value.show_indicator = false;
-				}
-			} else if (allow_animation && item.RemoveTime > 0) {
-				var move_duration = theme.ItemMoveTime * 1000;
-				var move_time = int64.max (0LL, frame_time - item.RemoveTime);
-				if (move_time < move_duration) {
-					var move_animation_progress = Drawing.easing_for_mode (AnimationMode.LINEAR, move_time, move_duration);
-					draw_value.opacity = 1.0 - Drawing.easing_for_mode (AnimationMode.EASE_OUT_EXPO, move_time, move_duration);
-					var change = move_animation_progress * (icon_size + position_manager.BottomPadding);
-					draw_value.move_in (position, -change);
-					draw_value.show_indicator = false;
-				}
-			}
-			
 			// animate icon on invalid state
 			if ((item.State & ItemState.INVALID) != 0) {
 				var invalid_duration = 3000 * 1000;
@@ -692,13 +673,71 @@
 				}
 			}
 			
-			return draw_value;
-		}
-			
-		void draw_item (Cairo.Context cr, DockItem item, ref PositionManager.DockItemDrawValue draw_value, int64 frame_time)
-		{
-			unowned PositionManager position_manager = controller.position_manager;
-			var icon_size = position_manager.IconSize;
+			if (x_offset != 0.0)
+				draw_value.move_right (position, x_offset);
+			
+			if (y_offset != 0.0)
+				draw_value.move_in (position, y_offset);
+		}
+		
+		[CCode (instance_pos = -1)]
+		void post_process_draw_values (Gee.HashMap<DockElement, PositionManager.DockItemDrawValue?> draw_values)
+		{
+			if (dynamic_animation_offset == 0.0)
+				return;
+			
+			unowned PositionManager position_manager = controller.position_manager;
+			var position = position_manager.Position;
+			
+			var x_offset = 0.0;
+			
+			switch (position_manager.Alignment) {
+			default:
+			case Gtk.Align.CENTER:
+				x_offset -= Math.round (dynamic_animation_offset / 2.0);
+				break;
+			case Gtk.Align.START:
+				break;
+			case Gtk.Align.END:
+				x_offset -= Math.round (dynamic_animation_offset);
+				break;
+			case Gtk.Align.FILL:
+				switch (position_manager.ItemsAlignment) {
+				default:
+				case Gtk.Align.FILL:
+				case Gtk.Align.CENTER:
+					x_offset -= Math.round (dynamic_animation_offset / 2.0);
+					break;
+				case Gtk.Align.START:
+					break;
+				case Gtk.Align.END:
+					x_offset -= Math.round (dynamic_animation_offset);
+					break;
+				}
+				break;
+			}
+			
+			if (x_offset == 0.0)
+				return;
+			
+#if HAVE_GEE_0_8
+			draw_values.map_iterator ().foreach ((i, val) => {
+				val.move_right (position, x_offset);
+				return true;
+			});
+#else
+			var draw_values_it = draw_values.map_iterator ();
+			while (draw_values_it.next ()) {
+				var val = draw_values_it.get_value ();
+				val.move_right (position, x_offset);
+			}
+#endif
+		}
+		
+		void draw_item (Cairo.Context cr, DockItem item, PositionManager.DockItemDrawValue draw_value, int64 frame_time)
+		{
+			unowned PositionManager position_manager = controller.position_manager;
+			var icon_size = (int) draw_value.icon_size;
 			var position = position_manager.Position;
 			
 			// load the icon
@@ -710,7 +749,7 @@
 			
 			DockSurface? icon_overlay_surface = null;
 			if (item.CountVisible || item.ProgressVisible)
-				icon_overlay_surface = item.get_foreground_surface (draw_item_foreground);
+				icon_overlay_surface = item.get_foreground_surface (icon_size * window_scale_factor, icon_size * window_scale_factor, item_buffer, (DrawDataFunc<DockItem>) draw_item_foreground);
 			
 			if (icon_overlay_surface != null) {
 				icon_cr.set_source_surface (icon_overlay_surface.Internal, 0, 0);
@@ -766,15 +805,17 @@
 				draw_indicator_state (cr, draw_value.hover_region, item.Indicator, item.State);
 		}
 		
-		void draw_item_shadow (Cairo.Context cr, DockItem item, ref PositionManager.DockItemDrawValue draw_value)
+		void draw_item_shadow (Cairo.Context cr, DockItem item, PositionManager.DockItemDrawValue draw_value)
 		{
 			unowned PositionManager position_manager = controller.position_manager;
 			var shadow_size = position_manager.IconShadowSize;
-
+			// Inflate size to fit shadow
+			var icon_size = (int) draw_value.icon_size + 2 * shadow_size;
+			
 			// load and draw the icon shadow
 			DockSurface? icon_shadow_surface = null;
 			if (shadow_size > 0)
-				icon_shadow_surface = item.get_background_surface (draw_item_background);
+				icon_shadow_surface = item.get_background_surface (icon_size * window_scale_factor, icon_size * window_scale_factor, item_buffer, (DrawDataFunc<DockItem>) draw_item_background);
 			
 			if (icon_shadow_surface != null) {
 				if (window_scale_factor > 1) {
@@ -793,20 +834,13 @@
 			}
 		}
 		
-		DockSurface draw_item_foreground (DockItem item, DockSurface icon_surface, DockSurface? current_surface)
+		[CCode (instance_pos = -1)]
+		DockSurface draw_item_foreground (int width, int height, DockSurface model, DockItem item)
 		{
-			unowned PositionManager position_manager = controller.position_manager;
-			var width = icon_surface.Width;
-			var height = icon_surface.Height;
-			
-			if (current_surface != null
-				&& width == current_surface.Width && height == current_surface.Height)
-				return current_surface;
-			
 			Logger.verbose ("DockItem.draw_item_overlay (width = %i, height = %i)", width, height);
-			var surface = new DockSurface.with_dock_surface (width, height, icon_surface);
+			var surface = new DockSurface.with_dock_surface (width, height, model);
 			
-			var icon_size = position_manager.IconSize * window_scale_factor;
+			var icon_size = int.min (width, height) * window_scale_factor;
 			var urgent_color = get_styled_color ();
 			urgent_color.add_hue (theme.UrgentHueShift);
 			
@@ -821,21 +855,18 @@
 			return surface;
 		}
 		
-		DockSurface draw_item_background (DockItem item, DockSurface icon_surface, DockSurface? current_surface)
+		[CCode (instance_pos = -1)]
+		DockSurface draw_item_background (int width, int height, DockSurface model, DockItem item)
 		{
 			unowned PositionManager position_manager = controller.position_manager;
 			var shadow_size = position_manager.IconShadowSize * window_scale_factor;
 			
-			// Inflate size to fit shadow
-			var width = icon_surface.Width + 2 * shadow_size;
-			var height = icon_surface.Height + 2 * shadow_size;
-			
-			if (current_surface != null
-				&& width == current_surface.Width && height == current_surface.Height)
-				return current_surface;
+			var draw_value = position_manager.get_draw_value_for_item (item);
+			var icon_size = (int) draw_value.icon_size;
+			var icon_surface = item.get_surface (icon_size, icon_size, model);
 			
 			Logger.verbose ("DockItem.draw_icon_with_shadow (width = %i, height = %i, shadow_size = %i)", width, height, shadow_size);
-			var surface = new DockSurface.with_dock_surface (width, height, icon_surface);
+			var surface = new DockSurface.with_dock_surface (width, height, model);
 			unowned Cairo.Context cr = surface.Context;
 			var shadow_surface = icon_surface.create_mask (0.4, null);
 			
@@ -976,6 +1007,35 @@
 			animated_draw ();
 		}
 		
+		void hovered_changed ()
+		{
+			force_frame_time_update ();
+			var now = frame_time;
+			var diff = now - last_hovered_changed;
+			var time = 150 * 1000;
+			
+			if (diff < time)
+				last_hovered_changed = now + (diff - time);
+			else
+				last_hovered_changed = now;
+			
+			animated_draw ();
+		}
+		
+		public void update_local_cursor (int x, int y)
+		{
+			Gdk.Point new_cursor = { x, y };
+			if (local_cursor == new_cursor)
+				return;
+			
+			local_cursor = new_cursor;
+			
+			if (controller.prefs.ZoomEnabled) {
+				zoom_changed = true;
+				animated_draw ();
+			}
+		}
+		
 		public void animate_items (Gee.List<DockElement> elements)
 		{
 			if (!screen_is_composited)
@@ -995,6 +1055,15 @@
 		 */
 		protected override bool animation_needed (int64 frame_time)
 		{
+			if (zoom_changed) {
+				//FIXME reset at a better place
+				zoom_changed = false;
+				return true;
+			}
+			
+			if (frame_time - last_hovered_changed <= 150 * 1000)
+				return true;
+			
 			if (theme.FadeOpacity == 1.0) {
 				if (frame_time - last_hide <= theme.HideTime * 1000)
 					return true;
@@ -1006,7 +1075,7 @@
 			if (transient_items.size > 0)
 				return true;
 			
-			foreach (var item in controller.VisibleItems)
+			foreach (var item in current_items)
 				if (item_animation_needed (item, frame_time))
 					return true;
 			
@@ -1039,5 +1108,22 @@
 			
 			return false;
 		}
+		
+		static int compare_dock_item_position (DockItem i1, DockItem i2)
+		{
+			var p_i1 = i1.Position;
+			var p_i2 = i2.Position;
+			
+			if (p_i1 > p_i2)
+				return 1;
+			
+			if (p_i1 < p_i2)
+				return -1;
+			
+			if (i1.RemoveTime > i2.RemoveTime)
+				return -1;
+			
+			return 1;
+		}
 	}
 }

=== modified file 'lib/DragManager.vala'
--- lib/DragManager.vala	2015-06-08 09:36:14 +0000
+++ lib/DragManager.vala	2015-07-16 08:37:41 +0000
@@ -178,7 +178,7 @@
 #if HAVE_HIDPI
 			window_scale_factor = controller.window.get_window ().get_scale_factor ();
 #endif
-			var drag_icon_size = (int) (1.2 * controller.position_manager.IconSize);
+			var drag_icon_size = (int) (1.2 * controller.position_manager.ZoomIconSize);
 			if (drag_icon_size % 2 == 1)
 				drag_icon_size++;
 #if HAVE_HIDPI
@@ -441,6 +441,7 @@
 				Gdk.drag_status (context, Gdk.DragAction.COPY, time_);
 			}
 			
+			controller.renderer.update_local_cursor (x, y);
 			hide_manager.update_hovered ();
 			window.update_hovered (x, y);
 			

=== modified file 'lib/Drawing/DockSurface.vala'
--- lib/Drawing/DockSurface.vala	2015-05-16 18:48:15 +0000
+++ lib/Drawing/DockSurface.vala	2015-07-16 08:37:41 +0000
@@ -125,6 +125,27 @@
 		}
 		
 		/**
+		 * Create a scaled copy of the surface
+		 *
+		 * @param width the resulting width
+		 * @param height the resulting height
+		 * @return scaled copy of this surface
+		 */
+		public DockSurface scaled_copy (int width, int height)
+		{
+			var result = new DockSurface.with_dock_surface (width, height, this);
+			unowned Cairo.Context cr = result.Context;
+			
+			cr.save ();
+			cr.scale ((double) width / Width, (double) height / Height);
+			cr.set_source_surface (Internal, 0, 0);
+			cr.paint ();
+			cr.restore ();
+			
+			return result;
+		}
+		
+		/**
 		 * Saves the current dock surface to a {@link Gdk.Pixbuf}.
 		 *
 		 * @return the {@link Gdk.Pixbuf}

=== added file 'lib/Drawing/SurfaceCache.vala'
--- lib/Drawing/SurfaceCache.vala	1970-01-01 00:00:00 +0000
+++ lib/Drawing/SurfaceCache.vala	2015-07-16 08:37:41 +0000
@@ -0,0 +1,322 @@
+//
+//  Copyright (C) 2015 Rico Tzschichholz
+//
+//  This file is part of Plank.
+//
+//  Plank is free software: you can redistribute it and/or modify
+//  it under the terms of the GNU General Public License as published by
+//  the Free Software Foundation, either version 3 of the License, or
+//  (at your option) any later version.
+//
+//  Plank is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+//
+
+using Plank.Services;
+
+namespace Plank.Drawing
+{
+	/**
+	 * Creates a new surface based on the given information
+	 *
+	 * @param width the width
+	 * @param height the height
+	 * @param model existing surface to use as basis of new surface
+	 * @param draw_data_func function which changes the surface
+	 * @return the newly created surface or NULL
+	 */
+	public delegate DockSurface? DrawFunc<G> (int width, int height, DockSurface model, DrawDataFunc<G>? draw_data_func);
+	
+	/**
+	 * Creates a new surface using the given element and information
+	 *
+	 * @param width the width
+	 * @param height the height
+	 * @param model existing surface to use as basis of new surface
+	 * @param data the data object used for drawing
+	 * @return the newly created surface or NULL
+	 */
+	public delegate DockSurface? DrawDataFunc<G> (int width, int height, DockSurface model, G? data);
+	
+	/**
+	 * Controls some internal behaviors of a {@link Plank.Drawing.SurfaceCache}
+	 */
+	[Flags]
+	public enum SurfaceCacheFlags
+	{
+		NONE = 0,
+		/**
+		 * Allow down-scaling of an existing cached surface for better performance
+		 */
+		ALLOW_DOWNSCALE = 1 << 0,
+		/**
+		 * Allow up-scaling of an existing cached surface for better performance
+		 */
+		ALLOW_UPSCALE = 1 << 1,
+		/**
+		 * Allow scaling of an existing cached surface for better performance
+		 * (This basically means the cache will only contain one entry which will be scaled accordingly on request)
+		 */
+		ALLOW_SCALE = ALLOW_UPSCALE | ALLOW_DOWNSCALE,
+		/**
+		 * Allow scaling if the drawing-time is significatly high
+		 */
+		ADAPTIVE_SCALE = 1 << 2,
+	}
+	
+	/**
+	 * Cache multiple sizes of the assumed same image
+	 */
+	public class SurfaceCache<G> : GLib.Object
+	{
+		const int64 MAX_CACHE_AGE = 5 * 60 * 1000 * 1000;
+		const int64 MIN_DRAWING_TIME = 10 * 1000;
+		const int64 ACCESS_REWARD = 500 * 1000;
+		
+		class SurfaceInfo
+		{
+			public uint16 width;
+			public uint16 height;
+			public uint access_count;
+			public int64 last_access_time;
+			public int64 drawing_time;
+			public double scale;
+			
+			public SurfaceInfo (uint16 width, uint16 height, int64 last_access_time, int64 drawing_time)
+			{
+				this.width = width;
+				this.height = height;
+				this.last_access_time = last_access_time;
+				this.drawing_time = drawing_time;
+				this.access_count = 0;
+				this.scale = 1.0;
+			}
+			
+			public static uint hash (SurfaceInfo s)
+			{
+				uint n1 = s.width, n2 = s.height;
+				return (n1 >= n2 ? n1 * n1 + n1 + n2 : n1 + n2 * n2);
+			}
+			
+			public static int compare (SurfaceInfo s1, SurfaceInfo s2)
+			{
+				if (s1 == s2)
+					return 0;
+				
+				return (2 * (s1.width - s2.width) + s2.height - s2.height);
+			}
+			
+			public int compare_with (uint16 width, uint16 height)
+			{
+				return (2 * (this.width - width) + this.height - height);
+			}
+		}
+		
+		public SurfaceCacheFlags flags { get; construct; }
+		
+		Gee.TreeSet<unowned SurfaceInfo> infos;
+		Gee.HashMap<SurfaceInfo, DockSurface> cache_map;
+		unowned SurfaceInfo? last_info;
+		Mutex cache_mutex;
+		
+		uint clean_up_timer = 0U;
+		
+		public SurfaceCache (SurfaceCacheFlags flags = SurfaceCacheFlags.NONE)
+		{
+			Object (flags: flags);
+		}
+		
+		construct
+		{
+			infos = new Gee.TreeSet<unowned SurfaceInfo> ((CompareDataFunc) SurfaceInfo.compare);
+			cache_map = new Gee.HashMap<SurfaceInfo, DockSurface> ((Gee.HashDataFunc<SurfaceInfo>) SurfaceInfo.hash);
+			last_info = null;
+			
+			//TODO Adaptive delay depending on the access rate
+			clean_up_timer = Gdk.threads_add_timeout (5 * 60 * 1000, () => {
+				clean_up ();
+				return true;
+			});
+		}
+		
+		~SurfaceCache ()
+		{
+			if (clean_up_timer > 0U) {
+				GLib.Source.remove (clean_up_timer);
+				clean_up_timer = 0U;
+			}
+			
+			cache_map.clear ();
+			infos.clear ();
+			last_info = null;
+		}
+		
+		public DockSurface? get_surface<G> (int width, int height, DockSurface model, DrawFunc<G> draw_func, DrawDataFunc<G>? draw_data_func)
+			requires (width >= 0 && height >= 0)
+		{
+			cache_mutex.lock ();
+			
+			unowned SurfaceInfo? info;
+			SurfaceInfo? current_info = null;
+			DockSurface? surface = null;
+			bool needs_scaling = false;
+			
+			info = find_match ((uint16) width, (uint16) height, out needs_scaling);
+			last_info = info;
+			current_info = info;
+			
+			var access_time = GLib.get_monotonic_time ();
+			
+			if (current_info != null) {
+				current_info.last_access_time = access_time;
+				current_info.access_count++;
+				surface = cache_map.get (current_info);
+				
+				cache_mutex.unlock ();
+				
+				if (needs_scaling)
+					return surface.scaled_copy (width, height);
+				else
+					return surface;
+			}
+			
+			surface = draw_func (width, height, model, draw_data_func);
+			
+			var finish_time = GLib.get_monotonic_time ();
+			var time_elapsed = finish_time - access_time;
+			
+			current_info = new SurfaceInfo ((uint16) width, (uint16) height, finish_time, time_elapsed);
+			current_info.access_count++;
+			
+			cache_map.set (current_info, surface);
+			infos.add (current_info);
+			
+			cache_mutex.unlock ();
+			
+			return surface;
+		}
+		
+		unowned SurfaceInfo? find_match (uint16 width, uint16 height, out bool needs_scaling)
+		{
+			needs_scaling = false;
+			
+			if (infos.is_empty)
+				return null;
+			
+			unowned SurfaceInfo? info;
+			// Check if the last requested entry matches already
+			if (last_info != null) {
+				info = last_info;
+				if (info.width == width && info.height == height)
+					return info;
+				
+				if ((flags & SurfaceCacheFlags.ALLOW_DOWNSCALE) != 0
+					&& info.width > width && info.height > height) {
+					needs_scaling = true;
+					return info;
+				}
+				
+				if ((flags & SurfaceCacheFlags.ALLOW_UPSCALE) != 0
+					&& info.width < width && info.height < height) {
+					needs_scaling = true;
+					return info;
+				}
+			}
+			
+			Gee.BidirIterator<unowned SurfaceInfo> infos_it;
+			if (last_info != null)
+				infos_it = (Gee.BidirIterator<unowned SurfaceInfo>) infos.iterator_at (last_info);
+			else
+				infos_it = infos.bidir_iterator ();
+			
+			if (last_info != null && last_info.compare_with (width, height) > 0) {
+				while (infos_it.previous ()) {
+					info = infos_it.get ();
+					
+					if (info.width == width && info.height == height)
+						return info;
+					
+					if ((flags & SurfaceCacheFlags.ALLOW_DOWNSCALE) != 0
+						&& info.width > width && info.height > height) {
+						needs_scaling = true;
+						return info;
+					}
+					
+					if ((flags & SurfaceCacheFlags.ALLOW_UPSCALE) != 0
+						&& info.width < width && info.height < height) {
+						needs_scaling = true;
+						return info;
+					}
+				}
+			} else {
+				while (infos_it.next ()) {
+					info = infos_it.get ();
+					
+					if (info.width == width && info.height == height)
+						return info;
+					
+					if ((flags & SurfaceCacheFlags.ALLOW_DOWNSCALE) != 0
+						&& info.width > width && info.height > height) {
+						needs_scaling = true;
+						return info;
+					}
+					
+					if ((flags & SurfaceCacheFlags.ALLOW_UPSCALE) != 0
+						&& info.width < width && info.height < height) {
+						needs_scaling = true;
+						return info;
+					}
+				}
+			}
+			
+			return null;
+		}
+		
+		public void clear ()
+		{
+			cache_mutex.lock ();
+			
+			infos.clear ();
+			cache_map.clear ();
+			last_info = null;
+			
+			cache_mutex.unlock ();
+		}
+		
+		void clean_up ()
+		{
+			cache_mutex.lock ();
+			
+			if (cache_map.size <= 1)
+				return;
+			
+			var now = GLib.get_monotonic_time ();
+			var size_before = cache_map.size;
+			
+			var cache_it = cache_map.map_iterator ();
+			while (cache_it.next ()) {
+				var info = cache_it.get_key ();
+				
+				if (now - info.last_access_time < ACCESS_REWARD * info.access_count)
+					continue;
+				
+				if (info.drawing_time > MIN_DRAWING_TIME)
+					continue;
+				
+				infos.remove (info);
+				cache_it.unset ();
+			}
+			
+			last_info = null;
+			
+			Logger.verbose ("SurfaceCache.clean_up (%i -> %i) ", size_before, cache_map.size);
+			
+			cache_mutex.unlock ();
+		}
+	}
+}

=== modified file 'lib/Items/DockItem.vala'
--- lib/Items/DockItem.vala	2015-06-10 20:26:28 +0000
+++ lib/Items/DockItem.vala	2015-07-16 08:37:41 +0000
@@ -24,16 +24,6 @@
 namespace Plank.Items
 {
 	/**
-	 * Draws a modified surface onto another newly created or given surface
-	 *
-	 * @param item the dock-item
-	 * @param source original surface which may not be changed
-	 * @param target the previously modified surface
-	 * @return the modified surface or passed through target
-	 */
-	public delegate DockSurface DrawItemFunc (DockItem item, DockSurface source, DockSurface? target);
-	
-	/**
 	 * The base class for all dock items.
 	 */
 	public abstract class DockItem : DockElement
@@ -134,8 +124,8 @@
 		 */
 		public DockItemPreferences Prefs { get; construct; }
 		
-		DockSurface? surface = null;
-		DockSurface? background_surface = null;
+		SurfaceCache<DockItem> buffer;
+		SurfaceCache<DockItem> background_buffer;
 		DockSurface? foreground_surface = null;
 		
 		FileMonitor? launcher_file_monitor = null;
@@ -154,6 +144,9 @@
 		
 		construct
 		{
+			buffer = new SurfaceCache<DockItem> (SurfaceCacheFlags.NONE);
+			background_buffer = new SurfaceCache<DockItem> (SurfaceCacheFlags.ALLOW_SCALE);
+			
 			Prefs.deleted.connect (handle_deleted);
 			Prefs.notify["Launcher"].connect (handle_launcher_changed);
 			
@@ -173,6 +166,9 @@
 		
 		~DockItem ()
 		{
+			buffer.clear ();
+			background_buffer.clear ();
+			
 			Prefs.deleted.disconnect (handle_deleted);
 			Prefs.notify["Launcher"].disconnect (handle_launcher_changed);
 			
@@ -232,8 +228,8 @@
 		 */
 		protected void reset_icon_buffer ()
 		{
-			surface = null;
-			background_surface = null;
+			buffer.clear ();
+			background_buffer.clear ();
 			foreground_surface = null;
 			
 			needs_redraw ();
@@ -244,7 +240,7 @@
 		 */
 		public override void reset_buffers ()
 		{
-			background_surface = null;
+			background_buffer.clear ();
 			foreground_surface = null;
 		}
 		
@@ -420,16 +416,31 @@
 			return true;
 		}
 		
-		unowned DockSurface get_surface (int width, int height, DockSurface model)
-		{
-			if (surface == null || width != surface.Width || height != surface.Height) {
-				surface = new DockSurface.with_dock_surface (width, height, model);
-				
-				Logger.verbose ("DockItem.draw_icon (width = %i, height = %i)", width, height);
-				draw_icon (surface);
-				
-				AverageIconColor = surface.average_color ();
-			}
+		/**
+		 * Returns the dock surface for this item.
+		 *
+		 * It might trigger an internal redraw if the requested size
+		 * isn't cached yet.
+		 *
+		 * @param width width of the icon surface
+		 * @param height height of the icon surface
+		 * @param model existing surface to use as basis of new surface
+		 * @return the dock surface for this item which may not be changed
+		 */
+		public DockSurface get_surface (int width, int height, DockSurface model)
+		{
+			return buffer.get_surface<DockItem> (width, height, model, (DrawFunc<DockItem>) internal_get_surface, null);
+		}
+		
+		[CCode (instance_pos = -1)]
+		DockSurface internal_get_surface (int width, int height, DockSurface model, DrawDataFunc<DockItem>? draw_data_func)
+		{
+			var surface = new DockSurface.with_dock_surface (width, height, model);
+			
+			Logger.verbose ("DockItem.draw_icon (width = %i, height = %i)", width, height);
+			draw_icon (surface);
+			
+			AverageIconColor = surface.average_color ();
 			
 			return surface;
 		}
@@ -442,18 +453,21 @@
 		 *
 		 * Passing null as draw_func will destroy the internal background buffer.
 		 *
-		 * @param draw_func function which creates/changes the background surface
+		 * @param draw_data_func function which creates/changes the background surface
 		 * @return the background surface of this item which may not be changed
 		 */
-		public unowned DockSurface? get_background_surface (DrawItemFunc? draw_func = null)
-			requires (surface != null)
-		{
-			if (draw_func != null)
-				background_surface = draw_func (this, surface, background_surface);
-			else
-				background_surface = null;
+		public DockSurface? get_background_surface (int width, int height, DockSurface model, DrawDataFunc<DockItem>? draw_data_func)
+		{
+			return background_buffer.get_surface<DockItem> (width, height, model, (DrawFunc<DockItem>) internal_get_background_surface, (DrawDataFunc<DockItem>) draw_data_func);
+		}
+		
+		[CCode (instance_pos = -1)]
+		DockSurface? internal_get_background_surface (int width, int height, DockSurface model, DrawDataFunc<DockItem>? draw_data_func)
+		{
+			if (draw_data_func == null)
+				return null;
 			
-			return background_surface;
+			return draw_data_func (width, height, model, this);
 		}
 		
 		/**
@@ -464,16 +478,21 @@
 		 *
 		 * Passing null as draw_func will destroy the internal foreground buffer.
 		 *
-		 * @param draw_func function which creates/changes the foreground surface
+		 * @param draw_data_func function which creates/changes the foreground surface
 		 * @return the background surface of this item which may not be changed
 		 */
-		public unowned DockSurface? get_foreground_surface (DrawItemFunc? draw_func = null)
-			requires (surface != null)
+		public DockSurface? get_foreground_surface (int width, int height, DockSurface model, DrawDataFunc<DockItem>? draw_data_func)
 		{
-			if (draw_func != null)
-				foreground_surface = draw_func (this, surface, foreground_surface);
-			else
+			if (draw_data_func == null) {
 				foreground_surface = null;
+				return null;
+			}
+			
+			if (foreground_surface != null
+				&& foreground_surface.Width == width && foreground_surface.Height == height)
+				return foreground_surface;
+			
+			foreground_surface = draw_data_func (width, height, model, this);
 			
 			return foreground_surface;
 		}

=== modified file 'lib/Makefile.am'
--- lib/Makefile.am	2015-06-08 21:48:12 +0000
+++ lib/Makefile.am	2015-07-16 08:37:41 +0000
@@ -79,6 +79,7 @@
 	Drawing/DockSurface.vala \
 	Drawing/DockTheme.vala \
 	Drawing/Easing.vala \
+	Drawing/SurfaceCache.vala \
 	Drawing/Theme.vala \
 	Factories/AbstractMain.vala \
 	Factories/Factory.vala \

=== modified file 'lib/PositionManager.vala'
--- lib/PositionManager.vala	2015-06-11 05:36:16 +0000
+++ lib/PositionManager.vala	2015-07-16 08:37:41 +0000
@@ -29,12 +29,41 @@
 	 */
 	public class PositionManager : GLib.Object
 	{
-		public struct DockItemDrawValue
-		{
+		public struct PointD
+		{
+			public double x;
+			public double y;
+		}
+		
+		/**
+		 * Modify the given DrawItemValue
+		 *
+		 * @param item the dock-item
+		 * @param draw_value the dock-item-drawvalue
+		 */
+		public delegate void DockItemDrawValueFunc (DockItem item, DockItemDrawValue draw_value);
+		
+		/**
+		 * Modify the all DrawItemValue of all dock-items
+		 *
+		 * @param draw_values the map of all current dock-items and their draw-values
+		 */
+		public delegate void DrawValuesFunc (Gee.HashMap<DockElement, DockItemDrawValue> draw_values);
+		
+		/**
+		 * Contains all positions and modifications to draw a dock-item on the dock
+		 */
+		public class DockItemDrawValue
+		{
+			public PointD center;
+			public PointD static_center;
+			public double icon_size;
+			
 			public Gdk.Rectangle hover_region;
 			public Gdk.Rectangle draw_region;
 			public Gdk.Rectangle background_region;
 			
+			public double zoom;
 			public double opacity;
 			
 			public double darken;
@@ -49,18 +78,26 @@
 				switch (position) {
 				default:
 				case Gtk.PositionType.BOTTOM:
+					center.y -= damount;
+					static_center.y -= damount;
 					hover_region.y -= amount;
 					draw_region.y -= amount;
 					break;
 				case Gtk.PositionType.TOP:
+					center.y += damount;
+					static_center.y += damount;
 					hover_region.y += amount;
 					draw_region.y += amount;
 					break;
 				case Gtk.PositionType.LEFT:
+					center.x += damount;
+					static_center.x += damount;
 					hover_region.x += amount;
 					draw_region.x += amount;
 					break;
 				case Gtk.PositionType.RIGHT:
+					center.x -= damount;
+					static_center.x -= damount;
 					hover_region.x -= amount;
 					draw_region.x -= amount;
 					break;
@@ -74,21 +111,29 @@
 				switch (position) {
 				default:
 				case Gtk.PositionType.BOTTOM:
+					center.x += damount;
+					static_center.x += damount;
 					hover_region.x += amount;
 					draw_region.x += amount;
 					background_region.x += amount;
 					break;
 				case Gtk.PositionType.TOP:
+					center.x += damount;
+					static_center.x += damount;
 					hover_region.x += amount;
 					draw_region.x += amount;
 					background_region.x += amount;
 					break;
 				case Gtk.PositionType.LEFT:
+					center.y += damount;
+					static_center.y += damount;
 					hover_region.y += amount;
 					draw_region.y += amount;
 					background_region.y += amount;
 					break;
 				case Gtk.PositionType.RIGHT:
+					center.y += damount;
+					static_center.y += damount;
 					hover_region.y += amount;
 					draw_region.y += amount;
 					background_region.y += amount;
@@ -102,7 +147,7 @@
 		public bool screen_is_composited { get; private set; }
 		
 		Gdk.Rectangle static_dock_region;
-		Gee.HashMap<DockElement, DockItemDrawValue?> draw_values;
+		Gee.HashMap<DockElement, DockItemDrawValue> draw_values;
 		
 		Gdk.Rectangle monitor_geo;
 		
@@ -121,9 +166,9 @@
 		construct
 		{
 			static_dock_region = {};
-			draw_values = new Gee.HashMap<DockElement, DockItemDrawValue?> ();			
+			draw_values = new Gee.HashMap<DockElement, DockItemDrawValue> ();
 			
-			controller.prefs.notify["Monitor"].connect (prefs_monitor_changed);
+			controller.prefs.notify.connect (prefs_changed);
 		}
 		
 		/**
@@ -151,11 +196,27 @@
 			screen.monitors_changed.disconnect (screen_changed);
 			screen.size_changed.disconnect (screen_changed);
 			screen.composited_changed.disconnect (screen_composited_changed);
-			controller.prefs.notify["Monitor"].disconnect (prefs_monitor_changed);
+			controller.prefs.notify.disconnect (prefs_changed);
 			
 			draw_values.clear ();
 		}
 		
+		void prefs_changed (Object prefs, ParamSpec prop)
+		{
+			switch (prop.name) {
+			case "Monitor":
+				prefs_monitor_changed ();
+				break;
+			case "ZoomPercent":
+			case "ZoomEnabled":
+				prefs_zoom_changed ();
+				break;
+			default:
+				// Nothing important for us changed
+				break;
+			}
+		}
+		
 		public static string[] get_monitor_plug_names (Gdk.Screen screen)
 		{
 			int n_monitors = screen.get_n_monitors ();
@@ -231,7 +292,12 @@
 		 * Cached current icon size for the dock.
 		 */
 		public int IconSize { get; private set; }
-			
+		
+		/**
+		 * Cached current icon size for the dock.
+		 */
+		public int ZoomIconSize { get; private set; }
+		
 		/**
 		 * Cached position of the dock.
 		 */
@@ -330,6 +396,10 @@
 		 */
 		int DockBackgroundWidth;
 		
+		double ZoomPercent;
+		
+		Gdk.Rectangle background_rect;
+		
 		/**
 		 * The maximum item count which fit the dock in its maximum
 		 * size with the current theme and icon-size.
@@ -363,24 +433,6 @@
 			thaw_notify ();
 		}
 		
-		/**
-		 * Resets all internal caches for the given item.
-		 *
-		 * @param item the dock item
-		 */
-		public void reset_item_cache (DockElement item)
-		{
-			draw_values.unset (item);
-		}
-		
-		/**
-		 * Resets all internal item caches.
-		 */
-		public void reset_item_caches ()
-		{
-			draw_values.clear ();
-		}
-		
 		void update_caches (DockTheme theme)
 		{
 			unowned DockPreferences prefs = controller.prefs;
@@ -413,6 +465,8 @@
 			}
 			
 			IconSize = int.min (MaxIconSize, prefs.IconSize);
+			ZoomPercent = (screen_is_composited ? prefs.ZoomPercent / 100.0 : 1.0);
+			ZoomIconSize = (screen_is_composited && prefs.ZoomEnabled ? (int) Math.round (IconSize * ZoomPercent) : IconSize);
 			
 			var scaled_icon_size = IconSize / 10.0;
 			
@@ -445,8 +499,14 @@
 				extra_hide_offset = (IconShadowSize - top_offset);
 			else
 				extra_hide_offset = 0;
+		}
+		
+		void prefs_zoom_changed ()
+		{
+			unowned DockPreferences prefs = controller.prefs;
 			
-			draw_values.clear ();
+			ZoomPercent = (screen_is_composited ? prefs.ZoomPercent / 100.0 : 1.0);
+			ZoomIconSize = (screen_is_composited && prefs.ZoomEnabled ? (int) Math.round (IconSize * ZoomPercent) : IconSize);
 		}
 		
 		/**
@@ -563,6 +623,15 @@
 			window_scale_factor = controller.window.get_window ().get_scale_factor ();
 #endif
 			
+			// If zoom is enabled extend cursor-region based on current hovered-item
+			if (controller.prefs.ZoomEnabled) {
+				unowned DockItem? hovered_item = controller.window.HoveredItem;
+				if (hovered_item != null) {
+					var hover_region = get_hover_region_for_element (hovered_item);
+					cursor_region.union (hover_region, out cursor_region);
+				}
+			}
+			
 			switch (Position) {
 			default:
 			case Gtk.PositionType.BOTTOM:
@@ -691,9 +760,6 @@
 			
 			update_dock_position ();
 			
-			// FIXME Maybe no need to purge all cached values?
-			draw_values.clear ();
-			
 			if (!screen_is_composited
 				|| old_region.x != static_dock_region.x
 				|| old_region.y != static_dock_region.y
@@ -721,91 +787,337 @@
 		 */
 		public DockItemDrawValue get_draw_value_for_item (DockItem item)
 		{
-			DockItemDrawValue? draw_value;
-			
-			if ((draw_value = draw_values.get (item)) == null) {
-				var hover_rect = internal_get_item_hover_region (item);
-				var draw_rect = get_item_draw_region (hover_rect);
-				var background_rect = get_item_background_region (hover_rect);
-			
-				draw_value = { hover_rect, draw_rect, background_rect, 1.0, 0.0, 0.0, true };
-				draw_values.set (item, draw_value);
+			if (draw_values.size == 0) {
+				critical ("Without draw_values there is trouble ahead");
+				update_draw_values (controller.VisibleItems);
+			}
+			
+			var draw_value = draw_values[item];
+			if (draw_value == null) {
+				critical ("Without a draw_value there is trouble ahead for '%s'", item.Text);
+				draw_value = new DockItemDrawValue ();
 			}
 			
 			return draw_value;
 		}
 		
 		/**
+		 * Update and recalculated all internal draw-values using the given methodes for custom manipulations.
+		 *
+		 * @param items the ordered list of all current item which are suppose to be shown on the dock
+		 * @param func a function which adjusts the draw-value per item
+		 * @param post_func a function which post-processes all draw-values
+		 */
+		public void update_draw_values (Gee.ArrayList<unowned DockItem> items, DockItemDrawValueFunc? func = null,
+			DrawValuesFunc? post_func = null)
+		{
+			unowned DockPreferences prefs = controller.prefs;
+			
+			draw_values.clear ();
+			
+			bool external_drag_active = controller.drag_manager.ExternalDragActive;
+			
+			// first we do the math as if this is a top dock, to do this we need to set
+			// up some "pretend" variables. we pretend we are a top dock because 0,0 is
+			// at the top.
+			int width = DockWidth;
+			int height = DockHeight;
+			int icon_size = IconSize;
+			
+			double zoom_in_progress = controller.renderer.zoom_in_progress;
+			Gdk.Point cursor = controller.renderer.local_cursor;
+			
+			// "relocate" our cursor to be on the top
+			switch (Position) {
+			case Gtk.PositionType.RIGHT:
+				cursor.x = width - cursor.x;
+				break;
+			case Gtk.PositionType.BOTTOM:
+				cursor.y = height - cursor.y;
+				break;
+			default:
+				break;
+			}
+			
+			// our width and height switch around if we have a vertical dock
+			if (!is_horizontal_dock ()) {
+				int tmp = cursor.y;
+				cursor.y = cursor.x;
+				cursor.x = tmp;
+				
+				tmp = width;
+				width = height;
+				height = tmp;
+			}
+			
+			//FIXME
+			// the line along the dock width about which the center of unzoomed icons sit
+			double center_y = (is_horizontal_dock () ? static_dock_region.height / 2.0 : static_dock_region.width / 2.0);
+			
+			double center_x = (icon_size + ItemPadding) / 2.0 + items_offset;
+			if (Alignment == Gtk.Align.FILL) {
+				switch (ItemsAlignment) {
+				default:
+				case Gtk.Align.FILL:
+				case Gtk.Align.CENTER:
+					if (is_horizontal_dock ())
+						center_x += static_dock_region.x + (static_dock_region.width - 2 * items_offset - items_width) / 2;
+					else
+						center_x += static_dock_region.y + (static_dock_region.height - 2 * items_offset - items_width) / 2;
+					break;
+				case Gtk.Align.START:
+					break;
+				case Gtk.Align.END:
+					if (is_horizontal_dock ())
+						center_x += static_dock_region.x + (static_dock_region.width - 2 * items_offset - items_width);
+					else
+						center_x += static_dock_region.y + (static_dock_region.height - 2 * items_offset - items_width);
+					break;
+				}
+			} else {
+				if (is_horizontal_dock ())
+					center_x += static_dock_region.x;
+				else
+					center_x += static_dock_region.y;
+			}
+			
+			PointD center = { Math.floor (center_x), Math.floor (center_y) };
+			
+			// ZoomPercent is a number greater than 1.  It should never be less than one.
+			
+			// zoom_in_percent is a range of 1 to ZoomPercent.
+			// We need a number that is 1 when ZoomIn is 0, and ZoomPercent when ZoomIn is 1.
+			// Then we treat this as if it were the ZoomPercent for the rest of the calculation.
+			double zoom_in_percent = (prefs.ZoomEnabled ? 1.0 + (ZoomPercent - 1.0) * zoom_in_progress : 1.0);
+			double zoom_icon_size = (prefs.ZoomEnabled ? ZoomIconSize : 2.0 * icon_size);
+			
+			foreach (unowned DockItem item in items) {
+				DockItemDrawValue val = new DockItemDrawValue ();
+				val.opacity = 1.0;
+				val.darken = 0.0;
+				val.lighten = 0.0;
+				val.show_indicator = true;
+				val.zoom = 1.0;
+				
+				val.static_center = center;
+				
+				// get us some handy doubles with fancy names
+				double cursor_position = cursor.x;
+				double center_position = center.x;
+				
+				// offset from the center of the true position, ranged between 0 and the zoom size
+				double offset = double.min (Math.fabs (cursor_position - center_position), zoom_icon_size);
+				
+				double offset_percent;
+				if (external_drag_active) {
+					// Provide space for dropping between items
+					offset += offset * zoom_icon_size / icon_size;
+					offset_percent = double.min (1.0, offset / (zoom_icon_size + ZoomIconSize));
+				} else {
+					offset_percent = offset / zoom_icon_size;
+				}
+				
+				if (offset_percent > 0.99)
+					offset_percent = 1.0;
+				
+				// pull in our offset to make things less spaced out
+				// explaination since this is a bit tricky...
+				// we have three terms, basically offset = f(x) * h(x) * g(x)
+				// f(x) == offset identity
+				// h(x) == a number from 0 to DockPreference.ZoomPercent - 1.  This is used to get the smooth "zoom in" effect.
+				//         additionally serves to "curve" the offset based on the max zoom
+				// g(x) == a term used to move the ends of the zoom inward.  Precalculated that the edges should be 66% of the current
+				//         value. The center is 100%. (1 - offset_percent) == 0,1 distance from center
+				// The .66 value comes from the area under the curve.  Dont ask me to explain it too much because it's too clever for me.
+				
+				// for external drags with no zoom, we pretend there is actually a zoom of 200%
+				if (external_drag_active && ZoomPercent == 1.0)
+					offset *= zoom_in_progress / 2.0;
+				else
+					offset *= zoom_in_percent - 1.0;
+				offset *= 1.0 - offset_percent / 3.0;
+				
+				if (cursor_position > center_position)
+					center_position -= offset;
+				else
+					center_position += offset;
+				
+				// zoom is calculated as 1 through target_zoom (default 2).
+				// The larger your offset, the smaller your zoom
+				
+				// First we get the point on our curve that defines our current zoom
+				// offset is always going to fall on a point on the curve >= 0
+				var zoom = 1.0 - Math.pow (offset_percent, 2);
+				
+				// scale this to match our zoom_in_percent
+				zoom = 1.0 + zoom * (zoom_in_percent - 1.0);
+				
+				double zoomed_center_height = (icon_size * zoom / 2.0);
+				
+				if (zoom == 1.0)
+					center_position = Math.round (center_position);
+				
+				val.center = { center_position, zoomed_center_height };
+				val.zoom = zoom;
+				val.icon_size = Math.round (zoom * icon_size);
+				
+				// now we undo our transforms to the point
+				if (!is_horizontal_dock ()) {
+					double tmp = val.center.y;
+					val.center.y = val.center.x;
+					val.center.x = tmp;
+					
+					tmp = val.static_center.y;
+					val.static_center.y = val.static_center.x;
+					val.static_center.x = tmp;
+				}
+				
+				switch (Position) {
+				case Gtk.PositionType.RIGHT:
+					val.center.x = height - val.center.x;
+					val.static_center.x = height - val.static_center.x;
+					break;
+				case Gtk.PositionType.BOTTOM:
+					val.center.y = height - val.center.y;
+					val.static_center.y = height - val.static_center.y;
+					break;
+				default:
+					break;
+				}
+				
+				//FIXME
+				val.move_in (Position, bottom_offset);
+				
+				// let the draw-value be modified by the given function
+				if (func != null)
+					func (item, val);
+				
+				draw_values[item] = val;
+				
+				//FIXME
+				// Don't reserve space for removed items
+				if (item.RemoveTime == 0)
+					center.x += icon_size + ItemPadding;
+			}
+			
+			if (post_func != null)
+				post_func (draw_values);
+			
+			update_background_region (draw_values[items.first ()], draw_values[items.last ()]);
+			
+			// precalculate and cache regions (for the current frame)
+#if HAVE_GEE_0_8
+			draw_values.map_iterator ().foreach ((i, val) => {
+				val.draw_region = get_item_draw_region (val);
+				val.hover_region = get_item_hover_region (val);
+				val.background_region = get_item_background_region (val);
+				return true;
+			});
+#else
+			var draw_values_it = draw_values.map_iterator ();
+			while (draw_values_it.next ()) {
+				var val = draw_values_it.get_value ();
+				val.draw_region = get_item_draw_region (val);
+				val.hover_region = get_item_hover_region (val);
+				val.background_region = get_item_background_region (val);
+			}
+#endif
+		}
+		/**
 		 * The region for drawing a dock item.
 		 *
-		 * @param hover_rect the item's hover region
-		 * @return the region for the dock item
-		 */
-		Gdk.Rectangle get_item_draw_region (Gdk.Rectangle hover_rect)
-		{
+		 * @param val the item's DockItemDrawValue
+		 * @return the region for the dock item
+		 */
+		Gdk.Rectangle get_item_draw_region (DockItemDrawValue val)
+		{
+			var width = val.icon_size, height = val.icon_size;
+			
+			return { (int) Math.round (val.center.x - width / 2.0),
+				(int) Math.round (val.center.y - height / 2.0),
+				(int) width,
+				(int) height };
+		}
+		
+		/**
+		 * The intersecting region of a dock item's hover region and the background.
+		 *
+		 * @param val the item's DockItemDrawValue
+		 * @return the region for the dock item
+		 */
+		Gdk.Rectangle get_item_background_region (DockItemDrawValue val)
+		{
+			Gdk.Rectangle rect;
+			
+			if (!val.hover_region.intersect (get_background_region (), out rect))
+				return {};
+			
+			return rect;
+		}
+		
+		/**
+		 * The cursor region for interacting with a dock element.
+		 *
+		 * @param val the item's DockItemDrawValue
+		 * @return the region for the dock item
+		 */
+		Gdk.Rectangle get_item_hover_region (DockItemDrawValue val)
+		{
+			Gdk.Rectangle rect;
+			
 			var item_padding = ItemPadding;
 			var top_padding = (top_offset < 0 ? 0 : top_offset);
 			var bottom_padding = bottom_offset;
+			var width = val.icon_size, height = val.icon_size;
 			
+			// Apply scalable padding
 			switch (Position) {
 			default:
 			case Gtk.PositionType.BOTTOM:
-				hover_rect.x += item_padding / 2;
-				hover_rect.y += top_padding;
-				hover_rect.width -= item_padding;
-				hover_rect.height -= bottom_padding + top_padding;
+				width += item_padding;
 				break;
 			case Gtk.PositionType.TOP:
-				hover_rect.x += item_padding / 2;
-				hover_rect.y += bottom_padding;
-				hover_rect.width -= item_padding;
-				hover_rect.height -= bottom_padding + top_padding;
+				width += item_padding;
 				break;
 			case Gtk.PositionType.LEFT:
-				hover_rect.x += bottom_padding;
-				hover_rect.y += item_padding / 2;
-				hover_rect.width -= bottom_padding + top_padding;
-				hover_rect.height -= item_padding;
+				height += item_padding;
 				break;
 			case Gtk.PositionType.RIGHT:
-				hover_rect.x += top_padding;
-				hover_rect.y += item_padding / 2;
-				hover_rect.width -= bottom_padding + top_padding;
-				hover_rect.height -= item_padding;
+				height += item_padding;
 				break;
 			}
 			
-			return hover_rect;
-		}
-		
-		/**
-		 * The intersecting region of a dock item's hover region and the background.
-		 *
-		 * @param rect the item's hover region
-		 * @return the region for the dock item
-		 */
-		Gdk.Rectangle get_item_background_region (Gdk.Rectangle rect)
-		{
-			var top_padding = (top_offset > 0 ? 0 : top_offset);
+			rect = { (int) Math.round (val.center.x - width / 2.0),
+				(int) Math.round (val.center.y - height / 2.0),
+				(int) width,
+				(int) height };
 			
+			// Apply static padding
 			switch (Position) {
 			default:
 			case Gtk.PositionType.BOTTOM:
 				rect.y -= top_padding;
-				rect.height += top_padding;
+				rect.height += bottom_padding + top_padding;
 				break;
 			case Gtk.PositionType.TOP:
-				rect.height += top_padding;
+				rect.y -= bottom_padding;
+				rect.height += bottom_padding + top_padding;
 				break;
 			case Gtk.PositionType.LEFT:
-				rect.width += top_padding;
+				rect.x -= bottom_padding;
+				rect.width += bottom_padding + top_padding;
 				break;
 			case Gtk.PositionType.RIGHT:
 				rect.x -= top_padding;
-				rect.width += top_padding;
+				rect.width += bottom_padding + top_padding;
 				break;
 			}
 			
+			Gdk.Rectangle background_region;
+			
+			if (rect.intersect (get_background_region (), out background_region))
+				background_region.union (get_item_draw_region (val), out rect);
+			
 			return rect;
 		}
 		
@@ -815,7 +1127,7 @@
 		 * @param element the dock element to find a region for
 		 * @return the region for the dock item
 		 */
-		public Gdk.Rectangle get_item_hover_region (DockElement element)
+		public Gdk.Rectangle get_hover_region_for_element (DockElement element)
 		{
 			unowned DockItem? item = (element as DockItem);
 			if (item != null)
@@ -830,73 +1142,16 @@
 			if (items.size == 0)
 				return {};
 			
-			var first_rect = get_item_hover_region (items.first ());
+			var first_rect = get_hover_region_for_element (items.first ());
 			if (items.size == 1)
 				return first_rect;
 			
-			var last_rect = get_item_hover_region (items.last ());
+			var last_rect = get_hover_region_for_element (items.last ());
 			
 			Gdk.Rectangle result;
 			first_rect.union (last_rect, out result);
 			return result;
 		}
-			
-		Gdk.Rectangle internal_get_item_hover_region (DockItem item)
-		{
-			Gdk.Rectangle rect = {};
-			
-			switch (Position) {
-			default:
-			case Gtk.PositionType.BOTTOM:
-				rect.width = IconSize + ItemPadding;
-				rect.height = VisibleDockHeight;
-				rect.x = static_dock_region.x + items_offset + item.Position * (ItemPadding + IconSize);
-				rect.y = DockHeight - rect.height;
-				break;
-			case Gtk.PositionType.TOP:
-				rect.width = IconSize + ItemPadding;
-				rect.height = VisibleDockHeight;
-				rect.x = static_dock_region.x + items_offset + item.Position * (ItemPadding + IconSize);
-				rect.y = 0;
-				break;
-			case Gtk.PositionType.LEFT:
-				rect.height = IconSize + ItemPadding;
-				rect.width = VisibleDockWidth;
-				rect.y = static_dock_region.y + items_offset + item.Position * (ItemPadding + IconSize);
-				rect.x = 0;
-				break;
-			case Gtk.PositionType.RIGHT:
-				rect.height = IconSize + ItemPadding;
-				rect.width = VisibleDockWidth;
-				rect.y = static_dock_region.y + items_offset + item.Position * (ItemPadding + IconSize);
-				rect.x = DockWidth - rect.width;
-				break;
-			}
-			
-			if (Alignment != Gtk.Align.FILL)
-				return rect;
-			
-			switch (ItemsAlignment) {
-			default:
-			case Gtk.Align.FILL:
-			case Gtk.Align.CENTER:
-				if (is_horizontal_dock ())
-					rect.x += (static_dock_region.width - 2 * items_offset - items_width) / 2;
-				else
-					rect.y += (static_dock_region.height - 2 * items_offset - items_width) / 2;
-				break;
-			case Gtk.Align.START:
-				break;
-			case Gtk.Align.END:
-				if (is_horizontal_dock ())
-					rect.x += (static_dock_region.width - 2 * items_offset - items_width);
-				else
-					rect.y += (static_dock_region.height - 2 * items_offset - items_width);
-				break;
-			}
-			
-			return rect;
-		}
 		
 		/**
 		 * Get's the x and y position to display a menu for a dock item.
@@ -908,7 +1163,7 @@
 		 */
 		public void get_menu_position (DockItem hovered, Gtk.Requisition requisition, out int x, out int y)
 		{
-			var rect = get_item_hover_region (hovered);
+			var rect = get_hover_region_for_element (hovered);
 			
 			var offset = 10;
 			switch (Position) {
@@ -941,25 +1196,26 @@
 		 */
 		public void get_hover_position (DockItem hovered, out int x, out int y)
 		{
-			var rect = get_item_hover_region (hovered);
+			var center = get_draw_value_for_item (hovered).static_center;
+			var offset = (ZoomIconSize - IconSize / 2.0);
 			
 			switch (Position) {
 			default:
 			case Gtk.PositionType.BOTTOM:
-				x = rect.x + win_x + rect.width / 2;
-				y = rect.y + win_y;
+				x = (int) Math.round (center.x + win_x);
+				y = (int) Math.round (center.y + win_y - offset);
 				break;
 			case Gtk.PositionType.TOP:
-				x = rect.x + win_x + rect.width / 2;
-				y = rect.y + win_y + rect.height;
+				x = (int) Math.round (center.x + win_x);
+				y = (int) Math.round (center.y + win_y + offset);
 				break;
 			case Gtk.PositionType.LEFT:
-				y = rect.y + win_y + rect.height / 2;
-				x = rect.x + win_x + rect.width;
+				x = (int) Math.round (center.x + win_x + offset);
+				y = (int) Math.round (center.y + win_y);
 				break;
 			case Gtk.PositionType.RIGHT:
-				y = rect.y + win_y + rect.height / 2;
-				x = rect.x + win_x;
+				x = (int) Math.round (center.x + win_x - offset);
+				y = (int) Math.round (center.y + win_y);
 				break;
 			}
 		}
@@ -973,7 +1229,7 @@
 		 */
 		public void get_urgent_glow_position (DockItem item, out int x, out int y)
 		{
-			var rect = get_item_hover_region (item);
+			var rect = get_hover_region_for_element (item);
 			var glow_size = GlowSize;
 			
 			switch (Position) {
@@ -1130,8 +1386,12 @@
 		 */
 		public Gdk.Rectangle get_background_region ()
 		{
-			var x = 0, y = 0;
-			var width = 0, height = 0;
+			return background_rect;
+		}
+		
+		void update_background_region (DockItemDrawValue val_first, DockItemDrawValue val_last)
+		{
+			var x = 0, y = 0, width = 0, height = 0;
 			
 			if (screen_is_composited) {
 				x = static_dock_region.x;
@@ -1143,27 +1403,64 @@
 				height = DockHeight;
 			}
 			
+			if (Alignment == Gtk.Align.FILL) {
+				switch (Position) {
+				default:
+				case Gtk.PositionType.BOTTOM:
+					x += (width - DockBackgroundWidth) / 2;
+					y += height - DockBackgroundHeight;
+					break;
+				case Gtk.PositionType.TOP:
+					x += (width - DockBackgroundWidth) / 2;
+					y = 0;
+					break;
+				case Gtk.PositionType.LEFT:
+					x = 0;
+					y += (height - DockBackgroundHeight) / 2;
+					break;
+				case Gtk.PositionType.RIGHT:
+					x += width - DockBackgroundWidth;
+					y += (height - DockBackgroundHeight) / 2;
+					break;
+				}
+				
+				background_rect = { x, y, DockBackgroundWidth, DockBackgroundHeight };
+				return;
+			}
+			
+			var center_first = val_first.center;
+			var center_last = val_last.center;
+			var padding = IconSize + ItemPadding + 2 * HorizPadding + 4 * LineWidth;
+			
 			switch (Position) {
 			default:
 			case Gtk.PositionType.BOTTOM:
-				x += (width - DockBackgroundWidth) / 2;
+				x = (int) Math.round (center_first.x - padding / 2.0);
 				y += height - DockBackgroundHeight;
+				width = (int) Math.round (center_last.x - center_first.x + padding);
+				height = DockBackgroundHeight;
 				break;
 			case Gtk.PositionType.TOP:
-				x += (width - DockBackgroundWidth) / 2;
+				x = (int) Math.round (center_first.x - padding / 2.0);
 				y = 0;
+				width = (int) Math.round (center_last.x - center_first.x + padding);
+				height = DockBackgroundHeight;
 				break;
 			case Gtk.PositionType.LEFT:
 				x = 0;
-				y += (height - DockBackgroundHeight) / 2;
+				y = (int) Math.round (center_first.y - padding / 2.0);
+				width = DockBackgroundWidth;
+				height = (int) Math.round (center_last.y - center_first.y + padding);
 				break;
 			case Gtk.PositionType.RIGHT:
 				x += width - DockBackgroundWidth;
-				y += (height - DockBackgroundHeight) / 2;
+				y = (int) Math.round (center_first.y - padding / 2.0);
+				width = DockBackgroundWidth;
+				height = (int) Math.round (center_last.y - center_first.y + padding);
 				break;
 			}
 			
-			return { x, y, DockBackgroundWidth, DockBackgroundHeight };
+			background_rect = { x, y, width, height };
 		}
 		
 		/**
@@ -1175,7 +1472,7 @@
 		 */
 		public Gdk.Rectangle get_icon_geometry (ApplicationDockItem item, bool for_hidden)
 		{
-			var region = get_item_hover_region (item);
+			var region = get_hover_region_for_element (item);
 			
 			if (!for_hidden) {
 				region.x += win_x;

=== modified file 'lib/Widgets/DockWindow.vala'
--- lib/Widgets/DockWindow.vala	2015-06-10 16:44:08 +0000
+++ lib/Widgets/DockWindow.vala	2015-07-16 08:37:41 +0000
@@ -234,6 +234,7 @@
 			if (menu_is_visible ())
 				return Gdk.EVENT_STOP;
 			
+			controller.renderer.update_local_cursor ((int) event.x, (int) event.y);
 			update_hovered ((int) event.x, (int) event.y);
 			
 			return Gdk.EVENT_PROPAGATE;
@@ -394,7 +395,7 @@
 			
 			// check if there already was a hovered-item and if it is still hovered to speed up things
 			if (HoveredItem != null) {
-				rect = position_manager.get_item_hover_region (HoveredItem);
+				rect = position_manager.get_hover_region_for_element (HoveredItem);
 				if (y >= rect.y && y < rect.y + rect.height && x >= rect.x && x < rect.x + rect.width)
 					// Do not allow the hovered-item to be the drag-item
 					if (drag_item == HoveredItem) {
@@ -421,7 +422,7 @@
 			foreach (var element in controller.VisibleElements) {
 				item = (element as DockItem);
 				if (item != null) {
-					rect = position_manager.get_item_hover_region (item);
+					rect = position_manager.get_hover_region_for_element (item);
 					if (y < rect.y || y >= rect.y + rect.height || x < rect.x || x >= rect.x + rect.width)
 						continue;
 					
@@ -438,7 +439,7 @@
 				if (provider == null)
 					continue;
 				
-				rect = position_manager.get_item_hover_region (provider);
+				rect = position_manager.get_hover_region_for_element (provider);
 				if (y < rect.y || y >= rect.y + rect.height || x < rect.x || x >= rect.x + rect.width)
 					continue;
 				
@@ -446,7 +447,7 @@
 				found_hovered_provider = true;
 				
 				foreach (var element2 in provider.VisibleElements) {
-					rect = position_manager.get_item_hover_region (element2);
+					rect = position_manager.get_hover_region_for_element (element2);
 					if (y < rect.y || y >= rect.y + rect.height || x < rect.x || x >= rect.x + rect.width)
 						continue;
 					

=== modified file 'lib/Widgets/PreferencesWindow.vala'
--- lib/Widgets/PreferencesWindow.vala	2015-07-12 09:40:05 +0000
+++ lib/Widgets/PreferencesWindow.vala	2015-07-16 08:37:41 +0000
@@ -40,11 +40,13 @@
 		Gtk.SpinButton sp_hide_delay;
 		Gtk.SpinButton sp_unhide_delay;
 		Gtk.Scale s_offset;
+		Gtk.Scale s_zoom_percent;
 		
 		Gtk.Adjustment adj_hide_delay;
 		Gtk.Adjustment adj_unhide_delay;
 		Gtk.Adjustment adj_iconsize;
 		Gtk.Adjustment adj_offset;
+		Gtk.Adjustment adj_zoom_percent;
 		
 		Gtk.Switch sw_hide;
 		Gtk.Switch sw_primary_display;
@@ -54,6 +56,7 @@
 		Gtk.Switch sw_auto_pinning;
 		Gtk.Switch sw_pressure_reveal;
 		Gtk.Switch sw_show_dock_item;
+		Gtk.Switch sw_zoom_enabled;
 		
 		public PreferencesWindow (DockPreferences prefs)
 		{
@@ -105,7 +108,9 @@
 				adj_unhide_delay = builder.get_object ("adj_unhide_delay") as Gtk.Adjustment;
 				adj_iconsize = builder.get_object ("adj_iconsize") as Gtk.Adjustment;
 				adj_offset = builder.get_object ("adj_offset") as Gtk.Adjustment;
+				adj_zoom_percent = builder.get_object ("adj_zoom_percent") as Gtk.Adjustment;
 				s_offset = builder.get_object ("s_offset") as Gtk.Scale;
+				s_zoom_percent = builder.get_object ("s_zoom_percent") as Gtk.Scale;
 				sw_hide = builder.get_object ("sw_hide") as Gtk.Switch;
 				sw_primary_display = builder.get_object ("sw_primary_display") as Gtk.Switch;
 				sw_workspace_only = builder.get_object ("sw_workspace_only") as Gtk.Switch;
@@ -114,6 +119,7 @@
 				sw_auto_pinning = builder.get_object ("sw_auto_pinning") as Gtk.Switch;
 				sw_pressure_reveal = builder.get_object ("sw_pressure_reveal") as Gtk.Switch;
 				sw_show_dock_item = builder.get_object ("sw_show_dock_item") as Gtk.Switch;
+				sw_zoom_enabled = builder.get_object ("sw_zoom_enabled") as Gtk.Switch;
 				cb_alignment = builder.get_object ("cb_alignment") as Gtk.ComboBoxText;
 				cb_items_alignment = builder.get_object ("cb_items_alignment") as Gtk.ComboBoxText;
 				
@@ -197,6 +203,12 @@
 			case "UnhideDelay":
 				adj_unhide_delay.value = prefs.UnhideDelay;
 				break;
+			case "ZoomEnabled":
+				sw_zoom_enabled.set_active (prefs.ZoomEnabled);
+				break;
+			case "ZoomPercent":
+				adj_zoom_percent.value = prefs.ZoomPercent;
+				break;
 			// Ignored settings
 			case "DockItems":
 				break;
@@ -292,6 +304,17 @@
 			prefs.ShowDockItem = ((Gtk.Switch) widget).get_active ();
 		}
 		
+		void zoom_enabled_toggled (GLib.Object widget, ParamSpec param)
+		{
+			if (((Gtk.Switch) widget).get_active ()) {
+				prefs.ZoomEnabled = true;
+				s_zoom_percent.sensitive = true;
+			} else {
+				prefs.ZoomEnabled = false;
+				s_zoom_percent.sensitive = false;
+			}
+		}
+		
 		void iconsize_changed (Gtk.Adjustment adj)
 		{
 			prefs.IconSize = (int) adj.value;
@@ -312,6 +335,11 @@
 			prefs.UnhideDelay = (int) adj.value;
 		}
 		
+		void zoom_percent_changed (Gtk.Adjustment adj)
+		{
+			prefs.ZoomPercent = (int) adj.value;
+		}
+		
 		void monitor_changed (Gtk.ComboBox widget)
 		{
 			prefs.Monitor = ((Gtk.ComboBoxText) widget).get_active_text ();
@@ -329,6 +357,7 @@
 			cb_display_plug.changed.connect (monitor_changed);
 			adj_iconsize.value_changed.connect (iconsize_changed);
 			adj_offset.value_changed.connect (offset_changed);
+			adj_zoom_percent.value_changed.connect (zoom_percent_changed);
 			sw_hide.notify["active"].connect (hide_toggled);
 			sw_primary_display.notify["active"].connect (primary_display_toggled);
 			sw_workspace_only.notify["active"].connect (workspace_only_toggled);
@@ -337,6 +366,7 @@
 			sw_auto_pinning.notify["active"].connect (auto_pinning_toggled);
 			sw_pressure_reveal.notify["active"].connect (pressure_reveal_toggled);
 			sw_show_dock_item.notify["active"].connect (show_dock_item_toggled);
+			sw_zoom_enabled.notify["active"].connect (zoom_enabled_toggled);
 			cb_alignment.changed.connect (cb_alignment_changed);
 			cb_items_alignment.changed.connect (cb_items_alignment_changed);
 		}
@@ -373,7 +403,9 @@
 			
 			adj_iconsize.value = prefs.IconSize;
 			adj_offset.value = prefs.Offset;
+			adj_zoom_percent.value = prefs.ZoomPercent;
 			s_offset.sensitive = (prefs.Alignment == Gtk.Align.CENTER);
+			s_zoom_percent.sensitive = prefs.ZoomEnabled;
 			sw_hide.set_active (prefs.HideMode != HideType.NONE);
 			sw_primary_display.set_active (prefs.Monitor == "");
 			sw_workspace_only.set_active (prefs.CurrentWorkspaceOnly);
@@ -382,6 +414,7 @@
 			sw_auto_pinning.set_active (prefs.AutoPinning);
 			sw_pressure_reveal.set_active (prefs.PressureReveal);
 			sw_show_dock_item.set_active (prefs.ShowDockItem);
+			sw_zoom_enabled.set_active (prefs.ZoomEnabled);
 			cb_alignment.active_id = ((int) prefs.Alignment).to_string ();
 			cb_items_alignment.active_id = ((int) prefs.ItemsAlignment).to_string ();
 			cb_items_alignment.sensitive = (prefs.Alignment == Gtk.Align.FILL);

