feat: search togglable and search by tooltip text ~t:{string}

This commit is contained in:
Vati
2026-04-16 02:33:53 +04:00
parent 1af002fa08
commit 6d7c5d4f4c
8 changed files with 391 additions and 56 deletions
+7 -1
View File
@@ -46,7 +46,8 @@ function DB:Initialize()
iconFontSize = 12,
showQualityBorderEquipment = true,
showQualityBorderOther = true,
showSearchBar = true,
showSearchBar = true, -- legacy boolean; preserved for migration. New code reads searchBarMode.
searchBarMode = "shown", -- "shown" | "hidden" | "toggle"
hideBagline = true,
hideFooter = false,
bgTransparency = 0.15,
@@ -120,6 +121,11 @@ function DB:Initialize()
if Guda_CharDB.settings.showSearchBar == nil then
Guda_CharDB.settings.showSearchBar = true
end
-- Migrate legacy boolean to three-state searchBarMode.
if Guda_CharDB.settings.searchBarMode == nil then
Guda_CharDB.settings.searchBarMode =
(Guda_CharDB.settings.showSearchBar == false) and "hidden" or "shown"
end
if Guda_CharDB.settings.questBarPinnedItems == nil then
Guda_CharDB.settings.questBarPinnedItems = {}
end
+59
View File
@@ -48,6 +48,7 @@ local tooltipCache = {
bindOnEquip = {}, -- IsBindOnEquip results
uniqueItem = {}, -- IsUniqueItem results
restoreTag = {}, -- GetConsumableRestoreTag results
fullText = {}, -- GetTooltipText results (full tooltip lowercased, for ~t: search)
}
local tooltipCacheStats = {
hits = 0,
@@ -61,6 +62,7 @@ function Utils:ClearTooltipCache()
tooltipCache.bindOnEquip = {}
tooltipCache.uniqueItem = {}
tooltipCache.restoreTag = {}
tooltipCache.fullText = {}
tooltipCacheStats.hits = 0
tooltipCacheStats.misses = 0
addon:Debug("Tooltip cache cleared")
@@ -1240,6 +1242,63 @@ function Utils:GetConsumableRestoreTag(bagID, slotID, itemLink)
return tag
end
-- Return the full tooltip text of an item as a single lowercase string, with
-- newlines between lines. Used by the ~t: keyword search filter — one scan
-- per item per session, cached by item ID so repeated searches are free.
-- Accepts (bagID, slotID) for live items or (nil, nil, itemLink) as a
-- link-only fallback (e.g. auction/mail items).
function Utils:GetTooltipText(bagID, slotID, itemLink)
local cacheLink = itemLink or (bagID and slotID and GetContainerItemLink(bagID, slotID))
local cacheKey = GetTooltipCacheKey(cacheLink)
if cacheKey and tooltipCache.fullText[cacheKey] ~= nil then
tooltipCacheStats.hits = tooltipCacheStats.hits + 1
local cached = tooltipCache.fullText[cacheKey]
return (cached ~= false) and cached or nil
end
tooltipCacheStats.misses = tooltipCacheStats.misses + 1
local tooltip = GetScanTooltip()
tooltip:ClearLines()
local ok = false
if bagID and slotID then
pcall(function() tooltip:SetBagItem(bagID, slotID); ok = true end)
end
if not ok and cacheLink then
local _, _, itemString = string.find(cacheLink, "|H(item:[^|]+)|h")
if itemString then
pcall(function() tooltip:SetHyperlink(itemString); ok = true end)
end
end
if not ok then
if cacheKey then tooltipCache.fullText[cacheKey] = false end
return nil
end
local parts = {}
local n = tooltip:NumLines() or 0
for i = 1, n do
local left = getglobal("GudaScanTooltipTextLeft" .. i)
local right = getglobal("GudaScanTooltipTextRight" .. i)
local lt = left and left:GetText() or nil
local rt = right and right:GetText() or nil
if lt and lt ~= "" then table.insert(parts, lt) end
if rt and rt ~= "" then table.insert(parts, rt) end
end
-- Don't cache partial data: vanilla sometimes returns a 1-line tooltip
-- before the client finishes loading the item record. 2+ lines means
-- at least name + one stat/description line was present.
if n < 2 then
return nil
end
local joined = string.lower(table.concat(parts, "\n"))
if cacheKey then
tooltipCache.fullText[cacheKey] = joined
end
return joined
end
-- Check if item is Arrow or Bullet (for Quiver routing).
-- Locale-independent: also matches against the local client's translated
-- subtype strings, resolved from reference vanilla items at runtime.
+1 -1
View File
@@ -2,7 +2,7 @@
## Title: Guda
## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW)
## Author: Vati
## Version: 2.2.9
## Version: 2.3.0
## SavedVariables: Guda_DB
## SavedVariablesPerCharacter: Guda_CharDB
## OptionalDeps: pfUI
+88 -8
View File
@@ -237,7 +237,9 @@ function Guda_BagFrame_OnShow(self)
BagFrame:UpdateBorderVisibility()
end
-- Apply search bar visibility setting
-- Apply search bar visibility setting. Always start collapsed in toggle
-- mode so opening the bag doesn't leave stale filter state visible.
BagFrame.searchBarExpanded = false
if BagFrame.UpdateSearchBarVisibility then
BagFrame:UpdateSearchBarVisibility()
end
@@ -3057,6 +3059,32 @@ function Guda_BagFrame_ClearSearch()
BagFrame:Update()
end
-- Queue a one-shot warmup of Utils:GetTooltipText for every occupied bag
-- slot currently visible. Runs once per session the first time the user
-- types a ~t: query — subsequent keystrokes hit warm cache and filter
-- instantly. Frame-budgeted via Utils:QueueWork so typing stays responsive.
local tooltipTextWarmed = false
local function WarmTooltipTextCache()
if tooltipTextWarmed then return end
tooltipTextWarmed = true
local Utils = addon.Modules.Utils
if not Utils or not Utils.QueueWork or not Utils.GetTooltipText then return end
for bagID = 0, 4 do
local numSlots = GetContainerNumSlots(bagID)
if numSlots and numSlots > 0 then
for slotID = 1, numSlots do
local link = GetContainerItemLink(bagID, slotID)
if link then
local b, s, l = bagID, slotID, link
Utils:QueueWork(function()
Utils:GetTooltipText(b, s, l)
end, "tooltipTextSearchWarmup")
end
end
end
end
end
-- Search changed handler
function Guda_BagFrame_OnSearchChanged(self)
local text = self:GetText()
@@ -3069,6 +3097,12 @@ function Guda_BagFrame_OnSearchChanged(self)
BagFrame.foundFirstMatch = false -- Reset debug flags
BagFrame.warnedAboutParsing = false
BagFrame.warnedAboutNoName = false
-- Kick off a one-time tooltip-text warmup if the query uses ~t:.
-- string.find returns nil when pattern is absent; plain=true to match
-- "~t:" literally (no magic chars in the pattern).
if string.find(text, "~t:", 1, true) then
WarmTooltipTextCache()
end
BagFrame:Update()
end
end
@@ -3677,29 +3711,75 @@ function BagFrame:UpdateBorderVisibility()
end
-- Update search bar visibility based on setting
-- Reads the three-state searchBarMode setting.
-- Returns one of "shown", "hidden", "toggle". Falls back to the legacy
-- boolean showSearchBar if the new setting isn't set yet.
local function GetSearchBarMode()
if not addon or not addon.Modules or not addon.Modules.DB then return "shown" end
local mode = addon.Modules.DB:GetSetting("searchBarMode")
if mode == "shown" or mode == "hidden" or mode == "toggle" then
return mode
end
local legacy = addon.Modules.DB:GetSetting("showSearchBar")
if legacy == false then return "hidden" end
return "shown"
end
function BagFrame:UpdateSearchBarVisibility()
if not addon or not addon.Modules or not addon.Modules.DB then return end
local searchBar = getglobal("Guda_BagFrame_SearchBar")
local searchBar = getglobal("Guda_BagFrame_SearchBar")
local itemContainer = getglobal("Guda_BagFrame_ItemContainer")
local toggleBtn = getglobal("Guda_BagFrame_SearchToggleButton")
if not searchBar or not itemContainer then return end
local showSearchBar = addon.Modules.DB:GetSetting("showSearchBar")
if showSearchBar == nil then
showSearchBar = true
local mode = GetSearchBarMode()
local effectiveShown
if mode == "shown" then
effectiveShown = true
elseif mode == "hidden" then
effectiveShown = false
else -- "toggle"
effectiveShown = self.searchBarExpanded and true or false
end
if showSearchBar then
if effectiveShown then
searchBar:Show()
-- Anchor ItemContainer to SearchBar's bottom
itemContainer:ClearAllPoints()
itemContainer:SetPoint("TOP", searchBar, "BOTTOM", 0, -5)
else
searchBar:Hide()
-- Anchor ItemContainer directly to frame top (skip search bar space)
itemContainer:ClearAllPoints()
itemContainer:SetPoint("TOP", "Guda_BagFrame", "TOP", 0, -40)
end
if toggleBtn then
if mode == "toggle" then toggleBtn:Show() else toggleBtn:Hide() end
end
end
-- Toggle the search bar's expanded state (only meaningful in "toggle" mode).
function BagFrame:ToggleSearchBar()
local mode = GetSearchBarMode()
if mode ~= "toggle" then return end -- no-op in shown/hidden modes
self.searchBarExpanded = not (self.searchBarExpanded and true or false)
self:UpdateSearchBarVisibility()
local searchBox = getglobal("Guda_BagFrame_SearchBar_SearchBox")
if self.searchBarExpanded then
if searchBox then searchBox:SetFocus() end
else
if searchBox then
searchBox:SetText("")
searchBox:ClearFocus()
if Guda_BagFrame_OnSearchChanged then
Guda_BagFrame_OnSearchChanged(searchBox)
end
end
end
if BagFrame.Update then BagFrame:Update() end
end
-- Update footer visibility based on settings
+65 -1
View File
@@ -147,6 +147,56 @@
</Scripts>
</Button>
<!-- Search Toggle Button (only visible when searchBarMode == "toggle") -->
<Button name="$parent_SearchToggleButton" hidden="true">
<Size>
<AbsDimension x="24" y="22"/>
</Size>
<Anchors>
<Anchor point="RIGHT" relativePoint="LEFT" relativeTo="$parent_SortButton">
<Offset>
<AbsDimension x="-4" y="0"/>
</Offset>
</Anchor>
</Anchors>
<Layers>
<Layer level="ARTWORK">
<Texture name="$parent_Icon">
<Size>
<AbsDimension x="18" y="18"/>
</Size>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
<Color r="0.8" g="0.8" b="0.8"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>
local icon = getglobal(this:GetName().."_Icon")
if icon then
icon:SetTexture("Interface\\AddOns\\Guda\\Assets\\search")
icon:SetTexCoord(0, 1, 0, 1)
end
</OnLoad>
<OnClick>
if Guda and Guda.Modules and Guda.Modules.BagFrame
and Guda.Modules.BagFrame.ToggleSearchBar then
Guda.Modules.BagFrame:ToggleSearchBar()
end
</OnClick>
<OnEnter>
GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:SetText(Guda_L and Guda_L["Search"] or "Search")
GameTooltip:Show()
</OnEnter>
<OnLeave>
GameTooltip:Hide()
</OnLeave>
</Scripts>
</Button>
<!-- Characters Button (header left) -->
<Button name="$parent_CharsButton">
<Size>
@@ -703,12 +753,21 @@
local clickCatcher = getglobal("Guda_ClickCatcher")
if clickCatcher then clickCatcher:Hide() end
local wasEmpty = (this:GetText() == "")
-- Restore placeholder if empty when focus is lost
if this:GetText() == "" then
if wasEmpty then
this:SetText("Search, try ~equipment")
this:SetTextColor(0.5, 0.5, 0.5, 1)
Guda_BagFrame_OnSearchChanged(this)
end
-- In "toggle" mode, empty + focus-lost collapses the bar.
if wasEmpty and Guda and Guda.Modules
and Guda.Modules.BagFrame
and Guda.Modules.BagFrame.searchBarExpanded then
Guda.Modules.BagFrame:ToggleSearchBar()
end
</OnEditFocusLost>
<OnTextChanged>
Guda_BagFrame_OnSearchChanged(this)
@@ -728,6 +787,11 @@
this:SetTextColor(0.5, 0.5, 0.5, 1)
this:ClearFocus()
Guda_BagFrame_OnSearchChanged(this)
-- Collapse if in toggle mode; no-op otherwise.
if Guda and Guda.Modules and Guda.Modules.BagFrame
and Guda.Modules.BagFrame.searchBarExpanded then
Guda.Modules.BagFrame:ToggleSearchBar()
end
</OnEscapePressed>
</Scripts>
</EditBox>
+89 -36
View File
@@ -367,58 +367,97 @@ function Guda_UpdateLockStates(parentsTable)
end
-- Shared search filter used by BagFrame and BankFrame
function Guda_PassesSearchFilter(itemData, searchText)
-- If no search text, everything matches
if not searchText or searchText == "" then
return true
-- Supports space-separated tokens, each of one of three kinds:
-- ~t:<keyword> — tooltip text substring (case-insensitive)
-- ~<category> — category shortcut (equipment, consumable, quest, ...)
-- <plain text> — item name substring
-- All tokens must match (AND) for the item to pass.
local function TokenizeSearch(text)
local tokens = {}
local s = string.lower(text)
local pos = 1
local len = string.len(s)
while pos <= len do
local startPos, endPos = string.find(s, "%S+", pos)
if not startPos then break end
table.insert(tokens, string.sub(s, startPos, endPos))
pos = endPos + 1
end
return tokens
end
-- Ignore common placeholders
local function MatchesCategoryShortcut(itemData, category)
local itemType = itemData.class or ""
local itemQuality = itemData.quality or -1
if category == "equipment" or category == "armor" or category == "weapon" then
return (itemType == "Armor" or itemType == "Weapon")
elseif category == "consumable" then
return itemType == "Consumable"
elseif category == "tradegoods" or category == "trades" then
return itemType == "Trade Goods"
elseif category == "quest" then
local isQuest, isQuestStarter = Guda_GetQuestInfo(itemData.bagID, itemData.slotID, itemData.isBank)
return (isQuest or isQuestStarter or itemType == "Quest") and true or false
elseif category == "reagent" then
return itemType == "Reagent"
elseif category == "common" then return itemQuality == 1
elseif category == "uncommon" then return itemQuality == 2
elseif category == "rare" then return itemQuality == 3
elseif category == "epic" then return itemQuality == 4
elseif category == "legendary" then return itemQuality == 5
end
return nil -- unknown shortcut — caller falls back to name-match
end
function Guda_PassesSearchFilter(itemData, searchText)
if not searchText or searchText == "" then return true end
if searchText == "Search, try ~equipment" or searchText == "Search bank..." then
return true
end
-- Empty slots don't match when searching
if not itemData then
return false
end
if not itemData then return false end
local itemName = itemData.name
if not itemName and itemData.link then
local _, _, name = string.find(itemData.link, "%[(.+)%]")
itemName = name
end
if not itemName then return false end
local lowerName = string.lower(itemName)
local search = string.lower(searchText)
local Utils = Guda and Guda.Modules and Guda.Modules.Utils
if string.sub(search, 1, 1) == "~" then
local category = string.sub(search, 2)
local itemType = itemData.class or ""
local itemQuality = itemData.quality or -1
for _, tok in ipairs(TokenizeSearch(searchText)) do
local matched = false
if category == "equipment" or category == "armor" or category == "weapon" then
if itemType == "Armor" or itemType == "Weapon" then return true end
elseif category == "consumable" then
if itemType == "Consumable" then return true end
elseif category == "tradegoods" or category == "trades" then
if itemType == "Trade Goods" then return true end
elseif category == "quest" then
local isQuest, isQuestStarter = Guda_GetQuestInfo(itemData.bagID, itemData.slotID, itemData.isBank)
if isQuest or isQuestStarter or itemType == "Quest" then return true end
elseif category == "reagent" then
if itemType == "Reagent" then return true end
elseif category == "common" then if itemQuality == 1 then return true end
elseif category == "uncommon" then if itemQuality == 2 then return true end
elseif category == "rare" then if itemQuality == 3 then return true end
elseif category == "epic" then if itemQuality == 4 then return true end
elseif category == "legendary" then if itemQuality == 5 then return true end
if string.sub(tok, 1, 3) == "~t:" then
-- Tooltip-text filter.
local keyword = string.sub(tok, 4)
if keyword == "" then
matched = true -- bare ~t: is a no-op, skip this token
elseif Utils and Utils.GetTooltipText then
local text = Utils:GetTooltipText(itemData.bagID, itemData.slotID, itemData.link)
if text and string.find(text, keyword, 1, true) then
matched = true
end
end
elseif string.sub(tok, 1, 1) == "~" then
-- Category shortcut. Unknown shortcuts fall back to name-match.
local category = string.sub(tok, 2)
local shortcut = MatchesCategoryShortcut(itemData, category)
if shortcut == true then
matched = true
elseif shortcut == nil then
-- unknown shortcut — behave like a plain name substring
matched = string.find(lowerName, tok, 1, true) ~= nil
end
else
matched = string.find(lowerName, tok, 1, true) ~= nil
end
if not matched then return false end
end
itemName = string.lower(itemName)
return string.find(itemName, string.lower(searchText), 1, true) ~= nil
return true
end
-- Generic ResizeFrame for Bag/Bank frames
@@ -445,8 +484,22 @@ function Guda_ResizeFrame(frameName, containerName, currentRow, currentCol, colu
local containerHeight = overrideHeight or (totalRows * (buttonSize + spacing) - spacing + 2 * math.abs(pad.startY))
local frameWidth = containerWidth + pad.frameExtra
local showSearchBar = addon.Modules.DB:GetSetting("showSearchBar")
if showSearchBar == nil then showSearchBar = true end
-- Effective shown state for the search bar: "shown" always, "hidden" never,
-- "toggle" only when the bag frame has expanded the bar via its icon button.
local mode = addon.Modules.DB:GetSetting("searchBarMode")
if mode ~= "shown" and mode ~= "hidden" and mode ~= "toggle" then
local legacy = addon.Modules.DB:GetSetting("showSearchBar")
mode = (legacy == false) and "hidden" or "shown"
end
local showSearchBar
if mode == "shown" then
showSearchBar = true
elseif mode == "hidden" then
showSearchBar = false
else
local BF = addon.Modules and addon.Modules.BagFrame
showSearchBar = BF and BF.searchBarExpanded and true or false
end
local titleHeight = pad.titleHeight
local searchBarHeight = pad.searchBarHeight
+66 -6
View File
@@ -960,24 +960,39 @@ function Guda_SettingsPopup_ShowSearchBarCheckbox_OnLoad(self)
-- Tooltip
self.tooltipText = L_SHOW_SEARCH_BAR_TT
local showSearchBar = true
-- The parent checkbox reads "is search bar available at all?" — i.e.
-- modes "shown" and "toggle" both leave it checked; only "hidden"
-- unchecks it.
local mode = "shown"
if Guda and Guda.Modules and Guda.Modules.DB then
showSearchBar = Guda.Modules.DB:GetSetting("showSearchBar")
if showSearchBar == nil then
showSearchBar = true
mode = Guda.Modules.DB:GetSetting("searchBarMode")
if mode ~= "shown" and mode ~= "hidden" and mode ~= "toggle" then
local legacy = Guda.Modules.DB:GetSetting("showSearchBar")
mode = (legacy == false) and "hidden" or "shown"
end
end
self:SetChecked(showSearchBar and 1 or 0)
self:SetChecked((mode ~= "hidden") and 1 or 0)
end
-- Show Search Bar Checkbox OnClick
function Guda_SettingsPopup_ShowSearchBarCheckbox_OnClick(self)
local isChecked = self:GetChecked() == 1
-- Save setting
-- Save setting — sync both the legacy boolean and the new three-state.
-- If the user had toggle mode enabled, unchecking flips to hidden;
-- re-checking returns to "shown" (toggle must be re-enabled via its
-- own control).
if Guda and Guda.Modules and Guda.Modules.DB then
Guda.Modules.DB:SetSetting("showSearchBar", isChecked)
Guda.Modules.DB:SetSetting("searchBarMode", isChecked and "shown" or "hidden")
end
-- Keep the sibling toggle-mode checkbox in sync.
local tog = getglobal("Guda_SettingsPopup_SearchBarToggleCheckbox")
if tog then
tog:SetChecked(0)
if not isChecked and tog.Disable then tog:Disable() end
if isChecked and tog.Enable then tog:Enable() end
end
-- Update search bar visibility in bag frame
@@ -1001,6 +1016,51 @@ function Guda_SettingsPopup_ShowSearchBarCheckbox_OnClick(self)
end
end
-- Search Bar Toggle Mode Checkbox: when checked, switches searchBarMode to
-- "toggle" (bar hidden by default, icon button reveals it). Only meaningful
-- while the parent "Show Search Bar" checkbox is checked — unchecking the
-- parent forces this off.
function Guda_SettingsPopup_SearchBarToggleCheckbox_OnLoad(self)
local text = getglobal(self:GetName().."Text")
if text then
text:SetText(Guda_L and Guda_L["Show via icon button"] or "Show via icon button")
local font, _, flags = text:GetFont()
if font then text:SetFont(font, 13, flags) end
end
self.tooltipText = Guda_L and Guda_L["Hide the search bar by default. Click the magnifying-glass icon on the bag to open it."]
or "Hide the search bar by default. Click the magnifying-glass icon on the bag to open it."
local mode = "shown"
if Guda and Guda.Modules and Guda.Modules.DB then
mode = Guda.Modules.DB:GetSetting("searchBarMode") or "shown"
end
self:SetChecked((mode == "toggle") and 1 or 0)
if mode == "hidden" and self.Disable then self:Disable() end
end
function Guda_SettingsPopup_SearchBarToggleCheckbox_OnClick(self)
local isChecked = self:GetChecked() == 1
if not (Guda and Guda.Modules and Guda.Modules.DB) then return end
if isChecked then
Guda.Modules.DB:SetSetting("searchBarMode", "toggle")
Guda.Modules.DB:SetSetting("showSearchBar", true)
local parent = getglobal("Guda_SettingsPopup_ShowSearchBarCheckbox")
if parent then parent:SetChecked(1) end
else
-- Fall back to the parent checkbox's state
local parent = getglobal("Guda_SettingsPopup_ShowSearchBarCheckbox")
local parentChecked = parent and parent:GetChecked() == 1
Guda.Modules.DB:SetSetting("searchBarMode", parentChecked and "shown" or "hidden")
end
if Guda.Modules.BagFrame and Guda.Modules.BagFrame.UpdateSearchBarVisibility then
Guda.Modules.BagFrame.searchBarExpanded = false
Guda.Modules.BagFrame:UpdateSearchBarVisibility()
Guda.Modules.BagFrame:Update()
end
end
-- Show Quest Bar Checkbox OnLoad
function Guda_SettingsPopup_ShowQuestBarCheckbox_OnLoad(self)
local text = getglobal(self:GetName().."Text")
+16 -3
View File
@@ -466,11 +466,24 @@
</Scripts>
</CheckButton>
<!-- Hide Bagline Checkbox -->
<CheckButton name="Guda_SettingsPopup_HoverBaglineCheckbox" inherits="OptionsCheckButtonTemplate">
<!-- Search Bar Toggle-Mode Checkbox (indented sub-option of Show Search Bar) -->
<CheckButton name="Guda_SettingsPopup_SearchBarToggleCheckbox" inherits="OptionsCheckButtonTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeTo="Guda_SettingsPopup_ShowSearchBarCheckbox">
<Offset><AbsDimension x="0" y="-8"/></Offset>
<Offset><AbsDimension x="20" y="-4"/></Offset>
</Anchor>
</Anchors>
<Scripts>
<OnLoad>Guda_SettingsPopup_SearchBarToggleCheckbox_OnLoad(this)</OnLoad>
<OnClick>Guda_SettingsPopup_SearchBarToggleCheckbox_OnClick(this)</OnClick>
</Scripts>
</CheckButton>
<!-- Hide Bagline Checkbox (anchored beneath the toggle sub-option) -->
<CheckButton name="Guda_SettingsPopup_HoverBaglineCheckbox" inherits="OptionsCheckButtonTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativePoint="BOTTOMLEFT" relativeTo="Guda_SettingsPopup_SearchBarToggleCheckbox">
<Offset><AbsDimension x="-20" y="-8"/></Offset>
</Anchor>
</Anchors>
<Scripts>