diff --git a/ESOUIModifications.lua b/ESOUIModifications.lua new file mode 100644 index 0000000..f256c03 --- /dev/null +++ b/ESOUIModifications.lua @@ -0,0 +1,229 @@ + +function Librarian:AddLoreReaderUnreadToggle() + local function IsSameBook(book, title) + if book.title then + return book.title == title + end + local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId) + local bookTitle = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) + return bookTitle == title + end + + self.loreReaderKeybinds = + { + { + alignment = KEYBIND_STRIP_ALIGN_RIGHT, + name = function() + local book = self:FindBook(self.lastShownBookId) + if not book or not IsSameBook(book, LORE_READER.titleText) then + self.loreReaderUnreadIndicator:SetHidden(true) + return "" + else + if book.unread then + if self.settings.showUnreadIndicatorInReader then + self.loreReaderUnreadIndicator:SetHidden(false) + else + self.loreReaderUnreadIndicator:SetHidden(true) + end + return GetString(LIBRARIAN_MARK_READ) + else + self.loreReaderUnreadIndicator:SetHidden(true) + return GetString(LIBRARIAN_MARK_UNREAD) + end + end + end, + visible = function() + local book = self:FindBook(self.lastShownBookId) + return book and IsSameBook(book, LORE_READER.titleText) + end, + keybind = "UI_SHORTCUT_SECONDARY", + callback = function() + local book = self:FindBook(self.lastShownBookId) + if book and IsSameBook(book, LORE_READER.titleText) then + self:ToggleReadBook(book) + KEYBIND_STRIP:UpdateKeybindButtonGroup(self.loreReaderKeybinds) + end + end + } + } + + local function OnSceneStateChange(oldState, newState) + -- Let's not add the keybind at all if this book is not registered in librarian + -- This way, other addon (like Librarium) can override the same keybind for something else + local book = self:FindBook(self.lastShownBookId) + if not book or not IsSameBook(book, LORE_READER.titleText) then + self.loreReaderUnreadIndicator:SetHidden(true) + return + end + + if newState == SCENE_SHOWING then + KEYBIND_STRIP:AddKeybindButtonGroup(self.loreReaderKeybinds) + elseif newState == SCENE_HIDDEN then + KEYBIND_STRIP:RemoveKeybindButtonGroup(self.loreReaderKeybinds) + end + end + + -- This list of scene should be the same as the list in esoui/ingame/lorereader/lorereader.lua in its Initialize function + LORE_READER_INVENTORY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) + LORE_READER_LORE_LIBRARY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) + LORE_READER_INTERACTION_SCENE:RegisterCallback("StateChange", OnSceneStateChange) + GAMEPAD_LORE_READER_INVENTORY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) + GAMEPAD_LORE_READER_LORE_LIBRARY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) + GAMEPAD_LORE_READER_INTERACTION_SCENE:RegisterCallback("StateChange", OnSceneStateChange) + + self.loreReaderUnreadIndicator = WINDOW_MANAGER:CreateControl("LibrarianLoreReaderUnreadIndicator", ZO_LoreReaderBookContainer, CT_TEXTURE) + self.loreReaderUnreadIndicator:SetAnchor(TOPLEFT, ZO_LoreReaderBookContainerFirstPage, TOPLEFT, -32, 3) + self.loreReaderUnreadIndicator:SetAlpha(self.settings.unreadIndicatorTransparency) + self.loreReaderUnreadIndicator:SetDimensions(32, 32) + self.loreReaderUnreadIndicator:SetHidden(true) + self.loreReaderUnreadIndicator:SetTexture("esoui/art/miscellaneous/new_icon.dds") +end + +function Librarian:AddLoreLibraryIcons() + if not self.settings.showUnreadIndicatorInLoreLibrary then + return + end + + -- ADD UNREAD ICON ON EACH UNREAD ENTRY + local BOOK_DATA_TYPE = 1 + local scrollList = LORE_LIBRARY.list:GetListControl() + local initalDataType = scrollList.dataTypes[BOOK_DATA_TYPE] + scrollList.dataTypes[BOOK_DATA_TYPE] = nil + + local function SetUpBookEntry(control, data) + initalDataType.setupCallback(control, data) + + local _, _, known, bookId = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex) + control.bookId = bookId + local shouldUnreadIconBeHidden = true + if known then + local book = self:FindBook(bookId) + if not book or book.unread then + shouldUnreadIconBeHidden = false + end + end + control:GetNamedChild("UnreadIcon"):SetHidden(shouldUnreadIconBeHidden) + end + + ZO_ScrollList_AddDataType(scrollList, BOOK_DATA_TYPE, "Librarian_LoreLibrary_BookEntry", initalDataType.height, SetUpBookEntry) + + -- WHEN READING BOOK FROM LORE LIBRARY, UPDATE LASTSHOWNBOOKID + local initial_ZO_LoreLibrary_ReadBook = ZO_LoreLibrary_ReadBook + ZO_LoreLibrary_ReadBook = function(categoryIndex, collectionIndex, bookIndex) + self.lastShownBookId = select(4, GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)) + initial_ZO_LoreLibrary_ReadBook(categoryIndex, collectionIndex, bookIndex) + end + + -- ORDER BOOK LIST TO MAKE UNREAD BOOK FIRST + local function BookEntryComparator(leftScrollData, rightScrollData) + local leftData = leftScrollData.data + local rightData = rightScrollData.data + local leftTitle, _, leftKnown, leftBookId = GetLoreBookInfo(leftData.categoryIndex, leftData.collectionIndex, leftData.bookIndex) + local rightTitle, _, rightKnown, rightBookId = GetLoreBookInfo(rightData.categoryIndex, rightData.collectionIndex, rightData.bookIndex) + + if leftKnown == rightKnown then + if leftKnown then + local leftBook = self:FindBook(leftBookId) + local rightBook = self:FindBook(rightBookId) + if (leftBook and not leftBook.unread) == (rightBook and not rightBook.unread) then + return leftTitle < rightTitle + end + + return leftBook and leftBook.unread + end + end + + return leftKnown + end + LORE_LIBRARY.list.SortScrollList = function(loreLibrarySelf) + local scrollData = ZO_ScrollList_GetDataList(loreLibrarySelf.list) + table.sort(scrollData, BookEntryComparator) + end + + -- ADD KEYBIND TO MARK BOOK AS READ FROM LORE LIBRARY + local loreLibraryKeybinds = LORE_LIBRARY.keybindStripDescriptor + + -- Make current secondary tertiary + for i, keybindDescriptor in ipairs(loreLibraryKeybinds) do + if keybindDescriptor.keybind == "UI_SHORTCUT_SECONDARY" then + keybindDescriptor.keybind = "UI_SHORTCUT_TERTIARY" + end + end + + local toggleKeybind = + { + alignment = KEYBIND_STRIP_ALIGN_RIGHT, + name = function() + local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() + if selectedRow and selectedRow.known then + local book = self:FindBook(selectedRow.bookId) + if not book or book.unread then + return GetString(LIBRARIAN_MARK_READ) + else + return GetString(LIBRARIAN_MARK_UNREAD) + end + end + end, + keybind = "UI_SHORTCUT_SECONDARY", + visible = function() + local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() + return selectedRow and selectedRow.known + end, + callback = function() + local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() + if selectedRow then + local book = self:FindBook(selectedRow.bookId) + if not book then + book = { bookId = selectedRow.bookId, title = selectedRow.text:GetText() } + self:AddBook(book, true) + end + + if book then + self:ToggleReadBook(book) + KEYBIND_STRIP:UpdateKeybindButtonGroup(loreLibraryKeybinds) + end + end + end + } + table.insert(loreLibraryKeybinds, toggleKeybind) + + -- ADD UNREAD ICON ON COLLECTION IF IT CONTAINS AN UNREAD BOOK OR ADD COMPLETED MARK IF ALL ARE KNOWN AND READ + local navigationEntryTemplateInfo = LORE_LIBRARY.navigationTree.templateInfo["ZO_LoreLibraryNavigationEntry"] + local previousSetupFunction = navigationEntryTemplateInfo.setupFunction + + local function TreeEntrySetup(node, control, data, open) + previousSetupFunction(node, control, data, open) + + local shouldHideIcon = true + local statusIcon = control:GetNamedChild("StatusIcon") + if data.numKnownBooks > 0 then + if self.unreadPerCollections[data.categoryIndex] and + self.unreadPerCollections[data.categoryIndex][data.collectionIndex] and + self.unreadPerCollections[data.categoryIndex][data.collectionIndex] > 0 then + shouldHideIcon = false + statusIcon:SetTexture("esoui/art/miscellaneous/new_icon.dds") + elseif data.numKnownBooks == data.totalBooks then + shouldHideIcon = false + statusIcon:SetTexture("esoui/art/miscellaneous/check.dds") + end + end + statusIcon:SetHidden(shouldHideIcon) + end + + navigationEntryTemplateInfo.setupFunction = TreeEntrySetup + navigationEntryTemplateInfo.template = "Librarian_LoreLibraryNavigationEntry" + navigationEntryTemplateInfo.objectPool.templateName = "Librarian_LoreLibraryNavigationEntry" + + -- Fix of ESO because a label header is equal to all of its entries with the current code (API 101033) + LORE_LIBRARY.navigationTree.templateInfo["ZO_LabelHeader"].equalityFunction = navigationEntryTemplateInfo.equalityFunction +end + +function Librarian:RefreshLoreLibraryData() + if self.settings.showUnreadIndicatorInLoreLibrary then + local selectedCategoryIndex = LORE_LIBRARY:GetSelectedCategoryIndex() + local selectedCollectionIndex = LORE_LIBRARY:GetSelectedCollectionIndex() + local selectedCollectionId = select(7, GetLoreCollectionInfo(selectedCategoryIndex, selectedCollectionIndex)) + LORE_LIBRARY:SetCollectionIdToSelect(selectedCollectionId) + LORE_LIBRARY:BuildCategoryList() + end +end diff --git a/Librarian.lua b/Librarian.lua index 19126f9..b882729 100644 --- a/Librarian.lua +++ b/Librarian.lua @@ -9,18 +9,18 @@ Librarian.menuSettings = { } Librarian.slashCommandText = "/librarian" -local SORT_ARROW_UP = "EsoUI/Art/Miscellaneous/list_sortUp.dds" -local SORT_ARROW_DOWN = "EsoUI/Art/Miscellaneous/list_sortDown.dds" -local LIBRARIAN_DATA = 1 -local LIBRARIAN_SEARCH = 1 +Librarian.constants = { + LIBRARIAN_DATA = 1, + LIBRARIAN_SEARCH = 1, -local ENTRY_SORT_KEYS = -{ - ["title"] = { }, - ["unread"] = { tiebreaker = "timeStamp" }, - ["timeStamp"] = { tiebreaker = "title" }, - ["category"] = { tiebreaker = "title" }, - ["wordCount"] = { tiebreaker = "title" } + ENTRY_SORT_KEYS = + { + ["title"] = { }, + ["unread"] = { tiebreaker = "timeStamp" }, + ["timeStamp"] = { tiebreaker = "title" }, + ["category"] = { tiebreaker = "title" }, + ["wordCount"] = { tiebreaker = "title" } + } } ESO_Dialogs["LIBRARIAN_DELETE_BOOK"] = @@ -63,7 +63,7 @@ function Librarian:Initialize(...) self.newBookCount = 0 self.sortHeaderGroup:SelectHeaderByKey("timeStamp") - ZO_ScrollList_AddDataType(self.list, LIBRARIAN_DATA, "LibrarianBookRow", 30, function(control, data) self:SetupBookRow(control, data) end) + ZO_ScrollList_AddDataType(self.list, self.constants.LIBRARIAN_DATA, "LibrarianBookRow", 30, function(control, data) self:SetupBookRow(control, data) end) ZO_ScrollList_EnableHighlight(self.list, "ZO_ThinListHighlight") self.localSavedVars = ZO_SavedVars:New("Librarian_SavedVariables", 1, nil, self.defaults, nil) @@ -94,9 +94,9 @@ function Librarian:Initialize(...) self.searchBox = GetControl(LibrarianFrame, "SearchBox") self.searchBox:SetHandler("OnTextChanged", function() self:OnSearchTextChanged() end) self.search = ZO_StringSearch:New() - self.search:AddProcessor(LIBRARIAN_SEARCH, function(stringSearch, data, searchTerm, cache) return self:ProcessBookEntry(stringSearch, data, searchTerm, cache) end) + self.search:AddProcessor(self.constants.LIBRARIAN_SEARCH, function(stringSearch, data, searchTerm, cache) return self:ProcessBookEntry(stringSearch, data, searchTerm, cache) end) - self.sortFunction = function(listEntry1, listEntry2) return ZO_TableOrderingFunction(listEntry1.data, listEntry2.data, self.currentSortKey, ENTRY_SORT_KEYS, self.currentSortOrder) end + self.sortFunction = function(listEntry1, listEntry2) return ZO_TableOrderingFunction(listEntry1.data, listEntry2.data, self.currentSortKey, self.constants.ENTRY_SORT_KEYS, self.currentSortOrder) end LibrarianDeprecation:UpdateSavedVariables(self) @@ -141,683 +141,6 @@ function Librarian:Initialize(...) self:ImportFromLoreLibrary() end -function Librarian:AddLoreReaderUnreadToggle() - local function IsSameBook(book, title) - if book.title then - return book.title == title - end - local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId) - local bookTitle = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) - return bookTitle == title - end - - self.loreReaderKeybinds = - { - { - alignment = KEYBIND_STRIP_ALIGN_RIGHT, - name = function() - local book = self:FindBook(self.lastShownBookId) - if not book or not IsSameBook(book, LORE_READER.titleText) then - self.loreReaderUnreadIndicator:SetHidden(true) - return "" - else - if book.unread then - if self.settings.showUnreadIndicatorInReader then - self.loreReaderUnreadIndicator:SetHidden(false) - else - self.loreReaderUnreadIndicator:SetHidden(true) - end - return GetString(LIBRARIAN_MARK_READ) - else - self.loreReaderUnreadIndicator:SetHidden(true) - return GetString(LIBRARIAN_MARK_UNREAD) - end - end - end, - visible = function() - local book = self:FindBook(self.lastShownBookId) - return book and IsSameBook(book, LORE_READER.titleText) - end, - keybind = "UI_SHORTCUT_SECONDARY", - callback = function() - local book = self:FindBook(self.lastShownBookId) - if book and IsSameBook(book, LORE_READER.titleText) then - self:ToggleReadBook(book) - KEYBIND_STRIP:UpdateKeybindButtonGroup(self.loreReaderKeybinds) - end - end - } - } - - local function OnSceneStateChange(oldState, newState) - -- Let's not add the keybind at all if this book is not registered in librarian - -- This way, other addon (like Librarium) can override the same keybind for something else - local book = self:FindBook(self.lastShownBookId) - if not book or not IsSameBook(book, LORE_READER.titleText) then - self.loreReaderUnreadIndicator:SetHidden(true) - return - end - - if newState == SCENE_SHOWING then - KEYBIND_STRIP:AddKeybindButtonGroup(self.loreReaderKeybinds) - elseif newState == SCENE_HIDDEN then - KEYBIND_STRIP:RemoveKeybindButtonGroup(self.loreReaderKeybinds) - end - end - - -- This list of scene should be the same as the list in esoui/ingame/lorereader/lorereader.lua in its Initialize function - LORE_READER_INVENTORY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) - LORE_READER_LORE_LIBRARY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) - LORE_READER_INTERACTION_SCENE:RegisterCallback("StateChange", OnSceneStateChange) - GAMEPAD_LORE_READER_INVENTORY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) - GAMEPAD_LORE_READER_LORE_LIBRARY_SCENE:RegisterCallback("StateChange", OnSceneStateChange) - GAMEPAD_LORE_READER_INTERACTION_SCENE:RegisterCallback("StateChange", OnSceneStateChange) - - self.loreReaderUnreadIndicator = WINDOW_MANAGER:CreateControl("LibrarianLoreReaderUnreadIndicator", ZO_LoreReaderBookContainer, CT_TEXTURE) - self.loreReaderUnreadIndicator:SetAnchor(TOPLEFT, ZO_LoreReaderBookContainerFirstPage, TOPLEFT, -32, 3) - self.loreReaderUnreadIndicator:SetAlpha(self.settings.unreadIndicatorTransparency) - self.loreReaderUnreadIndicator:SetDimensions(32, 32) - self.loreReaderUnreadIndicator:SetHidden(true) - self.loreReaderUnreadIndicator:SetTexture("esoui/art/miscellaneous/new_icon.dds") -end - -function Librarian:AddLoreLibraryIcons() - if not self.settings.showUnreadIndicatorInLoreLibrary then - return - end - - -- ADD UNREAD ICON ON EACH UNREAD ENTRY - local BOOK_DATA_TYPE = 1 - local scrollList = LORE_LIBRARY.list:GetListControl() - local initalDataType = scrollList.dataTypes[BOOK_DATA_TYPE] - scrollList.dataTypes[BOOK_DATA_TYPE] = nil - - local function SetUpBookEntry(control, data) - initalDataType.setupCallback(control, data) - - local _, _, known, bookId = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex) - control.bookId = bookId - local shouldUnreadIconBeHidden = true - if known then - local book = self:FindBook(bookId) - if not book or book.unread then - shouldUnreadIconBeHidden = false - end - end - control:GetNamedChild("UnreadIcon"):SetHidden(shouldUnreadIconBeHidden) - end - - ZO_ScrollList_AddDataType(scrollList, BOOK_DATA_TYPE, "Librarian_LoreLibrary_BookEntry", initalDataType.height, SetUpBookEntry) - - -- WHEN READING BOOK FROM LORE LIBRARY, UPDATE LASTSHOWNBOOKID - local initial_ZO_LoreLibrary_ReadBook = ZO_LoreLibrary_ReadBook - ZO_LoreLibrary_ReadBook = function(categoryIndex, collectionIndex, bookIndex) - self.lastShownBookId = select(4, GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex)) - initial_ZO_LoreLibrary_ReadBook(categoryIndex, collectionIndex, bookIndex) - end - - -- ORDER BOOK LIST TO MAKE UNREAD BOOK FIRST - local function BookEntryComparator(leftScrollData, rightScrollData) - local leftData = leftScrollData.data - local rightData = rightScrollData.data - local leftTitle, _, leftKnown, leftBookId = GetLoreBookInfo(leftData.categoryIndex, leftData.collectionIndex, leftData.bookIndex) - local rightTitle, _, rightKnown, rightBookId = GetLoreBookInfo(rightData.categoryIndex, rightData.collectionIndex, rightData.bookIndex) - - if leftKnown == rightKnown then - if leftKnown then - local leftBook = self:FindBook(leftBookId) - local rightBook = self:FindBook(rightBookId) - if (leftBook and not leftBook.unread) == (rightBook and not rightBook.unread) then - return leftTitle < rightTitle - end - - return leftBook and leftBook.unread - end - end - - return leftKnown - end - LORE_LIBRARY.list.SortScrollList = function(loreLibrarySelf) - local scrollData = ZO_ScrollList_GetDataList(loreLibrarySelf.list) - table.sort(scrollData, BookEntryComparator) - end - - -- ADD KEYBIND TO MARK BOOK AS READ FROM LORE LIBRARY - local loreLibraryKeybinds = LORE_LIBRARY.keybindStripDescriptor - - -- Make current secondary tertiary - for i, keybindDescriptor in ipairs(loreLibraryKeybinds) do - if keybindDescriptor.keybind == "UI_SHORTCUT_SECONDARY" then - keybindDescriptor.keybind = "UI_SHORTCUT_TERTIARY" - end - end - - local toggleKeybind = - { - alignment = KEYBIND_STRIP_ALIGN_RIGHT, - name = function() - local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() - if selectedRow and selectedRow.known then - local book = self:FindBook(selectedRow.bookId) - if not book or book.unread then - return GetString(LIBRARIAN_MARK_READ) - else - return GetString(LIBRARIAN_MARK_UNREAD) - end - end - end, - keybind = "UI_SHORTCUT_SECONDARY", - visible = function() - local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() - return selectedRow and selectedRow.known - end, - callback = function() - local selectedRow = LORE_LIBRARY.list:GetMouseOverRow() - if selectedRow then - local book = self:FindBook(selectedRow.bookId) - if not book then - book = { bookId = selectedRow.bookId, title = selectedRow.text:GetText() } - self:AddBook(book, true) - end - - if book then - self:ToggleReadBook(book) - KEYBIND_STRIP:UpdateKeybindButtonGroup(loreLibraryKeybinds) - end - end - end - } - table.insert(loreLibraryKeybinds, toggleKeybind) - - -- ADD UNREAD ICON ON COLLECTION IF IT CONTAINS AN UNREAD BOOK OR ADD COMPLETED MARK IF ALL ARE KNOWN AND READ - local navigationEntryTemplateInfo = LORE_LIBRARY.navigationTree.templateInfo["ZO_LoreLibraryNavigationEntry"] - local previousSetupFunction = navigationEntryTemplateInfo.setupFunction - - local function TreeEntrySetup(node, control, data, open) - previousSetupFunction(node, control, data, open) - - local shouldHideIcon = true - local statusIcon = control:GetNamedChild("StatusIcon") - if data.numKnownBooks > 0 then - if self.unreadPerCollections[data.categoryIndex] and - self.unreadPerCollections[data.categoryIndex][data.collectionIndex] and - self.unreadPerCollections[data.categoryIndex][data.collectionIndex] > 0 then - shouldHideIcon = false - statusIcon:SetTexture("esoui/art/miscellaneous/new_icon.dds") - elseif data.numKnownBooks == data.totalBooks then - shouldHideIcon = false - statusIcon:SetTexture("esoui/art/miscellaneous/check.dds") - end - end - statusIcon:SetHidden(shouldHideIcon) - end - - navigationEntryTemplateInfo.setupFunction = TreeEntrySetup - navigationEntryTemplateInfo.template = "Librarian_LoreLibraryNavigationEntry" - navigationEntryTemplateInfo.objectPool.templateName = "Librarian_LoreLibraryNavigationEntry" - - -- Fix of ESO because a label header is equal to all of its entries with the current code (API 101033) - LORE_LIBRARY.navigationTree.templateInfo["ZO_LabelHeader"].equalityFunction = navigationEntryTemplateInfo.equalityFunction -end - -function Librarian:InitializeKeybindStripDescriptors() - self.keybindStripDescriptor = - { - { - alignment = KEYBIND_STRIP_ALIGN_RIGHT, - name = GetString(SI_LORE_LIBRARY_READ), - keybind = "UI_SHORTCUT_PRIMARY", - visible = function() - return self.mouseOverRow - end, - callback = function() - self:ReadBook(self.mouseOverRow.data) - end, - }, - { - alignment = KEYBIND_STRIP_ALIGN_RIGHT, - name = function() - if not self.mouseOverRow then return nil end - local book = self:FindBook(self.mouseOverRow.data.bookId) - if book.unread then - return GetString(LIBRARIAN_MARK_READ) - else - return GetString(LIBRARIAN_MARK_UNREAD) - end - end, - keybind = "UI_SHORTCUT_SECONDARY", - visible = function() - return self.mouseOverRow - end, - callback = function() - local book = self:FindBook(self.mouseOverRow.data.bookId) - self:ToggleReadBook(book) - end, - }, - { - alignment = KEYBIND_STRIP_ALIGN_RIGHT, - name = GetString(LIBRARIAN_GO_TO_CATEGORY), - keybind = "UI_SHORTCUT_TERTIARY", - visible = function() - return self.mouseOverRow and self.mouseOverRow.data.categoryIndex and self.mouseOverRow.data.collectionIndex - end, - callback = function() - local collectionId = select(7, GetLoreCollectionInfo(self.mouseOverRow.data.categoryIndex, self.mouseOverRow.data.collectionIndex)) - LORE_LIBRARY:SetCollectionIdToSelect(collectionId) - MAIN_MENU_KEYBOARD:ShowScene("loreLibrary") - end, - }, - { - alignment = KEYBIND_STRIP_ALIGN_LEFT, - name = GetString(LIBRARIAN_DELETE_BOOK), - keybind = "UI_SHORTCUT_NEGATIVE", - visible = function() - return self.mouseOverRow and (not self.mouseOverRow.data.categoryIndex or not self.mouseOverRow.data.collectionIndex) - end, - callback = function() - self.potentialBookIdToDelete = self.mouseOverRow.data.bookId - ZO_Dialogs_ShowPlatformDialog("LIBRARIAN_DELETE_BOOK", {bookId = self.mouseOverRow.data.bookId}, {mainTextParams = {self.mouseOverRow.data.title}}) - end, - } - } -end - -function Librarian:InitializeScene() - if not LIBRARIAN_SCENE then - LIBRARIAN_TITLE_FRAGMENT = ZO_SetTitleFragment:New(LIBRARIAN_WINDOW_TITLE_LIBRARIAN) - LIBRARIAN_SCENE = ZO_Scene:New("librarian", SCENE_MANAGER) - LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.MOUSE_DRIVEN_UI_WINDOW) - if self.settings.enableCharacterSpin then - LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.FRAME_TARGET_STANDARD_RIGHT_PANEL) - end - LIBRARIAN_SCENE:AddFragment(ZO_FadeSceneFragment:New(LibrarianFrame)) - LIBRARIAN_SCENE:AddFragment(RIGHT_BG_FRAGMENT) - LIBRARIAN_SCENE:AddFragment(TITLE_FRAGMENT) - LIBRARIAN_SCENE:AddFragment(LIBRARIAN_TITLE_FRAGMENT) - LIBRARIAN_SCENE:AddFragment(CODEX_WINDOW_SOUNDS) - - LIBRARIAN_SCENE:RegisterCallback("StateChange", - function(oldState, newState) - if(newState == SCENE_SHOWING) then - KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor) - elseif(newState == SCENE_HIDDEN) then - KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor) - end - end) - end -end - -function Librarian:ImportFromLoreLibrary() - self.unreadPerCollections = {} - local hasImportedBooks = false - local chatEnabled = self.settings.chatEnabled - local alertEnabled = self.settings.alertEnabled - self.settings.chatEnabled = true - self.settings.alertEnabled = false - local identifier = "LibrarianLoreImport" - local em = GetEventManager() - local function endSearch() - em:UnregisterForUpdate(identifier) - self.settings.chatEnabled = chatEnabled - self.settings.alertEnabled = alertEnabled - if hasImportedBooks then - self:RefreshAllData() - else - self:RefreshLoreLibraryData() - end - end - - local categoryIndex, collectionIndex, bookIndex = 0, 0, 0 - local numCollections, totalBooks = 0, 0 - local function step() - if bookIndex >= totalBooks then - if collectionIndex >= numCollections then - if categoryIndex >= GetNumLoreCategories() then - endSearch() - return false - else - categoryIndex = categoryIndex + 1 - local loreCategoryName - numCollections = select(2, GetLoreCategoryInfo(categoryIndex)) - collectionIndex = 0 - end - end - collectionIndex = collectionIndex + 1 - totalBooks = select(4, GetLoreCollectionInfo(categoryIndex, collectionIndex)) - bookIndex = 0 - end - bookIndex = bookIndex + 1 - local title, _, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) - if known then - local book = self:FindBook(bookId) - local characterBook = self:FindCharacterBook(bookId) - if book and characterBook and book.unread then - self:AddUnreadBookInCollection(categoryIndex, collectionIndex) - elseif not characterBook then - local newBook = { bookId = bookId, title = title } - self:AddBook(newBook, false) - hasImportedBooks = true - end - end - return true - end - - local function steps() - local gettime = GetGameTimeMilliseconds - local start = gettime() - -- do as much as possible in 5ms - while ((gettime() - start) < 5) and step() do - end - end - em:RegisterForUpdate(identifier, 0, steps) -end - -function Librarian:BuildMasterList() - local GetLoreBookIndicesFromBookId, GetLoreCollectionInfo = GetLoreBookIndicesFromBookId, GetLoreCollectionInfo - local GetLoreBookInfo = GetLoreBookInfo - - local function ShouldDisplayBook(book) - if not self.settings.showHiddenBook then - local category, collection = GetLoreBookIndicesFromBookId(book.bookId) - if category and collection then - local hiddenCollection = select(5, GetLoreCollectionInfo(category, collection)) - if hiddenCollection then - return false - end - else - return false - end - end - return true - end - - for i, book in ipairs(self.books) do - if ShouldDisplayBook(book) then - if not self.masterList[i] then - self.masterList[i] = {} - end - - local data = self.masterList[i] - for k,v in pairs(book) do - if k == "body" then - data[k] = table.concat(book.body) - else - data[k] = v - end - end - - -- because we reuse the same list between 2 refresh, the data may already be filled up so no need to recompute it, these won't change - if not data.categoryIndex or not data.collectionIndex or not data.bookIndex then - data.categoryIndex, data.collectionIndex, data.bookIndex = GetLoreBookIndicesFromBookId(book.bookId) - end - if data.categoryIndex and data.collectionIndex and data.bookIndex and (not data.title or not data.body or not data.category) then - data.title = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex) - data.body = ReadLoreBook(data.categoryIndex, data.collectionIndex, data.bookIndex) - data.category = GetLoreCollectionInfo(data.categoryIndex, data.collectionIndex) - end - - if not data.category or data.category == "" then - data.category = GetString(LIBRARIAN_NO_CATEGORY) - end - - if not data.wordCount then - local wordCount = 0 - if data.body then - for w in data.body:gmatch("%S+") do - wordCount = wordCount + 1 - end - end - data.wordCount = wordCount - end - - data.type = LIBRARIAN_SEARCH - local characterBook = self:FindCharacterBook(book.bookId) - if characterBook then - data.seenByCurrentCharacter = true - data.timeStamp = characterBook.timeStamp - else - data.seenByCurrentCharacter = false - data.timeStamp = book.timeStamp - end - else - self.masterList[i] = nil - end - end -end - -function Librarian:FilterScrollList() - local scrollData = ZO_ScrollList_GetDataList(self.list) - ZO_ClearNumericallyIndexedTable(scrollData) - - local bookCount = 0 - local unreadCount = 0 - local searchTerm = self.searchBox:GetText() - for _, data in pairs(self.masterList) do - if self.settings.showAllBooks or data.seenByCurrentCharacter then - if(searchTerm == "" or self.search:IsMatch(searchTerm, data)) then - table.insert(scrollData, ZO_ScrollList_CreateDataEntry(LIBRARIAN_DATA, data)) - bookCount = bookCount + 1 - if data.unread then unreadCount = unreadCount + 1 end - end - end - end - - local message = string.format(GetString(LIBRARIAN_BOOK_COUNT), bookCount) - if unreadCount > 0 then message = string.format(GetString(LIBRARIAN_UNREAD_COUNT), message, unreadCount) end - LibrarianFrameBookCount:SetText(message) -end - -function Librarian:SortScrollList() - local scrollData = ZO_ScrollList_GetDataList(self.list) - table.sort(scrollData, self.sortFunction) -end - -function Librarian:SetupBookRow(control, data) - control.data = data - control.unread = GetControl(control, "Unread") - control.found = GetControl(control, "Found") - control.title = GetControl(control, "Title") - control.category = GetControl(control, "Category") - control.wordCount = GetControl(control, "WordCount") - - control.unread.nonRecolorable = true - if data.unread then control.unread:SetAlpha(1) else control.unread:SetAlpha(0) end - - control.found.normalColor = ZO_NORMAL_TEXT - control.found:SetText(self:FormatClockTime(data.timeStamp)) - - control.title.normalColor = ZO_NORMAL_TEXT - control.title:SetText(data.title) - - control.category.normalColor = ZO_NORMAL_TEXT - control.category:SetText(data.category) - - control.wordCount.normalColor = ZO_NORMAL_TEXT - control.wordCount:SetText(data.wordCount) - - ZO_SortFilterList.SetupRow(self, control, data) -end - -function Librarian:ProcessBookEntry(stringSearch, data, searchTerm, cache) - local lowerSearchTerm = searchTerm:lower() - - if data.title and zo_plainstrfind(data.title:lower(), lowerSearchTerm) then - return true - end - - if not self.searchOnlyTitle and data.body and zo_plainstrfind(data.body:lower(), lowerSearchTerm) then - return true - end - - return false -end - -function Librarian:FindCharacterBook(bookId) - if not self.characterBooks then - return nil - end - - if self.characterBooksCache[bookId] then - return self.characterBooksCache[bookId] - end - - for _,book in ipairs(self.characterBooks) do - if book.bookId == bookId then - self.characterBooksCache[bookId] = book - return book - end - end - - return nil -end - -function Librarian:FindBook(bookId) - for _,book in ipairs(self.books) do - if book.bookId == bookId then - return book - end - end -end - -function Librarian:AddBookToGlobalSave(book) - book.timeStamp = GetTimeStamp() - book.unread = true - - local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId) - if categoryIndex and collectionIndex and bookIndex then - -- if these indexes exist in the API, this mean that we will be able to get these info when needed (so no need to save them) - book.title = nil - book.body = nil - book.medium = nil - book.showTitle = nil - else - local newBody = book.body - book.body = {} - while string.len(newBody) > 1024 do - table.insert(book.body, string.sub(newBody, 0, 1024)) - newBody = string.sub(newBody, 1025) - end - table.insert(book.body, newBody) - end - - table.insert(self.books, book) - - for index, bookIdToDelete in ipairs(self.globalSavedVars.deletedBooks) do - if bookIdToDelete == book.bookId then - table.remove(self.globalSavedVars.deletedBooks, index) - break - end - end -end - -function Librarian:AddBook(book, refreshDataRightAway) - if not self:FindCharacterBook(book.bookId) then - local bookTitle = book.title -- storing it because AddBookToGlobalSave will delete it if we can retrieve it thanks to ESO API - local foundBook = self:FindBook(book.bookId) - if foundBook then - book = foundBook - else - self:AddBookToGlobalSave(book) - end - - local characterBook = { bookId = book.bookId, timeStamp = GetTimeStamp() } - table.insert(self.characterBooks, characterBook) - - if book.unread then - local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId) - if categoryIndex and collectionIndex then - self:AddUnreadBookInCollection(categoryIndex, collectionIndex) - end - end - - if refreshDataRightAway then - self:RefreshAllData() - end - - if self.settings.alertEnabled then - --CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED, GetString(SI_LIBRARIAN_NEW_BOOK_FOUND)) - local params = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED) - params:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_DISPLAY_ANNOUNCEMENT ) - params:SetText(GetString(LIBRARIAN_NEW_BOOK_FOUND)) - CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(params) - end - if self.settings.chatEnabled then - d(string.format(GetString(LIBRARIAN_NEW_BOOK_FOUND_WITH_TITLE), bookTitle)) - end - - self.newBookCount = self.newBookCount + 1 - if self.settings.reloadReminderBookCount and self.settings.reloadReminderBookCount > 0 and self.settings.reloadReminderBookCount <= self.newBookCount then - d(GetString(LIBRARIAN_RELOAD_REMINDER)) - end - end - -end - -function Librarian:RemoveBook(bookId) - for _, bookIdToDelete in ipairs(self.globalSavedVars.deletedBooks) do - if bookIdToDelete == bookId then - d("Couldn't delete book, it is already beeing deleted") - return - end - end - - table.insert(self.globalSavedVars.deletedBooks, bookId) - - for index ,book in ipairs(self.books) do - if book.bookId == bookId then - table.remove(self.books, index) - break - end - end - - self.characterBooksCache[bookId] = nil - self:RemoveCharacterBook(bookId) - - -- Clean the master list to be sure the entry is removed from the display as well - self.masterList = {} - self:RefreshAllData() -end - -function Librarian:RemoveCharacterBook(bookId) - for index ,book in ipairs(self.characterBooks) do - if book.bookId == bookId then - table.remove(self.characterBooks, index) - break - end - end -end - -function Librarian:AddUnreadBookInCollection(categoryIndex, collectionIndex) - if not self.unreadPerCollections[categoryIndex] then self.unreadPerCollections[categoryIndex] = {} end - if not self.unreadPerCollections[categoryIndex][collectionIndex] then self.unreadPerCollections[categoryIndex][collectionIndex] = 0 end - self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] + 1 -end - -function Librarian:ToggleReadBook(book) - book.unread = not book.unread - - local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId) - if categoryIndex and collectionIndex then - if not self.unreadPerCollections[categoryIndex] then self.unreadPerCollections[categoryIndex] = {} end - if not self.unreadPerCollections[categoryIndex][collectionIndex] then self.unreadPerCollections[categoryIndex][collectionIndex] = 0 end - if book.unread then - self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] + 1 - elseif self.unreadPerCollections[categoryIndex][collectionIndex] > 0 then - self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] - 1 - end - end - - self:RefreshAllData() -end - -function Librarian:Toggle() - if LibrarianFrame:IsControlHidden() then - SCENE_MANAGER:Show("librarian") - else - SCENE_MANAGER:Hide("librarian") - end -end - function Librarian:ReadBook(data) self.lastShownBookId = data.bookId @@ -843,44 +166,6 @@ function Librarian:RefreshAllData() self:RefreshLoreLibraryData() end -function Librarian:RefreshLoreLibraryData() - if self.settings.showUnreadIndicatorInLoreLibrary then - local selectedCategoryIndex = LORE_LIBRARY:GetSelectedCategoryIndex() - local selectedCollectionIndex = LORE_LIBRARY:GetSelectedCollectionIndex() - local selectedCollectionId = select(7, GetLoreCollectionInfo(selectedCategoryIndex, selectedCollectionIndex)) - LORE_LIBRARY:SetCollectionIdToSelect(selectedCollectionId) - LORE_LIBRARY:BuildCategoryList() - end -end - -function Librarian:FormatClockTime(time) - local midnightSeconds = GetSecondsSinceMidnight() - local utcSeconds = GetTimeStamp() % 86400 - local offset = midnightSeconds - utcSeconds - if offset < -43200 then - offset = offset + 86400 - end - - local dateString = GetDateStringFromTimestamp(time) - local timeString = ZO_FormatTime((time + offset) % 86400, TIME_FORMAT_STYLE_CLOCK_TIME, self.settings.timeFormat) - return string.format("%s %s", dateString, timeString) -end - -function Librarian:OnSearchTextChanged() - ZO_EditDefaultText_OnTextChanged(self.searchBox) - - -- Let's wait a little bit before updating the filters if the player didn't have time to enter its full string yet - -- Because if we try to update the filters too much it sometimes crash - local TIME_BEFORE_UPDATING_FILTERS = 600 -- ms - self.lastTimeSearchModified = GetGameTimeMilliseconds() - local function TryRefreshFilters() - if self.lastTimeSearchModified + TIME_BEFORE_UPDATING_FILTERS <= GetGameTimeMilliseconds() then - self:RefreshFilters() - end - end - zo_callLater(TryRefreshFilters, TIME_BEFORE_UPDATING_FILTERS) -end - function Librarian.SlashCommand(args) Librarian:Toggle() end @@ -896,18 +181,6 @@ function Librarian.OnShowBook(eventCode, title, body, medium, showTitle, bookId) KEYBIND_STRIP:AddKeybindButtonGroup(LIBRARIAN.loreReaderKeybinds) end -function Librarian.OnMouseEnterRow(control) - LIBRARIAN:Row_OnMouseEnter(control) -end - -function Librarian.OnMouseExitRow(control) - LIBRARIAN:Row_OnMouseExit(control) -end - -function Librarian.OnMouseUpRow(control, button, upInside) - LIBRARIAN:ReadBook(control.data) -end - function Librarian.OnAddonLoaded(event, addon) if addon == Librarian.name then EVENT_MANAGER:UnregisterForEvent(Librarian.name, EVENT_ADD_ON_LOADED) diff --git a/Librarian.txt b/Librarian.txt index d7d5192..798f8b7 100644 --- a/Librarian.txt +++ b/Librarian.txt @@ -10,7 +10,12 @@ /lang/$(language).lua Librarian.lua + +ESOUIModifications.lua +LibrarianData.lua LibrarianSettings.lua +LibrarianUI.lua + Librarian.xml Bindings.xml diff --git a/LibrarianData.lua b/LibrarianData.lua new file mode 100644 index 0000000..38567d6 --- /dev/null +++ b/LibrarianData.lua @@ -0,0 +1,222 @@ +function Librarian:ImportFromLoreLibrary() + self.unreadPerCollections = {} + local hasImportedBooks = false + local chatEnabled = self.settings.chatEnabled + local alertEnabled = self.settings.alertEnabled + self.settings.chatEnabled = true + self.settings.alertEnabled = false + local identifier = "LibrarianLoreImport" + local em = GetEventManager() + local function endSearch() + em:UnregisterForUpdate(identifier) + self.settings.chatEnabled = chatEnabled + self.settings.alertEnabled = alertEnabled + if hasImportedBooks then + self:RefreshAllData() + else + self:RefreshLoreLibraryData() + end + end + + local categoryIndex, collectionIndex, bookIndex = 0, 0, 0 + local numCollections, totalBooks = 0, 0 + local function step() + if bookIndex >= totalBooks then + if collectionIndex >= numCollections then + if categoryIndex >= GetNumLoreCategories() then + endSearch() + return false + else + categoryIndex = categoryIndex + 1 + local loreCategoryName + numCollections = select(2, GetLoreCategoryInfo(categoryIndex)) + collectionIndex = 0 + end + end + collectionIndex = collectionIndex + 1 + totalBooks = select(4, GetLoreCollectionInfo(categoryIndex, collectionIndex)) + bookIndex = 0 + end + bookIndex = bookIndex + 1 + local title, _, known, bookId = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) + if known then + local book = self:FindBook(bookId) + local characterBook = self:FindCharacterBook(bookId) + if book and characterBook and book.unread then + self:AddUnreadBookInCollection(categoryIndex, collectionIndex) + elseif not characterBook then + local newBook = { bookId = bookId, title = title } + self:AddBook(newBook, false) + hasImportedBooks = true + end + end + return true + end + + local function steps() + local gettime = GetGameTimeMilliseconds + local start = gettime() + -- do as much as possible in 5ms + while ((gettime() - start) < 5) and step() do + end + end + em:RegisterForUpdate(identifier, 0, steps) +end + +function Librarian:FindCharacterBook(bookId) + if not self.characterBooks then + return nil + end + + if self.characterBooksCache[bookId] then + return self.characterBooksCache[bookId] + end + + for _,book in ipairs(self.characterBooks) do + if book.bookId == bookId then + self.characterBooksCache[bookId] = book + return book + end + end + + return nil +end + +function Librarian:FindBook(bookId) + for _,book in ipairs(self.books) do + if book.bookId == bookId then + return book + end + end +end + +function Librarian:AddBookToGlobalSave(book) + book.timeStamp = GetTimeStamp() + book.unread = true + + local categoryIndex, collectionIndex, bookIndex = GetLoreBookIndicesFromBookId(book.bookId) + if categoryIndex and collectionIndex and bookIndex then + -- if these indexes exist in the API, this mean that we will be able to get these info when needed (so no need to save them) + book.title = nil + book.body = nil + book.medium = nil + book.showTitle = nil + else + local newBody = book.body + book.body = {} + while string.len(newBody) > 1024 do + table.insert(book.body, string.sub(newBody, 0, 1024)) + newBody = string.sub(newBody, 1025) + end + table.insert(book.body, newBody) + end + + table.insert(self.books, book) + + for index, bookIdToDelete in ipairs(self.globalSavedVars.deletedBooks) do + if bookIdToDelete == book.bookId then + table.remove(self.globalSavedVars.deletedBooks, index) + break + end + end +end + +function Librarian:AddBook(book, refreshDataRightAway) + if not self:FindCharacterBook(book.bookId) then + local bookTitle = book.title -- storing it because AddBookToGlobalSave will delete it if we can retrieve it thanks to ESO API + local foundBook = self:FindBook(book.bookId) + if foundBook then + book = foundBook + else + self:AddBookToGlobalSave(book) + end + + local characterBook = { bookId = book.bookId, timeStamp = GetTimeStamp() } + table.insert(self.characterBooks, characterBook) + + if book.unread then + local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId) + if categoryIndex and collectionIndex then + self:AddUnreadBookInCollection(categoryIndex, collectionIndex) + end + end + + if refreshDataRightAway then + self:RefreshAllData() + end + + if self.settings.alertEnabled then + --CENTER_SCREEN_ANNOUNCE:AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED, GetString(SI_LIBRARIAN_NEW_BOOK_FOUND)) + local params = CENTER_SCREEN_ANNOUNCE:CreateMessageParams(CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED) + params:SetCSAType(CENTER_SCREEN_ANNOUNCE_TYPE_DISPLAY_ANNOUNCEMENT ) + params:SetText(GetString(LIBRARIAN_NEW_BOOK_FOUND)) + CENTER_SCREEN_ANNOUNCE:AddMessageWithParams(params) + end + if self.settings.chatEnabled then + d(string.format(GetString(LIBRARIAN_NEW_BOOK_FOUND_WITH_TITLE), bookTitle)) + end + + self.newBookCount = self.newBookCount + 1 + if self.settings.reloadReminderBookCount and self.settings.reloadReminderBookCount > 0 and self.settings.reloadReminderBookCount <= self.newBookCount then + d(GetString(LIBRARIAN_RELOAD_REMINDER)) + end + end + +end + +function Librarian:RemoveBook(bookId) + for _, bookIdToDelete in ipairs(self.globalSavedVars.deletedBooks) do + if bookIdToDelete == bookId then + d("Couldn't delete book, it is already beeing deleted") + return + end + end + + table.insert(self.globalSavedVars.deletedBooks, bookId) + + for index ,book in ipairs(self.books) do + if book.bookId == bookId then + table.remove(self.books, index) + break + end + end + + self.characterBooksCache[bookId] = nil + self:RemoveCharacterBook(bookId) + + -- Clean the master list to be sure the entry is removed from the display as well + self.masterList = {} + self:RefreshAllData() +end + +function Librarian:RemoveCharacterBook(bookId) + for index ,book in ipairs(self.characterBooks) do + if book.bookId == bookId then + table.remove(self.characterBooks, index) + break + end + end +end + +function Librarian:AddUnreadBookInCollection(categoryIndex, collectionIndex) + if not self.unreadPerCollections[categoryIndex] then self.unreadPerCollections[categoryIndex] = {} end + if not self.unreadPerCollections[categoryIndex][collectionIndex] then self.unreadPerCollections[categoryIndex][collectionIndex] = 0 end + self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] + 1 +end + +function Librarian:ToggleReadBook(book) + book.unread = not book.unread + + local categoryIndex, collectionIndex = GetLoreBookIndicesFromBookId(book.bookId) + if categoryIndex and collectionIndex then + if not self.unreadPerCollections[categoryIndex] then self.unreadPerCollections[categoryIndex] = {} end + if not self.unreadPerCollections[categoryIndex][collectionIndex] then self.unreadPerCollections[categoryIndex][collectionIndex] = 0 end + if book.unread then + self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] + 1 + elseif self.unreadPerCollections[categoryIndex][collectionIndex] > 0 then + self.unreadPerCollections[categoryIndex][collectionIndex] = self.unreadPerCollections[categoryIndex][collectionIndex] - 1 + end + end + + self:RefreshAllData() +end diff --git a/LibrarianUI.lua b/LibrarianUI.lua new file mode 100644 index 0000000..a7668b4 --- /dev/null +++ b/LibrarianUI.lua @@ -0,0 +1,274 @@ +function Librarian:InitializeKeybindStripDescriptors() + self.keybindStripDescriptor = + { + { + alignment = KEYBIND_STRIP_ALIGN_RIGHT, + name = GetString(SI_LORE_LIBRARY_READ), + keybind = "UI_SHORTCUT_PRIMARY", + visible = function() + return self.mouseOverRow + end, + callback = function() + self:ReadBook(self.mouseOverRow.data) + end, + }, + { + alignment = KEYBIND_STRIP_ALIGN_RIGHT, + name = function() + if not self.mouseOverRow then return nil end + local book = self:FindBook(self.mouseOverRow.data.bookId) + if book.unread then + return GetString(LIBRARIAN_MARK_READ) + else + return GetString(LIBRARIAN_MARK_UNREAD) + end + end, + keybind = "UI_SHORTCUT_SECONDARY", + visible = function() + return self.mouseOverRow + end, + callback = function() + local book = self:FindBook(self.mouseOverRow.data.bookId) + self:ToggleReadBook(book) + end, + }, + { + alignment = KEYBIND_STRIP_ALIGN_RIGHT, + name = GetString(LIBRARIAN_GO_TO_CATEGORY), + keybind = "UI_SHORTCUT_TERTIARY", + visible = function() + return self.mouseOverRow and self.mouseOverRow.data.categoryIndex and self.mouseOverRow.data.collectionIndex + end, + callback = function() + local collectionId = select(7, GetLoreCollectionInfo(self.mouseOverRow.data.categoryIndex, self.mouseOverRow.data.collectionIndex)) + LORE_LIBRARY:SetCollectionIdToSelect(collectionId) + MAIN_MENU_KEYBOARD:ShowScene("loreLibrary") + end, + }, + { + alignment = KEYBIND_STRIP_ALIGN_LEFT, + name = GetString(LIBRARIAN_DELETE_BOOK), + keybind = "UI_SHORTCUT_NEGATIVE", + visible = function() + return self.mouseOverRow and (not self.mouseOverRow.data.categoryIndex or not self.mouseOverRow.data.collectionIndex) + end, + callback = function() + self.potentialBookIdToDelete = self.mouseOverRow.data.bookId + ZO_Dialogs_ShowPlatformDialog("LIBRARIAN_DELETE_BOOK", {bookId = self.mouseOverRow.data.bookId}, {mainTextParams = {self.mouseOverRow.data.title}}) + end, + } + } +end + +function Librarian:InitializeScene() + if not LIBRARIAN_SCENE then + LIBRARIAN_TITLE_FRAGMENT = ZO_SetTitleFragment:New(LIBRARIAN_WINDOW_TITLE_LIBRARIAN) + LIBRARIAN_SCENE = ZO_Scene:New("librarian", SCENE_MANAGER) + LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.MOUSE_DRIVEN_UI_WINDOW) + if self.settings.enableCharacterSpin then + LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.FRAME_TARGET_STANDARD_RIGHT_PANEL) + end + LIBRARIAN_SCENE:AddFragment(ZO_FadeSceneFragment:New(LibrarianFrame)) + LIBRARIAN_SCENE:AddFragment(RIGHT_BG_FRAGMENT) + LIBRARIAN_SCENE:AddFragment(TITLE_FRAGMENT) + LIBRARIAN_SCENE:AddFragment(LIBRARIAN_TITLE_FRAGMENT) + LIBRARIAN_SCENE:AddFragment(CODEX_WINDOW_SOUNDS) + + LIBRARIAN_SCENE:RegisterCallback("StateChange", + function(oldState, newState) + if(newState == SCENE_SHOWING) then + KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor) + elseif(newState == SCENE_HIDDEN) then + KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor) + end + end) + end +end + +function Librarian:BuildMasterList() + local GetLoreBookIndicesFromBookId, GetLoreCollectionInfo = GetLoreBookIndicesFromBookId, GetLoreCollectionInfo + local GetLoreBookInfo = GetLoreBookInfo + + local function ShouldDisplayBook(book) + if not self.settings.showHiddenBook then + local category, collection = GetLoreBookIndicesFromBookId(book.bookId) + if category and collection then + local hiddenCollection = select(5, GetLoreCollectionInfo(category, collection)) + if hiddenCollection then + return false + end + else + return false + end + end + return true + end + + for i, book in ipairs(self.books) do + if ShouldDisplayBook(book) then + if not self.masterList[i] then + self.masterList[i] = {} + end + + local data = self.masterList[i] + for k,v in pairs(book) do + if k == "body" then + data[k] = table.concat(book.body) + else + data[k] = v + end + end + + -- because we reuse the same list between 2 refresh, the data may already be filled up so no need to recompute it, these won't change + if not data.categoryIndex or not data.collectionIndex or not data.bookIndex then + data.categoryIndex, data.collectionIndex, data.bookIndex = GetLoreBookIndicesFromBookId(book.bookId) + end + if data.categoryIndex and data.collectionIndex and data.bookIndex and (not data.title or not data.body or not data.category) then + data.title = GetLoreBookInfo(data.categoryIndex, data.collectionIndex, data.bookIndex) + data.body = ReadLoreBook(data.categoryIndex, data.collectionIndex, data.bookIndex) + data.category = GetLoreCollectionInfo(data.categoryIndex, data.collectionIndex) + end + + if not data.category or data.category == "" then + data.category = GetString(LIBRARIAN_NO_CATEGORY) + end + + if not data.wordCount then + local wordCount = 0 + if data.body then + for w in data.body:gmatch("%S+") do + wordCount = wordCount + 1 + end + end + data.wordCount = wordCount + end + + data.type = self.constants.LIBRARIAN_SEARCH + local characterBook = self:FindCharacterBook(book.bookId) + if characterBook then + data.seenByCurrentCharacter = true + data.timeStamp = characterBook.timeStamp + else + data.seenByCurrentCharacter = false + data.timeStamp = book.timeStamp + end + else + self.masterList[i] = nil + end + end +end + +function Librarian:FilterScrollList() + local scrollData = ZO_ScrollList_GetDataList(self.list) + ZO_ClearNumericallyIndexedTable(scrollData) + + local bookCount = 0 + local unreadCount = 0 + local searchTerm = self.searchBox:GetText() + for _, data in pairs(self.masterList) do + if self.settings.showAllBooks or data.seenByCurrentCharacter then + if(searchTerm == "" or self.search:IsMatch(searchTerm, data)) then + table.insert(scrollData, ZO_ScrollList_CreateDataEntry(self.constants.LIBRARIAN_DATA, data)) + bookCount = bookCount + 1 + if data.unread then unreadCount = unreadCount + 1 end + end + end + end + + local message = string.format(GetString(LIBRARIAN_BOOK_COUNT), bookCount) + if unreadCount > 0 then message = string.format(GetString(LIBRARIAN_UNREAD_COUNT), message, unreadCount) end + LibrarianFrameBookCount:SetText(message) +end + +function Librarian:SortScrollList() + local scrollData = ZO_ScrollList_GetDataList(self.list) + table.sort(scrollData, self.sortFunction) +end + +function Librarian:SetupBookRow(control, data) + control.data = data + control.unread = GetControl(control, "Unread") + control.found = GetControl(control, "Found") + control.title = GetControl(control, "Title") + control.category = GetControl(control, "Category") + control.wordCount = GetControl(control, "WordCount") + + control.unread.nonRecolorable = true + if data.unread then control.unread:SetAlpha(1) else control.unread:SetAlpha(0) end + + control.found.normalColor = ZO_NORMAL_TEXT + control.found:SetText(self:FormatClockTime(data.timeStamp)) + + control.title.normalColor = ZO_NORMAL_TEXT + control.title:SetText(data.title) + + control.category.normalColor = ZO_NORMAL_TEXT + control.category:SetText(data.category) + + control.wordCount.normalColor = ZO_NORMAL_TEXT + control.wordCount:SetText(data.wordCount) + + ZO_SortFilterList.SetupRow(self, control, data) +end + +function Librarian:ProcessBookEntry(stringSearch, data, searchTerm, cache) + local lowerSearchTerm = searchTerm:lower() + + if data.title and zo_plainstrfind(data.title:lower(), lowerSearchTerm) then + return true + end + + if not self.searchOnlyTitle and data.body and zo_plainstrfind(data.body:lower(), lowerSearchTerm) then + return true + end + + return false +end + +function Librarian:Toggle() + if LibrarianFrame:IsControlHidden() then + SCENE_MANAGER:Show("librarian") + else + SCENE_MANAGER:Hide("librarian") + end +end + +function Librarian:FormatClockTime(time) + local midnightSeconds = GetSecondsSinceMidnight() + local utcSeconds = GetTimeStamp() % 86400 + local offset = midnightSeconds - utcSeconds + if offset < -43200 then + offset = offset + 86400 + end + + local dateString = GetDateStringFromTimestamp(time) + local timeString = ZO_FormatTime((time + offset) % 86400, TIME_FORMAT_STYLE_CLOCK_TIME, self.settings.timeFormat) + return string.format("%s %s", dateString, timeString) +end + +function Librarian:OnSearchTextChanged() + ZO_EditDefaultText_OnTextChanged(self.searchBox) + + -- Let's wait a little bit before updating the filters if the player didn't have time to enter its full string yet + -- Because if we try to update the filters too much it sometimes crash + local TIME_BEFORE_UPDATING_FILTERS = 600 -- ms + self.lastTimeSearchModified = GetGameTimeMilliseconds() + local function TryRefreshFilters() + if self.lastTimeSearchModified + TIME_BEFORE_UPDATING_FILTERS <= GetGameTimeMilliseconds() then + self:RefreshFilters() + end + end + zo_callLater(TryRefreshFilters, TIME_BEFORE_UPDATING_FILTERS) +end + +function Librarian.OnMouseEnterRow(control) + LIBRARIAN:Row_OnMouseEnter(control) +end + +function Librarian.OnMouseExitRow(control) + LIBRARIAN:Row_OnMouseExit(control) +end + +function Librarian.OnMouseUpRow(control, button, upInside) + LIBRARIAN:ReadBook(control.data) +end