diff --git a/Core.lua b/Core.lua index 0e1c3e0..5ce882b 100644 --- a/Core.lua +++ b/Core.lua @@ -24,7 +24,7 @@ EasyFind.db = {} -- SavedVariables version. Increment when changing DB schema. -- Each migration runs once: if saved dbVersion < DB_VERSION, run all steps in order. -local DB_VERSION = 1 +local DB_VERSION = 3 -- SavedVariables defaults - new keys are auto-merged for existing users local DB_DEFAULTS = { @@ -38,7 +38,7 @@ local DB_DEFAULTS = { mapSearchWidth = 0.88, uiSearchWidth = 0.88, uiResultsScale = 1.0, - uiResultsWidth = 300, + uiResultsWidth = 350, mapResultsScale = 1.0, mapResultsWidth = 300, searchBarOpacity = 0.75, -- ns.DEFAULT_OPACITY @@ -59,8 +59,8 @@ local DB_DEFAULTS = { resultsTheme = "Retail", -- "Classic" or "Retail" indicatorStyle = "EasyFind Arrow", -- Indicator texture style indicatorColor = "Yellow", -- Indicator color preset - uiMaxResults = 10, -- Maximum visible UI search results (3-24) - mapMaxResults = 6, -- Maximum visible map search results (3-24) + uiResultsHeight = 280, -- Visible height of UI search results panel in pixels + mapResultsHeight = 168, -- Visible height of map search results panel in pixels showTruncationMessage = true, -- Show "more results available" message when truncated hardResultsCap = false, -- Hard cap on results (no "more results" message) staticOpacity = false, -- Keep opacity constant while moving @@ -118,6 +118,21 @@ local DB_MIGRATIONS = { end if db.uiResultsWidth == 1.0 then db.uiResultsWidth = 300 end end, + -- [2] = Widen default UI results panel from 300 to 350 + [2] = function(db) + if db.uiResultsWidth == 300 then db.uiResultsWidth = 350 end + end, + -- [3] = Replace row-count settings with pixel height + [3] = function(db) + if not db.uiResultsHeight then + db.uiResultsHeight = db.uiMaxResults and (db.uiMaxResults * 28) or 280 + end + if not db.mapResultsHeight then + db.mapResultsHeight = db.mapMaxResults and (db.mapMaxResults * 26 + 12) or 168 + end + db.uiMaxResults = nil + db.mapMaxResults = nil + end, } -- Fields that are runtime-only and must not persist in SavedVariables diff --git a/MapSearch.lua b/MapSearch.lua index 0fb6451..0dbba9b 100644 --- a/MapSearch.lua +++ b/MapSearch.lua @@ -6234,16 +6234,15 @@ function MapSearch:ShowResults(results) -- Must compute maxVisibleHeight (including screen-space clamping) BEFORE the -- button loop, otherwise willScroll can be false while hasScroll ends up true -- and the scrollbar overlaps full-width buttons. - local maxVisibleRows = EasyFind.db.mapMaxResults or 6 local scrollBar = resultsFrame.scrollBar - local maxVisibleHeight = maxVisibleRows * 26 + 12 + local maxVisibleHeight = EasyFind.db.mapResultsHeight or 168 local preAnchor = activeSearchFrame or searchFrame if isGlobalSearch and globalSearchFrame then preAnchor = globalSearchFrame end local screenH = UIParent:GetHeight() if resultsAbove then - local available = (preAnchor:GetTop() or (screenH / 2)) - 16 + local available = screenH - (preAnchor:GetTop() or (screenH / 2)) - 16 if available > 0 and maxVisibleHeight > available then maxVisibleHeight = available end diff --git a/Options.lua b/Options.lua index 21d31dd..4b3465c 100644 --- a/Options.lua +++ b/Options.lua @@ -1674,7 +1674,7 @@ function Options:DoResetAll() EasyFind.db.mapSearchWidth = 0.88 EasyFind.db.uiSearchWidth = 0.88 EasyFind.db.uiResultsScale = 1.0 - EasyFind.db.uiResultsWidth = 300 + EasyFind.db.uiResultsWidth = 350 EasyFind.db.mapResultsScale = 1.0 EasyFind.db.mapResultsWidth = 300 EasyFind.db.searchBarOpacity = DEFAULT_OPACITY @@ -1691,8 +1691,8 @@ function Options:DoResetAll() EasyFind.db.globalMapDirectOpen = false EasyFind.db.smartShow = false EasyFind.db.resultsTheme = "Retail" - EasyFind.db.uiMaxResults = 10 - EasyFind.db.mapMaxResults = 6 + EasyFind.db.uiResultsHeight = 280 + EasyFind.db.mapResultsHeight = 168 EasyFind.db.pinsCollapsed = false EasyFind.db.staticOpacity = false EasyFind.db.indicatorStyle = "EasyFind Arrow" @@ -1840,9 +1840,9 @@ function Options:DoResetUI() EasyFind.db.uiSearchScale = 1.0 EasyFind.db.uiSearchWidth = 0.88 EasyFind.db.uiResultsScale = 1.0 - EasyFind.db.uiResultsWidth = 300 + EasyFind.db.uiResultsWidth = 350 EasyFind.db.uiSearchPosition = nil - EasyFind.db.uiMaxResults = 10 + EasyFind.db.uiResultsHeight = 280 EasyFind.db.uiSearchFilters = { ui = true, mounts = false, toys = false, pets = false, map = false } EasyFind.db.uiMapSearchLocal = true @@ -1884,7 +1884,7 @@ function Options:DoResetMap() EasyFind.db.globalSearchPosition = nil EasyFind.db.mapSearchPositionMax = nil EasyFind.db.globalSearchPositionMax = nil - EasyFind.db.mapMaxResults = 6 + EasyFind.db.mapResultsHeight = 168 EasyFind.db.mapPinHighlight = true EasyFind.db.blinkingPins = false EasyFind.db.minimapArrowGlow = true @@ -1942,7 +1942,7 @@ function Options:DoResetUIPositions() EasyFind.db.uiSearchScale = 1.0 EasyFind.db.uiSearchWidth = 0.88 EasyFind.db.uiResultsScale = 1.0 - EasyFind.db.uiResultsWidth = 300 + EasyFind.db.uiResultsWidth = 350 if _G["EasyFindSearchFrame"] and ns.UI then if ns.UI.ResetPosition then ns.UI:ResetPosition() end if ns.UI.UpdateScale then ns.UI:UpdateScale() end diff --git a/README.md b/README.md index bcfdad1..16db2bc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,22 @@ EasyFind lets you search for any panel, tab, or setting in WoW's interface and a Direct open skips straight to the panel instantly

+## Table of Contents + +- [Features](#features) + - [UI Search](#ui-search) + - [Map Search](#map-search) + - [Search Features](#search-features) + - [Two Navigation Modes](#two-navigation-modes) +- [How to Use](#how-to-use) +- [First-Time Setup](#first-time-setup) +- [Slash Commands](#slash-commands) +- [Keybinds](#keybinds) +- [Options](#options) +- [Moving the Search Bars](#moving-the-search-bars) +- [Feedback](#feedback) +- [Links](#links) + ## Features ### UI Search @@ -39,9 +55,8 @@ Find and navigate to any interface element such as: Currencies show your current amounts inline, and reputations display progress bars with renown level, friendship rank, or traditional standing.

-currency search -reputation search
-Currency amounts and reputation progress bars in search results +currency search +Reputation progress bars (left) and currency amounts (right) in search results

### Map Search @@ -60,6 +75,17 @@ Locate places and NPCs across Azeroth: * Chromie (Timewalking Campaigns) * Rare mobs (with optional auto-tracking that always shows active rares on the map) +

+all rares search +single rare search
+Search "rare" to see all active rares, or select a specific one to track it +

+ +

+rares filter
+Rares category with auto-track toggle in the filter menu +

+ Search the zone of the map you're currently focused on with the local bar (left side), or search across all of Azeroth with the global bar (right side). Map search results also appear in the UI search bar when the Map Search filter is enabled.

diff --git a/Rescaler.lua b/Rescaler.lua index efb79a6..f2f014b 100644 --- a/Rescaler.lua +++ b/Rescaler.lua @@ -18,8 +18,9 @@ local HANDLE_SIZE = 10 local GLOW_OUTSET = 6 local PREVIEW_ROW_H = 26 local PREVIEW_PAD = 16 -local MIN_ROWS = 3 -local MAX_ROWS = 24 +local PREVIEW_MAX_ROWS = 24 +local MIN_HEIGHT = 80 +local MAX_HEIGHT = 700 local MIN_FONT = 0.5 local MAX_FONT = 2.0 @@ -32,19 +33,20 @@ local resultsOverlay = nil -- glow around results local donePanel = nil -- instruction + Done button local previewResults = nil -- fake results frame for preview -local function GetMaxRows() - if activeMode == "ui" then return EasyFind.db.uiMaxResults or 10 end - return EasyFind.db.mapMaxResults or 6 +local function GetResultsHeight() + if activeMode == "ui" then return EasyFind.db.uiResultsHeight or 280 end + return EasyFind.db.mapResultsHeight or 168 end -local function SetMaxRows(rows) - if activeMode == "ui" then EasyFind.db.uiMaxResults = rows - else EasyFind.db.mapMaxResults = rows end +local function SetResultsHeight(h) + h = mmax(MIN_HEIGHT, mmin(MAX_HEIGHT, mfloor(h + 0.5))) + if activeMode == "ui" then EasyFind.db.uiResultsHeight = h + else EasyFind.db.mapResultsHeight = h end end -local function GetDefaultMaxRows() - if activeMode == "ui" then return 10 end - return 6 +local function GetDefaultResultsHeight() + if activeMode == "ui" then return 280 end + return 168 end local function GetFontScale() @@ -71,6 +73,22 @@ local function ClampWidth(v) return mmax(MIN_WIDTH, mmin(MAX_WIDTH, v)) end +local function GetScreenMaxHeight(anchorAbove) + if not activeSearchBar then return MAX_HEIGHT end + local available + if anchorAbove then + local screenTop = UIParent:GetTop() or UIParent:GetHeight() + local barTop = activeSearchBar:GetTop() or (screenTop / 2) + available = screenTop - barTop - 16 + else + available = (activeSearchBar:GetBottom() or (UIParent:GetHeight() / 2)) - 16 + end + if available > 0 then + return mmax(MIN_HEIGHT, mmin(MAX_HEIGHT, mfloor(available))) + end + return MAX_HEIGHT +end + local function AddResetButton(editBox, onConfirm) local btn = CreateFrame("Button", nil, editBox:GetParent()) btn:SetSize(editBox:GetWidth(), 16) @@ -222,14 +240,14 @@ end -- Preview results (fake rows to show results area) -local function CreatePreviewResults(parent, targetFrame, width, visibleRows, anchorAbove, leftAligned) +local function CreatePreviewResults(parent, targetFrame, width, heightPx, anchorAbove, leftAligned) local fontScale = GetFontScale() local rowH = PREVIEW_ROW_H * fontScale local frame = CreateFrame("Frame", nil, parent, "BackdropTemplate") frame:SetFrameStrata("FULLSCREEN_DIALOG") frame:SetFrameLevel(190) frame:SetWidth(width) - frame:SetHeight(visibleRows * rowH + PREVIEW_PAD) + frame:SetHeight(heightPx) if leftAligned then if anchorAbove then @@ -254,8 +272,9 @@ local function CreatePreviewResults(parent, targetFrame, width, visibleRows, anc frame:SetBackdropColor(0.1, 0.1, 0.1, 0.85) frame:SetClipsChildren(true) + local nVisible = mfloor((heightPx - PREVIEW_PAD) / rowH) frame.rows = {} - for i = 1, MAX_ROWS do + for i = 1, PREVIEW_MAX_ROWS do local row = frame:CreateFontString(nil, "OVERLAY", "GameFontDisable") row:SetPoint("LEFT", frame, "LEFT", 12, 0) row:SetPoint("RIGHT", frame, "RIGHT", -12, 0) @@ -269,16 +288,17 @@ local function CreatePreviewResults(parent, targetFrame, width, visibleRows, anc local path, baseSize, flags = GameFontDisable:GetFont() row:SetFont(path, baseSize * fontScale, flags) end - row:SetShown(i <= visibleRows) + row:SetShown(i <= nVisible) frame.rows[i] = row end - frame.SetVisibleRows = function(self, n) - n = mmax(MIN_ROWS, mmin(MAX_ROWS, n)) - local rowH = PREVIEW_ROW_H * GetFontScale() - self:SetHeight(n * rowH + PREVIEW_PAD) - for i = 1, MAX_ROWS do - self.rows[i]:SetShown(i <= n) + frame.SetPreviewHeight = function(self, h) + h = mmax(MIN_HEIGHT, mmin(MAX_HEIGHT, mfloor(h + 0.5))) + self:SetHeight(h) + local scaledRowH = PREVIEW_ROW_H * GetFontScale() + local nVis = mfloor((h - PREVIEW_PAD) / scaledRowH) + for i = 1, PREVIEW_MAX_ROWS do + self.rows[i]:SetShown(i <= nVis) end end @@ -286,8 +306,9 @@ local function CreatePreviewResults(parent, targetFrame, width, visibleRows, anc local scale = GetFontScale() local path, baseSize, flags = GameFontDisable:GetFont() local scaledRowH = PREVIEW_ROW_H * scale - local rows = GetMaxRows() - for i = 1, MAX_ROWS do + local h = GetResultsHeight() + local nVis = mfloor((h - PREVIEW_PAD) / scaledRowH) + for i = 1, PREVIEW_MAX_ROWS do local row = self.rows[i] row:SetFont(path, baseSize * scale, flags) row:SetHeight(scaledRowH) @@ -295,8 +316,9 @@ local function CreatePreviewResults(parent, targetFrame, width, visibleRows, anc row:SetPoint("LEFT", self, "LEFT", 12, 0) row:SetPoint("RIGHT", self, "RIGHT", -12, 0) row:SetPoint("TOP", self, "TOP", 0, -8 - (i - 1) * scaledRowH) + row:SetShown(i <= nVis) end - self:SetHeight(rows * scaledRowH + PREVIEW_PAD) + self:SetHeight(h) end return frame @@ -351,9 +373,9 @@ local function SetupWidthDrag(handle, getWidth, setWidth, widthLabel, side) end) end --- Corner drag handler (width + rows combo) +-- Corner drag handler (width + height combo) -local function SetupCornerDrag(handle, preview, getWidth, setWidth, widthLabel, rowsLabel, anchorAbove) +local function SetupCornerDrag(handle, preview, getWidth, setWidth, widthLabel, heightLabel, anchorAbove) handle:SetScript("OnDragStart", function(self) self.dragging = true local cx, cy = GetCursorPosition() @@ -361,8 +383,8 @@ local function SetupCornerDrag(handle, preview, getWidth, setWidth, widthLabel, self.startX = cx / es self.startY = cy / es self.startWidth = getWidth() - self.startRows = GetMaxRows() - self.scaledRowH = PREVIEW_ROW_H * GetFontScale() + self.startHeight = GetResultsHeight() + self.maxH = GetScreenMaxHeight(anchorAbove) end) handle:SetScript("OnDragStop", function(self) self.dragging = false @@ -383,23 +405,22 @@ local function SetupCornerDrag(handle, preview, getWidth, setWidth, widthLabel, local dy = self.startY - cy if anchorAbove then dy = -dy end - local rowDelta = mfloor(dy / self.scaledRowH + 0.5) - local rows = mmax(MIN_ROWS, mmin(MAX_ROWS, self.startRows + rowDelta)) - SetMaxRows(rows) - preview:SetVisibleRows(rows) - if rowsLabel and not rowsLabel:HasFocus() then rowsLabel:SetText(rows) end + local newH = mmax(MIN_HEIGHT, mmin(self.maxH, mfloor(self.startHeight + dy + 0.5))) + SetResultsHeight(newH) + preview:SetPreviewHeight(newH) + if heightLabel and not heightLabel:HasFocus() then heightLabel:SetText(newH) end end) end --- Row count drag handler +-- Height drag handler -local function SetupRowsDrag(handle, preview, rowsBox, anchorAbove) +local function SetupHeightDrag(handle, preview, heightBox, anchorAbove) handle:SetScript("OnDragStart", function(self) self.dragging = true local _, cy = GetCursorPosition() self.startY = cy / UIParent:GetEffectiveScale() - self.startRows = GetMaxRows() - self.scaledRowH = PREVIEW_ROW_H * GetFontScale() + self.startHeight = GetResultsHeight() + self.maxH = GetScreenMaxHeight(anchorAbove) end) handle:SetScript("OnDragStop", function(self) self.dragging = false @@ -410,11 +431,10 @@ local function SetupRowsDrag(handle, preview, rowsBox, anchorAbove) cy = cy / UIParent:GetEffectiveScale() local dy = self.startY - cy if anchorAbove then dy = -dy end - local rowDelta = mfloor(dy / self.scaledRowH + 0.5) - local rows = mmax(MIN_ROWS, mmin(MAX_ROWS, self.startRows + rowDelta)) - SetMaxRows(rows) - preview:SetVisibleRows(rows) - if not rowsBox:HasFocus() then rowsBox:SetText(rows) end + local newH = mmax(MIN_HEIGHT, mmin(self.maxH, mfloor(self.startHeight + dy + 0.5))) + SetResultsHeight(newH) + preview:SetPreviewHeight(newH) + if not heightBox:HasFocus() then heightBox:SetText(newH) end end) end @@ -445,12 +465,13 @@ local function SetupFontDrag(handle, fontLabel, preview) SetFontScale(newFont) ApplyFontUpdate() - -- Scale preview row text and height to match + -- Update preview row fonts; height stays fixed (font size doesn't change result height) if preview and preview.rows then local path, baseSize, flags = GameFontDisable:GetFont() local scaledRowH = PREVIEW_ROW_H * newFont - local rows = GetMaxRows() - for i = 1, MAX_ROWS do + local h = GetResultsHeight() + local nVis = mfloor((h - PREVIEW_PAD) / scaledRowH) + for i = 1, PREVIEW_MAX_ROWS do local row = preview.rows[i] row:SetFont(path, baseSize * newFont, flags) row:SetHeight(scaledRowH) @@ -458,8 +479,9 @@ local function SetupFontDrag(handle, fontLabel, preview) row:SetPoint("LEFT", preview, "LEFT", 12, 0) row:SetPoint("RIGHT", preview, "RIGHT", -12, 0) row:SetPoint("TOP", preview, "TOP", 0, -8 - (i - 1) * scaledRowH) + row:SetShown(i <= nVis) end - preview:SetHeight(rows * scaledRowH + PREVIEW_PAD) + preview:SetHeight(h) end if not fontLabel:HasFocus() then @@ -533,7 +555,7 @@ local function BuildBarOverlay(parent, targetFrame, mode) return overlay end -local function BuildResultsOverlay(parent, targetFrame) +local function BuildResultsOverlay(parent, targetFrame, anchorAbove) local overlay = CreateGlowOverlay("EasyFindRescaleResultsGlow", parent, targetFrame) overlay:SetPoint("TOPLEFT", targetFrame, "TOPLEFT", -GLOW_OUTSET, GLOW_OUTSET) overlay:SetPoint("BOTTOMRIGHT", targetFrame, "BOTTOMRIGHT", GLOW_OUTSET, -GLOW_OUTSET) @@ -546,8 +568,12 @@ local function BuildResultsOverlay(parent, targetFrame) local widthBox = CreateDimLabel(overlay, "LEFT", "RIGHT", 8, 0, "Width:") overlay.widthBox = widthBox - -- Corner handle (bottom-right) - local scaleHandle = CreateScaleHandle(overlay, "BOTTOMRIGHT", 0, 0, false, false) + -- Height drag edge and corner handle: top edge when above, bottom edge when below + local heightEdge = anchorAbove and "TOP" or "BOTTOM" + local cornerPoint = anchorAbove and "TOPRIGHT" or "BOTTOMRIGHT" + local flipV = anchorAbove + + local scaleHandle = CreateScaleHandle(overlay, cornerPoint, 0, 0, false, flipV) overlay.scaleHandle = scaleHandle -- Width drag handles @@ -556,30 +582,36 @@ local function BuildResultsOverlay(parent, targetFrame) overlay.leftHandle = leftHandle overlay.rightHandle = rightHandle - -- Bottom drag handle (for row count) - local bottomHandle = CreateHandle(overlay, "BOTTOM", 0, 0, nil, false) - overlay.bottomHandle = bottomHandle - - -- Rows field (below bottom edge) - local rowsBox = CreateFrame("EditBox", nil, overlay, "InputBoxTemplate") - rowsBox:SetSize(40, 20) - rowsBox:SetAutoFocus(false) - rowsBox:SetMaxLetters(2) - rowsBox:SetJustifyH("CENTER") - rowsBox:SetFontObject("GameFontHighlightSmall") - - local rowsPfx = overlay:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall") - rowsPfx:SetPoint("TOP", overlay, "BOTTOM", 0, -4) - rowsPfx:SetText("Rows:") - rowsPfx:SetTextColor(GOLD_COLOR[1], GOLD_COLOR[2], GOLD_COLOR[3], 0.7) - rowsBox:SetPoint("LEFT", rowsPfx, "RIGHT", 6, 0) + -- Height drag handle (top or bottom edge) + local heightHandle = CreateHandle(overlay, heightEdge, 0, 0, nil, false) + overlay.heightHandle = heightHandle + + -- Height field (outside the resize edge) + local heightBox = CreateFrame("EditBox", nil, overlay, "InputBoxTemplate") + heightBox:SetSize(50, 20) + heightBox:SetAutoFocus(false) + heightBox:SetMaxLetters(3) + heightBox:SetJustifyH("CENTER") + heightBox:SetFontObject("GameFontHighlightSmall") + + local heightPfx = overlay:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall") + heightPfx:SetText("Height:") + heightPfx:SetTextColor(GOLD_COLOR[1], GOLD_COLOR[2], GOLD_COLOR[3], 0.7) + if anchorAbove then + heightPfx:SetPoint("BOTTOM", overlay, "TOP", 0, 4) + else + heightPfx:SetPoint("TOP", overlay, "BOTTOM", 0, -4) + end + heightBox:SetPoint("LEFT", heightPfx, "RIGHT", 6, 0) - local rowsSuffix = rowsBox:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall") - rowsSuffix:SetPoint("LEFT", rowsBox, "RIGHT", 2, 0) - rowsSuffix:SetText("rows") - rowsSuffix:SetTextColor(GOLD_COLOR[1], GOLD_COLOR[2], GOLD_COLOR[3], 0.7) + local heightSuffix = heightBox:CreateFontString(nil, "OVERLAY", "GameFontDisableSmall") + heightSuffix:SetPoint("LEFT", heightBox, "RIGHT", 2, 0) + heightSuffix:SetText("px") + heightSuffix:SetTextColor(GOLD_COLOR[1], GOLD_COLOR[2], GOLD_COLOR[3], 0.7) - overlay.rowsBox = rowsBox + overlay.heightBox = heightBox + overlay.heightPfx = heightPfx + overlay.anchorAbove = anchorAbove return overlay end @@ -755,9 +787,9 @@ function Rescaler:Enter(mode) local resultsAbove = (mode == "ui" and EasyFind.db.uiResultsAbove) or (mode == "map" and EasyFind.db.mapResultsAbove) local previewW = getResultsWidth() - local currentRows = GetMaxRows() + local currentH = mmin(GetResultsHeight(), GetScreenMaxHeight(resultsAbove)) local leftAligned = (mode == "map") - previewResults = CreatePreviewResults(bg, searchBar, previewW, currentRows, resultsAbove, leftAligned) + previewResults = CreatePreviewResults(bg, searchBar, previewW, currentH, resultsAbove, leftAligned) previewResults:SetScale(getResultsScale()) previewResults:Show() @@ -766,9 +798,24 @@ function Rescaler:Enter(mode) barOverlay:Show() -- Results overlay (around the preview) - resultsOverlay = BuildResultsOverlay(bg, previewResults) + resultsOverlay = BuildResultsOverlay(bg, previewResults, resultsAbove) resultsOverlay:Show() + -- If height label is near screen edge, flip it inside the overlay + local resizeEdge = resultsAbove and resultsOverlay:GetTop() or resultsOverlay:GetBottom() + local screenLimit = resultsAbove and UIParent:GetTop() or 0 + local nearEdge = resultsAbove and (resizeEdge and screenLimit and (screenLimit - resizeEdge) < 40) + or (not resultsAbove and resizeEdge and resizeEdge < 40) + if nearEdge then + resultsOverlay.heightPfx:ClearAllPoints() + if resultsAbove then + resultsOverlay.heightPfx:SetPoint("TOPLEFT", resultsOverlay, "TOPLEFT", GLOW_OUTSET + 4, -8) + else + resultsOverlay.heightPfx:SetPoint("BOTTOMLEFT", resultsOverlay, "BOTTOMLEFT", GLOW_OUTSET + 4, 8) + end + resultsOverlay.heightInside = true + end + -- Wire bar width drag SetupWidthDrag(barOverlay.leftHandle, getBarWidth, setBarWidth, barOverlay.widthBox, "LEFT") SetupWidthDrag(barOverlay.rightHandle, getBarWidth, setBarWidth, barOverlay.widthBox, "RIGHT") @@ -828,35 +875,47 @@ function Rescaler:Enter(mode) resultsOverlay.widthBox:SetText(mfloor(defW + 0.5)) end) - -- Wire results corner (width + rows combo) - resultsOverlay.rowsBox:SetText(currentRows) - resultsOverlay.rowsBox:SetScript("OnEnterPressed", function(self) + -- Wire results corner (width + height combo) + resultsOverlay.heightBox:SetText(currentH) + resultsOverlay.heightBox:SetScript("OnEnterPressed", function(self) local val = tonumber(self:GetText()) if val then - val = mmax(MIN_ROWS, mmin(MAX_ROWS, mfloor(val + 0.5))) - SetMaxRows(val) - previewResults:SetVisibleRows(val) + val = mmax(MIN_HEIGHT, mmin(GetScreenMaxHeight(resultsAbove), mfloor(val + 0.5))) + SetResultsHeight(val) + previewResults:SetPreviewHeight(val) self:SetText(val) end self:ClearFocus() end) - resultsOverlay.rowsBox:SetScript("OnEscapePressed", function(self) - self:SetText(GetMaxRows()) + resultsOverlay.heightBox:SetScript("OnEscapePressed", function(self) + self:SetText(GetResultsHeight()) self:ClearFocus() end) - AddResetButton(resultsOverlay.rowsBox, function() - local def = GetDefaultMaxRows() - SetMaxRows(def) - previewResults:SetVisibleRows(def) - resultsOverlay.rowsBox:SetText(def) + local heightReset = AddResetButton(resultsOverlay.heightBox, function() + local def = GetDefaultResultsHeight() + SetResultsHeight(def) + previewResults:SetPreviewHeight(def) + resultsOverlay.heightBox:SetText(def) end) + if resultsOverlay.anchorAbove then + heightReset:ClearAllPoints() + heightReset:SetPoint("BOTTOM", resultsOverlay.heightBox, "TOP", 0, 2) + end + if resultsOverlay.heightInside then + heightReset:ClearAllPoints() + if resultsOverlay.anchorAbove then + heightReset:SetPoint("TOP", resultsOverlay.heightBox, "BOTTOM", 0, -2) + else + heightReset:SetPoint("BOTTOM", resultsOverlay.heightBox, "TOP", 0, 2) + end + end SetupCornerDrag(resultsOverlay.scaleHandle, previewResults, getResultsWidth, function(w) setResultsWidth(w) previewResults:SetWidth(w) - end, resultsOverlay.widthBox, resultsOverlay.rowsBox, resultsAbove) + end, resultsOverlay.widthBox, resultsOverlay.heightBox, resultsAbove) - -- Wire results bottom edge (row count) - SetupRowsDrag(resultsOverlay.bottomHandle, previewResults, resultsOverlay.rowsBox, resultsAbove) + -- Wire results bottom edge (height) + SetupHeightDrag(resultsOverlay.heightHandle, previewResults, resultsOverlay.heightBox, resultsAbove) -- Done panel donePanel = CreateDonePanel(bg) @@ -891,7 +950,7 @@ function Rescaler:Exit(reopenOptions) resultsOverlay.leftHandle:SetScript("OnUpdate", nil) resultsOverlay.rightHandle:SetScript("OnUpdate", nil) resultsOverlay.scaleHandle:SetScript("OnUpdate", nil) - resultsOverlay.bottomHandle:SetScript("OnUpdate", nil) + resultsOverlay.heightHandle:SetScript("OnUpdate", nil) resultsOverlay:Hide() resultsOverlay = nil end @@ -918,6 +977,7 @@ function Rescaler:Exit(reopenOptions) if ns.UI then if ns.UI.UpdateScale then ns.UI:UpdateScale() end if ns.UI.UpdateWidth then ns.UI:UpdateWidth() end + if ns.UI.RefreshResults then ns.UI:RefreshResults() end end elseif activeMode == "map" then if ns.MapSearch then diff --git a/UI.lua b/UI.lua index b7719a9..6929e97 100644 --- a/UI.lua +++ b/UI.lua @@ -27,6 +27,7 @@ local GetCursorPosition = GetCursorPosition local wipe = wipe local LIGHTNING_BOLT_TEX = "Interface\\AddOns\\EasyFind\\textures\\lightning-bolt" +local REP_BAR_WIDTH = 100 local searchFrame local resultsFrame @@ -34,6 +35,7 @@ local resultButtons = {} local MAX_BUTTON_POOL = 50 -- Maximum buttons (scroll handles overflow beyond this) local inCombat = false local selectingResult = false -- guard: suppress OnTextChanged re-renders during SelectResult +local deferredRepRefreshPending = false -- deferred re-render to let IsTruncated() settle -- PIN HELPERS @@ -190,7 +192,7 @@ THEMES["Classic"] = { rowHeight = 22, indentPx = 20, lineWidth = 2, - resultsWidth = 380, + resultsWidth = 350, resultsPadTop = 8, resultsPadBot = 8, btnWidth = 360, @@ -233,7 +235,7 @@ THEMES["Retail"] = { rowHeight = 28, indentPx = 20, -- matches INDENT_PX so tree lines align lineWidth = 2, - resultsWidth = 390, + resultsWidth = 350, resultsPadTop = 10, resultsPadBot = 10, resultsPadLeft = 12, @@ -1615,6 +1617,7 @@ function UI:CreateResultButton(index) tabText:SetPoint("LEFT", headerTab, "LEFT", 10, 0) tabText:SetPoint("RIGHT", toggleBtn, "LEFT", -4, 0) tabText:SetJustifyH("LEFT") + tabText:SetMaxLines(1) tabText:SetTextColor(0.60, 0.58, 0.55, 1.0) -- muted gray (normal state) resultRow.tabText = tabText @@ -1752,7 +1755,6 @@ function UI:CreateResultButton(index) -- Right-aligned reputation standing bar -- Structure: repBar (dark bg + border) → repClip (clips fill) → repFillFrame (colored, same shape) -- repBar → repTextOverlay (text on top of everything) - local REP_BAR_WIDTH = 100 local repBarBackdrop = { bgFile = "Interface\\BUTTONS\\WHITE8X8", edgeFile = TOOLTIP_BORDER, @@ -2260,10 +2262,9 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) local count = mmin(#visible, MAX_BUTTON_POOL) -- Pre-compute whether scrolling will be needed so buttons can be narrower. - -- Use >= to reserve scrollbar space when at the limit, since extra spacing - -- (pinned gaps, section separators) can push content height past the max. - local maxVisibleRows = EasyFind.db.uiMaxResults or 10 - local willScroll = #visible >= maxVisibleRows + -- Rough estimate: if all rows at base height exceed the limit, a scrollbar likely appears. + local maxVisibleHeight = EasyFind.db.uiResultsHeight or 280 + local willScroll = #visible * rowH > maxVisibleHeight local scrollInset = 0 if willScroll and resultsFrame.scrollBar then scrollInset = resultsFrame.scrollBar:GetWidth() @@ -2313,6 +2314,7 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) local yOffset = 0 local pinEndYOffset = 0 local catSepYPositions = {} + local hasSideBySideRepBar = false for i = 1, MAX_BUTTON_POOL do local resultRow = resultButtons[i] if i <= count then @@ -2837,7 +2839,7 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) if resultRow.repFill.SetBackdropColor then resultRow.repFill:SetBackdropColor(barR, barG, barB, 1.0) end - resultRow.repClip:SetWidth(mmax(fill * 100, 0.1)) + resultRow.repClip:SetWidth(mmax(fill * REP_BAR_WIDTH, 0.1)) resultRow.repBarText:SetText(standingText) if entry.isPathNode and theme.showHeaderTab then @@ -2848,18 +2850,44 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) resultRow.tabText:SetPoint("LEFT", resultRow.headerTab, "LEFT", 10, 0) resultRow.tabText:SetPoint("RIGHT", resultRow.repBar, "LEFT", -4, 0) elseif entry.isPathNode then - -- Classic theme: rep bar on right, text between icon and bar + -- Side-by-side by default; IsTruncated() reflects the previous frame's + -- layout, which is accurate for stable results. A deferred re-render + -- corrects it after first render or scale/width changes. + local indentPixels = depth * indPx + 4 resultRow.repBar:ClearAllPoints() resultRow.repBar:SetPoint("RIGHT", resultRow, "RIGHT", -6, 0) + resultRow.text:ClearAllPoints() + resultRow.text:SetPoint("LEFT", resultRow.icon, "RIGHT", 4, 0) + resultRow.text:SetPoint("RIGHT", resultRow.repBar, "LEFT", -4, 0) + if resultRow.text:IsTruncated() then + resultRow.text:ClearAllPoints() + resultRow.text:SetPoint("TOPLEFT", resultRow, "TOPLEFT", indentPixels, -3) + resultRow.text:SetPoint("TOPRIGHT", resultRow, "TOPRIGHT", -6, -3) + resultRow.repBar:ClearAllPoints() + resultRow.repBar:SetPoint("BOTTOM", resultRow, "BOTTOM", 0, 5) + resultRow:SetHeight(rowH + 25) + else + hasSideBySideRepBar = true + end else - -- Leaf: default position, hide icon, anchor text to bar - resultRow.repBar:ClearAllPoints() - resultRow.repBar:SetPoint("RIGHT", resultRow, "RIGHT", -6, 0) + -- Leaf: side-by-side by default, stack only if text truncates SetRowIcon(resultRow, "hidden", nil, theme.iconSize) local indentPixels = depth * indPx + 4 + resultRow.repBar:ClearAllPoints() + resultRow.repBar:SetPoint("RIGHT", resultRow, "RIGHT", -6, 0) resultRow.text:ClearAllPoints() resultRow.text:SetPoint("LEFT", resultRow, "LEFT", indentPixels, 0) resultRow.text:SetPoint("RIGHT", resultRow.repBar, "LEFT", -4, 0) + if resultRow.text:IsTruncated() then + resultRow.text:ClearAllPoints() + resultRow.text:SetPoint("TOPLEFT", resultRow, "TOPLEFT", indentPixels, -3) + resultRow.text:SetPoint("TOPRIGHT", resultRow, "TOPRIGHT", -6, -3) + resultRow.repBar:ClearAllPoints() + resultRow.repBar:SetPoint("BOTTOM", resultRow, "BOTTOM", 0, 5) + resultRow:SetHeight(rowH + 25) + else + hasSideBySideRepBar = true + end iconSet = true end resultRow.repBar:Show() @@ -2969,7 +2997,7 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) end -- Measure text height and expand row if text wraps - local actualH = rowH + local actualH = resultRow:GetHeight() local textObj if theme.showHeaderTab and entry.isPathNode and resultRow.headerTab:IsShown() then textObj = resultRow.tabText @@ -2979,7 +3007,7 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) if textObj then local textHeight = textObj:GetStringHeight() local minH = textHeight / ns.SEARCHBAR_FILL - if minH > rowH then + if minH > actualH then actualH = minH resultRow:SetHeight(actualH) if resultRow.headerTab:IsShown() then @@ -3049,10 +3077,20 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) -- Calculate total content height vs max visible height local totalContentHeight = yOffset - local maxVisibleHeight = maxVisibleRows * rowH local hasScroll = totalContentHeight > maxVisibleHeight local visibleHeight = hasScroll and maxVisibleHeight or totalContentHeight + -- If scrollbar appeared but we didn't reserve space for it (e.g. stacked rep rows + -- pushed content past maxVisibleHeight), retroactively narrow all visible rows + -- so they don't bleed into the scrollbar. + if hasScroll and scrollInset == 0 and resultsFrame.scrollBar then + local scrollBarW = resultsFrame.scrollBar:GetWidth() + scrollInset = scrollBarW + for i = 1, count do + resultButtons[i]:SetWidth(resultButtons[i]:GetWidth() - scrollBarW) + end + end + -- Size the results frame and scroll child resultsFrame:SetHeight(padT + theme.resultsPadBot + visibleHeight) resultsFrame.scrollChild:SetWidth(resultsFrame:GetWidth() - scrollInset) @@ -3089,6 +3127,17 @@ function UI:ShowHierarchicalResults(hierarchical, preserveScroll) resultsFrame:Show() + -- If any rep bar row is in side-by-side mode, schedule one deferred re-render so + -- IsTruncated() can reflect the layout we just set (it reads the previous frame's state). + if hasSideBySideRepBar and not deferredRepRefreshPending then + deferredRepRefreshPending = true + local selfRef = self + ns.Utils.SafeAfter(0, function() + deferredRepRefreshPending = false + selfRef:RefreshResults() + end) + end + -- Reset keyboard selection whenever results change selectedIndex = 0 toggleFocused = false @@ -4024,6 +4073,7 @@ end function UI:UpdateResultsScale() if resultsFrame then resultsFrame:SetScale(EasyFind.db.uiResultsScale or 1.0) + self:RefreshResults() end end @@ -4044,6 +4094,12 @@ function UI:UpdateResultsWidth() end end +function UI:RefreshResults() + if cachedHierarchical and resultsFrame and resultsFrame:IsShown() then + self:ShowHierarchicalResults(cachedHierarchical, true) + end +end + function UI:UpdateOpacity() if not searchFrame then return end local alpha = EasyFind.db.searchBarOpacity or DEFAULT_OPACITY diff --git a/Utils.lua b/Utils.lua index 7a8d325..aa05a70 100644 --- a/Utils.lua +++ b/Utils.lua @@ -116,7 +116,7 @@ ns.RESULT_ICON_SIZE = 18 ns.SEARCHBAR_HEIGHT = 30 -- base search bar frame height (before font scaling) ns.SEARCHBAR_FILL = 0.55 -- fraction of bar height filled by text/icon ns.SEARCHBAR_ICON_SCALE = 0.75 -- icon size relative to editBox height (font glyphs are shorter than line height) -ns.CLEAR_BTN_SIZE = 18 -- base clear button size (before font scaling) +ns.CLEAR_BTN_SIZE = 12 -- base clear button size (before font scaling) local EasyFindSearchFont = CreateFont("EasyFindSearchFont") local baseFont = Game15Font_Shadow or GameFontNormal EasyFindSearchFont:CopyFontObject(baseFont) @@ -128,6 +128,7 @@ ns.SEARCHBAR_FONT = "EasyFindSearchFont" -- Fill is tinted black with tunable opacity; border uses Blizzard's action bar gray. local SEARCH_TEX_FILL = "Interface\\AddOns\\EasyFind\\Textures\\SearchBarFill" local SEARCH_TEX_BORDER = "Interface\\AddOns\\EasyFind\\Textures\\SearchBarBorder" +local CLEAR_BTN_TEX = "Interface\\AddOns\\EasyFind\\Textures\\clear-button" local SEARCH_CAP_W = 8 local SEARCH_TEX_W = 64 local TC_LEFT = {0, SEARCH_CAP_W / SEARCH_TEX_W, 0, 1} @@ -681,13 +682,13 @@ function Utils.CreateClearButton(parent, globalName) local normal = btn:CreateTexture(nil, "ARTWORK") normal:SetAllPoints() - normal:SetTexture("Interface\\FriendsFrame\\ClearBroadcastIcon") + normal:SetTexture(CLEAR_BTN_TEX) btn:SetNormalTexture(normal) local highlight = btn:CreateTexture(nil, "HIGHLIGHT") highlight:SetAllPoints() - highlight:SetTexture("Interface\\FriendsFrame\\ClearBroadcastIcon") - highlight:SetVertexColor(1.2, 1.2, 1.2, 1) + highlight:SetTexture(CLEAR_BTN_TEX) + highlight:SetVertexColor(1.3, 1.3, 1.3, 1) highlight:SetBlendMode("ADD") btn:SetHighlightTexture(highlight)