Files
PathOfBuilding/Classes/EditControl.lua
Jack Lockwood f0df60d537 1.4.157.2
Add support for the Barrage Support skill gem
Add support for Ensnaring Arrow
Add support for Thread of Hope
Add support for Crimson Dance and amount of bleeds on enemy
Partial support for Timeless jewels
Brutal Restraint (Maraketh) and Lethal Pride (Karui) now provide stats when allocating small nodes on the tree
Elegant Hubris (Eternal) now negates all stats gained from nodes in its radius other than keystones
Add support for Void Shot granted by the Voidfletcher unique quiver
Add support for in-game jewel radius sprites
Add parsing for -res and increased phys damage delve helmet mods
Add support for "against Chilled or Frozen Enemies" mod
Add breakdown for Curse Effect for Curse Skills
Add breakdown for Aura for Aura Skills
Add breakdown for "Base from Armours" row for ES/Armour/Evasion
Add colors to the resistances' label on the side bar
Add Ctrl-Right and Ctrl-Left to text fields (skip words)
Add list of recently imported accounts to the Import/Export Build tab
Add parsing for Elusive mod on boots
Add support for "Ignites you inflict deal Damage faster" mod
Add support for "Fortify Buffs you create instead grant 30% more Evasion Rating" mod
Add missing "increased Flask Charges gained" mod to Nomad unique belt
Add support for Fungal Ground from Sporeguard unique body armour
Add Bone Armour and Mirage Warriors to skill pool
Add 15 fuses to Explosive Arrow drop-down list
Cap max elemental resistance at 90
Fix mods for many old jewels
Fix Spreading Rot jewel
Fix Chin Sol's mods
Fix quality mods on Awakened Swift Affliction and Awakened Unbound Ailments
Fix Arctic Breath's cold dot not being modified by area damage mods
Fix Transfiguration of Mind interaction bug with Crown of Eyes
Fix parsing for travel skill mods
2020-02-10 12:25:40 +11:00

658 lines
20 KiB
Lua

-- Path of Building
--
-- Class: Edit Control
-- Basic edit control.
--
local m_max = math.max
local m_min = math.min
local m_floor = math.floor
local function lastLine(str)
local lastLineIndex = 1
while true do
local nextLine = str:find("\n", lastLineIndex, true)
if nextLine then
lastLineIndex = nextLine + 1
else
break
end
end
return str:sub(lastLineIndex, -1)
end
local function newlineCount(str)
local count = 0
local lastLineIndex = 1
while true do
local nextLine = str:find("\n", lastLineIndex, true)
if nextLine then
count = count + 1
lastLineIndex = nextLine + 1
else
return count
end
end
end
local EditClass = newClass("EditControl", "ControlHost", "Control", "UndoHandler", "TooltipHost", function(self, anchor, x, y, width, height, init, prompt, filter, limit, changeFunc, lineHeight)
self.ControlHost()
self.Control(anchor, x, y, width, height)
self.UndoHandler()
self.TooltipHost()
self:SetText(init or "")
self.prompt = prompt
self.filter = filter or "^%w%p "
self.filterPattern = "["..self.filter.."]"
self.limit = limit
self.changeFunc = changeFunc
self.lineHeight = lineHeight
self.font = "VAR"
self.textCol = "^7"
self.inactiveCol = "^8"
self.disableCol = "^9"
self.selCol = "^0"
self.selBGCol = "^xBBBBBB"
self.blinkStart = GetTime()
if self.filter == "%D" or self.filter == "^%-%d" then
-- Add +/- buttons for integer number edits
self.isNumeric = true
local function buttonSize()
local width, height = self:GetSize()
return height - 4
end
self.controls.buttonDown = new("ButtonControl", {"RIGHT",self,"RIGHT"}, -2, 0, buttonSize, buttonSize, "-", function()
self:OnKeyUp("DOWN")
end)
self.controls.buttonUp = new("ButtonControl", {"RIGHT",self.controls.buttonDown,"LEFT"}, -1, 0, buttonSize, buttonSize, "+", function()
self:OnKeyUp("UP")
end)
end
self.controls.scrollBarH = new("ScrollBarControl", {"BOTTOMLEFT",self,"BOTTOMLEFT"}, 1, -1, 0, 14, 60, "HORIZONTAL", true)
self.controls.scrollBarH.width = function()
local width, height = self:GetSize()
return width - (self.controls.scrollBarV.enabled and 16 or 2)
end
self.controls.scrollBarV = new("ScrollBarControl", {"TOPRIGHT",self,"TOPRIGHT"}, -1, 1, 14, 0, (lineHeight or 0) * 3, "VERTICAL", true)
self.controls.scrollBarV.height = function()
local width, height = self:GetSize()
return height - (self.controls.scrollBarH.enabled and 16 or 2)
end
if not lineHeight then
self.controls.scrollBarH.shown = false
self.controls.scrollBarV.shown = false
end
end)
function EditClass:SetText(text, notify)
self.buf = tostring(text)
self.caret = #self.buf + 1
self.sel = nil
if notify and self.changeFunc then
self.changeFunc(self.buf)
end
self:ResetUndo()
end
function EditClass:IsMouseOver()
if not self:IsShown() then
return false
end
return self:IsMouseInBounds() or self:GetMouseOverControl()
end
function EditClass:SelectAll()
self.caret = #self.buf + 1
self.sel = 1
self:ScrollCaretIntoView()
end
function EditClass:ReplaceSel(text)
text = text:gsub("\r","")
if text:match(self.filterPattern) then
return
end
local left = m_min(self.caret, self.sel)
local right = m_max(self.caret, self.sel)
local newBuf = self.buf:sub(1, left - 1) .. text .. self.buf:sub(right)
if self.limit and #newBuf > self.limit then
return
end
self.buf = newBuf
self.caret = left + #text
self.sel = nil
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
if self.changeFunc then
self.changeFunc(self.buf)
end
self:AddUndoState()
end
function EditClass:Insert(text)
text = text:gsub("\r","")
-- Remove any illegal chars from the "text" variable, to stop resulting in no text when an illegal character is found.
text = text:gsub(self.filterPattern,"")
local newBuf = self.buf:sub(1, self.caret - 1) .. text .. self.buf:sub(self.caret)
if self.limit and #newBuf > self.limit then
return
end
self.buf = newBuf
self.caret = self.caret + #text
self.sel = nil
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
if self.changeFunc then
self.changeFunc(self.buf)
end
self:AddUndoState()
end
function EditClass:UpdateScrollBars()
local width, height = self:GetSize()
local textHeight = self.lineHeight or (height - 4)
if self.lineHeight then
self.controls.scrollBarH:SetContentDimension(DrawStringWidth(textHeight, self.font, self.buf) + 2, width - 18)
self.controls.scrollBarV:SetContentDimension(newlineCount(self.buf.."\n") * textHeight, height - (self.controls.scrollBarH.enabled and 18 or 4))
else
self.controls.scrollBarH:SetContentDimension(DrawStringWidth(textHeight, self.font, self.buf) + 2, width - 4 - (self.prompt and DrawStringWidth(textHeight, self.font, self.prompt) + textHeight/2 or 0))
end
end
function EditClass:ScrollCaretIntoView()
local width, height = self:GetSize()
local textHeight = self.lineHeight or (height - 4)
local pre = self.buf:sub(1, self.caret - 1)
local caretX = DrawStringWidth(textHeight, self.font, lastLine(pre))
self:UpdateScrollBars()
self.controls.scrollBarH:ScrollIntoView(caretX - textHeight, textHeight * 2)
if self.lineHeight then
local caretY = newlineCount(pre) * textHeight
self.controls.scrollBarV:ScrollIntoView(caretY, textHeight)
end
end
function EditClass:MoveCaretVertically(offset)
local pre = self.buf:sub(1, self.caret - 1)
local caretX = DrawStringWidth(self.lineHeight, self.font, lastLine(pre))
local caretY = newlineCount(pre) * self.lineHeight
self.caret = DrawStringCursorIndex(self.lineHeight, self.font, self.buf, caretX + 1, caretY + self.lineHeight/2 + offset)
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
end
function EditClass:Draw(viewPort)
local x, y = self:GetPos()
local width, height = self:GetSize()
local enabled = self:IsEnabled()
local mOver = self:IsMouseOver()
if not enabled then
SetDrawColor(0.33, 0.33, 0.33)
elseif mOver then
SetDrawColor(1, 1, 1)
else
SetDrawColor(0.5, 0.5, 0.5)
end
DrawImage(nil, x, y, width, height)
if not enabled then
SetDrawColor(0, 0, 0)
elseif self.hasFocus or mOver then
if self.lineHeight then
SetDrawColor(0.1, 0.1, 0.1)
else
SetDrawColor(0.15, 0.15, 0.15)
end
else
SetDrawColor(0, 0, 0)
end
DrawImage(nil, x + 1, y + 1, width - 2, height - 2)
local textX = x + 2
local textY = y + 2
local textHeight = self.lineHeight or (height - 4)
if self.prompt then
if not enabled then
DrawString(textX, textY, "LEFT", textHeight, self.font, self.disableCol..self.prompt)
else
DrawString(textX, textY, "LEFT", textHeight, self.font, self.textCol..self.prompt..":")
end
textX = textX + DrawStringWidth(textHeight, self.font, self.prompt) + textHeight/2
end
if not enabled then
return
end
if mOver then
SetDrawLayer(nil, 100)
self:DrawTooltip(x, y, width, height, viewPort)
SetDrawLayer(nil, 0)
end
self:UpdateScrollBars()
local marginL = textX - x - 2
local marginR = self.controls.scrollBarV:IsShown() and 14 or 0
local marginB = self.controls.scrollBarH:IsShown() and 14 or 0
SetViewport(textX, textY, width - 4 - marginL - marginR, height - 4 - marginB)
if not self.hasFocus then
SetDrawColor(self.inactiveCol)
DrawString(-self.controls.scrollBarH.offset, -self.controls.scrollBarV.offset, "LEFT", textHeight, self.font, self.buf)
SetViewport()
self:DrawControls(viewPort)
return
end
if not IsKeyDown("LEFTBUTTON") then
self.drag = false
end
if self.drag then
local cursorX, cursorY = GetCursorPos()
self.caret = DrawStringCursorIndex(textHeight, self.font, self.buf, cursorX - textX + self.controls.scrollBarH.offset, cursorY - textY + self.controls.scrollBarV.offset)
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
end
textX = -self.controls.scrollBarH.offset
textY = -self.controls.scrollBarV.offset
if self.lineHeight then
local left = m_min(self.caret, self.sel or self.caret)
local right = m_max(self.caret, self.sel or self.caret)
local caretX
SetDrawColor(self.textCol)
for s, line, e in (self.buf.."\n"):gmatch("()([^\n]*)\n()") do
textX = -self.controls.scrollBarH.offset
if left >= e or right <= s then
DrawString(textX, textY, "LEFT", textHeight, self.font, line)
end
if left < e then
if left > s then
local pre = line:sub(1, left - s)
DrawString(textX, textY, "LEFT", textHeight, self.font, pre)
textX = textX + DrawStringWidth(textHeight, self.font, pre)
end
if left >= s and left == self.caret then
caretX, caretY = textX, textY
end
end
if left ~= right and left < e and right > s then
local sel = self.selCol .. StripEscapes(line:sub(m_max(1, left - s + 1), m_min(#line, right - s)))
if right >= e then
sel = sel .. " "
end
local selWidth = DrawStringWidth(textHeight, self.font, sel)
SetDrawColor(self.selBGCol)
DrawImage(nil, textX, textY, selWidth, textHeight)
DrawString(textX, textY, "LEFT", textHeight, self.font, sel)
SetDrawColor(self.textCol)
textX = textX + selWidth
end
if right >= s and right < e and right == self.caret then
caretX, caretY = textX, textY
end
if right > s then
if right < e then
local post = line:sub(right - s + 1)
DrawString(textX, textY, "LEFT", textHeight, self.font, post)
textX = textX + DrawStringWidth(textHeight, self.font, post)
end
end
textY = textY + textHeight
end
if caretX then
if (GetTime() - self.blinkStart) % 1000 < 500 then
SetDrawColor(self.textCol)
DrawImage(nil, caretX, caretY, 1, textHeight)
end
end
elseif self.sel and self.sel ~= self.caret then
local left = m_min(self.caret, self.sel)
local right = m_max(self.caret, self.sel)
local pre = self.textCol .. self.buf:sub(1, left - 1)
local sel = self.selCol .. StripEscapes(self.buf:sub(left, right - 1))
local post = self.textCol .. self.buf:sub(right)
DrawString(textX, textY, "LEFT", textHeight, self.font, pre)
textX = textX + DrawStringWidth(textHeight, self.font, pre)
local selWidth = DrawStringWidth(textHeight, self.font, sel)
SetDrawColor(self.selBGCol)
DrawImage(nil, textX, textY, selWidth, textHeight)
DrawString(textX, textY, "LEFT", textHeight, self.font, sel)
DrawString(textX + selWidth, textY, "LEFT", textHeight, self.font, post)
if (GetTime() - self.blinkStart) % 1000 < 500 then
local caretX = (self.caret > self.sel) and textX + selWidth or textX
SetDrawColor(self.textCol)
DrawImage(nil, caretX, textY, 1, textHeight)
end
else
local pre = self.textCol .. self.buf:sub(1, self.caret - 1)
local post = self.buf:sub(self.caret)
DrawString(textX, textY, "LEFT", textHeight, self.font, pre)
textX = textX + DrawStringWidth(textHeight, self.font, pre)
DrawString(textX, textY, "LEFT", textHeight, self.font, post)
if (GetTime() - self.blinkStart) % 1000 < 500 then
SetDrawColor(self.textCol)
DrawImage(nil, textX, textY, 1, textHeight)
end
end
SetViewport()
self:DrawControls(viewPort)
end
function EditClass:OnFocusGained()
self.blinkStart = GetTime()
if not self.drag and not self.selControl and not self.lineHeight then
self:SelectAll()
end
end
function EditClass:OnKeyDown(key, doubleClick)
if not self:IsShown() or not self:IsEnabled() then
return
end
local mOverControl = self:GetMouseOverControl()
if mOverControl and mOverControl.OnKeyDown then
self.selControl = mOverControl
return mOverControl:OnKeyDown(key) and self
else
self.selControl = nil
end
local shift = IsKeyDown("SHIFT")
local ctrl = IsKeyDown("CTRL")
if key == "LEFTBUTTON" then
if not self.Object:IsMouseOver() then
return
end
if doubleClick then
if self.lineHeight then
if self.buf:sub(self.caret - 1, self.caret):match("^%C\n$") then
self.caret = self.caret - 1
end
while self.buf:sub(self.caret - 1, self.caret):match("[^\n][ \t]") do
self.caret = self.caret - 1
end
local caretChar = self.buf:sub(self.caret, self.caret)
if caretChar:match("%w") then
self.sel = self.caret
while self.buf:sub(self.sel - 1, self.sel - 1):match("%w") do
self.sel = self.sel - 1
end
while self.buf:sub(self.caret, self.caret):match("%w") do
self.caret = self.caret + 1
end
elseif caretChar:match("%S") then
self.sel = self.caret
while self.buf:sub(self.sel - 1, self.sel - 1) == caretChar do
self.sel = self.sel - 1
end
while self.buf:sub(self.caret, self.caret) == caretChar do
self.caret = self.caret + 1
end
end
else
self.sel = 1
self.caret = #self.buf + 1
end
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
else
self.drag = true
local x, y = self:GetPos()
local width, height = self:GetSize()
local textX = x + 2
local textY = y + 2
local textHeight = self.lineHeight or (height - 4)
if self.prompt then
textX = textX + DrawStringWidth(textHeight, self.font, self.prompt) + textHeight/2
end
local cursorX, cursorY = GetCursorPos()
self.caret = DrawStringCursorIndex(textHeight, self.font, self.buf, cursorX - textX + self.controls.scrollBarH.offset, cursorY - textY + self.controls.scrollBarV.offset)
self.sel = self.caret
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
end
elseif key == "ESCAPE" then
return
elseif key == "RETURN" then
if self.lineHeight then
self:Insert("\n")
else
if self.enterFunc then
self.enterFunc(self.buf)
end
return
end
elseif key == "a" and ctrl then
self:SelectAll()
elseif (key == "c" or key == "x") and ctrl then
if self.sel and self.sel ~= self.caret then
local left = m_min(self.caret, self.sel)
local right = m_max(self.caret, self.sel)
Copy(self.buf:sub(left, right - 1))
if key == "x" then
self:ReplaceSel("")
end
end
elseif key == "v" and ctrl then
local text = Paste()
if text then
if self.pasteFilter then
text = self.pasteFilter(text)
end
text = text:gsub("[\128-\255]","?")
if self.sel and self.sel ~= self.caret then
self:ReplaceSel(text)
else
self:Insert(text)
end
end
elseif key == "z" and ctrl then
self:Undo()
elseif key == "y" and ctrl then
self:Redo()
elseif key == "LEFT" then
self.sel = shift and (self.sel or self.caret) or nil
if self.caret > 1 then
if ctrl then
-- Skip leading space, then jump word
while self.buf:sub(self.caret-1, self.caret-1):match("[%s%p]") do
if self.caret > 1 then
self.caret = self.caret - 1
end
end
while self.buf:sub(self.caret-1, self.caret-1):match("%w") do
if self.caret > 1 then
self.caret = self.caret - 1
end
end
else
self.caret = self.caret - 1
end
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
end
elseif key == "RIGHT" then
self.sel = shift and (self.sel or self.caret) or nil
if self.caret <= #self.buf then
if ctrl then
-- Jump word, then skip trailing space,
while self.buf:sub(self.caret, self.caret):match("%w") do
if self.caret <= #self.buf then
self.caret = self.caret + 1
end
end
while self.buf:sub(self.caret, self.caret):match("[%s%p]") do
if self.caret <= #self.buf then
self.caret = self.caret + 1
end
end
else
self.caret = self.caret + 1
end
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
end
elseif key == "UP" and self.lineHeight then
self.sel = shift and (self.sel or self.caret) or nil
self:MoveCaretVertically(-self.lineHeight)
elseif key == "DOWN" and self.lineHeight then
self.sel = shift and (self.sel or self.caret) or nil
self:MoveCaretVertically(self.lineHeight)
elseif key == "HOME" then
self.sel = shift and (self.sel or self.caret) or nil
if self.lineHeight and not ctrl then
self.caret = self.caret - #lastLine(self.buf:sub(1, self.caret - 1))
else
self.caret = 1
end
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
elseif key == "END" then
self.sel = shift and (self.sel or self.caret) or nil
if self.lineHeight and not ctrl then
self.caret = self.caret + #self.buf:sub(self.caret, -1):match("[^\n]*")
else
self.caret = #self.buf + 1
end
self.lastUndoState.caret = self.caret
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
elseif key == "PAGEUP" and self.lineHeight then
self.sel = shift and (self.sel or self.caret) or nil
local width, height = self:GetSize()
self:MoveCaretVertically(-height + 18)
elseif key == "PAGEDOWN" and self.lineHeight then
self.sel = shift and (self.sel or self.caret) or nil
local width, height = self:GetSize()
self:MoveCaretVertically(height - 18)
elseif key == "BACK" then
if self.sel and self.sel ~= self.caret then
self:ReplaceSel("")
elseif self.caret > 1 then
local len = 1
if IsKeyDown("CTRL") then
while self.caret - len > 1 and self.buf:sub(self.caret - len, self.caret - len):match("%s") and not self.buf:sub(self.caret - len - 1, self.caret - len - 1):match("\n") do
len = len + 1
end
if self.buf:sub(self.caret - len, self.caret - len):match("%w") then
while self.caret - len > 1 and self.buf:sub(self.caret - len - 1, self.caret - len - 1):match("%w") do
len = len + 1
end
end
end
self.buf = self.buf:sub(1, self.caret - 1 - len) .. self.buf:sub(self.caret)
self.caret = self.caret - len
self.sel = nil
self:ScrollCaretIntoView()
self.blinkStart = GetTime()
if self.changeFunc then
self.changeFunc(self.buf)
end
self:AddUndoState()
end
elseif key == "DELETE" then
if self.sel and self.sel ~= self.caret then
self:ReplaceSel("")
elseif self.caret <= #self.buf then
local len = 1
if IsKeyDown("CTRL") then
while self.caret + len <= #self.buf and self.buf:sub(self.caret + len - 1, self.caret + len - 1):match("%s") and not self.buf:sub(self.caret + len, self.caret + len):match("\n") do
len = len + 1
end
if self.buf:sub(self.caret + len - 1, self.caret + len - 1):match("%w") then
while self.caret + len <= #self.buf and self.buf:sub(self.caret + len, self.caret + len):match("%w") do
len = len + 1
end
end
end
self.buf = self.buf:sub(1, self.caret - 1) .. self.buf:sub(self.caret + len)
self.sel = nil
self.blinkStart = GetTime()
if self.changeFunc then
self.changeFunc(self.buf)
end
self:AddUndoState()
end
elseif key == "TAB" then
return self.Object:TabAdvance(shift and -1 or 1)
end
return self
end
function EditClass:OnKeyUp(key)
if not self:IsShown() or not self:IsEnabled() then
return
end
if self.selControl then
local newSel = self.selControl:OnKeyUp(key)
if newSel then
return self
else
self.selControl = nil
end
end
if key == "LEFTBUTTON" then
if self.drag then
self.drag = false
end
elseif self.isNumeric then
local cur = tonumber(self.buf)
if key == "WHEELUP" or key == "UP" then
if cur then
self:SetText(tostring(cur + (self.numberInc or 1)), true)
else
self:SetText("1", true)
end
elseif key == "WHEELDOWN" or key == "DOWN" then
if cur and (self.filter ~= "%D" or cur > 0 )then
self:SetText(tostring(cur - (self.numberInc or 1)), true)
else
self:SetText("0", true)
end
end
elseif key == "WHEELUP" then
if self.controls.scrollBarV.enabled then
self.controls.scrollBarV:Scroll(-1)
else
self.controls.scrollBarH:Scroll(-1)
end
elseif key == "WHEELDOWN" then
if self.controls.scrollBarV.enabled then
self.controls.scrollBarV:Scroll(1)
else
self.controls.scrollBarH:Scroll(1)
end
end
return self.hasFocus and self
end
function EditClass:OnChar(key)
if not self:IsShown() or not self:IsEnabled() then
return
end
if key ~= '\b' then
if self.sel and self.sel ~= self.caret then
self:ReplaceSel(key)
else
self:Insert(key)
end
end
return self
end
function EditClass:CreateUndoState()
local state = {
buf = self.buf,
caret = self.caret,
}
self.lastUndoState = state
return state
end
function EditClass:RestoreUndoState(state)
self.buf = state.buf
self.caret = state.caret
self.sel = nil
self:ScrollCaretIntoView()
if self.changeFunc then
self.changeFunc(self.buf)
end
self.lastUndoState = state
end