# HG changeset patch # User Flick # Date 1224088181 0 # Node ID 7cabc8ac6c166ddead03653279e3aaccfdaf68b8 # Parent 491a6ffe72609bb6dace45c1e8c15cd2ba916dbd Updates for wow 3.0 - TOC update - updated changed APIs/frame names - rewrote state code per new SecureHandlers API - cleaned up Bar, ActionButton code - removed AceLibrary/Dewdrop, menu from bar right-click - fixed various small bugs Updated WowAce external locations Updated README.html diff -r 491a6ffe7260 -r 7cabc8ac6c16 Bar.lua --- a/Bar.lua Mon Oct 13 23:32:33 2008 +0000 +++ b/Bar.lua Wed Oct 15 16:29:41 2008 +0000 @@ -5,64 +5,48 @@ local floor = math.floor local fmod = math.fmod local format = string.format -local SecureStateHeader_Refresh = SecureStateHeader_Refresh ReAction:UpdateRevision("$Revision$") +local Bar = { } +local proto = { __index = Bar } +local weak = { __mode = "k" } ------- BAR CLASS ------ -local Bar = { _classID = {} } ReAction.Bar = Bar -- export to ReAction function Bar:New( name, config ) - -- create new self - self = setmetatable( { }, {__index = Bar} ) if type(config) ~= "table" then error("ReAction.Bar: config table required") end - config.width = config.width or 480 - config.height = config.height or 40 - self.name, self.config = name, config - self.buttons = setmetatable({},{__mode="k"}) - self.statedrivers = { } - self.keybinds = { } + -- create new self + self = setmetatable( + { + config = config, + name = name, + buttons = setmetatable( { }, weak ), + width = config.width or 480, + height = config.height or 40 + }, + proto ) + + -- The frame type is 'Button' in order to have an OnClick handler. However, the frame itself is + -- not mouse-clickable by the user. + local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent + local f = CreateFrame("Button", name and format("ReAction-%s",name), parent, + "SecureHandlerStateTemplate, SecureHandlerClickTemplate") + f:SetFrameStrata("MEDIUM") + f:SetWidth(self.width) + f:SetWidth(self.height) + f:Show() + f:EnableMouse(false) + f:SetClampedToScreen(true) - local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent - local f = CreateFrame("Button",name and format("ReAction-%s",name),parent,"SecureStateHeaderTemplate, SecureActionButtonTemplate") - f:SetFrameStrata("MEDIUM") - f:SetWidth(config.width) - f:SetWidth(config.height) - f:Show() - - -- The bar itself is also a Button derived from SecureActionButtonTemplate, so it has an OnClick handler - -- which we can use as a virtual button for keybinds, which will send attribute-value changes to itself. - -- However, we don't ever want the user to be able to click it directly. - f:EnableMouse(false) - f:SetAttribute("type","attribute") - - -- Buttons are contained in an anonymous intermediate sub-frame. This arrangement is to specifically - -- address the issue of the interaction with hidestates and auto-hiding empty action buttons (the two - -- don't play nicely together). It also has the fringe benefit of making show/hide faster because a - -- single frame is shown/hidden instead of potentially dozens. Unfortunately it does add an extra layer - -- of indirection to all state changes, as a secondary (trivial) statemap must be invoked. This - -- complicates frame setup slightly. - local bf = CreateFrame("Frame", nil, f, "SecureStateHeaderTemplate") - bf:SetAllPoints() - bf:Show() - bf:SetAttribute("useparent*",true) -- this facilitates SecureButton_GetModifiedAttribute() - bf:SetAttribute("statemap-parent","$input") -- However, we also need SetAttribute(state-parent) propagation too - f:SetAttribute("addchild",bf) - - -- Both frames are read-only. Override the default accessors for this object. + -- Override the default frame accessor to provide strict read-only access function self:GetFrame() return f end - function self:GetButtonFrame() - return bf - end - self:ApplyAnchor() ReAction.RegisterCallback(self, "OnConfigModeChanged") @@ -72,16 +56,10 @@ function Bar:Destroy() local f = self:GetFrame() f:UnregisterAllEvents() + ReAction.UnregisterAllCallbacks(self) f:Hide() f:SetParent(UIParent) f:ClearAllPoints() - ReAction.UnregisterAllCallbacks(self) - for driver in pairs(self.statedrivers) do - UnregisterStateDriver(f, driver) - end - self.labelString = nil - self.controlFrame = nil - self.config = nil end function Bar:OnConfigModeChanged(event, mode) @@ -89,22 +67,26 @@ end function Bar:ApplyAnchor() - local f, config = self:GetFrame(), self.config - f:SetWidth(config.width) - f:SetHeight(config.height) - local point = config.point + local f = self:GetFrame() + local c = self.config + local p = c.point + + f:SetWidth(c.width) + f:SetHeight(c.height) f:ClearAllPoints() - if point then - local anchor = f:GetParent() - if config.anchor then - local bar = ReAction:GetBar(config.anchor) + + if p then + local a = f:GetParent() + if c.anchor then + local bar = ReAction:GetBar(c.anchor) if bar then - anchor = bar:GetFrame() + a = bar:GetFrame() else - anchor = _G[config.anchor] + a = _G[c.anchor] end end - f:SetPoint(point, anchor or f:GetParent(), config.relpoint, config.x or 0, config.y or 0) + local fr = a or f:GetParent() + f:SetPoint(p, a or f:GetParent(), c.relpoint, c.x or 0, c.y or 0) else f:SetPoint("CENTER") end @@ -122,7 +104,11 @@ function Bar:GetAnchor() local c = self.config - return (c.point or "CENTER"), (c.anchor or self:GetFrame():GetParent():GetName()), (c.relpoint or c.point or "CENTER"), (c.x or 0), (c.y or 0) + return (c.point or "CENTER"), + (c.anchor or self:GetFrame():GetParent():GetName()), + (c.relpoint or c.point or "CENTER"), + (c.x or 0), + (c.y or 0) end function Bar:GetSize() @@ -131,9 +117,9 @@ end function Bar:SetSize(w,h) + local f = self:GetFrame() self.config.width = w self.config.height = h - local f = self:GetFrame() f:SetWidth(w) f:SetHeight(h) end @@ -182,121 +168,54 @@ function Bar:GetFrame() -- this method is included for documentation purposes. It is overridden - -- in the New method for each object. + -- for each object in the :New() method. error("Invalid Bar object: used without initialization") end -function Bar:GetButtonFrame() - -- this method is included for documentation purposes. It is overridden - -- in the New method for each object. - error("Invalid Bar object: used without initialization") +-- only ReAction:RenameBar() should call this function. Calling from any other +-- context will desync the bar list in the ReAction class. +function Bar:SetName(name) + self.name = name + self:SetLabel(self.name) -- Bar:SetLabel() defined in Overlay.lua end --- only ReAction:RenameBar() should call this function -function Bar:SetName(name) - self.name = name - -- controlLabelString is defined in Overlay.lua - if self.controlLabelString then - self.controlLabelString:SetText(self.name) +function Bar:AddButton(idx, button) + local f = self:GetFrame() + + -- store in a weak reverse-index array + self.buttons[button] = idx + + -- Store a properly wrapped reference to the child frame as an attribute + -- (accessible via "frameref-btn#") + f:SetFrameRef(format("btn%d",idx), button:GetFrame()) +end + +function Bar:RemoveButton(button) + local idx = self.buttons[button] + if idx then + self:GetFrame():SetAttribute(format("frameref-btn%d",idx),nil) + self.buttons[button] = nil end end -function Bar:AddButton(idx, button) - -- store in a reverse-index array - self.buttons[button] = idx - self:GetButtonFrame():SetAttribute("addchild",button:GetFrame()) - SecureStateHeader_Refresh(self:GetFrame()) -end - -function Bar:RemoveButton(button) - self.buttons[button] = nil -end - -function Bar:IterateButtons() -- iterator returns button, idx +-- iterator returns button, idx and does NOT iterate in index order +function Bar:IterateButtons() return pairs(self.buttons) end function Bar:PlaceButton(button, baseW, baseH) local idx = self.buttons[button] - if not idx then return end - local r, c, s = self:GetButtonGrid() - local bh, bw = self:GetButtonSize() - local row, col = floor((idx-1)/c), fmod((idx-1),c) -- zero-based - local x, y = col*bw + (col+0.5)*s, row*bh + (row+0.5)*s - local scale = bw/baseW - local f = button:GetFrame() + if idx then + local r, c, s = self:GetButtonGrid() + local bh, bw = self:GetButtonSize() + local row, col = floor((idx-1)/c), fmod((idx-1),c) -- zero-based + local x, y = col*bw + (col+0.5)*s, -(row*bh + (row+0.5)*s) + local scale = bw/baseW + local b = button:GetFrame() - f:ClearAllPoints() - f:SetPoint("TOPLEFT",x/scale,-y/scale) - f:SetScale(scale) -end - --- Creates (or updates) a named binding which binds a key press to a call to SetAttribute() --- pass a nil key to unbind -function Bar:SetAttributeBinding( name, key, attribute, value ) - if not name then - error("usage - Bar:SetAttributeBinding(name [, key, attribute, value]") - end - local f = self:GetFrame() - - -- clear the old binding, if any - if self.keybinds[name] then - SetOverrideBinding(f, false, self.keybinds[name], nil) - end - if key then - f:SetAttribute(format("attribute-name-%s",name), attribute) - f:SetAttribute(format("attribute-value-%s",name), value) - SetOverrideBindingClick(f, false, key, f:GetName(), name) -- binding name is the virtual mouse button - end - self.keybinds[name] = key -end - --- Sets up a state driver 'name' for the bar, using the provided 'rule' --- Also sets attributes 'statemap--'= for each entry in the passed map --- if 'rule' is nil or an empty string, the driver is unregistered. -function Bar:SetStateDriver( name, rule, map ) - local f = self:GetFrame() - if rule and #rule > 0 then - if map then - for key, value in pairs(map) do - f:SetAttribute( format("statemap-%s-%s",name,key), value ) - end - end - RegisterStateDriver(f, name, rule) - self.statedrivers[name] = true - elseif self.statedrivers[name] then - UnregisterStateDriver(f, name) - self.statedrivers[name] = nil + b:ClearAllPoints() + b:SetPoint("TOPLEFT",x/scale,y/scale) + b:SetScale(scale) end end --- Set an attribute on the frame (or each button if 'doButtons' = true) --- Either or both 'map' and 'default' can be passed: --- - If 'map' is omitted, then 'default' is set to the attribute. --- - If 'map' is provided, then it is interpreted as an unordered --- table of the form { ["statename"] = ["value"] }, and will be --- converted into a SecureStateHeaderTemplate style state-parsed --- string, e.g. ":;:". If 'default' --- is also provided, then its value will be converted to a string --- and appended. -function Bar:SetStateAttribute( attribute, map, default, doButtons ) - local value = default - if map then - local tmp = { } - for state, value in pairs(map) do - table.insert(tmp, format("%s:%s",tostring(state),tostring(value))) - end - if default then - table.insert(tmp, tostring(default)) - end - value = table.concat(tmp,";") - end - if doButtons then - for b in self:IterateButtons() do - b:GetFrame():SetAttribute(attribute,value) - end - else - self:GetFrame():SetAttribute(attribute, value) - end - SecureStateHeader_Refresh(self:GetFrame()) -end diff -r 491a6ffe7260 -r 7cabc8ac6c16 Overlay.lua --- a/Overlay.lua Mon Oct 13 23:32:33 2008 +0000 +++ b/Overlay.lua Wed Oct 15 16:29:41 2008 +0000 @@ -1,96 +1,36 @@ -local ReAction = ReAction -local L = ReAction.L -local CreateFrame = CreateFrame +local ReAction = ReAction +local L = ReAction.L +local CreateFrame = CreateFrame local InCombatLockdown = InCombatLockdown -local floor = math.floor -local min = math.min -local format = string.format -local GameTooltip = GameTooltip +local floor = math.floor +local min = math.min +local format = string.format +local GameTooltip = GameTooltip +local Bar = ReAction.Bar +local GetSize = Bar.GetSize +local GetButtonSize = Bar.GetButtonSize +local GetButtonGrid = Bar.GetButtonGrid +local SetSize = Bar.SetSize +local SetAnchor = Bar.SetAnchor +local SetButtonSize = Bar.SetButtonSize +local SetButtonGrid = Bar.SetButtonGrid +local ApplyAnchor = Bar.ApplyAnchor ReAction:UpdateRevision("$Revision$") --- Looking for a lightweight AceConfig3-struct-compatible --- replacement for Dewdrop (e.g. forthcoming AceConfigDropdown-3.0?). --- Considering Blizzard's EasyMenu/UIDropDownMenu, but that's --- a bit tricky to convert from AceConfig3-struct -local Dewdrop = AceLibrary("Dewdrop-2.0") - -local function OpenMenu (frame, opts) - Dewdrop:Open(frame, "children", opts, "cursorX", true, "cursorY", true) -end - -local function CloseMenu(frame) - if Dewdrop:GetOpenedParent() == frame then - Dewdrop:Close() - end -end - -local function ShowMenu(bar) - if not bar.menuOpts then - bar.menuOpts = { - type = "group", - args = { - openConfig = { - type = "execute", - name = L["Settings..."], - desc = L["Open the editor for this bar"], - func = function() CloseMenu(bar.controlFrame); ReAction:ShowEditor(bar) end, - disabled = InCombatLockdown, - order = 1 - }, - delete = { - type = "execute", - name = L["Delete Bar"], - desc = L["Remove the bar from the current profile"], - confirm = L["Are you sure you want to remove this bar?"], - func = function() ReAction:EraseBar(bar) end, - order = 2 - }, - } - } - end - OpenMenu(bar.controlFrame, bar.menuOpts) -end - - -- -- Bar config overlay -- --- localize some of these for small OnUpdate performance boost -local Bar = ReAction.Bar -local GetSize = Bar.GetSize -local GetButtonSize = Bar.GetButtonSize -local GetButtonGrid = Bar.GetButtonGrid -local SetSize = Bar.SetSize -local SetButtonSize = Bar.SetButtonSize -local SetButtonGrid = Bar.SetButtonGrid -local ApplyAnchor = Bar.ApplyAnchor + +local function StoreSize(bar) + local f = bar:GetFrame() + SetSize( bar, f:GetWidth(), f:GetHeight() ) +end local function StoreExtents(bar) local f = bar:GetFrame() - local point, relativeTo, relativePoint, x, y = f:GetPoint(1) - relativeTo = relativeTo or f:GetParent() - local anchorTo - for name, b in ReAction:IterateBars() do - if b and b:GetFrame() == relativeTo then - anchorTo = name - break - end - end - anchorTo = anchorTo or relativeTo:GetName() - local c = bar.config - c.anchor = point - c.anchorTo = anchorTo - c.relativePoint = relativePoint - c.x = x - c.y = y - c.width, c.height = f:GetWidth(), f:GetHeight() -end - -local function StoreSize(bar) - local f = bar:GetFrame() - local c = bar.config - c.width, c.height = f:GetWidth(), f:GetHeight() + SetAnchor( bar, f:GetPoint(1) ) + SetSize( bar, f:GetWidth(), f:GetHeight() ) end local function RecomputeButtonSize(bar) @@ -449,7 +389,7 @@ label:SetTextColor(1,1,1,1) label:SetText(bar:GetName()) label:Show() - bar.controlLabelString = label -- so that bar:SetName() can update it + bar.controlLabelString = label -- so that bar:SetLabel() can update it local function StopResize() f:StopMovingOrSizing() @@ -457,7 +397,7 @@ f:SetScript("OnUpdate",nil) StoreSize(bar) ClampToButtons(bar) - ApplyAnchor(bar) + --ApplyAnchor(bar) ReAction:RefreshOptions() end @@ -648,15 +588,17 @@ control:SetScript("OnClick", function() - ShowMenu(bar) + ReAction:ShowEditor(bar) end ) + control:Raise() + return control end --- export the ShowControls method to the Bar prototype +-- export methods to the Bar prototype function Bar:ShowControls(show) if show then @@ -666,9 +608,12 @@ self.controlFrame:Show() self.controlFrame:Raise() elseif self.controlFrame then - CloseMenu(self.controlFrame) self.controlFrame:Hide() end end - +function Bar:SetLabel(name) + if self.controlLabelString then + self.controlLabelString:SetText(self.name) + end +end diff -r 491a6ffe7260 -r 7cabc8ac6c16 README.html --- a/README.html Mon Oct 13 23:32:33 2008 +0000 +++ b/README.html Wed Oct 15 16:29:41 2008 +0000 @@ -10,7 +10,6 @@ Welcome to ReAction
Credits
Features
-Non-Features
Using ReAction
License
@@ -19,7 +18,7 @@

Welcome to ReAction

ReAction is a replacement for the various action bars provided by the default Blizzard UI. With ReAction, you can group your action buttons into bars however you want, and have as many bars as you want. Those bars can then have a number of dynamic behaviors assigned to them. ReAction provides an intuitive mouse-driven interface for arranging and resizing bars, and a dialog interface for the majority of the configuration options.

-

There are many action bar addons out there. I suggest you try several and choose the one that works best for you. Other popular action bar addons include:

+

ReAction is not an install-and-forget addon that magically makes your action bars look spiffy and work better. It's a tool, designed to help you build a UI to your liking. It's not exactly geared toward novice AddOn users, but it's pretty straightforward to use, so don't be scared off if this is your first AddOn. However, there are many action bar addons out there. I suggest you try several and choose the one that works best for you. Other popular action bar addons include:

  • Bartender
  • Flexbar
  • @@ -28,6 +27,7 @@
  • InfiniBar

Note that action bar addons are by nature generally incompatible with each other. Only enable one at a time! Most can currently be found on WowAce, WoWInterface, and/or Curse Gaming.

+

ReAction is compatible with WoW 3.0.

Credits

The WoW AddOn authoring community is rich and diverse. Virtually every successful addon author learns by reading, dissecting, and even pilfering (:-P) others' code. ReAction owes its existence to the following (in no particular order):

@@ -44,7 +44,6 @@
  • jjsheets (InfiniBar and ButtonFacade)
  • <The Kids Are In Bed> of US-Staghelm, my everynight crew and alpha testers
  • -

    ReAction embeds components of the Ace3 framework.

    Features of ReAction

    @@ -52,66 +51,104 @@
  • Unlimited number of bars
  • Unlimited number of buttons per bar
  • Arrange and resize bars, buttons, and grid layout with the mouse
  • -
  • Keybinding assignments (left and right click) for each button
  • -
  • Anchor bars to one another and to other UI elements
  • -
  • Button support for actions, pet actions, micro menu, bags, stances, forms, stealth, shadowform, totems, auras, and aspects, as well as specialized buttons for macros, spells, and items
  • +
  • Anchor bars to one another
  • +
  • Support for actions and pet actions
  • Paged actionbar and possess-target (e.g. Mind Control) support
  • -
  • Dynamic bar effects, such as page transitions, show/hide, expand/collapse, etc, as allowed by the Blizzard secure API
  • +
  • Dynamic bar effects, such as page transitions, show/hide, reposition, etc
  • +
  • Modular extensible architecture
  • + +

    The design goal of ReAction is to be user-friendly and feature-efficient. Useful features are incorporated: "fluff" is culled out. Many behaviors have been inspired by other addons. It is a work in progress.

    + +

    Planned Features of Reaction

    +

    The following are planned upcoming features:

    +
      +
    • Button support for micro menu and bag bars
    • +
    • Button support with intelligent populating for forms, stealth, shadowform, totems, auras, presences, and aspects
    • +
    • Non action-slot buttons which avoid the 120-action limitation, as an option
    • +
    • Additional dynamic behaviors, including mouseover pop-up bars, stateful clicks, etc
    • +
    • Manual page-switching controls for paged bars
    • CyCircled and ButtonFacade compatibility (install CyCircled and/or ButtonFacade separately)
    • -
    • Modular extensible architecture
    • Optional FuBar configuration plugin
    • +
    • More features for bar positioning and anchoring
    -

    The design goal of ReAction is to be user-friendly and feature-efficient. Useful features are incorporated: "fluff" is culled out. Many behaviors have been inspired by other addons.

    - -

    NON-Features of ReAction

    +

    NON-Features of ReAction

    • There is no method of inserting custom user script hooks to button events. If you want to do that sort of thing you'll have to hack the code or write your own module.
    • ReAction does not provide slash commands for most of its interface.
    • -
    • ReAction does not integrate with Titan Panel.
    • +
    • I'm not going to write a Titan panel plugin.
    • There are no auto-layout options other than a grid layout.
    • -
    • ReAction button keybindings cannot be configured via the standard Blizzard keybinding UI.
    • +
    • If you hide the Blizzard main action bar, ReAction doesn't provide any replacement for the experience bar or "lag meter".
    • +
    • No support for bar borders, backgrounds, or other textures.
    • +
    • ReAction button keybindings are not configured via the standard Blizzard keybinding UI, and probably never will be.
    • +
    • No plans to allow you to move around the default Blizzard buttons. ReAction makes and manages its own buttons, it doesn't swipe them from the default UI.
    -

    None of these limitations are fundamental (except perhaps the keybindings). With the extensible architecture, an enterprising mod author could certainly write an addon module that extends ReAction's capabilities.

    +

    Some of these (lack of an XP bar, for instance) are better served by other unrelated addons, as they're not action-button related. Others could be implemented by someone else as extensions.

    Using ReAction

    Installation

    ReAction is distributed as a ZIP file: like all addons, just extract the archive contents into World of Warcraft/Interface/Addons. This will create a new folder called 'ReAction' in the Addons folder. Restart WoW, check that the AddOn is enabled in the character-select screen, and away you go.

    A common mistake is to extract the contents into a new folder named ReAction (thereby creating a doubly nested Addons/ReAction/ReAction), or to rename the ReAction folder. Make sure that the path to this README file is World of Warcraft/Interface/Addons/ReAction/README.html.

    +

    Modules

    +

    ReAction ships with several modules pre-installed. Each module is in fact a completely self-contained AddOn, and can be removed from the ReAction/modules/ folder and dragged up to the main AddOn level, if you like. This really isn't necessary, as the modules themselves don't consume many resources unless you're actually using their features, in which case they need to be installed anyway. But if you're into optimizing your personal AddOn footprint, you can promote the modules you aren't using. They're even marked Load-On-Demand, so if you decide to use their features later, they'll load automatically.

    +

    First Run

    -

    After ReAction is installed and enabled, and you've logged in to your WoW character, you probably won't notice anything different. If you have FuBar installed, the ReAction FuBar plugin will be visible on your FuBar. If not, you'll see a small ReAction button on your minimap.

    +

    After ReAction is installed and enabled, and you've logged in to your WoW character, you probably won't notice anything different. ReAction assumes nothing about how you want to use it: by default, it does nothing at all. It doesn't make any new bars, and doesn't do anything to the standard Blizzard bars.

    -

    ReAction assumes nothing about how you want to use it: by default, it does nothing at all. It doesn't make any new bars, and doesn't do anything to the standard Blizzard bars.

    +

    To start, type /rxn (or /reaction) in the chat window. This will bring up the general ReAction dialogue window. You can also get there by hitting ESC to bring up the game menu, choose Interface Options, select the Addons tab, then select ReAction from the list.

    -

    From here, you can either click the FuBar/minimap button, or type /rxn (or /reaction) in the chat window. This will bring up the general ReAction dialogue window. You can also get there by hitting ESC to bring up the game menu, choose Interface Options, select the Addons tab, then select ReAction from the list.

    +

    The general configuration window only has a few options in it. The most interesting one is 'Hide Blizzard Action Bars'. Check that option, and *poof* your main action bar (as well as the extra bars and pet bar) disappears. Un-check it, and *whoosh* they're back. ReAction exists alongside the main action bar: you can use it with or without Blizzard's default UI. Note, however, that the default Blizzard UI makes use of all 120 action slots (depending on character class): therefore if you leave the Blizzard UI up, you'll almost certainly have to custom map the action slots of ReAction's buttons. For your first test-drive, I recommend hiding the Blizzard UI to start with a blank slate.

    -

    Note: By default, ReAction stores all settings per-character. Each character, when logged in for the first time using ReAction, will start from a blank slate. Use the Profile options to copy settings from one character to another. You can also use shared profiles, but note that ReAction also stores the keybindings with the profile, so toons using shared profiles will have shared keybinds (but different actions) on their buttons.

    - -

    To get started, click the button to launch the Bar Editor.

    +

    To get started making some bars, click the button to launch the Bar Editor.

    Laying Out Your Bars

    +

    Note: By default, ReAction stores all settings per-character. Each character, when logged in for the first time using ReAction, will start from a blank slate. Use the Profile options to copy settings from one character to another. You can also use shared profiles, but note that ReAction also stores the keybindings with the profile, so toons using shared profiles will have shared keybinds (but different actions) on their buttons, even if you select 'per-character bindings' in the Blizzard UI.

    + +

    To create a bar, you'll need to open the ReAction Bar Editor dialogue, which is a separate window from the general configuration dialogue. To get there, either click the Edit Bars... button at the top of the main dialogue, or type /rxn edit. When the Bar Editor is open, you're in "config mode". You can drag the bars around the screen and resize them to your heart's content. This mode is automatically exited during combat. You can also toggle "config mode" without bringing up the Bar Editor dialog by typing /rxn config.

    + +

    Once in the Bar Editor, you can create bars, which are simply collections of buttons. Each bar has a number of buttons arranged in a grid, and must have a name. Set up these basic properties and create your bar.

    + +

    Now that you've got a bar, you can drag it around on screen to put it wherever you want. You can also change its grid layout, as follows:

    +
      +
    • Drag on a corner to change the button size.
    • +
    • Right-click drag on a corner to change the button spacing.
    • +
    • Drag an edge to add/remove buttons to that edge.
    • +
    + +

    You can also right-click on a bar to configure its properties via the Bar Editor. Although a bar will work perfectly fine out of the box, the Bar Editor allows you to custom tune its behaviors and appearance. + +

    Key Bindings

    +

    To bind keys to your buttons, either type /rxn keybind or click the Key Bindings button in the main configuration dialogue. This will open a small window and outline all your ReAction buttons in green. To bind a key, hover your mouse over a button and press the key, or Escape to unbind the key from the button. Click the 'Save' button to exit keybinding mode. Note: keybinding changes are saved instantly and cannot be undone - the 'Revert' button doesn't actually do anything (yet).

    Configuring Your Bars

    +

    General Configuration

    +

    On the bar editor, each bar has several tabs to configure it. General configuration (e.g. position), action-type specific configuration (# pages, etc), and dynamic behavior. The general config options are pretty self-explanatory.

    -

    Key Bindings

    +

    Action Buttons

    +

    Action buttons can be hidden when there's no action in that slot. They can also be re-mapped to the target's actions if you're currently Mind Controlling something. You can also configure the bar to have multiple 'pages', like the Blizzard UI's main bar. This won't actually have any effect unless you also specify dynamic page transitions, discussed in the next section. (manual page-switching controls coming later)

    -

    Dynamic Behavior

    +

    You can also specify the action-slot ID for each button, either individually or all at once via a string. WoW only gives you 120 action slots. Each button consumes one action slot per page, so if you have a 6-page 20-button bar, they're all used up. By default, ReAction allocates these action slots automatically so you don't have to worry about it. Just keep in mind that if you have more than 120 total slots in use, you'll get some repeats, which means that you'll have some buttons that mirror each other all the time. If you want to re-arrange the action slot ordering for whatever reason (for example, to keep the UIs on multiple computers in sync), you can do it in this panel. Or, if you want to have some intentional button mirrors (which would allow you to set up multiple keybindings for the same action, or show the same action in multiple pages or locations, without consuming multiple action slots), you can set it up that way too.

    + +

    Pet Action Buttons

    +

    You can create a pet bar, which automatically shows itself when your pet is out. You can even create multiple pet bars, if you want, but there's not much motivation to do that. Pet buttons do not support multiple pages, and behave pretty much exactly like Blizzard's pet bar.

    + +

    Dynamic Behavior

    Dynamic bar behavior is a complex topic. At its most basic, you can have a bar switch between pages if you change stances/forms/stealth, just like the Blizzard UI does. For the more advanced user, multiple states can be created and transitions defined based on the available events as dictated by Blizzard's secure button API.

    To set up dynamic behavior for your bars, open the ReAction Bar Editor (either by shift-clicking the FuBar/minimap button, or from the Blizzard Interface/AddOns dialogue, or by typing /rxn edit in chat) and select the bar to work on. On the right panel, you'll see several tabs, one of which is labeled 'Dynamic State'. Choose that and you'll get a sub-panel with a state menu and more tabs.

    -

    Example: The simplest example is a bar that switches between three pages depending on a Warrior's stance. To set this up, create an action bar, give it three pages (see Configuring Your Bars), and then create three states for it. You can name those three states whatever you want, but for simplicity's sake, let's name them Battle, Defensive, and Berserker. Choose each state and select a page number (1,2, or 3) for it to display in the Properties tab. Then select the Rules tab, choose "when ALL of these" (or "when ANY of these"), and check the corresponding Battle/Defensive/Bersker checkbox for that state. NOTE: you can only do this on an actual Warrior character - other classes won't see the stance checkboxes, as they don't apply. That's it, you're done: the bar will now auto-switch between three pages when you change stances. Try it, and watch the actions on that bar change.

    +

    Example: The simplest example is a bar that switches between three pages depending on a Warrior's stance. To set this up, create an action bar, give it three pages (see Configuring Your Bars), and then create three states for it. You can name those three states whatever you want, but for simplicity's sake, let's name them Battle, Defensive, and Berserker. Choose each state and select a page number (1,2, or 3) for it to display in the Properties tab. Then select the Rules tab, choose "when ALL of these" (or "when ANY of these"), and check the corresponding Battle/Defensive/Bersker checkbox for that state. NOTE: you can only do this on an actual Warrior character - other classes won't see the stance checkboxes, as they don't apply, but you can do something similar for many classes). That's it, you're done: the bar will now auto-switch between three pages when you change stances. Try it, and watch the actions on that bar change (assuming, of course, that you've dropped abilities into those slots).

    Create states for your bar, and explore the various options of state properties and selection rules. State selection is evaluated in the order that the states are listed in the menu (top to bottom), so you may need to re-arrange them to get just the behavior that you want.

    -

    How it works: Every time the game registers an event of certain types (modifier key, stance/form/stealth change, target/focus change, party member change), it processes the list of states looking for a match, according to the selection rule. The first match becomes the new state immediately, and the state's properties are applied to the bar. The game also polls certain other conditions (anything that a macro can detect, see below) 5 times per second. So some state transitions will always be a tiny bit sluggish due to the architecture of the game.

    +

    How it works: Every time the game registers an event of certain types (modifier key, stance/form/stealth change, target/focus change, party member change), it processes the list of states looking for a match, according to the selection rule. The first match becomes the new state immediately, and the state's properties are applied to the bar. The game also polls certain other conditions (anything that a macro can detect, see below) 5 times per second. So some oddball state transitions will always be a tiny bit sluggish due to the architecture of the game.

    Remember: the more complicated you make your state-selectors, the more likely it is that you'll create a situation you didn't necessarily expect!

    Default State: If you select one of your rules to be a default state in the Rules tab, then if none of the other states are true, it will be the state. If you don't have a default state, then if no rule is true, the bar will just stay in whatever condition it's already in. If you specify multiple states as default states, the highest-ranking one will be used.

    -

    Custom Selection Rules: Only the most common selection rules are included in the 'any' and 'all' checkbox listing. However, you can do almost anything that the game will allow you to via custom rules. Custom selection rules work exactly like a macro options clauses (in fact, they get passed to the same function, SecureOptionsCmdParse()). The following are all valid examples of what you can put in the Custom textbox:

    +

    Custom Selection Rules: Only the most common selection rules are included in the 'any' and 'all' checkbox listing. However, you can do almost anything that the game will allow you to via custom rules. Custom selection rules work exactly like a macro options clauses (in fact, they get passed to the same API function, SecureOptionsCmdParse()). The following are all valid examples of what you can put in the Custom textbox:

    • [harm]
    • [mod:alt,help][notarget]
    • @@ -122,11 +159,9 @@

      Keybound States: If you select 'via Keybinding' from the Selection Rule drop-down, then you can define a hotkey which will toggle the state on/off. When toggled on, the bar will remain in that state and ignore all other state transition events until you toggle it off. All keybound states default to 'off' when the UI reloads. It is possible to define multiple keybound states for a single bar. In that case, the keybound states will override each other.

      -

      Modules

      -

      ReAction ships with several modules pre-installed. Each module is in fact a completely self-contained AddOn, and can be removed from the ReAction/modules/ folder and dragged up to the main AddOn level, if you like. This really isn't necessary, as the modules themselves don't consume many resources unless you're actually using their features, in which case they need to be installed anyway. But if you're into optimizing your personal AddOn footprint, you can promote the modules you aren't using. They're even marked Load-On-Demand, so if you decide to use their features later, they'll load automatically.

      +

      License

      +

      ReAction is licenced under a variant of the MIT software licence, and is essentially freeware. See the LICENSE.txt file in this folder.

      +

      ReAction embeds components of the Ace frameworks, which are covered under their own licenses.

      -

      One module, the FuBar_ReActionFu plugin module, is instead installed by default as a peer-level folder to the main ReAction folder in the AddOns folder. If you don't like this sort of clutter in your AddOns folder, you can move it to the ReAction/modules folder. By default it's packaged separately because it's the one component that's fairly heavyweight relative to its utility, especially if you don't have FuBar installed.

      - -

      License

      diff -r 491a6ffe7260 -r 7cabc8ac6c16 ReAction.lua --- a/ReAction.lua Mon Oct 13 23:32:33 2008 +0000 +++ b/ReAction.lua Wed Oct 15 16:29:41 2008 +0000 @@ -7,10 +7,10 @@ - options - bar-type constructors - and publishes events when those collections change. It also implements a single property, 'config mode', + and publishes events when those collections change. It also implements a couple properties and has a couple convenience methods which drill down to particular modules. - Most of the "real work" of the addon happens in Bar.lua and the various modules. + Most of the "real work" of the addon happens in Bar.lua, Overlay.lua, State.lua, and the various modules. Events (with handler arguments): -------------------------------- @@ -226,7 +226,7 @@ defaultBar = { } } } - -- default profile is character-specific + -- initial profile is character-specific ) self.db.RegisterCallback(self,"OnProfileChanged") self.db.RegisterCallback(self,"OnProfileReset","OnProfileChanged") @@ -347,6 +347,7 @@ return bars[name] end +-- returns pairs of name, bar function ReAction:IterateBars() return pairs(bars) end @@ -459,7 +460,9 @@ success, r = pcall(func, bar) end if success then - opts[module:GetName()] = { [module:GetName()] = r } + if r then + opts[module:GetName()] = { [module:GetName()] = r } + end else geterrorhandler()(r) end diff -r 491a6ffe7260 -r 7cabc8ac6c16 ReAction.toc --- a/ReAction.toc Mon Oct 13 23:32:33 2008 +0000 +++ b/ReAction.toc Wed Oct 15 16:29:41 2008 +0000 @@ -1,4 +1,4 @@ -## Interface: 20400 +## Interface: 30000 ## Title: ReAction ## Notes: Action button layout and configuration ## DefaultState: enabled diff -r 491a6ffe7260 -r 7cabc8ac6c16 State.lua --- a/State.lua Mon Oct 13 23:32:33 2008 +0000 +++ b/State.lua Wed Oct 15 16:29:41 2008 +0000 @@ -7,8 +7,9 @@ local ReAction = ReAction local L = ReAction.L local _G = _G +local format = string.format local InCombatLockdown = InCombatLockdown -local format = string.format +local RegisterStateDriver = RegisterStateDriver ReAction:UpdateRevision("$Revision$") @@ -53,10 +54,205 @@ end -local InitRules, ApplyStates, SetProperty, GetProperty, RegisterProperty +local InitRules, ApplyStates, CleanupStates, SetProperty, GetProperty, RegisterProperty, ShowAll -- PRIVATE -- do + + -- the field names must match the field names of the options table, below + -- the field values are secure snippets + local properties = { + hide = + [[ + local h = hide and hide[state] and not showAll + if h ~= hidden then + if h then + self:Hide() + else + self:Show() + end + hidden = h + end + ]], + + --keybindState TODO: broken + + -- the anchoring is handled in a special handler + anchorEnable = true, + --anchorFrame = true, TODO: broken + anchorPoint = true, + anchorRelPoint = true, + anchorX = true, + anchorY = true, + enableScale = true, + scale = true, + } + + + -- + -- Secure Handler Snippets + -- + local SetHandlerData, SetStateDriver, SetStateKeybind, RefreshState + do + local stateHandler_propInit = + [[ + propfuncs = table.new() + local proplist = self:GetAttribute("prop-func-list") + for s in string.gmatch(proplist, "(%w+)") do + table.insert(propfuncs, s) + end + ]] + + local onStateHandler = + -- function _onstate-reaction( self, stateid, newstate ) + [[ + print("received state",newstate,"on bar",self:GetName()) + set_state = newstate or set_state + + local oldState = state + state = state_override or set_state or state + + for i = 1, #propfuncs do + print("running state func",propfuncs[i]) + control:RunAttribute("func-"..propfuncs[i]) + end + + if anchorEnable and anchorEnable[state] ~= anchorstate then + anchorstate = anchorEnable[state] + control:RunAttribute("func-doanchor") + end + + control:ChildUpdate() + ]] + + local anchorHandler = + -- function func-doanchor( self ) + [[ + -- TODO + if anchorstate then + -- TODO: get anchor data from state tables + else + -- TODO: get anchor data from defaults + end + ]] + + local onClickHandler = + -- function OnClick( self, button, down ) + [[ + if state_override == button then + state_override = nil -- toggle + else + state_override = button + end + ]] .. onStateHandler + + local weak = { __mode = "k" } + local statedrivers = setmetatable( { }, weak ) + local keybinds = setmetatable( { }, weak ) + + -- Construct a lua assignment as a code string and execute it within the header + -- frame's sandbox. 'value' must be a string, boolean, number, or nil. If called + -- with four arguments, then it treats 'varname' as an existing global table and + -- sets a key-value pair. For a slight efficiency boost, pass the values in as + -- attributes and fetch them as attributes from the snippet code, to leverage snippet + -- caching. + function SetHandlerData( bar, varname, value, key ) + local f = bar:GetFrame() + f:SetAttribute("data-varname",varname) + f:SetAttribute("data-value", value) + f:SetAttribute("data-key", key) + f:Execute( + [[ + local name = self:GetAttribute("data-varname") + local value = self:GetAttribute("data-value") + local key = self:GetAttribute("data-key") + if name then + if key then + if not _G[name] then + _G[name] = table.new() + end + _G[name][key] = value + else + _G[name] = value + end + end + ]]) + end + + function SetDefaultAnchor( bar ) + local point, frame, relPoint, x, y = bar:GetAnchor() + SetHandlerData(bar, "defaultAnchor", point, "point") + SetHandlerData(bar, "defaultAnchor", relPoint, "relPoint") + SetHandlerData(bar, "defaultAnchor", x, "x") + SetHandlerData(bar, "defaultAnchor", y, "y") + + if frame then + local f = bar:GetFrame() + f:SetFrameRef("defaultAnchor", f) + f:Execute( + [[ + defaultAnchor.frame = self:GetAttribute("frameref-defaultAnchor") + ]]) + end + end + + function RefreshState( bar ) + SetDefaultAnchor(bar) + bar:GetFrame():Execute([[control:RunAttribute("reaction-refresh")]]) + end + + function SetStateDriver( bar, rule ) + local f = bar:GetFrame() + + local props = { } + for p, h in pairs(properties) do + if type(h) == "string" then + table.insert(props,p) + f:SetAttribute("func-"..p, h) + end + end + f:SetAttribute("prop-func-list", table.concat(props," ")) + f:Execute(stateHandler_propInit) + f:SetAttribute("reaction-refresh", onStateHandler) + f:SetAttribute("func-doanchor", anchorHandler) + + if rule and #rule > 0 then + f:SetAttribute( "_onstate-reaction", onStateHandler ) + RegisterStateDriver(f, "reaction", rule) + statedrivers[bar] = rule + elseif statedrivers[bar] then + UnregisterStateDriver(f, "reaction") + f:SetAttribute( "_onstate-reaction", nil ) + statedrivers[bar] = nil + end + end + + function SetStateKeybind( bar, key, state ) + local f = bar:GetFrame() + + local kb = keybinds[bar] + if kb == nil then + if key == nil then + -- nothing to do + return + end + kb = { } + keybinds[bar] = kb + end + + -- clear the old binding, if any + if kb[state] then + SetOverrideBinding(f, false, kb[state], nil) + end + kb[state] = key + + if key then + f:SetAttribute("_onclick", onClickHandler) + SetOverrideBindingClick(f, false, key, state, nil) -- state name is the virtual mouse button + end + end + end + -- As far as I can tell the macro clauses are NOT locale-specific. local ruleformats = { stealth = "stealth", @@ -113,146 +309,10 @@ ruleformats.moonkin = format("form:%d",moonkin) end - - -- state property functions - local ofskeys = { - anchorPoint = "point", - anchorRelPoint = "relpoint", - anchorX = "x", - anchorY = "y" - } - - local barofsidx = { - anchorPoint = 1, - anchorRelPoint = 3, - anchorX = 4, - anchorY = 5 - } - - local function UpdatePartialAnchor(bar, states, ckey) - local map = { } - local bc = bar.config - for state, c in pairs(states) do - if c.enableAnchor then - map[state] = c[ckey] - end - end - local ofskey = ofskeys[ckey] - local default = select(barofsidx[ckey], bar:GetAnchor()) - bar:SetStateAttribute(format("headofs%s",ofskeys[ckey]), map, default) - end - - -- the table key name for each function maps to the name of the config element - local propertyFuncs = { - hide = function( bar, states ) - local hs = { } - for state, config in pairs(states) do - if config.hide then - table.insert(hs, state) - end - end - bar:GetButtonFrame():SetAttribute("hidestates", table.concat(hs,",")) - end, - - keybindstate = function( bar, states ) - local map = { } - for state, config in pairs(states) do - local kbset = config.keybindstate and state - map[state] = kbset - for button in bar:IterateButtons() do - -- TODO: inform children they should maintain multiple binding sets - -- ?? button:UpdateBindingSet(kbset) - end - end - bar:SetStateAttribute("statebindings", map, true) -- apply to button frame, bindings only work for direct children - end, - - enableAnchor = function( bar, states ) - for ckey in pairs(ofskeys) do - UpdatePartialAnchor(bar, states, ckey) - end - end, - - enableScale = function( bar, states ) - local map = { } - for state, c in pairs(states) do - if c.enableScale then - map[state] = c.scale - end - end - bar:SetStateAttribute("headscale", map, 1.0) - end, - } - - -- generate some table entries - propertyFuncs.scale = propertyFuncs.enableScale - for ckey in pairs(ofskeys) do - propertyFuncs[ckey] = function( bar, states ) - UpdatePartialAnchor(bar, states, ckey) - end - end - - - function GetProperty( bar, state, propname ) - return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname) - end - - function SetProperty( bar, state, propname, value ) - local states = tbuild(module.db.profile.bars, bar:GetName(), "states") - tbuild(states, state)[propname] = value - local f = propertyFuncs[propname] - if f then - f(bar, states) - end - end - - function RegisterProperty( propname, f ) - propertyFuncs[propname] = f - for bar in ReAction:IterateBars() do - local states = tfetch(module.db.profile.bars, bar:GetName(), "states") - if states then - f(bar,states) - end - end - end - - - - -- - -- Build a state-transition spec string and statemap to be passed to - -- Bar:SetStateDriver(). - -- - -- The statemap building is complex: keybound states override all - -- other transitions, so must remain in their current state, but must - -- also remember other transitions that happen while they're stuck there - -- so that when the binding is toggled off it can return to the proper state - -- - local function BuildStateMap(states) + local function BuildRule(states) local rules = { } - local statemap = { } - local keybinds = { } local default - -- first grab all the keybind override states - -- and construct an override template - local override - do - local overrides = { } - for name, state in pairs(states) do - local type = tfetch(state, "rule", "type") - if type == "keybind" then - -- use the state-stack to remember the current transition - -- use $s as a marker for a later call to gsub() - table.insert(overrides, format("%s:$s set() %s", name, name)) - end - end - if #overrides > 0 then - table.insert(overrides, "") -- for a trailing ';' - end - override = table.concat(overrides, ";") or "" - end - - -- now iterate the rules in order for idx, state in ipairs(fieldsort(states, "rule", "order")) do local c = states[state].rule local type = c.type @@ -284,43 +344,83 @@ end end end - - -- use a different virtual button for the actual keybind transition, - -- to implement a toggle. You have to clear it regardless of the type - -- (which is usually a no-op) to unbind state transitions when switching - -- transition types. - local bindbutton = format("%s_binding",state) - if type == "keybind" then - keybinds[bindbutton] = c.keybind or false - statemap[bindbutton] = format("%s:pop();*:set(%s)", state, state) - else - keybinds[bindbutton] = false - end - - -- construct the statemap. gsub() the state name into the override template. - statemap[state] = format("%s%s", override:gsub("%$s",state), state) end -- make sure that the default, if any, is last if default then table.insert(rules, default) end - return table.concat(rules,";"), statemap, keybinds + return table.concat(rules,";") + end + + local function BuildKeybinds( bar, states ) + for name, state in pairs(states) do + local type = tfetch(state, "rule", "type") + if type == "keybind" then + local key = tfetch(state, "rule", "keybind") + SetStateKeybind(bar, key, name) + else + SetStateKeybind(bar, nil, name) -- this clears an existing keybind + end + end + end + + function GetProperty( bar, state, propname ) + return tfetch(module.db.profile.bars, bar:GetName(), "states", state, propname) + end + + function SetProperty( bar, state, propname, value ) + local s = tbuild(module.db.profile.bars, bar:GetName(), "states", state) + s[propname] = value + SetHandlerData(bar, propname, value, state) + RefreshState(bar) + end + + function RegisterProperty( propname, snippet ) + properties[propname] = snippet or true + print("registered property",propname) + for _, bar in ReAction:IterateBars() do + local states = tfetch(module.db.profile.bars, bar:GetName(), "states") + if states then + for name, s in pairs(states) do + SetHandlerData(bar, propname, s[propname], name) + end + SetStateDriver(bar, BuildRule(states)) + RefreshState(bar) + end + end + end + + function UnregisterProperty( propname ) + properties[propname] = nil + for _, bar in ReAction:IterateBars() do + SetHandlerData(bar, propname, nil) + SetStateDriver(bar, BuildRule(states)) + RefreshState(bar) + end end function ApplyStates( bar ) local states = tfetch(module.db.profile.bars, bar:GetName(), "states") if states then - local rule, statemap, keybinds = BuildStateMap(states) - bar:SetStateDriver("reaction", rule, statemap) - for state, key in pairs(keybinds) do - bar:SetAttributeBinding(state, key, "state-reaction", state) + for propname in pairs(properties) do + for name, s in pairs(states) do + SetHandlerData(bar, propname, s[propname], name) + end end - for k, f in pairs(propertyFuncs) do - f(bar, states) - end + BuildKeybinds(bar, states) + SetStateDriver(bar, BuildRule(states)) + RefreshState(bar) end end + function CleanupStates( bar ) + SetStateDriver(bar, nil) + end + + function ShowAll( bar, show ) + SetHandlerData(bar, "showAll", show) + RefreshState(bar) + end end @@ -342,6 +442,7 @@ ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") ReAction.RegisterCallback(self, "OnCreateBar","OnRefreshBar") + ReAction.RegisterCallback(self, "OnDestroyBar") ReAction.RegisterCallback(self, "OnRefreshBar") ReAction.RegisterCallback(self, "OnEraseBar") ReAction.RegisterCallback(self, "OnRenameBar") @@ -351,7 +452,7 @@ function module:PLAYER_AURAS_CHANGED() self:UnregisterEvent("PLAYER_AURAS_CHANGED") -- on login the number of stances is 0 until this event fires during the init sequence. - -- however if you reload just the UI the number of stances is correct immediately + -- however if you just reload the UI the number of stances is correct immediately -- and this event won't fire until you gain/lose buffs/debuffs, at which point you might -- be in combat. if not InCombatLockdown() then @@ -369,6 +470,10 @@ end end +function module:OnDestroyBar(event, bar, name) + CleanupStates(bar) +end + function module:OnEraseBar(event, bar, name) self.db.profile.bars[name] = nil end @@ -379,7 +484,11 @@ end function module:OnConfigModeChanged(event, mode) - -- nothing to do (yet) + for name, bar in ReAction:IterateBars() do + if self.db.profile.bars[name] then + ShowAll(bar, mode) + end + end end @@ -505,30 +614,40 @@ set = "SetProp", get = "GetProp", }, - keybindstate = { + --[[ BROKEN + keybindState = { name = L["Override Keybinds"], desc = L["Set this state to maintain its own set of keybinds which override the defaults when active"], order = 91, type = "toggle", set = "SetProp", get = "GetProp", - }, + }, ]] position = { name = L["Position"], order = 92, type = "group", inline = true, args = { - enableAnchor = { + anchorEnable = { name = L["Set New Position"], order = 1, type = "toggle", set = "SetProp", get = "GetProp", }, + --[[ TODO: broken + anchorFrame = { + name = L["Anchor Frame"], + order = 2, + type = "select", + values = "GetAnchorFrames", + set = ??? + get = ??? + }, ]] anchorPoint = { name = L["Point"], - order = 2, + order = 3, type = "select", values = pointTable, set = "SetAnchorPointProp", @@ -538,7 +657,7 @@ }, anchorRelPoint = { name = L["Relative Point"], - order = 3, + order = 4, type = "select", values = pointTable, set = "SetAnchorPointProp", @@ -548,7 +667,7 @@ }, anchorX = { name = L["X Offset"], - order = 4, + order = 5, type = "range", min = -100, max = 100, @@ -560,7 +679,7 @@ }, anchorY = { name = L["Y Offset"], - order = 5, + order = 6, type = "range", min = -100, max = 100, @@ -680,10 +799,35 @@ }, } - local StateHandler = { } + local handlers = { } + local meta = { + __index = function(self, key) + for _, h in pairs(handlers) do + if h[key] then + return h[key] + end + end + end, + } + local StateHandler = setmetatable({ }, meta) + local proto = { __index = StateHandler } + + function RegisterPropertyOptions( field, options, handler ) + stateOptions.properties.plugins[field] = options + handlers[field] = handler + end + + function UnregisterPropertyOptions( field ) + stateOptions.properties.plugins[field] = nil + handlers[field] = nil + end function StateHandler:New( bar, opts ) - local self = setmetatable({ bar = bar }, { __index = StateHandler }) + local self = setmetatable( + { + bar = bar + }, + proto ) function self:GetName() return opts.name @@ -700,10 +844,9 @@ -- get reference to states table: even if the bar -- name changes the states table ref won't self.states = tbuild(module.db.profile.bars, bar:GetName(), "states") + self.state = tbuild(self.states, opts.name) - tbuild(self.states, opts.name) - - opts.order = self:GetRule("order") + opts.order = self:GetRuleField("order") if opts.order == nil then -- add after the highest opts.order = 100 @@ -713,7 +856,7 @@ opts.order = x + 1 end end - self:SetRule("order",opts.order) + self:SetRuleField("order",opts.order) end return self @@ -721,12 +864,12 @@ -- helper methods - function StateHandler:SetRule( key, value, ... ) - tbuild(self.states, self:GetName(), "rule", ...)[key] = value + function StateHandler:SetRuleField( key, value, ... ) + tbuild(self.state, "rule", ...)[key] = value end - function StateHandler:GetRule( ... ) - return tfetch(self.states, self:GetName(), "rule", ...) + function StateHandler:GetRuleField( ... ) + return tfetch(self.state, "rule", ...) end function StateHandler:FixAll( setkey ) @@ -735,7 +878,7 @@ -- be chosen arbitrarily. Otherwise, if selecting a new checkbox from the field-set, -- it will be retained. local notified = false - if self:GetRule("type") == "all" then + if self:GetRuleField("type") == "all" then for _, c in ipairs(rules) do local rule, hidden, fields = unpack(c) local once = false @@ -748,9 +891,9 @@ end for idx, field in ipairs(fields) do local key = next(field) - if self:GetRule("values",key) then + if self:GetRuleField("values",key) then if once and key ~= setkey then - self:SetRule(key,false,"values") + self:SetRuleField(key,false,"values") if not setkey and not notified then ReAction:UserError(L["Warning: one or more incompatible rules were turned off"]) notified = true @@ -858,37 +1001,37 @@ end function StateHandler:SetType(info, value) - self:SetRule("type", value) + self:SetRuleField("type", value) self:FixAll() ApplyStates(self.bar) end function StateHandler:GetType() - return self:GetRule("type") + return self:GetRuleField("type") end function StateHandler:GetClearAllDisabled() - local t = self:GetRule("type") + local t = self:GetRuleField("type") return not( t == "any" or t == "all" or t == "custom") end function StateHandler:ClearAllConditions() - local t = self:GetRule("type") + local t = self:GetRuleField("type") if t == "custom" then - self:SetRule("custom","") + self:SetRuleField("custom","") elseif t == "any" or t == "all" then - self:SetRule("values", {}) + self:SetRuleField("values", {}) end ApplyStates(self.bar) end function StateHandler:GetConditionsDisabled() - local t = self:GetRule("type") + local t = self:GetRuleField("type") return not( t == "any" or t == "all") end function StateHandler:SetCondition(info, key, value) - self:SetRule(ruleMap[key], value or nil, "values") + self:SetRuleField(ruleMap[key], value or nil, "values") if value then self:FixAll(ruleMap[key]) end @@ -896,20 +1039,20 @@ end function StateHandler:GetCondition(info, key) - return self:GetRule("values", ruleMap[key]) or false + return self:GetRuleField("values", ruleMap[key]) or false end function StateHandler:GetCustomDisabled() - return self:GetRule("type") ~= "custom" + return self:GetRuleField("type") ~= "custom" end function StateHandler:SetCustomRule(info, value) - self:SetRule("custom",value) + self:SetRuleField("custom",value) ApplyStates(self.bar) end function StateHandler:GetCustomRule() - return self:GetRule("custom") or "" + return self:GetRuleField("custom") or "" end function StateHandler:ValidateCustomRule(info, value) @@ -929,18 +1072,18 @@ end function StateHandler:GetKeybindDisabled() - return self:GetRule("type") ~= "keybind" + return self:GetRuleField("type") ~= "keybind" end function StateHandler:GetKeybind() - return self:GetRule("keybind") + return self:GetRuleField("keybind") end function StateHandler:SetKeybind(info, value) if value and #value == 0 then value = nil end - self:SetRule("keybind",value) + self:SetRuleField("keybind",value) ApplyStates(self.bar) end @@ -957,17 +1100,6 @@ return opts end - - function RegisterPropertyOptions( field, options, handler ) - stateOptions.properties.plugins[field] = options - if handler then - for k,v in pairs(handler) do - StateHandler[k] = v - end - end - end - - function module:GetBarOptions(bar) local private = { } local states = tbuild(module.db.profile.bars, bar:GetName(), "states") @@ -1033,23 +1165,23 @@ -- Module API -- --- Pass in a property field-name, an implementation function, a static options table, and an +-- Pass in a property field-name, an implementation secure snippet, a static options table, and an -- optional options handler method-table -- --- propertyImplFunc prototype: --- propertyImplFunc( bar, stateTable ) --- where stateTable is a { ["statename"] = { state config } } table. --- -- The options table is static, i.e. not bar-specific and should only reference handler method -- strings (either existing ones or those added via optHandler). The existing options are ordered -- 90-99. Order #1 is reserved for the heading. -- --- The contents of optHandler, if provided, will be added to the existing StateHandler metatable. +-- The contents of optHandler, if provided, will be added to the existing StateHandler options metatable. -- See above, for existing API. In particular see the properties set up in the New method: self.bar, -- self.states, and self:GetName(), and the generic property handlers self:GetProp() and self:SetProp(). -- -function module:RegisterStateProperty( field, propertyImplFunc, options, optHandler ) - RegisterProperty(field, propertyImplFunc) +function module:RegisterStateProperty( field, snippetHandler, options, optHandler ) + RegisterProperty(field, snippetHandler) RegisterPropertyOptions(field, options, optHandler) end +function module:UnregisterStateProperty( field ) + UnregisterProperty(field) + UnregisterPropertyOptions(field) +end diff -r 491a6ffe7260 -r 7cabc8ac6c16 lib/embeds.xml --- a/lib/embeds.xml Mon Oct 13 23:32:33 2008 +0000 +++ b/lib/embeds.xml Wed Oct 15 16:29:41 2008 +0000 @@ -12,8 +12,4 @@ - -