annotate Veneer.lua @ 99:74d6d97a2d24 v7.1.5-r100

- artifact progress visualizations, green for current, blue for attainable - force fishing artifact to list last - support for fishing artifact power
author Nenue
date Fri, 20 Jan 2017 19:40:55 -0500
parents dadddb8a551f
children 1e511e9aaca5
rev   line source
Nenue@88 1 -- Veneer Custom Interface Framework
Nenue@88 2 -- 1. vn OnLoad
Nenue@88 3 -- 2. OnEvent where IsLoggedIn() == true
Nenue@88 4 -- 3. Setup() where (not self.initialized)
Nenue@88 5 -- 4. Update()
Nenue@88 6 -- 5. Reanchor()
Nenue@84 7
Nenue@84 8 SLASH_VENEER1 = "/veneer"
Nenue@84 9 SLASH_VENEER2 = "/vn"
Nenue@90 10 local VENEER_VERSION = 703
Nenue@93 11 local type, strrep, ipairs, tinsert, tostring, select = type, string.rep, ipairs, tinsert, tostring, select
Nenue@93 12 local pairs, tremove = pairs, tremove
Nenue@84 13
Nenue@84 14 SlashCmdList.VENEER = function(cmd)
Nenue@90 15
Nenue@90 16 if Veneer.ConfigMode then
Nenue@90 17 Veneer.ConfigMode = false
Nenue@90 18 else
Nenue@90 19 Veneer.ConfigMode = true
Nenue@90 20 end
Nenue@90 21 Veneer:UpdateConfigLayers()
Nenue@84 22 end
Nenue@88 23
Nenue@84 24 VeneerCore = {
Nenue@84 25 Frames = {},
Nenue@84 26 ConfigLayers = {},
Nenue@84 27 FrameClusters = {},
Nenue@84 28 parserDepth = 0,
Nenue@84 29 pendingCalls = {},
Nenue@90 30 AddOnCheck = {}
Nenue@84 31 }
Nenue@93 32
Nenue@93 33 local print = DEVIAN_WORKSPACE and function(...) _G.print('Veneer', ...) end or nop
Nenue@80 34 local wipe = table.wipe
Nenue@0 35
Nenue@59 36 local defaults = {
Nenue@59 37 enableAll = true,
Nenue@59 38 enableModule = {
Nenue@59 39 BuffFrame = true,
Nenue@59 40 },
Nenue@59 41 BuffFrame = {
Nenue@59 42 width = 48,
Nenue@59 43 height = 48,
Nenue@90 44 },
Nenue@90 45 ConfigMode = true
Nenue@59 46 }
Nenue@84 47
Nenue@71 48 local configMode
Nenue@79 49 local anonID = 0
Nenue@79 50 local IsFrameHandle = IsFrameHandle
Nenue@79 51 local GetAnonymousName = function(key)
Nenue@79 52 if not key then
Nenue@71 53 anonID = anonID + 1
Nenue@79 54 key = anonID
Nenue@71 55 end
Nenue@79 56 return 'VN' .. key
Nenue@71 57 end
Nenue@79 58 local GetTableName = function(table)
Nenue@79 59 return (IsFrameHandle(table) and table:GetName()) or tostring(table)
Nenue@79 60 end
Nenue@79 61
Nenue@87 62 local OFFSET_PARALLELS = {
Nenue@87 63 TOP = {'LEFT', 'RIGHT', 'SetHeight'},
Nenue@87 64 BOTTOM = {'LEFT', 'RIGHT', 'SetHeight'},
Nenue@87 65 LEFT = {'TOP', 'BOTTOM', 'SetWidth'},
Nenue@87 66 RIGHT = {'TOP', 'BOTTOM', 'SetWidth'},
Nenue@87 67 }
Nenue@87 68 local ANCHOR_OFFSET_POINT = {
Nenue@87 69 TOP = 'BOTTOM',
Nenue@87 70 TOPLEFT = 'BOTTOMRIGHT',
Nenue@87 71 TOPRIGHT = 'BOTTOMLEFT',
Nenue@87 72 LEFT = 'RIGHT',
Nenue@87 73 RIGHT = 'LEFT',
Nenue@87 74 CENTER = 'CENTER',
Nenue@87 75 BOTTOM = 'TOP',
Nenue@87 76 BOTTOMRIGHT = 'TOPLEFT',
Nenue@87 77 BOTTOMLEFT = 'TOPRIGHT',
Nenue@87 78 }
Nenue@87 79 local ANCHOR_INSET_DELTA = {
Nenue@87 80 TOP = {0, -1},
Nenue@87 81 TOPLEFT = {1, -1},
Nenue@87 82 TOPRIGHT = {-1,-1},
Nenue@87 83 LEFT = {1, 0},
Nenue@87 84 BOTTOMLEFT = {1, 1},
Nenue@87 85 BOTTOM = {0, 1},
Nenue@87 86 BOTTOMRIGHT = {-1, 1},
Nenue@87 87 RIGHT = {-1, 0},
Nenue@87 88 CENTER = {0, 0},
Nenue@72 89 }
Nenue@72 90
Nenue@84 91 function VeneerCore:print(...)
Nenue@84 92 local txt = '|cFFFFFF00Veneer|r:'
Nenue@84 93 for i = 1, select('#', ...) do
Nenue@84 94 txt = txt .. ' '.. tostring(select(i, ...))
Nenue@84 95 end
Nenue@84 96
Nenue@84 97 DEFAULT_CHAT_FRAME:AddMessage(txt)
Nenue@84 98 end
Nenue@84 99
Nenue@84 100 function VeneerCore:OnLoad()
Nenue@84 101 print('|cFFFFFF00Veneer!|r')
Nenue@84 102 self:RegisterEvent('ADDON_LOADED')
Nenue@84 103 self:RegisterEvent('PLAYER_LOGIN')
Nenue@84 104
Nenue@84 105 self.DEVIAN_PNAME = 'Veneer'
Nenue@84 106 self:RegisterForDrag('LeftButton')
Nenue@88 107
Nenue@88 108
Nenue@84 109 end
Nenue@84 110
Nenue@90 111 local select, IsAddOnLoaded, IsLoggedIn = select, IsAddOnLoaded, IsLoggedIn
Nenue@90 112
Nenue@84 113 function VeneerCore:OnEvent(event, ...)
Nenue@97 114 print('|cFFFF0088OnEvent()|r',event, ...)
Nenue@98 115 if (event == 'PLAYER_LOGIN') or (event == 'ADDON_LOADED') then
Nenue@90 116 print(IsLoggedIn(), self.initialized)
Nenue@84 117 if IsLoggedIn() and not self.intialized then
Nenue@84 118 self:Setup()
Nenue@90 119 self.intialized = true
Nenue@90 120 print('popping init sequence', self.intialized)
Nenue@90 121 end
Nenue@90 122
Nenue@90 123
Nenue@90 124 if self.intialized then
Nenue@90 125 local addon = ...
Nenue@90 126 if self.AddOnCheck[addon] then
Nenue@90 127 print(' - setting up '..addon..' dependent modules:')
Nenue@90 128 local keepChecking = false
Nenue@90 129 for index, handler in ipairs(self.AddOnCheck[addon]) do
Nenue@90 130 print(' -', handler:GetName(), (not handler.initialized) and (handler.addonFrame and not _G[handler.addonFrame]))
Nenue@90 131 if not handler.initialized then
Nenue@90 132 print(' '..handler:GetName()..':Setup()')
Nenue@90 133 handler:Setup()
Nenue@90 134 handler.initialized = true
Nenue@90 135 end
Nenue@90 136 end
Nenue@90 137 if not keepChecking then
Nenue@90 138 self.AddOnCheck[addon] = nil
Nenue@90 139 end
Nenue@90 140 end
Nenue@84 141 end
Nenue@84 142 end
Nenue@84 143 end
Nenue@84 144
Nenue@84 145 function VeneerCore:OnDragStart()
Nenue@84 146 self:StartMoving()
Nenue@84 147 end
Nenue@84 148
Nenue@84 149 function VeneerCore:OnDragStop()
Nenue@84 150 self:StopMovingOrSizing()
Nenue@84 151 end
Nenue@84 152
Nenue@93 153 local VeneerModule_Setup = function(frame)
Nenue@97 154 if not frame.initialized then
Nenue@97 155 local doSetup = (not frame.addonTrigger) or select(2, IsAddOnLoaded(frame.addonTrigger))
Nenue@97 156 print(' '..frame:GetName()..'.doSetup =', doSetup)
Nenue@97 157 if doSetup then
Nenue@93 158 frame:Setup()
Nenue@93 159 frame.initialized = true
Nenue@98 160 else
Nenue@98 161 Veneer:RegisterEvent('ADDON_LOADED')
Nenue@93 162 end
Nenue@97 163
Nenue@93 164 end
Nenue@93 165 end
Nenue@93 166
Nenue@84 167 function VeneerCore:Setup ()
Nenue@97 168 print('|cFFFF0088Setup()|r')
Nenue@90 169 local resetConfig = (not VeneerData)
Nenue@90 170 if (not VeneerData) then
Nenue@84 171 VeneerData = defaults
Nenue@90 172 VeneerData.version = VENEER_VERSION
Nenue@84 173 end
Nenue@84 174 self.data = VeneerData
Nenue@93 175 self:ExecuteOnClusters(nil, VeneerModule_Setup)
Nenue@90 176
Nenue@90 177 self.ConfigMode = VeneerData.ConfigMode
Nenue@90 178 self:UpdateConfigLayers()
Nenue@90 179 self:Reanchor()
Nenue@90 180 self:Update()
Nenue@87 181 end
Nenue@84 182
Nenue@90 183 function VeneerCore:UpdateConfigLayers()
Nenue@90 184 if VeneerData then
Nenue@90 185
Nenue@90 186 VeneerData.ConfigMode = self.ConfigMode
Nenue@90 187 end
Nenue@90 188
Nenue@90 189 self:print('Config mode '..(self.ConfigMode and '|cFF00FF00ON|r' or '|cFFFF0000OFF|r')..'.')
Nenue@90 190 self:ExecuteOnClusters(nil, function(frame)
Nenue@90 191 if frame.UpdateConfigLayers then
Nenue@90 192 frame:UpdateConfigLayers(self.ConfigMode)
Nenue@90 193 end
Nenue@90 194
Nenue@90 195
Nenue@90 196 if type(frame.ConfigLayer) == 'table' then
Nenue@90 197 for index, region in ipairs(frame.ConfigLayer) do
Nenue@90 198 print('setting', frame:GetName() .. '['.. index..']', 'to', self.ConfigMode)
Nenue@90 199
Nenue@90 200 region:SetShown(self.ConfigMode)
Nenue@90 201 end
Nenue@90 202 end
Nenue@90 203
Nenue@90 204 self.ConfigLayers[frame] = frame:IsShown()
Nenue@90 205 if self.ConfigMode then
Nenue@90 206 print(frame:GetName(), self.ConfigLayers[frame])
Nenue@90 207 frame:SetShown(self.ConfigMode)
Nenue@90 208 else
Nenue@90 209 frame:SetShown(self.ConfigLayers[frame])
Nenue@90 210 end
Nenue@90 211 end)
Nenue@90 212 end
Nenue@84 213
Nenue@93 214
Nenue@87 215 function VeneerCore:GetClusterFromArgs (...)
Nenue@87 216 local primaryAnchor
Nenue@87 217 local insertPosition
Nenue@90 218
Nenue@90 219
Nenue@90 220
Nenue@87 221 local clusterTable = self.FrameClusters
Nenue@87 222 for i = 1, select('#', ...) do
Nenue@87 223 local arg = select(i, ...)
Nenue@87 224 local argType = type(arg)
Nenue@87 225 if argType == 'string' then
Nenue@87 226 if not primaryAnchor then
Nenue@87 227 primaryAnchor = arg
Nenue@87 228 end
Nenue@87 229 clusterTable[arg] = clusterTable[arg] or {}
Nenue@87 230 clusterTable = clusterTable[arg]
Nenue@93 231 print(strrep(' ', i)..'anchor cluster', i, arg)
Nenue@87 232 elseif argType == 'boolean' then
Nenue@87 233 insertPosition = 1
Nenue@87 234 end
Nenue@87 235 end
Nenue@87 236 if not primaryAnchor then
Nenue@97 237 primaryAnchor = 'CENTER'
Nenue@97 238 clusterTable[primaryAnchor] = clusterTable[primaryAnchor] or {}
Nenue@97 239 clusterTable = clusterTable[primaryAnchor]
Nenue@87 240 end
Nenue@87 241 if not insertPosition then
Nenue@87 242 insertPosition = #clusterTable + 1
Nenue@87 243 end
Nenue@87 244 return primaryAnchor, clusterTable, insertPosition
Nenue@84 245 end
Nenue@84 246
Nenue@84 247 function VeneerCore:AddHandler(handler, ...)
Nenue@97 248 print('|cFFFFFF00*** Adding handler:', handler.moduleName or handler:GetName())
Nenue@87 249
Nenue@90 250
Nenue@90 251 local anchorGroup, clusterTable, clusterIndex = self:GetClusterFromArgs(...)
Nenue@90 252 if clusterIndex == 1 then
Nenue@90 253 for i, frame in ipairs(clusterTable) do
Nenue@90 254 frame.clusterIndex = i + 1
Nenue@90 255 end
Nenue@87 256 end
Nenue@90 257 tinsert(clusterTable, clusterIndex, handler)
Nenue@90 258
Nenue@97 259 print(' cluster', anchorGroup, 'table', clusterTable, 'position', clusterIndex)
Nenue@87 260
Nenue@87 261 handler.anchorCluster = clusterTable
Nenue@87 262 handler.anchorIndex = clusterIndex
Nenue@84 263 for k,v in pairs(VeneerHandlerMixin) do
Nenue@84 264 if not handler[k] then
Nenue@87 265 print(' * from mixin:', k)
Nenue@84 266 handler[k] = v
Nenue@84 267 end
Nenue@84 268 end
Nenue@90 269
Nenue@90 270 if handler.addonTrigger and not IsAddOnLoaded(handler.addonTrigger) then
Nenue@90 271 print('|cFFFF4400 -- dependency:', handler.addonTrigger)
Nenue@90 272 self.AddOnCheck[handler.addonTrigger] = self.AddOnCheck[handler.addonTrigger] or {}
Nenue@90 273 tinsert(self.AddOnCheck[handler.addonTrigger], handler)
Nenue@90 274 end
Nenue@90 275
Nenue@87 276 if self.initialized then
Nenue@90 277 print(' -- initialization check')
Nenue@90 278 if handler.Setup then
Nenue@90 279 local doInit = (not handler.initialized)
Nenue@90 280 if handler.addonTrigger and not IsAddOnLoaded(handler.addonTrigger) then
Nenue@90 281 doInit = false
Nenue@90 282 end
Nenue@90 283 -- room to add other checks
Nenue@90 284
Nenue@90 285 if doInit then
Nenue@90 286 handler:Setup()
Nenue@90 287 handler.initialized = true
Nenue@90 288 self:InternalReanchor(handler)
Nenue@90 289 end
Nenue@87 290 end
Nenue@87 291 end
Nenue@87 292 end
Nenue@87 293
Nenue@87 294 function VeneerCore:Reanchor()
Nenue@87 295 self:ExecuteOnClusters(nil, 'Reanchor')
Nenue@88 296 self:DynamicReanchor(self)
Nenue@87 297 end
Nenue@87 298
Nenue@87 299 function VeneerCore:Update()
Nenue@90 300 self:ExecuteOnClusters(nil, function(frame)
Nenue@90 301 if frame.initialized and frame.Update then
Nenue@90 302 frame:Update()
Nenue@90 303 end
Nenue@90 304 end)
Nenue@88 305 self:Reanchor()
Nenue@87 306 end
Nenue@87 307
Nenue@87 308 -- updates anchor relations to and from the target handler
Nenue@87 309 function VeneerCore:GetAnchor(...)
Nenue@87 310
Nenue@87 311 end
Nenue@87 312
Nenue@88 313 -- Evaluates frames visibility and chains them accordingly
Nenue@88 314
Nenue@88 315 function VeneerCore:DynamicReanchor(parent)
Nenue@88 316 parent = parent or self
Nenue@88 317 print('|cFF88FF00DynamicReanchor()')
Nenue@88 318 for anchorPoint, cluster in pairs(parent.FrameClusters) do
Nenue@88 319 local lastFrame
Nenue@88 320 for index, frame in ipairs(cluster) do
Nenue@90 321 print(' |cFF00FF00'..index, frame:GetName(), frame:IsVisible(), (lastFrame and ('|cFFFFFF00'..lastFrame:GetName()..'|r') or '|cFF00FFFFUIParent'))
Nenue@88 322 if frame:IsVisible() then
Nenue@90 323
Nenue@90 324 if frame.anchorFrame then
Nenue@97 325 print(frame.anchorPoint)
Nenue@90 326 frame:SetPoint(frame.anchorPoint, frame.anchorFrame, frame.anchorFrom, frame.anchorX, frame.anchorY)
Nenue@90 327 print(frame:GetTop(), frame:GetRight())
Nenue@88 328 else
Nenue@97 329 anchorPoint = frame.anchorPoint or anchorPoint
Nenue@90 330 frame:ClearAllPoints()
Nenue@90 331 if lastFrame then
Nenue@90 332 frame:SetPoint(anchorPoint, lastFrame, ANCHOR_OFFSET_POINT[anchorPoint], 0, 0)
Nenue@90 333 else
Nenue@90 334 frame:SetPoint(anchorPoint, UIParent, anchorPoint, frame.anchorX, frame.anchorY)
Nenue@90 335 end
Nenue@90 336 print(frame:GetTop(), frame:GetRight())
Nenue@90 337 lastFrame = frame
Nenue@88 338 end
Nenue@90 339
Nenue@88 340 end
Nenue@88 341
Nenue@88 342 end
Nenue@88 343 end
Nenue@88 344 end
Nenue@88 345
Nenue@88 346 -- Evaluates the current visibility state and re-anchors adjacent blocks accordingly
Nenue@87 347 function VeneerCore:InternalReanchor(handler, printFunc)
Nenue@87 348 print('|cFF00FFFFVeneer:InternalReanchor('..handler:GetName()..')')
Nenue@90 349 if handler.anchorFrame then
Nenue@90 350 handler:SetPoint(handler.anchorPoint, handler.anchorFrame, handler.anchorFrom, handler.anchorX, handler.anchorY)
Nenue@90 351 return
Nenue@90 352 end
Nenue@90 353
Nenue@90 354
Nenue@87 355 local anchorPoint = handler.anchorPath or handler.anchorPoint
Nenue@87 356 local anchorParent, anchorTo = UIParent, anchorPoint
Nenue@88 357 local subPoint, subTo
Nenue@88 358 local nextFrame
Nenue@88 359 for index, frame in ipairs(handler.anchorCluster) do
Nenue@88 360 print(' |cFF00FF00'..index, frame:GetName(), frame:IsVisible())
Nenue@88 361 if frame:IsVisible() then
Nenue@88 362 if frame ~= handler then
Nenue@88 363 anchorParent = frame
Nenue@88 364 anchorTo = ANCHOR_OFFSET_POINT[anchorPoint]
Nenue@87 365
Nenue@88 366 else
Nenue@88 367 nextFrame = handler.anchorCluster[index+1]
Nenue@88 368 if nextFrame then
Nenue@88 369
Nenue@88 370 subPoint = nextFrame.anchorPath or nextFrame.anchorPoint
Nenue@88 371 subTo = ANCHOR_OFFSET_POINT[subPoint]
Nenue@88 372 nextFrame:ClearAllPoints()
Nenue@88 373 nextFrame:SetPoint(subPoint, handler, subTo, 0, 0)
Nenue@88 374 print(' -- pushing '..nextFrame:GetName()..' down the anchor chain', subPoint, subTo)
Nenue@88 375 end
Nenue@88 376 break
Nenue@87 377 end
Nenue@87 378 end
Nenue@87 379 end
Nenue@87 380
Nenue@88 381 if handler:IsVisible() then
Nenue@88 382 handler:SetPoint(anchorPoint, anchorParent, anchorTo, 0, 0)
Nenue@88 383 else
Nenue@88 384 if anchorParent and nextFrame then
Nenue@88 385 nextFrame:SetPoint(subPoint, handler, subTo, 0, 0)
Nenue@88 386 end
Nenue@88 387 end
Nenue@88 388
Nenue@87 389
Nenue@87 390 print(handler.anchorPoint, anchorParent, anchorTo)
Nenue@87 391 if printFunc then
Nenue@87 392 printFunc('|cFF88FF00'..handler:GetName()..':SetPoint(', handler.anchorPoint, anchorParent, anchorTo)
Nenue@87 393 end
Nenue@88 394 end
Nenue@87 395
Nenue@88 396 function VeneerCore:SlideBlock(frame, ...)
Nenue@89 397 local aX, aY = frame:GetLeft(), frame:GetTop()
Nenue@88 398
Nenue@89 399 frame:SetPoint('TOPLEFT', frame, 'BOTTOMLEFT', aX, aY)
Nenue@89 400 frame.animation = frame.animation or {}
Nenue@89 401 frame.animation.startX = aX
Nenue@89 402 frame.animation.startY = aY
Nenue@88 403
Nenue@89 404 local targetPoint, targetParent, targetAnchor, offsetX, offsetY = ...
Nenue@89 405 frame.BlockSlide:SetScript('OnFinished', function()
Nenue@89 406 frame:SetPoint(targetPoint, targetParent, targetAnchor, offsetX, offsetY)
Nenue@89 407 VeneerAnimationMixin.OnFinished(frame)
Nenue@89 408 end)
Nenue@88 409
Nenue@84 410 end
Nenue@84 411
Nenue@88 412
Nenue@84 413 function VeneerCore:ExecuteOnClusters(layer, method)
Nenue@84 414 self.parserDepth = self.parserDepth + 1
Nenue@84 415 if not layer then
Nenue@87 416 if self.parserDepth > 1 then
Nenue@84 417 tinsert(self.pendingCalls, method)
Nenue@84 418 print('delaying walk for', method)
Nenue@84 419 return
Nenue@84 420 end
Nenue@97 421 print('|cFF00FF00ExecuteOnClusters|r('..tostring(layer)..', '..tostring(method)..')')
Nenue@84 422 else
Nenue@87 423 print(' Level '..self.parserDepth)
Nenue@84 424 end
Nenue@87 425
Nenue@87 426 layer = layer or self.FrameClusters
Nenue@84 427 for anchor, cluster in pairs(layer) do
Nenue@84 428 for index, frame in ipairs(cluster) do
Nenue@87 429 print(' '..anchor..'.'..index..' = '..frame:GetName())
Nenue@90 430 if type(method) == 'function' then
Nenue@90 431 method(frame, true)
Nenue@90 432 elseif frame[method] then
Nenue@87 433 print(' |cFF00FF00'..frame:GetName())
Nenue@87 434 frame[method](frame, true)
Nenue@84 435 end
Nenue@84 436 end
Nenue@84 437 if cluster.FrameClusters then
Nenue@84 438 self:ExecuteOnClusters(cluster.FrameClusters, method)
Nenue@84 439 end
Nenue@84 440 end
Nenue@84 441 self.parserDepth = self.parserDepth - 1
Nenue@84 442
Nenue@84 443 if (self.parserDepth == 0) and (#self.pendingCalls >= 1) then
Nenue@84 444 local delayedMethod = tremove(self.pendingCalls, 1)
Nenue@84 445 print('starting delayed walk for', delayedMethod)
Nenue@84 446 self:ExecuteOnClusters(nil, delayedMethod)
Nenue@84 447 end
Nenue@84 448 end
Nenue@84 449
Nenue@72 450
Nenue@71 451
Nenue@88 452 -- Takes frame handle and assigns a block to it
Nenue@84 453 function VeneerCore:Acquire (frame, template)
Nenue@71 454 if not frame then
Nenue@71 455 print('|cFFFF4400Unable to acquire frame...|r')
Nenue@71 456 return
Nenue@71 457 end
Nenue@84 458 local veneer = self.Frames[frame]
Nenue@84 459 if not veneer then
Nenue@94 460 local name = GetAnonymousName()
Nenue@94 461 veneer = CreateFrame('Frame', name, frame, template)
Nenue@90 462 print(self:GetName()..':Acquire()', frame:GetName(), template)
Nenue@71 463
Nenue@84 464 veneer:SetAllPoints(frame)
Nenue@84 465 veneer:SetParent(frame)
Nenue@84 466 veneer.label:SetText(name)
Nenue@84 467 veneer.bg:SetColorTexture(0,0,0,0)
Nenue@84 468 veneer:Hide()
Nenue@84 469 veneer:EnableMouse(false)
Nenue@84 470 -- find current X/Y
Nenue@84 471 veneer.currentLeft = frame:GetLeft()
Nenue@84 472 veneer.currentTop = frame:GetTop()
Nenue@84 473 self.Frames[frame] = veneer
Nenue@71 474 end
Nenue@84 475 return veneer
Nenue@88 476 end