diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ReBar.lua	Tue Mar 20 21:03:57 2007 +0000
@@ -0,0 +1,509 @@
+
+-- 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