require "ISUI/ISPanel"
require "ISUI/ISButton"
require "ISUI/ISMouseDrag"
require "ISUI/ISInventoryItem"
require "TimedActions/ISTimedActionQueue"
require "TimedActions/ISEatFoodAction"
require "BodyDragging/corpseStorageCheck"
require "TimedActions/ISInventoryTransferUtil"
local FONT_HGT_SMALL = getTextManager():getFontHeight(UIFont.Small)

ISInventoryPane = ISPanel:derive("ISInventoryPane")

ISInventoryPane.MAX_ITEMS_IN_STACK_TO_RENDER = 50
ISInventoryPane.ghc = getCore():getGoodHighlitedColor()
ISInventoryPaneDraggedItems = {}
local DraggedItems = ISInventoryPaneDraggedItems

local function CleanUI_safeIsItemAllowed(container, item)
    if not container or not item then
        return false
    end
    local ok, result = pcall(function()
        return container:isItemAllowed(item)
    end)
    if ok then
        return result == true
    end
    return false
end


local favoriteRecipeInputStarSize = 16
local normalTextColor = {r=0.7, g=0.7, b=0.7, a=1.0}
local unwantedTextColor = {r=0.5, g=0.5, b=0.5, a=0.65}

local function predicateNotEmpty(item)
	return item:getCurrentUsesFloat() > 0
end


local CleanUI_transferGuardLog = {}

local function CleanUI_transferGuardSafe(callback)
    local ok, result = pcall(callback)
    if ok then
        return result
    end
    return nil
end

local function CleanUI_transferGuardContainerType(container)
    if not container then return "nil" end
    return CleanUI_transferGuardSafe(function() return container:getType() end) or "unknown"
end

local function CleanUI_transferGuardItemName(item)
    if not item then return "nil" end
    return CleanUI_transferGuardSafe(function() return item:getFullType() end)
        or CleanUI_transferGuardSafe(function() return item:getType() end)
        or CleanUI_transferGuardSafe(function() return item:getName() end)
        or "unknown"
end

local function CleanUI_logSkippedInventoryTransfer(reason, item, srcContainer, destContainer)
    -- Log each skipped transfer shape once so the console remains readable.
    local itemName = CleanUI_transferGuardItemName(item)
    local srcType = CleanUI_transferGuardContainerType(srcContainer)
    local destType = CleanUI_transferGuardContainerType(destContainer)
    local key = tostring(reason) .. "|" .. tostring(itemName) .. "|" .. tostring(srcType) .. "|" .. tostring(destType)

    if CleanUI_transferGuardLog[key] then
        return
    end

    CleanUI_transferGuardLog[key] = true
    print("[CleanUI] Skipped unsafe inventory transfer reason " .. tostring(reason) .. " item " .. tostring(itemName) .. " from " .. tostring(srcType) .. " to " .. tostring(destType))
end

local function CleanUI_transferGuardIsItemAllowed(container, item)
    -- Keep item-allowed checks safe for unusual vehicle, seat, or modded containers.
    if not container or not item then
        return false
    end

    local ok, result = pcall(function()
        return container:isItemAllowed(item)
    end)
    if ok then
        return result == true
    end

    return false
end

local function CleanUI_queueTimedActionSafely(action, reason, item, srcContainer, destContainer)
    -- Queue transfer actions defensively without pre-validating them.
    -- Some queued floor-item transfers can become valid only when previous queued actions finish,
    -- especially in MP where IsoWorldInventoryObject state is synchronized asynchronously.
    if not action then
        CleanUI_logSkippedInventoryTransfer(reason .. " action missing", item, srcContainer, destContainer)
        return false
    end

    local addOk, addResult = pcall(function()
        return ISTimedActionQueue.add(action)
    end)
    if not addOk then
        CleanUI_logSkippedInventoryTransfer(reason .. " queue error " .. tostring(addResult), item, srcContainer, destContainer)
        return false
    end

    return true
end

local function CleanUI_queueInventoryTransferSafely(playerObj, item, destContainer)
    -- Avoid queuing invalid transfers from stale dragged items or containers that are no longer accessible.
    if not playerObj or not item or not destContainer then
        CleanUI_logSkippedInventoryTransfer("missing transfer data", item, nil, destContainer)
        return false
    end

    local srcContainer = CleanUI_transferGuardSafe(function() return item:getContainer() end)
    if not srcContainer then
        CleanUI_logSkippedInventoryTransfer("missing source container", item, nil, destContainer)
        return false
    end

    if srcContainer == destContainer then
        CleanUI_logSkippedInventoryTransfer("same source and destination", item, srcContainer, destContainer)
        return false
    end

    local contains = CleanUI_transferGuardSafe(function() return srcContainer:contains(item) end)
    if contains == false then
        CleanUI_logSkippedInventoryTransfer("source no longer contains item", item, srcContainer, destContainer)
        return false
    end

    if not CleanUI_transferGuardIsItemAllowed(destContainer, item) then
        CleanUI_logSkippedInventoryTransfer("item not allowed", item, srcContainer, destContainer)
        return false
    end

    local actionOk, action = pcall(function()
        return ISInventoryTransferUtil.newInventoryTransferAction(playerObj, item, srcContainer, destContainer)
    end)
    if not actionOk then
        CleanUI_logSkippedInventoryTransfer("action create error " .. tostring(action), item, srcContainer, destContainer)
        return false
    end

    return CleanUI_queueTimedActionSafely(action, "inventory transfer", item, srcContainer, destContainer)
end


ISInventoryPane.itemSortByWeightAsc = function(a,b)
    if a.equipped and not b.equipped then return false end
    if b.equipped and not a.equipped then return true end
    if not a.equipped and not b.equipped then
        if a.type == "separator" then return false end
        if b.type == "separator" then return true end
    end
    --if a.cat == b.cat then return not string.sort(a.name, b.name) end
    --return not string.sort(a.cat, b.cat)
    return a.weight < b.weight
end

ISInventoryPane.itemSortByWeightDesc = function(a,b)
    if a.equipped and not b.equipped then return false end
    if b.equipped and not a.equipped then return true end
    if not a.equipped and not b.equipped then
        if a.type == "separator" then return false end
        if b.type == "separator" then return true end
    end
    --if a.cat == b.cat then return not string.sort(a.name, b.name) end
    --return string.sort(a.cat, b.cat)
    return a.weight > b.weight
end

ISInventoryPane.itemSortByNameInc = function(a,b)
    if a.equipped and not b.equipped then return false end
    if b.equipped and not a.equipped then return true end
    if not a.equipped and not b.equipped then
        if a.type == "separator" then return false end
        if b.type == "separator" then return true end
    end
	--if a.inHotbar and not b.inHotbar then return true end
	--if b.inHotbar and not a.inHotbar then return false end
    return not string.sort(a.name, b.name)
end

ISInventoryPane.itemSortByNameDesc = function(a,b)
    if a.equipped and not b.equipped then return false end
    if b.equipped and not a.equipped then return true end
    if not a.equipped and not b.equipped then
        if a.type == "separator" then return false end
        if b.type == "separator" then return true end
    end
	--if a.inHotbar and not b.inHotbar then return true end
	--if b.inHotbar and not a.inHotbar then return false end
    return string.sort(a.name, b.name)
end

ISInventoryPane.itemSortByCatInc = function(a,b)
    if a.equipped and not b.equipped then return false end
    if b.equipped and not a.equipped then return true end
    if not a.equipped and not b.equipped then
        if a.type == "separator" then return false end
        if b.type == "separator" then return true end
    end
    if a.cat == b.cat then return not string.sort(a.name, b.name) end
    return not string.sort(a.cat, b.cat)
end

ISInventoryPane.itemSortByCatDesc = function(a,b)
    if a.equipped and not b.equipped then return false end
    if b.equipped and not a.equipped then return true end
    if not a.equipped and not b.equipped then
        if a.type == "separator" then return false end
        if b.type == "separator" then return true end
    end
    if a.cat == b.cat then return not string.sort(a.name, b.name) end
    return string.sort(a.cat, b.cat)
end

-- ----------------------------------------------------------------------------------------------------- --
-- initialise
-- ----------------------------------------------------------------------------------------------------- --

function ISInventoryPane:initialise()
	ISPanel.initialise(self)
end

function ISInventoryPane:new (x, y, width, height, inventory, zoom)
	local o = {}
	--o.data = {}
	o = ISPanel:new(x, y, width, height)
	setmetatable(o, self)
    self.__index = self
	o.x = x
	o.y = y
    o.borderColor = {r=0.4, g=0.4, b=0.4, a=1}
    o.backgroundColor = {r=0, g=0, b=0, a=0.5}
	o.width = width
	o.height = height
	o.anchorLeft = true
	o.anchorRight = false
	o.anchorTop = true
	o.anchorBottom = false
	o.inventory = inventory
    o.padding = math.floor(FONT_HGT_SMALL * 0.2)
    o.zoom = zoom
	o.mode = "details"
	o.column2 = 30
	o.column3 = 140
 	o.column4 = o.width
	o.items = {}
	o.selected = {}
    o.highlightItems = {}
	o.previousMouseUp = nil
    o.searchText = nil
    o.searchCategoryText = nil
    o.searchMode = "name"
    o.mouseOverColumnDivider = false
    o.dividerDragging = false
	local font = getCore():getOptionInventoryFont()
	if font == "Large" then
		o.font = UIFont.Large
	elseif font == "Small" then
		o.font = UIFont.Small
	else
		o.font = UIFont.Medium
	end
    if zoom > 1.5 then
        o.font = UIFont.Large
    end
    o.fontHgt = getTextManager():getFontFromEnum(o.font):getLineHeight()
    o.itemHgt = math.ceil(math.max(18, o.fontHgt) * o.zoom)
    o.texScale = math.min(32, (o.itemHgt - 2)) / 32
    o.draggedItems = DraggedItems:new(o)

    o.expandicon = getTexture("media/ui/inventoryPanes/Button_TreeExpandAll.png")
    o.filtericon = getTexture("media/ui/inventoryPanes/Button_TreeFilter.png")
    o.collapseicon = getTexture("media/ui/inventoryPanes/Button_TreeCollapseAll.png")
    o.treeexpicon = getTexture("media/ui/CleanUI/ICON/Icon_DownArrow.png")
    o.treecolicon = getTexture("media/ui/CleanUI/ICON/Icon_RightArrow.png")
    o.hideEquippedIcon = getTexture("media/ui/CleanUI/ICON/Icon_HideEquipped_pane.png")
    o.showEquippedIcon = getTexture("media/ui/CleanUI/ICON/Icon_ShowEquipped_pane.png")
	o.equippedItemIcon = getTexture("media/ui/icon.png")
	o.equippedInHotbar = getTexture("media/ui/iconInHotbar.png")
    o.brokenItemIcon = getTexture("media/ui/icon_broken.png")
    o.frozenItemIcon = getTexture("media/ui/icon_frozen.png")
    o.poisonIcon = getTexture("media/ui/SkullPoison.png")
    o.itemSortFunc = ISInventoryPane.itemSortByNameInc -- how to sort the items...
    o.favoriteStar = getTexture("media/ui/CleanUI/ICON/Icon_FavoriteStar.png")
    o.noFavoriteRecipeInputStar = getTexture("media/ui/inventoryPanes/nocraft.png")
    o.favoriteRecipeInputStar = getTexture("media/ui/inventoryPanes/craftok.png")
    o.columnResizerIcon = getTexture("media/ui/CleanUI/ICON/Icon_ColumnResizer.png")
    o.equippedCollapsed = CleanUIConfig.getConfig()["hideEquipped"] or false
   return o
end

-- ----------------------------------------------------------------------------------------------------- --
-- Create Children
-- ----------------------------------------------------------------------------------------------------- --

function ISInventoryPane:createChildren()
    self.minimumHeight = 50
    self.minimumWidth = 256
    self.headerHgt = 0

    self.column2 = (FONT_HGT_SMALL + 1)*2
    self.column3 = math.ceil(self.column3*self.zoom)
    self.column3 = self.column3 + 100

    local categoryWid = math.max(100,self.column4-self.column3-1)
    if self.column3 - 1 + categoryWid > self.width then
        self.column3 = self.width - categoryWid + 1
    end

    self.nameHeader = ISResizableButton:new(self.column2, 0, (self.column3 - self.column2), self.headerHgt, getText("IGUI_invpanel_Type"), self, ISInventoryPane.sortByName)
	self.nameHeader:initialise()
	self.nameHeader.borderColor.a = 0.2
	self.nameHeader.minimumWidth = 100
	self.nameHeader.onresize = { ISInventoryPane.onResizeColumn, self, self.nameHeader }
    self.nameHeader:setVisible(false)
	self:addChild(self.nameHeader)

	self.typeHeader = ISResizableButton:new(self.column3-1, 0, self.column4 - self.column3 + 1, self.headerHgt, getText("IGUI_invpanel_Category"), self, ISInventoryPane.sortByType)
	self.typeHeader.borderColor.a = 0.2
	self.typeHeader.anchorRight = true
	self.typeHeader.minimumWidth = 100
	self.typeHeader.resizeLeft = true
	self.typeHeader.onresize = { ISInventoryPane.onResizeColumn, self, self.typeHeader }
	self.typeHeader:initialise()
    self.typeHeader:setVisible(false)
	self:addChild(self.typeHeader)

    self.columnResizeButtonSize = self.itemHgt / 2
    self.columnResizeButton = ISButton:new(self.column3 - self.columnResizeButtonSize, 0, self.columnResizeButtonSize, self.columnResizeButtonSize, "", self, nil)
    self.columnResizeButton:initialise()
    self.columnResizeButton.prerender = function(btn)
        local alpha = (self.resizingColumn or btn.mouseOver) and 1 or 0.6
        btn:drawTextureScaled(self.columnResizerIcon, 0, 0, btn.width, btn.height, alpha, 0.8, 0.8, 0.8)
    end

    self.columnResizeButton:setVisible(true)
    self.columnResizeButton.onMouseDown = function(button, x, y)
        self.resizingColumn = true
        return true
    end

    self:addChild(self.columnResizeButton)

	local btnHgt = self.itemHgt
    self.contextButton1 = CleanUI_LongButton:new(0, 0, 10, btnHgt, getText("ContextMenu_Grab"), self, ISInventoryPane.onContext)
    self.contextButton1:initialise()
    self:addChild(self.contextButton1)
    self.contextButton1:setFont(self.font)
    self.contextButton1:setVisible(false)

    self.contextButton2 = CleanUI_LongButton:new(0, 0, 10, btnHgt, getText("IGUI_invpanel_Pack"), self, ISInventoryPane.onContext)
    self.contextButton2:initialise()
    self:addChild(self.contextButton2)
    self.contextButton2:setFont(self.font)
    self.contextButton2:setVisible(false)

    self.contextButton3 = CleanUI_LongButton:new(0, 0, 10, btnHgt, getText("IGUI_invpanel_Pack"), self, ISInventoryPane.onContext)
    self.contextButton3:initialise()
    self:addChild(self.contextButton3)
    self.contextButton3:setFont(self.font)
    self.contextButton3:setVisible(false)

    self.vscroll = CleanUI.ScrollBar:new(self)
    self.vscroll:initialise()
    self:addChild(self.vscroll)
end

function ISInventoryPane:onResizeColumn(button)
	if button == self.nameHeader then
		self.column3 = self.nameHeader.x + self.nameHeader.width
		self.typeHeader:setX(self.column3 - 1)
		self.typeHeader:setWidth(self.width - self.typeHeader.x)
	end
	if button == self.typeHeader then
		self.nameHeader:setWidth(self.typeHeader.x - self.column2 + 1)
		self.column3 = self.typeHeader.x
	end
    if self.columnResizeButton then
        self.columnResizeButton:setX(self.column3 - self.columnResizeButtonSize)
    end
end

function ISInventoryPane:onResize()
	ISPanel.onResize(self)
	if self.typeHeader:getWidth() == self.typeHeader.minimumWidth then
		self.column3 = self.width - self.typeHeader:getWidth() + 1
		self.nameHeader:setWidth(self.column3 - self.column2)
		self.typeHeader:setX(self.column3 - 1)
	end
	self.column4 = self.width
    
    if self.columnResizeButton then
        self.columnResizeButton:setX(self.column3 - self.columnResizeButtonSize)
    end
end

function ISInventoryPane:onContext(button)

    local playerObj = getSpecificPlayer(self.player)
    local playerInv = playerObj:getInventory()
    local lootInv = getPlayerLoot(self.player).inventory


    if button.mode == "unpack" then
        local k = self.items[self.buttonOption]
        local items = ISInventoryPane.getActualItems({k})
        ISInventoryPaneContextMenu.onMoveItemsTo(items, playerInv, self.player)
    end
    if button.mode == "grab" then
        local k = self.items[self.buttonOption]
        local items = ISInventoryPane.getActualItems({k})
        ISInventoryPaneContextMenu.onGrabItems(items, self.player)
    end
    if button.mode == "grab1" then
        local k = self.items[self.buttonOption]
        local items = ISInventoryPane.getActualItems({k})
        ISInventoryPaneContextMenu.onGrabOneItems(items, self.player)
    end
    if button.mode == "drop" then
        local k = self.items[self.buttonOption]
        local items = ISInventoryPane.getActualItems({k})
        ISInventoryPaneContextMenu.onDropItems(items, self.player)
    end
    if button.mode == "drop1" then
        local k = self.items[self.buttonOption]
        local items = ISInventoryPane.getActualItems({k})
        ISInventoryPaneContextMenu.onDropItems({items[1]}, self.player)
    end
    if button.mode == "place" then
        local k = self.items[self.buttonOption]
        local items = ISInventoryPane.getActualItems({k})
        local mo = ISMoveableCursor:new(getSpecificPlayer(self.player))
        getCell():setDrag(mo, mo.player)
        mo:setMoveableMode("place")
        mo:tryInitialItem(items[1])
    end

	getPlayerLoot(self.player).inventoryPane.selected = {}
	getPlayerInventory(self.player).inventoryPane.selected = {}
end


function ISInventoryPane:collapseAll(button)
    for k, v in pairs(self.collapsed) do
        self.collapsed[k] = true
    end
    self:refreshContainer()
end

function ISInventoryPane:expandAll(button)
    for k, v in pairs(self.collapsed) do
        self.collapsed[k] = false
    end
    self:refreshContainer()
end

function ISInventoryPane:onFilterMenu(button)
    local playerObj = getSpecificPlayer(self.player)
    local playerInv = playerObj:getInventory()
    if playerObj:isAsleep() then return end

    local x = button:getAbsoluteX()
    local y = button:getAbsoluteY()
    local context = ISContextMenu.get(self.player, x, y)

    getCell():setDrag(nil, self.player)

    local weight = context:addOption(getText("IGUI_invpanel_weight"), nil, nil)
    local subMenuWeight = ISContextMenu:getNew(context)
    context:addSubMenu(weight, subMenuWeight)

    subMenuWeight:addOption(getText("IGUI_invpanel_ascending"), self, ISInventoryPane.sortByWeight, true)
    subMenuWeight:addOption(getText("IGUI_invpanel_descending"), self, ISInventoryPane.sortByWeight, false)
end

function ISInventoryPane:sortByWeight(_isAscending)
    if _isAscending and self.itemSortFunc ~= ISInventoryPane.itemSortByWeightAsc then
        self.itemSortFunc = ISInventoryPane.itemSortByWeightAsc
        self:refreshContainer()
    end
    if (not _isAscending) and self.itemSortFunc ~= ISInventoryPane.itemSortByWeightDesc then
        self.itemSortFunc = ISInventoryPane.itemSortByWeightDesc
        self:refreshContainer()
    end
end

function ISInventoryPane:sortByName(button)
    if self.itemSortFunc == ISInventoryPane.itemSortByNameInc then
        self.itemSortFunc = ISInventoryPane.itemSortByNameDesc
    else
        self.itemSortFunc = ISInventoryPane.itemSortByNameInc
    end
    self:refreshContainer()
end

function ISInventoryPane:sortByType(button)
    if self.itemSortFunc == ISInventoryPane.itemSortByCatInc then
        self.itemSortFunc = ISInventoryPane.itemSortByCatDesc
    else
        self.itemSortFunc = ISInventoryPane.itemSortByCatInc
    end
    self:refreshContainer()
end

function ISInventoryPane:SaveLayout(name, layout)
    layout.column2 = self.nameHeader.width
    if self.itemSortFunc == self.itemSortByNameInc then layout.sortBy = "nameInc" end
    if self.itemSortFunc == self.itemSortByNameDesc then layout.sortBy = "nameDesc" end
    if self.itemSortFunc == self.itemSortByCatInc then layout.sortBy = "catInc" end
    if self.itemSortFunc == self.itemSortByCatDesc then layout.sortBy = "catDesc" end
end

function ISInventoryPane:RestoreLayout(name, layout)
    if layout.column2 and tonumber(layout.column2) then
        self.nameHeader:setWidth(tonumber(layout.column2))
        self:onResizeColumn(self.nameHeader)
    end
    if layout.sortBy == "nameInc" then self.itemSortFunc = self.itemSortByNameInc end
    if layout.sortBy == "nameDesc" then self.itemSortFunc = self.itemSortByNameDesc end
    if layout.sortBy == "catInc" then self.itemSortFunc = self.itemSortByCatInc end
    if layout.sortBy == "catDesc" then self.itemSortFunc = self.itemSortByCatDesc end
    self:refreshContainer()
end

function ISInventoryPane:rowAt(x, y)
	local rowCount = math.floor((self:getScrollHeight() - self.headerHgt) / self.itemHgt)
	if rowCount > 0 then
		return math.floor((y) / self.itemHgt) + 1
	end
	return -1
end

function ISInventoryPane:topOfItem(index)
	local rowCount = math.floor((self:getScrollHeight() - self.headerHgt) / self.itemHgt)
	if rowCount > 0 then
		return (index - 1) * self.itemHgt
	end
	return -1
end

function ISInventoryPane:onMouseWheel(del)
    if self.inventoryPage.isCollapsed then return false end
    if self.inventoryPage:isCycleContainerKeyDown() then return false end
	local yScroll = self.smoothScrollTargetY or self:getYScroll()
	local topRow = self:rowAt(0, -yScroll)
	if self.items[topRow] then
		if not self.smoothScrollTargetY then self.smoothScrollY = self:getYScroll() end
		local y = self:topOfItem(topRow)
		if del < 0 then
			if yScroll == -y and topRow > 1 then
				y = self:topOfItem(topRow - 1)
			end
			self.smoothScrollTargetY = -y
		else
			self.smoothScrollTargetY = -(y + self.itemHgt)
		end
	else
		self:setYScroll(self:getYScroll() - (del*9))
	end
    return true
end

function ISInventoryPane:selectIndex(index)
	local listItem = self.items[index]
	if not listItem then return end
	self.selected[index] = listItem
    if not instanceof(listItem, "InventoryItem") and not self.collapsed[listItem.name] then
        for i=2,#listItem.items do
            self.selected[index+i-1] = listItem.items[i]
            if i == ISInventoryPane.MAX_ITEMS_IN_STACK_TO_RENDER + 1 then break end
        end
    else
        local v = self.items[index-1]
        if v ~= nil and not instanceof(v, "InventoryItem") and not self.collapsed[v.name] and #v.items == 2 then
            self.selected[index-1] = v
        end
    end
end

function ISInventoryPane:onMouseMoveOutside(dx, dy)
	local x = self:getMouseX()
	local y = self:getMouseY()
    self.buttonOption = 0

    self.contextButton1:setVisible(false)
    self.contextButton2:setVisible(false)
    self.contextButton3:setVisible(false)

    self.mouseOverOption = 0

    if(self.draggingMarquis) then
		local x2 = self.draggingMarquisX
		local y2 = self.draggingMarquisY
        if(self:getMouseY() + self:getYScroll() > self:getHeight()) then
            self:setYScroll(self:getYScroll() - (9))
        end
        if(self:getMouseY() + self:getYScroll() < 0) then
            self:setYScroll(self:getYScroll() + (9))
        end

		if (x2 < x) then
			local xt = x
			x = x2
			x2 = xt
		end
		if (y2 < y) then
			local yt = y
			y = y2
			y2 = yt
		end

		if x > self.column3 then
			return
		end

		local startY = math.floor((y-self.headerHgt) / self.itemHgt) + 1
		local endY = math.floor((((y2)-self.headerHgt) / self.itemHgt)) + 1

		if(startY < 1) then
			startY = 1
		end

		self.selected = {}
		for i=startY, endY do
			self:selectIndex(i)
		end
	end

	if self.dragging and not self.dragStarted and (math.abs(x - self.draggingX) > 4 or math.abs(y - self.draggingY) > 4) then
		self.dragStarted = true
	end
end

function ISInventoryPane:hideButtons()
    self.contextButton1:setVisible(false)
    self.contextButton2:setVisible(false)
    self.contextButton3:setVisible(false)
    -- Clear the hover-button cache whenever the buttons are explicitly hidden.
    self._cleanUIButtonsRow = nil
    self._cleanUIButtonsEntry = nil
    self._cleanUIButtonsInventory = nil
    self._cleanUIButtonsVisible1 = nil
    self._cleanUIButtonsVisible2 = nil
    self._cleanUIButtonsVisible3 = nil
end

function ISInventoryPane:doButtons(y)

    self.contextButton1:setVisible(false)
    self.contextButton2:setVisible(false)
    self.contextButton3:setVisible(false)

	if UIManager.getSpeedControls():getCurrentGameSpeed() == 0 or
			getPlayerContextMenu(self.player):isAnyVisible() or
			getSpecificPlayer(self.player):isAsleep() then
		return
	end

	if ISMouseDrag.dragging and #ISMouseDrag.dragging > 0 then
		return
	end

    local cachedEntry = self.items and self.items[y]
    local canUseButtonCache = self.inventory and self.inventory.isDrawDirty and not self.inventory:isDrawDirty()
    if canUseButtonCache
            and self._cleanUIButtonsRow == y
            and self._cleanUIButtonsEntry == cachedEntry
            and self._cleanUIButtonsInventory == self.inventory then
        -- Smooth scrolling repeatedly calls doButtons for the same row; only Y changes.
        local ypos = ((y - 1) * self.itemHgt) + self.headerHgt + self:getYScroll()
        if self._cleanUIButtonsVisible1 then
            self.contextButton1:setY(ypos)
            self.contextButton1:setVisible(true)
        end
        if self._cleanUIButtonsVisible2 then
            self.contextButton2:setY(ypos)
            self.contextButton2:setVisible(true)
        end
        if self._cleanUIButtonsVisible3 then
            self.contextButton3:setY(ypos)
            self.contextButton3:setVisible(true)
        end
        self.buttonOption = y
        return
    end

    local count = 1
    local item = self.items[y]
    if not instanceof(item, "InventoryItem") then
        count = item.count-1
        item = item.items[1]
    end
    local isNoDropMoveable = instanceof(item, "Moveable") and not item:CanBeDroppedOnFloor()

    local mode1,mode2,mode3 = nil,nil,nil
    local label1,label2,label3 = nil,nil,nil

    if self.inventory:isInCharacterInventory(getSpecificPlayer(self.player)) and self.inventory ~= getSpecificPlayer(self.player):getInventory() then
       -- unpack, drop
        mode1,label1 = "unpack", getText("IGUI_invpanel_unpack")
        if isNoDropMoveable then
            -- No 'Drop' option
        elseif count == 1 then
            mode2,label2 = "drop", getText("ContextMenu_Drop")
        else
            mode2,label2 = "drop", getText("IGUI_invpanel_drop_all")
            mode3,label3 = "drop1", getText("IGUI_invpanel_drop_one")
        end
        if not instanceof(self.items[y], "InventoryItem") then
            local fav = true
            local firstFav = true
            for i,v in ipairs(self.items[y].items) do
                if i == 1 then firstFav = v:isFavorite() end
                if not v:isFavorite() then
                    fav = false
                end
            end
            if fav then
                mode2 = nil
                mode3 = nil
            elseif count > 1 and firstFav then
                mode3 = nil
            end
        else
            if self.items[y]:isFavorite() then
                mode2 = nil
                mode3 = nil
            end
        end
    elseif self.inventory == getSpecificPlayer(self.player):getInventory() then
        if isNoDropMoveable then
            -- No 'Drop' option
        elseif count == 1 then
            mode1,label1 = "drop", getText("ContextMenu_Drop")
        else
            mode1,label1 = "drop", getText("IGUI_invpanel_drop_all")
            mode2,label2 = "drop1", getText("IGUI_invpanel_drop_one")
        end
        if not instanceof(self.items[y], "InventoryItem") then
            local fav = true
            local firstFav = true
            for i,v in ipairs(self.items[y].items) do
                if i == 1 then firstFav = v:isFavorite() end
                if not v:isFavorite() then
                    fav = false
                end
            end
            if fav then
                mode1 = nil
                mode2 = nil
            elseif count > 1 and firstFav then
                mode2 = nil
            end
        else
            if self.items[y]:isFavorite() then
                mode1 = nil
                mode2 = nil
            end
        end

        if instanceof(item, "Moveable") then
            if mode1 and mode2 then
                mode3,label3 = "place", getText("IGUI_Place")
            elseif mode1 then
                mode2,label2 = "place", getText("IGUI_Place")
            else
                mode1,label1 = "place", getText("IGUI_Place")
            end
        end
    else
        if count == 1 then
            mode1,label1 = "grab",getText("ContextMenu_Grab")
        else
            mode1,label1 = "grab",getText("ContextMenu_Grab_all")
            mode2,label2 = "grab1",getText("ContextMenu_Grab_one")
        end
    end

    local ypos = ((y-1)*self.itemHgt) + self.headerHgt
    ypos = ypos + self:getYScroll()

	if getCore():getGameMode() ~= "Tutorial" then
		if mode1 then
			self.contextButton1:setTitle(label1)
			self.contextButton1.mode = mode1
			self.contextButton1:setWidthToTitle()
			self.contextButton1:setX(self.column3)
			self.contextButton1:setY(ypos)
			self.contextButton1:setVisible(true)
		end
		if mode2 then
			self.contextButton2:setTitle(label2)
			self.contextButton2.mode = mode2
			self.contextButton2:setWidthToTitle()
			self.contextButton2:setX(self.contextButton1:getRight() + 1)
			self.contextButton2:setY(ypos)
			self.contextButton2:setVisible(true)
		end
		if mode3 then
			self.contextButton3:setTitle(label3)
			self.contextButton3.mode = mode3
			self.contextButton3:setWidthToTitle()
			self.contextButton3:setX(self.contextButton2:getRight() + 1)
			self.contextButton3:setY(ypos)
			self.contextButton3:setVisible(true)
		end
	end

    self.buttonOption = y
    self._cleanUIButtonsRow = y
    self._cleanUIButtonsEntry = self.items[y]
    self._cleanUIButtonsInventory = self.inventory
    self._cleanUIButtonsVisible1 = mode1 ~= nil and getCore():getGameMode() ~= "Tutorial"
    self._cleanUIButtonsVisible2 = mode2 ~= nil and getCore():getGameMode() ~= "Tutorial"
    self._cleanUIButtonsVisible3 = mode3 ~= nil and getCore():getGameMode() ~= "Tutorial"
end

function ISInventoryPane:toggleStove()
    local stove = self.inventory:getParent()
    stove:Toggle()
    return stove:Activated()
end

function ISInventoryPane:sortItemsByType(items)
	table.sort(items, function(a,b)
		if a:getContainer() and a:getContainer() == b:getContainer() and a:getDisplayName() == b:getDisplayName() then
			return a:getContainer():getItems():indexOf(a) < b:getContainer():getItems():indexOf(b)
		end
		return not string.sort(a:getType(), b:getType())
	end)
end

function ISInventoryPane:sortItemsByWeight(items)
	table.sort(items, function(a,b)
		if a:getContainer() and a:getContainer() == b:getContainer() and a:getDisplayName() == b:getDisplayName() then
			return a:getContainer():getItems():indexOf(a) < b:getContainer():getItems():indexOf(b)
		end
		return a:getUnequippedWeight() < b:getUnequippedWeight()
	end)
end

function ISInventoryPane:sortItemsByTypeAndWeight(items)
	local indexMap = {}
	local containers = {}
	local allIndexMap = {}
	for index,item in ipairs(items) do
		local container = item:getContainer()
		if container and not containers[container] then
			containers[container] = true
			local containerItems = container:getItems()
			for i=1,containerItems:size() do
				indexMap[containerItems:get(i-1)] = i
			end
		end
		allIndexMap[item] = index
	end

	local itemsByName = {}
	for _,item in ipairs(items) do
		local key = item:getDisplayName()
		itemsByName[key] = itemsByName[key] or {}
		table.insert(itemsByName[key], item)
	end

	local sorted = {}
	for _,itemList in pairs(itemsByName) do
		timSort(itemList, function(a,b)
			if a:getContainer() and (a:getContainer() == b:getContainer()) then
                -- this sometimes catches items that have the same parent container
                -- returned by :getContainer() but parent:getItems() failed to return
                -- both of them (some desync issue elsewhere?). In which case it doesnt
                -- matter which gets sorted first.
                if not indexMap[a] or not indexMap[b] then return false end
				return indexMap[a] < indexMap[b]
			end
			return allIndexMap[a] < allIndexMap[b]
		end)
		table.insert(sorted, itemList)
	end
	timSort(sorted, function(a,b)
		local wa = a[1]:getUnequippedWeight()
		local wb = b[1]:getUnequippedWeight()
		if wa < wb then
			return true
		end
		if wa == wb then
			return allIndexMap[a[1]] < allIndexMap[b[1]]
		end
		return false
	end)
	table.wipe(items)
	local count = 1
	for _,itemList in ipairs(sorted) do
		for _,item in ipairs(itemList) do
			items[count] = item
			count = count + 1
		end
	end
end

function ISInventoryPane:transferItemsByWeight(items, container)
	local playerObj = getSpecificPlayer(self.player)
	if true then
		self:sortItemsByTypeAndWeight(items)
	else
		self:sortItemsByType(items)
		self:sortItemsByWeight(items)
	end

	for _,item in ipairs(items) do
		if not CleanUI_transferGuardIsItemAllowed(container, item) then
			-- Skip items that the destination container refuses or cannot validate safely.
		elseif CleanUI_transferGuardContainerType(container) == "floor" then
			ISInventoryPaneContextMenu.dropItem(item, self.player)
		else
			local isGrabbingCorpse = CleanUI_transferGuardSafe(function()
				return corpseStorageCheck.isPlayerGrabbingCorpseItem(self.player, item, container)
			end)

			if isGrabbingCorpse then
				local srcContainer = CleanUI_transferGuardSafe(function() return item:getContainer() end)
				local action = ISGrabCorpseItem:new(playerObj, item)
				CleanUI_queueTimedActionSafely(action, "grab corpse transfer", item, srcContainer, container)
				break
			end

			CleanUI_queueInventoryTransferSafely(playerObj, item, container)
		end
	end
end

function ISInventoryPane:removeAll(player)
	if self.removeAllDialog then
		self.removeAllDialog:destroy()
	end
	local width = 350
	local x = getPlayerScreenLeft(player) + (getPlayerScreenWidth(player) - width) / 2
	local height = 120
	local y = getPlayerScreenTop(player) + (getPlayerScreenHeight(player) - height) / 2
	local modal = ISModalDialog:new(x,y, width, height, getText("IGUI_ConfirmDeleteItems"), true, self, ISInventoryPane.onConfirmDelete, player)
	modal:initialise()
	self.removeAllDialog = modal
	modal:addToUIManager()
	if JoypadState.players[player+1] then
		modal.prevFocus = JoypadState.players[player+1].focus
		setJoypadFocus(player, modal)
	end
end

function ISInventoryPane:onConfirmDelete(button)
	if button.internal == "YES" then
		local object = self.inventory:getParent()
		local playerObj = getSpecificPlayer(self.player)
		local args = { x = object:getX(), y = object:getY(), z = object:getZ(), index = object:getObjectIndex() }
		sendClientCommand(playerObj, 'object', 'emptyTrash', args)
	end
	self.removeAllDialog = nil
end

function ISInventoryPane:lootAll()
	local playerObj = getSpecificPlayer(self.player)
	local playerInv = getPlayerInventory(self.player).inventory
	local items = {}
	local it = self.inventory:getItems()
	local heavyItem = nil
	if luautils.walkToContainer(self.inventory, self.player) then
		for i = 0, it:size()-1 do
			local item = it:get(i)
			if not item:isUnwanted(playerObj) then
                if isForceDropHeavyItem(item) then
                    heavyItem = item
                else
                    table.insert(items, item)
                end
            end
		end
		if heavyItem and it:size() == 1 then
			ISInventoryPaneContextMenu.equipHeavyItem(playerObj, heavyItem)
			return
		end
		self:transferItemsByWeight(items, playerInv)
	end
	self.selected = {}
	getPlayerLoot(self.player).inventoryPane.selected = {}
	getPlayerInventory(self.player).inventoryPane.selected = {}
end

function ISInventoryPane:transferAll()
	local playerObj = getSpecificPlayer(self.player)
	local playerLoot = getPlayerLoot(self.player).inventory
	local hotBar = getPlayerHotbar(self.player)
    local it = self.inventory:getItems();
	local items = {}
	if luautils.walkToContainer(self.inventory, self.player) then
		local toFloor = getPlayerLoot(self.player).inventory:getType() == "floor"
		for i = 0, it:size()-1 do
			local item = it:get(i);
			local ok = not item:isEquipped() and not item:isItemType(ItemType.KEY_RING) and not item:hasTag(ItemTag.KEY_RING) and not hotBar:isInHotbar(item)
			if item:isFavorite() then
				ok = false
			end
			if toFloor and instanceof(item, "Moveable") and item:getSpriteGrid() == nil and not item:CanBeDroppedOnFloor() then
				ok = false
			end
			if ok then
				table.insert(items, item)
			end
		end
		self:transferItemsByWeight(items, playerLoot)
	end
    self.selected = {};
    getPlayerLoot(self.player).inventoryPane.selected = {};
    getPlayerInventory(self.player).inventoryPane.selected = {};
end


local function CleanUI_normalizeSearchText(value)
    -- Normalize search input once so all matching paths use plain lowercase text.
    if value == nil then return nil end
    value = tostring(value):lower()
    if value == "" then return nil end
    return value
end

local function CleanUI_itemMatchesNameSearch(itemGroup, searchText)
    -- Match the displayed group name first, then fall back to each item's display name and script type.
    if not searchText then return true end
    local itemName = CleanUI_normalizeSearchText(itemGroup.name)
    if itemName and string.find(itemName, searchText, 1, true) then
        return true
    end
    for _, item in ipairs(itemGroup.items) do
        local displayName = CleanUI_normalizeSearchText(item:getDisplayName())
        local typeName = CleanUI_normalizeSearchText(item:getType())
        if (displayName and string.find(displayName, searchText, 1, true)) or
                (typeName and string.find(typeName, searchText, 1, true)) then
            return true
        end
    end
    return false
end

local function CleanUI_itemMatchesCategorySearch(itemGroup, searchText)
    -- Match the category shown in CleanUI and any per-item display/category fallback.
    if not searchText then return true end
    local category = CleanUI_normalizeSearchText(itemGroup.cat)
    if category and string.find(category, searchText, 1, true) then
        return true
    end
    for _, item in ipairs(itemGroup.items) do
        local displayCategory = CleanUI_normalizeSearchText(item:getDisplayCategory())
        local categoryName = CleanUI_normalizeSearchText(item:getCategory())
        if (displayCategory and string.find(displayCategory, searchText, 1, true)) or
                (categoryName and string.find(categoryName, searchText, 1, true)) then
            return true
        end
    end
    return false
end

function ISInventoryPane:searchContainer(searchText, searchCategoryText, searchMode)
    -- CleanUI supports name-only, category-only and combined search modes.
    self.searchMode = searchMode or self.searchMode or "name"
    if self.searchMode == "category" and searchCategoryText == nil then
        searchCategoryText = searchText
        searchText = nil
    end
    self.searchText = CleanUI_normalizeSearchText(searchText)
    self.searchCategoryText = CleanUI_normalizeSearchText(searchCategoryText)
    self:refreshContainer()
end

function ISInventoryPane:clearSearch()
    self.searchText = nil
    self.searchCategoryText = nil
    self:refreshContainer()
end

function ISInventoryPane:onMouseMove(dx, dy)
	if self.player ~= 0 then return end

    local x = self:getMouseX()
    local y = self:getMouseY()

    if self.resizingColumn then
        local newColumn3 = x + self.columnResizeButtonSize / 2
        newColumn3 = math.max(self.column2 + 100, math.min(self.width - 100, newColumn3))
        
        local newNameWidth = newColumn3 - self.column2
        self.nameHeader:setWidth(newNameWidth)
        self:onResizeColumn(self.nameHeader)
        
        if self.columnResizeButton then
            self.columnResizeButton:setX(newColumn3 - self.columnResizeButtonSize)
        end
        return
    end

    self.contextButton1:setVisible(false)
    self.contextButton2:setVisible(false)
    self.contextButton3:setVisible(false)

    if(self.draggingMarquis) then
		local x2 = self.draggingMarquisX
		local y2 = self.draggingMarquisY

		if (x2 < x) then
			local xt = x
			x = x2
			x2 = xt
		end
		if (y2 < y) then
			local yt = y
			y = y2
			y2 = yt
		end

		if x > self.column3 then
			return
		end

		local startY = math.floor((y-self.headerHgt) / self.itemHgt) + 1
		local endY = math.floor((((y2)-self.headerHgt) / self.itemHgt)) + 1

		if(startY < 1) then
			startY = 1
		end

		self.selected = {}
		for i=startY, endY do
			self:selectIndex(i)
		end
    else
        if self.dragging == nil and x >= 0 and y >= 0 and x >= self.column3 and not isShiftKeyDown() and not self:isMouseOverScrollBar() then
            y = y - self.headerHgt
            y = y / self.itemHgt
            y = math.floor(y + 1)
            local topOfRow = ((y - 1) * self.itemHgt) + self:getYScroll() + self.headerHgt
            if self.items[y] and topOfRow >= self.headerHgt then
                self:doButtons(y)
            end
        end
		if self.dragging == nil and x >= 0 and y >= 0 and x < self.column3 then
            y = y - self.headerHgt
            y = y / self.itemHgt
            y = math.floor(y + 1)
            self.mouseOverOption = y
		else
			self.mouseOverOption = 0
		end

	end
	if self.dragging and not self.dragStarted and (math.abs(x - self.draggingX) > 4 or math.abs(y - self.draggingY) > 4) then
		self.dragStarted = true
	end
end

function ISInventoryPane:updateTooltip()
	if not self:isReallyVisible() then
		if self.parent.onCharacter then
			Key.setHighlightDoors(self.player, nil)
		end
		return -- in the main menu
	end
	local item = nil
	if self.doController and self.joyselection then
		if self.joyselection < 0 then self.joyselection = #self.items - 1 end
		if self.joyselection >= #self.items then self.joyselection = 0 end
		item = self.items[self.joyselection+1]
	end
	if not self.doController and not self.dragging and not self.draggingMarquis and self:isMouseOver() then
		local x = self:getMouseX()
		local y = self:getMouseY()
		if x < self.column3 and y + self:getYScroll() >= self.headerHgt then
			y = y - self.headerHgt
			y = y / self.itemHgt
			self.mouseOverOption = math.floor(y + 1)
			item = self.items[self.mouseOverOption]
		end
	end
	local weightOfStack = 0.0
	if item and not instanceof(item, "InventoryItem") then
		if #item.items > 2 then
			weightOfStack = item.weight
		end
		item = item.items[1]
	end
	if getPlayerContextMenu(self.player):isAnyVisible() then
		item = nil
	end
	if item and self.toolRender and (item == self.toolRender.item) and
			(weightOfStack == self.toolRender.tooltip:getWeightOfStack()) and
			self.toolRender:isVisible() then
		if self.parent.onCharacter then
			Key.setHighlightDoors(self.player, item)
		end
		return
	end
	if item then
		if self.toolRender then
			self.toolRender:setItem(item)
			self.toolRender:setVisible(true)
			self.toolRender:addToUIManager()
			self.toolRender:bringToTop()
		else
			self.toolRender = ISToolTipInv:new(item)
			self.toolRender:initialise()
			self.toolRender:addToUIManager()
			self.toolRender:setVisible(true)
			self.toolRender:setOwner(self)
			self.toolRender:setCharacter(getSpecificPlayer(self.player))
--			self.toolRender:setX(getPlayerScreenLeft(self.player) + 60)
--			self.toolRender:setY(getPlayerScreenTop(self.player) + 60)
			self.toolRender.anchorBottomLeft = { x = self:getAbsoluteX() + self.column2, y = self:getParent():getAbsoluteY() }
		end
		self.toolRender.followMouse = not self.doController
		self.toolRender.tooltip:setWeightOfStack(weightOfStack)
		if not self.doController then
--			self.toolRender:setX(getMouseX())
--			self.toolRender:setY(getMouseY())
		end
	elseif self.toolRender then
		self.toolRender:removeFromUIManager()
		self.toolRender:setVisible(false)
--		self.toolRender = nil
	end

	-- Hack for highlighting doors when a Key tooltip is displayed.
	if self.parent.onCharacter then
		if not self.toolRender or not self.toolRender:getIsVisible() then
			item = nil
		end
		Key.setHighlightDoors(self.player, item)
	end

	local inventoryPage = getPlayerInventory(self.player)
	local inventoryTooltip = inventoryPage and inventoryPage.inventoryPane.toolRender
	local lootPage = getPlayerLoot(self.player)
	local lootTooltip = lootPage and lootPage.inventoryPane.toolRender
	UIManager.setPlayerInventoryTooltip(self.player,
		inventoryTooltip and inventoryTooltip.javaObject or nil,
		lootTooltip and lootTooltip.javaObject or nil)
end

--************************************************************************--
--** ISInventoryPane:onMouseUpOutside
--**
--************************************************************************--
function ISInventoryPane:onMouseDownOutside(x, y)
	self.dragging = nil
	self.draggedItems:reset()
	if self:isMouseOverScrollBar() then
		self.clickedScrollBar = true
	else
		self.clickedScrollBar = false
		self.selected = {}
	end
end

function ISInventoryPane:onMouseUpOutside(x, y)
    if self.resizingColumn then
        self.resizingColumn = false
        return
    end
    self.previousMouseUp = self.mouseOverOption
    if self.draggingMarquis then
        self:onMouseUp(x, y)
    elseif not self.clickedScrollBar then
        self.selected = {}
    end
    self.draggingMarquis = false
    self.clickedScrollBar = false
end

function ISInventoryPane.getActualItems(items)
	local ret = {}
	local contains = {}
	for _,item in ipairs(items) do
		if instanceof(item, "InventoryItem") then
			if not contains[item] then
				-- The top-level group and its children might both be selected.
				table.insert(ret, item)
				contains[item] = true
			end
		else
			-- The first item is a dummy duplicate, skip it.
			for i=2,#item.items do
				local item2 = item.items[i]
				if not contains[item2] then
					table.insert(ret, item2)
					contains[item2] = true
				end
			end
		end
	end
	return ret
end

function ISInventoryPane.getActualUniqueItems(items)
	local ret = {}
	local contains = {}
	for _,item in ipairs(items) do
		if instanceof(item, "InventoryItem") then
			if not contains[item] then
				-- The top-level group and its children might both be selected.
				table.insert(ret, item)
				contains[item] = true
			end
		else
            local item2 = item.items[2]
            if not contains[item2] then
                table.insert(ret, item2)
                contains[item2] = true
            end
		end
	end
	return ret
end

function ISInventoryPane:doContextualDblClick(item)
	local playerObj = getSpecificPlayer(self.player);
	if instanceof(item, "HandWeapon") then
		if playerObj:isHandItem(item) then
			ISInventoryPaneContextMenu.unequipItem(item, self.player);
		elseif item:getCondition() > 0 then
			ISInventoryPaneContextMenu.equipWeapon(item, true, item:isTwoHandWeapon(), self.player);
		end
	elseif instanceof(item, "Clothing") then
		if playerObj:isEquipped(item) then
			ISInventoryPaneContextMenu.onUnEquip({item}, self.player);
		else
			ISInventoryPaneContextMenu.onWearItems({item}, self.player);
		end
	elseif instanceof(item, "InventoryContainer") and item:canBeEquipped() ~= nil and item:canBeEquipped() ~= "" then
		if playerObj:isEquipped(item) then
			ISInventoryPaneContextMenu.onUnEquip({item}, self.player);
		else
			ISInventoryPaneContextMenu.onWearItems({item}, self.player);
		end
	elseif instanceof (item, "InventoryContainer") then
		if playerObj:isEquipped(item) then
			ISInventoryPaneContextMenu.onUnEquip({item}, self.player);
		else
            local primary = false
            if playerObj:getSecondaryHandItem() and not playerObj:getPrimaryHandItem() then
                primary = true
            end
			ISInventoryPaneContextMenu.equipWeapon(item, primary, false, self.player);
		end
	elseif instanceof(item, "Food") and item:getHungChange() < 0 and not item:getScriptItem():isCantEat() and (not item:isBurnt()) and (not item:isRotten())  and (not playerObj:isKnownPoison(item))
	and not (item:isbDangerousUncooked() and not item:isCooked()) then
        -- if playerObj:getMoodles():getMoodleLevel(MoodleType.FOOD_EATEN) < 3 or playerObj:getNutrition():getCalories() < 1000 then
        if playerObj:getMoodles():getMoodleLevel(MoodleType.FOOD_EATEN) < 3 then
            ISInventoryPaneContextMenu.onEatItems({item}, 1, self.player);
        end
    elseif instanceof(item, "Food") and item:getOpeningRecipe() and playerObj:getMoodles():getMoodleLevel(MoodleType.FOOD_EATEN) < 3 then
        local openingRecipe = item:getOpeningRecipe()
        if openingRecipe and getScriptManager():getCraftRecipe(openingRecipe) then
            openingRecipe = getScriptManager():getCraftRecipe(openingRecipe)
        else
            openingRecipe = nil
        end
        if openingRecipe then
            local containers = ISInventoryPaneContextMenu.getContainers(playerObj)
            local logic = HandcraftLogic.new(playerObj, nil, nil);

            logic:setContainers(containers);
            logic:setRecipeFromContextClick(openingRecipe, item);
            if not logic:canPerformCurrentRecipe() then
                openingRecipe = nil
            end
        end
        if openingRecipe then
            ISInventoryPaneContextMenu.onEatItems({item}, 1, self.player, openingRecipe, 100);
        end
    elseif instanceof(item, "DrainableComboItem") and (item:hasTag(ItemTag.CONSUMABLE)) and not item:hasTag(ItemTag.SHOW_POISON) then
-- 	    ISTimedActionQueue.add(ISTakePillAction:new(playerObj, item));
        if not item:getRequireInHandOrInventory() then
	        ISInventoryPaneContextMenu.takePill(item, self.player);
        else
            local list = item:getRequireInHandOrInventory();
            local found = false;
            if item:hasTag(ItemTag.SMOKABLE) and playerObj:getVehicle() and playerObj:getVehicle():canLightSmoke(playerObj) then found = true end
            if item:hasTag(ItemTag.SMOKABLE) and not found then
               found = ISInventoryPaneContextMenu.hasOpenFlame(playerObj)
            end
            if not found then
                for i=0,list:size()-1 do
                    local fullType = moduleDotType(item:getModule(), list:get(i))
                    if playerObj:getInventory():getFirstTypeEvalRecurse(fullType, predicateNotEmpty) then
                        found = true;
                        break;
                    end
                end
            end
            if found then
	            ISInventoryPaneContextMenu.takePill(item, self.player);
            end
        end
    elseif item:getFluidContainer() and (not item:getFluidContainer():isEmpty())  and item:getFluidContainer():getPrimaryFluid():isCategory(FluidCategory.Beverage) and (not item:getFluidContainer():isTaintedStatusKnown()) then
        if not item:isSealed() then
            ISInventoryPaneContextMenu.onDrinkFluid(item, 1, playerObj)
        else
            local openingRecipe = item:getOpeningRecipe()
            if openingRecipe and getScriptManager():getCraftRecipe(openingRecipe) then
                openingRecipe = getScriptManager():getCraftRecipe(openingRecipe)
            else
                openingRecipe = nil
            end
            if openingRecipe then
                local containers = ISInventoryPaneContextMenu.getContainers(playerObj)
                local logic = HandcraftLogic.new(playerObj, nil, nil);

                logic:setContainers(containers);
                logic:setRecipeFromContextClick(openingRecipe, item);
                if not logic:canPerformCurrentRecipe() then
                    openingRecipe = nil
                end
            end
            if openingRecipe then
                ISInventoryPaneContextMenu.onDrinkFluid(item, 1, playerObj, openingRecipe)
            end
        end
    elseif (item:isWaterSource() or (item:getFluidContainer() and item:getFluidContainer():getPrimaryFluid() and (item:getFluidContainer():getPrimaryFluid():getFluidTypeString() == "Water" or item:getFluidContainer():getPrimaryFluid():getFluidTypeString() == "CarbonatedWater"))) and playerObj:getStats():get(CharacterStat.THIRST) > 0.1  and (not item:getFluidContainer():isTaintedStatusKnown()) then
        local thirst = playerObj:getStats():get(CharacterStat.THIRST)
        local units = math.min((thirst * 2), item:getFluidContainer():getFluidAmount())
        local percent = units / item:getFluidContainer():getFluidAmount();
        if not item:isSealed() then
            ISInventoryPaneContextMenu.onDrinkFluid(item, percent, playerObj)
        else
            local openingRecipe = item:getOpeningRecipe()
            if openingRecipe and getScriptManager():getCraftRecipe(openingRecipe) then
                openingRecipe = getScriptManager():getCraftRecipe(openingRecipe)
            else
                openingRecipe = nil
            end
            if openingRecipe then
                local containers = ISInventoryPaneContextMenu.getContainers(playerObj)
                local logic = HandcraftLogic.new(playerObj, nil, nil);

                logic:setContainers(containers);
                logic:setRecipeFromContextClick(openingRecipe, item);
                if not logic:canPerformCurrentRecipe() then
                    openingRecipe = nil
                end
            end
            if openingRecipe then
                ISInventoryPaneContextMenu.onDrinkFluid(item, percent, playerObj, openingRecipe)
            end
        end
    elseif item:isCanBandage() then
        local body_part_damaged = ISInventoryPaneContextMenu.haveDamagePart(self.player)
        local worst_part = nil
        local worst_damage_level = 0
        for i,body_part in ipairs(body_part_damaged) do
            local damage_level = body_part:getBandageNeededDamageLevel()
            if worst_part == nil then
                worst_part = body_part
                worst_damage_level = damage_level
            elseif damage_level > worst_damage_level then
                worst_part = body_part
                worst_damage_level = damage_level
            end
        end
        if worst_part then
            ISInventoryPaneContextMenu.applyBandage(item, worst_part, self.player)
        end
    elseif item:IsMap() and not playerObj:tooDarkToRead() then
        ISInventoryPaneContextMenu.onCheckMap(item, self.player)
    elseif item:getScriptItem():isItemType(ItemType.LITERATURE) and not item:hasTag(ItemTag.UNINTERESTING) and (item:hasTag(ItemTag.PICTUREBOOK) or item:hasTag(ItemTag.PICTURE) or not playerObj:hasTrait(CharacterTrait.ILLITERATE)) and not playerObj:tooDarkToRead() then
        ISInventoryPaneContextMenu.readItem(item, self.player)
	elseif item:canBeActivated() then
		if playerObj:isHandItem(item) then
            if item:isActivated() then
                item:setActivated(false);
                item:playDeactivateSound();
            end
			ISInventoryPaneContextMenu.unequipItem(item, self.player);
		elseif item:getCondition() > 0 then
            local primary = false
            if playerObj:getSecondaryHandItem() and not playerObj:getPrimaryHandItem() then
                primary = true
            end
			ISInventoryPaneContextMenu.equipWeapon(item, primary, item:isTwoHandWeapon(), self.player, true);
		end
    elseif item:getDoubleClickRecipe() then
        local doubleClickRecipe = item:getDoubleClickRecipe()
        if doubleClickRecipe and getScriptManager():getCraftRecipe(doubleClickRecipe) then
            doubleClickRecipe = getScriptManager():getCraftRecipe(doubleClickRecipe)
        else
            doubleClickRecipe = nil
        end
        if doubleClickRecipe then
            ISInventoryPaneContextMenu.OnNewCraft(item, doubleClickRecipe, self.player)
        end
    elseif item:getScriptItem():isItemType(ItemType.RADIO) and item:getDeviceData() and item:getDeviceData():getIsPortable() then
		if playerObj:isHandItem(item) then
			ISInventoryPaneContextMenu.unequipItem(item, self.player);
		elseif item:getCondition() > 0 then
            local primary = false
            if playerObj:getSecondaryHandItem() and not playerObj:getPrimaryHandItem() then
                primary = true
            end
            ISInventoryPaneContextMenu.equipWeapon(item, primary, item:isTwoHandWeapon(), self.player);
        end
	end
end

local function CleanUI_getValidDblClickContainer(item)
    -- Validate the item/container reference right before walking to it.
    -- This avoids stale loot rows crashing when nearby containers switch rapidly.
    if not item or not instanceof(item, "InventoryItem") then
        return nil
    end

    local ok, container = pcall(function()
        return item:getContainer()
    end)

    if not ok or not container then
        return nil
    end

    -- luautils.walkToContainer() expects a real ItemContainer and calls getType().
    -- Check that call defensively without filtering out valid container types.
    local typeOk = pcall(function()
        return container:getType()
    end)

    if not typeOk then
        return nil
    end

    return container
end

function ISInventoryPane:onMouseDoubleClick(x, y)
	if self.vscroll and self:isMouseOverScrollBar() then
		return self.vscroll:onMouseDoubleClick(x - self.vscroll.x, y + self:getYScroll() - self.vscroll.y)
	end
	if self.items and self.mouseOverOption and self.previousMouseUp == self.mouseOverOption then
		if getCore():getGameMode() == "Tutorial" then
			if TutorialData.chosenTutorial.doubleClickInventory(self, x, y, self.mouseOverOption) then
				return
			end
		end
		local playerObj = getSpecificPlayer(self.player)
		local playerInv = getPlayerInventory(self.player).inventory
		local lootInv = getPlayerLoot(self.player).inventory
		local item = self.items[self.mouseOverOption]
		local doWalk = true
        local shiftHeld = isShiftKeyDown()
		if item and not instanceof(item, "InventoryItem") then
			-- expand or collapse...
			if x < self.column2 then
				self.collapsed[item.name] = not self.collapsed[item.name]
				self:refreshContainer()
				return
			end
			if item.items then
				for k, v in ipairs(item.items) do
                    if k ~= 1 then
                        local sourceContainer = CleanUI_getValidDblClickContainer(v)
                        if sourceContainer and sourceContainer ~= playerInv then
                            if doWalk then
                                if not luautils.walkToContainer(sourceContainer, self.player) then
                                    break
                                end
                                doWalk = false
                            end
                            if v:isHumanCorpse() then
                                ISTimedActionQueue.add(ISGrabCorpseItem:new(playerObj, v))
                                break
                            end
                            if isForceDropHeavyItem(v) then
                                ISInventoryPaneContextMenu.equipHeavyItem(playerObj, v)
                                break
                            end
                            ISTimedActionQueue.add(ISInventoryTransferUtil.newInventoryTransferAction(playerObj, v, sourceContainer, playerInv))
                            if instanceof(v, "Clothing") and not v:isBroken() and shiftHeld then
                                ISTimedActionQueue.add(ISWearClothing:new(playerObj, v))
                            end
                        elseif sourceContainer == playerInv then
                            local tItem = v
                            self:doContextualDblClick(tItem)
                            break
                        end
                    end
				end
			end
		elseif item then
            local itemContainer = CleanUI_getValidDblClickContainer(item)
            if itemContainer and itemContainer ~= playerInv then
                if isForceDropHeavyItem(item) then
                    ISInventoryPaneContextMenu.equipHeavyItem(playerObj, item)
                elseif luautils.walkToContainer(itemContainer, self.player) then
                    ISTimedActionQueue.add(ISInventoryTransferUtil.newInventoryTransferAction(playerObj, item, itemContainer, playerInv))
                end
            elseif itemContainer == playerInv then -- double click do some basic action, equip weapon/wear clothing...
                self:doContextualDblClick(item)
            end
		end
		self.previousMouseUp = nil
	end
end

function ISInventoryPane:onMouseUp(x, y)
	if self.player ~= 0 then return end

    if self.resizingColumn then
        self.resizingColumn = false
        return true
    end

    local playerObj = getSpecificPlayer(self.player)

	self.previousMouseUp = self.mouseOverOption
    if (not isShiftKeyDown() and not isCtrlKeyDown() and x >= self.column2 and  x == self.downX and y == self.downY) and  self.mouseOverOption ~= 0 and self.items[self.mouseOverOption] ~= nil then
        self.selected = {}
        self:selectIndex(self.mouseOverOption)
    end

    if ISMouseDrag.dragging ~= nil and ISMouseDrag.draggingFocus ~= self and ISMouseDrag.draggingFocus ~= nil then
        if getCore():getGameMode() ~= "Tutorial" then
            if self:canPutIn() then
                local doWalk = true
                local items = {}
                local dragging = ISInventoryPane.getActualItems(ISMouseDrag.dragging)
                for i,v in ipairs(dragging) do
                    local transfer = v:getContainer() and not self.inventory:isInside(v)
                    if v:isFavorite() and not self.inventory:isInCharacterInventory(playerObj) then
                        transfer = false
                    end
                    if transfer then
                        -- only walk for the first item
                        if doWalk then
                            if not luautils.walkToContainer(self.inventory, self.player) then
                                break
                            end
                            doWalk = false
                        end
                        table.insert(items, v)
                    end
                end
                self:transferItemsByWeight(items, self.inventory)
                self.selected = {}
                getPlayerLoot(self.player).inventoryPane.selected = {}
                getPlayerInventory(self.player).inventoryPane.selected = {}
            end
        end
        if ISMouseDrag.draggingFocus then
            ISMouseDrag.draggingFocus:onMouseUp(0,0)
        end
        ISMouseDrag.draggingFocus = nil
        ISMouseDrag.dragging = nil
        return
    end

	self.dragging = nil
	self.draggedItems:reset()
	ISMouseDrag.dragging = nil
	ISMouseDrag.draggingFocus = nil

    if x >= 0 and y >= 0 and x < self.column3 then
        y = y - self.headerHgt
        y = y / self.itemHgt
        y = math.floor(y + 1)
        self.mouseOverOption = y
    end
    self.draggingMarquis = false

	return true
end

function ISInventoryPane:canPutIn()
    local playerObj = getSpecificPlayer(self.player)

    if self.inventory == nil then
        return false
    end
    if self.inventory:getType() == "floor" then
        return true
    end

    if self.inventory:getParent() == playerObj then
        return true
    end

    local items = {}
    -- If the lightest item fits, allow the transfer.
    local minWeight = 100000
    local dragging = ISInventoryPane.getActualItems(ISMouseDrag.dragging)
    for i,v in ipairs(dragging) do
        local itemOK = true
        if v:isFavorite() and not self.inventory:isInCharacterInventory(playerObj) then
            itemOK = false
        end
        -- you can't draw the container in himself
        if (self.inventory:isInside(v)) then
            itemOK = false
        end
        if self.inventory:getType() == "floor" and v:getWorldItem() then
            itemOK = false
        end
        if v:getContainer() == self.inventory then
            itemOK = false
        end
        local inv = self.inventory
--        if self.mouseOverButton and self.mouseOverButton.inventory then
--            inv = self.mouseOverButton.inventory
--        end
        if not CleanUI_safeIsItemAllowed(inv, v) then
            itemOK = false
        end
        if itemOK then
            table.insert(items, v)
        end
        if v:getUnequippedWeight() < minWeight then
            minWeight = v:getUnequippedWeight()
        end
    end
    if #items == 1 then
        return self.inventory:hasRoomFor(playerObj, items[1])
    end
    return self.inventory:hasRoomFor(playerObj, minWeight)
end

function DraggedItems:getDropContainer()
    local playerInv = getPlayerInventory(self.playerNum)
    local playerLoot = getPlayerLoot(self.playerNum)
    if not playerInv or not playerLoot then
        return nil
    end
    if playerInv.mouseOverButton then
        return playerInv.mouseOverButton.inventory, "button"
    end
    if playerInv.inventoryPane:isMouseOver() then
        return playerInv.inventoryPane.inventory, "inventory"
    end
    if playerLoot.mouseOverButton then
        return playerLoot.mouseOverButton.inventory, "button"
    end
    if playerLoot.inventoryPane:isMouseOver() then
        return playerLoot.inventoryPane.inventory, "loot"
    end

    local mx = getMouseX()
    local my = getMouseY()
    local uis = UIManager.getUI()
    local mouseOverUI = nil
    for i=0,uis:size()-1 do
        local ui = uis:get(i)
        if ui:isPointOver(mx, my) then
            mouseOverUI = ui
            break
        end
    end
    if not mouseOverUI then
        return ISInventoryPage.GetFloorContainer(self.playerNum), "floor"
    end

    return nil
end

function DraggedItems:update()
    self.playerNum = self.inventoryPane.player
    local playerObj = getSpecificPlayer(self.playerNum)

    if not self.items then
        self.items = ISInventoryPane.getActualItems(ISMouseDrag.dragging)
        self.inventoryPane:sortItemsByTypeAndWeight(self.items)
    end

    -- Try to detect changes to the destination container.
    if self.mouseOverContainer and (self.mouseOverItemCount ~= self.mouseOverContainer:getItems():size()) then
        self.mouseOverContainer = nil
    end

    local container, what = self:getDropContainer()
    if (container == self.mouseOverContainer) and (what == self.mouseOverWhat) then
        return
    end
    self.mouseOverContainer = container
    self.mouseOverWhat = what
    self.mouseOverItemCount = container and container:getItems():size() or 0
    table.wipe(self.itemNotOK)

    if #self.items == 0 then
        return
    end

    if not container then
        return
    end

    local containerInInventory = container:isInCharacterInventory(playerObj)

    -- Items may always be dragged to the floor (except favorited items).
    if container:getType() == "floor" then
        for _,item in ipairs(self.items) do
            if item:isFavorite() and not containerInInventory then
                self.itemNotOK[item] = true
            end
            if what ~= "loot" and item:getWorldItem() then
                self.itemNotOK[item] = true
            end
        end
        return
    end

    -- Dragging from ourself to ourself does nothing, but don't show as prevented.
    if what ~= "button" and container == self.inventoryPane.inventory then
        return
    end

    local totalWeight = 0
    local weightAddedToFloor = 0
    local containerSq = container:hasWorldItem() and container:getWorldItem():getSquare() or nil
    local validItems = {}
    for _,item in ipairs(self.items) do
        local itemOK = true
        if container:isInside(item) then
            itemOK = false
        end
        if item:isFavorite() and not containerInInventory then
            itemOK = false
        end
        if item:getContainer() == container then
            itemOK = false
        end
        if not CleanUI_safeIsItemAllowed(container, item) then
            itemOK = false
        end
        -- Items are sorted by weight (see above)
        if itemOK then
            local newTotalWeight = totalWeight + item:getUnequippedWeight()
            -- If the container and item are both on the ground on the same square, we don't need to consider the
            -- floor's max capacity, since the weight of items on the square won't change by moving the item into
            -- a bag on the same square.
            local newWeightAddedToFloor = weightAddedToFloor
            if containerSq == nil or not item:isOnGroundOrInsideBagOnSquare(containerSq) then
                newWeightAddedToFloor = weightAddedToFloor + item:getUnequippedWeight()
            end
            if container:hasRoomFor(playerObj, newTotalWeight, newWeightAddedToFloor) then
                totalWeight = newTotalWeight
                weightAddedToFloor = newWeightAddedToFloor
            else
                itemOK = false
            end
        end
        if itemOK then
            table.insert(validItems, item)
        else
            self.itemNotOK[item] = true
        end
    end

    -- Hack: Allow any single item on a vehicle seat regardless of weight (ex, Generator)
    if #validItems == 1 then
        local item = validItems[1]
        self.itemNotOK[item] = not container:hasRoomFor(playerObj, item)
    end
end

function DraggedItems:cannotDropItem(item)
    if not item then return false end
    return self.itemNotOK[item] == true
end

function DraggedItems:cannotDropAnyItem()
    if self.items == nil then
        return true
    end
    for _,item in pairs(self.items) do
        if not self.itemNotOK[item] then
            return false
        end
    end
    return true
end

function DraggedItems:reset()
    self.mouseOverContainer = nil
    self.mouseOverWhat = nil
    self.items = nil
    table.wipe(self.itemNotOK)
end

function DraggedItems:new(inventoryPane)
    local o = {}
    setmetatable(o, self)
    self.__index = self
    o.inventoryPane = inventoryPane
    o.mouseOverContainer = nil
    o.mouseOverWhat = nil
    o.items = nil
    o.itemNotOK = {}
    return o
end

-----

function ISInventoryPane:doJoypadExpandCollapse()
    if not self.joyselection then return end
    if not self.items or not self.items[self.joyselection+1] then return end
    if not instanceof(self.items[self.joyselection+1], "InventoryItem") then
        self.collapsed[self.items[self.joyselection+1].name] = not self.collapsed[self.items[self.joyselection+1].name]
        self:refreshContainer()
    end
end

function ISInventoryPane:doGrabOnJoypadSelected()
    if self.joyselection == nil then return end
    if not self.doController then return end

    local playerObj = getSpecificPlayer(self.player)
    if playerObj:isAsleep() then return end

    if #self.items == 0 then return end

    self.selected = {}
    self:selectIndex(self.joyselection+1)

    local items = {}
    for k, v in ipairs(self.items) do
        if self.selected[k] ~= nil then
            if instanceof(v, "InventoryItem") then
                if not self.parent.onCharacter and isForceDropHeavyItem(v) then
                    ISInventoryPaneContextMenu.equipHeavyItem(playerObj, v)
                    return
                end
                table.insert(items, v)
            elseif self.collapsed[v.name] then
                if not self.parent.onCharacter and isForceDropHeavyItem(v.items[1]) then
                    ISInventoryPaneContextMenu.equipHeavyItem(playerObj, v.items[1])
                    return
                end
                table.insert(items, v)
            end
        end
    end
    if self.parent.onCharacter then
        ISInventoryPaneContextMenu.onPutItems(items, self.player)
    else
        ISInventoryPaneContextMenu.onGrabItems(items, self.player)
    end
end

function ISInventoryPane:doContextOnJoypadSelected()
	if JoypadState.disableInvInteraction then
		return
	end
	if UIManager.getSpeedControls() and UIManager.getSpeedControls():getCurrentGameSpeed() == 0 then
		return
	end

    local playerObj = getSpecificPlayer(self.player)
    if playerObj:isAsleep() then return end

    local isInInv = self.inventory:isInCharacterInventory(playerObj)

    if #self.items == 0 then
        local menu = ISInventoryPaneContextMenu.createMenuNoItems(self.player, not isInInv, self:getAbsoluteX()+64, self:getAbsoluteY()+64)
        if menu then
            menu.origin = self.inventoryPage
            menu.mouseOver = 1
            setJoypadFocus(self.player, menu)
        end
        return
    end

    if self.joyselection == nil then return end
    if not self.doController then return end

    self:selectIndex(self.joyselection+1)
    local item = self.items[self.joyselection+1]

    local contextMenuItems = {}
    for k, v in ipairs(self.items) do
       if self.selected[k] ~= nil then
           if v.type ~= "separator" and (instanceof(v, "InventoryItem") or self.collapsed[v.name]) then
                table.insert(contextMenuItems, v)
           end
       end
    end
	
	local menu = nil
	if getCore():getGameMode() == "Tutorial" then
		menu = Tutorial1.createInventoryContextMenu(self.player, isInInv, contextMenuItems, self:getAbsoluteX()+64, self:getAbsoluteY()+8+(self.joyselection*self.itemHgt)+self:getYScroll())
	else
    	menu = ISInventoryPaneContextMenu.createMenu(self.player, isInInv, contextMenuItems, self:getAbsoluteX()+64, self:getAbsoluteY()+8+(self.joyselection*self.itemHgt)+self:getYScroll())
	end
    if menu then
        menu.origin = self.inventoryPage
        menu.mouseOver = 1
        if menu.numOptions > 1 then
            setJoypadFocus(self.player, menu)
        end
    end
end

function ISInventoryPane:onRightMouseUp(x, y)

	if self.player ~= 0 then return end

    local isInInv = self.inventory:isInCharacterInventory(getSpecificPlayer(self.player))
    
    if #self.items == 0 then
        local menu = ISInventoryPaneContextMenu.createMenuNoItems(self.player, not isInInv, self:getAbsoluteX()+x, self:getAbsoluteY()+y+self:getYScroll())
        if menu and menu.numOptions > 1 and JoypadState.players[self.player+1] then
            menu.origin = self.inventoryPage
            menu.mouseOver = 1
            setJoypadFocus(self.player, menu)
        end
        return
    end

	if self.selected == nil then
		self.selected = {}
	end

    if self.mouseOverOption ~= 0 and self.items[self.mouseOverOption] ~= nil and self.selected[self.mouseOverOption] == nil then
        self.selected = {}
        self:selectIndex(self.mouseOverOption)
    end

    local contextMenuItems = {}
    for k, v in ipairs(self.items) do
       if self.selected[k] ~= nil then
           if v.type ~= "separator" and (instanceof(v, "InventoryItem") or self.collapsed[v.name]) then
                table.insert(contextMenuItems, v)
           end
       end
    end

	if self.toolRender then
		self.toolRender:setVisible(false)
	end

    local menu = ISInventoryPaneContextMenu.createMenu(self.player, isInInv, contextMenuItems, self:getAbsoluteX()+x, self:getAbsoluteY()+y+self:getYScroll())
    if menu and menu.numOptions > 1 and JoypadState.players[self.player+1] then
        menu.origin = self.inventoryPage
        menu.mouseOver = 1
        setJoypadFocus(self.player, menu)
    end

	return true
end

function ISInventoryPane:onMouseDown(x, y)

	if self.player ~= 0 then return true end

	getSpecificPlayer(self.player):nullifyAiming()

	local count = 0

    self.downX = x
    self.downY = y

    if self.selected == nil then
		self.selected = {}
    end

    if self.mouseOverOption ~= 0 and self.items[self.mouseOverOption] ~= nil then

        -- expand or collapse...
        if x < self.column2 then
            if self.items[self.mouseOverOption].type == "separator" then
                self.equippedCollapsed = not self.equippedCollapsed
                CleanUIConfig.updateConfig("hideEquipped", self.equippedCollapsed)
                return
            end

            if not instanceof(self.items[self.mouseOverOption], "InventoryItem") then
                self.collapsed[self.items[self.mouseOverOption].name] = not self.collapsed[self.items[self.mouseOverOption].name]
                self:refreshContainer()
                self.selected = {}
                return
            end
        end
        if not isShiftKeyDown() and not isCtrlKeyDown() and self.selected[self.mouseOverOption] == nil then
            self.selected = {}
            self.firstSelect = nil
        end

        if not isShiftKeyDown() then
            self.firstSelect = self.mouseOverOption
            if isCtrlKeyDown() then
                if self.selected[self.mouseOverOption] then
                    self.selected[self.mouseOverOption] = nil
                else
                    self.selected[self.mouseOverOption] =  self.items[self.mouseOverOption]
                end
            else
                self.selected[self.mouseOverOption] =  self.items[self.mouseOverOption]
            end
        end
        if isShiftKeyDown() then
           if self.firstSelect then
               self.selected = {}
               if self.firstSelect < self.mouseOverOption then
                    for i=self.firstSelect, self.mouseOverOption do
                        self.selected[i] =  self.items[i]
                    end
               else
                   for i=self.mouseOverOption, self.firstSelect do
                       self.selected[i] =  self.items[i]
                   end
               end
           else
               self.firstSelect = self.mouseOverOption
               self.selected[self.mouseOverOption] =  self.items[self.mouseOverOption]
           end
        end

        local listItem = self.items[self.mouseOverOption]
        if listItem and self.selected[self.mouseOverOption] and not instanceof(listItem, "InventoryItem") and not self.collapsed[listItem.name] then
            for i=2,#listItem.items do
                self.selected[self.mouseOverOption+i-1] = listItem.items[i]
                if i == ISInventoryPane.MAX_ITEMS_IN_STACK_TO_RENDER + 1 then break end
            end
        end

		self.dragging = self.mouseOverOption
		self.draggingX = x
		self.draggingY = y
		self.dragStarted = false
		--print ("Dragging "..self.selected)
		ISMouseDrag.dragging = {}
		----print(self.selected[self.mouseOverOption])
		for i,v in ipairs(self.items) do
            if self.selected[count+1] ~= nil then
                table.insert(ISMouseDrag.dragging, v)
            end
            count = count + 1
		end
		ISMouseDrag.draggingFocus = self
        return
	end
    if not isShiftKeyDown() and not isCtrlKeyDown() then
        self.selected = {}
        self.firstSelect = nil
    end

    if self.dragging == nil and x >= 0 and y >= 0 and (x<=self.column3 and y <= self:getScrollHeight() - self.itemHgt) then

	elseif count == 0 then
		self.draggingMarquis = true
		self.draggingMarquisX = x
		self.draggingMarquisY = y
		self.dragging = nil
		self.draggedItems:reset()
		ISMouseDrag.dragging = nil
		ISMouseDrag.draggingFocus = nil


	end

	if x < self.column2 and y < self.headerHgt then
		return false
	end
	return true
end
function ISInventoryPane:getScrollHeight()
	if self.javaObject == nil then
		return 0
	end
	return self.javaObject:getScrollHeight() - FONT_HGT_SMALL * 0.8
end

function ISInventoryPane:getScrollAreaHeight()
	return self:getHeight() - self.headerHgt - FONT_HGT_SMALL * 0.8
end

function ISInventoryPane:updateSmoothScrolling()
	if not self.smoothScrollTargetY or #self.items == 0 then return end
	local dy = self.smoothScrollTargetY - self.smoothScrollY
	local maxYScroll = self:getScrollHeight() - self:getScrollAreaHeight()
	local frameRateFrac = UIManager.getMillisSinceLastRender() / 33.3
	local itemHeightFrac = 160 / (self:getScrollHeight() / #self.items)
	local targetY = self.smoothScrollY + dy * math.min(0.5, 0.25 * frameRateFrac * itemHeightFrac)
	if frameRateFrac > 1 then
		targetY = self.smoothScrollY + dy * math.min(1.0, math.min(0.5, 0.25 * frameRateFrac * itemHeightFrac) * frameRateFrac)
	end
	if targetY > 0 then targetY = 0 end
	if targetY < -maxYScroll then targetY = -maxYScroll end
	if math.abs(targetY - self.smoothScrollY) > 0.1 then
		self:setYScroll(targetY)
		self.smoothScrollY = targetY
	else
		self:setYScroll(self.smoothScrollTargetY)
		self.smoothScrollTargetY = nil
		self.smoothScrollY = nil
	end
end

function ISInventoryPane:isMouseOverScrollBar()
	return self:isVScrollBarVisible() and self.vscroll:isMouseOver()
end

--************************************************************************--
--** ISInventoryPane:prerender
--**
--************************************************************************--
function ISInventoryPane:prerender()
	local mouseY = self:getMouseY()
	self:updateSmoothScrolling()
	if mouseY ~= self:getMouseY() and self:isMouseOver() then
		self:onMouseMove(0, self:getMouseY() - mouseY)
	end

	self.nameHeader.maximumWidth = self.width - self.typeHeader.minimumWidth - self.column2
	self.typeHeader.maximumWidth = self.width - self.nameHeader.minimumWidth - self.column2
	self:setStencilRect(0,0,self.width-1, self.height-1)

	if self.mode == "icons" then
		self:rendericons()
	elseif self.mode == "details" then
		self:renderdetails(false)
	end

    self.vscroll:setHeight(self.height - FONT_HGT_SMALL * 0.8)
	self:updateScrollbars()

end

function ISInventoryPane:rendericons()
	local xpad = 10
	local ypad = 10
	local iw = 40
	local ih = 40
	local xmax = math.floor((self.width - (xpad * 2)) / iw)
	local ymax = math.floor((self.height - (ypad * 2)) / ih)
	local xcount = 0
	local ycount = 0
    local it = self.inventory:getItems()
    for i = 0, it:size()-1 do
        local item = it:get(i)
        ISInventoryItem.renderItemIcon(self, item, (xcount * iw) + xpad + 4, (ycount * ih) + ypad + 4)
        --self:drawTexture(item:getTex(), (xcount * iw) + xpad + 4, (ycount * ih) + ypad + 4, 1, 1, 1, 1)

		xcount = xcount + 1

		if xcount >= xmax then
			xcount= 0
			ycount = ycount + 1
		end
	end
end

local function isSelectAllPossible(page)
    if not page then return false end
    if not page:isVisible() then return false end
    if page.isCollapsed then return false end
    if not page:isMouseOver() then return false end
    for _,v in pairs(page.inventoryPane.selected) do
        return true
    end
    return false
end

function ISInventoryPane:update()

    local playerObj = getSpecificPlayer(self.player)
    
    if self.doController then
        --print("do controller!")
        if (self.player ~= 0) or (wasMouseActiveMoreRecentlyThanJoypad() == false) then
            table.wipe(self.selected)
        end
        if self.joyselection == nil then
            self.joyselection = 0
        end
    end
	self:updateTooltip()


    local remove = nil

    for i,v in pairs(self.selected) do
        if instanceof(v, "InventoryItem") then
            if v:getContainer() ~= self.inventory then
                if remove == nil then
                    remove = {}
                end
                remove[i] = i
            end
        end
    end
    if remove ~= nil then
        for i,v in pairs(remove) do
            self.selected[v] = nil
        end
    end


    -- Make it select the header if all sub items in expanded item are selected.
    for i,v in ipairs(self.items) do
        if not instanceof(v, "InventoryItem") and self.selected[i] == nil and not self.collapsed[v.name] then
            local anyNot = false
            for j=2,#v.items do
                if self.selected[i+j-1] == nil then
                    anyNot = true
                    break
                end
            end
            if not anyNot then
                self.selected[i] = v
            end
        end
    end

    -- If the user was dragging items from this pane and the mouse wasn't released over a valid drop location,
    -- then we must clear the drag info.  Additionally, if the mouse was released outside any UIElement, then
    -- we will drop the items onto the floor (unless this pane is displaying the floor container).
    -- NOTE: This only works because update() is called after all the mouse-event handling, so other UIElements
    -- have already had a chance to accept the drag.
    if ISMouseDrag.dragging ~= nil and ISMouseDrag.draggingFocus == self and not isMouseButtonDown(0) then
		if getCore():getGameMode() == "Tutorial" then
            if ISMouseDrag.draggingFocus then
                ISMouseDrag.draggingFocus:onMouseUp(0,0)
            end
            ISMouseDrag.draggingFocus = nil
            ISMouseDrag.dragging = nil
			return
		end
        local dragContainsMovables = false
        local dragContainsNonMovables = false
        local mx = getMouseX()
        local my = getMouseY()
        local uis = UIManager.getUI()
        local mouseOverUI
        for i=0,uis:size()-1 do
            local ui = uis:get(i)
            if ui:isPointOver(mx, my) then
                mouseOverUI = ui
                break
            end
        end

        local noVehicle = true
        local vehicleNoWindow = true
        local vehicleWindowDestroyed = true
        local vehicleWindowOpen = true

        local vehicle = playerObj:getVehicle()

        if vehicle ~= nil then
            noVehicle = false
            local seat = vehicle:getSeat(playerObj)
            local door = vehicle:getPassengerDoor(seat)
            local windowPart = VehicleUtils.getChildWindow(door)
            if windowPart and (not windowPart:getItemType() or windowPart:getInventoryItem()) then
                vehicleNoWindow = false
                local window = windowPart:getWindow()
                if window:isOpenable() and not window:isOpen() then
                    vehicleWindowOpen = false
                end
            end
        end

        if self.inventory:getType() ~= "floor" and not mouseOverUI and (noVehicle or vehicleNoWindow or vehicleWindowOpen) then
            local dragging = ISInventoryPane.getActualItems(ISMouseDrag.dragging)
            local dropit = false
--            for i,v in ipairs(dragging) do
--                if not self.inventory:isInside(v) and not v:isFavorite() then
--                    if not instanceof(v, "Moveable") or v:CanBeDroppedOnFloor() then
--                        if self.inventoryPage.selectedSqDrop and self.inventoryPage.render3DItems and #self.inventoryPage.render3DItems > 0 then
--                            ISWorldObjectContextMenu.transferIfNeeded(playerObj, v)
--                        end
--                    end
--                end
--            end


--            if self.inventoryPage.selectedSqDrop and luautils.walkAdj(playerObj, self.inventoryPage.selectedSqDrop, true) then
--            if self.inventoryPage.selectedSqDrop then
--                dropit = true
--            end
            for i,v in ipairs(dragging) do
                if not self.inventory:isInside(v) and not v:isFavorite() then
                    if not instanceof(v, "Moveable") or v:CanBeDroppedOnFloor() then
--                        if self.inventoryPage.selectedSqDrop and self.inventoryPage.render3DItems and #self.inventoryPage.render3DItems > 0 then
--                            if dropit then
--                                if playerObj:isEquipped(v) then
--                                    ISTimedActionQueue.add(ISUnequipAction:new(playerObj, v, 1))
--                                end
--                                ISTimedActionQueue.add(ISDropWorldItemAction:new (playerObj, v, self.inventoryPage.selectedSqDrop, self.inventoryPage.render3DItemXOffset, self.inventoryPage.render3DItemYOffset, self.inventoryPage.render3DItemZOffset, self.inventoryPage.render3DItemRot))
--                            end
--                        else
                            ISInventoryPaneContextMenu.dropItem(v, self.player)
--                        end
                        dragContainsNonMovables = true
                    else
                        dragContainsMovables = dragContainsMovables or v
                    end
                end
            end
            self.selected = {}
            getPlayerLoot(self.player).inventoryPane.selected = {}
            getPlayerInventory(self.player).inventoryPane.selected = {}
        end
        self.inventoryPage.selectedSqDrop = nil
        self.inventoryPage.render3DItems = {}
        self.dragging = nil
        self.draggedItems:reset()
        ISMouseDrag.dragging = nil
        ISMouseDrag.draggingFocus = nil

        if dragContainsMovables and not dragContainsNonMovables then
            local mo = ISMoveableCursor:new(getSpecificPlayer(self.player))
            getCell():setDrag(mo, mo.player)
            mo:setMoveableMode("place")
            mo:tryInitialItem(dragContainsMovables)
        end
    end

    -- If the user was draggingMarquis from this pane and the mouse wasn't released over a valid location,
    -- then we must clear the drag info.
    if self.draggingMarquis and not isMouseButtonDown(0) then
        self.draggingMarquis = false
    end

    if self.doController then
        return
    end

    local page1 = getPlayerInventory(0)
    local page2 = getPlayerLoot(0)
    if not page1 or not page2 then
        return
    end
    if isCtrlKeyDown() and (isSelectAllPossible(page1) or isSelectAllPossible(page2)) then
        getCore():setIsSelectingAll(true)
    else
        getCore():setIsSelectingAll(false)
    end
    if isCtrlKeyDown() and isKeyDown(Keyboard.KEY_A) and isSelectAllPossible(self.parent) then
        table.wipe(self.selected)
        for k,v in ipairs(self.items) do
            self.selected[k] = v
        end
    end
end

function ISInventoryPane:saveSelection(selected)
    for _,v in pairs(self.selected) do
        if instanceof(v, "InventoryItem") then
            selected[v] = selected[v] or "item"
        else
            selected[v.items[1]] = "group"
        end
    end
    -- Hack for the selection being cleared while dragging items.
    if ISMouseDrag.dragging and (ISMouseDrag.draggingFocus == self) then
        for _,v in ipairs(ISMouseDrag.dragging) do
            if instanceof(v, "InventoryItem") then
                selected[v] = selected[v] or "item"
            else
                selected[v.items[1]] = "group"
            end
        end
    end
    return selected
end

function ISInventoryPane:restoreSelection(selected)
    local row = 1
    for _,v in ipairs(self.itemslist) do
        local item = v.items[1]
        if selected[item] == "group" then
            self.selected[row] = item
        end
        row = row + 1
        if not self.collapsed[v.name] then
            for j=2,#v.items do
                local item2 = v.items[j]
                if selected[item2] then
                    self.selected[row] = item2
                end
                row = row + 1
            end
        end
    end
end

function ISInventoryPane:refreshContainer()
    self.itemslist = {}
    self.itemindex = {}

    if self.collapsed == nil then
        self.collapsed = {}
    end
	if self.selected == nil then
		self.selected = {}
	end

	local selected = self:saveSelection({})
	table.wipe(self.selected)

	local playerObj = getSpecificPlayer(self.player)

	if not self.hotbar then
		self.hotbar = getPlayerHotbar(self.player)
	end

	local isEquipped = {}
	local isInHotbar = {}
	if self.parent.onCharacter then
		local wornItems = playerObj:getWornItems()
		for i=1,wornItems:size() do
			local wornItem = wornItems:get(i-1)
			isEquipped[wornItem:getItem()] = true
		end
		local item = playerObj:getPrimaryHandItem()
		if item then
			isEquipped[item] = true
		end
		item = playerObj:getSecondaryHandItem()
		if item then
			isEquipped[item] = true
		end
		if self.hotbar and self.hotbar.attachedItems then
			for _,item in pairs(self.hotbar.attachedItems) do
				isInHotbar[item] = true
			end
		end
	end

    local expandedItems = 0
    for k, v in pairs(self.collapsed) do
        if not self.collapsed[k] then
            expandedItems = 1
            break
        end
    end

    local it = self.inventory:getItems()
    for i = 0, it:size()-1 do
        local item = it:get(i)
		local add = true
		-- don't add the ZedDmg category, they are just equipped models
		if item:isHidden() then
			add = false
		end

		if add then
			local itemName = item:getName(playerObj);
			if item:IsFood() and item:getHerbalistType() and item:getHerbalistType() ~= "" then
				if playerObj:isRecipeActuallyKnown("Herbalist") then
					if item:getHerbalistType() == "Berry" then
						itemName = (item:getPoisonPower() > 0) and getText("IGUI_PoisonousBerry") or getText("IGUI_Berry")
					end
					if item:getHerbalistType() == "Mushroom" then
						itemName = (item:getPoisonPower() > 0) and getText("IGUI_PoisonousMushroom") or getText("IGUI_Mushroom")
					end
				else
					if item:getHerbalistType() == "Berry"  then
						itemName = getText("IGUI_UnknownBerry")
					end
					if item:getHerbalistType() == "Mushroom" then
						itemName = getText("IGUI_UnknownMushroom")
					end
				end
				if itemName ~= item:getDisplayName() then
					item:setName(itemName);
				end
				itemName = item:getName(playerObj)
			end
			local equipped = false
			local inHotbar = false
			if self.parent.onCharacter then
				if isEquipped[item] then
					itemName = "equipped:"..itemName
					equipped = true
				elseif (item:isItemType(ItemType.KEY_RING) or item:hasTag(ItemTag.KEY_RING)) and playerObj:getInventory():contains(item) then
					itemName = "keyring:"..itemName
					equipped = true
				end
				if self.hotbar then
					inHotbar = isInHotbar[item];
					if inHotbar then
						if not equipped then
							itemName = "hotbar:"..itemName
						end
						equipped = true
					end
				end
			end
-- 			if unstableScriptNameSpam then
--                 itemName = itemName .. " - " .. item:getFullType()
-- 			end
			if self.itemindex[itemName] == nil then
				self.itemindex[itemName] = {};
				self.itemindex[itemName].items = {}
				self.itemindex[itemName].count = 0
			end
			local ind = self.itemindex[itemName];
			ind.equipped = equipped
			ind.inHotbar = inHotbar;

			ind.count = ind.count + 1
			ind.items[ind.count] = item;
		end
    end

    for k, v in pairs(self.itemindex) do

        if v ~= nil then
            table.insert(self.itemslist, v)
            local count = 1
            local weight = 0
            for k2, v2 in ipairs(v.items) do
                if v2 == nil then
                    table.remove(v.items, k2)
                else
                    count = count + 1
                    weight = weight + v2:getUnequippedWeight()
                end
            end
            v.count = count
            v.invPanel = self
            v.name = k -- v.items[1]:getName()
            v.cat = v.items[1]:getDisplayCategory() or v.items[1]:getCategory()
            v.weight = weight
            if self.collapsed[v.name] == nil then
                self.collapsed[v.name] = true
            end
        end
    end

    local searchMode = self.searchMode or "name"
    local hasNameSearch = self.searchText and self.searchText ~= ""
    local hasCategorySearch = self.searchCategoryText and self.searchCategoryText ~= ""
    local hasActiveSearch = hasNameSearch or hasCategorySearch

    if hasActiveSearch then
        for k, v in ipairs(self.itemslist) do
            local matchesName = true
            local matchesCategory = true

            if searchMode == "category" then
                matchesCategory = CleanUI_itemMatchesCategorySearch(v, self.searchCategoryText)
            elseif searchMode == "both" then
                matchesName = CleanUI_itemMatchesNameSearch(v, self.searchText)
                matchesCategory = CleanUI_itemMatchesCategorySearch(v, self.searchCategoryText)
            else
                matchesName = CleanUI_itemMatchesNameSearch(v, self.searchText)
            end

            v.matchesSearch = matchesName and matchesCategory
        end

        table.sort(self.itemslist, function(a, b)
            if a.matchesSearch and not b.matchesSearch then
                return true
            elseif not a.matchesSearch and b.matchesSearch then
                return false
            end
            return self.itemSortFunc(a, b)
        end)
    else
        for k, v in ipairs(self.itemslist) do
            v.matchesSearch = false
        end

        local playerObj = getSpecificPlayer(self.player)
        if self.parent.onCharacter and self.inventory == playerObj:getInventory() then
            table.insert(self.itemslist, {
                type = "separator",
                equipped = false,
                items = {},
                count = 0,
                name = ".",
                cat = "",
                weight = 0
            })
        end

        table.sort(self.itemslist, self.itemSortFunc)
    end
    
    -- Adding the first item in list additionally at front as a dummy at the start...
    for k, v in ipairs(self.itemslist) do
        local item = v.items[1]
        table.insert(v.items, 1, item)
    end

    self:restoreSelection(selected)
    table.wipe(selected)

    self:updateScrollbars()
    self.inventory:setDrawDirty(false)

    -- Update the buttons
    if self:isMouseOver() then
        self:onMouseMove(0, 0)
    end

    self:updateWorldObjectHighlight()

    self.refreshContainerCount = (self.refreshContainerCount or 0) + 1
    if self.refreshContainerCount > 10000 then self.refreshContainerCount = 0 end
end


ISInventoryPane.highlightItem = nil
function ISInventoryPane:renderdetails(doDragged)

    self:updateScrollbars()
    local scrollBarWid = self:isVScrollBarVisible() and math.floor(FONT_HGT_SMALL * 0.6) or 0

    if doDragged == false then
        table.wipe(self.items)

        if self.inventory:isDrawDirty() then
            self:refreshContainer()
        end
    end
    
    local player = getSpecificPlayer(self.player)

    local checkDraggedItems = false
    if doDragged and self.dragging ~= nil and self.dragStarted then
        self.draggedItems:update()
        checkDraggedItems = true
    end

    if (self.itemsToHighlight ~= nil) and ((self.itemsToHighlightOwner == nil) or (not self.itemsToHighlightOwner:isReallyVisible())) then
        self.itemsToHighlightOwner = nil
        self.itemsToHighlight = nil
    end

    if not doDragged then
		-- background of item icon
        -- self:drawRectStatic(0, 0, self.column2, self.height, 0.6, 0, 0, 0)
    end
    local y = 0
    local alt = false
    if self.itemslist == nil then
        self:refreshContainer()
    end
    local MOUSEX = self:getMouseX()
    local MOUSEY = self:getMouseY()
    local YSCROLL = self:getYScroll()
    local HEIGHT = self:getHeight()
    local all3D = true
--    self.inventoryPage.render3DItems = {}
    -- Go through all the stacks of items.
    for k, v in ipairs(self.itemslist) do
        if v.type == "separator" then
            if not doDragged then
                table.insert(self.items, v)

                local bgY = (y * self.itemHgt) + self.headerHgt
                local bgWidth = self:getWidth() - 1 - scrollBarWid
                self:drawRect(1, bgY, bgWidth, self.itemHgt, 0.5, 0, 0, 0)
                
                local textY = bgY + (self.itemHgt - self.fontHgt) / 2

                local icon = self.equippedCollapsed and self.hideEquippedIcon or self.showEquippedIcon
                local iconSize = self.itemHgt * 0.6
                local iconX = 5
                local iconY = bgY + (self.itemHgt - iconSize) / 2
                self:drawTextureScaled(icon, iconX, iconY, iconSize, iconSize, 1.0, 1, 1, 1)

                self:drawText(getText("UI_CleanUI_EquippedItems"), iconX + iconSize + 5, textY, 0.7, 0.7, 0.7, 1.0, self.font)

                -- show Equipped Items Count and Weight
                local totalEquippedCount = 0
                local totalEquippedWeight = 0

                for k, v in ipairs(self.itemslist) do
                    if v.equipped and v.items and #v.items > 0 then
                        totalEquippedCount = totalEquippedCount + 1
                        for i = 2, #v.items do
                            local item = v.items[i]
                            totalEquippedWeight = totalEquippedWeight + item:getEquippedWeight()
                        end
                    end
                end

                if totalEquippedCount > 0 then
                    local countText = " (" .. totalEquippedCount .. ")"
                    local countX = iconX + iconSize + getTextManager():MeasureStringX(self.font, getText("UI_CleanUI_EquippedItems")) + 5
                    self:drawText(countText, countX, textY, 0.7, 0.7, 0.7, 1.0, self.font)
                end

                if totalEquippedWeight > 0 then
                    local weightText = tostring(round(totalEquippedWeight, 2))
                    local weightWidth = getTextManager():MeasureStringX(self.font, weightText)

                    local weightIcon = getTexture("media/ui/Moodles/48/Status_HeavyLoad.png")
                    local weightIconSize = math.floor(self.itemHgt)

                    local weightTextX = self:getWidth() - scrollBarWid - weightWidth - 10
                    local weightIconX = weightTextX - weightIconSize - 5
                    local weightIconY = bgY + (self.itemHgt - weightIconSize) / 2

                    self:drawTextureScaled(weightIcon, weightIconX, weightIconY, weightIconSize, weightIconSize, 1.0, 1, 1, 1)
                    self:drawText(weightText, weightTextX, textY, 0.7, 0.7, 0.7, 1.0, self.font)
                end
            end
            y = y + 1
        else
            if self.equippedCollapsed and v.equipped then
        else
        local count = 1
        -- Go through each item in stack..
        for k2, v2 in ipairs(v.items) do
           -- print("trace:a")
            local item = v2
            local doIt = true
            local xoff = 0
            local yoff = 0
            if doDragged == false then
                -- if it's the first item, then store the category, otherwise the item
                if count == 1 then
                    table.insert(self.items, v)
                else
                    table.insert(self.items, item)
                end

                if instanceof(item, 'InventoryItem') then
                    item:updateAge()
                end
                if instanceof(item, 'Clothing') then
                    item:updateWetness()
                end
            end
           -- print("trace:b")
            local isDragging = false
            if self.dragging ~= nil and self.selected[y+1] ~= nil and self.dragStarted then
                xoff = MOUSEX - self.draggingX
                yoff = MOUSEY - self.draggingY
                if not doDragged then
                    doIt = false
                else
                    self:suspendStencil()
                    isDragging = true
                end
            else
                if doDragged then
                    doIt = false
                end
            end
            local topOfItem = y * self.itemHgt + YSCROLL
            if not isDragging and ((topOfItem + self.itemHgt < 0) or (topOfItem > HEIGHT)) then
                doIt = false
            end
           -- print("trace:c")
            if doIt == true then
               -- print("trace:cc")
        --        print(count)
                if count == 1 then
					-- rect over the whole item line
--                    self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self:getWidth(), 1, 0.3, 0.0, 0.0, 0.0)
                end
               -- print("trace:d")

                -- do controller selection.
                if self.joyselection ~= nil and self.doController then
--                    if self.joyselection < 0 then self.joyselection = (#self.itemslist) - 1 end
--                    if self.joyselection >= #self.itemslist then self.joyselection = 0 end
                    if self.joyselection == y then
                        self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self:getWidth()-1, self.itemHgt, 0.2, 0.2, 1.0, 1.0)
                    end
                end
               -- print("trace:e")

                -- only do icon if header or dragging sub items without header.
                local tex = item:getTex()
                if tex ~= nil then
					local texDY = 0
					local texWH = math.min(self.itemHgt-2,32)
					local auxDXY = self.itemHgt-(self.itemHgt-texWH)/2-13
                    local texOffsetY = (y*self.itemHgt)+(self.itemHgt-texWH)/2+self.headerHgt+yoff
                    local texOffsetX = self.column2-texWH-(self.itemHgt-texWH)/2+xoff
                    if count == 1  then
                        --ISInventoryItem.renderItemIcon(self, item, 10+xoff, (y*self.itemHgt)+self.headerHgt+texDY+yoff, 1.0, texWH, texWH)
                        ISInventoryItem.renderItemIcon(self, item, texOffsetX, texOffsetY+texDY, 1.0, texWH, texWH)
						--self:drawTextureScaledAspect(tex, 10+xoff, (y*self.itemHgt)+self.headerHgt+texDY+yoff, texWH, texWH, 1, item:getR(), item:getG(), item:getB())
						if player:isEquipped(item) then
							self:drawTexture(self.equippedItemIcon, texOffsetX+auxDXY, texOffsetY+auxDXY, 1, 1, 1, 1)
						end
						if not self.hotbar then
							self.hotbar = getPlayerHotbar(self.player)
						end
						if not player:isEquipped(item) and self.hotbar and self.hotbar:isInHotbar(item) then
							self:drawTexture(self.equippedInHotbar, texOffsetX+auxDXY, texOffsetY+auxDXY, 1, 1, 1, 1)
						end
                        if item:isBroken() then
                            self:drawTexture(self.brokenItemIcon, texOffsetX+auxDXY, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        if instanceof(item, "Food") and item:isFrozen() then
                            self:drawTexture(self.frozenItemIcon, texOffsetX+auxDXY, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        if instanceof(item, "Food") and(item:isTainted() and getSandboxOptions():getOptionByName("EnableTaintedWaterText"):getValue()) or player:isKnownPoison(item) then
                            self:drawTexture(self.poisonIcon, texOffsetX+auxDXY, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        if self:isLiteratureRead(player, item) or item:hasBeenSeen(player) or item:hasBeenHeard(player) or player:hasReadMap(item) then
                            self:drawTexture(getTexture("media/ui/Tick_Mark-10.png"), texOffsetX+auxDXY, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        local fluidContainer = item:getFluidContainer() or (item:getWorldItem() and item:getWorldItem():getFluidContainer())
						if fluidContainer ~= nil and getSandboxOptions():getOptionByName("EnableTaintedWaterText"):getValue() and (not fluidContainer:isEmpty()) and (fluidContainer:contains(Fluid.Bleach) or (fluidContainer:contains(Fluid.TaintedWater) and fluidContainer:getPoisonRatio() > 0.1)) then
							self:drawTexture(self.poisonIcon, (10+auxDXY+xoff), (y*self.itemHgt)+self.headerHgt+auxDXY-1+yoff, 1, 1, 1, 1)
						end
                        if item:isNoRecipes(player) then
                            self:drawTextureScaled(self.noFavoriteRecipeInputStar, texOffsetX, texOffsetY+auxDXY - 4, favoriteRecipeInputStarSize, favoriteRecipeInputStarSize, 1, 1, 1, 1)
                        elseif item:isFavouriteRecipeInput(player) then
                            self:drawTextureScaled(self.favoriteRecipeInputStar, texOffsetX, texOffsetY+auxDXY - 4, favoriteRecipeInputStarSize, favoriteRecipeInputStarSize, 1, 1, 1, 1)
                        end
                    elseif v.count > 2 or (doDragged and count > 1 and self.selected[(y+1) - (count-1)] == nil) then
                        -- removed the fade effect on items in stacks as it makes it difficult to tell what color clothing and bags in stacks are
                        -- we also have variable icons for items now as well, so that makes it difficult as well when they are in stacks
                        ISInventoryItem.renderItemIcon(self, item, 10+16+xoff, texOffsetY+texDY, 1.0, texWH, texWH)
						if player:isEquipped(item) then
							self:drawTexture(self.equippedItemIcon, texOffsetX+auxDXY+16, texOffsetY+auxDXY, 1, 1, 1, 1)
                        end
						if not self.hotbar then
							self.hotbar = getPlayerHotbar(self.player)
						end
						if not player:isEquipped(item) and self.hotbar and self.hotbar:isInHotbar(item) then
							self:drawTexture(self.equippedInHotbar, texOffsetX+auxDXY, texOffsetY+auxDXY, 1, 1, 1, 1)
						end
                        if item:isBroken() then
                            self:drawTexture(self.brokenItemIcon, texOffsetX+auxDXY+16, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        if instanceof(item, "Food") and item:isFrozen() then
                            self:drawTexture(self.frozenItemIcon, texOffsetX+auxDXY+16, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        if self:isLiteratureRead(player, item) or item:hasBeenSeen(player) or item:hasBeenHeard(player) or player:hasReadMap(item) then
                            self:drawTexture(getTexture("media/ui/Tick_Mark-10.png"), texOffsetX+auxDXY, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        if instanceof(item, "Food") and(item:isTainted() and getSandboxOptions():getOptionByName("EnableTaintedWaterText"):getValue()) or player:isKnownPoison(item) then
                            self:drawTexture(self.poisonIcon, 10+16+xoff+auxDXY, texOffsetY+auxDXY-1, 1, 1, 1, 1)
                        end
                        local fluidContainer = item:getFluidContainer() or (item:getWorldItem() and item:getWorldItem():getFluidContainer())
						if fluidContainer ~= nil and getSandboxOptions():getOptionByName("EnableTaintedWaterText"):getValue() and (not fluidContainer:isEmpty()) and (fluidContainer:contains(Fluid.Bleach) or (fluidContainer:contains(Fluid.TaintedWater) and fluidContainer:getPoisonRatio() > 0.1)) then
							self:drawTexture(self.poisonIcon, (10+auxDXY+xoff), (y*self.itemHgt)+self.headerHgt+auxDXY-1+yoff, 1, 1, 1, 1)
						end
                        if item:isFavorite() then
                            local iconSize =  math.floor(texWH * 0.4)
                            self:drawTextureScaled(self.favoriteStar, texOffsetX+auxDXY + iconSize, texOffsetY + auxDXY - 1,iconSize,iconSize, 1, 1, 1, 1)
                        elseif item:isNoRecipes(player) then
                            self:drawTextureScaled(self.noFavoriteRecipeInputStar, texOffsetX, texOffsetY+auxDXY - 4, favoriteRecipeInputStarSize, favoriteRecipeInputStarSize, 1, 1, 1, 1);
                        elseif item:isFavouriteRecipeInput(player) then
                            self:drawTextureScaled(self.favoriteRecipeInputStar, texOffsetX, texOffsetY+auxDXY - 4, favoriteRecipeInputStarSize, favoriteRecipeInputStarSize, 1, 1, 1, 1);
                        end
                    end
                end
                if count == 1 and v.count > 2 then
					if not doDragged then
                        local texWH = math.min(self.itemHgt-2,32)
                        local texOffsetX = self.column2-texWH-(self.itemHgt-texWH)/2+xoff
                        local size = math.min(15, 8+getCore():getOptionFontSizeReal()*2)
                        local xPos = math.max(2, (2+texOffsetX-size)/2)
                        if not self.collapsed[v.name] then
                            self:drawTextureScaled(self.treeexpicon, xPos, (y*self.itemHgt)+self.headerHgt+(self.itemHgt-15)/2+yoff, size, size, 1, 1, 1, 0.8)
                        else
                            self:drawTextureScaled(self.treecolicon, xPos, (y*self.itemHgt)+self.headerHgt+(self.itemHgt-15)/2+yoff, size, size, 1, 1, 1, 0.8)
                        end
                    end
                end

                if v.matchesSearch and not self.selected[y+1] and self.mouseOverOption ~= y+1 then
                    self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self:getWidth()-1, self.itemHgt, 0.3, 0.5, 0.3, 0.1)
                end

                if self.selected[y+1] ~= nil and not self.highlightItem then -- clicked/dragged item
                	if checkDraggedItems and self.collapsed[v.name] and self.draggedItems:cannotDropAnyItem() then
						local bgWidth = self:getWidth() - 1 - scrollBarWid
                        self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, bgWidth, self.itemHgt, 0.20, 1.0, 0.0, 0.0)
					elseif checkDraggedItems and not self.collapsed[v.name] and self.draggedItems:cannotDropItem(item) then
						local bgWidth = self:getWidth() - 1 - scrollBarWid
                        self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, bgWidth, self.itemHgt, 0.20, 1.0, 0.0, 0.0)
					elseif false and (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() ~= 1) or item:getItemHeat() ~= 1) then
						if (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() > 1) or item:getItemHeat() > 1) then
							self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.5, math.abs(item:getInvHeat()), 0.0, 0.0)
						else
							self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.5, 0.0, 0.0, math.abs(item:getInvHeat()))
						end
                    else
                        local bgWidth = self:getWidth() - 1 - scrollBarWid
                        self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, bgWidth, self.itemHgt, 0.20, 1.0, 1.0, 1.0)
					end
                elseif self.mouseOverOption == y+1 and not self.highlightItem then -- called when you mose over an element
					if(((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() ~= 1) or item:getItemHeat() ~= 1) then
                        if (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() > 1) or item:getItemHeat() > 1) then
							self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.3, math.abs(item:getInvHeat()), 0.0, 0.0)
						else
							self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.3, 0.0, 0.0, math.abs(item:getInvHeat()))
						end
                    else
                        local bgWidth = self:getWidth() - 1 - scrollBarWid
                        self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, bgWidth, self.itemHgt, 0.05, 1.0, 1.0, 1.0)
					end
                else
                    if count == 1 then -- normal background (no selected, no dragging..)
						-- background of item line
                        if self.highlightItem and self.highlightItem == item:getType() then
                            if not self.blinkAlpha then self.blinkAlpha = 0.5 end
                            self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  self.blinkAlpha, 1, 1, 1)
                            if not self.blinkAlphaIncrease then
                                self.blinkAlpha = self.blinkAlpha - 0.05 * (UIManager.getMillisSinceLastRender() / 33.3)
                                if self.blinkAlpha < 0 then
                                    self.blinkAlpha = 0
                                    self.blinkAlphaIncrease = true
                                end
                            else
                                self.blinkAlpha = self.blinkAlpha + 0.05 * (UIManager.getMillisSinceLastRender() / 33.3)
                                if self.blinkAlpha > 0.5 then
                                    self.blinkAlpha = 0.5
                                    self.blinkAlphaIncrease = false
                                end
                            end
                        else
                            if (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() ~= 1) or item:getItemHeat() ~= 1) then
                                if (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() > 1) or item:getItemHeat() > 1) then
                                    self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.2, math.abs(item:getInvHeat()), 0.0, 0.0)
                                else
                                    self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.2, 0.0, 0.0, math.abs(item:getInvHeat()))
                                end
                            end
                        end
                    else
                        if (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() ~= 1) or item:getItemHeat() ~= 1) then
                            if (((instanceof(item,"Food") or instanceof(item,"DrainableComboItem")) and item:getHeat() > 1) or item:getItemHeat() > 1) then
								self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.2, math.abs(item:getInvHeat()), 0.0, 0.0)
							else
								self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.2, 0.0, 0.0, math.abs(item:getInvHeat()))
							end
						else
							self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, self.column4 - scrollBarWid, self.itemHgt,  0.4, 0.0, 0.0, 0.0)
						end
                    end
                end
               -- print("trace:h")

                if item:getJobDelta() > 0 and (count > 1 or self.collapsed[v.name]) then
--                 if item:getJobDelta() > 0 then
                    local displayWid = self.column4 - scrollBarWid
                    self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, displayWid * item:getJobDelta(), self.itemHgt, 0.2, 0.4, 1.0, 0.3)
                end
               -- print("trace:i")

                if self.itemsToHighlight ~= nil and self.itemsToHighlight[item] == true then
                    local bgWidth = self:getWidth() - 1 - scrollBarWid
                    self:drawRect(1+xoff, (y*self.itemHgt)+self.headerHgt+yoff, bgWidth, self.itemHgt, 0.20, 1.0, 1.0, 1.0)
                end

				local textDY = (self.itemHgt - self.fontHgt) / 2

                --~ 				local redDetail = false
                local itemName = item:getName(getSpecificPlayer(self.player))
--                 if unstableScriptNameSpam then
--                     itemName = itemName .. " - " .. item:getFullType()
--                 end
                local textColor = normalTextColor
                if item:isUnwanted(getSpecificPlayer(self.player)) then textColor = unwantedTextColor end
                if count == 1 then

					-- if we're dragging something and want to put it in a container wich is full
					if doDragged and ISMouseDrag.dragging and #ISMouseDrag.dragging > 0 then
						local red = false
                        if red then
                            if v.count > 2 then
                                self:drawText(itemName.." ("..(v.count-1)..")", self.column2+self.padding+xoff, (y*self.itemHgt)+self.headerHgt+textDY+yoff, 0.7, 0.0, 0.0, 1.0, self.font)
                            else
                                self:drawText(itemName, self.column2+self.padding+xoff, (y*self.itemHgt)+self.headerHgt+textDY+yoff, 0.7, 0.0, 0.0, 1.0, self.font)
                            end
                        else
                            if v.count > 2 then
                                self:drawText(itemName.." ("..(v.count-1)..")", self.column2+self.padding+xoff, (y*self.itemHgt)+self.headerHgt+textDY+yoff, 0.7, 0.7, 0.7, 1.0, self.font)
                            else
                                self:drawText(itemName, self.column2+self.padding+xoff, (y*self.itemHgt)+self.headerHgt+textDY+yoff, 0.7, 0.7, 0.7, 1.0, self.font)
                            end
                        end
					else
                        local clipX = math.max(0, self.column2+xoff)
                        local clipY = math.max(0, (y*self.itemHgt)+self.headerHgt+yoff+self:getYScroll())
                        local clipX2 = math.min(clipX + self.column3-self.column2, self.width)
                        local clipY2 = math.min(clipY + self.itemHgt, self.height)
                        if clipX < clipX2 and clipY < clipY2 then
                            local nameColor = CleanUI_getItemNameColor()
                            local textAlpha = item:isUnwanted(getSpecificPlayer(self.player)) and 0.6 or 1.0

                            local favoriteIconSize = math.floor(self.fontHgt * 0.5)
                            local favoriteIconOffset = 0
                            local textXOffset = self.column2 + self.padding + xoff

                            if item:isFavorite() then
                                favoriteIconOffset = favoriteIconSize + 2
                                local iconY = (y*self.itemHgt) + self.headerHgt + textDY + yoff + (self.fontHgt - favoriteIconSize) / 2
                                self:drawTextureScaled(self.favoriteStar, textXOffset, iconY, favoriteIconSize, favoriteIconSize, 1, 1, 1, 1)
                                textXOffset = textXOffset + favoriteIconOffset
                            end

                            local maxNameWidth = math.floor(self.column3 - self.column2 - self.padding - favoriteIconOffset)
                            
                            if v.count > 2 then
                                local fullText = itemName.." ("..(v.count-1)..")"
                                local truncatedText = NeatTool.truncateText(fullText, maxNameWidth, self.font)
                                self:drawText(truncatedText, textXOffset, (y*self.itemHgt)+self.headerHgt+textDY+yoff, nameColor.r, nameColor.g, nameColor.b, textAlpha, self.font)
                            else
                                local truncatedText = NeatTool.truncateText(itemName, maxNameWidth, self.font)
                                self:drawText(truncatedText, textXOffset, (y*self.itemHgt)+self.headerHgt+textDY+yoff, nameColor.r, nameColor.g, nameColor.b, textAlpha, self.font)
                            end
                        end
					end
                end
               -- print("trace:j")

                --~ 				if self.mouseOverOption == y+1 and self.dragging and not self.parent:canPutIn(item) then
                --~ 							self:drawText(item:getName(), self.column2+8+xoff, (y*18)+16+1+yoff, 0.7, 0.0, 0.0, 1.0)
                    --~ 						else

                if item:getJobDelta() > 0  then
                    if  (count > 1 or self.collapsed[v.name]) then
                        local rightBoundary = self.width - self.padding - scrollBarWid
                        local maxJobWidth = math.floor(rightBoundary - self.column3 - self.padding)
                        local jobText = item:getJobType()
                        local truncatedJob = NeatTool.truncateText(jobText, maxJobWidth, self.font)
                        
                        if self.dragging == count then
                            self:drawTextRight(truncatedJob, rightBoundary+xoff, (y*self.itemHgt)+self.headerHgt+textDY+yoff, 0.7, 0.0, 0.0, 1.0, self.font)
                        else
                            self:drawTextRight(truncatedJob, rightBoundary+xoff, (y*self.itemHgt)+self.headerHgt+textDY+yoff, 0.7, 0.7, 0.7, 1.0, self.font)
                        end
                    end

                else
                    if count == 1 then
                        local rightBoundary = self.width - self.padding - scrollBarWid
                        local maxCategoryWidth = math.floor(rightBoundary - self.column3 - self.padding)
                        
                        if doDragged then
                            -- Don't draw the category when dragging
                        elseif item:getDisplayCategory() then
                            local categoryColor = CleanUI_getItemCategoryColor()
                            local categoryText = getText("IGUI_ItemCat_" .. item:getDisplayCategory())
                            local truncatedCategory = NeatTool.truncateText(categoryText, maxCategoryWidth, self.font)
                            local categoryAlign = type(CleanUI_getCategoryTextAlignment) == "function" and CleanUI_getCategoryTextAlignment() or "2"
                            local categoryTextY = (y*self.itemHgt)+self.headerHgt+textDY+yoff
                            if categoryAlign == "1" then
                                local leftBoundary = self.column3 + self.padding + xoff
                                self:drawText(truncatedCategory, leftBoundary, categoryTextY, categoryColor.r, categoryColor.g, categoryColor.b, categoryColor.a, self.font)
                            else
                                self:drawTextRight(truncatedCategory, rightBoundary+xoff, categoryTextY, categoryColor.r, categoryColor.g, categoryColor.b, categoryColor.a, self.font)
                            end
                        else
                            local categoryColor = CleanUI_getItemCategoryColor()
                            local categoryText = getText("IGUI_ItemCat_" .. item:getCategory())
                            local truncatedCategory = NeatTool.truncateText(categoryText, maxCategoryWidth, self.font)
                            local categoryAlign = type(CleanUI_getCategoryTextAlignment) == "function" and CleanUI_getCategoryTextAlignment() or "2"
                            local categoryTextY = (y*self.itemHgt)+self.headerHgt+textDY+yoff
                            if categoryAlign == "1" then
                                local leftBoundary = self.column3 + self.padding + xoff
                                self:drawText(truncatedCategory, leftBoundary, categoryTextY, categoryColor.r, categoryColor.g, categoryColor.b, categoryColor.a, self.font)
                            else
                                self:drawTextRight(truncatedCategory, rightBoundary+xoff, categoryTextY, categoryColor.r, categoryColor.g, categoryColor.b, categoryColor.a, self.font)
                            end
                        end
                    else
                        local redDetail = false
                        self:drawItemDetails(item, y, xoff, yoff, redDetail)
                    end
                end
                
                if self.selected ~= nil and self.selected[y+1] ~= nil then
                    self:resumeStencil()
                end

            end
            if count == 1 then
                if alt == nil then alt = false end
                alt = not alt
            end

            y = y + 1

            if count == 1 and self.collapsed ~= nil and v.name ~= nil and self.collapsed[v.name] then
                if instanceof(item, "Food") then
                    -- Update all food items in a collapsed stack so they separate when freshness changes.
                    for k3,v3 in ipairs(v.items) do
                        v3:updateAge()
                    end
                end
                break
            end
            if count == ISInventoryPane.MAX_ITEMS_IN_STACK_TO_RENDER + 1 then
                break
            end
            count = count + 1
           -- print("trace:zz")
        end
        end
        end
	end

    self:setScrollHeight(y * self.itemHgt)
	self:setScrollWidth(0)

	if self.draggingMarquis then
		local w = self:getMouseX() - self.draggingMarquisX
		local h = self:getMouseY() - self.draggingMarquisY
		self:drawRectBorder(self.draggingMarquisX, self.draggingMarquisY, w, h, 0.4, 0.9, 0.9, 1)
    end


    if not doDragged then
		self:drawRectStatic(1, 0, self.width-2, self.headerHgt, 1, 0, 0, 0)
    end

end

function ISInventoryPane:isLiteratureRead(playerObj, item)
    if not item then return false end
    if not item:IsLiterature() then return false end
    local modData = item:hasModData() and item:getModData() or nil
    if modData ~= nil then
        if (modData.literatureTitle) and playerObj:isLiteratureRead(modData.literatureTitle) then return true end
        if (modData.printMedia ~= nil) and playerObj:isPrintMediaRead(modData.printMedia.title) then return true end
        if (modData.learnedRecipe ~= nil) and playerObj:getKnownRecipes():contains(modData.learnedRecipe) then return true end
    end
    local skillBook = SkillBook[item:getSkillTrained()]
    if (skillBook ~= nil) and (item:getMaxLevelTrained() < playerObj:getPerkLevel(skillBook.perk) + 1) then return true end
    if (item:getNumberOfPages() > 0) and (playerObj:getAlreadyReadPages(item:getFullType()) == item:getNumberOfPages()) then return true end
    if (item:getLearnedRecipes() ~= nil) and playerObj:getKnownRecipes():containsAll(item:getLearnedRecipes()) then return true end
    return false
end

function ISInventoryPane:getProgressBarWidth()
    return 200
end

function ISInventoryPane:getProgressBarHeight()
    local height = 5
    if self.font == UIFont.Medium then
        height = 6
    elseif self.font == UIFont.Large then
        height = 7
    end
    return height
end

function ISInventoryPane:drawProgressBar(x, y, w, h, f, fg)
	if f < 0.0 then f = 0.0 end
	if f > 1.0 then f = 1.0 end
	local done = math.floor(w * f)
	if f > 0 then done = math.max(done, 1) end
	self:drawRect(x-1, y-1, w+2, h, 1.0, 0.25, 0.25, 0.25)
	self:drawRect(x, y, done, h-2, fg.a, fg.r, fg.g, fg.b)
	local bg = {r=0.5, g=0.5, b=0.5, a=1.0}
	self:drawRect(x + done, y, w - done, h-2, bg.a, bg.r, bg.g, bg.b)
end

function ISInventoryPane:drawTextAndProgressBar(text, fraction, xoff, top, fgText, fgBar)
	self:drawText(text, 40 + 30 + xoff, top + (self.itemHgt - self.fontHgt) / 2, fgText.a, fgText.r, fgText.g, fgText.b, self.font)
	local textWid = getTextManager():MeasureStringX(self.font, text)
	self:drawProgressBar(40 + math.max(120, 30 + textWid + 20) + xoff, top+(self.itemHgt/2)-1, self:getProgressBarWidth(), self:getProgressBarHeight(), fraction, fgBar)
end

function ISInventoryPane:drawItemDetails(item, y, xoff, yoff, red)

    if item == nil then
        return
    end

--~ 	print("renderdetail")
--~ 	print(red)
	local hdrHgt = self.headerHgt
	local top = hdrHgt + y * self.itemHgt + yoff
    local hc = getCore():getGoodHighlitedColor()
    local fgBar = {r=hc:getR(), g=hc:getG(), b=hc:getB(), a=1}
	local fgText = {r=0.6, g=0.8, b=0.5, a=0.6}
	if red then fgText = {r=0.0, g=0.0, b=0.5, a=0.7} end
	if instanceof(item, "HandWeapon") then
		local text = getText("IGUI_invpanel_Condition") .. ":"
		self:drawTextAndProgressBar(text, item:getCondition() / item:getConditionMax(), xoff, top, fgText, fgBar)
	elseif instanceof(item, "Drainable") and not item:hasTag(ItemTag.HIDE_REMAINING) then
		local text = getText("IGUI_invpanel_Remaining") .. ":"
		self:drawTextAndProgressBar(text, item:getCurrentUsesFloat(), xoff, top, fgText, fgBar)
    elseif item:getMeltingTime() > 0 then
		local text = getText("IGUI_invpanel_Melting") .. ":"
		self:drawTextAndProgressBar(text, item:getMeltingTime() / 100, xoff, top, fgText, fgBar)
	elseif instanceof(item, "Food") then
		if item:isIsCookable() and not item:isFrozen() and item:getHeat() > 1.6 then
			local ct = item:getCookingTime()
			local mtc = item:getMinutesToCook()
			local mtb = item:getMinutesToBurn()
			local f = ct / mtc
			local s = getText("IGUI_invpanel_Cooking") .. ":"
			if ct > mtb then
				s = getText("IGUI_invpanel_Burnt") .. ":"
			elseif ct > mtc then
				s = getText("IGUI_invpanel_Burning") .. ":"
				f = (ct - mtc) / (mtb - mtc)
                fgBar.r = getCore():getBadHighlitedColor():getR()
                fgBar.g = getCore():getBadHighlitedColor():getG()
                fgBar.b = getCore():getBadHighlitedColor():getB()
			end	
			self:drawText(s, 40 + 30 + xoff, top + (self.itemHgt - self.fontHgt) / 2, fgText.a, fgText.r, fgText.g, fgText.b, self.font)
			if item:isBurnt() then return end
			local textWid = getTextManager():MeasureStringX(self.font, s)
			self:drawProgressBar(40 + math.max(120, 30 + textWid + 20) + xoff, top+(self.itemHgt/2)-1, self:getProgressBarWidth(), self:getProgressBarHeight(), f, fgBar)
        elseif item:getFreezingTime() > 0 then
			local text = getText("IGUI_invpanel_FreezingTime") .. ":"
			self:drawTextAndProgressBar(text, item:getFreezingTime() / 100, xoff, top, fgText, fgBar)
		else
			local hunger = item:getHungerChange()
			if(hunger ~= 0) then
				local text = getText("IGUI_invpanel_Nutrition") .. ":"
				self:drawTextAndProgressBar(text, (-hunger) / 1.0, xoff, top, fgText, fgBar)
			else
				self:drawText(item:getName(getSpecificPlayer(self.player)), 40 + 30 + xoff, top + (self.itemHgt - self.fontHgt) / 2, fgText.a, fgText.r, fgText.g, fgText.b, self.font)
			end
		end
   else
		self:drawText(item:getName(getSpecificPlayer(self.player)), 40 + 30 + xoff, top + (self.itemHgt - self.fontHgt) / 2, fgText.a, fgText.r, fgText.g, fgText.b, self.font)
   end
end

function ISInventoryPane:doWorldObjectHighlight(_item)
    --attempt to find the world item, if it doesn't exist we assume it's not on the floor
    if instanceof(_item, "InventoryItem") then
        local worldItem = _item:getWorldItem()
        if (worldItem and worldItem:getChunk() ~= nil) then
            --found the world item, highlight and keep track of it
            if not self.highlightItems[worldItem] then
                worldItem:setHighlighted(self.player, true, false)
            end
            self.highlightItems[worldItem] = worldItem
        end

        --valid item is highlighted
        return true
    end

    return false
end

function ISInventoryPane:clearWorldObjectHighlights()
    for worldItem in pairs(self.highlightItems) do
        if worldItem:getItem() ~= nil then
            worldItem:setHighlighted(self.player, false)
        end
        self.highlightItems[worldItem] = nil
    end
end

function ISInventoryPane:findItemForWorldObjectHighlight(_itemTest)
    if _itemTest then
        if not instanceof(_itemTest, "InventoryItem") then
            if _itemTest.items then
                for _, item in ipairs(_itemTest.items) do
                    --break on an invalid item
                    if not self:doWorldObjectHighlight(item) then
                        break
                    end
                end
            end
        else
            self:doWorldObjectHighlight(_itemTest)
        end
    end
end

function ISInventoryPane:updateWorldObjectHighlight()
    --reset highlights
    self:clearWorldObjectHighlights()

    --controller selected
    if self.doController and self.joyselection ~= nil then
        --joyselection is offset to self.items by +1
        self:findItemForWorldObjectHighlight(self.items[self.joyselection + 1])
    end

    --selected (clicked or draggingMarquis)
    for _, item in ipairs(self.selected) do
        self:findItemForWorldObjectHighlight(item)
    end

    --hovering mouse
    if self.mouseOverOption ~= 0 then
        self:findItemForWorldObjectHighlight(self.items[self.mouseOverOption])
    end
end

function ISInventoryPane:render()

	if self.mode == "icons" then
		self:rendericons()
	elseif self.mode == "details" then
		self:renderdetails(true)
	end

	self:clearStencilRect()

	if self.resizingColumn then
		self:repaintStencilRect(self.nameHeader:getRight() - self.columnResizeButtonSize / 2 - 1, self.nameHeader.y, 1, self.height)
		self:drawRectStatic(self.nameHeader:getRight() - self.columnResizeButtonSize / 2 - 1, self.nameHeader.y, 1, self.height, 0.5, 1, 1, 1)
	end

    --highlight mouseOverOption world objects
    self:updateWorldObjectHighlight()
end

function ISInventoryPane:setMode(mode)
    self.mode = mode

end

function ISInventoryPane:onInventoryFontChanged()
	local font = getCore():getOptionInventoryFont()
	if font == "Large" then
		self.font = UIFont.Large
	elseif font == "Small" then
		self.font = UIFont.Small
	else
		self.font = UIFont.Medium
	end
	self.fontHgt = getTextManager():getFontFromEnum(self.font):getLineHeight()
	self.itemHgt = math.ceil(math.max(18, self.fontHgt) * self.zoom)
    self.texScale = math.min(32, (self.itemHgt - 2)) / 32
    
    self.contextButton1:setFont(self.font)
    self.contextButton2:setFont(self.font)
    self.contextButton3:setFont(self.font)
    self.contextButton1:setHeight(self.itemHgt)
    self.contextButton2:setHeight(self.itemHgt)
    self.contextButton3:setHeight(self.itemHgt)
end

function ISInventoryPane:setItemsToHighlight(owner, itemTable)
    if itemTable then
        self.itemsToHighlightOwner = owner
        self.itemsToHighlight = itemTable
    elseif self.itemsToHighlightOwner == owner then
        self.itemsToHighlightOwner = nil
        self.itemsToHighlight = nil
    end
end