From 6469a557ff826440cf4dac5cb783ca6591893361 Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:48:02 -0500 Subject: [PATCH] Reuse one question dialog, backfill chat history, dim arrow icons on events CreateQuestionDialog builds its frame once and sizes it right away from the text's unbounded width. A frame per call broke the one-dialog guard after the first dialog, since 1.12 binds a frame name only once. An onclose that opens the next question runs on the next frame. Chat history goes in with BackFillMessage, above the lines the frame already has. SkinArrowButton dims its icon from OnEnable/OnDisable instead of an OnUpdate per button. --- api/ui-widgets.lua | 265 ++++++++++++++++++++++----------------------- modules/chat.lua | 9 +- 2 files changed, 133 insertions(+), 141 deletions(-) diff --git a/api/ui-widgets.lua b/api/ui-widgets.lua index afda557c..e2b5fe5b 100644 --- a/api/ui-widgets.lua +++ b/api/ui-widgets.lua @@ -391,6 +391,14 @@ function pfUI.api.SkinCloseButton(button, parentFrame, offsetX, offsetY) button.texture:SetVertexColor(1,.25,.25,1) end +local function ArrowButtonOnEnable() + this.icon:SetVertexColor(.8,.8,.8,1) +end + +local function ArrowButtonOnDisable() + this.icon:SetVertexColor(.2,.2,.2,1) +end + function pfUI.api.SkinArrowButton(button, dir, size) if not button then return end @@ -415,19 +423,17 @@ function pfUI.api.SkinArrowButton(button, dir, size) button.icon:SetTexture(pfUI.media["img:"..dir]) - if not button.pficonfade then - local button, state = button, nil - button.pficonfade = CreateFrame("Frame", nil, button) - button.pficonfade:SetScript("OnUpdate", function() - if state == button:IsEnabled() then return end - state = button:IsEnabled() + -- the icon dims while the button is disabled + if not button.pfArrowHooked then + button:HookScript("OnEnable", ArrowButtonOnEnable) + button:HookScript("OnDisable", ArrowButtonOnDisable) + button.pfArrowHooked = true + end - if state > 0 then - button.icon:SetVertexColor(.8,.8,.8,1) - else - button.icon:SetVertexColor(.2,.2,.2,1) - end - end) + if button:IsEnabled() > 0 then + button.icon:SetVertexColor(.8,.8,.8,1) + else + button.icon:SetVertexColor(.2,.2,.2,1) end end @@ -641,148 +647,137 @@ function pfUI.api.SkinSlider(frame) end -- [ Question Dialog ] --- Creates a pfUI user dialog popup: +-- Shows the pfUI user dialog popup. A call while it is shown closes it. -- 'text' [string] text that will be displayed. -- 'yes' [function] function that is triggered on 'Okay' button. -- 'no' [function] function that is triggered on 'Cancel' button. +-- either can be { caption, function }. -- 'editbox' [bool] if set, a inputfield will be shown. it can be. --- accessed with "GetParent().input". -function pfUI.api.CreateQuestionDialog(text, yes, no, editbox, onclose) - -- do not allow multiple instances of question dialogs - if _G["pfQuestionDialog"] and _G["pfQuestionDialog"]:IsShown() then - _G["pfQuestionDialog"]:Hide() - _G["pfQuestionDialog"] = nil - return - end - - local yes, no = yes, no - local yescap, nocap = YES, NO - - if yes and type(yes) == "table" then - yescap = yes[1] - yes = yes[2] - end - - if no and type(no) == "table" then - nocap = no[1] - no = no[2] - end - - if not text then text = "Are you sure?" end - - local rawborder, border = GetBorderSize() +-- accessed with "this:GetParent().input". +-- 'onclose' [function] runs once the dialog has closed. +do local padding = 15 + local question - -- frame - local question = CreateFrame("Frame", "pfQuestionDialog", UIParent) - question:ClearAllPoints() - question:SetPoint("CENTER", 0, 0) - question:SetFrameStrata("TOOLTIP") - question:SetMovable(true) - question:EnableMouse(true) - question:RegisterForDrag("LeftButton") - question:SetScript("OnDragStart",function() - this:StartMoving() - end) - - question:SetScript("OnDragStop",function() - this:StopMovingOrSizing() - end) - - question:SetScript("OnHide", onclose) - - pfUI.api.CreateBackdrop(question, nil, nil, .85) - pfUI.api.CreateBackdropShadow(question) - - -- text - question.text = question:CreateFontString("Status", "LOW", "GameFontNormal") - question.text:SetFontObject(GameFontWhite) - question.text:SetPoint("TOPLEFT", question, "TOPLEFT", padding, -padding) - question.text:SetPoint("TOPRIGHT", question, "TOPRIGHT", -padding, -padding) - question.text:SetText(text) - - -- editbox - if editbox then - question.input = CreateFrame("EditBox", "pfQuestionDialogEdit", question) - pfUI.api.CreateBackdrop(question.input) - question.input:SetTextColor(.2,1,.8,1) - question.input:SetJustifyH("CENTER") - question.input:SetAutoFocus(false) - question.input:SetPoint("TOPLEFT", question.text, "BOTTOMLEFT", border, -padding) - question.input:SetPoint("TOPRIGHT", question.text, "BOTTOMRIGHT", -border, -padding) - question.input:SetHeight(20) - - question.input:SetFontObject(GameFontNormal) - question.input:SetScript("OnEscapePressed", function() this:ClearFocus() end) - question.input:SetAutoFocus(true) + local function QuestionOnHide() + -- onclose may show the next question in this frame, so it runs once + -- the hide is over + if this.onclose then + RunNextFrame(this.onclose) + this.onclose = nil + end end - -- buttons - question.yes = CreateFrame("Button", "pfQuestionDialogYes", question, "UIPanelButtonTemplate") - pfUI.api.SkinButton(question.yes) - question.yes:SetSize(100, 22) - question.yes:SetText(yescap) - question.yes:SetScript("OnClick", function() - if yes then yes() end + -- the answer runs with 'this' on its button, where it finds the input + local function AnswerOnClick() + if this.answer then this.answer() end this:GetParent():Hide() - end) - - if question.input then - question.yes:SetPoint("TOPRIGHT", question.input, "BOTTOM", -3*border, -padding) - else - question.yes:SetPoint("TOPRIGHT", question.text, "BOTTOM", -3*border, -padding) end - question.no = CreateFrame("Button", "pfQuestionDialogNo", question, "UIPanelButtonTemplate") - pfUI.api.SkinButton(question.no) - question.no:SetSize(100, 22) - question.no:SetText(nocap) - question.no:SetScript("OnClick", function() - if no then no() end - this:GetParent():Hide() - end) + local function CreateQuestion() + local _, border = GetBorderSize() - if question.input then - question.no:SetPoint("TOPLEFT", question.input, "BOTTOM", 3*border, -padding) - else - question.no:SetPoint("TOPLEFT", question.text, "BOTTOM", 3*border, -padding) + local f = CreateFrame("Frame", nil, UIParent) + f:SetFrameStrata("TOOLTIP") + f:SetMovable(true) + f:EnableMouse(true) + f:RegisterForDrag("LeftButton") + f:SetScript("OnDragStart", function() this:StartMoving() end) + f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) + f:SetScript("OnHide", QuestionOnHide) + f:Hide() + + CreateBackdrop(f, nil, nil, .85) + CreateBackdropShadow(f) + + f.text = f:CreateFontString(nil, "LOW", "GameFontWhite") + f.text:SetPoint("TOP", 0, -padding) + + f.input = CreateFrame("EditBox", nil, f) + CreateBackdrop(f.input) + f.input:SetTextColor(.2,1,.8,1) + f.input:SetJustifyH("CENTER") + f.input:SetPoint("TOPLEFT", f.text, "BOTTOMLEFT", border, -padding) + f.input:SetPoint("TOPRIGHT", f.text, "BOTTOMRIGHT", -border, -padding) + f.input:SetHeight(20) + f.input:SetFontObject(GameFontNormal) + f.input:SetScript("OnEscapePressed", function() this:ClearFocus() end) + f.input:SetAutoFocus(true) + + f.yes = CreateFrame("Button", nil, f, "UIPanelButtonTemplate") + SkinButton(f.yes) + f.yes:SetSize(100, 22) + f.yes:SetScript("OnClick", AnswerOnClick) + + f.no = CreateFrame("Button", nil, f, "UIPanelButtonTemplate") + SkinButton(f.no) + f.no:SetSize(100, 22) + f.no:SetScript("OnClick", AnswerOnClick) + + f.close = CreateFrame("Button", nil, f) + f.close:SetPoint("TOPRIGHT", -border, -border) + CreateBackdrop(f.close) + f.close:SetSize(10, 10) + f.close.texture = f.close:CreateTexture() + f.close.texture:SetTexture(pfUI.media["img:close"]) + f.close.texture:SetAllPoints(f.close) + f.close.texture:SetVertexColor(1,.25,.25,1) + f.close:SetScript("OnEnter", function() + this.backdrop:SetBackdropBorderColor(1,.25,.25,1) + end) + f.close:SetScript("OnLeave", function() + CreateBackdrop(this) + end) + f.close:SetScript("OnClick", function() + this:GetParent():Hide() + end) + + return f end - question.close = CreateFrame("Button", "pfQuestionDialogClose", question) - question.close:SetPoint("TOPRIGHT", -border, -border) - pfUI.api.CreateBackdrop(question.close) - question.close:SetSize(10, 10) - question.close.texture = question.close:CreateTexture("pfQuestionDialogCloseTex") - question.close.texture:SetTexture(pfUI.media["img:close"]) - question.close.texture:ClearAllPoints() - question.close.texture:SetAllPoints(question.close) - question.close.texture:SetVertexColor(1,.25,.25,1) - question.close:SetScript("OnEnter", function () - this.backdrop:SetBackdropBorderColor(1,.25,.25,1) - end) + function pfUI.api.CreateQuestionDialog(text, yes, no, editbox, onclose) + question = question or CreateQuestion() - question.close:SetScript("OnLeave", function () - pfUI.api.CreateBackdrop(this) - end) + -- do not allow multiple instances of question dialogs + if question:IsShown() then + question:Hide() + return + end - question.close:SetScript("OnClick", function() - this:GetParent():Hide() - end) + local yescap, nocap = YES, NO + if type(yes) == "table" then yescap, yes = yes[1], yes[2] end + if type(no) == "table" then nocap, no = no[1], no[2] end - -- resize window - local textspace = question.text:GetHeight() + padding - local inputspace = 0 - if question.input then inputspace = question.input:GetHeight() + padding end - local buttonspace = question.no:GetHeight() + padding - question:SetHeight(textspace + inputspace + buttonspace + padding) + question.onclose = onclose + question.yes.answer = yes + question.yes:SetText(yescap) + question.no.answer = no + question.no:SetText(nocap) - local width = 200 - - -- delay the auto sizing, to make sure the font rendering happened - RunNextFrame(function() - if question.text:GetStringWidth() > width then width = question.text:GetStringWidth() end + -- the dialog is as wide as the longest line of text, and at least 200 + question.text:SetText(text or "Are you sure?") + local width = math.max(200, question.text:GetUnboundedStringWidth()) + question.text:SetWidth(width) question:SetWidth(width + 2*padding) - end) + + -- the buttons go below the input field when it is shown + local _, border = GetBorderSize() + local above = editbox and question.input or question.text + question.input:SetShown(editbox) + question.input:SetText("") + question.yes:ClearAllPoints() + question.yes:SetPoint("TOPRIGHT", above, "BOTTOM", -3*border, -padding) + question.no:ClearAllPoints() + question.no:SetPoint("TOPLEFT", above, "BOTTOM", 3*border, -padding) + + local height = padding + question.text:GetStringHeight() + padding + question.no:GetHeight() + padding + if editbox then height = height + question.input:GetHeight() + padding end + question:SetHeight(height) + + question:ClearAllPoints() + question:SetPoint("CENTER", 0, 0) + question:Show() + end end diff --git a/modules/chat.lua b/modules/chat.lua index 450cccac..39f898d1 100644 --- a/modules/chat.lua +++ b/modules/chat.lua @@ -924,12 +924,9 @@ pfUI:RegisterModule("chat", function () if not _G["ChatFrame"..i].HookAddMessage then if C.chat.text.history == "1" then - -- write history to chat - local history = GetChatHistory(i) - for j=30,0,-1 do - if history[j] then - _G["ChatFrame"..i]:AddMessage(history[j], .7,.7,.7) - end + -- write history to chat, newest first, above the lines already there + for _, msg in ipairs(GetChatHistory(i)) do + _G["ChatFrame"..i]:BackFillMessage(msg, .7,.7,.7) end end