Blizzard_TradeSkillUI/Blizzard_TradeSkillRecipeList.lua

Blizzard_TradeSkillUI/Blizzard_TradeSkillRecipeList.lua (9.0.2.36753; unchanged since 9.0.2.36665)
  1. local ROW_HEIGHT = 16;
  2. local LIST_FULL_HEIGHT = 405;
  3. local FAVORITES_CATEGORY_ID = -1;
  4. TradeSkillRecipeListMixin = {};
  5. local LEARNED_TAB = 1;
  6. local UNLEARNED_TAB = 2;
  7. function TradeSkillRecipeListMixin:OnLoad()
  8. HybridScrollFrame_CreateButtons(self, "TradeSkillRowButtonTemplate", 0, 0);
  9. self.update = self.RefreshDisplay;
  10. self.stepSize = ROW_HEIGHT * 2;
  11. self.dataList = {};
  12. PanelTemplates_SetNumTabs(self, 2);
  13. UIDropDownMenu_Initialize(self.RecipeOptionsMenu, function(...) self:InitRecipeOptionsMenu(...) end, "MENU");
  14. self:OnLearnedTabClicked();
  15. end
  16. function TradeSkillRecipeListMixin:InitRecipeOptionsMenu(dropdown, level)
  17. local info = UIDropDownMenu_CreateInfo();
  18. info.notCheckable = true;
  19. info.disabled = nil;
  20. local isFavorite = self.contextMenuRecipeID and C_TradeSkillUI.IsRecipeFavorite(self.contextMenuRecipeID);
  21. if isFavorite then
  22. info.text = BATTLE_PET_UNFAVORITE;
  23. info.func = function()
  24. C_TradeSkillUI.SetRecipeFavorite(self.contextMenuRecipeID, false);
  25. end
  26. else
  27. info.text = BATTLE_PET_FAVORITE;
  28. info.func = function()
  29. C_TradeSkillUI.SetRecipeFavorite(self.contextMenuRecipeID, true);
  30. end
  31. end
  32. UIDropDownMenu_AddButton(info, level);
  33. info.disabled = nil;
  34. info.text = CANCEL
  35. info.func = nil
  36. UIDropDownMenu_AddButton(info, level)
  37. end
  38. function TradeSkillRecipeListMixin:OnUpdate()
  39. if self.pendingRefresh then
  40. if self.tradeSkillChanged then
  41. self.collapsedCategories = {};
  42. self.tradeSkillChanged = nil;
  43. end
  44. if C_TradeSkillUI.IsRecipeSearchInProgress() then
  45. return;
  46. end
  47. if not C_TradeSkillUI.IsTradeSkillReady() then
  48. wipe(self.dataList);
  49. self:SetSelectedRecipeID(nil);
  50. self:RefreshDisplay();
  51. return;
  52. end
  53. self:RebuildDataList();
  54. if self:VerifySelection() then
  55. self:RefreshDisplay();
  56. end
  57. self.pendingRefresh = nil;
  58. if self.pendingForceRecipeIDIntoView then
  59. self:SelectedAndForceRecipeIDIntoView(self.pendingForceRecipeIDIntoView);
  60. self.pendingForceRecipeIDIntoView = nil;
  61. end
  62. end
  63. end
  64. function TradeSkillRecipeListMixin:OnDataSourceChanging()
  65. wipe(self.dataList);
  66. self:SetSelectedRecipeID(nil);
  67. for i, tradeSkillButton in ipairs(self.buttons) do
  68. tradeSkillButton:Clear();
  69. end
  70. self:Refresh();
  71. end
  72. function TradeSkillRecipeListMixin:OnDataSourceChanged(tradeSkillChanged)
  73. self.selectedRecipeID = nil;
  74. self.tradeSkillChanged = self.tradeSkillChanged or tradeSkillChanged;
  75. local isNPCCrafting = C_TradeSkillUI.IsNPCCrafting();
  76. self:OnLearnedTabClicked();
  77. self.LearnedTab:SetShown(not isNPCCrafting);
  78. self.UnlearnedTab:SetShown(not isNPCCrafting);
  79. if (not isNPCCrafting and not GetCVarBitfield("closedInfoFrames", LE_FRAME_TUTORIAL_TRADESKILL_UNLEARNED_TAB)) then
  80. SetButtonPulse(self.UnlearnedTab, 60, 1);
  81. end
  82. self:Refresh();
  83. end
  84. function TradeSkillRecipeListMixin:OnHeaderButtonClicked(categoryButton, categoryInfo, mouseButton)
  85. PlaySound(SOUNDKIT.UI_90_BLACKSMITHING_TREECLICK);
  86. self:SetCategoryCollapsed(categoryInfo.categoryID, not self:IsCategoryCollapsed(categoryInfo.categoryID));
  87. end
  88. function TradeSkillRecipeListMixin:SetCategoryCollapsed(categoryID, collapsed)
  89. if self.collapsedCategories[categoryID] ~= collapsed then
  90. self.collapsedCategories[categoryID] = collapsed;
  91. self:Refresh();
  92. end
  93. end
  94. function TradeSkillRecipeListMixin:IsCategoryCollapsed(categoryID)
  95. return self.collapsedCategories[categoryID] == true;
  96. end
  97. function TradeSkillRecipeListMixin:OnRecipeButtonClicked(recipeButton, recipeInfo, mouseButton)
  98. if mouseButton == "LeftButton" then
  99. self:SetSelectedRecipeID(recipeInfo.recipeID);
  100. PlaySound(SOUNDKIT.UI_90_BLACKSMITHING_TREEITEMCLICK);
  101. elseif mouseButton == "RightButton" then
  102. if recipeInfo.learned and not C_TradeSkillUI.IsTradeSkillGuild() and not C_TradeSkillUI.IsNPCCrafting() and not C_TradeSkillUI.IsTradeSkillLinked() then
  103. self.contextMenuRecipeID = recipeInfo.recipeID;
  104. ToggleDropDownMenu(1, nil, self.RecipeOptionsMenu, recipeButton, 0, 0);
  105. PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
  106. end
  107. end
  108. end
  109. function TradeSkillRecipeListMixin:OnLearnedTabClicked()
  110. PanelTemplates_SetTab(self, LEARNED_TAB);
  111. C_TradeSkillUI.SetOnlyShowLearnedRecipes(true);
  112. C_TradeSkillUI.SetOnlyShowUnlearnedRecipes(false);
  113. self:Refresh();
  114. end
  115. function TradeSkillRecipeListMixin:OnUnlearnedTabClicked()
  116. PanelTemplates_SetTab(self, UNLEARNED_TAB);
  117. C_TradeSkillUI.SetOnlyShowLearnedRecipes(false);
  118. C_TradeSkillUI.SetOnlyShowUnlearnedRecipes(true);
  119. SetButtonPulse(self.UnlearnedTab, 0, 1);
  120. SetCVarBitfield("closedInfoFrames", LE_FRAME_TUTORIAL_TRADESKILL_UNLEARNED_TAB, true);
  121. self:Refresh();
  122. end
  123. function TradeSkillRecipeListMixin:Refresh()
  124. self.pendingRefresh = true;
  125. end
  126. function TradeSkillRecipeListMixin:SetRecipeChangedCallback(recipeChangedCallback)
  127. self.recipeChangedCallback = recipeChangedCallback;
  128. end
  129. function TradeSkillRecipeListMixin:FindBestStarRankLinksForRecipe(recipeInfo)
  130. local startingRecipeInfo = recipeInfo;
  131. while startingRecipeInfo.previousRecipeInfo do
  132. startingRecipeInfo = startingRecipeInfo.previousRecipeInfo;
  133. end
  134. local bestRecipeInfo = startingRecipeInfo;
  135. while bestRecipeInfo.nextRecipeInfo and bestRecipeInfo.nextRecipeInfo.learned do
  136. bestRecipeInfo = bestRecipeInfo.nextRecipeInfo;
  137. end
  138. return bestRecipeInfo;
  139. end
  140. local function IsFavoriteRecipesCategoriesVisible(recipeInfo)
  141. local categoryData = C_TradeSkillUI.GetCategoryInfo(recipeInfo.categoryID);
  142. if categoryData.enabled then
  143. if categoryData.parentCategoryID then
  144. local parentCategoryData = C_TradeSkillUI.GetCategoryInfo(recipeInfo.categoryID);
  145. return parentCategoryData.enabled;
  146. end
  147. return true;
  148. end
  149. return false;
  150. end
  151. function TradeSkillRecipeListMixin:RebuildDataList()
  152. wipe(self.dataList);
  153. self.recipeIDs = C_TradeSkillUI.GetFilteredRecipeIDs(self.recipeIDs);
  154. local currentCategoryID, currentParentCategoryID;
  155. local isCurrentCategoryEnabled, isCurrentParentCategoryEnabled = true, true;
  156. local favoritesIndex = nil;
  157. local starRankLinks = {};
  158. local emptyCategoriesToAdd = {};
  159. if (not C_TradeSkillUI.GetOnlyShowUnlearnedRecipes()) then
  160. local categories = {C_TradeSkillUI.GetCategories()};
  161. for i, categoryID in ipairs(categories) do
  162. if (C_TradeSkillUI.IsEmptySkillLineCategory(categoryID)) then
  163. local categoryData = C_TradeSkillUI.GetCategoryInfo(categoryID);
  164. table.insert(emptyCategoriesToAdd, categoryData);
  165. end
  166. end
  167. end
  168. for i, recipeID in ipairs(self.recipeIDs) do
  169. if not starRankLinks[recipeID] then
  170. local recipeInfo = C_TradeSkillUI.GetRecipeInfo(recipeID);
  171. TradeSkillFrame_GenerateRankLinks(recipeInfo, starRankLinks);
  172. recipeInfo = self:FindBestStarRankLinksForRecipe(recipeInfo);
  173. if recipeInfo.favorite then
  174. if IsFavoriteRecipesCategoriesVisible(recipeInfo) then
  175. if not favoritesIndex then
  176. -- Insert the special favorites category header
  177. favoritesIndex = 1;
  178. table.insert(self.dataList, favoritesIndex, { type = "header", numIndents = 0, name = FAVORITES, categoryID = FAVORITES_CATEGORY_ID, });
  179. favoritesIndex = favoritesIndex + 1; -- Start inserting new favorites here
  180. end
  181. if not self:IsCategoryCollapsed(FAVORITES_CATEGORY_ID) then
  182. recipeInfo.numIndents = 0; -- No subcategories under favorites, force no indents
  183. table.insert(self.dataList, favoritesIndex, recipeInfo);
  184. favoritesIndex = favoritesIndex + 1;
  185. end
  186. end
  187. else
  188. if recipeInfo.categoryID ~= currentCategoryID then
  189. local categoryData = C_TradeSkillUI.GetCategoryInfo(recipeInfo.categoryID);
  190. isCurrentCategoryEnabled = categoryData.enabled;
  191. if categoryData.parentCategoryID ~= currentParentCategoryID then
  192. currentParentCategoryID = categoryData.parentCategoryID;
  193. if currentParentCategoryID then
  194. local parentCategoryData = C_TradeSkillUI.GetCategoryInfo(currentParentCategoryID);
  195. isCurrentParentCategoryEnabled = parentCategoryData.enabled;
  196. if isCurrentParentCategoryEnabled then
  197. -- Make sure any base-level empty categories with higher priority are added first.
  198. while (#emptyCategoriesToAdd > 0) and (emptyCategoriesToAdd[1].uiOrder < parentCategoryData.uiOrder) do
  199. table.insert(self.dataList, emptyCategoriesToAdd[1]);
  200. table.remove(emptyCategoriesToAdd, 1);
  201. end
  202. table.insert(self.dataList, parentCategoryData);
  203. end
  204. else
  205. isCurrentParentCategoryEnabled = true;
  206. end
  207. end
  208. if isCurrentCategoryEnabled and isCurrentParentCategoryEnabled and (not currentParentCategoryID or not self:IsCategoryCollapsed(currentParentCategoryID)) then
  209. table.insert(self.dataList, categoryData);
  210. currentCategoryID = recipeInfo.categoryID;
  211. end
  212. end
  213. if isCurrentCategoryEnabled and isCurrentParentCategoryEnabled and (not currentParentCategoryID or not self:IsCategoryCollapsed(currentParentCategoryID)) and not self:IsCategoryCollapsed(currentCategoryID) then
  214. table.insert(self.dataList, recipeInfo);
  215. end
  216. end
  217. end
  218. end
  219. for i, emptyCategory in ipairs(emptyCategoriesToAdd) do
  220. table.insert(self.dataList, emptyCategory);
  221. end
  222. end
  223. function TradeSkillRecipeListMixin:VerifySelection()
  224. local firstVisibleRecipeID = nil;
  225. if self.selectedRecipeID then
  226. for i, listData in ipairs(self.dataList) do
  227. if listData.type == "recipe" then
  228. if self.selectedRecipeID == listData.recipeID then
  229. -- Recipe is still valid, do nothing
  230. return true;
  231. end
  232. if not firstVisibleRecipeID then
  233. firstVisibleRecipeID = listData.recipeID;
  234. end
  235. local currentRecipeInfo = listData.previousRecipeInfo;
  236. while currentRecipeInfo do
  237. if self.selectedRecipeID == currentRecipeInfo.recipeID then
  238. -- Recipe rank changed, switch to the new rank
  239. return not self:SetSelectedRecipeID(listData.recipeID);
  240. end
  241. currentRecipeInfo = currentRecipeInfo.previousRecipeInfo;
  242. end
  243. local currentRecipeInfo = listData.nextRecipeInfo;
  244. while currentRecipeInfo do
  245. if self.selectedRecipeID == currentRecipeInfo.recipeID then
  246. -- Recipe rank changed, switch to the new rank
  247. return not self:SetSelectedRecipeID(listData.recipeID);
  248. end
  249. currentRecipeInfo = currentRecipeInfo.nextRecipeInfo;
  250. end
  251. end
  252. end
  253. else
  254. for i, listData in ipairs(self.dataList) do
  255. if listData.type == "recipe" then
  256. firstVisibleRecipeID = listData.recipeID;
  257. break;
  258. end
  259. end
  260. end
  261. -- Couldn't find recipe, probably filtered
  262. -- Try using the first visible recipe
  263. return not self:SetSelectedRecipeID(firstVisibleRecipeID);
  264. end
  265. local function GetUnfilteredSubCategoryName(categoryID, ...)
  266. local areAllUnfiltered = true;
  267. for i = 1, select("#", ...) do
  268. local subCategoryID = select(i, ...);
  269. if C_TradeSkillUI.IsRecipeCategoryFiltered(categoryID, subCategoryID) then
  270. areAllUnfiltered = false;
  271. break;
  272. end
  273. end
  274. if areAllUnfiltered then
  275. return nil;
  276. end
  277. for i = 1, select("#", ...) do
  278. local subCategoryID = select(i, ...);
  279. if not C_TradeSkillUI.IsRecipeCategoryFiltered(categoryID, subCategoryID) then
  280. local subCategoryData = C_TradeSkillUI.GetCategoryInfo(subCategoryID);
  281. return subCategoryData.name;
  282. end
  283. end
  284. end
  285. local function GetUnfilteredCategoryName(...)
  286. -- Try subCategories first
  287. for i = 1, select("#", ...) do
  288. local categoryID = select(i, ...);
  289. local subCategoryName = GetUnfilteredSubCategoryName(categoryID, C_TradeSkillUI.GetSubCategories(categoryID));
  290. if subCategoryName then
  291. return subCategoryName;
  292. end
  293. end
  294. for i = 1, select("#", ...) do
  295. local categoryID = select(i, ...);
  296. if not C_TradeSkillUI.IsRecipeCategoryFiltered(categoryID) then
  297. local categoryData = C_TradeSkillUI.GetCategoryInfo(categoryID);
  298. return categoryData.name;
  299. end
  300. end
  301. return nil;
  302. end
  303. local function GetUnfilteredInventorySlotName(...)
  304. for i = 1, select("#", ...) do
  305. if not C_TradeSkillUI.IsInventorySlotFiltered(i) then
  306. local inventorySlot = select(i, ...);
  307. return inventorySlot;
  308. end
  309. end
  310. return nil;
  311. end
  312. function TradeSkillRecipeListMixin:UpdateFilterBar()
  313. local filters = nil;
  314. if C_TradeSkillUI.GetOnlyShowMakeableRecipes() then
  315. filters = filters or {};
  316. filters[#filters + 1] = CRAFT_IS_MAKEABLE;
  317. end
  318. if C_TradeSkillUI.GetOnlyShowSkillUpRecipes() then
  319. filters = filters or {};
  320. filters[#filters + 1] = TRADESKILL_FILTER_HAS_SKILL_UP;
  321. end
  322. if C_TradeSkillUI.AnyRecipeCategoriesFiltered() then
  323. local categoryName = GetUnfilteredCategoryName(C_TradeSkillUI.GetCategories());
  324. if categoryName then
  325. filters = filters or {};
  326. filters[#filters + 1] = categoryName;
  327. end
  328. end
  329. if C_TradeSkillUI.AreAnyInventorySlotsFiltered() then
  330. local inventorySlot = GetUnfilteredInventorySlotName(C_TradeSkillUI.GetAllFilterableInventorySlots());
  331. if inventorySlot then
  332. filters = filters or {};
  333. filters[#filters + 1] = inventorySlot;
  334. end
  335. end
  336. if not TradeSkillFrame_AreAllSourcesUnfiltered() then
  337. local numSources = C_PetJournal.GetNumPetSources();
  338. for i = 1, numSources do
  339. if C_TradeSkillUI.IsAnyRecipeFromSource(i) and C_TradeSkillUI.IsRecipeSourceTypeFiltered(i) then
  340. filters = filters or {};
  341. filters[#filters + 1] = _G["BATTLE_PET_SOURCE_"..i];
  342. end
  343. end
  344. end
  345. if filters == nil then
  346. self.FilterBar:Hide();
  347. self:SetHeight(LIST_FULL_HEIGHT);
  348. self:SetPoint("TOPLEFT", 7, -83);
  349. self.LearnedTab:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 10, 3);
  350. self.scrollBar:SetPoint("TOPLEFT", self, "TOPRIGHT", 1, -14);
  351. else
  352. self:SetHeight(LIST_FULL_HEIGHT - ROW_HEIGHT);
  353. self.FilterBar:Show();
  354. self:SetPoint("TOPLEFT", 7, -83 - ROW_HEIGHT);
  355. self.LearnedTab:SetPoint("BOTTOMLEFT", self, "TOPLEFT", 10, 3 + ROW_HEIGHT);
  356. self.scrollBar:SetPoint("TOPLEFT", self, "TOPRIGHT", 1, -14 + ROW_HEIGHT);
  357. self.FilterBar.Text:SetFormattedText("%s: %s", FILTER, table.concat(filters, PLAYER_LIST_DELIMITER));
  358. end
  359. end
  360. function TradeSkillRecipeListMixin:RefreshDisplay()
  361. self:UpdateFilterBar();
  362. local offset = HybridScrollFrame_GetOffset(self);
  363. for i, tradeSkillButton in ipairs(self.buttons) do
  364. local dataIndex = offset + i;
  365. local tradeSkillInfo = self.dataList[dataIndex];
  366. if tradeSkillInfo then
  367. if tradeSkillInfo.type == "header" or tradeSkillInfo.type == "subheader" then
  368. tradeSkillInfo.collapsed = self:IsCategoryCollapsed(tradeSkillInfo.categoryID);
  369. end
  370. tradeSkillButton:SetUp(tradeSkillInfo);
  371. if tradeSkillInfo.type == "recipe" then
  372. tradeSkillButton:SetSelected(self.selectedRecipeID == tradeSkillInfo.recipeID);
  373. end
  374. else
  375. tradeSkillButton:Clear();
  376. end
  377. end
  378. HybridScrollFrame_Update(self, #self.dataList * ROW_HEIGHT, 389);
  379. end
  380. function TradeSkillRecipeListMixin:FindRecipeIndexInCurrentList(recipeID)
  381. for i, listData in ipairs(self.dataList) do
  382. if listData.type == "recipe" and recipeID == listData.recipeID then
  383. return i;
  384. end
  385. end
  386. return nil;
  387. end
  388. function TradeSkillRecipeListMixin:IsRecipeInCurrentList(recipeID)
  389. return self:FindRecipeIndexInCurrentList(recipeID) ~= nil;
  390. end
  391. function TradeSkillRecipeListMixin:ForceRecipeIntoView(recipeID)
  392. local dataIndex = self:FindRecipeIndexInCurrentList(recipeID);
  393. if dataIndex then
  394. local offset = HybridScrollFrame_GetOffset(self);
  395. if dataIndex - 1 < offset or dataIndex - 1 > offset + math.floor(self:GetHeight() / ROW_HEIGHT) then
  396. local centerish = ((dataIndex - 1) - math.floor((self:GetHeight() * .5) / ROW_HEIGHT)) * ROW_HEIGHT;
  397. self.scrollBar:SetValue(centerish);
  398. end
  399. end
  400. end
  401. function TradeSkillRecipeListMixin:SelectedAndForceRecipeIDIntoView(recipeID)
  402. if self.pendingRefresh then
  403. self.pendingForceRecipeIDIntoView = recipeID;
  404. elseif self:IsRecipeInCurrentList(recipeID) then
  405. self:SetSelectedRecipeID(recipeID);
  406. self:ForceRecipeIntoView(recipeID);
  407. end
  408. end
  409. function TradeSkillRecipeListMixin:SetSelectedRecipeID(recipeID)
  410. if self.selectedRecipeID ~= recipeID and (not recipeID or self:IsRecipeInCurrentList(recipeID)) then
  411. self.selectedRecipeID = recipeID;
  412. self:RefreshDisplay();
  413. if self.recipeChangedCallback then
  414. self.recipeChangedCallback(recipeID);
  415. end
  416. return true;
  417. end
  418. return false;
  419. end
  420. function TradeSkillRecipeListMixin:GetSelectedRecipeID()
  421. return self.selectedRecipeID;
  422. end