| yellowfive@57 | 1 --[[----------------------------------------------------------------------------- | 
| yellowfive@57 | 2 TabGroup Container | 
| yellowfive@57 | 3 Container that uses tabs on top to switch between groups. | 
| yellowfive@57 | 4 -------------------------------------------------------------------------------]] | 
| yellowfive@57 | 5 local Type, Version = "TabGroup", 35 | 
| yellowfive@57 | 6 local AceGUI = LibStub and LibStub("AceGUI-3.0", true) | 
| yellowfive@57 | 7 if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end | 
| yellowfive@57 | 8 | 
| yellowfive@57 | 9 -- Lua APIs | 
| yellowfive@57 | 10 local pairs, ipairs, assert, type, wipe = pairs, ipairs, assert, type, wipe | 
| yellowfive@57 | 11 | 
| yellowfive@57 | 12 -- WoW APIs | 
| yellowfive@57 | 13 local PlaySound = PlaySound | 
| yellowfive@57 | 14 local CreateFrame, UIParent = CreateFrame, UIParent | 
| yellowfive@57 | 15 local _G = _G | 
| yellowfive@57 | 16 | 
| yellowfive@57 | 17 -- Global vars/functions that we don't upvalue since they might get hooked, or upgraded | 
| yellowfive@57 | 18 -- List them here for Mikk's FindGlobals script | 
| yellowfive@57 | 19 -- GLOBALS: PanelTemplates_TabResize, PanelTemplates_SetDisabledTabState, PanelTemplates_SelectTab, PanelTemplates_DeselectTab | 
| yellowfive@57 | 20 | 
| yellowfive@57 | 21 -- local upvalue storage used by BuildTabs | 
| yellowfive@57 | 22 local widths = {} | 
| yellowfive@57 | 23 local rowwidths = {} | 
| yellowfive@57 | 24 local rowends = {} | 
| yellowfive@57 | 25 | 
| yellowfive@57 | 26 --[[----------------------------------------------------------------------------- | 
| yellowfive@57 | 27 Support functions | 
| yellowfive@57 | 28 -------------------------------------------------------------------------------]] | 
| yellowfive@57 | 29 local function UpdateTabLook(frame) | 
| yellowfive@57 | 30 	if frame.disabled then | 
| yellowfive@57 | 31 		PanelTemplates_SetDisabledTabState(frame) | 
| yellowfive@57 | 32 	elseif frame.selected then | 
| yellowfive@57 | 33 		PanelTemplates_SelectTab(frame) | 
| yellowfive@57 | 34 	else | 
| yellowfive@57 | 35 		PanelTemplates_DeselectTab(frame) | 
| yellowfive@57 | 36 	end | 
| yellowfive@57 | 37 end | 
| yellowfive@57 | 38 | 
| yellowfive@57 | 39 local function Tab_SetText(frame, text) | 
| yellowfive@57 | 40 	frame:_SetText(text) | 
| yellowfive@57 | 41 	local width = frame.obj.frame.width or frame.obj.frame:GetWidth() or 0 | 
| yellowfive@57 | 42 	PanelTemplates_TabResize(frame, 0, nil, nil, width, frame:GetFontString():GetStringWidth()) | 
| yellowfive@57 | 43 end | 
| yellowfive@57 | 44 | 
| yellowfive@57 | 45 local function Tab_SetSelected(frame, selected) | 
| yellowfive@57 | 46 	frame.selected = selected | 
| yellowfive@57 | 47 	UpdateTabLook(frame) | 
| yellowfive@57 | 48 end | 
| yellowfive@57 | 49 | 
| yellowfive@57 | 50 local function Tab_SetDisabled(frame, disabled) | 
| yellowfive@57 | 51 	frame.disabled = disabled | 
| yellowfive@57 | 52 	UpdateTabLook(frame) | 
| yellowfive@57 | 53 end | 
| yellowfive@57 | 54 | 
| yellowfive@57 | 55 local function BuildTabsOnUpdate(frame) | 
| yellowfive@57 | 56 	local self = frame.obj | 
| yellowfive@57 | 57 	self:BuildTabs() | 
| yellowfive@57 | 58 	frame:SetScript("OnUpdate", nil) | 
| yellowfive@57 | 59 end | 
| yellowfive@57 | 60 | 
| yellowfive@57 | 61 --[[----------------------------------------------------------------------------- | 
| yellowfive@57 | 62 Scripts | 
| yellowfive@57 | 63 -------------------------------------------------------------------------------]] | 
| yellowfive@57 | 64 local function Tab_OnClick(frame) | 
| yellowfive@57 | 65 	if not (frame.selected or frame.disabled) then | 
| yellowfive@57 | 66 		PlaySound("igCharacterInfoTab") | 
| yellowfive@57 | 67 		frame.obj:SelectTab(frame.value) | 
| yellowfive@57 | 68 	end | 
| yellowfive@57 | 69 end | 
| yellowfive@57 | 70 | 
| yellowfive@57 | 71 local function Tab_OnEnter(frame) | 
| yellowfive@57 | 72 	local self = frame.obj | 
| yellowfive@57 | 73 	self:Fire("OnTabEnter", self.tabs[frame.id].value, frame) | 
| yellowfive@57 | 74 end | 
| yellowfive@57 | 75 | 
| yellowfive@57 | 76 local function Tab_OnLeave(frame) | 
| yellowfive@57 | 77 	local self = frame.obj | 
| yellowfive@57 | 78 	self:Fire("OnTabLeave", self.tabs[frame.id].value, frame) | 
| yellowfive@57 | 79 end | 
| yellowfive@57 | 80 | 
| yellowfive@57 | 81 local function Tab_OnShow(frame) | 
| yellowfive@57 | 82 	_G[frame:GetName().."HighlightTexture"]:SetWidth(frame:GetTextWidth() + 30) | 
| yellowfive@57 | 83 end | 
| yellowfive@57 | 84 | 
| yellowfive@57 | 85 --[[----------------------------------------------------------------------------- | 
| yellowfive@57 | 86 Methods | 
| yellowfive@57 | 87 -------------------------------------------------------------------------------]] | 
| yellowfive@57 | 88 local methods = { | 
| yellowfive@57 | 89 	["OnAcquire"] = function(self) | 
| yellowfive@57 | 90 		self:SetTitle() | 
| yellowfive@57 | 91 	end, | 
| yellowfive@57 | 92 | 
| yellowfive@57 | 93 	["OnRelease"] = function(self) | 
| yellowfive@57 | 94 		self.status = nil | 
| yellowfive@57 | 95 		for k in pairs(self.localstatus) do | 
| yellowfive@57 | 96 			self.localstatus[k] = nil | 
| yellowfive@57 | 97 		end | 
| yellowfive@57 | 98 		self.tablist = nil | 
| yellowfive@57 | 99 		for _, tab in pairs(self.tabs) do | 
| yellowfive@57 | 100 			tab:Hide() | 
| yellowfive@57 | 101 		end | 
| yellowfive@57 | 102 	end, | 
| yellowfive@57 | 103 | 
| yellowfive@57 | 104 	["CreateTab"] = function(self, id) | 
| yellowfive@57 | 105 		local tabname = ("AceGUITabGroup%dTab%d"):format(self.num, id) | 
| yellowfive@57 | 106 		local tab = CreateFrame("Button", tabname, self.border, "OptionsFrameTabButtonTemplate") | 
| yellowfive@57 | 107 		tab.obj = self | 
| yellowfive@57 | 108 		tab.id = id | 
| yellowfive@57 | 109 | 
| yellowfive@57 | 110 		tab.text = _G[tabname .. "Text"] | 
| yellowfive@57 | 111 		tab.text:ClearAllPoints() | 
| yellowfive@57 | 112 		tab.text:SetPoint("LEFT", 14, -3) | 
| yellowfive@57 | 113 		tab.text:SetPoint("RIGHT", -12, -3) | 
| yellowfive@57 | 114 | 
| yellowfive@57 | 115 		tab:SetScript("OnClick", Tab_OnClick) | 
| yellowfive@57 | 116 		tab:SetScript("OnEnter", Tab_OnEnter) | 
| yellowfive@57 | 117 		tab:SetScript("OnLeave", Tab_OnLeave) | 
| yellowfive@57 | 118 		tab:SetScript("OnShow", Tab_OnShow) | 
| yellowfive@57 | 119 | 
| yellowfive@57 | 120 		tab._SetText = tab.SetText | 
| yellowfive@57 | 121 		tab.SetText = Tab_SetText | 
| yellowfive@57 | 122 		tab.SetSelected = Tab_SetSelected | 
| yellowfive@57 | 123 		tab.SetDisabled = Tab_SetDisabled | 
| yellowfive@57 | 124 | 
| yellowfive@57 | 125 		return tab | 
| yellowfive@57 | 126 	end, | 
| yellowfive@57 | 127 | 
| yellowfive@57 | 128 	["SetTitle"] = function(self, text) | 
| yellowfive@57 | 129 		self.titletext:SetText(text or "") | 
| yellowfive@57 | 130 		if text and text ~= "" then | 
| yellowfive@57 | 131 			self.alignoffset = 25 | 
| yellowfive@57 | 132 		else | 
| yellowfive@57 | 133 			self.alignoffset = 18 | 
| yellowfive@57 | 134 		end | 
| yellowfive@57 | 135 		self:BuildTabs() | 
| yellowfive@57 | 136 	end, | 
| yellowfive@57 | 137 | 
| yellowfive@57 | 138 	["SetStatusTable"] = function(self, status) | 
| yellowfive@57 | 139 		assert(type(status) == "table") | 
| yellowfive@57 | 140 		self.status = status | 
| yellowfive@57 | 141 	end, | 
| yellowfive@57 | 142 | 
| yellowfive@57 | 143 	["SelectTab"] = function(self, value) | 
| yellowfive@57 | 144 		local status = self.status or self.localstatus | 
| yellowfive@57 | 145 		local found | 
| yellowfive@57 | 146 		for i, v in ipairs(self.tabs) do | 
| yellowfive@57 | 147 			if v.value == value then | 
| yellowfive@57 | 148 				v:SetSelected(true) | 
| yellowfive@57 | 149 				found = true | 
| yellowfive@57 | 150 			else | 
| yellowfive@57 | 151 				v:SetSelected(false) | 
| yellowfive@57 | 152 			end | 
| yellowfive@57 | 153 		end | 
| yellowfive@57 | 154 		status.selected = value | 
| yellowfive@57 | 155 		if found then | 
| yellowfive@57 | 156 			self:Fire("OnGroupSelected",value) | 
| yellowfive@57 | 157 		end | 
| yellowfive@57 | 158 	end, | 
| yellowfive@57 | 159 | 
| yellowfive@57 | 160 	["SetTabs"] = function(self, tabs) | 
| yellowfive@57 | 161 		self.tablist = tabs | 
| yellowfive@57 | 162 		self:BuildTabs() | 
| yellowfive@57 | 163 	end, | 
| yellowfive@57 | 164 | 
| yellowfive@57 | 165 | 
| yellowfive@57 | 166 	["BuildTabs"] = function(self) | 
| yellowfive@57 | 167 		local hastitle = (self.titletext:GetText() and self.titletext:GetText() ~= "") | 
| yellowfive@57 | 168 		local status = self.status or self.localstatus | 
| yellowfive@57 | 169 		local tablist = self.tablist | 
| yellowfive@57 | 170 		local tabs = self.tabs | 
| yellowfive@57 | 171 | 
| yellowfive@57 | 172 		if not tablist then return end | 
| yellowfive@57 | 173 | 
| yellowfive@57 | 174 		local width = self.frame.width or self.frame:GetWidth() or 0 | 
| yellowfive@57 | 175 | 
| yellowfive@57 | 176 		wipe(widths) | 
| yellowfive@57 | 177 		wipe(rowwidths) | 
| yellowfive@57 | 178 		wipe(rowends) | 
| yellowfive@57 | 179 | 
| yellowfive@57 | 180 		--Place Text into tabs and get thier initial width | 
| yellowfive@57 | 181 		for i, v in ipairs(tablist) do | 
| yellowfive@57 | 182 			local tab = tabs[i] | 
| yellowfive@57 | 183 			if not tab then | 
| yellowfive@57 | 184 				tab = self:CreateTab(i) | 
| yellowfive@57 | 185 				tabs[i] = tab | 
| yellowfive@57 | 186 			end | 
| yellowfive@57 | 187 | 
| yellowfive@57 | 188 			tab:Show() | 
| yellowfive@57 | 189 			tab:SetText(v.text) | 
| yellowfive@57 | 190 			tab:SetDisabled(v.disabled) | 
| yellowfive@57 | 191 			tab.value = v.value | 
| yellowfive@57 | 192 | 
| yellowfive@57 | 193 			widths[i] = tab:GetWidth() - 6 --tabs are anchored 10 pixels from the right side of the previous one to reduce spacing, but add a fixed 4px padding for the text | 
| yellowfive@57 | 194 		end | 
| yellowfive@57 | 195 | 
| yellowfive@57 | 196 		for i = (#tablist)+1, #tabs, 1 do | 
| yellowfive@57 | 197 			tabs[i]:Hide() | 
| yellowfive@57 | 198 		end | 
| yellowfive@57 | 199 | 
| yellowfive@57 | 200 		--First pass, find the minimum number of rows needed to hold all tabs and the initial tab layout | 
| yellowfive@57 | 201 		local numtabs = #tablist | 
| yellowfive@57 | 202 		local numrows = 1 | 
| yellowfive@57 | 203 		local usedwidth = 0 | 
| yellowfive@57 | 204 | 
| yellowfive@57 | 205 		for i = 1, #tablist do | 
| yellowfive@57 | 206 			--If this is not the first tab of a row and there isn't room for it | 
| yellowfive@57 | 207 			if usedwidth ~= 0 and (width - usedwidth - widths[i]) < 0 then | 
| yellowfive@57 | 208 				rowwidths[numrows] = usedwidth + 10 --first tab in each row takes up an extra 10px | 
| yellowfive@57 | 209 				rowends[numrows] = i - 1 | 
| yellowfive@57 | 210 				numrows = numrows + 1 | 
| yellowfive@57 | 211 				usedwidth = 0 | 
| yellowfive@57 | 212 			end | 
| yellowfive@57 | 213 			usedwidth = usedwidth + widths[i] | 
| yellowfive@57 | 214 		end | 
| yellowfive@57 | 215 		rowwidths[numrows] = usedwidth + 10 --first tab in each row takes up an extra 10px | 
| yellowfive@57 | 216 		rowends[numrows] = #tablist | 
| yellowfive@57 | 217 | 
| yellowfive@57 | 218 		--Fix for single tabs being left on the last row, move a tab from the row above if applicable | 
| yellowfive@57 | 219 		if numrows > 1 then | 
| yellowfive@57 | 220 			--if the last row has only one tab | 
| yellowfive@57 | 221 			if rowends[numrows-1] == numtabs-1 then | 
| yellowfive@57 | 222 				--if there are more than 2 tabs in the 2nd last row | 
| yellowfive@57 | 223 				if (numrows == 2 and rowends[numrows-1] > 2) or (rowends[numrows] - rowends[numrows-1] > 2) then | 
| yellowfive@57 | 224 					--move 1 tab from the second last row to the last, if there is enough space | 
| yellowfive@57 | 225 					if (rowwidths[numrows] + widths[numtabs-1]) <= width then | 
| yellowfive@57 | 226 						rowends[numrows-1] = rowends[numrows-1] - 1 | 
| yellowfive@57 | 227 						rowwidths[numrows] = rowwidths[numrows] + widths[numtabs-1] | 
| yellowfive@57 | 228 						rowwidths[numrows-1] = rowwidths[numrows-1] - widths[numtabs-1] | 
| yellowfive@57 | 229 					end | 
| yellowfive@57 | 230 				end | 
| yellowfive@57 | 231 			end | 
| yellowfive@57 | 232 		end | 
| yellowfive@57 | 233 | 
| yellowfive@57 | 234 		--anchor the rows as defined and resize tabs to fill thier row | 
| yellowfive@57 | 235 		local starttab = 1 | 
| yellowfive@57 | 236 		for row, endtab in ipairs(rowends) do | 
| yellowfive@57 | 237 			local first = true | 
| yellowfive@57 | 238 			for tabno = starttab, endtab do | 
| yellowfive@57 | 239 				local tab = tabs[tabno] | 
| yellowfive@57 | 240 				tab:ClearAllPoints() | 
| yellowfive@57 | 241 				if first then | 
| yellowfive@57 | 242 					tab:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 0, -(hastitle and 14 or 7)-(row-1)*20 ) | 
| yellowfive@57 | 243 					first = false | 
| yellowfive@57 | 244 				else | 
| yellowfive@57 | 245 					tab:SetPoint("LEFT", tabs[tabno-1], "RIGHT", -10, 0) | 
| yellowfive@57 | 246 				end | 
| yellowfive@57 | 247 			end | 
| yellowfive@57 | 248 | 
| yellowfive@57 | 249 			-- equal padding for each tab to fill the available width, | 
| yellowfive@57 | 250 			-- if the used space is above 75% already | 
| yellowfive@57 | 251 			-- the 18 pixel is the typical width of a scrollbar, so we can have a tab group inside a scrolling frame, | 
| yellowfive@57 | 252 			-- and not have the tabs jump around funny when switching between tabs that need scrolling and those that don't | 
| yellowfive@57 | 253 			local padding = 0 | 
| yellowfive@57 | 254 			if not (numrows == 1 and rowwidths[1] < width*0.75 - 18) then | 
| yellowfive@57 | 255 				padding = (width - rowwidths[row]) / (endtab - starttab+1) | 
| yellowfive@57 | 256 			end | 
| yellowfive@57 | 257 | 
| yellowfive@57 | 258 			for i = starttab, endtab do | 
| yellowfive@57 | 259 				PanelTemplates_TabResize(tabs[i], padding + 4, nil, nil, width, tabs[i]:GetFontString():GetStringWidth()) | 
| yellowfive@57 | 260 			end | 
| yellowfive@57 | 261 			starttab = endtab + 1 | 
| yellowfive@57 | 262 		end | 
| yellowfive@57 | 263 | 
| yellowfive@57 | 264 		self.borderoffset = (hastitle and 17 or 10)+((numrows)*20) | 
| yellowfive@57 | 265 		self.border:SetPoint("TOPLEFT", 1, -self.borderoffset) | 
| yellowfive@57 | 266 	end, | 
| yellowfive@57 | 267 | 
| yellowfive@57 | 268 	["OnWidthSet"] = function(self, width) | 
| yellowfive@57 | 269 		local content = self.content | 
| yellowfive@57 | 270 		local contentwidth = width - 60 | 
| yellowfive@57 | 271 		if contentwidth < 0 then | 
| yellowfive@57 | 272 			contentwidth = 0 | 
| yellowfive@57 | 273 		end | 
| yellowfive@57 | 274 		content:SetWidth(contentwidth) | 
| yellowfive@57 | 275 		content.width = contentwidth | 
| yellowfive@57 | 276 		self:BuildTabs(self) | 
| yellowfive@57 | 277 		self.frame:SetScript("OnUpdate", BuildTabsOnUpdate) | 
| yellowfive@57 | 278 	end, | 
| yellowfive@57 | 279 | 
| yellowfive@57 | 280 	["OnHeightSet"] = function(self, height) | 
| yellowfive@57 | 281 		local content = self.content | 
| yellowfive@57 | 282 		local contentheight = height - (self.borderoffset + 23) | 
| yellowfive@57 | 283 		if contentheight < 0 then | 
| yellowfive@57 | 284 			contentheight = 0 | 
| yellowfive@57 | 285 		end | 
| yellowfive@57 | 286 		content:SetHeight(contentheight) | 
| yellowfive@57 | 287 		content.height = contentheight | 
| yellowfive@57 | 288 	end, | 
| yellowfive@57 | 289 | 
| yellowfive@57 | 290 	["LayoutFinished"] = function(self, width, height) | 
| yellowfive@57 | 291 		if self.noAutoHeight then return end | 
| yellowfive@57 | 292 		self:SetHeight((height or 0) + (self.borderoffset + 23)) | 
| yellowfive@57 | 293 	end | 
| yellowfive@57 | 294 } | 
| yellowfive@57 | 295 | 
| yellowfive@57 | 296 --[[----------------------------------------------------------------------------- | 
| yellowfive@57 | 297 Constructor | 
| yellowfive@57 | 298 -------------------------------------------------------------------------------]] | 
| yellowfive@57 | 299 local PaneBackdrop  = { | 
| yellowfive@57 | 300 	bgFile = "Interface\\ChatFrame\\ChatFrameBackground", | 
| yellowfive@57 | 301 	edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", | 
| yellowfive@57 | 302 	tile = true, tileSize = 16, edgeSize = 16, | 
| yellowfive@57 | 303 	insets = { left = 3, right = 3, top = 5, bottom = 3 } | 
| yellowfive@57 | 304 } | 
| yellowfive@57 | 305 | 
| yellowfive@57 | 306 local function Constructor() | 
| yellowfive@57 | 307 	local num = AceGUI:GetNextWidgetNum(Type) | 
| yellowfive@57 | 308 	local frame = CreateFrame("Frame",nil,UIParent) | 
| yellowfive@57 | 309 	frame:SetHeight(100) | 
| yellowfive@57 | 310 	frame:SetWidth(100) | 
| yellowfive@57 | 311 	frame:SetFrameStrata("FULLSCREEN_DIALOG") | 
| yellowfive@57 | 312 | 
| yellowfive@57 | 313 	local titletext = frame:CreateFontString(nil,"OVERLAY","GameFontNormal") | 
| yellowfive@57 | 314 	titletext:SetPoint("TOPLEFT", 14, 0) | 
| yellowfive@57 | 315 	titletext:SetPoint("TOPRIGHT", -14, 0) | 
| yellowfive@57 | 316 	titletext:SetJustifyH("LEFT") | 
| yellowfive@57 | 317 	titletext:SetHeight(18) | 
| yellowfive@57 | 318 	titletext:SetText("") | 
| yellowfive@57 | 319 | 
| yellowfive@57 | 320 	local border = CreateFrame("Frame", nil, frame) | 
| yellowfive@57 | 321 	border:SetPoint("TOPLEFT", 1, -27) | 
| yellowfive@57 | 322 	border:SetPoint("BOTTOMRIGHT", -1, 3) | 
| yellowfive@57 | 323 	border:SetBackdrop(PaneBackdrop) | 
| yellowfive@57 | 324 	border:SetBackdropColor(0.1, 0.1, 0.1, 0.5) | 
| yellowfive@57 | 325 	border:SetBackdropBorderColor(0.4, 0.4, 0.4) | 
| yellowfive@57 | 326 | 
| yellowfive@57 | 327 	local content = CreateFrame("Frame", nil, border) | 
| yellowfive@57 | 328 	content:SetPoint("TOPLEFT", 10, -7) | 
| yellowfive@57 | 329 	content:SetPoint("BOTTOMRIGHT", -10, 7) | 
| yellowfive@57 | 330 | 
| yellowfive@57 | 331 	local widget = { | 
| yellowfive@57 | 332 		num          = num, | 
| yellowfive@57 | 333 		frame        = frame, | 
| yellowfive@57 | 334 		localstatus  = {}, | 
| yellowfive@57 | 335 		alignoffset  = 18, | 
| yellowfive@57 | 336 		titletext    = titletext, | 
| yellowfive@57 | 337 		border       = border, | 
| yellowfive@57 | 338 		borderoffset = 27, | 
| yellowfive@57 | 339 		tabs         = {}, | 
| yellowfive@57 | 340 		content      = content, | 
| yellowfive@57 | 341 		type         = Type | 
| yellowfive@57 | 342 	} | 
| yellowfive@57 | 343 	for method, func in pairs(methods) do | 
| yellowfive@57 | 344 		widget[method] = func | 
| yellowfive@57 | 345 	end | 
| yellowfive@57 | 346 | 
| yellowfive@57 | 347 	return AceGUI:RegisterAsContainer(widget) | 
| yellowfive@57 | 348 end | 
| yellowfive@57 | 349 | 
| yellowfive@57 | 350 AceGUI:RegisterWidgetType(Type, Constructor, Version) |