From 1375069ddd25d79eb80381e698d88e8a2fc33386 Mon Sep 17 00:00:00 2001 From: NOSTY Date: Sat, 21 Feb 2026 05:56:10 +0700 Subject: [PATCH 1/5] Add search functionality to Notes tab --- src/Classes/EditControl.lua | 4 +- src/Classes/NotesTab.lua | 113 +++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 4 deletions(-) diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 2964057adf..00c7804d57 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) @@ -364,7 +364,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 f78ea2eb41..fe3ec805d1 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -31,12 +31,38 @@ 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", "%c", 100, function(buf) + self.searchStr = buf + self.controls.edit.sel = nil + self:FindNext() + 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 + 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 @@ -67,6 +93,89 @@ 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 = {} + for s, e in textLower:gmatch("()%^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 + for s, e in textLower:gmatch("()%^_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 + 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.s >= targetIndex then + nextMatch = match + break + end + end + self.controls.edit.sel = nextMatch.s + self.controls.edit.caret = nextMatch.e + 1 + 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.controls.edit:ScrollCaretIntoView() +end + function NotesTabClass:Load(xml, fileName) for _, node in ipairs(xml) do if type(node) == "string" then From 8c35db8ab76b8a7213a2618d6c2e3970cc02bf67 Mon Sep 17 00:00:00 2001 From: NOSTY Date: Sat, 21 Feb 2026 06:21:23 +0700 Subject: [PATCH 2/5] Enable clear button on search bar --- src/Classes/NotesTab.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index fe3ec805d1..816a24485d 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -35,7 +35,7 @@ Below are some common color codes PoB uses: ]] self.searchStr = buf self.controls.edit.sel = nil self:FindNext() - end) + end, nil, nil, true) 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 From 78b1529b9abe22bb5fc434b9121d2a8de83bec67 Mon Sep 17 00:00:00 2001 From: NOSTY Date: Sat, 21 Feb 2026 06:46:21 +0700 Subject: [PATCH 3/5] Initialize searchStr --- src/Classes/NotesTab.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index 816a24485d..95adc2ac0c 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,7 +32,7 @@ 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.search = new("EditControl", {"TOPLEFT",self.controls.strength,"TOPLEFT"}, {0, 24, 416, 20}, "", "Search", "%c", 100, function(buf) + 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 self:FindNext() From fbcba4da836295e56dc74cbe01225fa283e07495 Mon Sep 17 00:00:00 2001 From: NOSTY Date: Sun, 22 Feb 2026 00:30:52 +0700 Subject: [PATCH 4/5] Restore match highlight when refocusing search box --- src/Classes/NotesTab.lua | 43 +++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/Classes/NotesTab.lua b/src/Classes/NotesTab.lua index 95adc2ac0c..e08b5fa5e2 100644 --- a/src/Classes/NotesTab.lua +++ b/src/Classes/NotesTab.lua @@ -35,8 +35,20 @@ Below are some common color codes PoB uses: ]] 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 @@ -51,6 +63,17 @@ Below are some common color codes PoB uses: ]] 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) @@ -68,6 +91,7 @@ Below are some common color codes PoB uses: ]] 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) @@ -102,10 +126,13 @@ function NotesTabClass:GetSearchMatches() 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 = {} - for s, e in textLower:gmatch("()%^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 - for s, e in textLower:gmatch("()%^_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 + 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 @@ -142,13 +169,15 @@ function NotesTabClass:FindNext() 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.s >= targetIndex then + 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 @@ -157,7 +186,6 @@ function NotesTabClass:FindPrev() self.controls.edit.sel = nil return end - local matches = self:GetSearchMatches() if #matches == 0 then self.controls.edit.sel = nil @@ -171,9 +199,10 @@ function NotesTabClass:FindPrev() 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 From 528b156b010bcfdd8cc48e29c11fb7a8e01df454 Mon Sep 17 00:00:00 2001 From: NOSTY Date: Sun, 22 Feb 2026 00:32:31 +0700 Subject: [PATCH 5/5] Fix coloured text rendering --- src/Classes/EditControl.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 00c7804d57..ddf1f0c212 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -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