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.

473 lines
14 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).
height ......... Default is 0. Internal frame height (without borders).
- 0 .... auto height
- >0 ... fixed height with scrollbar
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).
height ......... Height relative to frame (replace General value).
Note: All other arguments can be used as a general!
paddingX ....... Default is 25. Left and Right padding.
paddingTop ..... Default is 20.
paddingBottom .. Default is 20.
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.
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', 16)
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 frameBorderLeft = 7
local frameBorderRight = 9
local frameBorderTop = 26
local frameBorderBottom = 28
local freeEditboxes = {}
local default = {
title = "Tutorial",
width = 350,
height = 0,
font = "",
paddingX = 25,
paddingTop = 20,
paddingBottom = 20,
imageWidth = 256,
imageHeight = 128,
imagePoint = "TOP",
imageX = 0,
imageY = 0,
headingFont = "Fonts\\FRIZQT__.TTF",
headingSize = 16,
imageFloat = false,
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
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
-- 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
-- Frame
frame:ClearAllPoints()
frame:SetPoint(data.point, data.anchor, data.relPoint, data.x, data.y)
frame:SetWidth(data.width + frameBorderLeft + frameBorderRight)
local titleText = WOW_PROJECT_ID == WOW_PROJECT_MAINLINE and frame.TitleContainer.TitleText or frame.TitleText
titleText:SetPoint('TOP', 0, -5)
titleText:SetText(data.title)
frame.scroll:SetPoint('TOPLEFT', frameBorderLeft + data.paddingX, (frameBorderTop + data.paddingTop) * -1)
frame.scroll:SetPoint('BOTTOMRIGHT', (frameBorderRight + 23) * -1, frameBorderBottom + data.paddingBottom)
frame.scroll:SetVerticalScroll(0)
frame.content:SetWidth(data.width - data.paddingX - max(data.paddingX, 25))
local height = 0
local textPaddingTop = 0
-- 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")
img:SetFrameLevel(1)
img.texture = img:CreateTexture()
img.texture:SetAllPoints()
end
img:SetParent(data.imageAbsolute and frame or frame.content)
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, data.imageX, data.imageY)
img:Show()
frame.images[i] = img
if not data.imageAbsolute then
textPaddingTop = data.imageY + data.imageHeight + 20
height = height + textPaddingTop
end
end
-- Heading
if data.heading then
frame.heading:SetPoint('TOPLEFT', 0, textPaddingTop * -1)
if data.headingFont then
frame.heading:SetFont(data.headingFont, data.headingSize)
end
frame.heading:SetText(data.heading)
height = height + frame.heading:GetHeight()
frame.heading:Show()
else
frame.heading:Hide()
end
-- Text
if data.heading then
frame.text:SetPoint('TOPLEFT', frame.heading, 'BOTTOMLEFT', 0, -16)
height = height + 16
else
frame.text:SetPoint('TOPLEFT', 0, textPaddingTop * -1)
end
frame.text:SetText(data.text)
local textHeight = round(frame.text:GetHeight() + 3)
textHeight = textHeight + fmod(textHeight, 2)
height = height + textHeight
frame.content:SetHeight(height)
height = height + data.paddingTop + data.paddingBottom
if data.height > 0 and height > data.height then
height = data.height
end
height = height + frameBorderTop + frameBorderBottom
frame:SetHeight(height)
frame.i = i
frame:Show()
-- EditBox
RemoveEditboxes(frame)
if data.editbox then
for i = 1, #data.editbox do
frame.editboxes[i] = NewEditbox(frame.content, 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', (data.editbox[i].left or 0) + 5, (data.editbox[i].top or 0) * -1)
elseif data.editbox[i].bottom then
frame.editboxes[i]:SetPoint('BOTTOMLEFT', (data.editbox[i].left or 0) + 5, 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.paddingX + (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.scroll = CreateFrame("ScrollFrame", nil, frame, "ScrollFrameTemplate")
frame.scroll.ScrollBar:SetHideIfUnscrollable(true)
frame.scroll.ScrollBar:SetPoint("TOPLEFT", frame.scroll, "TOPRIGHT", 7, 0)
frame.scroll.ScrollBar:SetPoint("BOTTOMLEFT", frame.scroll, "BOTTOMRIGHT", 7, -1)
frame.content = CreateFrame("Frame", nil, frame.scroll)
frame.content:SetHeight(1) -- for correct init height
frame.scroll:SetScrollChild(frame.content)
frame.images = {}
frame.heading = frame.content:CreateFontString(nil, nil, 'GameFontNormal')
frame.heading:SetPoint('RIGHT')
frame.heading:SetJustifyH('LEFT')
frame.heading:Hide()
frame.text = frame.content:CreateFontString(nil, nil, 'GameFontHighlight')
frame.text:SetPoint('RIGHT')
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
-- for update height
hooksecurefunc(UIParent, "SetScale", function(self)
UpdateFrame(frame, frame.i)
end)
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