Nenue@1: -- User: Krakyn Nenue@1: -- Created: 12/4/2015 11:13 PM Nenue@1: --[[ Nenue@1: -- Core tools, mostly frame handling business. It's an interface script after all. Nenue@1: -- Types: Nenue@1: -- StatusFrame Nenue@1: -- Primary container for status data, carries primary value information by default Nenue@1: -- FIELDS Nenue@1: -- value = exact primary value represented by frame Nenue@1: -- min = exact minimum for primary value Nenue@1: -- max = exact maximum for primary value Nenue@1: -- percent = value / max Nenue@1: -- alpha = base frame alpha level, set when a fade animation has completed Nenue@1: -- fadeTo = when set, the frame will begin fading to this level Nenue@1: -- fadeDuration = set with fadeTo when the transition needs to be time-adjusted for unpredictable alpha states Nenue@1: -- fadeTime = set with fadeTo when the transition has an explicit desired timing Nenue@1: -- throttle_rate = animation frame delay Nenue@1: -- Nenue@1: -- METHODS Nenue@1: -- Update() = Runs the frame's Update script, and calls UpdateText() Nenue@1: -- UpdateText() = Iterates over frame children and calls Update() if it is a StatusText frame Nenue@1: -- Nenue@1: -- StatusText Nenue@1: -- Sub-container for individual text elements, carries string output values, controlled by from Update(). By default Nenue@1: -- these are derived from the parent's primary value data, but is mostly overridden in module code. Nenue@1: -- FIELDS Nenue@1: -- text = WoW FontString object housing text data Nenue@1: -- format = a format string for expressing complex data (timer bars, etc.) Nenue@1: -- METHODS Nenue@1: -- SetText() = shortcut for text:SetText(...) Nenue@1: -- SetJustifyH() = shortcut for text:SetJustifyH(...) Nenue@1: -- Implements: Nenue@1: -- frame = CreateBar(name, config root) Nenue@1: -- Creates and displays a generic bar frame with progress textures and default updater, based on the variables from Nenue@1: -- the smart config table. Seeded with methods Update() and UpdateText(). Nenue@1: -- Update() will refresh the entire frame and tail call UpdateText() Nenue@1: -- UpdateText() will iterate over the contents of frame:GetChildren() and call Update() Nenue@1: -- NEVER CREATE A CHILD FRAME WITHOUT Update() DEFINED Nenue@1: -- NEVER INVOKE METHODS FROM A GetParent() RETURN Nenue@1: -- Nenue@1: -- Bar_SetUpdateHandler(frame, function) Nenue@1: -- Takes the desired OnUpdate script and wraps it inside frame throttling logic, then does SetScript Nenue@1: -- Nenue@1: -- frame = AddLabel(frame, config root, name) Nenue@1: -- Constructs a text display and adds it to the frame's list of internal text objects. Contains method Update(), which Nenue@1: -- refreshed text. Nenue@1: -- Nenue@1: -- HANDLERS Nenue@1: -- Bar_Update([value, min/duration, max/end]) Nenue@1: -- Default handler for any non-timed bar (where isTimer = false in config vars. Invoked by frame:Update() Nenue@1: ]] Nenue@1: local T = LibStub("AceAddon-3.0"):GetAddon("Turok") Nenue@1: local LSM = LibStub("LibSharedMedia-3.0") Nenue@1: local TL = 'Fog' Nenue@1: local print = function(...) Nenue@1: _G.print(TL, ...) Nenue@1: end Nenue@1: --setprinthandler(function (...) T:debug('Fog', nil, ...) end) Nenue@1: local FADE_OUT_TIME = 1 -- duration per 1-0 alpha transition Nenue@1: local FADE_IN_TIME = 0.4 -- duration per 0-1 alpha transition Nenue@1: Nenue@1: local inset_factor = { Nenue@1: ['TOPLEFT'] = {-1, 1}, Nenue@1: ['TOP'] = {0, 1}, Nenue@1: ['TOPRIGHT'] = {1, 1}, Nenue@1: ['RIGHT'] = {1, 0}, Nenue@1: ['BOTTOMRIGHT'] = {1, -1}, Nenue@1: ['BOTTOM'] = {0, -1}, Nenue@1: ['BOTTOMLEFT'] = {-1,-1}, Nenue@1: ['LEFT'] = {-1, 0} Nenue@1: } Nenue@1: Nenue@1: T.anchor_inverse = { ['LEFT'] = 'RIGHT', ['RIGHT'] = 'LEFT', ['UP'] = 'DOWN', ['DOWN'] = 'UP' } -- directional inverse Nenue@1: T.direction_coord = { ['LEFT'] = {1, 0}, ['RIGHT'] = {-1,0}, ['UP'] = {0, -1}, ['DOWN'] = {0, 1 } } -- directional derivatives Nenue@1: T.anchor_direction = { ['LEFT'] = 'RIGHT', ['RIGHT'] = 'LEFT', ['UP'] = 'BOTTOM', ['DOWN'] = 'TOP'} -- directional anchors Nenue@1: Nenue@1: function T:CreateBar(name, config) Nenue@1: local parent = type(config.parent) == 'string' and _G[config.parent] or parent Nenue@1: if not parent then Nenue@1: parent = CreateFrame('Frame', config.parent, UIParent) Nenue@1: print('creating dry frame |cFFDDDDDD' .. config.parent .. '|r for |cFFFFFF00' .. name .. '|r') Nenue@1: end Nenue@1: Nenue@1: local f Nenue@1: if _G[name] and _G[name].GetFrameType and _G[name].GetFrameType() == 'Frame' then Nenue@1: f = _G[name] Nenue@1: print('found existing table |cFFFFFF00' .. name .. '|r') Nenue@1: else Nenue@1: print('creating statusbar '.. name, config.anchor, parent:GetName(), config.anchorTo, config.posX, config.posY) Nenue@1: end Nenue@1: Nenue@1: Nenue@1: f = CreateFrame('Frame', name, UIParent) Nenue@1: f.db = config Nenue@1: Nenue@1: -- state vars Nenue@1: f.combat = InCombatLockdown() Nenue@1: f.min = 0 Nenue@1: f.max = 1 Nenue@1: f.value = 0 Nenue@1: f.percent = 0 Nenue@1: f.alpha = f.combat and config.alpha or config.alpha_ooc Nenue@1: f:SetAlpha(f.combat and config.alpha or config.alpha_ooc) Nenue@1: f:SetPoint(config.anchor, config.parent, config.anchorTo, config.posX, config.posY) Nenue@1: f:SetSize(config.width, config.height) Nenue@1: f:SetFrameStrata(config.strata) Nenue@1: self:CreateStatusTextures(f, config) Nenue@1: Nenue@1: f.throttle_rate = 0.0166666666 -- ~60fps Nenue@2: f.throttle_point = GetTime() Nenue@1: Nenue@1: -- default handler Nenue@1: f.Update = T.Bar_Update Nenue@1: f.UpdateText = T.Bar_UpdateText Nenue@1: T:Bar_SetUpdateHandler(f, T.Bar_Update) Nenue@1: Nenue@1: return f Nenue@1: end Nenue@1: Nenue@1: function T:CreateStatusTextures(region, c) Nenue@1: c = c and c or region.db Nenue@1: print('STATUSTEX_make', region:GetName(), c.background_texture, c.background_color, c.foreground_texture, c.foreground_color) Nenue@1: region.background = region:CreateTexture('background', 'BACKGROUND') Nenue@1: region.background:SetAllPoints(region) Nenue@1: if c.background_texture then Nenue@1: region.background:SetTexture(LSM:Fetch('statusbar', c.background_texture)) Nenue@1: region.background:SetVertexColor(unpack(c.background_color)) Nenue@1: else Nenue@1: region.background:SetTexture(unpack(c.background_color)) Nenue@1: end Nenue@1: region.background:SetBlendMode(c.background_blend) Nenue@1: Nenue@1: region.foreground = region:CreateTexture('foreground', 'ARTWORK') Nenue@1: region.foreground:SetPoint('TOPLEFT', region, 'TOPLEFT', 0-c.foreground_inset, c.foreground_inset) Nenue@1: region.foreground:SetPoint('BOTTOMRIGHT', region, 'BOTTOMRIGHT', c.foreground_inset, 0-c.foreground_inset) Nenue@1: if c.foreground_texture then Nenue@1: region.foreground:SetTexture(LSM:Fetch('statusbar', c.foreground_texture)) Nenue@1: region.foreground:SetVertexColor(unpack(c.foreground_color)) Nenue@1: else Nenue@1: region.foreground:SetTexture(unpack(c.foreground_color)) Nenue@1: end Nenue@1: region.foreground:SetBlendMode(c.foreground_blend) Nenue@1: end Nenue@1: Nenue@1: function T:CreateStatusIcon(region, c) Nenue@1: local file = type(c) == 'string' and c or region.icon Nenue@1: if type(c) ~= 'table' then Nenue@1: c = region.db Nenue@1: end Nenue@1: print('STATUSICON', region:GetName(), file, c.icon_show) Nenue@1: Nenue@1: end Nenue@1: Nenue@1: -- Sets the OnUpdate handler within a timing scheme Nenue@1: function T:Bar_SetUpdateHandler(bar, func) Nenue@1: bar:SetScript('OnUpdate', function(bar) Nenue@1: if GetTime() < bar.throttle_point then Nenue@1: return Nenue@1: end Nenue@1: Nenue@1: bar.throttle_point = bar.throttle_point + bar.throttle_rate Nenue@1: if type(func) == 'string' then Nenue@1: if type(bar[func]) ~= 'function' then Nenue@1: error('TurokBar:SetUpdateScript(); string "' ..func.. '" is not a valid method name') Nenue@1: return Nenue@1: end Nenue@1: elseif type(func) ~= 'function' then Nenue@1: error('TurokBar:SetUpdateScript(function or string); got '.. type(func) .. 'instead') Nenue@1: return Nenue@1: end Nenue@1: Nenue@1: func(bar) Nenue@1: end) Nenue@1: end Nenue@1: Nenue@1: -- Default update loop Nenue@1: function T:Bar_Update (value, min, max) Nenue@1: Nenue@1: if value ~= nil then -- could be 0 Nenue@1: self.value = value Nenue@1: end Nenue@1: if (min ~= nil and max ~= nil) then Nenue@1: if self.isTimer then Nenue@1: self.duration = min Nenue@1: self.endTime = max Nenue@1: else Nenue@1: self.min = min Nenue@1: self.max = max Nenue@1: self.duration = max Nenue@1: end Nenue@1: end Nenue@1: Nenue@1: if self.combat ~= InCombatLockdown() then Nenue@1: self.combat = InCombatLockdown() Nenue@1: if self.combat then Nenue@1: self.fadeTo = self.db.alpha Nenue@1: self.fadeDuration = FADE_IN_TIME Nenue@1: else Nenue@1: self.fadeTo = self.db.alpha_ooc Nenue@1: self.fadeDuration = FADE_OUT_TIME Nenue@1: end Nenue@1: end Nenue@1: Nenue@1: -- we need the time for something Nenue@1: if self.isTimer or self.fadeTo ~= nil then Nenue@1: local time = GetTime() Nenue@1: Nenue@1: -- start doing fade animation things Nenue@1: if self.fadeTo ~= nil then Nenue@1: Nenue@1: if not self.fadeFrom then Nenue@1: self.fadeFrom = self:GetAlpha() Nenue@1: end Nenue@1: Nenue@1: -- fadeDuration missing, fill it in Nenue@1: if not self.fadeDuration then Nenue@1: self.fadeDuration = self.fadeFrom > self.fadeTo and FADE_OUT_TIME or FADE_IN_TIME Nenue@1: end Nenue@1: Nenue@1: -- fadeTime missing, Nenue@1: if not self.fadeTime then Nenue@1: local fadeRatio = 1 Nenue@1: if self.fadeFrom ~= self.alpha then Nenue@1: fadeRatio = (self.fadeTo - self.fadeFrom) / (self.fadeTo - self.alpha) -- target delta Nenue@1: end Nenue@1: --print(fadeRatio, self.fadeDuration) Nenue@1: self.fadeTime = time + fadeRatio * self.fadeDuration -- fixed rate of change Nenue@1: end Nenue@1: Nenue@1: -- are we done? Nenue@1: if time >= self.fadeTime then Nenue@1: self:SetAlpha(self.fadeTo) Nenue@1: self.alpha = self.fadeTo Nenue@1: self.fadeTo = nil Nenue@1: self.fadeFrom = nil Nenue@1: self.fadeDuration = nil Nenue@1: self.fadeTime = nil Nenue@1: else --nope Nenue@1: local remaining = (self.fadeTime - time) / self.fadeDuration Nenue@1: local fadeTotal = (self.fadeTo - self.fadeFrom) Nenue@1: --print(self.fadeTo - (fadeTotal * remaining), fadeTotal * remaining) Nenue@1: self:SetAlpha(self.fadeTo - (fadeTotal * remaining)) Nenue@1: end Nenue@1: end Nenue@1: Nenue@1: -- termination check Nenue@1: if self.isTimer then Nenue@1: if time >= self.endTime and self.fadeTo == nil then Nenue@1: self:Hide() Nenue@1: return Nenue@1: else Nenue@1: self.percent = (self.endTime - time) / self.duration Nenue@1: end Nenue@1: end Nenue@1: Nenue@1: else Nenue@1: self.percent = (self.value - self.min) / (self.max - self.min) Nenue@1: end Nenue@1: Nenue@1: self:UpdateText() Nenue@1: self.foreground:SetPoint('BOTTOMRIGHT', self, 'BOTTOMLEFT', self.db.width * self.percent + self.db.foreground_inset, 0-self.db.foreground_inset) Nenue@1: end Nenue@1: Nenue@1: function T:Bar_UpdateText() Nenue@1: local c = {self:GetChildren()} Nenue@1: for _, rl in ipairs(c) do Nenue@1: if rl.text then Nenue@1: rl.format = self.db.label_string Nenue@1: rl:Update() Nenue@1: end Nenue@1: end Nenue@1: end Nenue@1: Nenue@1: -- addon:AddLabel Nenue@1: -- Constructs a text display and adds it to that region Nenue@1: -- Nenue@1: function T:AddLabel (region, config, labelname) Nenue@1: assert(region:IsObjectType('Frame'), "T:CreateLabel(table, table [, string])") Nenue@1: labelname = (labelname or string.format('%x', GetTime() % 1000000)) Nenue@1: print('assigning frame for text object |cFFFF9922'..labelname..'|r to '..region:GetName()) Nenue@1: Nenue@1: local lf = CreateFrame('Frame', region:GetName()..'_'..labelname, region) Nenue@1: local ft = lf:CreateFontString(labelname, 'OVERLAY') Nenue@1: local lx, ly, lp = 0,0, config.label_point Nenue@1: if config.label_inset ~= 0 and lp ~= 'CENTER' then Nenue@1: lx = config.label_inset * inset_factor[lp][1] Nenue@1: ly = config.label_inset * inset_factor[lp][2] Nenue@1: end Nenue@1: Nenue@1: lf:SetPoint(lp, region, lp, lx, ly) Nenue@1: lf:SetSize(region:GetWidth(), region:GetHeight()) Nenue@1: lf:SetFrameStrata(config.label_strata) Nenue@1: Nenue@1: ft:SetFont(LSM:Fetch('font', config.label_font), config.label_size, config.label_outline) Nenue@1: ft:SetAllPoints(lf) Nenue@1: ft:SetJustifyH(config.label_justifyH or (config.label_point:find('LEFT') and 'LEFT' or (config.label_point:find('RIGHT') and 'RIGHT' or 'CENTER'))) Nenue@1: ft:SetJustifyV(config.label_justifyV or (config.label_point:find('TOP') and 'TOP' or (config.label_point:find('BOTTOM') and 'BOTTOM' or 'MIDDLE'))) Nenue@1: ft:SetTextColor(1, 1, 1) Nenue@1: ft:SetText('SET ME!') Nenue@1: Nenue@1: lf.Update = region.isTimer and T.Label_UpdateFormat or T.Label_Update Nenue@1: lf.SetJustifyH = function(self, justify) ft:SetJustifyH(justify) end Nenue@1: lf.SetText = function(self, text) ft:SetText(text) end Nenue@1: Nenue@1: lf.text = ft Nenue@1: region[labelname] = lf Nenue@1: return lf Nenue@1: end Nenue@1: Nenue@1: -- one of two possible assignments for label:Update() Nenue@1: function T:Label_UpdateFormat() Nenue@1: local region = self:GetParent() Nenue@1: if self.format then Nenue@1: self.value = string.gsub(self.format, '%%p', string.format('%.1f', region.value)) Nenue@1: self.value = string.gsub(self.value, '%%d', region.duration) Nenue@1: if region.name then Nenue@1: self.value = string.gsub(self.value, '%%n', region.name) Nenue@1: end Nenue@1: end Nenue@1: self.text:SetText(self.value) Nenue@1: end Nenue@1: function T:Label_Update() Nenue@1: self.text:SetText(self:GetParent().value) Nenue@1: end Nenue@1: