--- MSA-Tutorials-1.0 --- Tutorials from Marouan Sabbagh based on CustomTutorials from João Cardoso. --[[ Copyright 2010-2015 João Cardoso CustomTutorials is distributed under the terms of the GNU General Public License (or the Lesser GPL). CustomTutorials is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. CustomTutorials is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with CustomTutorials. If not, see . --]] --[[ General Arguments ----------------- savedvariable icon ........... Default is "?" icon. Image path (tga or blp). title .......... Default is "Tutorial". width .......... Default is 350. Internal frame width (without borders). font ........... Default is game font (empty string). Frame Arguments --------------- title .......... Title relative to frame (replace General value). width .......... Width relative to frame (replace General value). Note: All other arguments can be used as a general! image .......... [optional] Image path (tga or blp). imageHeight .... Default is 128. Default image size is 256x128. imageX ......... Default is 0 (center). Left/Right position relative to center. imageY ......... Default is 20 (top margin). text ........... Text string. textHeight ..... Default is 0 (auto height). textX .......... Default is 25. Left and Right margin. textY .......... Default is 20 (top margin). editbox ........ [optional] Table of edit boxes. Edit box is out of content flow. One table keys: - text ..... [required] - width .... Default is 400 - left ..... Default is 0 - top ...... Default is 0 - bottom ... Default is 0 button ......... [optional] Button text string (directing value). Button is out of content flow. buttonWidth .... Default is 100. buttonClick .... Function with button's click action. buttonLeft, buttonBottom shine .......... [optional] The frame to anchor the flashing "look at me!" glow. shineTop, shineBottom, shineLeft, shineRight point .......... Default is "CENTER". anchor ......... Default is "UIParent". relPoint ....... Default is "CENTER". x, y ........... Default is 0, 0. --]] -- Lua API local floor = math.floor local fmod = math.fmod local format = string.format local strfind = string.find local round = function(n) return floor(n + 0.5) end local Lib = LibStub:NewLibrary('MSA-Tutorials-1.0', 11) if Lib then Lib.NewFrame, Lib.NewButton, Lib.UpdateFrame = nil Lib.numFrames = Lib.numFrames or 1 Lib.frames = Lib.frames or {} else return end local BUTTON_TEX = 'Interface\\Buttons\\UI-SpellbookIcon-%sPage-%s' local Frames = Lib.frames local freeEditboxes = {} local default = { title = "Tutorial", width = 350, font = "", imageHeight = 128, imageX = 0, imageY = 20, textHeight = 0, textX = 25, textY = 20, buttonWidth = 100, point = "CENTER", anchor = UIParent, relPoint = "CENTER", x = 0, y = 0, } --[[ Internal API ]]-- local function ConvertPixelsToUI(pixels, frameScale) local physicalScreenHeight = select(2, GetPhysicalScreenSize()); return (pixels * 768.0)/(physicalScreenHeight * frameScale); end local function NewEditbox(frame, width) local numFreeEditboxes = #freeEditboxes local editbox if numFreeEditboxes > 0 then editbox = tremove(freeEditboxes, numFreeEditboxes) editbox:SetParent(frame) else editbox = CreateFrame('EditBox', nil, frame, 'InputBoxTemplate') editbox:SetAutoFocus(false) end editbox:SetWidth(width) editbox:SetHeight(20) editbox:Show() return editbox end local function RemoveEditboxes(frame) for i = 1, #frame.editboxes do tinsert(freeEditboxes, frame.editboxes[i]) frame.editboxes[i]:Hide() frame.editboxes[i] = nil end end local function UpdateFrame(frame, i) local data = frame.data[i] if not data then return end if not data.image and not data.textY then data.textY = 0 end for k, v in pairs(default) do if not data[k] then if not frame.data[k] then data[k] = v else data[k] = frame.data[k] end end end -- Callbacks if frame.data.onShow then frame.data.onShow(frame.data, i) end -- Frame frame:ClearAllPoints() frame:SetPoint(data.point, data.anchor, data.relPoint, data.x, data.y) frame:SetWidth(data.width + 16) local titleText = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE and frame.TitleContainer.TitleText or frame.TitleText titleText:SetPoint('TOP', 0, -5) titleText:SetText(data.title) -- Cache inline texture local j, idx = 1, 1 local lastTex while idx do local s, e, tex = strfind(data.text, "|T(Interface\\AddOns\\[^:]+)[^|]+|t", idx) if tex then if tex ~= lastTex then if not frame["cache"..j] then frame["cache"..j] = frame:CreateTexture() end frame["cache"..j]:SetTexture(tex) lastTex = tex j = j + 1 end idx = e else break end end -- Image for _, image in pairs(frame.images) do image:Hide() end if data.image then local img = frame.images[i] or frame:CreateTexture() img:SetPoint('TOP', frame, data.imageX - 1, -(26 + data.imageY)) img:SetTexture(data.image) img:Show() frame.images[i] = img end -- Text frame.text:SetPoint('TOP', frame, 0, -((data.image and 26 + data.imageY + data.imageHeight or 60) + data.textY)) frame.text:SetWidth(data.width - (2 * data.textX)) frame.text:SetText(data.text) local textHeight = round(frame.text:GetHeight()) if data.textHeight > textHeight then textHeight = data.textHeight end textHeight = textHeight - fmod(textHeight, 2) frame:SetHeight((data.image and 56 + data.imageY + data.imageHeight or 90) + (data.text and data.textY + textHeight or 0) + 18) frame.i = i frame:Show() -- EditBox RemoveEditboxes(frame) if data.editbox then for i = 1, #data.editbox do frame.editboxes[i] = NewEditbox(frame, data.editbox[i].width or 400) frame.editboxes[i]:ClearFocus() frame.editboxes[i]:ClearAllPoints() if data.editbox[i].top then frame.editboxes[i]:SetPoint('TOPLEFT', 14 + data.textX + (data.editbox[i].left or 0), -(60 + data.textY + (data.editbox[i].top or 0))) elseif data.editbox[i].bottom then frame.editboxes[i]:SetPoint('BOTTOMLEFT', 14 + data.textX + (data.editbox[i].left or 0), 28 + 18 + (data.editbox[i].bottom or 0)) end frame.editboxes[i]:SetText(data.editbox[i].text) end end -- Button if data.button then frame.button:SetWidth(data.buttonWidth) frame.button:SetPoint('BOTTOMLEFT', 8 + data.textX + (data.buttonLeft or 0), 28 + 18 + (data.buttonBottom or 0)) frame.button:SetText(data.button) frame.button:SetScript('OnClick', data.buttonClick) frame.button:Show() else frame.button:Hide() end -- Shine if data.shine then frame.shine:SetParent(data.shine) frame.shine:SetPoint('BOTTOMRIGHT', data.shineRight or 0, data.shineBottom or 0) frame.shine:SetPoint('TOPLEFT', data.shineLeft or 0, data.shineTop or 0) frame.shine:Show() frame.flash:Play() else frame.flash:Stop() frame.shine:Hide() end -- Buttons if i == 1 then frame.prev:Disable() else frame.prev:Enable() end frame.pageNum:SetText(("%d/%d"):format(i, frame.unlocked)) if i < (frame.unlocked or 0) then frame.next:Enable() else frame.next:Disable() end -- Save local sv = frame.data.key or frame.data.savedvariable if sv then local table = frame.data.key and frame.data.savedvariable or _G table[sv] = max(i, table[sv] or 0) end end local function NewButton(frame, name, direction) local button = CreateFrame('Button', nil, frame) button:SetHighlightTexture('Interface\\Buttons\\UI-Common-MouseHilight') button:SetDisabledTexture(BUTTON_TEX:format(name, 'Disabled')) button:SetPushedTexture(BUTTON_TEX:format(name, 'Down')) button:SetNormalTexture(BUTTON_TEX:format(name, 'Up')) button:SetPoint('BOTTOM'..((direction == -1) and 'LEFT' or 'RIGHT'), -(30 * direction), 1) button:SetSize(26, 26) button:SetScript('OnClick', function() UpdateFrame(frame, frame.i + direction) end) local text = button:CreateFontString(nil, nil, 'GameFontHighlightSmall') text:SetText(_G[strupper(name)]) text:SetPoint('LEFT', -(13 + text:GetStringWidth()/2) * direction, 0) return button end local function NewFrame(data) local frame = CreateFrame('Frame', 'Tutorials'..Lib.numFrames, UIParent, 'ButtonFrameTemplate') if UIParent:GetScale() < 0.65 then local scale = ConvertPixelsToUI(1, frame:GetEffectiveScale()) frame:SetScale(scale) -- UIParent 0.64 scale correction end local portrait = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE and frame:GetPortrait() or frame.portrait portrait:SetPoint('TOPLEFT', -3, 5) portrait:SetTexture(data.icon or 'Interface\\TutorialFrame\\UI-HELP-PORTRAIT') frame.Inset:SetPoint('TOPLEFT', 4, -23) frame.Inset.Bg:SetColorTexture(0, 0, 0) frame.images = {} frame.text = frame:CreateFontString(nil, nil, 'GameFontHighlight') if data.font then frame.text:SetFont(data.font, 12) end frame.text:SetJustifyH('LEFT') frame.editboxes = {} frame.prev = NewButton(frame, 'Prev', -1) frame.next = NewButton(frame, 'Next', 1) frame.pageNum = frame:CreateFontString(nil, nil, 'GameFontHighlightSmall') frame.pageNum:SetPoint('BOTTOM', 0, 9) frame:SetFrameStrata('DIALOG') frame:SetClampedToScreen(true) frame:EnableMouse(true) frame:SetToplevel(true) frame:SetScript('OnHide', function() frame.flash:Stop() frame.shine:Hide() if frame.data.onHide then frame.data.onHide() end end) frame.button = CreateFrame('Button', nil, frame, 'UIPanelButtonTemplate') frame.button:SetSize(100, 22) frame.button:SetPoint("CENTER") frame.button:Hide() frame.shine = CreateFrame('Frame', nil, frame, BackdropTemplateMixin and 'BackdropTemplate') frame.shine:SetBackdrop({edgeFile = 'Interface\\TutorialFrame\\UI-TutorialFrame-CalloutGlow', edgeSize = 16}) for i = 1, frame.shine:GetNumRegions() do select(i, frame.shine:GetRegions()):SetBlendMode('ADD') end local flash = frame.shine:CreateAnimationGroup() flash:SetLooping('BOUNCE') frame.flash = flash local anim = flash:CreateAnimation('Alpha') anim:SetDuration(.75) anim:SetFromAlpha(.7) anim:SetToAlpha(0) frame.data = data Lib.numFrames = Lib.numFrames + 1 return frame end --[[ User API ]]-- function Lib:RegisterTutorial(data) assert(type(data) == 'table', 'RegisterTutorials: 2nd arg must be a table', 2) assert(self, 'RegisterTutorials: 1st arg was not provided', 2) if not Lib.frames[self] then Lib.frames[self] = NewFrame(data) end end function Lib:TriggerTutorial(index, maxAdvance) assert(type(index) == 'number', 'TriggerTutorial: 2nd arg must be a number', 2) assert(self, 'RegisterTutorials: 1st arg was not provided', 2) local frame = Lib.frames[self] if frame then local sv = frame.data.key or frame.data.savedvariable local table = frame.data.key and frame.data.savedvariable or _G local last = sv and table[sv] or 0 if index > last then frame.unlocked = index UpdateFrame(frame, (maxAdvance == true or not sv) and index or last + (maxAdvance or 1)) end end end function Lib:ResetTutorial() assert(self, 'RegisterTutorials: 1st arg was not provided', 2) local frame = Lib.frames[self] if frame then local sv = frame.data.key or frame.data.savedvariable if sv then local table = frame.data.key and frame.data.savedvariable or _G table[sv] = false end end end function Lib:GetTutorial() return self and Lib.frames[self] and Lib.frames[self].data end