Mercurial > wow > reaction
view ReBar.lua @ 1:c11ca1d8ed91
Version 0.1
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Tue, 20 Mar 2007 21:03:57 +0000 |
parents | |
children | 8e0ff8ae4c08 |
line wrap: on
line source
-- private constants local insideFrame = 1 local outsideFrame = 2 local _G = getfenv(0) -- global variable table local pointFindTable = { BOTTOMLEFT = function(f) return f:GetLeft(), f:GetBottom() end, BOTTOM = function(f) return nil, f:GetBottom() end, BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end, RIGHT = function(f) return f:GetRight(), nil end, TOPRIGHT = function(f) return f:GetRight(), f:GetTop() end, TOP = function(f) return nil, f:GetTop() end, TOPLEFT = function(f) return f:GetLeft(), f:GetTop() end, LEFT = function(f) return f:GetLeft(), nil end, } local oppositePointTable = { BOTTOMLEFT = "TOPRIGHT", BOTTOM = "TOP", BOTTOMRIGHT = "TOPLEFT", RIGHT = "LEFT", TOPRIGHT = "BOTTOMLEFT", TOP = "BOTTOM", TOPLEFT = "BOTTOMRIGHT", LEFT = "RIGHT" } -- private variables local stickyTargets = { [UIParent] = insideFrame, [WorldFrame] = insideFrame } -- ReBar is an Ace 2 class prototype object. ReBar = AceLibrary("AceOO-2.0").Class("AceEvent-2.0") local dewdrop = AceLibrary("Dewdrop-2.0") function ReBar.prototype:init( config, id ) ReBar.super.prototype.init(self) local buttonClass = config and config.btnConfig and config.btnConfig.type and _G[config.btnConfig.type] self.config = config self.barID = id self.class = { button = buttonClass } self.buttons = { } -- create the bar and control widgets self.barFrame = CreateFrame("Frame", "ReBar_"..self.barID, UIParent, "ReBarTemplate") self.controlFrame = _G[self.barFrame:GetName().."Controls"] self.controlFrame.reBar = self -- set the text label on the control widget _G[self.controlFrame:GetName().."LabelString"]:SetText(id) -- initialize the bar layout self:ApplySize() self:ApplyAnchor() self:LayoutButtons() self:ApplyVisibility() -- add bar to stickyTargets list stickyTargets[self.barFrame] = outsideFrame -- initialize dewdrop menu dewdrop:Register(self.controlFrame, 'children', function() dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions) dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(self)) dewdrop:FeedAceOptionsTable(GenerateReActionButtonOptions(self)) dewdrop:FeedAceOptionsTable(ReActionProfileMenuOptions) end, 'cursorX', true, 'cursorY', true ) end function ReBar.prototype:Destroy() if self.barFrame == dewdrop:GetOpenedParent() then dewdrop:Close() dewdrop:Unregister(self.barFrame) end self.barFrame:Hide() self.barFrame:ClearAllPoints() self.barFrame:SetParent(nil) -- need to keep around self.config for dewdrop menus in the process of deleting self while #self.buttons > 0 do self.class.button:release(table.remove(self.buttons)) end -- remove from sticky targets table stickyTargets[self.barFrame] = nil -- remove from global table -- for some reason after a destroy/recreate the globals still reference -- the old frames setglobal(self.barFrame:GetName(), nil) setglobal(self.barFrame:GetName().."Controls", nil) setglobal(self.controlFrame:GetName().."LabelString", nil) end -- show/hide the control frame function ReBar.prototype:ShowControls() self.controlFrame:Show() end function ReBar.prototype:HideControls() local b = self.barFrame if b.isMoving or b.resizing then b:StopMovingOrSizing() b:SetScript("OnUpdate",nil) end -- close any dewdrop menu owned by us if self.barFrame == dewdrop:GetOpenedParent() then dewdrop:Close() end self.controlFrame:Hide() end -- accessors function ReBar.prototype:GetVisibility() return self.config.visible end function ReBar.prototype:ToggleVisibility() self.config.visible = not self.config.visible self:ApplyVisibility() end function ReBar.prototype:GetOpacity() return self.config.opacity or 100 end function ReBar.prototype:SetOpacity( o ) self.config.opacity = tonumber(o) self:ApplyVisibility() return self.config.opacity end -- layout methods function ReBar.prototype:ApplySize() local buttonSz = self.config.size or 36 local spacing = self.config.spacing or 4 local rows = self.config.rows or 1 local columns = self.config.columns or 12 local w = buttonSz * columns + spacing * (columns + 1) local h = buttonSz * rows + spacing * (rows + 1) local f = self.barFrame -- +1: avoid resizing oddities caused by fractional UI scale setting f:SetMinResize(buttonSz + spacing*2 + 1, buttonSz + spacing*2 + 1) f:SetWidth(w + 1) f:SetHeight(h + 1) end function ReBar.prototype:ApplyAnchor() local a = self.config.anchor local f = self.barFrame f:ClearAllPoints() f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y) end function ReBar.prototype:ApplyVisibility() local v = self.config.visibility if type(v) == "table" then if v.class then local _, c = UnitClass("player") v = v.class[c] end elseif type(v) == "string" then local value = getglobal(v) v = value end if self.config.opacity then self.barFrame:SetAlpha(self.config.opacity / 100) end if v then self.barFrame:Show() else self.barFrame:Hide() end end function ReBar.prototype:LayoutButtons() local r = self.config.rows local c = self.config.columns local n = r * c local sp = self.config.spacing local sz = self.config.size local gSize = sp + sz for i = 1, n do if self.buttons[i] == nil then table.insert(self.buttons, self.class.button:acquire(self.barFrame, self.config.btnConfig, i)) end local b = self.buttons[i] if b == nil then break -- handling for button types that support limited numbers end b:PlaceButton("TOPLEFT", sp + gSize * math.fmod(i-1,c), - (sp + gSize * math.floor((i-1)/c)), sz) end -- b == nil, above, should always be the case if and only if i == n. ReBar never monkeys -- with buttons in the middle of the sequence: it always adds or removes on the array end while #self.buttons > n do self.class.button:release(table.remove(self.buttons)) end end function ReBar.prototype:StoreAnchor(f, p, rp, x, y) local name = f:GetName() -- no point if we can't store the name or the offsets are incomplete if name and x and y then self.config.anchor = { to = name, point = p, relPoint = rp or p, x = x, y = y } end end -- mouse event handlers (clicking/dragging/resizing the bar) function ReBar.prototype:BeginDrag() local f = self.barFrame f:StartMoving() f.isMoving = true f:SetScript("OnUpdate", function() self:StickyIndicatorUpdate() end) end function ReBar.prototype:FinishDrag() local f, p, rp, x, y local bf = self.barFrame bf:StopMovingOrSizing() bf.isMoving = false bf:SetScript("OnUpdate",nil) if IsShiftKeyDown() then f, p, rp, x, y = self:GetStickyAnchor() ReBarStickyIndicator1:Hide() ReBarStickyIndicator2:Hide() end if f == nil then f = UIParent local _ _, p,rp,x,y = self:GetClosestPointTo(f) end if f then self:StoreAnchor(f,p,rp,x,y) self:ApplyAnchor() end end function ReBar.prototype:BeginBarResize( sizingPoint ) local f = self.barFrame f:StartSizing(sizingPoint) f.resizing = true f:SetScript("OnUpdate",function() self:ReflowButtons() end) end function ReBar.prototype:BeginButtonResize( sizingPoint, mouseBtn ) local f = self.barFrame f:StartSizing(sizingPoint) f.resizing = true local r = self.config.rows local c = self.config.columns local s = self.config.spacing local sz = self.config.size if mouseBtn == "LeftButton" then f:SetMinResize(c*(12 + 2*s) +1, r*(12 + 2*s) +1) f:SetScript("OnUpdate",function() self:DragSizeButtons() end) elseif mouseBtn == "RightButton" then f:SetMinResize(c*sz+1, r*sz+1) f:SetScript("OnUpdate",function() self:DragSizeSpacing() end) end end function ReBar.prototype:FinishResize() local f = self.barFrame f:StopMovingOrSizing() f.resizing = false f:SetScript("OnUpdate",nil) self:ApplySize() end -- sticky anchoring functions function ReBar.prototype:StickyIndicatorUpdate() local si1 = ReBarStickyIndicator1 local si2 = ReBarStickyIndicator2 if IsShiftKeyDown() then local f, p, rp, x, y = self:GetStickyAnchor() if f then si1:ClearAllPoints() si2:ClearAllPoints() si1:SetPoint("CENTER",self.barFrame,p,0,0) si2:SetPoint("CENTER",f,rp,x,y) si1:Show() si2:Show() return nil end end si1:Hide() si2:Hide() si1:ClearAllPoints() si2:ClearAllPoints() end function ReBar.prototype:CheckAnchorable(f) -- can't anchor to self or to a hidden frame if f == self.barFrame or not(f:IsShown()) then return false end -- also can't anchor to frames that are anchored to self for i = 1, f:GetNumPoints() do local _, f2 = f:GetPoint(i) if f2 == self.barFrame then return false end end return true end function ReBar.prototype:GetStickyAnchor() local snapRange = (self.config.size + self.config.spacing) local r2, f, p, rp, x, y = self:GetClosestAnchor() if f and p then local xx, yy = pointFindTable[p](f) if r2 and r2 < (snapRange*snapRange) then if xx or math.abs(x) < snapRange then x = 0 end if yy or math.abs(y) < snapRange then y = 0 end elseif not(yy) and math.abs(x) < snapRange then x = 0 elseif not(xx) and math.abs(y) < snapRange then y = 0 else f = nil -- nothing in range end end return f, p, rp, x, y end function ReBar.prototype:GetClosestAnchor() -- choose the closest anchor point on the list of target frames local range2, frame, point, relPoint, offsetX, offsetY for f, tgtRegion in pairs(stickyTargets) do if self:CheckAnchorable(f) then local r2 ,p, rp, x, y = self:GetClosestPointTo(f,tgtRegion) if r2 then if not(range2 and range2 < r2) then range2, frame, point, relPoint, offsetX, offsetY = r2, f, p, rp, x, y end end end end return range2, frame, point, relPoint, offsetX, offsetY end function ReBar.prototype:GetClosestPointTo(f,inside) local range2, point, relPoint, offsetX, offsetY local pft = pointFindTable local cx, cy = self.barFrame:GetCenter() local fcx, fcy = f:GetCenter() local fh = f:GetHeight() local fw = f:GetWidth() -- compute whether edge bisector intersects target edge local dcx = math.abs(cx-fcx) < fw/2 and (cx-fcx) local dcy = math.abs(cy-fcy) < fh/2 and (cy-fcy) for p, func in pairs(pft) do local rp, x, y if inside == outsideFrame then rp = oppositePointTable[p] x, y = self:GetOffsetToPoint(f, func, pft[rp]) else rp = p x, y = self:GetOffsetToPoint(f, func, func) end -- if anchoring to an edge, only anchor if the center point overlaps the other edge if (x or dcx) and (y or dcy) then local r2 = (x or 0)^2 + (y or 0)^2 if range2 == nil or r2 < range2 then range2, point, relPoint, offsetX, offsetY = r2, p, rp, x or dcx, y or dcy end end end return range2, point, relPoint, offsetX, offsetY end function ReBar.prototype:GetOffsetToPoint(f,func,ffunc) local x, y = func(self.barFrame) -- coordinates of the point on this frame local fx, fy = ffunc(f) -- coordinates of the point on the target frame -- guarantees: if x then fx, if y then fy return x and (x-fx), y and (y-fy) end -- utility function to get the height, width, and button size attributes function ReBar.prototype:GetLayout() local c = self.config local f = self.barFrame return f:GetWidth(), f:GetHeight(), c.size, c.rows, c.columns, c.spacing end -- add and remove buttons dynamically as the bar is resized function ReBar.prototype:ReflowButtons() local w, h, sz, r, c, sp = self:GetLayout() self.config.rows = math.floor( (h - sp) / (sz + sp) ) self.config.columns = math.floor( (w - sp) / (sz + sp) ) if self.config.rows ~= r or self.config.columns ~= c then self:LayoutButtons() end end -- change the size of buttons as the bar is resized function ReBar.prototype:DragSizeButtons() local w, h, sz, r, c, sp = self:GetLayout() local newSzW = math.floor((w - (c+1)*sp)/c) local newSzH = math.floor((h - (r+1)*sp)/r) self.config.size = math.max(12, math.min(newSzW, newSzH)) if self.config.size ~= sz then self:LayoutButtons() self:UpdateResizeTooltip() end end -- change the spacing of buttons as the bar is resized function ReBar.prototype:DragSizeSpacing() local w, h, sz, r, c, sp = self:GetLayout() local newSpW = math.floor((w - c*sz)/(c+1)) local newSpH = math.floor((h - r*sz)/(r+1)) self.config.spacing = math.max(0, math.min(newSpW, newSpH)) if self.config.spacing ~= sp then self:LayoutButtons() self:UpdateResizeTooltip() end end -- update the drag tooltip to indicate current sizes function ReBar.prototype:UpdateResizeTooltip() GameTooltipTextRight4:SetText(self.config.size) GameTooltipTextRight5:SetText(self.config.spacing) GameTooltip:Show() end function ReBar.prototype:ShowTooltip() GameTooltip:SetOwner(self.barFrame, "ANCHOR_TOPRIGHT") GameTooltip:AddLine("Bar "..self.barID) GameTooltip:AddLine("Drag to move") GameTooltip:AddLine("Shift-drag for sticky mode") GameTooltip:AddLine("Right-click for options") GameTooltip:Show() end function ReBar.prototype:ShowButtonResizeTooltip(point) GameTooltip:SetOwner(self.barFrame, "ANCHOR_"..point) GameTooltip:AddLine("Drag to resize buttons") GameTooltip:AddLine("Right-click-drag") GameTooltip:AddLine("to change spacing") GameTooltip:AddDoubleLine("Size: ", "0") GameTooltip:AddDoubleLine("Spacing: ", "0") self:UpdateResizeTooltip() end function ReBar.prototype:ShowBarResizeTooltip(point) GameTooltip:SetOwner(self.barFrame, "ANCHOR_"..point) GameTooltip:AddLine("Drag to add/remove buttons") GameTooltip:Show() end