local CFG_THEME = SFrames.ActiveTheme SFrames.Whisper = SFrames.Whisper or {} SFrames.Whisper.history = SFrames.Whisper.history or {} SFrames.Whisper.contacts = SFrames.Whisper.contacts or {} SFrames.Whisper.unreadCount = SFrames.Whisper.unreadCount or {} SFrames.Whisper.activeContact = nil local MAX_WHISPER_CONTACTS = 200 local MAX_MESSAGES_PER_CONTACT = 100 function SFrames.Whisper:SaveCache() if not SFramesDB then SFramesDB = {} end SFramesDB.whisperContacts = {} SFramesDB.whisperHistory = {} for _, contact in ipairs(self.contacts) do table.insert(SFramesDB.whisperContacts, contact) if self.history[contact] then local msgs = self.history[contact] -- Only persist the last MAX_MESSAGES_PER_CONTACT messages per contact local start = math.max(1, table.getn(msgs) - MAX_MESSAGES_PER_CONTACT + 1) local trimmed = {} for i = start, table.getn(msgs) do table.insert(trimmed, { time = msgs[i].time, text = msgs[i].text, isMe = msgs[i].isMe, translated = msgs[i].translated }) end SFramesDB.whisperHistory[contact] = trimmed end end end function SFrames.Whisper:LoadCache() if not SFramesDB then return end if type(SFramesDB.whisperContacts) ~= "table" or type(SFramesDB.whisperHistory) ~= "table" then return end self.contacts = {} self.history = {} for _, contact in ipairs(SFramesDB.whisperContacts) do if type(contact) == "string" and SFramesDB.whisperHistory[contact] and table.getn(SFramesDB.whisperHistory[contact]) > 0 then table.insert(self.contacts, contact) self.history[contact] = SFramesDB.whisperHistory[contact] end end -- Trim to max contacts (remove oldest = last in list) while table.getn(self.contacts) > MAX_WHISPER_CONTACTS do local oldest = table.remove(self.contacts) if oldest then self.history[oldest] = nil end end end function SFrames.Whisper:RemoveContact(contact) for i, v in ipairs(self.contacts) do if v == contact then table.remove(self.contacts, i) break end end self.history[contact] = nil self.unreadCount[contact] = nil if self.activeContact == contact then self.activeContact = self.contacts[1] end self:SaveCache() if self.frame and self.frame:IsShown() then self:UpdateContacts() self:UpdateMessages() end end function SFrames.Whisper:ClearAllHistory() self.history = {} self.contacts = {} self.unreadCount = {} self.activeContact = nil self:SaveCache() if self.frame and self.frame:IsShown() then self:UpdateContacts() self:UpdateMessages() end end local function FormatTime() local h, m = GetGameTime() return string.format("%02d:%02d", h, m) end local function GetFont() if SFrames and SFrames.GetFont then return SFrames:GetFont() end return "Fonts\\ARIALN.TTF" end local function SetRoundBackdrop(frame, bgColor, borderColor) frame:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 3, right = 3, top = 3, bottom = 3 }, }) local bg = bgColor or CFG_THEME.panelBg local bd = borderColor or CFG_THEME.panelBorder frame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1) frame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1) end local function CreateShadow(parent, size) local s = CreateFrame("Frame", nil, parent) local sz = size or 4 s:SetPoint("TOPLEFT", parent, "TOPLEFT", -sz, sz) s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", sz, -sz) s:SetFrameLevel(math.max(parent:GetFrameLevel() - 1, 0)) s:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 4, right = 4, top = 4, bottom = 4 }, }) s:SetBackdropColor(0, 0, 0, 0.55) s:SetBackdropBorderColor(0, 0, 0, 0.4) return s end local function EnsureBackdrop(frame) if not frame then return end if frame.sfCfgBackdrop then return end SetRoundBackdrop(frame) frame.sfCfgBackdrop = true end local function StyleActionButton(btn, label) SetRoundBackdrop(btn, CFG_THEME.buttonBg, CFG_THEME.buttonBorder) local fs = btn:CreateFontString(nil, "OVERLAY") fs:SetFont(GetFont(), 12, "OUTLINE") fs:SetTextColor(CFG_THEME.btnText[1], CFG_THEME.btnText[2], CFG_THEME.btnText[3]) fs:SetPoint("CENTER", btn, "CENTER", 0, 0) if label then fs:SetText(label) end btn.nLabel = fs btn:SetScript("OnEnter", function() this:SetBackdropColor(CFG_THEME.buttonHoverBg[1], CFG_THEME.buttonHoverBg[2], CFG_THEME.buttonHoverBg[3], CFG_THEME.buttonHoverBg[4]) this:SetBackdropBorderColor(CFG_THEME.btnHoverBorder[1], CFG_THEME.btnHoverBorder[2], CFG_THEME.btnHoverBorder[3], CFG_THEME.btnHoverBorder[4]) if this.nLabel then this.nLabel:SetTextColor(CFG_THEME.btnActiveText[1], CFG_THEME.btnActiveText[2], CFG_THEME.btnActiveText[3]) end end) btn:SetScript("OnLeave", function() this:SetBackdropColor(CFG_THEME.buttonBg[1], CFG_THEME.buttonBg[2], CFG_THEME.buttonBg[3], CFG_THEME.buttonBg[4]) this:SetBackdropBorderColor(CFG_THEME.buttonBorder[1], CFG_THEME.buttonBorder[2], CFG_THEME.buttonBorder[3], CFG_THEME.buttonBorder[4]) if this.nLabel then this.nLabel:SetTextColor(CFG_THEME.btnText[1], CFG_THEME.btnText[2], CFG_THEME.btnText[3]) end end) btn:SetScript("OnMouseDown", function() this:SetBackdropColor(CFG_THEME.buttonDownBg[1], CFG_THEME.buttonDownBg[2], CFG_THEME.buttonDownBg[3], CFG_THEME.buttonDownBg[4]) end) btn:SetScript("OnMouseUp", function() this:SetBackdropColor(CFG_THEME.buttonHoverBg[1], CFG_THEME.buttonHoverBg[2], CFG_THEME.buttonHoverBg[3], CFG_THEME.buttonHoverBg[4]) end) return fs end local function IsNotFullyChinese(text) -- If there's any english letter, we consider it requires translation return string.find(text, "[a-zA-Z]") ~= nil end local function TranslateMessage(text, callback) if SFramesDB and SFramesDB.Chat and SFramesDB.Chat.translateEnabled == false then callback(nil) return end if not IsNotFullyChinese(text) then callback(nil) return end local cleanText = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "") cleanText = string.gsub(cleanText, "|r", "") cleanText = string.gsub(cleanText, "|H.-|h(.-)|h", "%1") if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then _G.STranslateAPI.Translate(cleanText, "auto", "zh", function(result, err, meta) if result then callback(result) end end, "Nanami-UI") else callback(nil) end end function SFrames.Whisper:AddMessage(sender, text, isMe) if not self.history[sender] then self.history[sender] = {} table.insert(self.contacts, 1, sender) else -- Move to front for i, v in ipairs(self.contacts) do if v == sender then table.remove(self.contacts, i) break end end table.insert(self.contacts, 1, sender) end local msgData = { time = FormatTime(), text = text, isMe = isMe } table.insert(self.history[sender], msgData) if not isMe then PlaySound("TellIncoming") if self.activeContact ~= sender or not (self.frame and self.frame:IsShown()) then self.unreadCount[sender] = (self.unreadCount[sender] or 0) + 1 if SFrames.Chat and SFrames.Chat.frame and SFrames.Chat.frame.whisperButton then SFrames.Chat.frame.whisperButton.hasUnread = true if SFrames.Chat.frame.whisperButton.flashFrame then SFrames.Chat.frame.whisperButton.flashFrame:Show() end end end TranslateMessage(text, function(translated) if translated and translated ~= "" then msgData.translated = "(翻译) " .. translated if self.frame and self.frame:IsShown() and self.activeContact == sender then self:UpdateMessages() end end end) end -- Trim contacts if exceeding max while table.getn(self.contacts) > MAX_WHISPER_CONTACTS do local oldest = table.remove(self.contacts) if oldest then self.history[oldest] = nil self.unreadCount[oldest] = nil end end if self.frame and self.frame:IsShown() then self:UpdateContacts() if self.activeContact == sender then self:UpdateMessages() end end self:SaveCache() end function SFrames.Whisper:SelectContact(contact) self.activeContact = contact self.unreadCount[contact] = 0 self:UpdateContacts() self:UpdateMessages() -- Clear global unread state if no more unread local totalUnread = 0 for k, v in pairs(self.unreadCount) do totalUnread = totalUnread + v end if totalUnread == 0 and SFrames.Chat and SFrames.Chat.frame and SFrames.Chat.frame.whisperButton then SFrames.Chat.frame.whisperButton.hasUnread = false if SFrames.Chat.frame.whisperButton.flashFrame then SFrames.Chat.frame.whisperButton.flashFrame:Hide() end end if self.frame and self.frame.editBox then self.frame.editBox:SetText("") self.frame.editBox:SetFocus() end end function SFrames.Whisper:UpdateContacts() if not self.frame or not self.frame.contactList then return end local scrollChild = self.frame.contactList.scrollChild for _, child in ipairs({scrollChild:GetChildren()}) do child:Hide() end local yOffset = -5 local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF" for i, contact in ipairs(self.contacts) do local btn = self.contactButtons and self.contactButtons[i] if not btn then btn = CreateFrame("Button", nil, scrollChild) btn:SetWidth(120) btn:SetHeight(24) EnsureBackdrop(btn) local txt = btn:CreateFontString(nil, "OVERLAY") txt:SetFont(fontPath, 12, "OUTLINE") txt:SetPoint("LEFT", btn, "LEFT", 8, 0) btn.txt = txt local closeBtn = CreateFrame("Button", nil, btn) closeBtn:SetWidth(16) closeBtn:SetHeight(16) closeBtn:SetPoint("RIGHT", btn, "RIGHT", -2, 0) local closeTxt = closeBtn:CreateFontString(nil, "OVERLAY") closeTxt:SetFont(fontPath, 10, "OUTLINE") closeTxt:SetPoint("CENTER", closeBtn, "CENTER", 0, 1) closeTxt:SetText("x") closeTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) closeBtn:SetFontString(closeTxt) closeBtn:SetScript("OnEnter", function() closeTxt:SetTextColor(1, 0.4, 0.5) end) closeBtn:SetScript("OnLeave", function() closeTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end) closeBtn:SetScript("OnClick", function() SFrames.Whisper:RemoveContact(this:GetParent().contact) end) btn.closeBtn = closeBtn local unreadTxt = btn:CreateFontString(nil, "OVERLAY") unreadTxt:SetFont(fontPath, 10, "OUTLINE") unreadTxt:SetPoint("RIGHT", closeBtn, "LEFT", -2, 0) unreadTxt:SetTextColor(1, 0.4, 0.4) btn.unreadTxt = unreadTxt btn:SetScript("OnClick", function() SFrames.Whisper:SelectContact(this.contact) end) btn:SetScript("OnEnter", function() if this.contact ~= SFrames.Whisper.activeContact then this:SetBackdropColor(CFG_THEME.buttonHoverBg[1], CFG_THEME.buttonHoverBg[2], CFG_THEME.buttonHoverBg[3], CFG_THEME.buttonHoverBg[4]) end end) btn:SetScript("OnLeave", function() if this.contact ~= SFrames.Whisper.activeContact then this:SetBackdropColor(0,0,0,0) end end) if not self.contactButtons then self.contactButtons = {} end self.contactButtons[i] = btn end btn.contact = contact btn:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 5, yOffset) btn.txt:SetText(contact) local unreads = self.unreadCount[contact] or 0 if unreads > 0 then btn.unreadTxt:SetText("("..unreads..")") btn.txt:SetTextColor(1, 0.8, 0.2) else btn.unreadTxt:SetText("") btn.txt:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3]) end if contact == self.activeContact then btn:SetBackdropColor(CFG_THEME.buttonBg[1], CFG_THEME.buttonBg[2], CFG_THEME.buttonBg[3], CFG_THEME.buttonBg[4]) btn:SetBackdropBorderColor(CFG_THEME.buttonBorder[1], CFG_THEME.buttonBorder[2], CFG_THEME.buttonBorder[3], CFG_THEME.buttonBorder[4]) else btn:SetBackdropColor(0,0,0,0) btn:SetBackdropBorderColor(0,0,0,0) end btn:Show() yOffset = yOffset - 26 end scrollChild:SetHeight(math.abs(yOffset)) self.frame.contactList:UpdateScrollChildRect() local maxScroll = math.max(0, math.abs(yOffset) - 330) local slider = _G["SFramesWhisperContactScrollScrollBar"] if slider then slider:SetMinMaxValues(0, maxScroll) end end function SFrames.Whisper:UpdateMessages() if not self.frame or not self.frame.messageScroll then return end local scrollChild = self.frame.messageScroll.scrollChild if self.msgLabels then for i, fs in ipairs(self.msgLabels) do fs:Hide() end end if self.msgCopyBtns then for i, btn in ipairs(self.msgCopyBtns) do btn:Hide() end end if not self.activeContact then return end local messages = self.history[self.activeContact] or {} local yOffset = -5 local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF" if not self.msgLabels then self.msgLabels = {} end if not self.msgCopyBtns then self.msgCopyBtns = {} end for i, msg in ipairs(messages) do local fs = self.msgLabels[i] if not fs then fs = scrollChild:CreateFontString(nil, "OVERLAY") fs:SetFont(fontPath, 13, "OUTLINE") fs:SetJustifyH("LEFT") fs:SetWidth(380) if fs.EnableMouse then fs:EnableMouse(true) end self.msgLabels[i] = fs end local btn = self.msgCopyBtns[i] if not btn then btn = CreateFrame("Button", nil, scrollChild) btn:SetWidth(20) btn:SetHeight(16) local txt = btn:CreateFontString(nil, "OVERLAY") txt:SetFont(fontPath, 13, "OUTLINE") txt:SetPoint("CENTER", btn, "CENTER", 0, 0) txt:SetText("[+]") txt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) btn:SetFontString(txt) btn:SetScript("OnEnter", function() txt:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) end) btn:SetScript("OnLeave", function() txt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end) btn:SetScript("OnClick", function() if SFrames and SFrames.Chat and SFrames.Chat.OpenMessageContextMenu then SFrames.Chat:OpenMessageContextMenu("whisper", this.rawText, this.senderName) end end) self.msgCopyBtns[i] = btn end local color = msg.isMe and "|cff66ccff" or "|cffffbbee" local nameStr = msg.isMe and "我" or self.activeContact local textStr = string.format("%s[%s] %s:|r %s", color, msg.time, nameStr, msg.text) if msg.translated then textStr = textStr .. "\n|cffaaaaaa" .. msg.translated .. "|r" end btn.rawText = msg.text btn.senderName = nameStr btn:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 5, yOffset) btn:Show() fs:SetText(textStr) fs:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 28, yOffset) fs:Show() yOffset = yOffset - fs:GetHeight() - 8 end scrollChild:SetHeight(math.abs(yOffset)) self.frame.messageScroll:UpdateScrollChildRect() local maxScroll = math.max(0, math.abs(yOffset) - 270) local slider = _G["SFramesWhisperMessageScrollScrollBar"] if slider then slider:SetMinMaxValues(0, maxScroll) slider:SetValue(maxScroll) end self.frame.messageScroll:SetVerticalScroll(maxScroll) end function SFrames.Whisper:EnsureFrame() if self.frame then return end local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF" local f = CreateFrame("Frame", "SFramesWhisperContainer", UIParent) f:SetWidth(580) f:SetHeight(380) f:SetPoint("CENTER", UIParent, "CENTER", 0, 100) f:SetFrameStrata("HIGH") f:SetMovable(true) f:EnableMouse(true) f:RegisterForDrag("LeftButton") f:SetScript("OnDragStart", function() this:StartMoving() end) f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) if not self.enterHooked then self.enterHooked = true local orig_ChatFrame_OpenChat = ChatFrame_OpenChat if orig_ChatFrame_OpenChat then ChatFrame_OpenChat = function(text, chatFrame) if SFrames and SFrames.Whisper and SFrames.Whisper.frame and SFrames.Whisper.frame:IsShown() and (not text or text == "") then SFrames.Whisper.frame.editBox:SetFocus() return end orig_ChatFrame_OpenChat(text, chatFrame) end end end table.insert(UISpecialFrames, "SFramesWhisperContainer") SetRoundBackdrop(f, CFG_THEME.panelBg, CFG_THEME.panelBorder) CreateShadow(f, 5) local titleIcon = SFrames:CreateIcon(f, "chat", 14) titleIcon:SetDrawLayer("OVERLAY") titleIcon:SetPoint("TOPLEFT", f, "TOPLEFT", 15, -12) titleIcon:SetVertexColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) local title = f:CreateFontString(nil, "OVERLAY") title:SetFont(fontPath, 14, "OUTLINE") title:SetPoint("LEFT", titleIcon, "RIGHT", 4, 0) title:SetText("私聊对话管理") title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) local close = CreateFrame("Button", nil, f) close:SetWidth(18) close:SetHeight(18) close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -8, -8) close:SetFrameLevel(f:GetFrameLevel() + 3) SetRoundBackdrop(close, CFG_THEME.buttonDownBg or { 0.35, 0.06, 0.06, 0.85 }, CFG_THEME.buttonBorder or { 0.45, 0.1, 0.1, 0.6 }) local closeIco = SFrames:CreateIcon(close, "close", 12) closeIco:SetDrawLayer("OVERLAY") closeIco:SetPoint("CENTER", close, "CENTER", 0, 0) closeIco:SetVertexColor(1, 0.7, 0.7) close:SetScript("OnClick", function() f:Hide() end) close:SetScript("OnEnter", function() local h = CFG_THEME.buttonHoverBg or { 0.55, 0.1, 0.1, 0.95 } local hb = CFG_THEME.btnHoverBd or { 0.65, 0.15, 0.15, 0.9 } this:SetBackdropColor(h[1], h[2], h[3], h[4] or 0.95) this:SetBackdropBorderColor(hb[1], hb[2], hb[3], hb[4] or 0.9) end) close:SetScript("OnLeave", function() local d = CFG_THEME.buttonDownBg or { 0.35, 0.06, 0.06, 0.85 } local db = CFG_THEME.buttonBorder or { 0.45, 0.1, 0.1, 0.6 } this:SetBackdropColor(d[1], d[2], d[3], d[4] or 0.85) this:SetBackdropBorderColor(db[1], db[2], db[3], db[4] or 0.6) end) -- Contact List local contactList = CreateFrame("ScrollFrame", "SFramesWhisperContactScroll", f, "UIPanelScrollFrameTemplate") contactList:SetWidth(130) contactList:SetHeight(330) contactList:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -40) SetRoundBackdrop(contactList, CFG_THEME.listBg, CFG_THEME.listBorder) local contactChild = CreateFrame("Frame", nil, contactList) contactChild:SetWidth(130) contactChild:SetHeight(10) contactList:SetScrollChild(contactChild) contactList.scrollChild = contactChild f.contactList = contactList -- Apply Nanami-UI scrollbar styling local contactSlider = _G["SFramesWhisperContactScrollScrollBar"] if contactSlider then contactSlider:SetWidth(12) local regions = { contactSlider:GetRegions() } for i = 1, table.getn(regions) do local region = regions[i] if region and region.GetObjectType and region:GetObjectType() == "Texture" then region:SetTexture(nil) end end local track = contactSlider:CreateTexture(nil, "BACKGROUND") track:SetTexture("Interface\\Buttons\\WHITE8X8") track:SetPoint("TOPLEFT", contactSlider, "TOPLEFT", 3, 0) track:SetPoint("BOTTOMRIGHT", contactSlider, "BOTTOMRIGHT", -3, 0) track:SetVertexColor(0.22, 0.12, 0.18, 0.9) if contactSlider.SetThumbTexture then contactSlider:SetThumbTexture("Interface\\Buttons\\WHITE8X8") end local thumb = contactSlider.GetThumbTexture and contactSlider:GetThumbTexture() if thumb then thumb:SetWidth(8) thumb:SetHeight(20) thumb:SetVertexColor(0.85, 0.55, 0.70, 0.95) end local upBtn = _G["SFramesWhisperContactScrollScrollBarScrollUpButton"] local downBtn = _G["SFramesWhisperContactScrollScrollBarScrollDownButton"] if upBtn then upBtn:Hide() end if downBtn then downBtn:Hide() end contactSlider:ClearAllPoints() contactSlider:SetPoint("TOPRIGHT", contactList, "TOPRIGHT", -2, -6) contactSlider:SetPoint("BOTTOMRIGHT", contactList, "BOTTOMRIGHT", -2, 6) end -- Message List local messageScroll = CreateFrame("ScrollFrame", "SFramesWhisperMessageScroll", f, "UIPanelScrollFrameTemplate") messageScroll:SetWidth(405) messageScroll:SetHeight(270) messageScroll:SetPoint("TOPLEFT", contactList, "TOPRIGHT", 5, 0) SetRoundBackdrop(messageScroll, CFG_THEME.listBg, CFG_THEME.listBorder) local messageChild = CreateFrame("Frame", nil, messageScroll) messageChild:SetWidth(405) messageChild:SetHeight(10) messageChild:EnableMouse(true) -- Hyperlink 脚本仅部分帧类型支持,pcall 防止不支持的客户端报错 pcall(function() messageChild:SetScript("OnHyperlinkClick", function() local link = arg1 if not link then return end if IsShiftKeyDown() then if ChatFrameEditBox and ChatFrameEditBox:IsShown() then ChatFrameEditBox:Insert(link) elseif SFrames.Whisper.frame and SFrames.Whisper.frame.editBox then SFrames.Whisper.frame.editBox:Insert(link) end else pcall(function() SetItemRef(link, arg2, arg3) end) end end) end) pcall(function() messageChild:SetScript("OnHyperlinkEnter", function() local link = arg1 if not link then return end GameTooltip:SetOwner(UIParent, "ANCHOR_CURSOR") local ok = pcall(function() GameTooltip:SetHyperlink(link) end) if ok then GameTooltip:Show() else GameTooltip:Hide() end end) end) pcall(function() messageChild:SetScript("OnHyperlinkLeave", function() GameTooltip:Hide() end) end) messageScroll:SetScrollChild(messageChild) messageScroll.scrollChild = messageChild f.messageScroll = messageScroll local messageSlider = _G["SFramesWhisperMessageScrollScrollBar"] if messageSlider then messageSlider:SetWidth(12) local regions = { messageSlider:GetRegions() } for i = 1, table.getn(regions) do local region = regions[i] if region and region.GetObjectType and region:GetObjectType() == "Texture" then region:SetTexture(nil) end end local track = messageSlider:CreateTexture(nil, "BACKGROUND") track:SetTexture("Interface\\Buttons\\WHITE8X8") track:SetPoint("TOPLEFT", messageSlider, "TOPLEFT", 3, 0) track:SetPoint("BOTTOMRIGHT", messageSlider, "BOTTOMRIGHT", -3, 0) track:SetVertexColor(0.22, 0.12, 0.18, 0.9) if messageSlider.SetThumbTexture then messageSlider:SetThumbTexture("Interface\\Buttons\\WHITE8X8") end local thumb = messageSlider.GetThumbTexture and messageSlider:GetThumbTexture() if thumb then thumb:SetWidth(8) thumb:SetHeight(20) thumb:SetVertexColor(0.85, 0.55, 0.70, 0.95) end local upBtn = _G["SFramesWhisperMessageScrollScrollBarScrollUpButton"] local downBtn = _G["SFramesWhisperMessageScrollScrollBarScrollDownButton"] if upBtn then upBtn:Hide() end if downBtn then downBtn:Hide() end messageSlider:ClearAllPoints() messageSlider:SetPoint("TOPRIGHT", messageScroll, "TOPRIGHT", -2, -22) messageSlider:SetPoint("BOTTOMRIGHT", messageScroll, "BOTTOMRIGHT", -2, 22) local topBtn = CreateFrame("Button", nil, messageScroll) topBtn:SetWidth(12) topBtn:SetHeight(12) topBtn:SetPoint("BOTTOM", messageSlider, "TOP", 0, 4) local topTxt = topBtn:CreateFontString(nil, "OVERLAY") topTxt:SetFont(fontPath, 11, "OUTLINE") topTxt:SetPoint("CENTER", topBtn, "CENTER", 0, 1) topTxt:SetText("▲") topTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) topBtn:SetFontString(topTxt) topBtn:SetScript("OnEnter", function() topTxt:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) end) topBtn:SetScript("OnLeave", function() topTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end) topBtn:SetScript("OnClick", function() messageSlider:SetValue(0) end) local bottomBtn = CreateFrame("Button", nil, messageScroll) bottomBtn:SetWidth(12) bottomBtn:SetHeight(12) bottomBtn:SetPoint("TOP", messageSlider, "BOTTOM", 0, -4) local botTxt = bottomBtn:CreateFontString(nil, "OVERLAY") botTxt:SetFont(fontPath, 11, "OUTLINE") botTxt:SetPoint("CENTER", bottomBtn, "CENTER", 0, -1) botTxt:SetText("▼") botTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) bottomBtn:SetFontString(botTxt) bottomBtn:SetScript("OnEnter", function() botTxt:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) end) bottomBtn:SetScript("OnLeave", function() botTxt:SetTextColor(CFG_THEME.dimText[1], CFG_THEME.dimText[2], CFG_THEME.dimText[3]) end) bottomBtn:SetScript("OnClick", function() local _, maxVal = messageSlider:GetMinMaxValues() messageSlider:SetValue(maxVal) end) if messageScroll.EnableMouseWheel then messageScroll:EnableMouseWheel(true) messageScroll:SetScript("OnMouseWheel", function() local delta = arg1 local _, maxVal = messageSlider:GetMinMaxValues() local val = messageSlider:GetValue() - delta * 40 if val < 0 then val = 0 end if val > maxVal then val = maxVal end messageSlider:SetValue(val) end) end end local contactSlider = _G["SFramesWhisperContactScrollScrollBar"] if contactList and contactSlider and contactList.EnableMouseWheel then contactList:EnableMouseWheel(true) contactList:SetScript("OnMouseWheel", function() local delta = arg1 local _, maxVal = contactSlider:GetMinMaxValues() local val = contactSlider:GetValue() - delta * 30 if val < 0 then val = 0 end if val > maxVal then val = maxVal end contactSlider:SetValue(val) end) end -- EditBox for replying local editBox = CreateFrame("EditBox", "SFramesWhisperEditBox", f, "InputBoxTemplate") editBox:SetWidth(405) editBox:SetHeight(20) editBox:SetPoint("TOPLEFT", messageScroll, "BOTTOMLEFT", 0, -10) editBox:SetAutoFocus(false) editBox:SetFont(fontPath, 13, "OUTLINE") local translateCheck = CreateFrame("CheckButton", "SFramesWhisperTranslateCheck", f, "UICheckButtonTemplate") translateCheck:SetWidth(24) translateCheck:SetHeight(24) translateCheck:SetPoint("TOPLEFT", editBox, "BOTTOMLEFT", -5, -6) local txt = translateCheck:CreateFontString(nil, "OVERLAY") txt:SetFont(fontPath, 11, "OUTLINE") txt:SetPoint("LEFT", translateCheck, "RIGHT", 2, 0) txt:SetText("发前自动翻译 (译后可修改再回车)") txt:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3]) -- Hide the default text and reskin the button if you have defined StyleCfgCheck inside your environment if _G["SFramesWhisperTranslateCheckText"] then _G["SFramesWhisperTranslateCheckText"]:Hide() end -- Reskin checkbox local function StyleCheck(cb) local box = CreateFrame("Frame", nil, cb) box:SetPoint("TOPLEFT", cb, "TOPLEFT", 2, -2) box:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -2, 2) local boxLevel = (cb:GetFrameLevel() or 1) - 1 if boxLevel < 0 then boxLevel = 0 end box:SetFrameLevel(boxLevel) EnsureBackdrop(box) if box.SetBackdropColor then box:SetBackdropColor(CFG_THEME.buttonBg[1], CFG_THEME.buttonBg[2], CFG_THEME.buttonBg[3], CFG_THEME.buttonBg[4]) end if box.SetBackdropBorderColor then box:SetBackdropBorderColor(CFG_THEME.buttonBorder[1], CFG_THEME.buttonBorder[2], CFG_THEME.buttonBorder[3], CFG_THEME.buttonBorder[4]) end cb.sfBox = box if cb.GetNormalTexture and cb:GetNormalTexture() then cb:GetNormalTexture():Hide() end if cb.GetPushedTexture and cb:GetPushedTexture() then cb:GetPushedTexture():Hide() end if cb.GetHighlightTexture and cb:GetHighlightTexture() then cb:GetHighlightTexture():Hide() end if cb.SetCheckedTexture then cb:SetCheckedTexture("Interface\\Buttons\\WHITE8X8") end local checked = cb.GetCheckedTexture and cb:GetCheckedTexture() if checked then checked:ClearAllPoints() checked:SetPoint("TOPLEFT", cb, "TOPLEFT", 5, -5) checked:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -5, 5) checked:SetVertexColor(CFG_THEME.checkFill[1], CFG_THEME.checkFill[2], CFG_THEME.checkFill[3], CFG_THEME.checkFill[4]) end end StyleCheck(translateCheck) f.translateCheck = translateCheck local function SendReply() if SFrames.Whisper.isTranslating then return end local text = editBox:GetText() if not text or text == "" or not SFrames.Whisper.activeContact then return end if f.translateCheck and f.translateCheck:GetChecked() then if text == SFrames.Whisper.lastTranslation then SendChatMessage(text, "WHISPER", nil, SFrames.Whisper.activeContact) editBox:SetText("") editBox:ClearFocus() SFrames.Whisper.lastTranslation = nil else local targetLang = "en" if not string.find(text, "[\128-\255]") then targetLang = "zh" end if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then SFrames.Whisper.isTranslating = true editBox:SetText("翻译中...") _G.STranslateAPI.Translate(text, "auto", targetLang, function(result, err, meta) SFrames.Whisper.isTranslating = false if result then editBox:SetText(result) SFrames.Whisper.lastTranslation = result editBox:SetFocus() else editBox:SetText(text) DEFAULT_CHAT_FRAME:AddMessage("|cffff3333[私聊翻译失败]|r " .. tostring(err)) end end, "Nanami-UI") else DEFAULT_CHAT_FRAME:AddMessage("|cffff3333[Nanami-UI] STranslate插件未加载|r") SFrames.Whisper.lastTranslation = text end end else SendChatMessage(text, "WHISPER", nil, SFrames.Whisper.activeContact) editBox:SetText("") editBox:ClearFocus() end end editBox:SetScript("OnEnterPressed", SendReply) editBox:SetScript("OnEscapePressed", function() this:ClearFocus() end) f.editBox = editBox local sendBtn = CreateFrame("Button", nil, f) sendBtn:SetWidth(60) sendBtn:SetHeight(24) sendBtn:SetPoint("TOPRIGHT", editBox, "BOTTOMRIGHT", 0, -5) StyleActionButton(sendBtn, "发送") sendBtn:SetScript("OnClick", SendReply) f:Hide() self.frame = f end function SFrames.Whisper:Toggle() self:EnsureFrame() if self.frame:IsShown() then self.frame:Hide() else self.frame:Show() self:UpdateContacts() if self.contacts[1] and not self.activeContact then self:SelectContact(self.contacts[1]) elseif self.activeContact then self:SelectContact(self.activeContact) end end end -- Hook Events local eventFrame = CreateFrame("Frame") eventFrame:RegisterEvent("CHAT_MSG_WHISPER") eventFrame:RegisterEvent("CHAT_MSG_WHISPER_INFORM") eventFrame:RegisterEvent("PLAYER_LOGIN") eventFrame:SetScript("OnEvent", function() if event == "PLAYER_LOGIN" then SFrames.Whisper:LoadCache() return end local text = arg1 local sender = arg2 if not sender or sender == "" then return end sender = string.gsub(sender, "-.*", "") -- remove realm name if attached if event == "CHAT_MSG_WHISPER" then SFrames.Whisper:AddMessage(sender, text, false) elseif event == "CHAT_MSG_WHISPER_INFORM" then SFrames.Whisper:AddMessage(sender, text, true) end end)