Mercurial > wow > reaction
diff classes/ReBar.lua @ 7:f920db5fc6b1
version 0.3
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Tue, 20 Mar 2007 21:25:29 +0000 |
parents | dfd829db3ad0 |
children | c05fd3e18b4f |
line wrap: on
line diff
--- a/classes/ReBar.lua Tue Mar 20 21:20:20 2007 +0000 +++ b/classes/ReBar.lua Tue Mar 20 21:25:29 2007 +0000 @@ -1,55 +1,155 @@ +-- private constants +local anchoredLabelColor = { r = 1.0, g = 0.82, b = 0.0 } +local nonAnchoredLabelColor = { r = 1.0, g = 1.0, b = 1.0 } --- private constants -local insideFrame = 1 -local outsideFrame = 2 +local DRAG_UPDATE_RATE = 0.125 -- cap at 8 Hz -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 nStancePages = { + WARRIOR = 3, + ROGUE = 0, + HUNTER = 0, + DRUID = 3, -- updated to 4 if talented + PALADIN = 0, + SHAMAN = 0, -- As far as I know, ghost wolf is not a proper shapeshift form, it's just a buff + MAGE = 0, + PRIEST = 0, -- updated to 1 if talented + WARLOCK = 0, } -local oppositePointTable = { - BOTTOMLEFT = "TOPRIGHT", - BOTTOM = "TOP", - BOTTOMRIGHT = "TOPLEFT", - RIGHT = "LEFT", - TOPRIGHT = "BOTTOMLEFT", - TOP = "BOTTOM", - TOPLEFT = "BOTTOMRIGHT", - LEFT = "RIGHT" +local stanceMaps = { + WARRIOR = { + -- note: warriors are never in shapeshift form 0. + [1] = "*:1", -- battle stance. All states go to state (page) 1. + [2] = "*:2", -- defensive stance. All states go to state (page) 2. + [3] = "*:3", -- berserker stance. All states go to state (page) 3. + }, + ROGUE = {}, + HUNTER = {}, + DRUID = { + [0] = "*:1", -- humanoid form. All states go to state (page) 1. + [1] = "*:2", -- bear/dire bear form. All states go to state (page) 2. + [2] = "*:1", -- aquatic form. All states to go state (page) 1 (same as humanoid) + [3] = "*:3", -- cat form. All states go to state (page) 3 + [4] = "*:1", -- travel form. All states go to state (page) 1 (same as humanoid) + [5] = "*:4", -- oomkin/tree form [talent only]. All states go to state (page) 4 + [6] = "*:1", -- flight form. All states go to state (page) 1 (same as humanoid) (??? How does this work with oomkin/tree?) + }, + PALADIN = {}, + SHAMAN = {}, + MAGE = {}, + PRIEST = { + [0] = "*:1", -- normal form. All states go to page 1. + [1] = "*:2", -- shadowform (talent only). All states go to page 2. + }, + WARLOCK = {}, } -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 +local stealthMaps = { + WARRIOR = "", + ROGUE = "1:2", -- go to page 2 for rogues + HUNTER = "", + DRUID = "3:5", -- we'll replace with 1:2 if stance mapping is not enabled, and page 5 with 6 if oomkin/tree + PALADIN = "", + SHAMAN = "", + MAGE = "", + PRIEST = "", } +local unstealthMaps = { + WARRIOR = "", + ROGUE = "2:1", -- go to page 1 for rogues + HUNTER = "", + DRUID = "5:3", -- we'll replace with 2:1 if stance mapping is not enabled, and page 5 with 6 if oomkin/tree + PALADIN = "", + SHAMAN = "", + MAGE = "", + PRIEST = "", +} + +-- Immediately fix if a druid with oomkin or tree (note: can't have both) +local _, playerClass = UnitClass("player") +if playerClass == "DRUID" and GetNumShapeshiftForms() > 4 then + nStancePages.DRUID = 4 + stanceMaps.DRUID[5] = "*:4" + stealthMaps.DRUID = "3:6" + unstealthMaps.DRUID ="6:3" +end + +-- Immediately fix if a priest with shadowform +if playerClass == "PRIEST" and GetNumShapeshiftForms() > 1 then + nStancePages.PRIEST = 2 +end + +local AceOO = AceLibrary("AceOO-2.0") + -- ReBar is an Ace 2 class prototype object. -ReBar = AceLibrary("AceOO-2.0").Class("AceEvent-2.0") +ReBar = AceOO.Class("AceEvent-2.0", ReAnchor, ReAnchor.IAnchorable) -local dewdrop = AceLibrary("Dewdrop-2.0") +---------------------- +-- Static members +---------------------- +-- IButtonClass is an interface required of any button class type (not the class objects themselves) to be used with the bar +ReBar.IButtonClass = AceOO.Interface { + Acquire = "function", -- btn = Acquire(config, barIdx, pages, buttonsPerPage) + -- analogous to new(), this should re-use recycled frames for memory efficiency + Release = "function", -- Release(btn) should hide and dispose of the button (and recycle it) +} + +-- IButton is an interface required of any button objects to be used with the bar +ReBar.IButton = AceOO.Interface { + GetActionFrame = "function", -- obtains the action frame, presumably inherited from SecureActionButtonTemplate or similar. + BarLocked = "function", -- BarLocked() called when the bar is locked. + BarUnlocked = "function", -- BarUnlocked() called when the bar is unlocked. + PlaceButton = "function", -- PlaceButton(parent, anchorPoint, offsetX, offsetY, squareSz), one-stop position and size setting + SetPages = "function", -- SetPages(n): sets number of pages for multi-paging. Can error if paging not supported. +} + +-- wrap UIParent and WorldFrame in objects which implement the ReAnchor.IAnchorable interface +local UIParentPlaceable = { + GetFrame = function() return UIParent end, + GetAnchorage = function() return ReAnchor.anchorInside end, +} + +local WorldFramePlaceable = { + GetFrame = function() return WorldFrame end, + GetAnchorage = function() return ReAnchor.anchorInside end, +} + +ReBar.anchorTargets = { + UIParentPlaceable, + WorldFramePlaceable +} + +ReBar.UIParentAnchorTarget = { + UIParentPlaceable +} + + + + + + +-------------------- +-- instance methods +-------------------- +-- construction and destruction 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 = { } + self.locked = true + + self.buttonClass = config.btnConfig and config.btnConfig.type and getglobal(config.btnConfig.type) + if not AceOO.inherits(self.buttonClass, ReBar.IButton) then + error("ReBar: Supplied Button type does not meet required interface.") + end + -- create the bar and control widgets - self.barFrame = CreateFrame("Frame", "ReBar_"..self.barID, UIParent, "ReBarTemplate") + self.barFrame = CreateFrame("Frame", "ReBar"..id, config.parent and getglobal(config.parent) or UIParent, "ReBarTemplate") self.controlFrame = getglobal(self.barFrame:GetName().."Controls") self.controlFrame.reBar = self self.barFrame:SetClampedToScreen(true) @@ -58,82 +158,113 @@ self.labelString = getglobal(self.controlFrame:GetName().."LabelString") self.labelString:SetText(id) + -- initial stateheader state + self.barFrame.StateChanged = function() self:StateChanged() end + self.barFrame:SetAttribute("state",config.pages and config.pages.currentPage or 1) -- initial state + -- initialize the bar layout self:ApplySize() self:ApplyAnchor() + self:AcquireButtons() 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 - ) + if self.config.pages then + self:SetPages(self.config.pages.n) + self:ApplyAutoStanceSwitch() + self:ApplyAutoStealthSwitch() + self:RefreshPageControls() + end + + -- register page up/down buttons with ReBound for keybinding + if ReBound then + ReBound:AddKeybindTarget(getglobal(self.barFrame:GetName().."PageUp")) + ReBound:AddKeybindTarget(getglobal(self.barFrame:GetName().."PageDown")) + end + + -- add bar to anchorTargets list + table.insert(ReBar.anchorTargets, self) end +function ReBar.prototype:Destroy() + local f = self.barFrame -function ReBar.prototype:Destroy() - if self.barFrame == dewdrop:GetOpenedParent() then - dewdrop:Close() - dewdrop:Unregister(self.barFrame) + self:HideControls() + f:Hide() + f:ClearAllPoints() + f:SetParent(nil) + f:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0) + + while #self.buttons > 0 do + self.buttonClass:Release(table.remove(self.buttons)) end - self:HideControls() - self.barFrame:Hide() - self.barFrame:ClearAllPoints() - self.barFrame:SetParent(nil) - self.barFrame:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0) + -- remove from anchorTargets table + for idx, b in ipairs(ReBar.anchorTargets) do + if b == self then + table.remove(ReBar.anchorTargets, idx) + break + end + end - -- need to keep around self.config for dewdrop menus in the process of deleting self + -- remove from globals + local n = f:GetName() + setglobal(n, nil) + setglobal(n.."Control", nil) + setglobal(n.."Controls", nil) + setglobal(n.."ControlsLabelString", nil) + setglobal(n.."PageUp", nil) + setglobal(n.."PageDown", nil) + setglobal(n.."Page", nil) + setglobal(n.."PageNumber", nil) + setglobal(n.."PageNumberLabel", nil) + setglobal(n.."PageNumberLabelText", nil) +end - 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) + + +-- ReAnchor.IAnchorable interface implementation +function ReBar.prototype:GetFrame() + return self.barFrame end +function ReBar.prototype:GetAnchorage() + return ReAnchor.anchorOutside +end + + + -- show/hide the control frame function ReBar.prototype:ShowControls() + self.locked = false self.controlFrame:Show() for _, b in ipairs(self.buttons) do b:BarUnlocked() + b:DisplayVisibility() end + self:ApplyVisibility() end function ReBar.prototype:HideControls() + self.locked = true 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() + b:DisplayVisibility() end self.controlFrame:Hide() + self:ApplyVisibility() end - +function ReBar.prototype:GetControlFrame() + return self.controlFrame +end -- accessors @@ -142,20 +273,265 @@ end function ReBar.prototype:ToggleVisibility() - self.config.visible = not self.config.visible + self:SetVisibility( not self:GetVisibility() ) +end + +function ReBar.prototype:SetVisibility(v) + self.config.visible = v and true or false -- force data integrity self:ApplyVisibility() end function ReBar.prototype:GetOpacity() - return self.config.opacity or 100 + return tonumber(self.config.opacity) or 100 end function ReBar.prototype:SetOpacity( o ) - self.config.opacity = tonumber(o) - self:ApplyVisibility() - return self.config.opacity + o = tonumber(o) + if o then + self.config.opacity = o + self:ApplyVisibility() + return self.config.opacity + end end +function ReBar.prototype:GetButtonList() + return self.buttons +end + +function ReBar.prototype:GetGrowLeft() + return self.config.growLeft +end + +function ReBar.prototype:SetGrowLeft(g) + self.config.growLeft = g and true or false + self:LayoutButtons() +end + +function ReBar.prototype:GetGrowUp() + return self.config.growUp +end + +function ReBar.prototype:SetGrowUp(g) + self.config.growUp = g and true or false + self:LayoutButtons() +end + +function ReBar.prototype:GetColumnMajor() + return self.config.columnMajor +end + +function ReBar.prototype:SetColumnMajor(m) + self.config.columnMajor = m and true or false + self:LayoutButtons() +end + + +-- paging methods +function ReBar.prototype:IsPagingDisabled() + return not (self.config.pages and self.config.pages.n > 1) +end + +function ReBar.prototype:GetPages() + return self.config.pages and self.config.pages.n or 1 +end + +function ReBar.prototype:SetPages(n) + n = tonumber(n) + if n and n >= 1 then + self.config.pages = self.config.pages or { } + self.config.pages.n = n + for _, btn in pairs(self.buttons) do + btn:SetPages(n) + end + if n > 1 then + local statebutton = "0:page1;" + -- map states 1-n to 'page1'-'pagen' + -- page 0 is the same as page 1. + for i = 1, n do + statebutton = statebutton..i..":page"..i..";" + end + self.barFrame:SetAttribute("statebutton",statebutton) + else + self.barFrame:SetAttribute("statebutton",ATTRIBUTE_NOOP) + end + self.barFrame:SetAttribute("statemap-anchor-next","1-"..n) + self.barFrame:SetAttribute("statemap-anchor-prev",tostring(n).."-1") + self:RefreshPageControls() + end +end + +function ReBar.prototype:GetAutoStanceSwitch() + return self.config.pages and self.config.pages.autoStanceSwitch +end + +function ReBar.prototype:SetAutoStanceSwitch( s ) + if not self.config.pages then + self.config.pages = { n = 1 } + end + self.config.pages.autoStanceSwitch = s and true or false + self:ApplyAutoStanceSwitch() +end + +function ReBar.prototype:ToggleAutoStanceSwitch() + self:SetAutoStanceSwitch( not self:GetAutoStanceSwitch() ) +end + +function ReBar.prototype:ApplyAutoStanceSwitch() + local switch = self:GetAutoStanceSwitch() + local _, class = UnitClass("player") + if switch then + -- check that the number of pages available is sufficient + local totalPages = nStancePages[class] + (self:GetAutoStealthSwitch() and 1 or 0) + if self:GetPages() < totalPages then + self:SetPages(totalPages) + end + for form, spec in pairs(stanceMaps[class]) do + self.barFrame:SetAttribute("statemap-stance-"..form,spec) + end + -- set initial value + self.barFrame:SetAttribute("state-stance",GetShapeshiftForm(true)) + else + for form, _ in pairs(stanceMaps[class]) do + self.barFrame:SetAttribute("statemap-stance-"..form, ATTRIBUTE_NOOP) + end + end +end + +function ReBar.prototype:GetAutoStealthSwitch() + return self.config.pages and self.config.pages.autoStealthSwitch +end + +function ReBar.prototype:SetAutoStealthSwitch( s ) + if not self.config.pages then + self.config.pages = { n = 1 } + end + self.config.pages.autoStealthSwitch = s and true or false + self:ApplyAutoStealthSwitch() +end + +function ReBar.prototype:ToggleAutoStealthSwitch() + self:SetAutoStealthSwitch( not self:GetAutoStealthSwitch() ) +end + +function ReBar.prototype:ApplyAutoStealthSwitch() + local switch = self:GetAutoStealthSwitch() + local _, class = UnitClass("player") + if switch then + -- check that the number of pages available is sufficient + local totalPages = (self:GetAutoStanceSwitch() and nStancePages[class] > 0 and nStancePages[class] or 1) + 1 + if self:GetPages() < totalPages then + self:SetPages(totalPages) + end + local s, s2 + if class == "DRUID" and not self:GetAutoStanceSwitch() then + -- change mapping for cat->prowl and prowl->cat to 1:2 and 2:1 since no stance mapping + s = "1:2" + s2 = "2:1" + end + self.barFrame:SetAttribute("statemap-stealth-1",s or stealthMaps[class]) + self.barFrame:SetAttribute("statemap-stealth-0",s2 or unstealthMaps[class]) + -- set initial value + self.barFrame:SetAttribute("state-stealth",IsStealthed() or 0) + else + self.barFrame:SetAttribute("statemap-stealth-1",ATTRIBUTE_NOOP) + self.barFrame:SetAttribute("statemap-stealth-0",ATTRIBUTE_NOOP) + end +end + +function ReBar.prototype:ArePageControlsHidden() + return not ( self.config.pages and self.config.pages.showControls ) +end + +function ReBar.prototype:TogglePageControlsHidden() + if self.config.pages then + self.config.pages.showControls = not self.config.pages.showControls + self:RefreshPageControls() + end +end + +function ReBar.prototype:GetPageControlsLoc() + return self.config.pages and self.config.pages.controlsLoc +end + +function ReBar.prototype:SetPageControlsLoc(loc) + if self.config.pages then + self.config.pages.controlsLoc = loc + self:RefreshPageControls() + end +end + +function ReBar.prototype:RefreshPageControls() + local b = self.barFrame; + local upArrow = getglobal(b:GetName().."PageUp") + local downArrow = getglobal(b:GetName().."PageDown") + local pageNum = getglobal(b:GetName().."PageNumber") + + if self:GetPages() > 1 and self.config.pages.showControls then + local loc = self.config.pages.controlsLoc + + pageNum:ClearAllPoints() + upArrow:ClearAllPoints() + downArrow:ClearAllPoints() + + local vertical = { 0,0,0,1,1,0,1,1 } + local horizontal = { 0,1,1,1,0,0,1,0 } + local textures = { + upArrow:GetNormalTexture(), + upArrow:GetPushedTexture(), + upArrow:GetDisabledTexture(), + upArrow:GetHighlightTexture(), + downArrow:GetNormalTexture(), + downArrow:GetPushedTexture(), + downArrow:GetDisabledTexture(), + downArrow:GetHighlightTexture(), + } + + local offset = 10 + local mult = (loc == "RIGHT" or loc == "TOP") and 1 or -1 + local pageNumOffset = mult * (self.config.spacing/2 - offset) + local arrowOffset = mult * offset + + if loc == "Blizzard" or loc == nil then + pageNum:SetPoint("LEFT", b, "RIGHT", 28, 0) + upArrow:SetPoint("LEFT", b, "RIGHT", -2, 9) + downArrow:SetPoint("LEFT", b, "RIGHT", -2, -10) + for _, tex in ipairs(textures) do + tex:SetTexCoord(unpack(vertical)) + end + elseif loc == "RIGHT" or loc == "LEFT" then + local relPoint = loc == "RIGHT" and "LEFT" or "RIGHT" + pageNum:SetPoint(relPoint, b, loc, pageNumOffset, 0) + upArrow:SetPoint("BOTTOM",pageNum,"TOP",arrowOffset, -12) + downArrow:SetPoint("TOP",pageNum,"BOTTOM",arrowOffset, 12) + for _, tex in ipairs(textures) do + tex:SetTexCoord(unpack(vertical)) + end + else + pageNum:SetPoint(loc == "BOTTOM" and "TOP" or "BOTTOM", b, loc, 0, pageNumOffset) + upArrow:SetPoint("LEFT",pageNum,"RIGHT",-12,arrowOffset) + downArrow:SetPoint("RIGHT",pageNum,"LEFT",12,arrowOffset) + for _, tex in ipairs(textures) do + tex:SetTexCoord(unpack(horizontal)) + end + end + self:StateChanged() + upArrow:Show() + downArrow:Show() + pageNum:Show() + else + upArrow:Hide() + downArrow:Hide() + pageNum:Hide() + end +end + +function ReBar.prototype:StateChanged() + local page = self.barFrame:GetAttribute("state") or 1 + getglobal(self.barFrame:GetName().."PageNumberLabelText"):SetText(page) + if self.config.pages then self.config.pages.currentPage = page end +end + + -- layout methods function ReBar.prototype:ApplySize() @@ -163,14 +539,14 @@ 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 w = buttonSz * columns + spacing * columns + local h = buttonSz * rows + spacing * rows 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) + -- + 0.1: avoid issues with UI scaling + f:SetMinResize(buttonSz + spacing + 0.1, buttonSz + spacing + 0.1) + f:SetWidth(w + 0.1) + f:SetHeight(h + 0.1) end function ReBar.prototype:ApplyAnchor() @@ -178,9 +554,9 @@ local f = self.barFrame if a then f:ClearAllPoints() - f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y) + f:SetPoint(a.point,getglobal(a.frame),a.relPoint,a.x,a.y) local color = anchoredLabelColor - if a.to == "UIParent" or a.to == "WorldFrame" then + if a.frame == "UIParent" or a.frame == "WorldFrame" then color = nonAnchoredLabelColor end self.labelString:SetTextColor(color.r, color.g, color.b) @@ -188,18 +564,9 @@ 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 + local v = self.config.visible or not self.locked - if self.config.opacity then + if tonumber(self.config.opacity) then self.barFrame:SetAlpha(self.config.opacity / 100) end @@ -213,26 +580,55 @@ 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 + local point = (self.config.growUp and "BOTTOM" or "TOP")..(self.config.growLeft and "RIGHT" or "LEFT") + local major = self.config.columnMajor and r or c + + for i, b in ipairs(self.buttons) do + local x = sp/2 + gSize * math.fmod(i-1,major) + local y = sp/2 + gSize * math.floor((i-1)/major) + if self.config.columnMajor then x,y = y,x end + if self.config.growLeft then x = -x end + if not self.config.growUp then y = -y end + b:PlaceButton(self.barFrame, point, x, y, sz) + end +end + +function ReBar.prototype:FlipRowsColumns() + self.config.rows, self.config.columns = self.config.columns, self.config.rows + self.config.columnMajor = not self.config.columnMajor + self:ApplySize() + self:LayoutButtons() +end + +function ReBar.prototype:AcquireButtons() + local n = self.config.rows * self.config.columns 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)) + local b = self.buttonClass:Acquire(self.config.btnConfig, i, self.config.pages and self.config.pages.n, n) + if b then + if not AceOO.inherits(b, ReBar.IButton) then + error("ReBar: specified Button class object did not meet required interface") + end + if self.locked == false then + b:BarUnlocked() -- buttons assume they are created in a barlocked state + end + self.buttons[i] = b + self.barFrame:SetAttribute("addchild",b:GetActionFrame()) + end 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)) + local maxn = table.maxn(self.buttons) + for i = n+1, table.maxn(self.buttons) do + local b = self.buttons[i] + self.buttons[i] = nil + if b then + self.buttonClass:Release(b) + end end end @@ -243,7 +639,7 @@ -- 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, + frame = name, point = p, relPoint = rp or p, x = x, @@ -254,45 +650,53 @@ +function ReBar.prototype:StickyIndicatorUpdate() + if IsShiftKeyDown() then + local snapRange = self.config.size + self.config.spacing + self:DisplaySnapIndicator(ReBar.anchorTargets, snapRange, 0, 0) + else + self:HideSnapIndicator() + 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) + self.updateTime = DRAG_UPDATE_RATE + f:SetScript("OnUpdate", function(frame, elapsed) self:StickyIndicatorUpdate(elapsed) end) end function ReBar.prototype:FinishDrag() - local f, p, rp, x, y + local o, p, rp, x, y local bf = self.barFrame + local snapRange = self.config.size + self.config.spacing bf:StopMovingOrSizing() bf.isMoving = false bf:SetScript("OnUpdate",nil) if IsShiftKeyDown() then - f, p, rp, x, y = self:GetStickyAnchor() - ReBarStickyIndicator1:Hide() - ReBarStickyIndicator2:Hide() + o, p, rp, x, y = self:GetClosestPointSnapped(ReBar.anchorTargets, snapRange, 0, 0) end - if f == nil then - f = UIParent - local _ - _, p,rp,x,y = self:GetClosestPointTo(f) + if o == nil then + o, p, rp, x, y = self:GetClosestVisiblePoint(ReBar.UIParentAnchorTarget, snapRange, 0, 0) end - - if f then - self:StoreAnchor(f,p,rp,x,y) - self:ApplyAnchor() - end + + self:HideSnapIndicator() + self:StoreAnchor(o:GetFrame(), p, rp, x, y) + self:ApplyAnchor() end function ReBar.prototype:BeginBarResize( sizingPoint ) local f = self.barFrame f:StartSizing(sizingPoint) f.resizing = true - f:SetScript("OnUpdate",function() self:ReflowButtons() end) + self.updateTime = DRAG_UPDATE_RATE + f:SetScript("OnUpdate",function(frame, elapsed) self:ReflowButtons(elapsed) end) end function ReBar.prototype:BeginButtonResize( sizingPoint, mouseBtn ) @@ -303,12 +707,13 @@ local c = self.config.columns local s = self.config.spacing local sz = self.config.size + self.updateTime = DRAG_UPDATE_RATE if mouseBtn == "LeftButton" then - f:SetMinResize(c*(12 + 2*s) +1, r*(12 + 2*s) +1) - f:SetScript("OnUpdate",function() self:DragSizeButtons() end) + f:SetMinResize(c*(12 + s) +0.1, r*(12 + s) +0.1) + f:SetScript("OnUpdate",function(frame, elapsed) self:DragSizeButtons(elapsed) end) elseif mouseBtn == "RightButton" then - f:SetMinResize(c*sz+1, r*sz+1) - f:SetScript("OnUpdate",function() self:DragSizeSpacing() end) + f:SetMinResize(c*sz+0.1, r*sz+0.1) + f:SetScript("OnUpdate",function(frame, elapsed) self:DragSizeSpacing(elapsed) end) end end @@ -323,122 +728,6 @@ --- 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 - - - @@ -449,47 +738,64 @@ 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() +function ReBar.prototype:ReflowButtons( elapsed ) + self.updateTime = self.updateTime - elapsed + if self.updateTime <= 0 then + self.updateTime = DRAG_UPDATE_RATE + + 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) ) + self.config.rows = math.floor( (h+1) / (sz + sp) ) + self.config.columns = math.floor( (w+1) / (sz + sp) ) - if self.config.rows ~= r or self.config.columns ~= c then - self:LayoutButtons() + if self.config.rows ~= r or self.config.columns ~= c then + self:AcquireButtons() + self:LayoutButtons() + end 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() +function ReBar.prototype:DragSizeButtons( elapsed ) + self.updateTime = self.updateTime - elapsed + if self.updateTime <= 0 then + self.updateTime = DRAG_UPDATE_RATE + + 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)) + local newSzW = math.floor((w - c*sp)/c) + local newSzH = math.floor((h - r*sp)/r) + + self.config.size = math.max(12, math.min(newSzW, newSzH)) - if self.config.size ~= sz then - self:LayoutButtons() - self:UpdateResizeTooltip() + if self.config.size ~= sz then + self:LayoutButtons() + self:UpdateResizeTooltip() + end 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() +function ReBar.prototype:DragSizeSpacing( elapsed ) + self.updateTime = self.updateTime - elapsed + if self.updateTime <= 0 then + self.updateTime = DRAG_UPDATE_RATE + + 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)) + local newSpW = math.floor((w - c*sz)/c) + local newSpH = math.floor((h - r*sz)/r) - self.config.spacing = math.max(0, math.min(newSpW, newSpH)) + self.config.spacing = math.max(0, math.min(newSpW, newSpH)) - if self.config.spacing ~= sp then - self:LayoutButtons() - self:UpdateResizeTooltip() + if self.config.spacing ~= sp then + self:LayoutButtons() + self:UpdateResizeTooltip() + end end end @@ -503,7 +809,7 @@ function ReBar.prototype:ShowTooltip() GameTooltip:SetOwner(self.barFrame, "ANCHOR_TOPRIGHT") - GameTooltip:AddLine("Bar "..self.barID) + GameTooltip:AddLine(self.config.btnConfig.subtype.." Bar "..self.barID..(self.config.visible and "" or " (Hidden)")) GameTooltip:AddLine("Drag to move") GameTooltip:AddLine("Shift-drag for sticky mode") GameTooltip:AddLine("Right-click for options")