Mercurial > wow > reaction
diff classes/ReBar.lua @ 4:dfd829db3ad0
(none)
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Tue, 20 Mar 2007 21:19:34 +0000 |
parents | ReBar.lua@8e0ff8ae4c08 |
children | f920db5fc6b1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/classes/ReBar.lua Tue Mar 20 21:19:34 2007 +0000 @@ -0,0 +1,527 @@ + +-- private constants +local insideFrame = 1 +local outsideFrame = 2 + +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" +} + +local anchoredLabelColor = { r =0.6, g = 0.2, b = 1.0 } +local nonAnchoredLabelColor = { r = 1.0, g = 0.82, b = 0.0 } + +-- 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 getglobal(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 = getglobal(self.barFrame:GetName().."Controls") + self.controlFrame.reBar = self + self.barFrame:SetClampedToScreen(true) + + -- set the text label on the control widget + self.labelString = getglobal(self.controlFrame:GetName().."LabelString") + self.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)) + end, + 'cursorX', true, + 'cursorY', true + ) +end + + +function ReBar.prototype:Destroy() + if self.barFrame == dewdrop:GetOpenedParent() then + dewdrop:Close() + dewdrop:Unregister(self.barFrame) + end + + self:HideControls() + self.barFrame:Hide() + self.barFrame:ClearAllPoints() + self.barFrame:SetParent(nil) + self.barFrame:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0) + + -- 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() + for _, b in ipairs(self.buttons) do + b:BarUnlocked() + end +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 + for _, b in ipairs(self.buttons) do + b:BarLocked() + 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 + if a then + f:ClearAllPoints() + f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y) + local color = anchoredLabelColor + if a.to == "UIParent" or a.to == "WorldFrame" then + color = nonAnchoredLabelColor + end + self.labelString:SetTextColor(color.r, color.g, color.b) + end +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