1553757 - Refactor updateIndicators
[wowui.git] / libs / AceGUI-3.0-SharedMediaWidgets / Libs / AceGUI-3.0 / widgets / AceGUIWidget-DropDown.lua
1 --[[ $Id: AceGUIWidget-DropDown.lua 1029 2011-06-10 23:10:58Z nevcairiel $ ]]--
2 local AceGUI = LibStub("AceGUI-3.0")
3
4 -- Lua APIs
5 local min, max, floor = math.min, math.max, math.floor
6 local select, pairs, ipairs, type = select, pairs, ipairs, type
7 local tsort = table.sort
8
9 -- WoW APIs
10 local PlaySound = PlaySound
11 local UIParent, CreateFrame = UIParent, CreateFrame
12 local _G = _G
13
14 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
15 -- List them here for Mikk's FindGlobals script
16 -- GLOBALS: CLOSE
17
18 local function fixlevels(parent,...)
19         local i = 1
20         local child = select(i, ...)
21         while child do
22                 child:SetFrameLevel(parent:GetFrameLevel()+1)
23                 fixlevels(child, child:GetChildren())
24                 i = i + 1
25                 child = select(i, ...)
26         end
27 end
28
29 local function fixstrata(strata, parent, ...)
30         local i = 1
31         local child = select(i, ...)
32         parent:SetFrameStrata(strata)
33         while child do
34                 fixstrata(strata, child, child:GetChildren())
35                 i = i + 1
36                 child = select(i, ...)
37         end
38 end
39
40 do
41         local widgetType = "Dropdown-Pullout"
42         local widgetVersion = 3
43         
44         --[[ Static data ]]--
45         
46         local backdrop = {
47                 bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
48                 edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
49                 edgeSize = 32,
50                 tileSize = 32,
51                 tile = true,
52                 insets = { left = 11, right = 12, top = 12, bottom = 11 },
53         }
54         local sliderBackdrop  = {
55                 bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
56                 edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
57                 tile = true, tileSize = 8, edgeSize = 8,
58                 insets = { left = 3, right = 3, top = 3, bottom = 3 }
59         }
60
61         local defaultWidth = 200
62         local defaultMaxHeight = 600
63         
64         --[[ UI Event Handlers ]]--
65         
66         -- HACK: This should be no part of the pullout, but there
67         --       is no other 'clean' way to response to any item-OnEnter
68         --       Used to close Submenus when an other item is entered
69         local function OnEnter(item)
70                 local self = item.pullout
71                 for k, v in ipairs(self.items) do
72                         if v.CloseMenu and v ~= item then
73                                 v:CloseMenu()
74                         end
75                 end
76         end
77         
78         -- See the note in Constructor() for each scroll related function
79         local function OnMouseWheel(this, value)
80                 this.obj:MoveScroll(value)
81         end
82         
83         local function OnScrollValueChanged(this, value)
84                 this.obj:SetScroll(value)
85         end
86         
87         local function OnSizeChanged(this)
88                 this.obj:FixScroll()
89         end
90         
91         --[[ Exported methods ]]--
92         
93         -- exported
94         local function SetScroll(self, value)
95                 local status = self.scrollStatus
96                 local frame, child = self.scrollFrame, self.itemFrame
97                 local height, viewheight = frame:GetHeight(), child:GetHeight()
98
99                 local offset
100                 if height > viewheight then
101                         offset = 0
102                 else
103                         offset = floor((viewheight - height) / 1000 * value)
104                 end
105                 child:ClearAllPoints()
106                 child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset)
107                 child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", self.slider:IsShown() and -12 or 0, offset)
108                 status.offset = offset
109                 status.scrollvalue = value              
110         end
111         
112         -- exported
113         local function MoveScroll(self, value)
114                 local status = self.scrollStatus
115                 local frame, child = self.scrollFrame, self.itemFrame
116                 local height, viewheight = frame:GetHeight(), child:GetHeight()
117
118                 if height > viewheight then
119                         self.slider:Hide()
120                 else
121                         self.slider:Show()
122                         local diff = height - viewheight
123                         local delta = 1
124                         if value < 0 then
125                                 delta = -1
126                         end
127                         self.slider:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000))
128                 end
129         end
130         
131         -- exported
132         local function FixScroll(self)
133                 local status = self.scrollStatus
134                 local frame, child = self.scrollFrame, self.itemFrame
135                 local height, viewheight = frame:GetHeight(), child:GetHeight()
136                 local offset = status.offset or 0
137
138                 if viewheight < height then
139                         self.slider:Hide()
140                         child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, offset)
141                         self.slider:SetValue(0)
142                 else
143                         self.slider:Show()                      
144                         local value = (offset / (viewheight - height) * 1000)
145                         if value > 1000 then value = 1000 end
146                         self.slider:SetValue(value)
147                         self:SetScroll(value)
148                         if value < 1000 then
149                                 child:ClearAllPoints()
150                                 child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset)
151                                 child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, offset)
152                                 status.offset = offset
153                         end
154                 end
155         end
156         
157         -- exported, AceGUI callback
158         local function OnAcquire(self)
159                 self.frame:SetParent(UIParent)
160                 --self.itemFrame:SetToplevel(true)
161         end
162         
163         -- exported, AceGUI callback
164         local function OnRelease(self)
165                 self:Clear()
166                 self.frame:ClearAllPoints()
167                 self.frame:Hide()
168         end
169         
170         -- exported
171         local function AddItem(self, item)
172                 self.items[#self.items + 1] = item
173                 
174                 local h = #self.items * 16
175                 self.itemFrame:SetHeight(h)
176                 self.frame:SetHeight(min(h + 34, self.maxHeight)) -- +34: 20 for scrollFrame placement (10 offset) and +14 for item placement
177                 
178                 item.frame:SetPoint("LEFT", self.itemFrame, "LEFT")
179                 item.frame:SetPoint("RIGHT", self.itemFrame, "RIGHT")
180                 
181                 item:SetPullout(self)
182                 item:SetOnEnter(OnEnter)
183         end
184                 
185         -- exported
186         local function Open(self, point, relFrame, relPoint, x, y)              
187                 local items = self.items
188                 local frame = self.frame
189                 local itemFrame = self.itemFrame
190                 
191                 frame:SetPoint(point, relFrame, relPoint, x, y)
192
193                                 
194                 local height = 8
195                 for i, item in pairs(items) do
196                         if i == 1 then
197                                 item:SetPoint("TOP", itemFrame, "TOP", 0, -2)
198                         else
199                                 item:SetPoint("TOP", items[i-1].frame, "BOTTOM", 0, 1)
200                         end
201                         
202                         item:Show()
203                         
204                         height = height + 16
205                 end
206                 itemFrame:SetHeight(height)
207                 fixstrata("TOOLTIP", frame, frame:GetChildren())
208                 frame:Show()
209                 self:Fire("OnOpen")
210         end     
211         
212         -- exported
213         local function Close(self)
214                 self.frame:Hide()
215                 self:Fire("OnClose")
216         end     
217         
218         -- exported
219         local function Clear(self)
220                 local items = self.items
221                 for i, item in pairs(items) do
222                         AceGUI:Release(item)
223                         items[i] = nil
224                 end
225         end     
226         
227         -- exported
228         local function IterateItems(self)
229                 return ipairs(self.items)
230         end
231         
232         -- exported
233         local function SetHideOnLeave(self, val)
234                 self.hideOnLeave = val
235         end
236         
237         -- exported
238         local function SetMaxHeight(self, height)
239                 self.maxHeight = height or defaultMaxHeight
240                 if self.frame:GetHeight() > height then
241                         self.frame:SetHeight(height)
242                 elseif (self.itemFrame:GetHeight() + 34) < height then
243                         self.frame:SetHeight(self.itemFrame:GetHeight() + 34) -- see :AddItem
244                 end
245         end
246                 
247         -- exported
248         local function GetRightBorderWidth(self)
249                 return 6 + (self.slider:IsShown() and 12 or 0)
250         end
251         
252         -- exported
253         local function GetLeftBorderWidth(self)
254                 return 6
255         end
256         
257         --[[ Constructor ]]--
258         
259         local function Constructor()
260                 local count = AceGUI:GetNextWidgetNum(widgetType)
261                 local frame = CreateFrame("Frame", "AceGUI30Pullout"..count, UIParent)
262                 local self = {}
263                 self.count = count
264                 self.type = widgetType
265                 self.frame = frame
266                 frame.obj = self
267                 
268                 self.OnAcquire = OnAcquire
269                 self.OnRelease = OnRelease
270
271                 self.AddItem = AddItem
272                 self.Open    = Open
273                 self.Close   = Close
274                 self.Clear   = Clear
275                 self.IterateItems = IterateItems
276                 self.SetHideOnLeave = SetHideOnLeave
277
278                 self.SetScroll  = SetScroll
279                 self.MoveScroll = MoveScroll
280                 self.FixScroll  = FixScroll
281                 
282                 self.SetMaxHeight = SetMaxHeight
283                 self.GetRightBorderWidth = GetRightBorderWidth
284                 self.GetLeftBorderWidth = GetLeftBorderWidth
285                 
286                 self.items = {}
287                 
288                 self.scrollStatus = {
289                         scrollvalue = 0,
290                 }
291                 
292                 self.maxHeight = defaultMaxHeight
293                         
294                 frame:SetBackdrop(backdrop)
295                 frame:SetBackdropColor(0, 0, 0)
296                 frame:SetFrameStrata("FULLSCREEN_DIALOG")
297                 frame:SetClampedToScreen(true)
298                 frame:SetWidth(defaultWidth)
299                 frame:SetHeight(self.maxHeight) 
300                 --frame:SetToplevel(true)
301         
302                 -- NOTE: The whole scroll frame code is copied from the AceGUI-3.0 widget ScrollFrame
303                 local scrollFrame = CreateFrame("ScrollFrame", nil, frame)
304                 local itemFrame = CreateFrame("Frame", nil, scrollFrame)
305                 
306                 self.scrollFrame = scrollFrame
307                 self.itemFrame = itemFrame
308                 
309                 scrollFrame.obj = self
310                 itemFrame.obj = self
311                 
312                 local slider = CreateFrame("Slider", "AceGUI30PulloutScrollbar"..count, scrollFrame)
313                 slider:SetOrientation("VERTICAL")
314                 slider:SetHitRectInsets(0, 0, -10, 0)
315                 slider:SetBackdrop(sliderBackdrop)
316                 slider:SetWidth(8)
317                 slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical")
318                 slider:SetFrameStrata("FULLSCREEN_DIALOG")
319                 self.slider = slider
320                 slider.obj = self
321                                         
322                 scrollFrame:SetScrollChild(itemFrame)
323                 scrollFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 6, -12)
324                 scrollFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -6, 12)
325                 scrollFrame:EnableMouseWheel(true)
326                 scrollFrame:SetScript("OnMouseWheel", OnMouseWheel)
327                 scrollFrame:SetScript("OnSizeChanged", OnSizeChanged)
328                 scrollFrame:SetToplevel(true)
329                 scrollFrame:SetFrameStrata("FULLSCREEN_DIALOG")
330                 
331                 itemFrame:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", 0, 0)
332                 itemFrame:SetPoint("TOPRIGHT", scrollFrame, "TOPRIGHT", -12, 0)
333                 itemFrame:SetHeight(400)
334                 itemFrame:SetToplevel(true)
335                 itemFrame:SetFrameStrata("FULLSCREEN_DIALOG")
336                 
337                 slider:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", -16, 0)
338                 slider:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", -16, 0)
339                 slider:SetScript("OnValueChanged", OnScrollValueChanged)
340                 slider:SetMinMaxValues(0, 1000)
341                 slider:SetValueStep(1)
342                 slider:SetValue(0)
343                 
344                 scrollFrame:Show()
345                 itemFrame:Show()
346                 slider:Hide()
347                                                 
348                 self:FixScroll()
349                 
350                 AceGUI:RegisterAsWidget(self)
351                 return self
352         end
353         
354         AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion)
355 end
356
357 do
358         local widgetType = "Dropdown"
359         local widgetVersion = 25
360         
361         --[[ Static data ]]--
362         
363         --[[ UI event handler ]]--
364         
365         local function Control_OnEnter(this)
366                 this.obj:Fire("OnEnter")
367         end
368         
369         local function Control_OnLeave(this)
370                 this.obj:Fire("OnLeave")
371         end
372
373         local function Dropdown_OnHide(this)
374                 local self = this.obj
375                 if self.open then
376                         self.pullout:Close()
377                 end
378         end
379         
380         local function Dropdown_TogglePullout(this)
381                 local self = this.obj
382                 PlaySound("igMainMenuOptionCheckBoxOn") -- missleading name, but the Blizzard code uses this sound
383                 if self.open then
384                         self.open = nil
385                         self.pullout:Close()
386                         AceGUI:ClearFocus()
387                 else
388                         self.open = true
389                         self.pullout:SetWidth(self.frame:GetWidth())
390                         self.pullout:Open("TOPLEFT", self.frame, "BOTTOMLEFT", 0, self.label:IsShown() and -2 or 0)
391                         AceGUI:SetFocus(self)
392                 end
393         end
394         
395         local function OnPulloutOpen(this)
396                 local self = this.userdata.obj
397                 local value = self.value
398                 
399                 if not self.multiselect then
400                         for i, item in this:IterateItems() do
401                                 item:SetValue(item.userdata.value == value)
402                         end
403                 end
404                 
405                 self.open = true
406         end
407
408         local function OnPulloutClose(this)
409                 local self = this.userdata.obj
410                 self.open = nil
411                 self:Fire("OnClosed")
412         end
413         
414         local function ShowMultiText(self)
415                 local text
416                 for i, widget in self.pullout:IterateItems() do
417                         if widget.type == "Dropdown-Item-Toggle" then
418                                 if widget:GetValue() then
419                                         if text then
420                                                 text = text..", "..widget:GetText()
421                                         else
422                                                 text = widget:GetText()
423                                         end
424                                 end
425                         end
426                 end
427                 self:SetText(text)
428         end
429         
430         local function OnItemValueChanged(this, event, checked)
431                 local self = this.userdata.obj
432                 
433                 if self.multiselect then
434                         self:Fire("OnValueChanged", this.userdata.value, checked)
435                         ShowMultiText(self)
436                 else
437                         if checked then
438                                 self:SetValue(this.userdata.value)
439                                 self:Fire("OnValueChanged", this.userdata.value)
440                         else
441                                 this:SetValue(true)
442                         end
443                         if self.open then       
444                                 self.pullout:Close()
445                         end
446                 end
447         end
448         
449         --[[ Exported methods ]]--
450         
451         -- exported, AceGUI callback
452         local function OnAcquire(self)
453                 local pullout = AceGUI:Create("Dropdown-Pullout")
454                 self.pullout = pullout
455                 pullout.userdata.obj = self
456                 pullout:SetCallback("OnClose", OnPulloutClose)
457                 pullout:SetCallback("OnOpen", OnPulloutOpen)
458                 self.pullout.frame:SetFrameLevel(self.frame:GetFrameLevel() + 1)
459                 fixlevels(self.pullout.frame, self.pullout.frame:GetChildren())
460                 
461                 self:SetHeight(44)
462                 self:SetWidth(200)
463                 self:SetLabel()
464         end
465         
466         -- exported, AceGUI callback
467         local function OnRelease(self)
468                 if self.open then
469                         self.pullout:Close()
470                 end
471                 AceGUI:Release(self.pullout)
472                 self.pullout = nil
473                 
474                 self:SetText("")
475                 self:SetDisabled(false)
476                 self:SetMultiselect(false)
477                 
478                 self.value = nil
479                 self.list = nil
480                 self.open = nil
481                 self.hasClose = nil
482                 
483                 self.frame:ClearAllPoints()
484                 self.frame:Hide()
485         end
486         
487         -- exported
488         local function SetDisabled(self, disabled)
489                 self.disabled = disabled
490                 if disabled then
491                         self.text:SetTextColor(0.5,0.5,0.5)
492                         self.button:Disable()
493                         self.label:SetTextColor(0.5,0.5,0.5)
494                 else
495                         self.button:Enable()
496                         self.label:SetTextColor(1,.82,0)
497                         self.text:SetTextColor(1,1,1)
498                 end
499         end
500         
501         -- exported
502         local function ClearFocus(self)
503                 if self.open then
504                         self.pullout:Close()
505                 end
506         end
507         
508         -- exported
509         local function SetText(self, text)
510                 self.text:SetText(text or "")
511         end
512         
513         -- exported
514         local function SetLabel(self, text)
515                 if text and text ~= "" then
516                         self.label:SetText(text)
517                         self.label:Show()
518                         self.dropdown:SetPoint("TOPLEFT",self.frame,"TOPLEFT",-15,-18)
519                         self:SetHeight(44)
520                         self.alignoffset = 30
521                 else
522                         self.label:SetText("")
523                         self.label:Hide()
524                         self.dropdown:SetPoint("TOPLEFT",self.frame,"TOPLEFT",-15,0)
525                         self:SetHeight(26)
526                         self.alignoffset = 12
527                 end
528         end
529         
530         -- exported
531         local function SetValue(self, value)
532                 if self.list then
533                         self:SetText(self.list[value] or "")
534                 end
535                 self.value = value
536         end
537         
538         -- exported
539         local function GetValue(self)
540                 return self.value
541         end
542         
543         -- exported
544         local function SetItemValue(self, item, value)
545                 if not self.multiselect then return end
546                 for i, widget in self.pullout:IterateItems() do
547                         if widget.userdata.value == item then
548                                 if widget.SetValue then
549                                         widget:SetValue(value)
550                                 end
551                         end
552                 end
553                 ShowMultiText(self)
554         end
555         
556         -- exported
557         local function SetItemDisabled(self, item, disabled)
558                 for i, widget in self.pullout:IterateItems() do
559                         if widget.userdata.value == item then
560                                 widget:SetDisabled(disabled)
561                         end
562                 end
563         end
564         
565         local function AddListItem(self, value, text, itemType)
566                 if not itemType then itemType = "Dropdown-Item-Toggle" end
567                 local exists = AceGUI:GetWidgetVersion(itemType)
568                 if not exists then error(("The given item type, %q, does not exist within AceGUI-3.0"):format(tostring(itemType)), 2) end
569
570                 local item = AceGUI:Create(itemType)
571                 item:SetText(text)
572                 item.userdata.obj = self
573                 item.userdata.value = value
574                 item:SetCallback("OnValueChanged", OnItemValueChanged)
575                 self.pullout:AddItem(item)
576         end
577         
578         local function AddCloseButton(self)
579                 if not self.hasClose then
580                         local close = AceGUI:Create("Dropdown-Item-Execute")
581                         close:SetText(CLOSE)
582                         self.pullout:AddItem(close)
583                         self.hasClose = true
584                 end
585         end
586         
587         -- exported
588         local sortlist = {}
589         local function SetList(self, list, order, itemType)
590                 self.list = list
591                 self.pullout:Clear()
592                 self.hasClose = nil
593                 if not list then return end
594                 
595                 if type(order) ~= "table" then
596                         for v in pairs(list) do
597                                 sortlist[#sortlist + 1] = v
598                         end
599                         tsort(sortlist)
600                         
601                         for i, key in ipairs(sortlist) do
602                                 AddListItem(self, key, list[key], itemType)
603                                 sortlist[i] = nil
604                         end
605                 else
606                         for i, key in ipairs(order) do
607                                 AddListItem(self, key, list[key], itemType)
608                         end
609                 end
610                 if self.multiselect then
611                         ShowMultiText(self)
612                         AddCloseButton(self)
613                 end
614         end
615         
616         -- exported
617         local function AddItem(self, value, text, itemType)
618                 if self.list then
619                         self.list[value] = text
620                         AddListItem(self, value, text, itemType)
621                 end
622         end
623         
624         -- exported
625         local function SetMultiselect(self, multi)
626                 self.multiselect = multi
627                 if multi then
628                         ShowMultiText(self)
629                         AddCloseButton(self)
630                 end
631         end
632         
633         -- exported
634         local function GetMultiselect(self)
635                 return self.multiselect
636         end
637         
638         --[[ Constructor ]]--
639         
640         local function Constructor()
641                 local count = AceGUI:GetNextWidgetNum(widgetType)
642                 local frame = CreateFrame("Frame", nil, UIParent)
643                 local dropdown = CreateFrame("Frame", "AceGUI30DropDown"..count, frame, "UIDropDownMenuTemplate")
644                 
645                 local self = {}
646                 self.type = widgetType
647                 self.frame = frame
648                 self.dropdown = dropdown
649                 self.count = count
650                 frame.obj = self
651                 dropdown.obj = self
652                 
653                 self.OnRelease   = OnRelease
654                 self.OnAcquire   = OnAcquire
655                 
656                 self.ClearFocus  = ClearFocus
657
658                 self.SetText     = SetText
659                 self.SetValue    = SetValue
660                 self.GetValue    = GetValue
661                 self.SetList     = SetList
662                 self.SetLabel    = SetLabel
663                 self.SetDisabled = SetDisabled
664                 self.AddItem     = AddItem
665                 self.SetMultiselect = SetMultiselect
666                 self.GetMultiselect = GetMultiselect
667                 self.SetItemValue = SetItemValue
668                 self.SetItemDisabled = SetItemDisabled
669                 
670                 self.alignoffset = 30
671                 
672                 frame:SetScript("OnHide",Dropdown_OnHide)
673
674                 dropdown:ClearAllPoints()
675                 dropdown:SetPoint("TOPLEFT",frame,"TOPLEFT",-15,0)
676                 dropdown:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",17,0)
677                 dropdown:SetScript("OnHide", nil)
678
679                 local left = _G[dropdown:GetName() .. "Left"]
680                 local middle = _G[dropdown:GetName() .. "Middle"]
681                 local right = _G[dropdown:GetName() .. "Right"]
682                 
683                 middle:ClearAllPoints()
684                 right:ClearAllPoints()
685                 
686                 middle:SetPoint("LEFT", left, "RIGHT", 0, 0)
687                 middle:SetPoint("RIGHT", right, "LEFT", 0, 0)
688                 right:SetPoint("TOPRIGHT", dropdown, "TOPRIGHT", 0, 17)
689
690                 local button = _G[dropdown:GetName() .. "Button"]
691                 self.button = button
692                 button.obj = self
693                 button:SetScript("OnEnter",Control_OnEnter)
694                 button:SetScript("OnLeave",Control_OnLeave)
695                 button:SetScript("OnClick",Dropdown_TogglePullout)
696                 
697                 local text = _G[dropdown:GetName() .. "Text"]
698                 self.text = text
699                 text.obj = self
700                 text:ClearAllPoints()
701                 text:SetPoint("RIGHT", right, "RIGHT" ,-43, 2)
702                 text:SetPoint("LEFT", left, "LEFT", 25, 2)
703                 
704                 local label = frame:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
705                 label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
706                 label:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,0)
707                 label:SetJustifyH("LEFT")
708                 label:SetHeight(18)
709                 label:Hide()
710                 self.label = label
711
712                 AceGUI:RegisterAsWidget(self)
713                 return self
714         end
715         
716         AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion)
717 end