diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 2964057ad..ddf1f0c21 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -290,7 +290,7 @@ function EditClass:Draw(viewPort, noTooltip) local marginR = self.controls.scrollBarV:IsShown() and 14 or 0 local marginB = self.controls.scrollBarH:IsShown() and 14 or 0 SetViewport(textX, textY, width - 4 - marginL - marginR, height - 4 - marginB) - if not self.hasFocus then + if not self.hasFocus and not self.sel then if self.buf == '' and self.placeholder then SetDrawColor(self.disableCol) DrawString(-self.controls.scrollBarH.offset, -self.controls.scrollBarV.offset, "LEFT", textHeight, self.font, self.placeholder) @@ -346,10 +346,11 @@ function EditClass:Draw(viewPort, noTooltip) sel = sel .. " " end local selWidth = DrawStringWidth(textHeight, self.font, sel) + local r,g,b,a = GetDrawColor() SetDrawColor(self.selBGCol) DrawImage(nil, textX, textY, selWidth, textHeight) DrawString(textX, textY, "LEFT", textHeight, self.font, sel) - SetDrawColor(self.textCol) + SetDrawColor(r,g,b,a) textX = textX + selWidth end if right >= s and right < e and right == self.caret then @@ -364,7 +365,7 @@ function EditClass:Draw(viewPort, noTooltip) end textY = textY + textHeight end - if caretX then + if caretX and self.hasFocus then if (GetTime() - self.blinkStart) % 1000 < 500 then SetDrawColor(self.textCol) DrawImage(nil, caretX, caretY, 1, textHeight) diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index f78ea2eb4..e08b5fa5e 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -12,6 +12,7 @@ local NotesTabClass = newClass("NotesTab", "ControlHost", "Control", function(se self.build = build self.lastContent = "" + self.searchStr = "" self.showColorCodes = false local notesDesc = [[^7You can use Ctrl +/- (or Ctrl+Scroll) to zoom in and out and Ctrl+0 to reset. @@ -31,16 +32,66 @@ Below are some common color codes PoB uses: ]] self.controls.intelligence = new("ButtonControl", {"TOPLEFT",self.controls.dexterity,"TOPLEFT"}, {120, 0, 100, 18}, colorCodes.INTELLIGENCE.."INTELLIGENCE", function() self:SetColor(colorCodes.INTELLIGENCE) end) self.controls.default = new("ButtonControl", {"TOPLEFT",self.controls.intelligence,"TOPLEFT"}, {120, 0, 100, 18}, "^7DEFAULT", function() self:SetColor("^7") end) - self.controls.edit = new("EditControl", {"TOPLEFT",self.controls.fire,"TOPLEFT"}, {0, 48, 0, 0}, "", nil, "^%C\t\n", nil, nil, 16, true) + self.controls.search = new("EditControl", {"TOPLEFT",self.controls.strength,"TOPLEFT"}, {0, 24, 416, 20}, "", "Search", nil, 100, function(buf) + self.searchStr = buf + self.controls.edit.sel = nil + if buf == "" then + self.lastMatchS = nil + self.lastMatchE = nil + return + end + self:FindNext() + end, nil, nil, true) + local originalSearchDraw = self.controls.search.Draw + self.controls.search.Draw = function(control, viewPort, noTooltip) + if not control.hasFocus and control.sel == control.caret then + control.sel = nil + end + originalSearchDraw(control, viewPort, noTooltip) + end + self.controls.search.tooltipText = "Type to search your notes.\nPress Enter for next match, Shift+Enter for previous." + -- Intercept OnKeyDown instead of using enterFunc to prevent the engine from dropping keyboard focus + local originalOnKeyDown = self.controls.search.OnKeyDown + self.controls.search.OnKeyDown = function(control, key, doubleClick) + if key == "RETURN" then + if IsKeyDown("SHIFT") then + self:FindPrev() + else + self:FindNext() + end + return control + end + return originalOnKeyDown(control, key, doubleClick) + end + -- Intercept OnFocusGained to re-highlight matches when clicking back into the search box + local originalOnFocusGained = self.controls.search.OnFocusGained + self.controls.search.OnFocusGained = function(control) + originalOnFocusGained(control) + if self.searchStr and self.searchStr ~= "" + and self.lastMatchS + and (not self.controls.edit.sel or self.controls.edit.sel == self.controls.edit.caret) then + self.controls.edit.sel = self.lastMatchS + self.controls.edit.caret = self.lastMatchE + 1 + end + end + self.controls.btnPrev = new("ButtonControl", {"LEFT",self.controls.search,"RIGHT"}, {2, 0, 20, 20}, "<", function() + self:FindPrev() + end) + self.controls.btnNext = new("ButtonControl", {"LEFT",self.controls.btnPrev,"RIGHT"}, {2, 0, 20, 20}, ">", function() + self:FindNext() + end) + + self.controls.edit = new("EditControl", {"TOPLEFT",self.controls.search,"TOPLEFT"}, {0, 28, 0, 0}, "", nil, "^%C\t\n", nil, nil, 16, true) self.controls.edit.width = function() return self.width - 16 end self.controls.edit.height = function() - return self.height - 128 + return self.height - 150 end self.controls.toggleColorCodes = new("ButtonControl", {"TOPRIGHT",self,"TOPRIGHT"}, {-10, 70, 160, 20}, "Show Color Codes", function() self.showColorCodes = not self.showColorCodes self:SetShowColorCodes(self.showColorCodes) + self.controls.edit.sel = nil end) self:SelectControl(self.controls.edit) end) @@ -67,6 +118,94 @@ function NotesTabClass:SetColor(color) end end +function NotesTabClass:GetSearchMatches() + if self.cachedMatches and self.lastSearchStr == self.searchStr and self.lastSearchBuf == self.controls.edit.buf then + return self.cachedMatches + end + local textLower = self.controls.edit.buf:lower() + local searchLower = self.searchStr:lower() + -- Build a list of ranges covering color codes so we don't accidentally match search text inside them + local ranges = {} + if self.showColorCodes then + for s, e in textLower:gmatch("()%^x%x%x%x%x%x%x()") do t_insert(ranges, {s, e - 1}) end + for s, e in textLower:gmatch("()%^%d()") do t_insert(ranges, {s, e - 1}) end + else + for s, e in textLower:gmatch("()%^_x%x%x%x%x%x%x()") do t_insert(ranges, {s, e - 1}) end + for s, e in textLower:gmatch("()%^_%d()") do t_insert(ranges, {s, e - 1}) end + end + local matches = {} + local searchStart = 1 + while true do + local s, e = textLower:find(searchLower, searchStart, true) + if not s then break end + local valid = true + for _, r in ipairs(ranges) do + if not (e < r[1] or s > r[2]) then + valid = false + break + end + end + if valid then + t_insert(matches, {s = s, e = e}) + end + searchStart = e + 1 + end + self.cachedMatches = matches + self.lastSearchStr = self.searchStr + self.lastSearchBuf = self.controls.edit.buf + return matches +end + +function NotesTabClass:FindNext() + if not self.searchStr or self.searchStr == "" then + self.controls.edit.sel = nil + return + end + local matches = self:GetSearchMatches() + if #matches == 0 then + self.controls.edit.sel = nil + return + end + local targetIndex = self.controls.edit.sel and self.controls.edit.caret or 1 + local nextMatch = matches[1] + for _, match in ipairs(matches) do + if match.e >= targetIndex then + nextMatch = match + break + end + end + self.controls.edit.sel = nextMatch.s + self.controls.edit.caret = nextMatch.e + 1 + self.lastMatchS = nextMatch.s + self.lastMatchE = nextMatch.e + self.controls.edit:ScrollCaretIntoView() +end + +function NotesTabClass:FindPrev() + if not self.searchStr or self.searchStr == "" then + self.controls.edit.sel = nil + return + end + local matches = self:GetSearchMatches() + if #matches == 0 then + self.controls.edit.sel = nil + return + end + local targetIndex = self.controls.edit.sel or (#self.controls.edit.buf + 1) + local prevMatch = matches[#matches] + for i = #matches, 1, -1 do + if matches[i].s < targetIndex then + prevMatch = matches[i] + break + end + end + self.controls.edit.sel = prevMatch.s + self.controls.edit.caret = prevMatch.e + 1 + self.lastMatchS = prevMatch.s + self.lastMatchE = prevMatch.e + self.controls.edit:ScrollCaretIntoView() +end + function NotesTabClass:Load(xml, fileName) for _, node in ipairs(xml) do if type(node) == "string" then