diff --git a/game/engines/default/engine/Entity.lua b/game/engines/default/engine/Entity.lua
index 6c582f1b4ef2f2e7574ac217a112671f491b75c4..0a73a661fbb87e90b81d5ca70ccd64de4c473e08 100644
--- a/game/engines/default/engine/Entity.lua
+++ b/game/engines/default/engine/Entity.lua
@@ -413,6 +413,32 @@ function _M:getEntityFinalSurface(tiles, w, h)
 	return _M.__mo_final_repo[self][id].surface, _M.__mo_final_repo[self][id].tex
 end
 
+--- Get the entity image as an sdl texture for the given tiles and size
+-- @param tiles a Tiles instance that will handle the tiles (usually pass it the current Map.tiles)
+-- @param w the width
+-- @param h the height
+-- @return the sdl texture
+function _M:getEntityFinalTexture(tiles, w, h)
+	local id = w.."x"..h
+	if _M.__mo_final_repo[self] and _M.__mo_final_repo[self][id] then return _M.__mo_final_repo[self][id].tex end
+
+	local Map = require "engine.Map"
+	tiles = tiles or Map.tiles
+
+	local mos = {}
+	local list = {}
+	self:getMapObjects(tiles, mos, 1)
+	local listsize = #list
+	for i = 1, Map.zdepth do
+		if mos[i] then list[listsize+i] = mos[i] end
+	end
+	local tex = core.map.mapObjectsToTexture(w, h, unpack(list))
+	if not tex then return nil end
+	_M.__mo_final_repo[self] = _M.__mo_final_repo[self] or {}
+	_M.__mo_final_repo[self][id] = {tex=tex}
+	return _M.__mo_final_repo[self][id].tex
+end
+
 --- Get a string that will display in text the texture of this entity
 function _M:getDisplayString(tstr)
 	if tstr then
diff --git a/game/engines/default/engine/Game.lua b/game/engines/default/engine/Game.lua
index b5a3ae1889870e9563ba23c106b2334b8c9a5488..d5501fe0a1689ef9cee7f54284ba3835952e83dd 100644
--- a/game/engines/default/engine/Game.lua
+++ b/game/engines/default/engine/Game.lua
@@ -318,8 +318,8 @@ function _M:unregisterDialog(d)
 	local last = self.dialogs[#self.dialogs] or self
 	if last.key then last.key:setCurrent() end
 	if last.mouse then last.mouse:setCurrent() end
-	if last.on_recover_focus then last:on_recover_focus() end
 	if self.onUnregisterDialog then self:onUnregisterDialog(d) end
+	if last.on_recover_focus then last:on_recover_focus() end
 end
 
 --- Do we have a dialog running
diff --git a/game/engines/default/engine/PlayerProfile.lua b/game/engines/default/engine/PlayerProfile.lua
index ab656b88daa28ff3e65d6e09bc51a9e25f0d990c..bcda3d83369069b57f089c6184fb9f6f3a3867d4 100644
--- a/game/engines/default/engine/PlayerProfile.lua
+++ b/game/engines/default/engine/PlayerProfile.lua
@@ -256,6 +256,7 @@ end
 --- Saves a profile data
 function _M:saveGenericProfile(name, data, no_sync, nowrite)
 	-- Delay when we are currently saving
+	if not profile then return end
 	if savefile_pipe and savefile_pipe.saving then savefile_pipe:pushGeneric("saveGenericProfile", function() self:saveGenericProfile(name, data, nosync) end) return end
 
 	if not generic_profile_defs[name] then print("[PROFILE] refusing unknown generic data", name) return end
diff --git a/game/engines/default/engine/Tooltip.lua b/game/engines/default/engine/Tooltip.lua
index 88cba17b617417a823f34b3157df4109981cada2..836f055af45435547c810e90ecd5a53acba12f23 100644
--- a/game/engines/default/engine/Tooltip.lua
+++ b/game/engines/default/engine/Tooltip.lua
@@ -20,28 +20,44 @@
 require "engine.class"
 local Base = require "engine.ui.Base"
 local TextzoneList = require "engine.ui.TextzoneList"
+local UIContainer = require "engine.ui.UIContainer"
 local Map = require "engine.Map"
 
 --- A generic tooltip
 module(..., package.seeall, class.inherit(Base))
 
-tooltip_bound_x1 = 0
+tooltip_bound_x1 = function() return 0 end
 tooltip_bound_x2 = function() return game.w end
-tooltip_bound_y1 = 0
+tooltip_bound_y1 = function() return 0 end
 tooltip_bound_y2 = function() return game.h end
 
-function _M:init(fontname, fontsize, color, bgcolor, max)
+function _M:init(fontname, fontsize, color, bgcolor, max, lockstatus_icon)
 	self.max = max or 300
 	self.ui = "simple"
-    
+	
 	self.fontsize = fontsize
 	self.font = core.display.newFont(fontname or "/data/font/DroidSans.ttf", fontsize or 12)
 
 	self.default_ui = { TextzoneList.new{weakstore=true, width=self.max, height=500, variable_height=true, font=self.font, ui=self.ui} }
-
+	self.locked = false
+	
+	if lockstatus_icon then
+		local open, ow, oh = self:getImage(lockstatus_icon.open)
+		local locked, lw, lh = self:getImage(lockstatus_icon.locked)
+		self.status_icon = {open = {_tex = open:glTexture(), w = ow, h = oh}, locked = {_tex = locked:glTexture(), w = lw, h = lh} }
+	end
+	
+	self.empty = true
+	self.inhibit = false
 	self.uis = {}
-	self.w = self.max + 10
+	self.w = self.max
 	self.h = 200
+	self.uis_h = 0
+	self.pingpong = 0
+	self.last_display_x = 0
+	self.last_display_y = 0
+	self.dest_area = { h = self.h }
+	self.container = UIContainer.new{width = self.w, height = self.h, dest_area = { h = self.h, fixed = true} }
 	Base.init(self, {})
 end
 
@@ -49,79 +65,124 @@ function _M:generate()
 	self.frame = self:makeFrame("ui/tooltip/", self.w + 6, self.h + 6)
 end
 
---- Set the tooltip text    
+--- Set the tooltip text	
 function _M:set(str, ...)
+	-- if locked change is forbiden
+	if self.locked then return end
+	self.pingpong = 0
+	
 	if type(str) == "string" then str = ... and str:format(...):toTString() or str:toTString() end
 	if type(str) == "number" then str = tostring(str):toTString() end
 
+	if self.add_map_str then
+		if type(self.add_map_str) == "string" then
+			str = self.add_map_str:toTString()
+		elseif type(self.add_map_str) == "table" then
+			str = tstring{}
+			str:merge(self.add_map_str)
+		else
+			str = self.add_map_str
+		end
+	end
+	
 	local max_w = 0
-	local max_str_w = self.add_map_str and (self.add_map_str:toTString()):maxWidth(self.font) or 0
+	local max_str_w = 0
 	local uis = {}
+	local part
 	if not str.is_tstring then
+		-- format all texts into tstring and calculate required width 
 		for i=1, #str do
-		if type(str[i]) == "string" then
-			str[i] = str[i]:toTString()
-		end
-			if str[i].is_tstring then
-				max_str_w = math.max(str[i]:maxWidth(self.font) + 5, max_str_w)
-		else
-				max_w = math.max(str[i].w, max_w)
+			part = str[i]
+			if part then
+				if type(part) == "string" then
+					part = part:toTString()
+				end
+				if part.is_tstring then
+					local width = part:maxWidth(self.font)
+					if width > max_str_w then max_str_w = width end
+				elseif part.w > max_w then 
+					max_w = part.w 
+				end
 			end
 		end
 		
-		max_str_w = math.min(max_str_w, self.max)
-		max_w = math.max(max_w, max_str_w)
+		-- clip calculated width to max tooltip width
+		if self.max < max_str_w then max_str_w = self.max end
+		if max_str_w > max_w then max_w = max_str_w end
 
 		local ts = tstring{}
-		if self.add_map_str then ts:merge(self.add_map_str:toTString()) ts:add("---", true) end
+		local uis_size = #uis
 		for i=1, #str do
-			if str[i].is_tstring then
-				if i > 1 then 
-					if str[i - 1] and str[i - 1].is_tstring then 
-						ts:add(true) 
-					end 
-					ts:add("---", true)
-				end
-				ts:merge(str[i]:toTString())
-			else
-				if i > 1 then
-					if not str[i].suppress_line_break then
-						ts:add(true, "---")
+			-- if the item is tstring then merge it with main ts
+			if str[i] then
+				if str[i].is_tstring then
+					if i > 1 then 
+						if str[i - 1] and str[i - 1].is_tstring then 
+							ts:add(true) 
+						end 
+						ts:add("---", true)
+					end
+					ts:merge(str[i])
+				else
+					-- if we have more than one item in list then create TextzoneList and output ts that was gathering texts till now
+					if i > 1 then
+						-- if the item dont have special flag separe them with separator
+						if not str[i].suppress_line_break then
+							ts:add(true, "---")
+						end
+						local tz = TextzoneList.new{weakstore=true, width=max_w, height=500, variable_height=true, font=self.font, ui=self.ui}
+						tz:switchItem(ts, ts)
+						uis_size = uis_size + 1
+						uis[uis_size] = tz
 					end
-					local tz = TextzoneList.new{weakstore=true, width=max_w, height=500, variable_height=true, font=self.font, ui=self.ui}
-					tz:switchItem(ts, ts)
-					uis[#uis + 1] = tz
+					uis_size = uis_size + 1
+					uis[uis_size] = str[i]
+					
+					-- reset ts
+					ts = tstring{}
 				end
-				uis[#uis + 1] = str[i]
-
-				ts = tstring{}
 			end
 		end
 		local tz = TextzoneList.new{weakstore=true, width=max_w, height=500, variable_height=true, font=self.font, ui=self.ui}
 		tz:switchItem(ts, ts)
-		uis[#uis + 1] = tz
+		
+		uis_size = uis_size + 1
+		uis[uis_size] = tz
 		
 		if self.uis == self.default_ui then self:erase() end
 		self.uis = uis
 		self.empty = false
+	-- whole element is tstring so just put it into default element which is textzone
 	else
 		self:erase()
 		self.default_ui[1]:switchItem(str, str) 
 		max_w = self.max
 		self.empty = str:isEmpty()
 	end
+	
+	self.container:changeUI(self.uis)
 
 	local uih = 0
 	for i = 1, #self.uis do uih = uih + self.uis[i].h end
-    
+	
+	self.uis_h = uih
+	
+	-- change width and height to new values
 	self.h = uih + self.frame.b2.h
 	self.w = max_w + self.frame.b4.w
-    
+	
+	local clip_h = self.h
+	if game.tooltip:tooltip_bound_y2() < clip_h then clip_h = game.tooltip:tooltip_bound_y2() end
+	
+	self.container:resize(self.w, clip_h - self.frame.b2.h, self.w, clip_h - self.frame.b2.h)
+	self.h = clip_h
+	-- resize background frame
 	self.frame.h = self.h
 	self.frame.w = self.w
 end
 
 function _M:erase()
+	self.uis_h = 0
 	self.uis = self.default_ui
 	self.default_ui[1].list = nil
 	self.empty = true
@@ -130,28 +191,54 @@ end
 function _M:display() end
 
 function _M:toScreen(x, y, nb_keyframes)
-	-- We translate and scale opengl matrix to make the popup effect easily
-	local ox, oy = math.floor(x), math.floor(y)
-	x, y = ox, oy
-	local hw, hh = math.floor(self.w * 0.5), math.floor(self.h * 0.5)
-	local tx, ty = x + hw, y + hh
-	x, y = -hw, -hh
-	core.display.glTranslate(tx, ty, 0)
+	self.last_display_x = x
+	self.last_display_y = y
 
+	if self.inhibited == true then return nil end
+	nb_keyframes = nb_keyframes or 0
+	-- Save current matrix and load coords to default values
+	core.display.glPush()
+	core.display.glIdentity()
+	
+	local locked_w = ( (self.locked and self.uis_h > self.container.dest_area.h) and self.container.scrollbar.w or 0)
+	
+	if not self.locked then
+		if self.pingpong == 0 then
+			self.container.scrollbar.pos = self.container.scrollbar.pos + nb_keyframes
+			if self.container.scrollbar.pos > self.container.scrollbar.max then 
+				self.container.scrollbar.pos = self.container.scrollbar.max 
+				self.pingpong = 1
+			end
+		else
+			self.container.scrollbar.pos = self.container.scrollbar.pos - nb_keyframes
+			if self.container.scrollbar.pos < 0 then 
+				self.container.scrollbar.pos = 0
+				self.pingpong = 0
+			end
+		end
+	end
+	
 	-- Draw the frame and shadow
-	self:drawFrame(self.frame, x+1, y+1, 0, 0, 0, 0.3)
-	self:drawFrame(self.frame, x-3, y-3, 1, 1, 1, 0.75)
-
-	-- UI elements
-	local uih = 0
-	for i = 1, #self.uis do
-		local ui = self.uis[i]
-		ui:display(x + 5, y + 5 + uih, nb_keyframes, ox + 5, oy + 5 + uih)
-		uih = uih + ui.h
+	self:drawFrame(self.frame, x - locked_w, y, 0, 0, 0, 0.3, self.w + locked_w, self.h) -- shadow
+	if self.locked then
+		self:drawFrame(self.frame, x - locked_w, y, 1, 1, 1, 1, self.w + locked_w, self.h) -- locked frame
+	else
+		self:drawFrame(self.frame, x, y, 1, 1, 1, 0.75) -- unlocked frame
+	end
+	
+	if self.status_icon then
+		local status_off = ( (self.locked and self.uis_h > self.container.dest_area.h) and 0 or self.container.scrollbar.w)
+		if self.locked then
+			self.status_icon.locked._tex:toScreen(x + status_off - self.status_icon.locked.w, y + (self.h - self.status_icon.locked.h) * 0.5, self.status_icon.locked.w, self.status_icon.locked.h)
+		else
+			self.status_icon.open._tex:toScreen(x + status_off - self.status_icon.open.w, y + (self.h - self.status_icon.open.h) * 0.5, self.status_icon.open.w, self.status_icon.open.h )
+		end
 	end
 
-	-- Restore normal opengl matrix
-	core.display.glTranslate(-tx, -ty, 0)
+	self.container:display(x + 8 - locked_w, y + 8, nb_keyframes, x + 8 - locked_w, y + 8)
+	
+	-- Restore saved opengl matrix
+	core.display.glPop()
 end
 
 --- Displays the tooltip at the given map coordinates
@@ -161,48 +248,46 @@ end
 -- @param my the screen coordinate to display at, if nil it will be computed from tmy
 -- @param text a text to display, if nil it will interrogate the map under the mouse using the "tooltip" property
 -- @param force forces tooltip to refresh
-function _M:displayAtMap(tmx, tmy, mx, my, text, force)
+function _M:displayAtMap(tmx, tmy, mx, my, text, force, nb_keyframes)
 	if not mx then
 		mx, my = game.level.map:getTileToScreen(tmx, tmy)
 	end
 
-	if text then
-		if text ~= self.old_text then
-			self:set(text)
-			self:display()
-			self.old_text = text
-		end
-	else
-		if self.old_ttmx ~= tmx or self.old_ttmy ~= tmy or (game.paused and self.old_turn ~= game.turn) or force then
-			self.old_text = ""
-			self.old_ttmx, self.old_ttmy = tmx, tmy
-			self.old_turn = game.turn
-			local ts = self:getTooltipAtMap(tmx, tmy, mx, my)
-			if ts then
-				self:set(ts)
-				self:display()
-			else
-				self:erase()
+	if not self.locked then
+		if text then
+			if text ~= self.old_text or force then
+				self:set(text)
+				self.old_text = text
+			end
+		else
+			if self.old_ttmx ~= tmx or self.old_ttmy ~= tmy or (game.paused and self.old_turn ~= game.turn) or force then
+				self.old_text = ""
+				self.old_ttmx, self.old_ttmy = tmx, tmy
+				self.old_turn = game.turn
+				local ts = self:getTooltipAtMap(tmx, tmy, mx, my)
+				if ts then
+					self:set(ts)
+				else
+					self:erase()
+				end
 			end
 		end
 	end
 
 	if not self.empty then
-		local x1, x2, y1, y2 = util.getval(self.tooltip_bound_x1), util.getval(self.tooltip_bound_x2), util.getval(self.tooltip_bound_y1), util.getval(self.tooltip_bound_y2)
+		local x1, x2, y1, y2 = self.tooltip_bound_x1(), self.tooltip_bound_x2(), self.tooltip_bound_y1(), self.tooltip_bound_y2()
 		if mx < x1 then mx = x1 end
 		if my < y1 then my = y1 end
 		if mx > x2 - self.w then mx = x2 - self.w end
 		if my > y2 - self.h then my = y2 - self.h end
-		self.last_display_x = mx
-		self.last_display_y = my
-		self:toScreen(mx, my)
+		self:toScreen(mx, my, nb_keyframes)
 	end
 end
 
 --- Gets the tooltips at the given map coord
--- This method can/should be overloaded by a module to provide custom tooltips
 function _M:getTooltipAtMap(tmx, tmy, mx, my)
-    local tt = {}
+	if self.locked then return nil end
+	local tt = {}
 	local seen = game.level.map.seens(tmx, tmy)
 	local remember = game.level.map.remembers(tmx, tmy)
 	local ctrl_state = core.key.modState("ctrl")
@@ -211,8 +296,13 @@ function _M:getTooltipAtMap(tmx, tmy, mx, my)
 		local to_add = game.level.map:checkEntity(tmx, tmy, check_type, "tooltip", game.level.map.actor_player)
 		if to_add then 
 			if type(to_add) == "string" then to_add = to_add:toTString() end
-			tt[#tt+1] = to_add 
+			if to_add.is_tstring then 
+				tt[#tt+1] = to_add 
+			else 
+				table.append(tt, to_add) 
+			end
 		end
+		return to_add
 	end
 	
 	if seen and not ctrl_state then
@@ -230,6 +320,6 @@ function _M:getTooltipAtMap(tmx, tmy, mx, my)
 	if #tt > 0 then
 		return tt
 	end
-	if self.add_map_str then return self.add_map_str:toTString() end
+	if self.add_map_str then return self.add_map_str end
 	return nil
-end
+end
\ No newline at end of file
diff --git a/game/engines/default/engine/dialogs/ShowAchievements.lua b/game/engines/default/engine/dialogs/ShowAchievements.lua
index 76ab1966772a5d5bfb59e32ba0240807315da01b..f7eb1350bc314548bdfdadc7da1690bb160e73dc 100644
--- a/game/engines/default/engine/dialogs/ShowAchievements.lua
+++ b/game/engines/default/engine/dialogs/ShowAchievements.lua
@@ -44,9 +44,29 @@ function _M:init(title, player)
 	self.c_desc = TextzoneList.new{width=math.floor(self.iw * 0.4 - 10), height=self.ih - self.c_self.h}
 
 	self:generateList("main")
+	
+	local direct_draw= function(item, x, y, w, h, total_w, total_h, loffset_x, loffset_y, dest_area)
+		-- if there is object and is withing visible bounds
+		if item.tex and total_h + h > loffset_y and total_h < loffset_y + dest_area.h then 
+			local clip_y_start, clip_y_end = 0, 0
+			-- if it started before visible area then compute its top clip
+			if total_h < loffset_y then 
+				clip_y_start = loffset_y - total_h
+			end
+			-- if it ended after visible area then compute its bottom clip
+			if total_h + h > loffset_y + dest_area.h then 
+			   clip_y_end = total_h + h - loffset_y - dest_area.h 
+			end
+
+			local one_by_tex_h = 1 / h
+			item.tex[1]:toScreenPrecise(x, y, h, h - clip_y_start - clip_y_end, 0, 1, clip_y_start * one_by_tex_h, (h - clip_y_end) * one_by_tex_h)
+			return h, h, 0, 0, clip_y_start, clip_y_end
+		end 
+		return 0, 0, 0, 0, 0, 0
+	end
 
-	self.c_list = ListColumns.new{width=math.floor(self.iw * 0.6 - 10), height=self.ih - 10 - self.c_self.h, scrollbar=true, sortable=true, columns={
-		{name="", width={24,"fixed"}, display_prop="--", direct_draw=function(item, x, y) if item.tex then item.tex[1]:toScreen(x+4, y, 16, 16) end end},
+	self.c_list = ListColumns.new{width=math.floor(self.iw * 0.6 - 10), height=self.ih - 10 - self.c_self.h, floating_headers = true, scrollbar=true, sortable=true, columns={
+		{name="", width={24,"fixed"}, display_prop="--", direct_draw=direct_draw},
 		{name="Achievement", width=60, display_prop="name", sort="name"},
 		{name="When", width=20, display_prop="when", sort="when"},
 		{name="Who", width=20, display_prop="who", sort="who"},
@@ -77,8 +97,7 @@ function _M:switchTo(kind)
 	elseif kind == "all" then self.c_main.checked = false self.c_self.checked = false
 	end
 
-	self.c_list.list = self.list
-	self.c_list:generate()
+	self.c_list:setList(self.list)
 	self.c_desc.items = {}
 	self.c_desc:switchItem(nil)
 end
diff --git a/game/engines/default/engine/interface/GameTargeting.lua b/game/engines/default/engine/interface/GameTargeting.lua
index 1aea763beb0b1ac1391bde1bf1a277b5ceaaec48..afcd76d98c3bf1c6c0483cb803e490994fb21632 100644
--- a/game/engines/default/engine/interface/GameTargeting.lua
+++ b/game/engines/default/engine/interface/GameTargeting.lua
@@ -52,16 +52,17 @@ end
 
 --- Display the tooltip, if any
 
-function _M:targetDisplayTooltip(dx, dy, force)
+function _M:targetDisplayTooltip(dx, dy, force, nb_keyframes)
 	-- Tooltip is displayed over all else
 	if self.level and self.level.map and self.level.map.finished then
+		local tmx, tmy
 		-- Display a tooltip if available
 		if self.tooltip_x then
 			if type(self.tooltip_x) == "table" then
-				self.tooltip:toScreen(self.tooltip.last_display_x, self.tooltip.last_display_y)
+				self.tooltip:toScreen(self.tooltip.last_display_x, self.tooltip.last_display_y, nb_keyframes)
 			else
-				local tmx, tmy = self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y)
-				self.tooltip:displayAtMap(tmx, tmy, dx, dy, nil, force)
+				tmx, tmy = self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y)
+				self.tooltip:displayAtMap(tmx, tmy, dx, dy, nil, force, nb_keyframes)
 			end
 		end
 
@@ -74,8 +75,8 @@ function _M:targetDisplayTooltip(dx, dy, force)
 end
 
 --- Forces the tooltip to pop with the given text
-function _M:tooltipDisplayAtMap(x, y, text, extra, force)
-	self.tooltip:displayAtMap(nil, nil, x, y, text, force)
+function _M:tooltipDisplayAtMap(x, y, text, extra, force, nb_keyframes)
+	self.tooltip:displayAtMap(nil, nil, x, y, text, force, nb_keyframes)
 	if extra then
 		if extra.up then self.tooltip.last_display_y = self.tooltip.last_display_y - self.tooltip.h end
 	end
diff --git a/game/engines/default/engine/ui/Base.lua b/game/engines/default/engine/ui/Base.lua
index 14e15f301e3aee44b06b35b65351293b10f5a73b..22fd0146d84edbccfcca7b227639891d477d821b 100644
--- a/game/engines/default/engine/ui/Base.lua
+++ b/game/engines/default/engine/ui/Base.lua
@@ -114,7 +114,7 @@ function _M:init(t, no_gen)
 			self.font_h = self.font:lineSkip()
 		end
 	end
-
+	
 	if t.ui then self.ui = t.ui end
 
 	if not no_gen then self:generate() end
@@ -160,28 +160,120 @@ function _M:makeFrame(base, w, h)
 	return f
 end
 
-function _M:drawFrame(f, x, y, r, g, b, a, w, h)
-	if not f.b7 then return end
-
-	x = math.floor(x)
-	y = math.floor(y)
+function _M:drawFrame(f, x, y, r, g, b, a, w, h, total_w, total_h, loffset_x, loffset_y, clip_area)
+	if not f.b7 then return 0, 0, 0, 0 end
+	
+	loffset_x = loffset_x or 0
+	loffset_y = loffset_y or 0
+	total_w = total_w or 0
+	total_h = total_h or 0
+	
 	f.w = w or f.w
 	f.h = h or f.h
+	
+	clip_area = clip_area or { h = f.h, w = f.w }
 
-	-- Sides
-	f.b8.t:toScreenFull(x + f.b7.w, y, f.w - f.b7.w - f.b9.w + 1, f.b8.h, f.b8.tw, f.b8.th, r, g, b, a)
-	f.b2.t:toScreenFull(x + f.b7.w, y + f.h - f.b3.h + 1, f.w - f.b7.w - f.b9.w + 1, f.b2.h, f.b2.tw, f.b2.th, r, g, b, a)
-	f.b4.t:toScreenFull(x, y + f.b7.h, f.b4.w, f.h - f.b7.h - f.b1.h + 1, f.b4.tw, f.b4.th, r, g, b, a)
-	f.b6.t:toScreenFull(x + f.w - f.b9.w + 1, y + f.b7.h, f.b6.w, f.h - f.b7.h - f.b1.h + 1, f.b6.tw, f.b6.th, r, g, b, a)
+	-- check if anything is visible
+	if total_h + f.h > loffset_y and total_h < loffset_y + clip_area.h then 
+		local clip_y_start = 0
+		local clip_y_end = 0
+		local total_clip_y_start = 0
+		local total_clip_y_end = 0
+		
+		local one_by_tex_h = 1
+		local fw = 0
+		local fh = 0
+		
+		-- if it started before visible area then compute its top clip
+		if total_h < loffset_y then 
+			clip_y_start = loffset_y - total_h 
+		end
+		-- if it ended after visible area then compute its bottom clip
+		if total_h + f.b7.h > loffset_y + clip_area.h then 
+		   clip_y_end = total_h + f.b7.h - loffset_y - clip_area.h
+		   total_clip_y_end = clip_y_end
+		end
+		
+		-- check if top is visible
+		if total_h + f.b7.h > loffset_y and total_h < loffset_y + clip_area.h then 
+			one_by_tex_h = 1 / f.b7.th
+			fw, fh = f.b7.w, f.b7.h
+			f.b7.t:toScreenPrecise(x, y, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b7.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- left top
+			
+			one_by_tex_h = 1 / f.b8.th
+			fw, fh = f.w - f.b7.w - f.b9.w, f.b8.h
+			f.b8.t:toScreenPrecise(x + f.b7.w, y, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b8.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- top
+			
+			one_by_tex_h = 1 / f.b9.th
+			fw, fh = f.b9.w, f.b9.h
+			f.b9.t:toScreenPrecise(x + f.w - f.b9.w, y, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b9.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- right top
+			total_clip_y_start = clip_y_start
+		else
+			total_clip_y_start = f.b7.h 
+		end
+		total_h = total_h + f.b7.h 
+		
+		local mid_h = f.h - f.b2.h - f.b8.h
+		clip_y_start = 0
+		clip_y_end = 0
+		-- if it started before visible area then compute its top clip
+		if total_h < loffset_y then 
+			clip_y_start = loffset_y - total_h 
+		end
+		-- if it ended after visible area then compute its bottom clip
+		if total_h + mid_h > loffset_y + clip_area.h then 
+		   clip_y_end = total_h + mid_h - loffset_y - clip_area.h
+		   total_clip_y_end = total_clip_y_end + clip_y_end
+		end
+		-- check if center is visible
+		if total_h + mid_h > loffset_y and total_h < loffset_y + clip_area.h then 
+			one_by_tex_h = 1 / f.b4.th
+			fw, fh = f.b4.w, mid_h
+			f.b4.t:toScreenPrecise(x, y + f.b7.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b4.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- left
+			
+			one_by_tex_h = 1 / f.b6.th
+			fw, fh = f.b6.w, mid_h
+			f.b6.t:toScreenPrecise(x + f.w - f.b9.w, y + f.b7.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b6.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- right
+			
+			one_by_tex_h = 1 / f.b5.th
+			fw, fh = f.w - f.b7.w - f.b3.w, mid_h
+			f.b5.t:toScreenPrecise(x + f.b7.w, y + f.b7.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b5.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- center
+			total_clip_y_start = total_clip_y_start + clip_y_start
+		else
+			total_clip_y_start = total_clip_y_start + mid_h
+		end
+		total_h = total_h + mid_h
+		
+		clip_y_start = 0
+		clip_y_end = 0
+ 		-- if it started before visible area then compute its top clip
+		if total_h < loffset_y then 
+			clip_y_start = loffset_y - total_h 
+		end
+		-- if it ended after visible area then compute its bottom clip
+		if total_h + f.b2.h > loffset_y + clip_area.h then 
+		   clip_y_end = total_h + f.b2.h - loffset_y - clip_area.h
+		   total_clip_y_end = total_clip_y_end + clip_y_end
+		end
+		
+		-- check if bottom is visible
+		if total_h + f.b2.h > loffset_y and total_h < loffset_y + clip_area.h then 
+			one_by_tex_h = 1 / f.b1.th
+			fw, fh = f.b1.w, f.b1.h
+			f.b1.t:toScreenPrecise(x, y + f.h - f.b1.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b1.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- left bottom
 
-	-- Body
-	f.b5.t:toScreenFull(x + f.b7.w, y + f.b7.h, f.w - f.b7.w - f.b3.w + 1, f.h - f.b7.h - f.b3.h + 1, f.b6.tw, f.b6.th, r, g, b, a)
+			one_by_tex_h = 1 / f.b2.th
+			fw, fh = f.w - f.b7.w - f.b9.w, f.b2.h
+			f.b2.t:toScreenPrecise(x + f.b7.w, y + f.h - f.b2.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b2.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- bottom
 
-	-- Corners
-	f.b7.t:toScreenFull(x, y, f.b7.w, f.b7.h, f.b7.tw, f.b7.th, r, g, b, a)
-	f.b1.t:toScreenFull(x, y + f.h - f.b1.h + 1, f.b1.w, f.b1.h, f.b1.tw, f.b1.th, r, g, b, a)
-	f.b9.t:toScreenFull(x + f.w - f.b9.w + 1, y, f.b9.w, f.b9.h, f.b9.tw, f.b9.th, r, g, b, a)
-	f.b3.t:toScreenFull(x + f.w - f.b3.w + 1, y + f.h - f.b3.h + 1, f.b3.w, f.b3.h, f.b3.tw, f.b3.th, r, g, b, a)
+			one_by_tex_h = 1 / f.b3.th
+			fw, fh = f.b3.w, f.b3.h
+			f.b3.t:toScreenPrecise(x + f.w - f.b3.w, y + f.h - f.b3.h - total_clip_y_start, fw, fh - (clip_y_start + clip_y_end), 0, fw / f.b3.tw, clip_y_start * one_by_tex_h, (fh - clip_y_end) * one_by_tex_h, r, g, b, a ) -- right bottom
+		end
+		
+		return 0, 0, total_clip_y_start, total_clip_y_end
+	end
+	return 0, 0, 0, 0
 end
 
 function _M:setTextShadow(v)
diff --git a/game/engines/default/engine/ui/Dialog.lua b/game/engines/default/engine/ui/Dialog.lua
index df596139812de4bb34116e260ad06fc540c1e290..d79e56c8c50925d9761e94511f74368bb80bb8ca 100644
--- a/game/engines/default/engine/ui/Dialog.lua
+++ b/game/engines/default/engine/ui/Dialog.lua
@@ -618,7 +618,7 @@ function _M:toScreen(x, y, nb_keyframes)
 
 	if self.first_display then self:firstDisplay() self.first_display = false end
 
-	-- Restiore normal opengl matrix
+	-- Restore normal opengl matrix
 	if zoom < 1 then core.display.glScale() end
 	core.display.glTranslate(-tx, -ty, 0)
 end
diff --git a/game/engines/default/engine/ui/Focusable.lua b/game/engines/default/engine/ui/Focusable.lua
index dc628a0789a5f1d4a005002ec2359a13f3575d90..8e507b45dc4fc7778c6676ef7456745e7db2a757 100644
--- a/game/engines/default/engine/ui/Focusable.lua
+++ b/game/engines/default/engine/ui/Focusable.lua
@@ -23,8 +23,9 @@ require "engine.class"
 module(..., package.seeall, class.make)
 
 can_focus = true
-focus_decay_max = 5
+focus_decay_max = 8
 focus_decay_max_d = 8
+one_by_focus_decay = 1/32
 
 function _M:setFocus(v)
 	self.focused = v
diff --git a/game/engines/default/engine/ui/Inventory.lua b/game/engines/default/engine/ui/Inventory.lua
index 4c4ae8b2c78c8be6f61ae7eb725d0782a9ab6ee5..e89db2583e862e42bf067608136f326108a86cce 100644
--- a/game/engines/default/engine/ui/Inventory.lua
+++ b/game/engines/default/engine/ui/Inventory.lua
@@ -41,6 +41,8 @@ function _M:init(t)
 	self.on_drag = t.on_drag
 	self.on_drag_end = t.on_drag_end
 	self.special_bg = t.special_bg
+	
+	self._last_x, _last_y, self._last_ox, self._last_oy = 0, 0, 0, 0
 
 	if self.tabslist == nil and self.default_tabslist then
 		if type(self.default_tabslist) == "function" then self.tabslist = self.default_tabslist(self)
@@ -67,16 +69,37 @@ function _M:generate()
 		end
 		self.uis[#self.uis+1] = {x=0, y=0, ui=self.c_tabs}
 	end
+	
+	local direct_draw= function(item, x, y, w, h, total_w, total_h, loffset_x, loffset_y, dest_area)
+		-- if there is object and is withing visible bounds
+		if item.object and total_h + h > loffset_y and total_h < loffset_y + dest_area.h then 
+			local clip_y_start, clip_y_end = 0, 0
+			-- if it started before visible area then compute its top clip
+			if total_h < loffset_y then 
+				clip_y_start = loffset_y - total_h
+			end
+			-- if it ended after visible area then compute its bottom clip
+			if total_h + h > loffset_y + dest_area.h then 
+			   clip_y_end = total_h + h - loffset_y - dest_area.h 
+			end
+			-- get entity texture with everything it has i.e particles
+			local texture = item.object:getEntityFinalTexture(nil, h, h)
+			local one_by_tex_h = 1 / h
+			texture:toScreenPrecise(x, y, h, h - clip_y_start - clip_y_end, 0, 1, clip_y_start * one_by_tex_h, (h - clip_y_end) * one_by_tex_h)
+			return h, h, 0, 0, clip_y_start, clip_y_end
+		end 
+		return 0, 0, 0, 0, 0, 0
+	end
 
-	self.c_inven = ListColumns.new{width=self.w, height=self.h - (self.c_tabs and self.c_tabs.h or 0), sortable=true, scrollbar=true, columns=self.columns or {
-		{name="", width={20,"fixed"}, display_prop="char", sort="id"},
-		{name="", width={24,"fixed"}, display_prop="object", sort="sortname", direct_draw=function(item, x, y) if item.object then item.object:toScreen(nil, x+4, y, 16, 16) end end},
+	self.c_inven = ListColumns.new{width=self.w, height=self.h - (self.c_tabs and self.c_tabs.h or 0), floating_headers = true, sortable=true, scrollbar=true, columns=self.columns or {
+		{name="", width={33,"fixed"}, display_prop="char", sort="id"},
+		{name="", width={24,"fixed"}, display_prop="object", sort="sortname", direct_draw=direct_draw},
 		{name="Inventory", width=72, display_prop="name", sort="sortname"},
 		{name="Category", width=20, display_prop="cat", sort="cat"},
 		{name="Enc.", width=8, display_prop="encumberance", sort="encumberance"},
 	}, list={},
 		fct=function(item, sel, button, event) if self.fct then self.fct(item, button, event) end end,
-		select=function(item, sel) if self.on_select then self.on_select(item, sel) end end,
+		select=self.on_select,
 		on_drag=function(item) if self.on_drag then self.on_drag(item) end end,
 		on_drag_end=function() if self.on_drag_end then self.on_drag_end() end end,
 	}
diff --git a/game/engines/default/engine/ui/ListColumns.lua b/game/engines/default/engine/ui/ListColumns.lua
index 9513494a4d4ed636c10ec6802218c0443f3d4d3f..1e69ba3a694b6c983e2b0da8c153f3ce4f5aba39 100644
--- a/game/engines/default/engine/ui/ListColumns.lua
+++ b/game/engines/default/engine/ui/ListColumns.lua
@@ -26,244 +26,436 @@ local Slider = require "engine.ui.Slider"
 module(..., package.seeall, class.inherit(Base, Focusable))
 
 function _M:init(t)
-	self.list = assert(t.list, "no list list")
+	self.list = assert(t.list, "no list entires")
 	self.columns = assert(t.columns, "no list columns")
 	self.w = assert(t.width, "no list width")
-	self.h = t.height
-	self.nb_items = t.nb_items
-	assert(self.h or self.nb_items, "no list height/nb_items")
+	assert(t.height or t.nb_items, "no list height/nb_items")
+	self.nb_rows = t.nb_items
+	self.font = t.font or self.font
+	self.row_height = t.item_height or (self.font_h + 6)
+	self.h = t.height or self.row_height * ( (self.nb_rows or 0) + (self.hide_columns and 1 or 0) )
 	self.sortable = t.sortable
 	self.scrollbar = t.scrollbar
 	self.fct = t.fct
-	self.select = t.select
+	self.on_select = t.select
 	self.on_drag = t.on_drag
 	self.on_drag_end = t.on_drag_end
 	self.all_clicks = t.all_clicks
-	self.hide_columns = t.hide_columns
+	self.floating_headers = t.floating_headers or true
+	self.hide_columns = t.hide_columns or false
+	self.dest_area = t.dest_area and t.dest_area or { h = self.h }
+	self.text_shadow = t.text_shadow or self.text_shadow
+	self.click_select = t.click_select or false
+	self.only_display = t.only_display or false
 
-	self.fh = t.item_height or ls_h
+	self.max_h = 0
+	self.max_h_columns = 0
+	self.prevrow = 0
+	self.prevclick = 0
+	self.last_input_was_keyboard = false
+	self.scroll_inertia = 0
 
-	self.fh = t.item_height or (self.font_h + 6)
+	self.mouse_pos = { x = 0, y = 0 }
 
-	local w = self.w
-	if self.scrollbar then w = w - 10 end
-	for j, col in ipairs(self.columns) do
-		if type(col.width) == "table" then
-			if col.width[2] == "fixed" then
-				w = w - col.width[1]
-			end
+	if self.scrollbar then self.scrollbar = Slider.new{size=self.h, max=1} end
+
+	self:setColumns(t.columns)
+
+	Base.init(self, t)
+end
+
+function _M:generate()
+	-- Create the scrollbar
+	if self.scrollbar then self.scrollbar.h = self.h - (self.hide_columns and 0 or self.max_h_columns) end
+
+	self.sel = 1
+	self:selectColumn(1, true)
+
+	-- Init rows
+	for i=1, #self.list do
+		local row = self.list[i]
+		self:generateRow(row)
+		self.max_h = self.max_h + row.h
+	end
+	if self.scrollbar then
+		self.scrollbar.max = self.max_h - self.h
+		self.scrollbar.pos = 0
+	end
+
+	self:setupInput()
+	self:onSelect()
+end
+
+function _M:setupInput()
+	self.mouse:reset()
+	self.key:reset()
+	local colx = 0
+
+	for i=1, #self.columns do
+		local col = self.columns[i]
+		local on_left = function(button, x, y, xrel, yrel, bx, by, event)
+			if button == "left" and event == "button" then self:selectColumn(i) end
 		end
+		self.mouse:registerZone(colx, 0, col.width, self.row_height, on_left, nil, ("column header%d"):format(i))
+		colx = colx + col.width
 	end
-	for j, col in ipairs(self.columns) do
-		if type(col.width) == "table" then
-			if col.width[2] == "fixed" then
-				col.width = col.width[1]
+
+	local on_mouse = function(button, x, y, xrel, yrel, bx, by, event)
+		self.last_input_was_keyboard = false
+		if button == "wheelup" and event == "button" then self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 5 end
+		elseif button == "wheeldown" and event == "button" then self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 5 end
+		end
+		if button == "middle" and self.scrollbar then
+			if not self.scroll_drag then
+				self.scroll_drag = true
+				self.scroll_drag_x_start = x
+				self.scroll_drag_y_start = y
+			else
+				self.scrollbar.pos = util.minBound(self.scrollbar.pos + y - self.scroll_drag_y_start, 0, self.scrollbar.max)
+				self.scroll_drag_x_start = x
+				self.scroll_drag_y_start = y
 			end
 		else
-			col.width = w * col.width / 100
+			self.scroll_drag = false
 		end
 
-		col.surface = core.display.newSurface(col.width, self.fh)
-		col.frame = self:makeFrame(nil, col.width, self.fh)
-		col.frame_special = self:makeFrame("ui/selector", col.width, self.fh)
-		col.frame_sel = self:makeFrame("ui/selector-sel", col.width, self.fh)
-		col.frame_usel = self:makeFrame("ui/selector", col.width, self.fh)
-		col.frame_col = self:makeFrame("ui/heading", col.width, self.fh)
-		col.frame_col_sel = self:makeFrame("ui/heading-sel", col.width, self.fh)
+		if (self.all_clicks or button == "left") and event == "button" then
+			if self.click_select then
+				if self.prevclick == self.sel then self:onUse(button, event) end
+				self:onSelect()
+				self.prevclick = self.sel
+			else
+				self:onUse(button, event)
+			end
+		end
+		if event == "motion" and button == "left" and self.on_drag then self.on_drag(self.list[self.sel], self.sel) end
+		if button == "drag-end" and self.on_drag_end then self.on_drag_end(self.list[self.sel], self.sel) end
+		self.mouse_pos = { x = bx, y = by }
 	end
 
-	Base.init(self, t)
+	self.mouse:registerZone(0, self.row_height, self.w, self.h, on_mouse, nil, "list area")
+	self.key:addBinds{
+		ACCEPT = function() self.last_input_was_keyboard = true self:onUse("left", "key") end,
+		MOVE_UP = function()
+			self.last_input_was_keyboard = true
+			self.prevrow = self.sel
+			self.sel = util.minBound(self.sel - 1, 1, #self.list) end,
+		MOVE_DOWN = function()
+			self.last_input_was_keyboard = true
+			self.prevrow = self.sel
+			self.sel = util.minBound(self.sel + 1, 1, #self.list) end,
+	}
+
+	self.key:addCommands{
+		[{"_UP","ctrl"}] = function() self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 5 end end,
+		[{"_DOWN","ctrl"}] = function() self.last_input_was_keyboard = false if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 5 end end,
+		_HOME = function()
+			self.last_input_was_keyboard = true
+			self.sel = 1
+			self.scrollbar.pos = 0
+			self:onSelect()
+		end,
+		_END = function()
+			self.last_input_was_keyboard = true
+			self.sel = self.list and #self.list or 1
+			self.scrollbar.pos = self.scrollbar.max
+			self:onSelect()
+		end,
+		_PAGEUP = function()
+			self.last_input_was_keyboard = false
+			if self.scrollbar then
+				local scrollby = self.h - (self.only_display and 0 or (self.hide_columns and 0 or (self.floating_headers and self.max_h_columns or 0)))
+				self.scrollbar.pos = util.minBound(self.scrollbar.pos - scrollby, 0, self.scrollbar.max)
+			end
+		end,
+		_PAGEDOWN = function()
+			self.last_input_was_keyboard = false
+			if self.scrollbar then
+				local scrollby = self.h - (self.only_display and 0 or (self.hide_columns and 0 or (self.floating_headers and self.max_h_columns or 0)))
+				self.scrollbar.pos = util.minBound(self.scrollbar.pos + scrollby, 0, self.scrollbar.max)
+			end
+		end,
+	}
 end
 
-function _M:drawItem(item, nb_keyframes)
-	nb_keyframes = (nb_keyframes or 0) / 2
-	for j, col in ipairs(self.columns) do
-		if not col.direct_draw then
-			local fw, fh = col.fw, self.fh
+function _M:generateRow(row, force)
+	local max_h = 0
+	row.cells = {}
+	for j=1, #self.columns do
+		local col = self.columns[j]
+		row.cells[j] = row.cells[j] or {}
+
+		-- if color for each column is different
+		if row.color and type(row.color[1]) == "table" then
+			color = row.color[j]
+			row.cells[j].color = color
+		else
+			row.color = row.color or {255,255,255}
+		end
 
-			local text
-			if type(col.display_prop) == "function" then
-				text = col.display_prop(item)
+		if not row.cells[j]._tex then
+			if col.direct_draw then
+				row.cells[j].w, row.cells[j].h = col.direct_draw(row, 0, 0, col.width, self.row_height, 0, 0, 0, 0, self.dest_area) or self.row_height, self.row_height
 			else
-				text = item[col.display_prop or col.sort]
+				if type(col.display_prop) == "function" then
+					text = col.display_prop(row)
+				else
+					text = row[col.display_prop or col.sort]
+				end
+				if type(text) ~= "table" or not text.is_tstring then
+					text = util.getval(text, row)
+					if type(text) ~= "table" then text = tstring.from(tostring(text)) end
+				end
+
+				if text.is_tstring then
+					gen = self.font:draw(text:toString(), text:maxWidth(self.font), 255, 255, 255)
+				else
+					gen = self.font:draw(text, text:toTString():maxWidth(self.font), 255, 255, 255)
+				end
+
+				row.cells[j]._tex, row.cells[j]._tex_w, row.cells[j]._tex_h, row.cells[j].w, row.cells[j].h = gen[1]._tex, gen[1]._tex_w, gen[1]._tex_h, gen[1].w, gen[1].h
+
+				if row.cells[j].w > col.width - 2 * col.frame_sel.b4.w then
+					row.cells[j].display_offset = { x = 0, x_dir = 0 }
+				end
 			end
-			if type(text) ~= "table" or not text.is_tstring then
-				text = util.getval(text, item)
-				if type(text) ~= "table" then text = tstring.from(tostring(text)) end
+		end
+		if row.cells[j].h > max_h then max_h = row.cells[j].h end
+	end
+	row.h = self.row_height or max_h
+end
+
+function _M:drawRow(row, row_i, nb_keyframes, x, y, total_w, total_h, loffset_x, loffset_y, dest_area)
+	nb_keyframes = (nb_keyframes or 0) * 0.5
+	dest_area = dest_area or self.dest_area
+	local column_w_offset = x
+	local clip_y_start = 0
+	local clip_y_end = 0
+	local clip_x_start = 0
+	local clip_x_end = 0
+	local clip_maxy_start = 0
+	local clip_maxy_end = 0
+	local clip_maxx_start = 0
+	local clip_maxx_end = 0
+	local frame_clip_y = 0
+
+	local one_by_white = 1/255
+
+	for j = 1, #self.columns do
+		col = self.columns[j]
+		clip_y_start = 0
+		clip_y_end = 0
+
+		-- if we only want to display content without being able to select etc.
+		if not self.only_display then
+			if self.sel == row_i then
+				if self.focused then
+					self:drawFrame(col.frame_sel, column_w_offset, y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area)
+				else
+					self:drawFrame(col.frame_usel, column_w_offset, y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area)
+				end
+			else
+				if row.focus_decay then
+					if self.focused then self:drawFrame(col.frame_sel, column_w_offset, y, 1, 1, 1, row.focus_decay * self.one_by_focus_decay, nil, nil, 0, total_h, 0, loffset_y, dest_area)
+					else self:drawFrame(col.frame_usel, column_w_offset, y, 1, 1, 1, row.focus_decay * self.one_by_focus_decay, nil, nil, 0, total_h, 0, loffset_y, dest_area) end
+				else
+					self:drawFrame(col.frame, column_w_offset, y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area)
+				end
 			end
-			local color = item.color or {255,255,255}
 
-			if item.color and type(item.color[1]) == "table" then
-				color = item.color[j]
+			if row.special_bg then
+				local c = row.special_bg
+				if type(c) == "function" then c = c(row) end
+				if c then
+					self:drawFrame(col.frame_special, column_w_offset, y, c.r, c.g, c.b, c.a or 1, nil, nil, 0, total_h, 0, loffset_y, dest_area)
+				end
+			end
+			if total_h < loffset_y then
+				frame_clip_y = loffset_y - total_h
 			end
+		end
+
+		if clip_y_start > clip_maxy_start then clip_maxy_start = clip_y_start end
+
+		if col.direct_draw then
+			_, _, clip_x_start, clip_x_end, clip_y_start, clip_y_end = col.direct_draw(row, column_w_offset, y, col.width, self.row_height, total_w, total_h, loffset_x, loffset_y, dest_area) or 0, 0, 0, 0, 0, 0
+			frame_clip_y = 0
+		elseif row.cells[j]._tex then
+			local center_h = ( (self.row_height and self.row_height or row.cells[j].h) - row.cells[j].h) * 0.5
 
-			local s = col.surface
+			-- if it started before visible area then compute its top clip, take centering into account
+			if total_h + center_h < loffset_y then
+				clip_y_start = loffset_y - total_h - center_h
+				frame_clip_y = center_h
+			end
+			-- if it ended after visible area then compute its bottom clip
+			if total_h + row.cells[j].h + center_h > loffset_y + dest_area.h then
+			   clip_y_end = total_h + row.cells[j].h + center_h - loffset_y - dest_area.h
+			end
 
-			s:erase(0, 0, 0, 0)
-			local test_text = text:toString()
-			local font_w, _ = self.font:size(test_text)
+			-- clip clipping to avoid texture display errors
+			if clip_y_start > row.cells[j].h then clip_y_start = row.cells[j].h end
 
-			if font_w > fw then
-				item.displayx_offset = item.displayx_offset or {}
-				item.displayx_offset[j] = item.displayx_offset[j] or 0
-				item.dir = item.dir or {}
-				item.dir[j] = item.dir[j] or 0
+			local one_by_tex_h = 1 / row.cells[j]._tex_h -- precalculate for using it to multiply instead of division
+			local one_by_tex_w = 1 / row.cells[j]._tex_w
 
-				if item.dir[j] == 0 then
-					item.displayx_offset[j] = item.displayx_offset[j] - nb_keyframes
-					if -item.displayx_offset[j] >= font_w - fw + 15 then
-						item.dir[j] = 1
+			if row.cells[j].display_offset then
+				if self.sel == row_i then
+					-- if we are going right
+					if row.cells[j].display_offset.x_dir == 0 then
+						row.cells[j].display_offset.x = row.cells[j].display_offset.x + nb_keyframes
+					-- if we are going left
+					else
+						row.cells[j].display_offset.x = row.cells[j].display_offset.x - nb_keyframes
 					end
-				elseif item.dir[j] == 1 then
-					item.displayx_offset[j] = item.displayx_offset[j] + nb_keyframes
-					if item.displayx_offset[j] >= 0 then
-						item.dir[j] = 0
+
+					-- if we would see too much to right then clip it and change dir
+					if row.cells[j].display_offset.x >= row.cells[j].w - col.width + 2 * col.frame_sel.b4.w then
+						row.cells[j].display_offset.x_dir = 1
+						row.cells[j].display_offset.x = row.cells[j].w - col.width + 2 * col.frame_sel.b4.w
+					-- if we would see too much to left then clip it and change dir
+					elseif row.cells[j].display_offset.x <= 0 then
+						row.cells[j].display_offset.x_dir = 0
+						row.cells[j].display_offset.x = 0
 					end
+				else
+					row.cells[j].display_offset.x = 0
 				end
-
-				-- We use 1000 and do not cut lines to make sure it draws as much as possible
-				text:drawOnSurface(s, 10000, nil, self.font, col.frame_sel.b4.w+item.displayx_offset[j], (fh - self.font_h) / 2, color[1], color[2], color[3])
-				item.autoscroll = true
+				if self.text_shadow then row.cells[j]._tex:toScreenPrecise(column_w_offset + 1 + col.frame_sel.b4.w, y + 1 + center_h - frame_clip_y, col.width - 2 * col.frame_sel.b4.w, row.cells[j].h - (clip_y_start + clip_y_end), row.cells[j].display_offset.x * one_by_tex_w, (row.cells[j].display_offset.x + col.width - 2 * col.frame_sel.b4.w) * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end
+				row.cells[j]._tex:toScreenPrecise(column_w_offset + col.frame_sel.b4.w, y + center_h - frame_clip_y, col.width - 2 * col.frame_sel.b4.w, row.cells[j].h - (clip_y_start + clip_y_end), row.cells[j].display_offset.x * one_by_tex_w, (row.cells[j].display_offset.x + col.width - 2 * col.frame_sel.b4.w) * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, row.color[1] * one_by_white, row.color[2] * one_by_white, row.color[3] * one_by_white, 1.0 )
 			else
-				text:drawOnSurface(s, 10000, nil, self.font, col.frame_sel.b4.w, (fh - self.font_h) / 2, color[1], color[2], color[3])
+				if self.text_shadow then row.cells[j]._tex:toScreenPrecise(column_w_offset + 1 + col.frame_sel.b4.w, y + 1 + center_h - frame_clip_y, row.cells[j].w, row.cells[j].h - (clip_y_start + clip_y_end), 0, row.cells[j].w * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end
+				row.cells[j]._tex:toScreenPrecise(column_w_offset + col.frame_sel.b4.w, y + center_h - frame_clip_y, row.cells[j].w, row.cells[j].h - (clip_y_start + clip_y_end), 0, row.cells[j].w * one_by_tex_w, clip_y_start * one_by_tex_h, (row.cells[j].h - clip_y_end) * one_by_tex_h, row.color[1] * one_by_white, row.color[2] * one_by_white, row.color[3] * one_by_white, 1.0 )
 			end
-
-			item._tex = item._tex or {}
-			item._tex[j] = {s:glTexture()}
 		end
+		clip_y_start = clip_y_start + frame_clip_y
+		column_w_offset = column_w_offset + col.width
+		if clip_x_start > clip_maxx_start then clip_maxx_start = clip_x_start end
+		if clip_x_end > clip_maxx_end then clip_maxx_end = clip_x_end end
+		if clip_y_start > clip_maxy_start then clip_maxy_start = clip_y_start end
+		if clip_y_end > clip_maxy_end then clip_maxy_end = clip_y_end end
 	end
+	return 0, 0, clip_maxy_start, clip_maxy_end
 end
 
-function _M:generate()
-	self.mouse:reset()
-	self.key:reset()
-
-	self.sel = 1
-	self.scroll = 1
-	self.max = #self.list
-	self:selectColumn(1, true)
-
-	local fh = self.fh
-
-	if not self.h then self.h = self.nb_items * fh end
+function _M:setColumns(columns, force)
+	local w = self.w
+	local col_size = #columns
+	local max_h = 0
 
-	self.max_display = math.floor(self.h / fh) - 1
+	if self.scrollbar then w = w - self.scrollbar.w end
 
-	-- Draw the scrollbar
-	if self.scrollbar then
-		self.scrollbar = Slider.new{size=self.h - fh, max=self.max}
+	for i=1, col_size do
+		local col = columns[i]
+		if type(col.width) == "table" then
+			if col.width[2] == "fixed" then
+				w = w - col.width[1]
+			end
+		end
 	end
-
-	-- Draw the list columns
 	local colx = 0
-	for j, col in ipairs(self.columns) do
-		local fw = col.width
-		col.fw = fw
-		local text = col.name
-		local s = col.surface
+	for i=1, col_size do
+		local col = columns[i]
+		if type(col.width) == "table" then
+			if col.width[2] == "fixed" then
+				col.width = col.width[1]
+			end
+		else
+			col.width = w * col.width * 0.01
+		end
+
+		col.frame = self:makeFrame(nil, col.width, self.row_height)
+		col.frame_special = self:makeFrame("ui/selector", col.width, self.row_height)
+		col.frame_sel = self:makeFrame("ui/selector-sel", col.width, self.row_height)
+		col.frame_usel = self:makeFrame("ui/selector", col.width, self.row_height)
+		col.frame_col = self:makeFrame("ui/heading", col.width, self.row_height)
+		col.frame_col_sel = self:makeFrame("ui/heading-sel", col.width, self.row_height)
 
 		self.font:setStyle("bold")
-		s:erase(0, 0, 0, 0)
-		s:drawColorStringBlended(self.font, text, col.frame_sel.b4.w, (fh - self.font_h) / 2, 255, 255, 255, true, fw - col.frame_sel.b4.w - col.frame_sel.b6.w)
+		local gen = self.font:draw(col.name:toString(), col.width, 255, 255, 255)
 		self.font:setStyle("normal")
 
-		col._tex, col._tex_w, col._tex_h = s:glTexture()
-
-		self.mouse:registerZone(colx, 0, col.width, self.fh, function(button, x, y, xrel, yrel, bx, by, event)
-			if button == "left" and event == "button" then self:selectColumn(j) end
-		end)
+		col._tex, col._tex_w, col._tex_h, col.w, col.h = gen[1]._tex, gen[1]._tex_w, gen[1]._tex_h, gen[1].w, gen[1].h
 		colx = colx + col.width
+		if col.h > max_h then max_h = col.h end
 	end
+	self.max_h_columns = self.hide_columns and 0 or (self.row_height or max_h)
+	self.max_h = self.max_h_columns
 
-	-- Draw the list items
-	for i, item in ipairs(self.list) do self:drawItem(item) end
-
-	-- Add UI controls
-	self.mouse:registerZone(0, self.fh, self.w, self.h - (self.hide_columns and 0 or self.fh), function(button, x, y, xrel, yrel, bx, by, event)
-		if button == "wheelup" and event == "button" then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1)
-		elseif button == "wheeldown" and event == "button" then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end
-
-		if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-		self.sel = util.bound(self.scroll + math.floor(by / self.fh), 1, self.max)
-		self:onSelect()
-		if (self.all_clicks or button == "left") and event == "button" then self:onUse(button, event) end
-		if event == "motion" and button == "left" and self.on_drag then self.on_drag(self.list[self.sel], self.sel) end
-		if button == "drag-end" and self.on_drag_end then self.on_drag_end(self.list[self.sel], self.sel) end
-	end)
-	self.key:addBinds{
-		ACCEPT = function() self:onUse("left", "key") end,
-		MOVE_UP = function()
-			if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-			self.sel = util.boundWrap(self.sel - 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect()
-		end,
-		MOVE_DOWN = function()
-			if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-			self.sel = util.boundWrap(self.sel + 1, 1, self.max) self.scroll = util.scroll(self.sel, self.scroll, self.max_display) self:onSelect()
-		end,
-	}
-	self.key:addCommands{
-		[{"_UP","ctrl"}] = function() self.key:triggerVirtual("MOVE_UP") end,
-		[{"_DOWN","ctrl"}] = function() self.key:triggerVirtual("MOVE_DOWN") end,
-		_HOME = function()
-			if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-			self.sel = 1
-			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
-			self:onSelect()
-		end,
-		_END = function()
-			if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-			self.sel = self.max
-			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
-			self:onSelect()
-		end,
-		_PAGEUP = function()
-			if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-			self.sel = util.bound(self.sel - self.max_display, 1, self.max)
-			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
-			self:onSelect()
-		end,
-		_PAGEDOWN = function()
-			if self.sel and self.list[self.sel] then self.list[self.sel].focus_decay = self.focus_decay_max end
-			self.sel = util.bound(self.sel + self.max_display, 1, self.max)
-			self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
-			self:onSelect()
-		end,
-	}
+	self.sel = 1
+	self:selectColumn(1, true)
+end
 
-	self:onSelect()
+function _M:setList(list, force)
+	if list and #list > 0 and (self.list ~= list or force) then
+		self.list = list
+		self.sel = util.minBound(self.sel, 1, #self.list)
+		local oldcol = self.cur_col
+		self:selectColumn(oldcol or 1, (not oldcol) and true or false, self.sort_reverse)
+
+		self.max_h = self.max_h_columns
+		for i=1, #self.list do
+			local row = self.list[i]
+			if self.focus_decay_max then self.list[i].focus_decay = 0 end
+			self:generateRow(row)
+			self.max_h = self.max_h + row.h
+		end
+	else
+		self.list = {}
+		self.sel = 1
+		self.max_h = self.max_h_columns
+	end
+	if self.scrollbar then
+		self.scrollbar.pos = 0
+		self.scrollbar.max = self.max_h - self.h
+	end
+	self.prevrow = 0
 end
 
-function _M:setList(list)
-	self.list = list
-	self.max = #self.list
-	self.sel = util.bound(self.sel, 1, self.max)
-	self.scroll = util.bound(self.scroll, 1, self.max)
-	self.scroll = util.scroll(self.sel, self.scroll, self.max_display)
+function _M:changeAll(columns, list)
+	self:setColumns(columns)
+	self:setList(list)
+	self:setupInput()
+end
 
-	local oldcol, oldrev = self.cur_col, self.sort_reverse
-	self:selectColumn(oldcol or 1, (not oldcol) and true or false, self.sort_reverse)
+function _M:onSelect(force)
+	if self.only_display then return end
 
-	for i, item in ipairs(self.list) do self:drawItem(item) end
+	local row = self.list[self.sel]
+	-- if not found fall back
+	if not row then return end
+--	self.scroll_inertia = 0
+	if self.on_select then self.on_select(row, self.sel) end
 end
 
-function _M:onSelect(force_refresh)
-	local item = self.list[self.sel]
-	if not item or (not force_refresh and self.previtem and self.previtem==item) then return end
+function _M:removeRow(row_i)
+	table.remove(self.list, row_i)
+end
 
-	if rawget(self, "select") then self.select(item, self.sel) end
---	self.previtem = item
+function _M:appendList(row)
+	self.list[#self.list + 1] = row
+	if (not self.sortable or self.only_display) and not force then return end
+	local col = self.columns[self.cur_col]
+	if self.sortable and not force then
+		local fct = col.sort
+		if type(fct) == "string" then fct = function(a, b) return a[col.sort] < b[col.sort] end end
+		if self.sort_reverse and fct then local old=fct fct = function(a, b) return old(b, a) end end
+		pcall(table.sort, self.list, fct)
+	end
 end
 
 function _M:onUse(...)
-	local item = self.list[self.sel]
-	if not item then return end
+	if #self.list == 0 or self.only_display then return end
+
+	local row = self.list[self.sel]
+	if not row then return end
 	self:sound("button")
-	if item.fct then item:fct(item, self.sel, ...)
-	else self.fct(item, self.sel, ...) end
+	if row.fct then row:fct(row, self.sel, ...)
+	else self.fct(row, self.sel, ...) end
 end
 
 function _M:selectColumn(i, force, reverse)
-	if not self.sortable and not force then return end
+	if (not self.sortable or self.only_display) and not force then return end
 	local col = self.columns[i]
 	if not col then return end
 
@@ -273,7 +465,7 @@ function _M:selectColumn(i, force, reverse)
 	else
 		self.sort_reverse = not self.sort_reverse
 	end
-	if type(reverse) == "boolean" then self.sort_reverse = reverse end
+	self.sort_reverse = reverse
 
 	if self.sortable and not force then
 		local fct = col.sort
@@ -283,75 +475,157 @@ function _M:selectColumn(i, force, reverse)
 	end
 end
 
-function _M:display(x, y, nb_keyframes, screen_x, screen_y)
+function _M:display(x, y, nb_keyframes, screen_x, screen_y, offset_x, offset_y, local_x, local_y)
 	self.last_display_x = screen_x
 	self.last_display_y = screen_y
 
-	local bx, by = x, y
-	if self.sel then
-		local item = self.list[self.sel]
-		if self.previtem and self.previtem~=item then
-			self.previtem.displayx_offset = {}
-			self:drawItem(self.previtem)
-			self.previtem = nil
-		end
-		if item and item.autoscroll then
-			self:drawItem(item, nb_keyframes)
-			self.previtem = item
-		end
-	end
+	nb_keyframes = nb_keyframes or 0
+	offset_x = offset_x and offset_x or 0
+	local row = 0
 
-	for j = 1, #self.columns do
-		local col = self.columns[j]
-		local y = y
-		if not self.hide_columns then
-			if self.cur_col == j then self:drawFrame(col.frame_col, x, y)
-			else self:drawFrame(col.frame_col_sel, x, y) end
-			col._tex:toScreenFull(x, y, col.fw, self.fh, col._tex_w, col._tex_h)
-			y = y + self.fh
+	if self.scrollbar then
+		self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.scroll_inertia, 0, self.scrollbar.max)
+		if self.scroll_inertia > 0 then self.scroll_inertia = math.max(self.scroll_inertia - 1, 0)
+		elseif self.scroll_inertia < 0 then self.scroll_inertia = math.min(self.scroll_inertia + 1, 0)
 		end
+		if self.scrollbar.pos == 0 or self.scrollbar.pos == self.scrollbar.max then self.scroll_inertia = 0 end
+	end
 
-		local max = math.min(self.scroll + self.max_display - 1, self.max)
-		for i = self.scroll, max do
-			local item = self.list[i]
-			if not item then break end
+	-- if we used keyboard then match display to input
+	if self.scrollbar and self.last_input_was_keyboard then
+		local columns_h = self.hide_columns and 0 or self.max_h_columns
+		local mul = self.floating_headers and 2 or 1 -- if we have floating headers we have to take them into account while scrolling
+		local pos = self.hide_columns and 0 or self.max_h_columns
+		for i = 1, #self.list do
+			row = self.list[i]
+			pos = pos + row.h
+			-- we've reached selected row
 			if self.sel == i then
-				if self.focused then self:drawFrame(col.frame_sel, x, y)
-				else self:drawFrame(col.frame_usel, x, y) end
-			else
-				self:drawFrame(col.frame, x, y)
-				if item.focus_decay then
-					if self.focused then self:drawFrame(col.frame_sel, x, y, 1, 1, 1, item.focus_decay / self.focus_decay_max_d)
-					else self:drawFrame(col.frame_usel, x, y, 1, 1, 1, item.focus_decay / self.focus_decay_max_d) end
-					item.focus_decay = item.focus_decay - nb_keyframes
-					if item.focus_decay <= 0 then item.focus_decay = nil end
+				-- check if it was visible if not go scroll over there
+				if pos - mul * columns_h < self.scrollbar.pos then self.scrollbar.pos = util.minBound(pos - mul * columns_h, 0, self.scrollbar.max)
+				elseif pos + columns_h > self.scrollbar.pos + self.h then self.scrollbar.pos = util.minBound(pos - self.h, 0, self.scrollbar.max)
 				end
+				break
 			end
+		end
+	end
+	offset_y = offset_y and offset_y or (self.scrollbar and self.scrollbar.pos or 0)
+	local_x = local_x and local_x or 0
+	local_y = local_y and local_y or 0
+
+	local loffset_y = offset_y - local_y
+	local current_y = 0
+	local current_x = 0
+	local total_h = 0
+	local clip_x_start = 0
+	local clip_x_end = 0
+	local clip_y_start = 0
+	local clip_y_end = 0
+	local frame_clip_y = 0
+	local dest_area = {}
+	dest_area.h, dest_area.fixed = self.dest_area.h, self.dest_area.fixed
+	local frame_clip_y_start, frame_clip_y_end
+
+	local max_h = 0
+	if not self.hide_columns then
+		for j = 1, #self.columns do
+			clip_y_end = 0
+			local col = self.columns[j]
+			local center_h = ( (self.row_height and self.row_height or col.h) - col.h) * 0.5
+
+			if self.floating_headers then
+				if self.cur_col == j then self:drawFrame(col.frame_col, x + current_x, y + current_y)
+				else self:drawFrame(col.frame_col_sel, x + current_x, y + current_y) end
+				local one_by_tex_h = 1 / col._tex_h
+				col._tex:toScreenPrecise(x + current_x + col.frame_sel.b4.w, y + current_y + center_h, col.w, col.h - (clip_y_start + clip_y_end), 0, col.w / col._tex_w, clip_y_start * one_by_tex_h, (col.h - clip_y_end) * one_by_tex_h )
+			elseif total_h + self.max_h_columns > loffset_y and total_h < loffset_y + dest_area.h then
+
+				if self.cur_col == j then _, _, frame_clip_y_start, frame_clip_y_end = self:drawFrame(col.frame_col, x + current_x, y + current_y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area)
+				else _, _, frame_clip_y_start, frame_clip_y_end = self:drawFrame(col.frame_col_sel, x + current_x, y + current_y, nil, nil, nil, nil, nil, nil, 0, total_h, 0, loffset_y, dest_area) end
+				self.mouse:updateZone(("column header%d"):format(j), current_x, current_y, col.width, self.row_height - frame_clip_y_start - frame_clip_y_end)
+
+				if self.only_display then frame_clip_y = center_h else frame_clip_y = loffset_y - total_h end
+
+				-- if its visible then compute how much of it needs to be clipped, take centering into account
+				if total_h + center_h < loffset_y then
+					clip_y_start = loffset_y - total_h - center_h
+					frame_clip_y = center_h
+				end
 
-			if item.special_bg then
-				local c = item.special_bg
-				if type(c) == "function" then c = c(item) end
-				if c then
-					self:drawFrame(col.frame_special, x, y, c.r, c.g, c.b, c.a or 1)
+				-- if it ended after visible area then compute its bottom clip
+				if total_h + col.h  + center_h > loffset_y + dest_area.h then
+				   clip_y_end = total_h + col.h + center_h - loffset_y - dest_area.h
+				end
+				local one_by_tex_h = 1 / col._tex_h
+				if total_h + col.h > loffset_y and total_h < loffset_y + dest_area.h then
+					col._tex:toScreenPrecise(x + current_x + col.frame_sel.b4.w, y + current_y + center_h - frame_clip_y, col.w, col.h - (clip_y_start + clip_y_end), 0, col.w / col._tex_w, clip_y_start * one_by_tex_h, (col.h - clip_y_end) * one_by_tex_h )
 				end
 			end
 
-			if col.direct_draw then
-				col.direct_draw(item, x, y, col.fw, self.fh)
-			elseif item._tex then
-				if self.text_shadow then item._tex[j][1]:toScreenFull(x+1, y+1, col.fw, self.fh, item._tex[j][2], item._tex[j][3], 0, 0, 0, self.text_shadow) end
-				item._tex[j][1]:toScreenFull(x, y, col.fw, self.fh, item._tex[j][2], item._tex[j][3])
-			end
-			item.last_display_x = screen_x + (x - bx)
-			item.last_display_y = screen_y + (y - by)
-			y = y + self.fh
+			if col.h > max_h then max_h = col.h end
+			current_x = current_x + col.width
 		end
+		max_h = self.row_height or max_h
+
+		if self.floating_headers then
+			dest_area.h = dest_area.h - max_h
+			current_y = current_y + max_h
+			loffset_y = loffset_y + max_h
+		elseif total_h + max_h > loffset_y and total_h < loffset_y + dest_area.h then
+			current_y = current_y + max_h
+			if not self.only_display then current_y = current_y + total_h - loffset_y end
+		end
+		total_h = total_h + max_h
+	end
+
+	local list_start_y = current_y
+	self.mouse:updateZone("list area", 0, current_y, self.w, self.h - current_y)
 
-		x = x + col.width
+	-- if list is empty then display only column headers and fall back
+	if  #self.list == 0 then return end
+
+	-- refresh focus decay if any
+	if self.focus_decay_max then self.list[self.sel].focus_decay = self.focus_decay_max end
+
+	-- if we are too deep then end this
+	if total_h > loffset_y + dest_area.h then return end
+	for i = 1, #self.list do
+		row = self.list[i]
+		-- if its visible then draw it
+		if total_h + row.h > loffset_y and total_h < loffset_y + dest_area.h then
+			_, _, clip_y_start, clip_y_end = self:drawRow(row, i, nb_keyframes, x, y + current_y, 0, total_h, 0, loffset_y, dest_area)
+
+			row.last_display_x = screen_x
+			row.last_display_y = screen_y + current_y
+
+			-- use display loop to determine which row is selected
+			if not self.last_input_was_keyboard and self.mouse_pos.y + list_start_y> current_y and self.mouse_pos.y + list_start_y< current_y + row.h - clip_y_start then self.sel = i end
+			current_y = current_y + row.h - clip_y_start
+		end
+
+		-- decay focus if any
+		if row.focus_decay then
+			row.focus_decay = row.focus_decay - nb_keyframes
+			if row.focus_decay <= 0 then row.focus_decay = nil end
+		end
+		-- add full size of row
+		total_h = total_h + row.h
+		-- if we are too deep then end this
+		if total_h > loffset_y + dest_area.h then break end
+	end
+
+	-- show scrollbar only if there is one, total size of UI element is greater than visible one and only_display switch is not set
+	if self.focused and self.scrollbar and self.max_h > self.h and not self.only_display then
+		if self.hide_columns then
+			self.scrollbar:display(x + self.w - self.scrollbar.w, y)
+		else
+			self.scrollbar:display(x + self.w - self.scrollbar.w, y + max_h)
+		end
 	end
 
-	if self.focused and self.scrollbar then
-		self.scrollbar.pos = self.sel
-		self.scrollbar:display(bx + self.w - self.scrollbar.w, by + self.fh)
+	-- if row was changed then refresh it
+	if self.prevrow ~= self.sel then
+		self.prevrow = self.sel
+		if not self.click_select then self:onSelect() end
 	end
 end
diff --git a/game/engines/default/engine/ui/Separator.lua b/game/engines/default/engine/ui/Separator.lua
index 0e1553afcb05555380639081cbc26f40f6753505..789c36bd528220c556e36f8ce36c44f0df384de7 100644
--- a/game/engines/default/engine/ui/Separator.lua
+++ b/game/engines/default/engine/ui/Separator.lua
@@ -27,6 +27,7 @@ function _M:init(t)
 	self.dir = assert(t.dir, "no separator dir")
 	self.size = assert(t.size, "no separator size")
 
+	self.dest_area = {w = 1, h = 1}
 	Base.init(self, t)
 end
 
@@ -36,26 +37,70 @@ function _M:generate()
 		self.middle = self:getUITexture("ui/border_vert_middle.png")
 		self.bottom = self:getUITexture("ui/border_vert_bottom.png")
 		self.w, self.h = self.middle.w, self.size
+		
 	else
 		self.left = self:getUITexture("ui/border_hor_left.png")
 		self.middle = self:getUITexture("ui/border_hor_middle.png")
 		self.right = self:getUITexture("ui/border_hor_right.png")
 		self.w, self.h = self.size, self.middle.h
 	end
+	self.dest_area.w = self.w
+	self.dest_area.h = self.h
 end
 
-function _M:display(x, y)
+function _M:display(x, y, total_w, nb_keyframes, ox, oy, total_h, loffset_x, loffset_y, dest_area)
+	dest_area = dest_area or self.dest_area
+	loffset_x = loffset_x and loffset_x or 0
+	loffset_y = loffset_y and loffset_y or 0
+	total_w = total_w and total_w or 0
+	total_h = total_h and total_h or 0
+	
+	local clip_y_start = 0
+	local clip_y_end = 0
+	local clip_x_start = 0
+	local clip_x_end = 0
+
+	if total_h < loffset_y then clip_y_start = loffset_y - total_h end
+	
 	if self.dir == "horizontal" then
---		x = x - math.floor(self.top.w / 2)
---		y = y - math.floor(self.top.h / 2)
-		self.top.t:toScreenFull(x, y, self.top.w, self.top.h, self.top.tw, self.top.th)
-		self.bottom.t:toScreenFull(x, y + self.h - self.bottom.h, self.bottom.w, self.bottom.h, self.bottom.tw, self.bottom.th)
-		self.middle.t:toScreenFull(x, y + self.top.h, self.middle.w, self.h - self.top.h - self.bottom.h, self.middle.tw, self.middle.th)
+		if total_h + self.top.h > loffset_y and total_h < loffset_y + dest_area.h then
+			if total_h + self.top.h > loffset_y + dest_area.h then clip_y_end = total_h + self.top.h - loffset_y - dest_area.h end
+			local one_by_tex_h = 1 / self.top.th
+			self.top.t:toScreenPrecise(x, y, self.top.w, self.top.h - clip_y_start - clip_y_end, 0, self.top.w / self.top.tw, clip_y_start * one_by_tex_h, (self.top.h - clip_y_end) * one_by_tex_h)
+		end
+		clip_y_end = 0
+		
+		if total_h + self.bottom.h > loffset_y and total_h < loffset_y + dest_area.h then
+			if total_h + self.bottom.h > loffset_y + dest_area.h then clip_y_end = total_h + self.bottom.h - loffset_y - dest_area.h end
+			local one_by_tex_h = 1 / self.bottom.th
+			self.bottom.t:toScreenPrecise(x, y + self.h - self.bottom.h, self.bottom.w, self.bottom.h - clip_y_start - clip_y_end, 0, self.bottom.w / self.bottom.tw, clip_y_start * one_by_tex_h, (self.bottom.h - clip_y_end) * one_by_tex_h)
+		end
+		clip_y_end = 0
+		
+		if total_h + self.middle.h > loffset_y and total_h < loffset_y + dest_area.h then
+			if total_h + self.middle.h > loffset_y + dest_area.h then clip_y_end = total_h + self.middle.h - loffset_y - dest_area.h end
+			local one_by_tex_h = 1 / self.middle.th
+			self.middle.t:toScreenPrecise(x, y + self.top.h, self.middle.w, self.h - self.top.h - self.bottom.h - clip_y_start - clip_y_end, 0, self.middle.w / self.middle.tw, clip_y_start * one_by_tex_h, (self.h - self.top.h - self.bottom.h - clip_y_end) * one_by_tex_h)
+		end
 	else
---		x = x - math.floor(self.left.w / 2)
---		y = y - math.floor(self.left.h / 2)
-		self.left.t:toScreenFull(x, y, self.left.w, self.left.h, self.left.tw, self.left.th)
-		self.right.t:toScreenFull(x + self.w - self.right.w, y, self.right.w, self.right.h, self.right.tw, self.right.th)
-		self.middle.t:toScreenFull(x + self.left.w, y, self.w - self.left.w - self.right.w, self.middle.h, self.middle.tw, self.middle.th)
+		if total_h + self.left.h > loffset_y and total_h < loffset_y + dest_area.h then
+			if total_h + self.left.h > loffset_y + dest_area.h then clip_y_end = total_h + self.left.h - loffset_y - dest_area.h end
+			local one_by_tex_h = 1 / self.left.th
+			self.left.t:toScreenPrecise(x, y, self.left.w, self.left.h - clip_y_start - clip_y_end, 0, self.left.w / self.left.tw, clip_y_start * one_by_tex_h, (self.left.h - clip_y_end) * one_by_tex_h)
+		end
+		clip_y_end = 0
+		
+		if total_h + self.right.h > loffset_y and total_h < loffset_y + dest_area.h then
+			if total_h + self.right.h > loffset_y + dest_area.h then clip_y_end = total_h + self.right.h - loffset_y - dest_area.h end
+			local one_by_tex_h = 1 / self.right.th
+			self.right.t:toScreenPrecise(x + self.w - self.right.w, y, self.right.w, self.right.h - clip_y_start - clip_y_end, 0, self.right.w / self.right.tw, clip_y_start * one_by_tex_h, (self.right.h - clip_y_end) * one_by_tex_h)
+		end
+		clip_y_end = 0
+		
+		if total_h + self.middle.h > loffset_y and total_h < loffset_y + dest_area.h then
+			if total_h + self.middle.h > loffset_y + dest_area.h then clip_y_end = total_h + self.middle.h - loffset_y - dest_area.h end
+			local one_by_tex_h = 1 / self.middle.th
+			self.middle.t:toScreenPrecise(x + self.left.w, y, self.w - self.left.w - self.right.w, self.middle.h - clip_y_start - clip_y_end, 0, (self.w - self.left.w - self.right.w) / self.middle.tw, clip_y_start * one_by_tex_h, (self.middle.h - clip_y_end) * one_by_tex_h)
+		end
 	end
 end
diff --git a/game/engines/default/engine/ui/Slider.lua b/game/engines/default/engine/ui/Slider.lua
index a32d92cc2424ad7942cde4256360e39b1659b108..632d3e92e99020d8125ae7a85eb168ccd21cc7c9 100644
--- a/game/engines/default/engine/ui/Slider.lua
+++ b/game/engines/default/engine/ui/Slider.lua
@@ -24,10 +24,11 @@ local Base = require "engine.ui.Base"
 module(..., package.seeall, class.inherit(Base))
 
 function _M:init(t)
-	self.size = assert(t.size, "no slider size")
+	self.h = assert(t.size, "no slider size")
 	self.max = assert(t.max, "no slider max")
 	self.pos = t.pos or 0
 	self.inverse = t.inverse
+	self.pos = util.minBound(self.pos, 0, self.max)
 
 	Base.init(self, t)
 end
@@ -37,18 +38,19 @@ function _M:generate()
 	self.middle = self:getUITexture("ui/scrollbar.png")
 	self.bottom = self:getUITexture("ui/scrollbar_bottom.png")
 	self.sel = self:getUITexture("ui/scrollbar-sel.png")
-	self.w, self.h = self.middle.w, self.size
+	self.w = self.middle.w
+	self.pos = util.minBound(self.pos, 0, self.max)
 end
 
 function _M:display(x, y)
 	self.top.t:toScreenFull(x, y, self.top.w, self.top.h, self.top.tw, self.top.th)
 	self.bottom.t:toScreenFull(x, y + self.h - self.bottom.h, self.bottom.w, self.bottom.h, self.bottom.tw, self.bottom.th)
 	self.middle.t:toScreenFull(x, y + self.top.h, self.middle.w, self.h - self.top.h - self.bottom.h, self.middle.tw, self.middle.th)
-	self.pos = util.bound(self.pos, 0, self.max)
+	self.pos = util.minBound(self.pos, 0, self.max)
 	if self.inverse then
-		y = y + self.h - (self.pos * self.size / self.max) + self.sel.h / 2
+		y = y + self.h - (self.pos / self.max) * (self.h - self.bottom.h - self.top.h - self.sel.h * 0.5) + self.sel.h * 0.5
 	else
-		y = y + (self.pos * self.size / self.max) + self.sel.h / 2
+		y = y + (self.pos / self.max) * (self.h - self.bottom.h - self.top.h - self.sel.h * 0.5) + self.sel.h * 0.5
 	end
-	self.sel.t:toScreenFull(x - (self.sel.w - self.top.w) / 2, y, self.sel.w, self.sel.h, self.sel.tw, self.sel.th)
+	self.sel.t:toScreenFull(x - (self.sel.w - self.top.w) * 0.5, y, self.sel.w, self.sel.h, self.sel.tw, self.sel.th)
 end
diff --git a/game/engines/default/engine/ui/Textzone.lua b/game/engines/default/engine/ui/Textzone.lua
index 69a25cf460e0fa05c5b90ec36ac4295c4a4d3f9d..ac8911c6c5a7dcc6a441b32910ef494aef762f37 100644
--- a/game/engines/default/engine/ui/Textzone.lua
+++ b/game/engines/default/engine/ui/Textzone.lua
@@ -27,17 +27,21 @@ module(..., package.seeall, class.inherit(Base, Focusable))
 
 function _M:init(t)
 	self.text = tostring(assert(t.text, "no textzone text"))
+	
+	if t.auto_height then t.height = 1 end
 	if t.auto_width then t.width = 1 end
+	
 	self.w = assert(t.width, "no list width")
-	if t.auto_height then t.height = 1 end
 	self.h = assert(t.height, "no list height")
 	self.scrollbar = t.scrollbar
-	self.no_color_bleed = t.no_color_bleed
 	self.auto_height = t.auto_height
 	self.auto_width = t.auto_width
+	
+	self.dest_area = t.dest_area and t.dest_area or { h = self.h }
+	
 	self.color = t.color or {r=255, g=255, b=255}
-
-	if self.auto_width then self.w = 10000 end
+	self.can_focus = false
+	self.scroll_inertia = 0
 
 	Base.init(self, t)
 end
@@ -45,65 +49,118 @@ end
 function _M:generate()
 	self.mouse:reset()
 	self.key:reset()
-
-	local text, max_lines, max_w = self.font:draw(self.text:toString(), self.w, self.color.r, self.color.g, self.color.b)
-	if self.auto_width then
-		self.w = max_w
+	
+	if self.scrollbar then 
+		self.can_focus = true
+		self.scrollbar = Slider.new{size=self.h, max=1} 
 	end
-	self.scroll = 1
-	self.max = max_lines
-
-	local fw, fh = self.w, self.font_h
-	self.fw, self.fh = fw, fh
 
-	if self.auto_height then self.h = self.fh * max_lines end
+	local gen, max_lines, max_w = self.font:draw(self.text, self.auto_width and self.text:toTString():maxWidth(self.font) or (self.scrollbar and self.w - self.scrollbar.w or self.w), self.color.r, self.color.g, self.color.b)
+	if self.auto_width then self.w = max_w end
+	
+	self.max = max_lines
 
-	self.max_display = math.floor(self.h / self.fh)
-	self.can_focus = false
-	if self.scrollbar and (self.max_display < self.max) then
-		self.can_focus = true
+	if self.auto_height then 
+		self.h = self.font_h * max_lines 
+		self.dest_area.h = self.h 
 	end
 
-	-- Draw the list items
-	self.list = text
+	self.max_display = max_lines * self.font_h
+	self.list = gen
 
-	-- Draw the scrollbar
-	if self.scrollbar then
-		self.scrollbar = Slider.new{size=self.h - fh, max=self.max - self.max_display + 1}
-	end
+	if self.scrollbar then self.scrollbar.max=self.max_display - self.h end
 
 	-- Add UI controls
 	self.mouse:registerZone(0, 0, self.w, self.h, function(button, x, y, xrel, yrel, bx, by, event)
 		if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP")
 		elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN")
 		end
+		if button == "middle" and self.scrollbar then
+			if not self.scroll_drag then
+				self.scroll_drag = true
+				self.scroll_drag_x_start = bx
+				self.scroll_drag_y_start = by
+			else
+				self.scrollbar.pos = util.minBound(self.scrollbar.pos + by - self.scroll_drag_y_start, 0, self.scrollbar.max)
+				self.scroll_drag_x_start = bx
+				self.scroll_drag_y_start = by
+			end
+		else
+			self.scroll_drag = false
+		end
 	end)
+	
 	self.key:addBinds{
-		MOVE_UP = function() self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1) end,
-		MOVE_DOWN = function() self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end,
+		MOVE_UP = function() if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 10  end end,
+		MOVE_DOWN = function() if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 10  end end,
+	}
+	
+	self.key:addCommands{
+		_HOME = function() if self.scrollbar then self.scrollbar.pos = 0 end end,
+		_END = function() if self.scrollbar then self.scrollbar.pos = self.scrollbar.max end end,
+		_PAGEUP = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos - self.h, 0, self.scrollbar.max) end end,
+		_PAGEDOWN = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.h, 0, self.scrollbar.max) end end,
 	}
 end
 
-function _M:spawn(t)
-	local n = self:cloneFull()
-	for k, e in pairs(t) do n[k] = e end
-	n:generate()
-	return n
-end
-
-function _M:display(x, y)
-	local bx, by = x, y
-	local max = math.min(self.scroll + self.max_display - 1, self.max)
-	for i = self.scroll, max do
+function _M:display(x, y, nb_keyframes, screen_x, screen_y, offset_x, offset_y, local_x, local_y)
+	if not self.list then return end
+	offset_x = offset_x and offset_x or 0
+	offset_y = (offset_y and offset_y) or (self.scrollbar and self.scrollbar.pos or 0)
+	local_x = local_x and local_x or 0
+	local_y = local_y and local_y or 0
+	
+	local loffset_y = offset_y - local_y
+	local current_y = 0
+	local current_x = 0
+	local total_h = 0
+	local clip_y_start = 0
+	local clip_y_end = 0
+	
+	if self.scrollbar then
+		self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.scroll_inertia, 0, self.scrollbar.max)
+		if self.scroll_inertia > 0 then self.scroll_inertia = math.max(self.scroll_inertia - 1, 0)
+		elseif self.scroll_inertia < 0 then self.scroll_inertia = math.min(self.scroll_inertia + 1, 0)
+		end
+		if self.scrollbar.pos == 0 or self.scrollbar.pos == self.scrollbar.max then self.scroll_inertia = 0 end
+	end
+	
+	local scroll_w = 0
+	if self.focused and self.scrollbar and self.h < self.max_display then
+		scroll_w = self.w
+	end
+	
+	for i = 1, #self.list do
 		local item = self.list[i]
-		if not item then break end
-		if self.text_shadow then item._tex:toScreenFull(x+1, y+1, item.w, item.h, item._tex_w, item._tex_h, 0, 0, 0, self.text_shadow) end
-		item._tex:toScreenFull(x, y, item.w, item.h, item._tex_w, item._tex_h)
-		y = y + self.fh
+		clip_y_start = 0
+		clip_y_end = 0
+		
+		-- if item is within visible area bounds
+		if total_h + item.h > loffset_y and total_h < loffset_y + self.dest_area.h then
+			-- if it started before visible area then compute its top clip
+			if total_h < loffset_y then 
+				clip_y_start = loffset_y - total_h 
+			end
+			-- if it ended after visible area then compute its bottom clip
+			if total_h + item.h > loffset_y + self.dest_area.h then 
+			   clip_y_end = total_h + item.h - (loffset_y + self.dest_area.h)
+			end
+			if item.background then
+				core.display.drawQuad(x + current_x, y + current_y, item._tex_w, item.h - (clip_y_start + clip_y_end), item.background[1], item.background[2], item.background[3], item.background[4])
+			end
+
+			local one_by_tex_h = 1 / item._tex_h
+			if self.text_shadow then item._tex:toScreenPrecise(x + current_x + 1, y + current_y + 1, item.w, item.h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item.h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end
+			item._tex:toScreenPrecise(x + current_x, y + current_y, item.w, item.h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item.h - clip_y_end) * one_by_tex_h )
+			-- add only visible part of item
+			current_y = current_y + item.h - clip_y_start
+		end
+		-- add full size of item
+		total_h = total_h + item.h
+		-- if we are too deep then end this
+		if total_h > loffset_y + self.dest_area.h then break end
 	end
-
-	if self.focused and self.scrollbar then
-		self.scrollbar.pos = self.scroll
-		self.scrollbar:display(bx + self.w - self.scrollbar.w, by)
+	if self.focused and self.scrollbar and self.h < self.max_display then
+		self.scrollbar:display(x + self.w - self.scrollbar.w, y)
 	end
 end
diff --git a/game/engines/default/engine/ui/TextzoneList.lua b/game/engines/default/engine/ui/TextzoneList.lua
index c8ba182a5ec1e267ca2bc54cb95fa24e723a5b06..9711463f70526be8f51a9db5fb41deee25eff36b 100644
--- a/game/engines/default/engine/ui/TextzoneList.lua
+++ b/game/engines/default/engine/ui/TextzoneList.lua
@@ -31,21 +31,26 @@ function _M:init(t)
 	if t.weakstore then setmetatable(self.items, {__mode="k"}) end
 	self.cur_item = 0
 	self.w = assert(t.width, "no list width")
-	if t.auto_height then t.height = 1 end
 	self.h = assert(t.height, "no list height")
 	self.scrollbar = t.scrollbar
-	self.no_color_bleed = t.no_color_bleed
 	self.variable_height = t.variable_height
-
-	if self.scrollbar then
-		self.can_focus = true
-	end
+	
+	self.dest_area = t.dest_area and t.dest_area or { h = self.h }
+	self.max_h = 0
+	self.scroll_inertia = 0
+	
+	if self.scrollbar then self.can_focus = true end
 
 	Base.init(self, t)
 
 	self.sep = Separator.new{dir="vertical", size=self.w, ui=self.ui}
 end
 
+function _M:erase()
+	self.surface:erase(0,0,0,0)
+	self.surface:updateTexture(self.texture)
+end
+
 function _M:generate()
 	self.mouse:reset()
 	self.key:reset()
@@ -54,29 +59,53 @@ function _M:generate()
 
 	-- Draw the scrollbar
 	if self.scrollbar then
-		self.scrollbar = Slider.new{size=self.h - self.font_h, max=1}
+		self.scrollbar = Slider.new{size=self.h, max=1}
 	end
 
 	-- Add UI controls
-	self.mouse:registerZone(0, 0, self.w, self.h, function(button, x, y, xrel, yrel, bx, by, event)
+	local on_mousewheel = function(button, x, y, xrel, yrel, bx, by, event)
 		if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP")
 		elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN")
 		end
-	end)
+		if button == "middle" and self.scrollbar then
+			if not self.scroll_drag then
+				self.scroll_drag = true
+				self.scroll_drag_x_start = bx
+				self.scroll_drag_y_start = by
+			else
+				self.scrollbar.pos = util.minBound(self.scrollbar.pos + by - self.scroll_drag_y_start, 0, self.scrollbar.max)
+				self.scroll_drag_x_start = bx
+				self.scroll_drag_y_start = by
+			end
+		else
+			self.scroll_drag = false
+		end
+	end
+	
+	self.mouse:registerZone(0, 0, self.w, self.h, on_mousewheel)
 	self.key:addBinds{
-		MOVE_UP = function() if self.scroll then self.scroll = util.bound(self.scroll - 1, 1, self.max - self.max_display + 1) end end,
-		MOVE_DOWN = function() if self.scroll then self.scroll = util.bound(self.scroll + 1, 1, self.max - self.max_display + 1) end end,
+		MOVE_UP = function() if self.scrollbar then self.scroll_inertia = math.min(self.scroll_inertia, 0) - 10  end end,
+		MOVE_DOWN = function() if self.scrollbar then self.scroll_inertia = math.max(self.scroll_inertia, 0) + 10  end end
+	}
+	
+	self.key:addCommands{
+		_HOME = function() if self.scrollbar then self.scrollbar.pos = 0 end end,
+		_END = function() if self.scrollbar then self.scrollbar.pos = self.scrollbar.max end end,
+		_PAGEUP = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos - self.h, 0, self.scrollbar.max) end end,
+		_PAGEDOWN = function() if self.scrollbar then self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.h, 0, self.scrollbar.max) end end,
 	}
 end
 
 function _M:createItem(item, text)
-	local old_style = self.font:getStyle()
-    
-	local max_display = math.floor(self.h / self.fh)
-
+	local gen = {}
 	-- Draw the list items
-	local gen = self.font:draw(text:toString(), self.fw, 255, 255, 255)
+	if self.scrollbar then
+		gen = self.font:draw(text:toString(), self.fw - self.scrollbar.w , 255, 255, 255)
+	else
+		gen = self.font:draw(text:toString(), self.fw, 255, 255, 255) -- bug with UIDs displaying
+	end
 
+	self.max_h = 0
 	for i = 1, #gen do
 		if gen[i].line_extra then
 			if gen[i].line_extra:sub(1, 7) == "linebg:" then
@@ -90,31 +119,33 @@ function _M:createItem(item, text)
 				end
 			end
 		end
+		if gen[i].is_separator then 
+			self.max_h = self.max_h + (self.fh - self.sep.h)
+		else 
+			self.max_h = self.max_h + gen[i].h
+		end
 	end
 
 	local max = #gen
 	if self.variable_height then
-		self.h = max * self.fh
-		max_display = max
+		self.h = self.max_h
+		if not self.dest_area.fixed then self.dest_area.h = self.max_h end
 	end
-	self.items[item] = {
-		list = gen,
-		scroll = 1,
-		max = max,
-		max_display = max_display,
-	}
-	self.font:setStyle(old_style)
+	self.items[item] = { list = gen, max_h = self.max_h }
 end
 
-function _M:switchItem(item, create_if_needed)
-	self.cur_item = item
-	if create_if_needed then if not self.items[item] then self:createItem(item, create_if_needed) end end
+function _M:switchItem(item, create_if_needed, force)
+	if self.cur_item == item and not force then return true end
+	if create_if_needed and not self.items[item] then self:createItem(item, create_if_needed) end
 	if not item or not self.items[item] then self.list = nil return false end
 	local d = self.items[item]
-
-	self.scroll = d.scroll
+	
+	self.max_h = d.max_h
+	if self.scrollbar then
+		self.scrollbar.max = self.max_h - self.h
+		self.scrollbar.pos = 0
+	end
 	self.list = d.list
-	self.max = d.max
 	self.max_display = d.max_display
 	self.cur_item = item
 	return true
@@ -125,31 +156,67 @@ function _M:erase()
 	self.items = {}
 end
 
-function _M:display(x, y)
+--@param x, y - x, y position of displaying
+--@param nb_keyframes -
+--@param ox, oy -
+--@param offset_x, offset_y - offset values of UI element relative to its parent
+--@param local_x, local_y - local starting values of UI element relative to its parent
+function _M:display(x, y, nb_keyframes, ox, oy, offset_x, offset_y, local_x, local_y)
 	if not self.list then return end
-
-	local bx, by = x, y
-	local max = math.min(self.scroll + self.max_display - 1, self.max)
-	for i = self.scroll, max do
-		local item = self.list[i]
-		if not item then break end
-
-		if item.background then
-			core.display.drawQuad(x, y, self.fw, self.fh, item.background[1], item.background[2], item.background[3], item.background[4])
+	offset_x = offset_x and offset_x or 0
+	offset_y = offset_y and offset_y or (self.scrollbar and self.scrollbar.pos or 0)
+	local_x = local_x and local_x or 0
+	local_y = local_y and local_y or 0
+	
+	if self.scrollbar then
+		self.scrollbar.pos = util.minBound(self.scrollbar.pos + self.scroll_inertia, 0, self.scrollbar.max)
+		if self.scroll_inertia > 0 then self.scroll_inertia = math.max(self.scroll_inertia - 1, 0)
+		elseif self.scroll_inertia < 0 then self.scroll_inertia = math.min(self.scroll_inertia + 1, 0)
 		end
-
-		if item.is_separator then
-			self.sep:display(x, y + (self.fh - self.sep.h) / 2)
-		else
-			if self.text_shadow then item._tex:toScreenFull(x+1, y+1, item.w, item.h, item._tex_w, item._tex_h, 0, 0, 0, self.text_shadow) end
-			item._tex:toScreenFull(x, y, item.w, item.h, item._tex_w, item._tex_h)
+		if self.scrollbar.pos == 0 or self.scrollbar.pos == self.scrollbar.max then self.scroll_inertia = 0 end
+	end
+	
+	local loffset_y = offset_y - local_y
+	local current_y = 0
+	local current_x = 0
+	local total_h = 0
+	local clip_y_start = 0
+	local clip_y_end = 0
+	for i = 1, #self.list do
+		local item = self.list[i]
+		clip_y_start = 0
+		clip_y_end = 0
+		
+		local item_h = item.is_separator and (self.fh - self.sep.h) or item.h
+		-- if item is within visible area bounds
+		if total_h + item_h > loffset_y and total_h < loffset_y + self.dest_area.h then
+			-- if it started before visible area then compute its top clip
+			if total_h < loffset_y then 
+				clip_y_start = loffset_y - total_h 
+			end
+			-- if it ended after visible area then compute its bottom clip
+			if total_h + item_h > loffset_y + self.dest_area.h then 
+			   clip_y_end = total_h + item_h - (loffset_y + self.dest_area.h)
+			end
+			if item.background then
+				core.display.drawQuad(x + current_x, y + current_y, item._tex_w, item_h - (clip_y_start + clip_y_end), item.background[1], item.background[2], item.background[3], item.background[4])
+			end
+			if item.is_separator then
+				self.sep:display(x + current_x, y + current_y + (self.fh - self.sep.h) * 0.5 - clip_y_start, nb_keyframes, ox, oy, 0, total_h + (self.fh - self.sep.h) * 0.5, 0, loffset_y, self.dest_area)
+			else
+				local one_by_tex_h = 1 / item._tex_h
+				if self.text_shadow then item._tex:toScreenPrecise(x + current_x + 1, y + current_y + 1, item.w, item_h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item_h - clip_y_end) * one_by_tex_h, 0, 0, 0, self.text_shadow) end
+				item._tex:toScreenPrecise(x + current_x, y + current_y, item.w, item_h - (clip_y_start + clip_y_end), 0, item.w / item._tex_w, clip_y_start * one_by_tex_h, (item_h - clip_y_end) * one_by_tex_h )
+			end
+			-- add only visible part of item
+			current_y = current_y + item_h - clip_y_start
 		end
-		y = y + self.fh
+		-- add full size of item
+		total_h = total_h + item_h
+		-- if we are too deep then end this
+		if total_h > loffset_y + self.dest_area.h then break end
 	end
-
-	if self.focused and self.scrollbar then
-		self.scrollbar.pos = self.scroll
-		self.scrollbar.pos = self.max - self.max_display + 1
-		self.scrollbar:display(bx + self.w - self.scrollbar.w, by)
+	if self.focused and self.scrollbar and self.h < self.max_h then
+		self.scrollbar:display(x + self.w - self.scrollbar.w, y)
 	end
 end
diff --git a/game/engines/default/engine/ui/UIContainer.lua b/game/engines/default/engine/ui/UIContainer.lua
new file mode 100644
index 0000000000000000000000000000000000000000..f6610c5b83620cb29a550ce1ebde3924f22834b1
--- /dev/null
+++ b/game/engines/default/engine/ui/UIContainer.lua
@@ -0,0 +1,114 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
+--
+-- This program 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.
+--
+-- This program 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/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+require "engine.class"
+local Base = require "engine.ui.Base"
+local Focusable = require "engine.ui.Focusable"
+local Slider = require "engine.ui.Slider"
+
+--- A generic UI list
+module(..., package.seeall, class.inherit(Base, Focusable))
+
+function _M:init(t)
+	self.w = assert(t.width, "no container width")
+	self.h = assert(t.height, "no container height")
+	self.dest_area = t.dest_area or { h = self.h }
+	
+	self:erase()
+	
+	self.scrollbar = Slider.new{size=self.h, max=0}
+	self.uis_h = 0
+
+	Base.init(self, t)
+end
+
+function _M:generate()
+	self.mouse:reset()
+	self.key:reset()
+
+	-- Add UI controls
+	local on_mousewheel = function(button, x, y, xrel, yrel, bx, by, event)
+		if button == "wheelup" and event == "button" then self.key:triggerVirtual("MOVE_UP")
+		elseif button == "wheeldown" and event == "button" then self.key:triggerVirtual("MOVE_DOWN")
+		end
+	end
+	
+	self.mouse:registerZone(0, 0, self.w, self.h, on_mousewheel)
+	
+	self.key:addBinds{
+		MOVE_UP = function() if self.scrollbar.pos and self.uis_h > self.h then self.scrollbar.pos = util.bound(self.scrollbar.pos - 1, 0, self.scrollbar.max) end end,
+		MOVE_DOWN = function() if self.scrollbar.pos and self.uis_h > self.h then self.scrollbar.pos = util.bound(self.scrollbar.pos + 1, 0, self.scrollbar.max) end end,
+	}
+end
+
+function _M:erase()
+	self.uis = {}
+end
+
+function _M:changeUI(uis)
+	local max_h = 0
+	self.uis = uis
+	for i=1, #self.uis do
+		max_h = max_h + self.uis[i].h
+	end
+	self.uis_h = max_h
+	self.scrollbar.max = max_h - self.h
+	if not self.dest_area.fixed then self.dest_area.h = max_h end
+end
+
+function _M:resize(w, h, dest_w, dest_h)
+	self.w = w
+	self.h = h
+	self.dest_area.w = dest_w
+	self.dest_area.h = dest_h
+	self.scrollbar.max = self.uis_h - self.h
+	self.scrollbar.pos = util.minBound(self.scrollbar.pos, 0, self.scrollbar.max)
+	self.scrollbar.h = dest_h
+end
+
+function _M:display(x, y, nb_keyframes, screen_x, screen_y, offset_x, offset_y, local_x, local_y)
+	local_x = local_x and local_x or 0
+	local_y = local_y and local_y or 0
+	
+	offset_x = offset_x and offset_x or 0
+	offset_y = offset_y and offset_y or (self.scrollbar and self.scrollbar.pos or 0)
+	
+	local current_y = y
+	local prev_loffset = 0
+	local total_h = 0 
+	local ui
+	local first = true
+	for i=1, #self.uis do
+		ui = self.uis[i]
+		ui.dest_area.h = self.dest_area.h
+		if offset_y <= total_h + self.uis[i].h then 
+			ui:display(x, current_y, nb_keyframes, x, current_y, offset_x, offset_y, local_x, local_y) 
+			current_y = current_y + self.uis[i].h
+			if total_h < offset_y then current_y = current_y + local_y - offset_y end
+		end
+		
+		local_y = local_y + self.uis[i].h
+		total_h = total_h + self.uis[i].h
+		if total_h > offset_y + self.h then break end
+	end
+	
+	if self.focused and self.uis_h > self.h then
+		self.scrollbar:display(x + self.w - self.scrollbar.w, y)
+	end
+end
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index 8a5419a424ac3e67dbf2d08104010038bb7940b5..32b3d1f335f3daeb7b7099ff8c54accbf194ad48 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -227,10 +227,10 @@ function table.readonly(src)
 	end
    end
    return setmetatable(src, {
-     __newindex = function(src, key, value)
-                    error("Attempt to modify read-only table")
-                  end,
-     __metatable = false
+	 __newindex = function(src, key, value)
+					error("Attempt to modify read-only table")
+				end,
+	__metatable = false
    });
 end
 
@@ -725,15 +725,19 @@ end
 
 function tstring:maxWidth(font)
 	local max_w = 0
+	local old_style = font:getStyle()
 	local line_max = 0
 	local v
-	local w = game.level.map.tiles.w * 0.5
+	local w, h = font:size("")
 	for i = 1, #self do
 		v = self[i]
-		if type(v) == "string" then line_max = line_max + font:size(v)
-	elseif type(v) == "table" then if v[1] == "uid" then line_max = line_max + w end
+		if type(v) == "string" then line_max = line_max + font:size(v) + 1
+	elseif type(v) == "table" then if v[1] == "uid" then line_max = line_max + h -- UID surface is same as font size  
+		elseif v[1] == "font" and v[2] == "bold" then font:setStyle("bold") 
+		elseif v[1] == "font" and v[2] == "normal" then font:setStyle("normal") end
 		elseif type(v) == "boolean" then max_w = math.max(max_w, line_max) line_max = 0 end
 	end
+	font:setStyle(old_style) 
 	max_w = math.max(max_w, line_max)
 	return max_w
 end
@@ -1364,11 +1368,17 @@ function util.boundWrap(i, min, max)
 	elseif i > max then i = min end
 	return i
 end
+
 function util.bound(i, min, max)
 	if min and i < min then i = min
 	elseif max and i > max then i = max end
 	return i
 end
+
+function util.minBound(i, min, max)
+	return math.max(math.min(max, i), min)
+end
+
 function util.scroll(sel, scroll, max)
 	if sel > scroll + max - 1 then scroll = sel - max + 1 end
 	if sel < scroll then scroll = sel end
diff --git a/game/engines/default/modules/boot/dialogs/Addons.lua b/game/engines/default/modules/boot/dialogs/Addons.lua
index 714031c064e5e2db87089e4aadce7d0d777c8a89..b0102bbb5afad53a753c2c73a4c17b9e257e2ad9 100644
--- a/game/engines/default/modules/boot/dialogs/Addons.lua
+++ b/game/engines/default/modules/boot/dialogs/Addons.lua
@@ -36,8 +36,8 @@ function _M:init()
 	self:generateList()
 
 	self.c_list = ListColumns.new{width=math.floor(self.iw / 3 - 10), height=self.ih - 10 - self.c_compat.h, scrollbar=true, columns={
-		{name="Game Module", width=80, display_prop="name"},
-		{name="Version", width=20, display_prop="version_txt"},
+		{name="Game Module", width=75, display_prop="name"},
+		{name="Version", width=25, display_prop="version_txt"},
 	}, list=self.list, fct=function(item) end, select=function(item, sel) self:select(item) end}
 
 	self.c_adds = ListColumns.new{width=math.floor(self.iw * 2 / 3 - 10), height=self.ih - 10 - self.c_compat.h, scrollbar=true, columns={
@@ -70,8 +70,7 @@ end
 
 function _M:select(item)
 	if item and item.adds and self.c_adds then
-		self.c_adds.list = item.adds
-		self.c_adds:generate()
+		self.c_adds:setList(item.adds)
 	end
 end
 
@@ -82,7 +81,7 @@ function _M:switchAddon(item)
 	elseif v == true then config.settings.addons[item.for_module][item.short_name] = false
 	elseif v == false then config.settings.addons[item.for_module][item.short_name] = nil
 	end
-	self.c_adds:drawItem(item)
+	--self.c_adds:drawItem(item)
 
 	local lines = {}
 	lines[#lines+1] = ("addons = {}"):format(w)
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index e3a0fe7032bd70e1c7d5ee340827ebb70f03b3c1..4bb02641302d6176c7edc92520e75f5c6f489eaf 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -1153,17 +1153,17 @@ function _M:display(nb_keyframes)
 
 	engine.GameTurnBased.display(self, nb_keyframes)
 
-	-- Tooltip is displayed over all else, even dialogs
+	-- Tooltip is displayed over all else, even dialogs but before FBO
 	local mx, my, button = core.mouse.get()
 
 	self.old_ctrl_state = self.ctrl_state
 	self.ctrl_state = core.key.modState("ctrl")
 
-	if self.tooltip.w and mx > self.w - self.tooltip.w and my > Tooltip:tooltip_bound_y2() - self.tooltip.h then
-		self:targetDisplayTooltip(Map.display_x, self.h, self.old_ctrl_state~=self.ctrl_state )
+	-- if tooltip is in way of mouse and its not locked then move it
+	if self.tooltip.w and mx > self.w - self.tooltip.w and my > Tooltip:tooltip_bound_y2() - self.tooltip.h and not self.tooltip.locked then
+		self:targetDisplayTooltip(Map.display_x, self.h, self.old_ctrl_state~=self.ctrl_state, nb_keyframes )
 	else
-		self:targetDisplayTooltip(self.w, self.h, self.old_ctrl_state~=self.ctrl_state )
-
+		self:targetDisplayTooltip(self.w, self.h, self.old_ctrl_state~=self.ctrl_state, nb_keyframes )
 	end
 
 	if self.full_fbo then
@@ -1232,6 +1232,18 @@ do return end
 			self.player:grantQuest("love-melinda")
 			self.player:hasQuest("love-melinda"):melindaCompanion(self.player, "Defiler", "Corruptor")
 		end end,
+		[{"_UP","ctrl"}] = function() 
+			game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max)
+		end,
+		[{"_DOWN","ctrl"}] = function() 
+			game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max)
+		end,
+		[{"_HOME","ctrl"}] = function()
+			game.tooltip.container.scrollbar.pos = 0
+		end,
+		[{"_END","ctrl"}] = function()
+			game.tooltip.container.scrollbar.pos = game.tooltip.container.scrollbar.max
+		end,
 	}
 
 	self.key.any_key = function(sym)
@@ -1464,6 +1476,11 @@ do return end
 
 		HELP = "EXIT",
 		EXIT = function()
+			if self.tooltip.locked then
+				self.tooltip.locked = false
+				self.tooltip.container.focused = self.tooltip.locked
+				game.log("Tooltip %s", self.tooltip.locked and "locked" or "unlocked")
+			end
 			local l = {
 				"resume",
 				"achievements",
@@ -1514,6 +1531,22 @@ do return end
 			local ok, err = coroutine.resume(co)
 			if not ok and err then print(debug.traceback(co)) error(err) end
 		end,
+		
+		LOCK_TOOLTIP = function()
+			if not self.tooltip.empty then
+				self.tooltip.locked = not self.tooltip.locked
+				self.tooltip.container.focused = self.tooltip.locked
+				game.log("Tooltip %s", self.tooltip.locked and "locked" or "unlocked")
+			end
+		end,
+		
+		LOCK_TOOLTIP_COMPARE = function()
+			if not self.tooltip.empty then
+				self.tooltip.locked = not self.tooltip.locked
+				self.tooltip.container.focused = self.tooltip.locked
+				game.log("Tooltip %s", self.tooltip.locked and "locked" or "unlocked")
+			end
+		end,
 
 		SHOW_MAP = function()
 			game:registerDialog(require("mod.dialogs.ShowMap").new())
@@ -1549,6 +1582,27 @@ function _M:setupMouse(reset)
 	self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel, bx, by, event, extra)
 		self.tooltip.add_map_str = extra and extra.log_str
 
+		if game.tooltip.locked then 
+			if button == "wheelup" and event == "button" then
+				game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max)
+			elseif button == "wheeldown" and event == "button" then 
+				game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max)
+			end
+			if button == "middle" then
+				if not game.tooltip.container.draging then
+					game.tooltip.container.draging = true
+					game.tooltip.container.drag_x_start = mx
+					game.tooltip.container.drag_y_start = my
+				else
+					game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + my - game.tooltip.container.drag_y_start, 0, game.tooltip.container.scrollbar.max)
+					game.tooltip.container.drag_x_start = mx
+					game.tooltip.container.drag_y_start = my
+				end
+			else
+				game.tooltip.container.draging = false
+			end
+		end
+		
 		-- Handle targeting
 		if self:targetMouse(button, mx, my, xrel, yrel, event) then return end
 
diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua
index 1a55566ca752c9824a35e895a42ed4caccc1ae54..2cd9b5e1c45d5098970f6580f2ad16a27ed9a742 100644
--- a/game/modules/tome/class/Object.lua
+++ b/game/modules/tome/class/Object.lua
@@ -151,7 +151,7 @@ function _M:descAttribute(attr)
 		return c.dam.."-"..(c.dam*(c.damrange or 1.1)).." power, "..(c.apr or 0).." apr, "..DamageType:get(c.damtype).name.." damage"
 	elseif attr == "SHIELD" then
 		local c = self.special_combat
-		if c and (game.player:knowTalentType("technique/shield-offense") or game.player:knowTalentType("technique/shield-defense")) or game.player:attr("show_shield_combat") then
+		if c and (game.player:knowTalentType("technique/shield-offense") or game.player:knowTalentType("technique/shield-defense") or game.player:attr("show_shield_combat")) then
 			return c.dam.." dam, "..c.block.." block"
 		else
 			return c.block.." block"
@@ -468,7 +468,7 @@ function _M:getTextualDesc(compare_with)
 		compare_fields(combat, compare_with, field, "physcrit", "%+.1f%%", "Physical crit. chance: ", 1, false, false, add_table)
 		compare_fields(combat, compare_with, field, "physspeed", "%.0f%%", "Attack speed: ", 100, false, true, add_table)
 
-		compare_fields(combat, compare_with, field, "block", "%+d", "Block value: ", 1, false, false, add_table)
+		compare_fields(combat, compare_with, field, "block", "%+d", "Block value: ", 1, false, true, add_table)
 
 		compare_fields(combat, compare_with, field, "range", "%+d", "Firing range: ", 1, false, false, add_table)
 		compare_fields(combat, compare_with, field, "capacity", "%d", "Capacity: ", 1, false, false, add_table)
@@ -1173,19 +1173,9 @@ function _M:getDesc(name_param, compare_with, never_compare)
 	name_param = name_param or {}
 	name_param.do_color = true
 	compare_with = compare_with or {}
-	if not self:isIdentified() then
-		desc:merge(self:getName(name_param):toTString())
-		desc:add({"color", "WHITE"}, true)
-	else
-		desc:merge(self:getName(name_param):toTString())
-		desc:add({"color", "WHITE"}, true)
-		desc:add(true)
-		desc:add({"color", "ANTIQUE_WHITE"})
-		desc:merge(self.desc:toTString())
-		desc:add(true, true)
-		desc:add({"color", "WHITE"})
-	end
 
+	desc:merge(self:getName(name_param):toTString())
+	desc:add({"color", "WHITE"}, true)
 	local reqs = self:getRequirementDesc(game.player)
 	if reqs then
 		desc:merge(reqs)
@@ -1204,20 +1194,24 @@ function _M:getDesc(name_param, compare_with, never_compare)
 		desc:add({"color",0x67,0xAD,0x00}, ("%0.2f Encumbrance."):format(self.encumber), {"color", "LAST"})
 	end
 	if self.ego_bonus_mult then
-		desc:add(true)
-		desc:add({"color",0x67,0xAD,0x00}, ("%0.2f Ego Multiplier."):format(1 + self.ego_bonus_mult), {"color", "LAST"})
+		desc:add(true, {"color",0x67,0xAD,0x00}, ("%0.2f Ego Multiplier."):format(1 + self.ego_bonus_mult), {"color", "LAST"})
 	end
 
-	desc:add(true, true)
-
 	local could_compare = false
 	if not name_param.force_compare and not core.key.modState("ctrl") then
 		if compare_with[1] then could_compare = true end
 		compare_with = {}
 	end
 
+	desc:add(true, true)
 	desc:merge(self:getTextualDesc(compare_with))
 
+	if self:isIdentified() then
+		desc:add(true, true, {"color", "ANTIQUE_WHITE"})
+		desc:merge(self.desc:toTString())
+		desc:add({"color", "WHITE"})
+	end
+
 	if could_compare and not never_compare then desc:add(true, {"font","italic"}, {"color","GOLD"}, "Press <control> to compare", {"color","LAST"}, {"font","normal"}) end
 
 	return desc
diff --git a/game/modules/tome/class/PartyMember.lua b/game/modules/tome/class/PartyMember.lua
index 1d199229e286ee14c3aa1fa008de6eb648160947..e2c3a027130c15d88705d0d3c8c7660ebdc73fb0 100644
--- a/game/modules/tome/class/PartyMember.lua
+++ b/game/modules/tome/class/PartyMember.lua
@@ -41,7 +41,7 @@ function _M:tooltip(x, y, seen_by)
 		true,
 		{"color", "TEAL"},
 		("Behavior: %s"):format(self.ai_tactic.type or "default"), true,
-		("Action radius: %d"):format(self.ai_state.tactic_leash), true,
+		("Action radius: %d"):format(self.ai_state.tactic_leash),
 		{"color", "WHITE"}
 	)
 	return str
diff --git a/game/modules/tome/class/Tooltip.lua b/game/modules/tome/class/Tooltip.lua
index e00f2977ddb6ad243b3684bc403874d105b9eca0..3bfed72cb8bcffd98bc3729d50a4f5e46e2b6898 100644
--- a/game/modules/tome/class/Tooltip.lua
+++ b/game/modules/tome/class/Tooltip.lua
@@ -25,12 +25,9 @@ module(..., package.seeall, class.inherit(Tooltip))
 
 tooltip_bound_y2 = function() return game.uiset.map_h_stop_tooltip end
 
-function _M:init(...)
-	Tooltip.init(self, ...)
-end
-
 --- Gets the tooltips at the given map coord
 function _M:getTooltipAtMap(tmx, tmy, mx, my)
+	if self.locked then return nil end
 	local tt = {}
 	local seen = game.level.map.seens(tmx, tmy)
 	local remember = game.level.map.remembers(tmx, tmy)
@@ -40,25 +37,26 @@ function _M:getTooltipAtMap(tmx, tmy, mx, my)
 		local to_add = game.level.map:checkEntity(tmx, tmy, check_type, "tooltip", game.level.map.actor_player)
 		if to_add then 
 			if type(to_add) == "string" then to_add = to_add:toTString() end
-			tt[#tt+1] = to_add 
+			if to_add.is_tstring then 
+				tt[#tt+1] = to_add 
+			else 
+				table.append(tt, to_add) 
+			end
 		end
+		return to_add
 	end
 	
-	if seen and not ctrl_state then
-		check(Map.PROJECTILE)
+	if seen or remember and not ctrl_state then
+		check(Map.TRAP)
 		check(Map.ACTOR)
-	end
-	if seen or remember then
-		local obj = check(Map.OBJECT)
-		if not ctrl_state or not obj then
-			check(Map.TRAP)
-			check(Map.TERRAIN)
-		end
+		check(Map.OBJECT)
+		check(Map.PROJECTILE)
+		check(Map.TERRAIN)
 	end
 	
 	if #tt > 0 then
 		return tt
 	end
-	if self.add_map_str then return self.add_map_str:toTString() end
+	if self.add_map_str then return self.add_map_str end
 	return nil
-end
+end
\ No newline at end of file
diff --git a/game/modules/tome/class/uiset/Minimalist.lua b/game/modules/tome/class/uiset/Minimalist.lua
index 2e4a3e97fd0c03a13b33a888399f586fdc2a7ef7..33e657fd3acf51438bf10c9ca4fcaee3fd5686a5 100644
--- a/game/modules/tome/class/uiset/Minimalist.lua
+++ b/game/modules/tome/class/uiset/Minimalist.lua
@@ -1846,17 +1846,17 @@ function _M:setupMouse(mouse)
 		local mx, my = core.mouse.get()
 		if not item or not sub_es or #sub_es == 0 then game.mouse:delegate(button, mx, my, xrel, yrel, nil, nil, event, "playmap") return end
 
-		local str = tstring{}
+		local tooltips = {}
 		for i, e in ipairs(sub_es) do
 			if e.tooltip then
-				str:merge(e:tooltip():toTString())
-				if i < #sub_es then str:add(true, "---", true)
-				else str:add(true) end
+				table.append(tooltips, e:tooltip())
+				if i < #sub_es then table.append(tooltips, { tstring{ true, "---" } } )
+				else table.append(tooltips, { tstring{ true } } ) end
 			end
 		end
 
 		local extra = {}
-		extra.log_str = str
+		extra.log_str = tooltips
 		game.tooltip.old_ttmx = -100
 		game.mouse:delegate(button, mx, my, xrel, yrel, nil, nil, event, "playmap", extra)
 	end)
diff --git a/game/modules/tome/data/keybinds/tome.lua b/game/modules/tome/data/keybinds/tome.lua
index 53e5a8833d6eed34bc1cd22f4da68ff923a9c49f..e51a801e475c6e867394669fc4a5f261ddf92bbe 100644
--- a/game/modules/tome/data/keybinds/tome.lua
+++ b/game/modules/tome/data/keybinds/tome.lua
@@ -205,3 +205,17 @@ defineAction{
 	group = "interface",
 	name = "Toggle UI display",
 }
+
+defineAction{
+	default = { "sym:_l:false:true:false:false" },
+	type = "LOCK_TOOLTIP",
+	group = "interface",
+	name = "Locks tooltip in place",
+}
+
+defineAction{
+	default = { "sym:_l:true:true:false:false" },
+	type = "LOCK_TOOLTIP_COMPARE",
+	group = "interface",
+	name = "Locks tooltip in place while comparing items",
+}
\ No newline at end of file
diff --git a/game/modules/tome/dialogs/LevelupDialog.lua b/game/modules/tome/dialogs/LevelupDialog.lua
index b0598c33ee6b05cb5885cf1ee1ed2b89c17fd537..78cf29f747a659eb6a71a171fc684e2c95ad29c3 100644
--- a/game/modules/tome/dialogs/LevelupDialog.lua
+++ b/game/modules/tome/dialogs/LevelupDialog.lua
@@ -24,6 +24,7 @@ local Dialog = require "engine.ui.Dialog"
 local Button = require "engine.ui.Button"
 local Textzone = require "engine.ui.Textzone"
 local TextzoneList = require "engine.ui.TextzoneList"
+local UIContainer = require "engine.ui.UIContainer"
 local TalentTrees = require "mod.dialogs.elements.TalentTrees"
 local Separator = require "engine.ui.Separator"
 local DamageType = require "engine.DamageType"
@@ -646,10 +647,7 @@ function _M:createDisplay()
 
 	if self.no_tooltip then
 		local vsep3 = Separator.new{dir="horizontal", size=self.ih - 20}
-		self.c_desc = TextzoneList.new{
-			width=self.iw - 200 - 530 - 40, height = self.ih,
-			scrollbar = true,
-		}
+        self.c_desc = TextzoneList.new{ width=self.iw - 200 - 530 - 40, height = self.ih, dest_area = { h = self.ih } }
 		ret[#ret+1] = {right=0, top=0, ui=self.c_desc}
 		ret[#ret+1] = {right=self.c_desc.w, top=0, ui=vsep3}
 	end
diff --git a/game/modules/tome/dialogs/LorePopup.lua b/game/modules/tome/dialogs/LorePopup.lua
index 82e24b77a82995aa46a20333e0ef16150cee17b1..f3a1931271065d974f44499e635df1c44054a264 100644
--- a/game/modules/tome/dialogs/LorePopup.lua
+++ b/game/modules/tome/dialogs/LorePopup.lua
@@ -73,10 +73,18 @@ function _M:init(l, w, force_height)
 			{left = 3, top = 3 + image.h, ui=c_text},
 		}
 	end
+	
+	local on_end = function() 
+		game.tooltip.inhibited = false
+		game:unregisterDialog(self) 
+		if fct then 
+			fct() 
+		end  
+	end
 
 	self:loadUI(uis)
-	self.key:addBind("EXIT", function() game:unregisterDialog(self) if fct then fct() end end)
-	self.key:addBind("ACCEPT", function() game:unregisterDialog(self) if fct then fct() end end)
+	self.key:addBind("EXIT", on_end)
+	self.key:addBind("ACCEPT", on_end)
 	self:setupUI(true, true)
 
 	if self.w >= game.w or self.h >= game.h then
@@ -94,4 +102,6 @@ function _M:init(l, w, force_height)
 
 	game:registerDialog(self)
 	game:playSound("actions/read")
-end
+	
+	game.tooltip.inhibited = true
+end
\ No newline at end of file
diff --git a/game/modules/tome/dialogs/ShowEquipInven.lua b/game/modules/tome/dialogs/ShowEquipInven.lua
index e45c5f21e90dacc3b6cb4bd8027f65359af13951..9732c1e681c3dc268ac7e8c6dfaff0b6ef543009 100644
--- a/game/modules/tome/dialogs/ShowEquipInven.lua
+++ b/game/modules/tome/dialogs/ShowEquipInven.lua
@@ -41,10 +41,8 @@ function _M:init(title, actor, filter, action, on_select)
 	-- Add tooltips
 	self.on_select = function(item)
 		if item.last_display_x and item.object then
-			local x = nil
-			if self.focus_ui and self.focus_ui.ui == self.c_inven then
-				x = self.c_inven._last_ox - game.tooltip.max
-			end
+			local x
+			if self.focus_ui and self.focus_ui.ui == self.c_inven then x = self.c_inven._last_ox - game.tooltip.w end
 			game:tooltipDisplayAtMap(x or item.last_display_x, item.last_display_y, item.object:getDesc({do_color=true}, self.actor:getInven(item.object:wornInven())))
 		elseif item.last_display_x and item.data and item.data.desc then
 			game:tooltipDisplayAtMap(item.last_display_x, item.last_display_y, item.data.desc, {up=true})
@@ -81,6 +79,43 @@ function _M:init(title, actor, filter, action, on_select)
 	self:loadUI(uis)
 	self:setFocus(self.c_inven)
 	self:setupUI()
+	
+	local lock_tooltip = function()
+		if not game.tooltip.empty then
+			game.tooltip.locked = not game.tooltip.locked
+			game.tooltip.container.focused = game.tooltip.locked
+			game.log("Tooltip %s", game.tooltip.locked and "locked" or "unlocked")
+			if game.tooltip.locked then
+				self.old_areas_name = self.mouse.areas_name
+				self.old_areas = self.mouse.areas
+				self.mouse:reset()
+				local on_mouse = function(button, x, y, xrel, yrel, bx, by, event)
+					if button == "wheelup" and event == "button" then
+						game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max)
+					elseif button == "wheeldown" and event == "button" then 
+						game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max)
+					end
+					if button == "middle" then
+						if not self.scroll_drag then
+							self.scroll_drag = true
+							self.scroll_drag_x_start = x
+							self.scroll_drag_y_start = y
+						else
+							game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + y - self.scroll_drag_y_start, 0, game.tooltip.container.scrollbar.max)
+							self.scroll_drag_x_start = x
+							self.scroll_drag_y_start = y
+						end
+					else
+						self.scroll_drag = false
+					end
+				end
+				self.mouse:registerZone(0, 0, self.w, self.h, on_mouse)
+			else
+				self.mouse.areas_name = self.old_areas_name
+				self.mouse.areas = self.old_areas
+			end
+		end
+	end
 
 	engine.interface.PlayerHotkeys:bindAllHotkeys(self.key, function(i) self:defineHotkey(i) end)
 	self.key:addBinds{
@@ -88,7 +123,11 @@ function _M:init(title, actor, filter, action, on_select)
 			if self.focus_ui and self.focus_ui.ui == self.c_inven then self:use(self.c_inven.c_inven.list[self.c_inven.c_inven.sel])
 			end
 		end,
-		EXIT = function() game:unregisterDialog(self) end,
+		EXIT = function() game.tooltip.locked = false game:unregisterDialog(self) end,
+		LOCK_TOOLTIP = lock_tooltip,
+		LOCK_TOOLTIP_COMPARE = lock_tooltip,
+		MOVE_UP = function() if game.tooltip.locked then game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos - 1, 0, game.tooltip.container.scrollbar.max) end end,
+		MOVE_DOWN = function() if game.tooltip.locked then game.tooltip.container.scrollbar.pos = util.minBound(game.tooltip.container.scrollbar.pos + 1, 0, game.tooltip.container.scrollbar.max) end end,
 	}
 
 	self.key.any_key = function(sym)
diff --git a/game/modules/tome/dialogs/ShowStore.lua b/game/modules/tome/dialogs/ShowStore.lua
index de300ae051371ab07ce5f83e00c5a3c2526b34b6..43a75f2484bcb957e1e28e98b331bef443e81f09 100644
--- a/game/modules/tome/dialogs/ShowStore.lua
+++ b/game/modules/tome/dialogs/ShowStore.lua
@@ -49,10 +49,32 @@ function _M:init(title, store_inven, actor_inven, store_filter, actor_filter, ac
 		on_drag=function(item) self:onDrag(item, "store-sell") end,
 		on_drag_end=function() self:onDragTakeoff("store-buy") end,
 	}
+	
+	local direct_draw= function(item, x, y, w, h, total_w, total_h, loffset_x, loffset_y, dest_area)
+		-- if there is object and is withing visible bounds
+		if item.object and total_h + h > loffset_y and total_h < loffset_y + dest_area.h then 
+			local clip_y_start, clip_y_end = 0, 0
+			-- if it started before visible area then compute its top clip
+			if total_h < loffset_y then 
+				clip_y_start = loffset_y - total_h
+			end
+			-- if it ended after visible area then compute its bottom clip
+			if total_h + h > loffset_y + dest_area.h then 
+			   clip_y_end = total_h + h - loffset_y - dest_area.h 
+			end
+			-- get entity texture with everything it has i.e particles
+			local texture = item.object:getEntityFinalTexture(nil, h, h)
+			local one_by_tex_h = 1 / h
+			texture:toScreenPrecise(x, y, h, h - clip_y_start - clip_y_end, 0, 1, clip_y_start * one_by_tex_h, (h - clip_y_end) * one_by_tex_h)
+			return h, h, 0, 0, clip_y_start, clip_y_end
+		end 
+		return 0, 0, 0, 0, 0, 0
+	end
+	
 	self.c_store = Inventory.new{actor=store_actor, inven=store_inven, filter=store_filter, width=math.floor(self.iw / 2 - 10), height=self.ih - 10, tabslist=false,
 		columns={
 			{name="", width={20,"fixed"}, display_prop="char", sort="id"},
-			{name="", width={24,"fixed"}, display_prop="object", direct_draw=function(item, x, y) item.object:toScreen(nil, x+4, y, 16, 16) end},
+			{name="", width={24,"fixed"}, display_prop="object", direct_draw=direct_draw},
 			{name="Store", width=80, display_prop="name"},
 			{name="Category", width=20, display_prop="cat"},
 			{name="Price", width={50,"fixed"}, display_prop=function(item) return self.descprice("buy", item.object) end, sort=function(a, b) return descprice("buy", a.object) < descprice("buy", b.object) end},
@@ -93,12 +115,14 @@ function _M:init(title, store_inven, actor_inven, store_filter, actor_filter, ac
 			elseif self.focus_ui and self.focus_ui.ui == self.c_inven then
 				x = self.c_inven._last_ox - game.tooltip.max
 			end
+
 			game:tooltipDisplayAtMap(x or item.last_display_x, item.last_display_y, item.object:getDesc({do_color=true}, game.player:getInven(item.object:wornInven())))
 		end
 	end
 	self.key.any_key = function(sym)
 		-- Control resets the tooltip
-		if sym == self.key._LCTRL or sym == self.key._RCTRL then local i = self.cur_item self.cur_item = nil self:select(i) end
+		if (sym == self.key._LCTRL or sym == self.key._RCTRL) and sym~=self.prev_ctrl then local i = self.cur_item self.cur_item = nil self:select(i, true) end
+		self.prev_ctrl = sym
 	end
 end
 
@@ -110,14 +134,18 @@ function _M:updateStore()
 	self:generateList()
 end
 
-function _M:select(item)
-	if self.cur_item == item then return end
-	if item then
-		if self.on_select then self.on_select(item) end
-	end
+function _M:select(item, force)
+	if self.cur_item == item and not force then return end
+	if item then if self.on_select then self.on_select(item) end end
 	self.cur_item = item
 end
 
+function _M:on_recover_focus()
+	if self.focus_ui and self.focus_ui.ui == self.c_inven then self:select(self.c_inven.c_inven.list[self.c_inven.c_inven.sel], true)
+	elseif self.focus_ui and self.focus_ui.ui == self.c_store then self:select(self.c_store.c_inven.list[self.c_store.c_inven.sel], true)
+	end
+end
+
 function _M:on_focus(id, ui)
 	if self.focus_ui and self.focus_ui.ui == self.c_inven then self:select(self.c_inven.c_inven.list[self.c_inven.c_inven.sel])
 	elseif self.focus_ui and self.focus_ui.ui == self.c_store then self:select(self.c_store.c_inven.list[self.c_store.c_inven.sel])
diff --git a/src/core_lua.c b/src/core_lua.c
index f5775ef00bda157df95b869272f928239bef6781..908ee7c6d072d6ca7a354dab6d74a4884209073a 100644
--- a/src/core_lua.c
+++ b/src/core_lua.c
@@ -1827,6 +1827,24 @@ static int gl_rotate(lua_State *L)
 	return 0;
 }
 
+static int gl_push(lua_State *L)
+{
+	glPushMatrix();
+	return 0;
+}
+
+static int gl_pop(lua_State *L)
+{
+	glPopMatrix();
+	return 0;
+}
+
+static int gl_identity(lua_State *L)
+{
+	glLoadIdentity();
+	return 0;
+}
+
 static int gl_matrix(lua_State *L)
 {
 	if (lua_toboolean(L, 1)) glPushMatrix();
@@ -2540,6 +2558,9 @@ static const struct luaL_reg displaylib[] =
 	{"glTranslate", gl_translate},
 	{"glScale", gl_scale},
 	{"glRotate", gl_rotate},
+	{"glPush", gl_push},
+	{"glPop", gl_pop},
+	{"glIdentity", gl_identity},
 	{"glColor", gl_color},
 	{"glMatrix", gl_matrix},
 	{"glDepthTest", gl_depth_test},
diff --git a/src/map.c b/src/map.c
index 577de4b73e39c83bcac93ecbb603c65405ac0dd8..1727ae48f93545d7563788155341f10d5883d8dc 100644
--- a/src/map.c
+++ b/src/map.c
@@ -31,6 +31,8 @@
 //#include "shaders.h"
 #include "useshader.h"
 
+#include "assert.h"
+
 static const char IS_HEX_KEY = 'k';
 
 /*
@@ -487,13 +489,14 @@ static int map_objects_display(lua_State *L)
 	glLoadIdentity();
 	glOrtho(0, w, 0, h, -101, 101);
 	glMatrixMode( GL_MODELVIEW );
-
+	CHECKGL(glPushMatrix());
 	/* Reset The View */
-	glLoadIdentity( );
+	glLoadIdentity();
+	
 
 	tglClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
 	CHECKGL(glClear(GL_COLOR_BUFFER_BIT));
-	CHECKGL(glLoadIdentity());
+	//CHECKGL(glLoadIdentity());
 
 	GLfloat vertices[3*4];
 	GLfloat texcoords[2*4] = {
@@ -560,10 +563,11 @@ static int map_objects_display(lua_State *L)
 	// No, dot not it's a static, see upwards
 //	CHECKGL(glDeleteFramebuffersEXT(1, &fbo));
 
+	CHECKGL(glPopMatrix());
 	glMatrixMode(GL_PROJECTION);
 	CHECKGL(glPopMatrix());
 	glMatrixMode( GL_MODELVIEW );
-
+	
 	tglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
 
 
@@ -1452,16 +1456,25 @@ static int map_to_screen(lua_State *L)
 
 	map->used_mx = mx;
 	map->used_my = my;
+	
+	int mini = mx - 1, maxi = mx + map->mwidth + 2, minj =  my - 1, maxj = my + map->mheight + 2;
+	
+	if(mini < 0)
+		mini = 0;
+	if(minj < 0)
+		minj = 0;
+	if(maxi > map->w)
+		maxi = map->w;
+	if(maxj > map->h)
+		maxj = map->h;
 
 	// Always display some more of the map to make sure we always see it all
 	for (z = 0; z < map->zdepth; z++)
 	{
-		for (j = my - 1; j < my + map->mheight + 2; j++)
+		for (j = minj; j < maxj; j++)
 		{
-			for (i = mx - 1; i < mx + map->mwidth + 2; i++)
+			for (i = mini; i < maxi; i++)
 			{
-				if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h)) continue;
-
 				int dx = x + i * map->tile_w;
 				int dy = y + j * map->tile_h + (i & map->is_hex) * map->tile_h / 2;
 				map_object *mo = map->grids[i][j][z];
@@ -1565,13 +1578,24 @@ static int minimap_to_screen(lua_State *L)
 	int ptr;
 	GLubyte *mm = map->minimap;
 	memset(mm, 0, map->mm_rh * map->mm_rw * 4 * sizeof(GLubyte));
+	
+	int mini = mdx, maxi = mdx + mdw, minj = mdy, maxj = mdy + mdh;
+	
+	if(mini < 0)
+		mini = 0;
+	if(minj < 0)
+		minj = 0;
+	if(maxi > map->w)
+		maxi = map->w;
+	if(maxj > map->h)
+		maxj = map->h;
+	
 	for (z = 0; z < map->zdepth; z++)
 	{
-		for (i = mdx; i < mdx + mdw; i++)
+		for (j = minj; j < maxj; j++)
 		{
-			for (j = mdy; j < mdy + mdh; j++)
+			for (i = mini; i < maxi; i++)
 			{
-				if ((i < 0) || (j < 0) || (i >= map->w) || (j >= map->h)) continue;
 				map_object *mo = map->grids[i][j][z];
 				if (!mo || mo->mm_r < 0) continue;
 				ptr = (((1+f)*(j-mdy) + (i & f)) * map->mm_rw + (i-mdx)) * 4;