You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

416 lines
13 KiB

--- 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 <http://www.gnu.org/licenses/>.
--]]
--[[
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).
imageWidth ..... Default is 256.
imageHeight .... Default is 128.
imagePoint ..... Default is "TOP".
imageX ......... Default is 0.
imageY ......... Default is 20.
imageAbsolute .. Default is false. The image is not part of the content flow and no place is created for it.
imageTexCoords . [optional] Sets the coordinates for cropping or transforming the texture.
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', 14)
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 = "",
imageWidth = 256,
imageHeight = 128,
imagePoint = "TOP",
imageX = 0,
imageY = 20,
imageFloat = false,
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 or data.imageAbsolute) 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]
if not img then
img = CreateFrame("Frame", nil, frame)
img:SetFrameLevel(1)
img.texture = img:CreateTexture()
img.texture:SetAllPoints()
end
img.texture:SetTexture(data.image)
if data.imageTexCoords then
img.texture:SetTexCoord(unpack(data.imageTexCoords))
end
img:SetSize(data.imageWidth, data.imageHeight)
img:SetPoint(data.imagePoint, frame, data.imageX - 1, -(25 + data.imageY))
img:Show()
frame.images[i] = img
end
-- Text
frame.text:SetPoint('TOP', frame, 0, -(((data.image and not data.imageAbsolute) 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 not data.imageAbsolute) 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')
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:SetScript("OnEvent", function(self, event)
if event == "PLAYER_ENTERING_WORLD" then
UpdateFrame(self, self.i) -- for update textHeight (UI Scale)
self:UnregisterEvent(event)
end
end)
frame:RegisterEvent("PLAYER_ENTERING_WORLD")
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