farmbuyer@1: --[[----------------------------------------------------------------------------- farmbuyer@1: lib-st Beta Wrapper Widget farmbuyer@1: farmbuyer@1: lib-st does not recycle the objects (called "ST" here) that it creates and farmbuyer@1: returns. We therefore do not try to hold onto an ST when the widget is farmbuyer@1: being recycled. This means that Constructor() does very little work, and farmbuyer@1: does not actually construct an ST. farmbuyer@1: farmbuyer@1: OnAcquire cannot construct an ST either, because we don't yet have any farmbuyer@1: creation parameters from the user to feed to CreateST. (Allowing such to farmbuyer@1: be passed along from AceGUI:Create() would require changes to core AceGUI farmbuyer@1: code, and I don't feel like trying to overcome that inertia.) farmbuyer@1: farmbuyer@1: The upshot is that the widget returned from Create is broken and useless farmbuyer@1: until its CreateST member has been called. This means that correct behavior farmbuyer@1: depends entirely on the user remembering to do so. farmbuyer@1: farmbuyer@1: "The gods do not protect fools. Fools are protected by more capable fools." farmbuyer@1: - Ringworld farmbuyer@1: farmbuyer@1: farmbuyer@1: Version 1 initial functioning implementation farmbuyer@1: Version 2 reshuffle to follow new AceGUI widget coding style farmbuyer@1: Version 3 add .tail_offset, defaulting to same absolute value as .head_offset farmbuyer@47: Version 4 restore original frame methods, as fortold by ancient prophecy farmbuyer@49: Version 5 don't bogart the widget object farmbuyer@1: -farmbuyer farmbuyer@1: -------------------------------------------------------------------------------]] farmbuyer@126: local Type, Version = "lib-st", 5 farmbuyer@1: local AceGUI = LibStub and LibStub("AceGUI-3.0", true) farmbuyer@1: if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end farmbuyer@1: farmbuyer@1: -- Lua APIs farmbuyer@1: local ipairs, error = ipairs, error farmbuyer@1: farmbuyer@1: -- WoW APIs farmbuyer@1: local debugstack = debugstack farmbuyer@1: local CreateFrame = CreateFrame farmbuyer@1: farmbuyer@1: farmbuyer@1: --[[----------------------------------------------------------------------------- farmbuyer@1: Support functions farmbuyer@1: -------------------------------------------------------------------------------]] farmbuyer@1: farmbuyer@1: -- Some AceGUI functions simply Won't Work in this context. Name them farmbuyer@1: -- here, and code calling them will get a somewhat informative error(). farmbuyer@1: local oopsfuncs = { farmbuyer@1: 'SetRelativeWidth', 'SetRelativeHeight', farmbuyer@1: 'SetFullWidth', 'SetFullHeight', farmbuyer@1: } farmbuyer@1: local err = "Oops! The AceGUI function you tried to call (%s) does not " farmbuyer@1: .. "make sense with lib-st and has not been implemented." farmbuyer@1: farmbuyer@1: local function Oops(self) farmbuyer@1: -- if you ever wanted an example of "brown paper bag" code, here it is farmbuyer@1: local ds = debugstack(0) farmbuyer@1: local func = ds:match("AceGUIWidget%-lib%-st%.lua:%d+:%s+in function `(%a+)'") farmbuyer@1: error(err:format(func or "?")) farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: --[[ farmbuyer@1: Users with an ST already constructed can drop it into a widget directly farmbuyer@1: using this routine. It must be safe to call this more than once with farmbuyer@1: new widgets on the same ST. farmbuyer@1: farmbuyer@1: This is where most of the intelligence of the wrapper is located. That farmbuyer@1: is, if you can call my code "intelligent" with a straight face. Lemme farmbuyer@1: try again. farmbuyer@1: farmbuyer@1: Think of the widget wrapper as a brain. When ALL THREE neurons manage farmbuyer@1: to fire at the same time and produce a thought, this function represents farmbuyer@1: the thought. Sigh. farmbuyer@1: ]] farmbuyer@1: local ShiftingSetPoint, ShiftingSetAllPoints farmbuyer@1: local function WrapST (self, st) farmbuyer@1: if not st.frame then farmbuyer@1: error"lib-st instance has no '.frame' field... wtf did you pass to this function?" farmbuyer@1: end farmbuyer@1: self.st = st farmbuyer@1: if not st.head then farmbuyer@49: error"lib-st instance has no '.head' field, must use either ScrollingTable:CreateST or this widget's CreateST first" farmbuyer@1: end farmbuyer@1: self.frame = st.frame -- gutsy, but looks doable farmbuyer@1: farmbuyer@1: -- Possibly have already wrapped this ST in a previous widget, careful. farmbuyer@126: self.frame.customSetPoint = rawget(self.frame,"SetPoint") farmbuyer@126: self.frame.realSetPoint = self.frame.SetPoint farmbuyer@126: self.frame.SetPoint = ShiftingSetPoint farmbuyer@126: self.frame.SetAllPoints = ShiftingSetAllPoints farmbuyer@1: farmbuyer@47: -- This needs the .frame field. This also unconditionally creates .obj farmbuyer@47: -- inside that field and calls a SetScript on it as well. farmbuyer@1: return AceGUI:RegisterAsWidget(self) farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: --[[----------------------------------------------------------------------------- farmbuyer@1: Scripts farmbuyer@1: -------------------------------------------------------------------------------]] farmbuyer@1: --[[ farmbuyer@1: All of an ST's subframes are attached to its main frame, which we have in farmbuyer@1: the st.frame link, and that's what AceGUI uses for all positioning. Except farmbuyer@1: that ST:SetDisplayCols creates its "head" row /above/ the main frame, and farmbuyer@1: so the row of labels eats into whatever upper border space AceGUI calculates, farmbuyer@1: often overlapping other elements. farmbuyer@1: farmbuyer@1: We get around this by replacing ST's main frame's SetPoint with a custom farmbuyer@1: version that just moves everything down a few pixels to allow room for the farmbuyer@1: head row. farmbuyer@1: farmbuyer@1: FIXME this may need to be a secure hook (ugh, would end up calling the real farmbuyer@1: setpoint twice) rather than a replacement. farmbuyer@1: ]] farmbuyer@1: local DEFAULT_OFFSET = 7 farmbuyer@1: function ShiftingSetPoint(frame,anchor,other,otheranchor,xoff,yoff) farmbuyer@1: local ho,to = frame.obj.head_offset, frame.obj.tail_offset farmbuyer@1: yoff = yoff or 0 farmbuyer@1: if anchor:sub(1,3) == "TOP" then farmbuyer@1: yoff = yoff - ho farmbuyer@1: elseif anchor:sub(1,6) == "BOTTOM" then farmbuyer@1: yoff = yoff + to farmbuyer@1: end farmbuyer@1: return frame.realSetPoint(frame,anchor,other,otheranchor,xoff,yoff) farmbuyer@1: end farmbuyer@1: function ShiftingSetAllPoints(frame,other) farmbuyer@1: ShiftingSetPoint(frame,"TOPLEFT",other,"TOPLEFT",0,0) farmbuyer@1: ShiftingSetPoint(frame,"BOTTOMRIGHT",other,"BOTTOMRIGHT",0,0) farmbuyer@1: end farmbuyer@1: farmbuyer@1: farmbuyer@1: --[[----------------------------------------------------------------------------- farmbuyer@1: Methods farmbuyer@1: -------------------------------------------------------------------------------]] farmbuyer@1: local methods = { farmbuyer@1: -- -------------------------------------------------------------- farmbuyer@1: -- These are expected by AceGUI containers (and AceGUI users) farmbuyer@1: -- farmbuyer@1: ["OnAcquire"] = function (self) farmbuyer@1: -- Almost nothing can usefully be done here. farmbuyer@1: self.head_offset = DEFAULT_OFFSET farmbuyer@1: self.tail_offset = DEFAULT_OFFSET farmbuyer@1: end, farmbuyer@1: farmbuyer@1: ["OnRelease"] = function (self) farmbuyer@1: if self.st then farmbuyer@1: self.st.frame:ClearAllPoints() farmbuyer@1: self.st:Hide() farmbuyer@1: end farmbuyer@1: self.st = nil farmbuyer@47: self.frame.realSetPoint = nil farmbuyer@47: self.frame.SetAllPoints = nil farmbuyer@47: self.frame.SetPoint = self.frame.customSetPoint farmbuyer@47: self.frame.customSetPoint = nil farmbuyer@1: end, farmbuyer@1: farmbuyer@1: --[[ farmbuyer@1: STs don't use a "normal" SetWidth, if we define "normal" to be the farmbuyer@1: behavior of the blizzard :SetWidth. Column width is passed in during farmbuyer@1: creation of the whole ST. The SetWidth defined by an ST takes no farmbuyer@1: arguments; "ReCalculateWidth" would be a more precise description of farmbuyer@1: what it does. farmbuyer@1: farmbuyer@1: Parts of AceGUI look for a .width field because a widget's SetWidth farmbuyer@1: sets such. ST calculates a total width and dispatches it to its member farmbuyer@1: frame... but doesn't store a local copy. We need to bridge these farmbuyer@1: differences. farmbuyer@1: farmbuyer@1: This widget wrapper does not make use of On{Width,Height}Set hooks, farmbuyer@1: but the acegui widget base functions do. Since we're not inheriting farmbuyer@1: them, we may as well supply them. farmbuyer@1: ]] farmbuyer@1: ["SetWidth"] = function (self) farmbuyer@1: self.st:SetWidth() -- re-total the columns farmbuyer@1: local w = self.st.frame:GetWidth() -- fetch the answer back farmbuyer@1: self.frame.width = w -- store it for acegui farmbuyer@1: if self.OnWidthSet then farmbuyer@1: self:OnWidthSet(w) farmbuyer@1: end farmbuyer@1: end, farmbuyer@1: farmbuyer@1: -- Everything said about SetWidth applies here too. farmbuyer@1: ["SetHeight"] = function (self) farmbuyer@1: self.st:SetHeight() farmbuyer@1: local h = self.st.frame:GetHeight() farmbuyer@1: self.frame.height = h farmbuyer@1: if self.OnHeightSet then farmbuyer@1: self:OnHeightSet(h) farmbuyer@1: end farmbuyer@1: end, farmbuyer@1: farmbuyer@1: -- Some of the container layouts call Show/Hide on the innermost frame farmbuyer@1: -- directly. We need to make sure the slightly-higher-level routine is farmbuyer@1: -- also called. farmbuyer@1: ["LayoutFinished"] = function (self) farmbuyer@1: if self.frame:IsShown() then farmbuyer@1: self.st:Show() farmbuyer@1: else farmbuyer@1: self.st:Hide() farmbuyer@1: end farmbuyer@1: end, farmbuyer@1: farmbuyer@1: -- -------------------------------------------------------------- farmbuyer@1: -- Functions specific to this widget farmbuyer@1: -- farmbuyer@1: farmbuyer@1: ["GetSTLibrary"] = function (self) -- Purely for convenience farmbuyer@1: return LibST farmbuyer@1: end, farmbuyer@1: farmbuyer@1: --[[ farmbuyer@1: Replacement wrapper, so that instead of farmbuyer@1: st = ScrollingTable:CreateST( args ) farmbuyer@1: the user should be able to do farmbuyer@1: st = AceGUI:Create("lib-st"):CreateST( args ) farmbuyer@1: instead, without needing to get a lib-st handle. farmbuyer@1: ]] farmbuyer@1: ["CreateST"] = function (self, ...) farmbuyer@1: return self:WrapST( LibST:CreateST(...) ) farmbuyer@1: end, farmbuyer@1: farmbuyer@1: ["WrapST"] = WrapST, farmbuyer@1: } farmbuyer@1: farmbuyer@1: farmbuyer@1: --[[----------------------------------------------------------------------------- farmbuyer@1: Constructor farmbuyer@1: -------------------------------------------------------------------------------]] farmbuyer@1: local function Constructor() farmbuyer@1: -- .frame not done here, see WrapST farmbuyer@1: local widget = { farmbuyer@1: type = Type farmbuyer@1: } farmbuyer@1: for method, func in pairs(methods) do farmbuyer@1: widget[method] = func farmbuyer@1: end farmbuyer@1: farmbuyer@1: for _,func in ipairs(oopsfuncs) do farmbuyer@1: widget[func] = Oops farmbuyer@1: end farmbuyer@1: farmbuyer@1: -- AceGUI:RegisterAsWidget needs .frame farmbuyer@1: return widget farmbuyer@1: end farmbuyer@1: farmbuyer@1: AceGUI:RegisterWidgetType(Type,Constructor,Version) farmbuyer@1: