Mercurial > wow > reaction
comparison modules/ReAction_Action/ReAction_Action.lua @ 87:3499ac7c3a9b
Implemented paged actions and mind control actions, and config menus to suit.
There's still some sort of bug in the actionID-selection routine, it doesn't always auto-select the IDs that I think it's going to if you resize a bar several times (especially in the presence of multiple pages).
| author | Flick <flickerstreak@gmail.com> |
|---|---|
| date | Sat, 28 Jun 2008 00:54:21 +0000 |
| parents | 502cdb5666e2 |
| children | fc83b3f5b322 |
comparison
equal
deleted
inserted
replaced
| 86:f32e2375e39b | 87:3499ac7c3a9b |
|---|---|
| 1 --[[ | 1 --[[ |
| 2 ReAction Action button module. | 2 ReAction Action button module. |
| 3 | 3 |
| 4 The button module implements standard action button functionality by wrapping Blizzard's | 4 The button module implements standard action button functionality by wrapping Blizzard's |
| 5 ActionButton frame and associated functions. | 5 ActionBarButtonTemplate frame and associated functions. |
| 6 | 6 |
| 7 It also provides support for multiple pages (interacting with the State module) as well | 7 It also provides action remapping support for multiple pages and possessed targets |
| 8 as optional action remapping for possessed targets (mind control). | 8 (Mind Control, Eyes of the Beast, Karazhan Chess event, various quests, etc). This is done |
| 9 | 9 by interacting with the built-in State module to map these features to states via the |
| 10 "statebutton" attribute. | |
| 10 --]] | 11 --]] |
| 11 | 12 |
| 12 -- local imports | 13 -- local imports |
| 13 local ReAction = ReAction | 14 local ReAction = ReAction |
| 14 local L = ReAction.L | 15 local L = ReAction.L |
| 15 local _G = _G | 16 local _G = _G |
| 16 local CreateFrame = CreateFrame | 17 local CreateFrame = CreateFrame |
| 17 | 18 |
| 18 ReAction:UpdateRevision("$Revision: 103 $") | 19 ReAction:UpdateRevision("$Revision$") |
| 19 | 20 |
| 20 -- module declaration | 21 -- module declaration |
| 21 local moduleID = "Action" | 22 local moduleID = "Action" |
| 22 local module = ReAction:NewModule( moduleID ) | 23 local module = ReAction:NewModule( moduleID ) |
| 23 | 24 |
| 24 -- Button class declaration | 25 -- Button class declaration |
| 25 local Button = { } | 26 local Button = { } |
| 26 | 27 |
| 27 -- private -- | 28 -- private utility -- |
| 28 local function RefreshLite(bar) | 29 local function RefreshLite(bar) |
| 29 local btns = module.buttons[bar] | 30 local btns = module.buttons[bar] |
| 30 if btns then | 31 if btns then |
| 31 for _, b in ipairs(btns) do | 32 for _, b in ipairs(btns) do |
| 32 b:Refresh() | 33 b:Refresh() |
| 33 end | 34 end |
| 34 end | 35 end |
| 35 end | 36 end |
| 37 | |
| 38 local function GetBarConfig(bar) | |
| 39 return module.db.profile.bars[bar:GetName()] | |
| 40 end | |
| 41 | |
| 36 | 42 |
| 37 -- Event handlers | 43 -- Event handlers |
| 38 function module:OnInitialize() | 44 function module:OnInitialize() |
| 39 self.db = ReAction.db:RegisterNamespace( moduleID, | 45 self.db = ReAction.db:RegisterNamespace( moduleID, |
| 40 { | 46 { |
| 88 local btnCfg = profile.buttons[name] | 94 local btnCfg = profile.buttons[name] |
| 89 local barCfg = profile.bars[name] | 95 local barCfg = profile.bars[name] |
| 90 | 96 |
| 91 local r, c = bar:GetButtonGrid() | 97 local r, c = bar:GetButtonGrid() |
| 92 local n = r*c | 98 local n = r*c |
| 93 for i = 1, n do | 99 if n ~= #btns then |
| 94 if btnCfg[i] == nil then | 100 for i = 1, n do |
| 95 btnCfg[i] = {} | 101 if btnCfg[i] == nil then |
| 96 end | 102 btnCfg[i] = {} |
| 97 if btns[i] == nil then | 103 end |
| 98 local b = Button:New(bar, i, btnCfg[i], barCfg) | 104 if btns[i] == nil then |
| 99 btns[i] = b | 105 local b = Button:New(bar, i, btnCfg[i], barCfg) |
| 100 bar:AddButton(i,b) | 106 btns[i] = b |
| 101 end | 107 bar:AddButton(i,b) |
| 102 end | 108 end |
| 103 for i = n+1, #btns do | 109 end |
| 104 if btns[i] then | 110 for i = n+1, #btns do |
| 105 bar:RemoveButton(btns[i]) | 111 if btns[i] then |
| 106 btns[i] = btns[i]:Destroy() | 112 bar:RemoveButton(btns[i]) |
| 107 if btnCfg[i] then | 113 btns[i] = btns[i]:Destroy() |
| 108 btnCfg[i] = nil | 114 if btnCfg[i] then |
| 115 btnCfg[i] = nil | |
| 116 end | |
| 109 end | 117 end |
| 110 end | 118 end |
| 111 end | 119 end |
| 112 RefreshLite(bar) | 120 RefreshLite(bar) |
| 113 end | 121 end |
| 147 end | 155 end |
| 148 end | 156 end |
| 149 | 157 |
| 150 | 158 |
| 151 ---- Options ---- | 159 ---- Options ---- |
| 152 local Handler = { } | 160 do |
| 153 | 161 local Handler = { } |
| 154 local options = { | 162 |
| 155 hideEmpty = { | 163 local options = { |
| 156 name = L["Hide Empty Buttons"], | 164 hideEmpty = { |
| 157 desc = L["Hide buttons when empty. This option is not supported for multi-state bars"], | 165 name = L["Hide Empty Buttons"], |
| 158 order = 1, | 166 order = 1, |
| 159 type = "toggle", | 167 type = "toggle", |
| 160 get = "GetHideEmpty", | 168 width = "double", |
| 161 set = "SetHideEmpty", | 169 get = "GetHideEmpty", |
| 162 }, | 170 set = "SetHideEmpty", |
| 163 } | 171 }, |
| 164 | 172 pages = { |
| 165 function module:GetBarOptions(bar) | 173 name = L["# Pages"], |
| 166 return { | 174 desc = L["Use the Dynamic State tab to specify page transitions"], |
| 167 type = "group", | 175 order = 2, |
| 168 name = L["Action Buttons"], | 176 type = "range", |
| 169 handler = Handler:New(bar), | 177 min = 1, |
| 170 hidden = "Hidden", | 178 max = 10, |
| 171 args = options | 179 step = 1, |
| 180 get = "GetNumPages", | |
| 181 set = "SetNumPages", | |
| 182 }, | |
| 183 actions = { | |
| 184 name = L["Edit Action IDs"], | |
| 185 order = 13, | |
| 186 type = "group", | |
| 187 inline = true, | |
| 188 args = { | |
| 189 method = { | |
| 190 name = L["Assign"], | |
| 191 order = 1, | |
| 192 type = "select", | |
| 193 width = "double", | |
| 194 values = { [0] = L["Choose Method..."], | |
| 195 [1] = L["Individually"], | |
| 196 [2] = L["All at Once"], }, | |
| 197 get = "GetActionEditMethod", | |
| 198 set = "SetActionEditMethod", | |
| 199 }, | |
| 200 rowSelect = { | |
| 201 name = L["Row"], | |
| 202 desc = L["Rows are numbered top to bottom"], | |
| 203 order = 2, | |
| 204 type = "select", | |
| 205 width = "half", | |
| 206 hidden = "IsButtonSelectHidden", | |
| 207 values = "GetRowList", | |
| 208 get = "GetSelectedRow", | |
| 209 set = "SetSelectedRow", | |
| 210 }, | |
| 211 colSelect = { | |
| 212 name = L["Col"], | |
| 213 desc = L["Columns are numbered left to right"], | |
| 214 order = 3, | |
| 215 type = "select", | |
| 216 width = "half", | |
| 217 hidden = "IsButtonSelectHidden", | |
| 218 values = "GetColumnList", | |
| 219 get = "GetSelectedColumn", | |
| 220 set = "SetSelectedColumn", | |
| 221 }, | |
| 222 pageSelect = { | |
| 223 name = L["Page"], | |
| 224 order = 4, | |
| 225 type = "select", | |
| 226 width = "half", | |
| 227 hidden = "IsPageSelectHidden", | |
| 228 values = "GetPageList", | |
| 229 get = "GetSelectedPage", | |
| 230 set = "SetSelectedPage", | |
| 231 }, | |
| 232 single = { | |
| 233 name = L["Action ID"], | |
| 234 usage = L["Specify ID 1-120"], | |
| 235 order = 5, | |
| 236 type = "input", | |
| 237 width = "half", | |
| 238 hidden = "IsButtonSelectHidden", | |
| 239 get = "GetActionID", | |
| 240 set = "SetActionID", | |
| 241 validate = "ValidateActionID", | |
| 242 }, | |
| 243 multi = { | |
| 244 name = L["ID List"], | |
| 245 usage = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"], | |
| 246 order = 6, | |
| 247 type = "input", | |
| 248 multiline = true, | |
| 249 width = "double", | |
| 250 hidden = "IsMultiIDHidden", | |
| 251 get = "GetMultiID", | |
| 252 set = "SetMultiID", | |
| 253 validate = "ValidateMultiID", | |
| 254 } | |
| 255 } | |
| 256 }, | |
| 172 } | 257 } |
| 173 end | 258 |
| 174 | 259 function module:GetBarOptions(bar) |
| 175 -- options handler private | 260 return { |
| 176 do | 261 type = "group", |
| 177 local function GetBarConfig( bar ) | 262 name = L["Action Buttons"], |
| 178 return module.db.profile.bars[bar:GetName()] | 263 handler = Handler:New(bar), |
| 179 end | 264 hidden = "Hidden", |
| 180 | 265 args = options |
| 266 } | |
| 267 end | |
| 268 | |
| 269 -- options handler private | |
| 181 function Handler:New(bar) | 270 function Handler:New(bar) |
| 182 return setmetatable( { bar = bar }, { __index = Handler } ) | 271 return setmetatable( { bar = bar }, { __index = Handler } ) |
| 183 end | 272 end |
| 184 | 273 |
| 185 function Handler:Hidden() | 274 function Handler:Hidden() |
| 197 end | 286 end |
| 198 | 287 |
| 199 function Handler:GetHideEmpty() | 288 function Handler:GetHideEmpty() |
| 200 return GetBarConfig(self.bar).hideEmpty | 289 return GetBarConfig(self.bar).hideEmpty |
| 201 end | 290 end |
| 202 end | 291 |
| 203 | 292 function Handler:GetNumPages() |
| 293 return GetBarConfig(self.bar).nPages | |
| 294 end | |
| 295 | |
| 296 function Handler:SetNumPages(info, value) | |
| 297 GetBarConfig(self.bar).nPages = value | |
| 298 RefreshLite(self.bar) | |
| 299 end | |
| 300 | |
| 301 function Handler:GetActionEditMethod() | |
| 302 return self.editMethod or 0 | |
| 303 end | |
| 304 | |
| 305 function Handler:SetActionEditMethod(info, value) | |
| 306 self.editMethod = value | |
| 307 end | |
| 308 | |
| 309 function Handler:IsButtonSelectHidden() | |
| 310 return self.editMethod ~= 1 | |
| 311 end | |
| 312 | |
| 313 function Handler:GetRowList() | |
| 314 local r,c = self.bar:GetButtonGrid() | |
| 315 if self.rowList == nil or #self.rowList ~= r then | |
| 316 local list = { } | |
| 317 for i = 1, r do | |
| 318 table.insert(list,i) | |
| 319 end | |
| 320 self.rowList = list | |
| 321 end | |
| 322 return self.rowList | |
| 323 end | |
| 324 | |
| 325 function Handler:GetSelectedRow() | |
| 326 local r, c = self.bar:GetButtonGrid() | |
| 327 local row = self.selectedRow or 1 | |
| 328 if row > r then | |
| 329 row = 1 | |
| 330 end | |
| 331 self.selectedRow = row | |
| 332 return row | |
| 333 end | |
| 334 | |
| 335 function Handler:SetSelectedRow(info, value) | |
| 336 self.selectedRow = value | |
| 337 end | |
| 338 | |
| 339 function Handler:GetColumnList() | |
| 340 local r,c = self.bar:GetButtonGrid() | |
| 341 if self.columnList == nil or #self.columnList ~= c then | |
| 342 local list = { } | |
| 343 for i = 1, c do | |
| 344 table.insert(list,i) | |
| 345 end | |
| 346 self.columnList = list | |
| 347 end | |
| 348 return self.columnList | |
| 349 end | |
| 350 | |
| 351 function Handler:GetSelectedColumn() | |
| 352 local r, c = self.bar:GetButtonGrid() | |
| 353 local col = self.selectedColumn or 1 | |
| 354 if col > c then | |
| 355 col = 1 | |
| 356 end | |
| 357 self.selectedColumn = col | |
| 358 return col | |
| 359 end | |
| 360 | |
| 361 function Handler:SetSelectedColumn(info, value) | |
| 362 self.selectedColumn = value | |
| 363 end | |
| 364 | |
| 365 function Handler:IsPageSelectHidden() | |
| 366 return self.editMethod ~= 1 or (GetBarConfig(self.bar).nPages or 1) < 2 | |
| 367 end | |
| 368 | |
| 369 function Handler:GetPageList() | |
| 370 local n = GetBarConfig(self.bar).nPages or 1 | |
| 371 if self.pageList == nil or #self.pageList ~= n then | |
| 372 local p = { } | |
| 373 for i = 1, n do | |
| 374 table.insert(p,i) | |
| 375 end | |
| 376 self.pageList = p | |
| 377 end | |
| 378 return self.pageList | |
| 379 end | |
| 380 | |
| 381 function Handler:GetSelectedPage() | |
| 382 local p = self.selectedPage or 1 | |
| 383 if p > (GetBarConfig(self.bar).nPages or 1) then | |
| 384 p = 1 | |
| 385 end | |
| 386 self.selectedPage = p | |
| 387 return p | |
| 388 end | |
| 389 | |
| 390 function Handler:SetSelectedPage(info, value) | |
| 391 self.selectedPage = value | |
| 392 end | |
| 393 | |
| 394 function Handler:GetActionID() | |
| 395 local row = self.selectedRow or 1 | |
| 396 local col = self.selectedColumn or 1 | |
| 397 local r, c = self.bar:GetButtonGrid() | |
| 398 local n = (row-1) * c + col | |
| 399 local btn = module.buttons[self.bar][n] | |
| 400 if btn then | |
| 401 return tostring(btn:GetActionID(self.selectedPage or 1)) | |
| 402 end | |
| 403 end | |
| 404 | |
| 405 function Handler:SetActionID(info, value) | |
| 406 local row = self.selectedRow or 1 | |
| 407 local col = self.selectedColumn or 1 | |
| 408 local r, c = self.bar:GetButtonGrid() | |
| 409 local n = (row-1) * c + col | |
| 410 local btn = module.buttons[self.bar][n] | |
| 411 if btn then | |
| 412 btn:SetActionID(tonumber(value), self.selectedPage or 1) | |
| 413 end | |
| 414 end | |
| 415 | |
| 416 function Handler:ValidateActionID(info, value) | |
| 417 value = tonumber(value) | |
| 418 if value == nil or value < 1 or value > 120 then | |
| 419 return L["Specify ID 1-120"] | |
| 420 end | |
| 421 return true | |
| 422 end | |
| 423 | |
| 424 function Handler:IsMultiIDHidden() | |
| 425 return self.editMethod ~= 2 | |
| 426 end | |
| 427 | |
| 428 function Handler:GetMultiID() | |
| 429 local p = { } | |
| 430 for i = 1, GetBarConfig(self.bar).nPages or 1 do | |
| 431 local b = { } | |
| 432 for _, btn in ipairs(module.buttons[self.bar]) do | |
| 433 table.insert(b, btn:GetActionID(i)) | |
| 434 end | |
| 435 table.insert(p, table.concat(b,",")) | |
| 436 end | |
| 437 return table.concat(p,";\n") | |
| 438 end | |
| 439 | |
| 440 | |
| 441 local function ParseMultiID(nBtns, nPages, s) | |
| 442 if s:match("[^%d%s,;]") then | |
| 443 ReAction:Print("items other than digits, spaces, commas, and semicolons in string",s) | |
| 444 return nil | |
| 445 end | |
| 446 local p = { } | |
| 447 for list in s:gmatch("[^;]+") do | |
| 448 local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns)) | |
| 449 local ids = { list:match(pattern) } | |
| 450 if #ids ~= nBtns then | |
| 451 ReAction:Print("found",#ids,"buttons instead of",nBtns) | |
| 452 return nil | |
| 453 end | |
| 454 table.insert(p,ids) | |
| 455 end | |
| 456 if #p ~= nPages then | |
| 457 ReAction:Print("found",#p,"pages instead of",nPages) | |
| 458 return nil | |
| 459 end | |
| 460 return p | |
| 461 end | |
| 462 | |
| 463 function Handler:SetMultiID(info, value) | |
| 464 local btns = module.buttons[self.bar] | |
| 465 local p = ParseMultiID(#btns, GetBarConfig(self.bar).nPages or 1, value) | |
| 466 for page, b in ipairs(p) do | |
| 467 for button, id in ipairs(b) do | |
| 468 btns[button]:SetActionID(id, page) | |
| 469 end | |
| 470 end | |
| 471 end | |
| 472 | |
| 473 function Handler:ValidateMultiID(info, value) | |
| 474 local bad = L["Invalid action ID list string"] | |
| 475 if value == nil or ParseMultiID(#module.buttons[self.bar], GetBarConfig(self.bar).nPages or 1, value) == nil then | |
| 476 return bad | |
| 477 end | |
| 478 return true | |
| 479 end | |
| 480 end | |
| 481 | |
| 482 | |
| 483 ------ State property options ------ | |
| 484 do | |
| 485 local pageOptions = { | |
| 486 mindcontrol = { | |
| 487 name = L["Mind Control Support"], | |
| 488 desc = L["When possessing a target (e.g. via Mind Control), map the first 12 buttons of this bar to the possessed target's actions. Select the 'Mind Control' option for the rule type to enable."], | |
| 489 order = 11, | |
| 490 type = "toggle", | |
| 491 disabled = "IsMCDisabled", | |
| 492 hidden = "IsPageHidden", | |
| 493 width = "double", | |
| 494 set = "SetProp", | |
| 495 get = "GetProp", | |
| 496 }, | |
| 497 page = { | |
| 498 name = L["Show Page #"], | |
| 499 order = 12, | |
| 500 type = "select", | |
| 501 width = "half", | |
| 502 disabled = "IsPageDisabled", | |
| 503 hidden = "IsPageHidden", | |
| 504 values = "GetPageValues", | |
| 505 set = "SetProp", | |
| 506 get = "GetPage", | |
| 507 }, | |
| 508 } | |
| 509 | |
| 510 local function pageImpl( bar, states ) | |
| 511 local map = { } | |
| 512 for state, c in pairs(states) do | |
| 513 if c.mindcontrol then | |
| 514 map[state] = "mc" | |
| 515 elseif c.page then | |
| 516 map[state] = ("page%d"):format(c.page) | |
| 517 end | |
| 518 end | |
| 519 bar:SetStateAttribute("statebutton", map, 1, true) | |
| 520 end | |
| 521 | |
| 522 local PageOptsHandler = { } -- will inherit properties and methods via State:RegisterStateProperty | |
| 523 | |
| 524 function PageOptsHandler:IsMCDisabled(info) | |
| 525 if self:IsPageHidden() then | |
| 526 return true | |
| 527 end | |
| 528 -- only allow this if the mind-control selector or custom/keybind is chosen | |
| 529 -- see State.lua for the structure of the 'rule' config element | |
| 530 local rule = self.states[self:GetName()].rule | |
| 531 if rule then | |
| 532 if rule.type == "custom" or rule.type == "keybind" then | |
| 533 return false | |
| 534 else | |
| 535 if rule.values and rule.values.possess then | |
| 536 return false | |
| 537 end | |
| 538 end | |
| 539 end | |
| 540 return true | |
| 541 end | |
| 542 | |
| 543 function PageOptsHandler:IsPageDisabled() | |
| 544 -- disabled if not an action button | |
| 545 return not GetBarConfig(self.bar) or | |
| 546 -- OR mind-control remapping is enabled | |
| 547 self.states[self:GetName()].mindcontrol or | |
| 548 -- OR only one page is enabled | |
| 549 (GetBarConfig(self.bar).nPages and GetBarConfig(self.bar).nPages < 2) | |
| 550 end | |
| 551 | |
| 552 function PageOptsHandler:IsPageHidden() | |
| 553 return not GetBarConfig(self.bar) | |
| 554 end | |
| 555 | |
| 556 function PageOptsHandler:GetPageValues() | |
| 557 local c = GetBarConfig(self.bar) | |
| 558 if c then | |
| 559 local n = c.nPages | |
| 560 if self._npages ~= n then | |
| 561 self._pagevalues = { } | |
| 562 self._npages = n | |
| 563 -- cache the results | |
| 564 for i = 1, n do | |
| 565 self._pagevalues[i] = i | |
| 566 end | |
| 567 end | |
| 568 return self._pagevalues | |
| 569 end | |
| 570 end | |
| 571 | |
| 572 function PageOptsHandler:GetPage(info) | |
| 573 return self:GetProp(info) or 1 | |
| 574 end | |
| 575 | |
| 576 ReAction:GetModule("State"):RegisterStateProperty("page", pageImpl, pageOptions, PageOptsHandler) | |
| 577 end | |
| 578 | |
| 579 ------ ActionID allocation ------ | |
| 580 -- this needs to be high performance when requesting new IDs, | |
| 581 -- or certain controls will become sluggish. However, the new-request | |
| 582 -- infrastructure can be built lazily the first time that a new request | |
| 583 -- comes in (which will only happen at user config time: at static startup | |
| 584 -- config time all actionIDs should already have been assigned and stored | |
| 585 -- in the config file) | |
| 586 | |
| 587 local IDAlloc | |
| 588 do | |
| 589 local n = 120 | |
| 590 | |
| 591 IDAlloc = setmetatable({ wrap = 1, freecount = n }, {__index = function() return 0 end}) | |
| 592 | |
| 593 function IDAlloc:Acquire(id, hint) | |
| 594 id = tonumber(id) | |
| 595 hint = tonumber(hint) | |
| 596 if id and (id < 1 or id > n) then | |
| 597 id = nil | |
| 598 end | |
| 599 if hint and (hint < 1 or hint > n) then | |
| 600 hint = nil | |
| 601 end | |
| 602 if id == nil then | |
| 603 -- get a free ID | |
| 604 if hint and self[hint] == 0 then | |
| 605 -- use the hint if it's free | |
| 606 id = hint | |
| 607 elseif self.freecount > 0 then | |
| 608 -- if neither the id nor the hint are defined or free, but | |
| 609 -- the list is known to have free IDs, then start searching | |
| 610 -- at the hint for a free one | |
| 611 for i = hint or 1, n do | |
| 612 if self[i] == 0 then | |
| 613 id = i | |
| 614 break | |
| 615 end | |
| 616 end | |
| 617 -- self.wrap the search | |
| 618 if id == nil and hint and hint > 1 then | |
| 619 for i = 1, hint - 1 do | |
| 620 if self[i] == 0 then | |
| 621 id = i | |
| 622 break | |
| 623 end | |
| 624 end | |
| 625 end | |
| 626 end | |
| 627 if id == nil then | |
| 628 -- if there are no free IDs, start wrapping at 1 | |
| 629 id = self.wrap | |
| 630 self.wrap = id + 1 | |
| 631 if self.wrap > n then | |
| 632 self.wrap = 1 | |
| 633 end | |
| 634 end | |
| 635 end | |
| 636 if self[id] == 0 then | |
| 637 self.freecount = self.freecount - 1 | |
| 638 end | |
| 639 self[id] = self[id] + 1 | |
| 640 return id | |
| 641 end | |
| 642 | |
| 643 function IDAlloc:Release(id) | |
| 644 id = tonumber(id) | |
| 645 if id and (id >= 1 or id <= n) then | |
| 646 self[id] = self[id] - 1 | |
| 647 if self[id] == 0 then | |
| 648 self.freecount = self.freecount + 1 | |
| 649 self.wrap = 1 | |
| 650 end | |
| 651 end | |
| 652 end | |
| 653 end | |
| 654 | |
| 655 local frameRecycler = { } | |
| 204 | 656 |
| 205 ------ Button class ------ | 657 ------ Button class ------ |
| 206 | |
| 207 -- use-count of action IDs | |
| 208 local nActionIDs = 120 | |
| 209 local ActionIDList = setmetatable( {}, { | |
| 210 __index = function(self, idx) | |
| 211 if idx == nil then | |
| 212 for i = 1, nActionIDs do | |
| 213 if rawget(self,i) == nil then | |
| 214 rawset(self,i,1) | |
| 215 return i | |
| 216 end | |
| 217 end | |
| 218 error("ran out of action IDs") | |
| 219 else | |
| 220 local c = rawget(self,idx) or 0 | |
| 221 rawset(self,idx,c+1) | |
| 222 return idx | |
| 223 end | |
| 224 end, | |
| 225 __newindex = function(self,idx,value) | |
| 226 if value == nil then | |
| 227 value = rawget(self,idx) | |
| 228 if value == 1 then | |
| 229 value = nil | |
| 230 elseif value then | |
| 231 value = value - 1 | |
| 232 end | |
| 233 end | |
| 234 rawset(self,idx,value) | |
| 235 end | |
| 236 }) | |
| 237 | |
| 238 function Button:New( bar, idx, config, barConfig ) | 658 function Button:New( bar, idx, config, barConfig ) |
| 239 -- create new self | 659 -- create new self |
| 240 self = setmetatable( { }, {__index = Button} ) | 660 self = setmetatable( { }, {__index = Button} ) |
| 241 self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig | 661 self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig |
| 242 | 662 |
| 243 config.name = config.name or ("ReAction_%s_%d"):format(bar:GetName(),idx) | 663 local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx) |
| 244 self.name = config.name | 664 self.name = name |
| 245 config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured | 665 config.name = name |
| 666 local lastButton = module.buttons[bar][#module.buttons[bar]] | |
| 667 config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured | |
| 246 self.nPages = 1 | 668 self.nPages = 1 |
| 247 | 669 |
| 248 local f = CreateFrame("CheckButton", self.name, bar:GetButtonFrame(), "ActionBarButtonTemplate") | 670 -- have to recycle frames with the same name: CreateFrame() |
| 671 -- doesn't overwrite existing globals (below). Can't set to nil in the global | |
| 672 -- table because you end up getting taint | |
| 673 local parent = bar:GetButtonFrame() | |
| 674 local f = frameRecycler[name] | |
| 675 if f then | |
| 676 f:SetParent(parent) | |
| 677 else | |
| 678 f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate") | |
| 679 end | |
| 249 | 680 |
| 250 f:SetAttribute("action", config.actionID) | 681 f:SetAttribute("action", config.actionID) |
| 251 -- install mind control action support for all buttons here just for simplicity | 682 -- install mind control action support for all buttons here just for simplicity |
| 252 if self.idx <= 12 then | 683 if self.idx <= 12 then |
| 253 f:SetAttribute("action-mc", 120 + self.idx) | 684 f:SetAttribute("*action-mc", 120 + self.idx) |
| 254 end | 685 end |
| 255 | 686 |
| 256 self.frame = f | 687 self.frame = f |
| 257 self.normalTexture = getglobal(format("%sNormalTexture",f:GetName())) | 688 self.normalTexture = getglobal(format("%sNormalTexture",f:GetName())) |
| 258 | 689 |
| 259 -- initialize the hide state | 690 -- initialize the hide state |
| 691 f:SetAttribute("showgrid",0) | |
| 260 self:ShowGrid(not barConfig.hideEmpty) | 692 self:ShowGrid(not barConfig.hideEmpty) |
| 261 if ReAction:GetConfigMode() then | 693 if ReAction:GetConfigMode() then |
| 262 self:ShowGrid(true) | 694 self:ShowGrid(true) |
| 263 end | 695 end |
| 264 | 696 |
| 274 f:UnregisterAllEvents() | 706 f:UnregisterAllEvents() |
| 275 f:Hide() | 707 f:Hide() |
| 276 f:SetParent(UIParent) | 708 f:SetParent(UIParent) |
| 277 f:ClearAllPoints() | 709 f:ClearAllPoints() |
| 278 if self.name then | 710 if self.name then |
| 279 _G[self.name] = nil | 711 frameRecycler[self.name] = f |
| 280 end | 712 end |
| 281 if self.config.actionID then | 713 if self.config.actionID then |
| 282 ActionIDList[self.config.actionID] = nil | 714 IDAlloc:Release(self.config.actionID) |
| 283 end | 715 end |
| 284 if self.config.pages then | 716 if self.config.pages then |
| 285 for _, id in ipairs(self.config.pages) do | 717 for _, id in ipairs(self.config.pageactions) do |
| 286 ActionIDList[id] = nil | 718 IDAlloc:Release(id) |
| 287 end | 719 end |
| 288 end | 720 end |
| 289 self.frame = nil | 721 self.frame = nil |
| 290 self.config = nil | 722 self.config = nil |
| 291 self.bar = nil | 723 self.bar = nil |
| 306 | 738 |
| 307 function Button:GetName() | 739 function Button:GetName() |
| 308 return self.name | 740 return self.name |
| 309 end | 741 end |
| 310 | 742 |
| 311 function Button:GetActionID() | 743 function Button:GetActionID(page) |
| 312 return SecureButton_GetModifiedAttribute(self.frame, "action") | 744 if page == nil then |
| 313 end | 745 -- get the effective ID |
| 314 | 746 return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction() |
| 315 function Button:RefreshPages() | 747 else |
| 316 local nPages = 1 --self.bar:GetNumPages() | 748 if page == 1 then |
| 317 if nPages ~= self.nPages then | 749 return self.config.actionID |
| 750 else | |
| 751 return self.config.pageactions and self.config.pageactions[page] or self.config.actionID | |
| 752 end | |
| 753 end | |
| 754 end | |
| 755 | |
| 756 function Button:SetActionID( id, page ) | |
| 757 id = tonumber(id) | |
| 758 page = tonumber(page) | |
| 759 if id == nil or id < 1 or id > 120 then | |
| 760 error("Button:SetActionID - invalid action ID") | |
| 761 end | |
| 762 if page and page ~= 1 then | |
| 763 if not self.config.pageactions then | |
| 764 self.config.pageactions = { } | |
| 765 end | |
| 766 if self.config.pageactions[page] then | |
| 767 IDAlloc:Release(self.config.pageactions[page]) | |
| 768 end | |
| 769 self.config.pageactions[page] = id | |
| 770 IDAlloc:Acquire(self.config.pageactions[page]) | |
| 771 self.frame:SetAttribute(("*action-page%d"):format(page),id) | |
| 772 else | |
| 773 IDAlloc:Release(self.config.actionID) | |
| 774 self.config.actionID = id | |
| 775 IDAlloc:Acquire(self.config.actionID) | |
| 776 self.frame:SetAttribute("action",id) | |
| 777 if self.config.pageactions then | |
| 778 self.config.pageactions[1] = id | |
| 779 self.frame:SetAttribute("*action-page1",id) | |
| 780 end | |
| 781 end | |
| 782 end | |
| 783 | |
| 784 function Button:RefreshPages( force ) | |
| 785 local nPages = self.barConfig.nPages | |
| 786 if nPages and (nPages ~= self.nPages or force) then | |
| 318 local f = self:GetFrame() | 787 local f = self:GetFrame() |
| 319 local c = self.config.pages | 788 local c = self.config.pageactions |
| 320 if nPages > 1 and not c then | 789 if nPages > 1 and not c then |
| 321 c = { } | 790 c = { } |
| 322 self.config.pages = c | 791 self.config.pageactions = c |
| 323 end | 792 end |
| 324 for i = 1, nPages do | 793 for i = 1, nPages do |
| 325 c[i] = ActionIDList[c[i]] -- gets a free one if none configured | 794 if i > 1 then |
| 326 f:SetAttribute(("action-page%d"):format(i)) | 795 c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons()) |
| 796 else | |
| 797 c[i] = self.config.actionID -- page 1 is the same as the base actionID | |
| 798 end | |
| 799 f:SetAttribute(("*action-page%d"):format(i),c[i]) | |
| 327 end | 800 end |
| 328 for i = nPages+1, #c do | 801 for i = nPages+1, #c do |
| 329 ActionIDList[c[i]] = nil | 802 IDAlloc:Release(c[i]) |
| 330 c[i] = nil | 803 c[i] = nil |
| 331 f:SetAttribute(("action-page%d"):format(i)) | 804 f:SetAttribute(("*action-page%d"):format(i),nil) |
| 332 end | 805 end |
| 333 | 806 self.nPages = nPages |
| 334 -- TODO: | |
| 335 -- apply next-page, prev-page, and direct-page keybinds (via bar:SetStateKeybind abstraction) | |
| 336 end | 807 end |
| 337 end | 808 end |
| 338 | 809 |
| 339 function Button:ShowGrid( show ) | 810 function Button:ShowGrid( show ) |
| 340 if not InCombatLockdown() then | 811 if not InCombatLockdown() then |
| 359 end | 830 end |
| 360 end | 831 end |
| 361 end | 832 end |
| 362 | 833 |
| 363 function Button:ShowActionIDLabel( show ) | 834 function Button:ShowActionIDLabel( show ) |
| 835 local f = self:GetFrame() | |
| 364 if show then | 836 if show then |
| 365 local id = self:GetActionID() | 837 local id = self:GetActionID() |
| 366 if not self.actionIDLabel and id and id ~= 0 then | 838 if not f.actionIDLabel then |
| 367 local f = self:GetFrame() | |
| 368 local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") | 839 local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") |
| 369 label:SetAllPoints() | 840 label:SetAllPoints() |
| 370 label:SetJustifyH("CENTER") | 841 label:SetJustifyH("CENTER") |
| 371 label:SetShadowColor(0,0,0,1) | 842 label:SetShadowColor(0,0,0,1) |
| 372 label:SetShadowOffset(2,-2) | 843 label:SetShadowOffset(2,-2) |
| 373 label:SetText(tostring(id)) | 844 f.actionIDLabel = label -- store the label with the frame for recycling |
| 374 self.actionIDLabel = label | |
| 375 f:HookScript("OnAttributeChanged", | 845 f:HookScript("OnAttributeChanged", |
| 376 function(frame, attr, value) | 846 function(frame, attr, value) |
| 377 if attr == "state-parent" then | 847 if label:IsVisible() and (attr == "state-parent" or attr:match("action")) then |
| 378 label:SetText(tostring(self:GetActionID())) | 848 label:SetText(tostring(frame.action)) |
| 379 end | 849 end |
| 380 end) | 850 end) |
| 381 end | 851 end |
| 382 self.actionIDLabel:Show() | 852 f.actionIDLabel:SetText(tostring(id)) |
| 383 elseif self.actionIDLabel then | 853 f.actionIDLabel:Show() |
| 384 self.actionIDLabel:Hide() | 854 elseif f.actionIDLabel then |
| 385 end | 855 f.actionIDLabel:Hide() |
| 386 end | 856 end |
| 387 | 857 end |
| 858 |
