diff --git a/src/Classes/ListControl.lua b/src/Classes/ListControl.lua index ebc8287c..34dd0e7f 100644 --- a/src/Classes/ListControl.lua +++ b/src/Classes/ListControl.lua @@ -346,10 +346,12 @@ function ListClass:OnKeyDown(key, doubleClick) newSelect = index end else + local scrollOffsetH = self.controls.scrollBarH.offset for colIndex, column in ipairs(self.colList) do local relX = cursorX - (x + 2) local relY = cursorY - (y + 2) - local mOver = relX >= column._offset and relX <= column._offset + column._width and relY >= 0 and relY <= 18 + local adjustedRelX = relX + scrollOffsetH + local mOver = adjustedRelX >= column._offset and adjustedRelX <= column._offset + column._width and relY >= 0 and relY <= 18 if self:GetColumnProperty(column, "sortable") and mOver and self.ReSort then self:ReSort(colIndex) end diff --git a/src/Export/Classes/DatListControl.lua b/src/Export/Classes/DatListControl.lua index 57ceafe0..e1153982 100644 --- a/src/Export/Classes/DatListControl.lua +++ b/src/Export/Classes/DatListControl.lua @@ -4,9 +4,27 @@ -- Dat list control. -- local DatListClass = newClass("DatListControl", "ListControl", function(self, anchor, rect) - self.ListControl(anchor, rect, 14, "VERTICAL", false, main.datFileList) + self.originalList = main.datFileList + self.searchBuf = "" + self.filteredList = self.originalList + self.ListControl(anchor, rect, 14, "VERTICAL", false, self.filteredList) end) +function DatListClass:BuildFilteredList() + local search = self.searchBuf:lower() + if search == "" then + self.filteredList = self.originalList + else + self.filteredList = {} + for _, file in ipairs(self.originalList) do + if file.name:lower():find(search, 1, true) then + table.insert(self.filteredList, file) + end + end + end + self.list = self.filteredList +end + function DatListClass:GetRowValue(column, index, datFile) if column == 1 then return "^7"..datFile.name diff --git a/src/Export/Classes/RowListControl.lua b/src/Export/Classes/RowListControl.lua index fe443a6f..08d1e344 100644 --- a/src/Export/Classes/RowListControl.lua +++ b/src/Export/Classes/RowListControl.lua @@ -9,6 +9,7 @@ local t_insert = table.insert local RowListClass = newClass("RowListControl", "ListControl", function(self, anchor, rect) self.ListControl(anchor, rect, 14, "HORIZONTAL", false, { }) self.colLabels = true + self._autoSizeToggleState = {} -- internal toggle memory, not saved to spec end) function RowListClass:BuildRows(filter) @@ -44,13 +45,19 @@ end function RowListClass:BuildColumns() wipeTable(self.colList) - self.colList[1] = { width = 50, label = "#", font = "FIXED" } + self.colList[1] = { width = 50, label = "#", font = "FIXED", sortable = true } for _, specCol in ipairs(main.curDatFile.spec) do - t_insert(self.colList, { width = specCol.width, label = specCol.name, font = function() return IsKeyDown("CTRL") and "FIXED" or "VAR" end }) + t_insert(self.colList, { + width = specCol.width, + specColRef = specCol, -- Link to the original data + label = specCol.name, + font = function() return IsKeyDown("ALT") and "FIXED" or "VAR" end, + sortable = true + }) end local short = main.curDatFile.rowSize - main.curDatFile.specSize if short > 0 then - t_insert(self.colList, { width = short * DrawStringWidth(self.rowHeight, "FIXED", "00 "), font = "FIXED" }) + t_insert(self.colList, { width = short * DrawStringWidth(self.rowHeight, "FIXED", "00 "), font = "FIXED", sortable = true }) end end @@ -58,7 +65,7 @@ function RowListClass:GetRowValue(column, index, row) if column == 1 then return string.format("%5d", row) end - if not main.curDatFile.spec[column - 1] or IsKeyDown("CTRL") then + if not main.curDatFile.spec[column - 1] or IsKeyDown("ALT") then local out = { main.curDatFile:ReadCellRaw(row, column - 1) } for i, b in ipairs(out) do out[i] = string.format("%02X", b) @@ -76,3 +83,280 @@ function RowListClass:GetRowValue(column, index, row) end end end + +function RowListClass:Draw(viewPort) + local x, y = self:GetPos() + local width, height = self:GetSize() + local rowHeight = self.rowHeight + local list = self.list + + local colOffset = 0 + for index, column in ipairs(self.colList) do + column._offset = colOffset + column._width = self:GetColumnProperty(column, "width") or (index == #self.colList and self.scroll and width - 20 or width - colOffset) or 0 + colOffset = colOffset + column._width + end + + local scrollBarV = self.controls.scrollBarV + local rowRegion = self:GetRowRegion() + scrollBarV:SetContentDimension(#list * rowHeight, rowRegion.height) + local scrollOffsetV = scrollBarV.offset + local scrollBarH = self.controls.scrollBarH + local lastCol = self.colList[#self.colList] + scrollBarH:SetContentDimension(lastCol._offset + lastCol._width, rowRegion.width) + local scrollOffsetH = scrollBarH.offset + + local cursorX, cursorY = GetCursorPos() + + local label = self:GetProperty("label") + if label then + DrawString(x + self.labelPositionOffset[1], y - 20 + self.labelPositionOffset[2], "LEFT", 16, self.font, label) + end + if self.hasFocus then + SetDrawColor(1, 1, 1) + else + SetDrawColor(0.5, 0.5, 0.5) + end + DrawImage(nil, x, y, width, height) + SetDrawColor(0, 0, 0) + DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + self:DrawControls(viewPort) + + SetViewport(x + 2, y + 2, self.scroll and width - 20 or width, height - 4 - (self.scroll and self.scrollH and 16 or 0)) + local textOffsetY = self.showRowSeparators and 2 or 0 + local textHeight = rowHeight - textOffsetY * 2 + local minIndex = math.floor(scrollOffsetV / rowHeight + 1) + local maxIndex = math.min(math.floor((scrollOffsetV + height) / rowHeight + 1), #list) + for colIndex, column in ipairs(self.colList) do + local colFont = self:GetColumnProperty(column, "font") or "VAR" + local clipWidth = DrawStringWidth(textHeight, colFont, "...") + colOffset = column._offset - scrollOffsetH + local colWidth = column._width + local relX = cursorX - (x + 2) + local relY = cursorY - (y + 2) + for index = minIndex, maxIndex do + local lineY = rowHeight * (index - 1) - scrollOffsetV + (self.colLabels and 18 or 0) + local value = list[index] + local text = self:GetRowValue(colIndex, index, value) + local textWidth = DrawStringWidth(textHeight, colFont, text) + if textWidth > colWidth - 2 then + local clipIndex = DrawStringCursorIndex(textHeight, colFont, text, colWidth - clipWidth - 2, 0) + text = text:sub(1, clipIndex - 1) .. "..." + textWidth = DrawStringWidth(textHeight, colFont, text) + end + if self.showRowSeparators then + if self.hasFocus and value == self.selValue then + SetDrawColor(1, 1, 1) + else + SetDrawColor(0.5, 0.5, 0.5) + end + DrawImage(nil, colOffset, lineY, not self.scroll and colWidth - 4 or colWidth, rowHeight) + if index % 2 == 0 then + SetDrawColor(0.05, 0.05, 0.05) + else + SetDrawColor(0, 0, 0) + end + DrawImage(nil, colOffset, lineY + 1, not self.scroll and colWidth - 4 or colWidth, rowHeight - 2) + elseif value == self.selValue then + if self.hasFocus and value == self.selValue then + SetDrawColor(1, 1, 1) + else + SetDrawColor(0.5, 0.5, 0.5) + end + DrawImage(nil, colOffset, lineY, not self.scroll and colWidth - 4 or colWidth, rowHeight) + SetDrawColor(0.15, 0.15, 0.15) + DrawImage(nil, colOffset, lineY + 1, not self.scroll and colWidth - 4 or colWidth, rowHeight - 2) + end + if not self.SetHighlightColor or not self:SetHighlightColor(index, value) then + SetDrawColor(1, 1, 1) + end + DrawString(colOffset, lineY + textOffsetY, "LEFT", textHeight, colFont, text) + end + if self.colLabels then + local mOver = relX >= colOffset and relX <= colOffset + colWidth and relY >= 0 and relY <= 18 + + local isSelected = (colIndex - 1) == main.curSpecColIndex + local outerColor + if mOver then + outerColor = {1, 1, 1} + elseif isSelected then + outerColor = {1, 0.3, 0.2} + else + outerColor = {0.5, 0.5, 0.5} + end + local innerColor = isSelected and {0.6, 0.25, 0.2} or (mOver and self:GetColumnProperty(column, "sortable") and {0.33, 0.33, 0.33} or {0.15, 0.15, 0.15}) + + SetDrawColor(unpack(outerColor)) + DrawImage(nil, colOffset, 1, colWidth, 18) + SetDrawColor(unpack(innerColor)) + DrawImage(nil, colOffset + 1, 2, colWidth - 2, 16) + + local label = self:GetColumnProperty(column, "label") + if label and #label > 0 then + SetDrawColor(1, 1, 1) + DrawString(colOffset + colWidth/2, 4, "CENTER_X", 12, "VAR", label) + end + end + end + if #self.list == 0 and self.defaultText then + SetDrawColor(1, 1, 1) + DrawString(2, 2, "LEFT", 14, self.font, self.defaultText) + end + SetViewport() +end + +function RowListClass:ReSort(colIndex) + local asc = true + if self.lastSortedCol == colIndex then + asc = not self.sortAsc + end + + table.sort(self.list, function(a, b) + local valA = self:GetRowValue(colIndex, nil, a) + local valB = self:GetRowValue(colIndex, nil, b) + + local isBlankA = valA == nil or valA == "" or tostring(valA):match("^%s*$") + local isBlankB = valB == nil or valB == "" or tostring(valB):match("^%s*$") + + -- Always put blank items at the bottom + if isBlankA and not isBlankB then + return false + elseif not isBlankA and isBlankB then + return true + elseif isBlankA and isBlankB then + return false + end + + local numA = tonumber(valA) + local numB = tonumber(valB) + + if numA and numB then + if asc then + return numA < numB + else + return numA > numB + end + else + valA = tostring(valA or "") + valB = tostring(valB or "") + if asc then + return valA < valB + else + return valA > valB + end + end + end) + + self.lastSortedCol = colIndex + self.sortAsc = asc +end + +function RowListClass:OnKeyUp(key, doubleClick) + if not self:IsShown() or not self:IsEnabled() then + return + end + + local function isScrollKey(k) + if k == "WHEELUP" then return true, -1, 10 end + if k == "WHEELDOWN" then return true, 1, -10 end + return false, 0, 0 + end + + local mOverControl = self:GetMouseOverControl() + if mOverControl and mOverControl.OnKeyDown then + return mOverControl:OnKeyDown(key) + end + + if not self:IsMouseOver() and key:match("BUTTON") then + return + end + + -- Get cursor info + local x, y = self:GetPos() + local cursorX, cursorY = GetCursorPos() + local scrollOffsetH = self.controls.scrollBarH and self.controls.scrollBarH.offset or 0 + local relX = cursorX - (x + 2) + local relY = cursorY - (y + 2) + local adjustedRelX = relX + scrollOffsetH + + -- Middle-click resets column width + if key == "MIDDLEBUTTON" then + for colIndex, column in ipairs(self.colList) do + local colOffset = column._offset + local colWidth = column.width or column._width + if colOffset and colWidth then + local mOver = adjustedRelX >= colOffset and adjustedRelX <= colOffset + colWidth and relY >= 0 and relY <= 18 + if mOver then + -- Initialize if not present + self._autoSizeToggleState[colIndex] = not self._autoSizeToggleState[colIndex] + + local newWidth + if self._autoSizeToggleState[colIndex] then + -- First toggle: size to contents + local maxWidth = 0 + for _, rowIndex in ipairs(self.list) do + local val = self:GetRowValue(colIndex, nil, rowIndex) + if val ~= nil then + local width = DrawStringWidth(self.rowHeight, "FIXED", tostring(val)) + maxWidth = math.max(maxWidth, width) + end + end + local labelWidth = DrawStringWidth(self.rowHeight, "FIXED", tostring(column.label or "")) + newWidth = math.max(40, math.max(maxWidth, labelWidth) + 10) + else + -- Second toggle: reset to label or 150, whichever is greater + local labelWidth = DrawStringWidth(self.rowHeight, "FIXED", tostring(column.label or "")) + newWidth = math.max(150, labelWidth + 10) + end + + column.width = newWidth + + if column.specColRef then + column.specColRef.width = newWidth + main.curSpecCol = column.specColRef + main.controls.colWidth:SetText(newWidth) + end + + self:BuildColumns() + return self + end + end + end + return self + end + -- Scroll behavior + local isScroll, scrollStep, colDelta = isScrollKey(key) + if isScroll then + local overColumnHeader = false + for _, column in ipairs(self.colList) do + local colOffset = column._offset + local colWidth = column.width or column._width + if colOffset and colWidth then + local mOver = adjustedRelX >= colOffset and adjustedRelX <= colOffset + colWidth and relY >= 0 and relY <= 18 + if mOver then + -- Widen column if hovering over it + overColumnHeader = true + local newWidth = math.max(40, colWidth + colDelta) + column.width = newWidth + if column.specColRef then + column.specColRef.width = newWidth + main.curSpecCol = column.specColRef + main.controls.colWidth:SetText(newWidth) + end + self:BuildColumns() + break + end + end + end + -- Scroll vertically or horizontally if not resizing column + if not overColumnHeader then + if self.scroll and self.scrollH and IsKeyDown("SHIFT") then + self.controls.scrollBarH:Scroll(scrollStep) + else + self.controls.scrollBarV:Scroll(scrollStep) + end + end + return self + end + return self +end diff --git a/src/Export/Classes/ScriptListControl.lua b/src/Export/Classes/ScriptListControl.lua index d31b9da9..048f0254 100644 --- a/src/Export/Classes/ScriptListControl.lua +++ b/src/Export/Classes/ScriptListControl.lua @@ -15,6 +15,9 @@ end function ScriptListClass:OnSelClick(index, script, doubleClick) if doubleClick then + if main.controls.clearAutoClearOutput.state then + wipeTable(main.scriptOutput) + end local errMsg = PLoadModule("Scripts/"..script..".lua") if errMsg then print(errMsg) diff --git a/src/Export/Main.lua b/src/Export/Main.lua index 4e53c74e..c764d17b 100644 --- a/src/Export/Main.lua +++ b/src/Export/Main.lua @@ -205,8 +205,10 @@ function main:Init() return #self.scriptOutput > 0 end } - - self.controls.helpText = new("LabelControl", {"TOPLEFT",self.controls.clearOutput,"BOTTOMLEFT"}, {0, 12, 100, 16}, "Press Ctrl+F5 to re-export\ndata from the game") + self.controls.clearAutoClearOutput = new("CheckBoxControl", { "TOPLEFT", self.controls.clearOutput, "BOTTOMLEFT" }, { 120, 10, 20, 20 }, "Auto Clear Output:", function(state) + self.clearAutoClearOutput = state + end, nil, false) + self.controls.helpText = new("LabelControl", {"TOPLEFT",self.controls.clearOutput,"BOTTOMLEFT"}, {0, 42, 100, 16}, "Press Ctrl+F5 to re-export\ndata from the game") self.controls.scriptList = new("ScriptListControl", nil, {270, 35, 100, 300}) { shown = function() @@ -219,7 +221,12 @@ function main:Init() end } - self.controls.datList = new("DatListControl", nil, {10, 70, 250, function() return self.screenH - 70 end}) + self.controls.datSearch = new("EditControl", {"TOPLEFT", self.controls.datSource, "BOTTOMLEFT"}, {0, 2, 250, 18}, nil, "^7Search", nil, nil, function(buf) + self.controls.datList.searchBuf = buf + self.controls.datList:BuildFilteredList() + end, nil, nil, true) + + self.controls.datList = new("DatListControl", {"TOPLEFT",self.controls.datSearch,"BOTTOMLEFT"}, {0, 2, 250, function() return self.screenH - 100 end}) self.controls.specEditToggle = new("ButtonControl", nil, {270, 10, 100, 18}, function() return self.editSpec and "Done <<" or "Edit >>" end, function() self.editSpec = not self.editSpec @@ -316,6 +323,7 @@ function main:Init() } self.controls.filter.tooltipText = "Takes a Lua expression that returns true or false for a row.\nE.g. `Id:match(\"test\")` or for a key column, `Col and Col.Id:match(\"test\")`" self.controls.filterError = new("LabelControl", {"LEFT",self.controls.filter,"RIGHT"}, {4, 2, 0, 14}, "") + self.controls.showRaw = new("LabelControl", {"LEFT",self.controls.filter,"RIGHT"}, {600, 2, 0, 14}, "^7Hold ALT to show raw data.") self.controls.rowList = new("RowListControl", nil, {270, 0, 0, 0}) { y = function() @@ -428,6 +436,19 @@ function main:OnFrame() end function main:OnKeyDown(key, doubleClick) + -- Ctrl+F shortcut for focusing dat file Search + if key == "f" and IsKeyDown("CTRL") then + if self.controls and self.controls.datSearch and self.SelectControl then + self:SelectControl(self.controls.datSearch) + end + return + -- ESC key closes Dat window so that the script menu is shown. + elseif key == "ESCAPE" then + if self.controls and self.controls.scripts and self.SelectControl then + self:SetCurrentDat() + end + return + end t_insert(self.inputEvents, { type = "KeyDown", key = key, doubleClick = doubleClick }) end