local _, T = ... local XU, type = T.exUI, type local assert, getWidgetData, newWidgetData, setWidgetData, AddObjectMethods, CallObjectScript = XU:GetImpl() local TextArea, TextAreaData, int = {}, {}, {} local TextAreaProps = { api=TextArea, scripts={"OnCursorChanged"}, self2methods={SetHyperlinksEnabled=1, GetHyperlinksEnabled=1}, self2scripts={"OnKeyDown", "OnKeyUp", "OnChar", "OnHyperlinkClick", "OnHyperlinkEnter", "OnHyperlinkLeave"}, } AddObjectMethods({"TextArea", "EditBox"}, TextAreaProps) local tooltipBackdrop = {edgeFile="Interface/Tooltips/UI-Tooltip-Border", bgFile="Interface/DialogFrame/UI-DialogBox-Background-Dark", tile=true, edgeSize=16, tileSize=16, insets={left=4,right=4,bottom=4,top=4}, bgColor=0xb2000000, edgeColor=0xb2b2b2} function TextArea:GetHighlightText() local editBox = assert(getWidgetData(self, TextAreaData), 'invalid object type').editBox local text, curPos = editBox:GetText(), editBox:GetCursorPosition() editBox:Insert("") local text2, selStart = editBox:GetText(), editBox:GetCursorPosition() local selEnd = selStart + #text - #text2 if text ~= text2 then editBox:SetText(text) editBox:SetCursorPosition(curPos) editBox:HighlightText(selStart, selEnd) end return text:sub(selStart+1, selEnd), selStart end function TextArea:SetHighlightText(newText, preserveCaret) assert(type(newText) == 'string', 'Syntax: TextArea:SetHighlightText("text"[, preserveCaret])') local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') local editBox, op, op2 = d.editBox d.holdScroll, op = true, preserveCaret and editBox:GetCursorPosition() editBox:Insert("") op2 = editBox:GetCursorPosition() editBox:Insert(newText) d.holdScroll = nil if op == op2 then editBox:SetCursorPosition(op2) end editBox:HighlightText(op2, op2+#newText) end function TextArea:SetStickyFocus(sticky) assert(sticky == nil or type(sticky) == "function" or type(sticky) == "boolean", 'Syntax: TextArea:SetStickyFocus(sticky)') local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') d.editBox.HasStickyFocus = sticky == true and int.alwaysHasStickyFocus or sticky or nil end function TextArea:SetStyle(style) assert(type(style) == "string", 'Syntax: TextArea:SetStyle("style")') local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') local px, py = 0, 0 if style == "tooltip" then if not d.backdrop then d.backdrop = XU:Create("Backdrop", d.self) end d.backdrop:SetBackdrop(tooltipBackdrop) px, py = 5, 5 elseif d.backdrop then d.backdrop:SetBackdrop(nil) end d.clipArea:SetPoint("TOPLEFT", px, -py) d.scrollBar:SetPoint("TOPRIGHT", -px, -py) d.scrollBar:SetPoint("BOTTOMRIGHT", -px, py) end function int.alwaysHasStickyFocus() return true end function int:OnCursorChanged(...) local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') if d.holdScroll then return end repeat local _x, y, _w, h = ... local sb, insT, insB = d.scrollBar, 2, 2 local occH, occP, y = d.clipArea:GetHeight(), sb:GetValue(), -y if not self:HasFocus() then -- only move if focused elseif occP > y-insT then occP = y > insT and y-insT or 0 -- too far elseif occP < y+h-occH+insB+insT then occP = y+h-occH+insB+insT -- not far enough else break end local _, mx = sb:GetMinMaxValues() occP = (mx-occP)^2 < 1 and mx or math.floor(occP) sb:SetMinMaxValues(0, occP < mx and mx or occP) sb:SetWindowRange(occH) sb:SetValue(occP) until 1 CallObjectScript(d.self, "OnCursorChanged", ...) end function int:OnClick() local eb = assert(getWidgetData(self, TextAreaData), 'invalid object type').editBox eb:SetCursorPosition(#eb:GetText()) eb:SetFocus() end function int:OnScrollValueChanged(nv) local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') d.editBox:SetPoint("TOPLEFT", 0, nv) d.editBox:SetPoint("TOPRIGHT", 0, nv) end function int:OnSizeChanged() local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') if d.holdScroll then return end local sb, ch, th = d.scrollBar, d.clipArea:GetHeight(), d.editBox:GetHeight() + (d.editBox:GetText():sub(-1) == "\n" and 12+2/9 or 0) sb:SetMinMaxValues(0, th > ch and th-ch or 0) sb:SetWindowRange(ch) sb:SetStepsPerPage(math.min(5,math.ceil(ch/18))) end function int:OnShow() local d = assert(getWidgetData(self, TextAreaData), 'invalid object type') d.scrollBar:SetValue(0) end local function CreateTextArea(name, parent, outerTemplate, id) local area, d = CreateFrame("Frame", name, parent, outerTemplate, id) local sb = XU:Create("ScrollBar", nil, area) sb:SetPoint("TOPRIGHT") sb:SetPoint("BOTTOMRIGHT") sb:SetScript("OnValueChanged", int.OnScrollValueChanged) sb:SetWheelScrollTarget(area) sb:SetValueStep(12) local ec = CreateFrame("Frame", nil, area) ec:SetPoint("TOPLEFT") ec:SetPoint("BOTTOMRIGHT", sb, "BOTTOMLEFT") ec:SetClipsChildren(true) ec:SetScript("OnSizeChanged", int.OnSizeChanged) ec:EnableMouse(1) ec:SetScript("OnMouseDown", int.OnClick) local input = CreateFrame("EditBox", type(name) == "string" and name .. "EB" or nil, ec) input:SetPoint("TOPLEFT") input:SetPoint("TOPRIGHT") input:SetMultiLine(true) input:SetAutoFocus(false) input:SetFontObject(GameFontHighlight) input:SetScript("OnCursorChanged", int.OnCursorChanged) input:SetScript("OnSizeChanged", int.OnSizeChanged) input:SetScript("OnShow", int.OnShow) d = newWidgetData(area, TextAreaData, TextAreaProps, input) d.clipArea, d.scrollBar, d.editBox = ec, sb, input setWidgetData(input, TextAreaData, d) setWidgetData(sb, TextAreaData, d) setWidgetData(ec, TextAreaData, d) return area end XU:RegisterFactory("TextArea", CreateTextArea)