Skin the Challenges window, its tab row and the Trials tab
ChallengesFrame is a HIGH-strata window parented straight to UIParent and mouse-enabled itself, not one of the three that hang off the Collections container -- so it gets its own module and its own enable switch rather than joining the collections group, and it deliberately skips ApplyWindowScale and CollectionTabs: the first scales Collections, which would resize the other three windows and not this one, and the second skins the tab row those three share. The panel is built as a backdrop instead of through SetTemplate. A template's backdrop is drawn as regions of the frame and can only match its rect, and the title sits above that rect -- the native art carries a banner up there -- so a panel sized to the frame left the title floating over the open world. A backdrop has points of its own, so its top edge is pushed up over the title, measured live rather than hardcoded, with the hit rect and the close button following it. The nine-slice is stripped rather than run through HideArt for a related reason: the title is one of its regions, and Hide() would take it down with the border. The tab row is the first stock Blizzard flavour the plugin skins, so the shared layer grows to cover it. TAB_TEXTURES picks up the Middle pieces those name their body after, and the OnUpdate art re-check watches the Disabled set as well -- that pair is how a stock tab draws its selected state, so watching only the inactive half missed art returning on whichever tab was open. Native panel tabs also interlock, each anchored 16px back into the one before it so the ornate end caps overlap, which reads as one merged bar once the art is gone; the overlap is absorbed by narrowing each tab rather than by spacing the row out, which would have grown it 80px past a frame with 16px of slack. The row is pulled up flush against the panel it belongs to, since the native tops that spanned that gap went with the art. Inside the Trials tab: the search box, the filter dropdown and the scroll bar. The dropdown is the same widget as the vanity and wardrobe ones and takes the existing handler unchanged. The search box needs its border cleared by file, not stripped -- an EditBox draws its caret and selection highlight as plain textures of its own, and a blind strip leaves a box you can type in with no cursor in it. The scroll bar needed a handler of its own. HandleScrollBar reaches for the thumb through GetThumbTexture and then calls SetTexture on it, but these bars are hand-built: the thumb is a Button with art of its own, and the arrows are named off the scroll frame rather than off the bar, so it finds neither. Both pieces are also arted lazily -- at skin time their textures read back nil, which is why clearing them, noop'ing their SetTexture and alpha'ing them all failed in turn -- so the art is suppressed from OnUpdate, the same answer the tab art needed. Colours follow ElvUI's own proportional scroll bars, and the track is left empty rather than given a backdrop.
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
local E, L, V, P, G = unpack(ElvUI)
|
||||
local CoA = E:GetModule("CoA")
|
||||
local Skin = CoA.Skin
|
||||
|
||||
local FRAME_NAME = "ChallengesFrame"
|
||||
|
||||
-- Unlike the talent/vanity/wardrobe trio, this window is not one of the
|
||||
-- Collections container's children: it's a HIGH-strata frame parented straight
|
||||
-- to UIParent, and it's mouse-enabled itself rather than being dragged by a
|
||||
-- container (probed). So it deliberately doesn't go through
|
||||
-- Skin:ApplyWindowScale or Skin:CollectionTabs -- the first scales Collections,
|
||||
-- which would resize the other three windows and not this one, and the second
|
||||
-- skins the tab row those three share, which this window has no part in. A
|
||||
-- scale slider for it later would set the scale on the frame itself, and the
|
||||
-- drag target moves with it because the frame is its own drag target.
|
||||
|
||||
-- Stock Blizzard panel tabs, unlike every other tab row the plugin skins: plain
|
||||
-- Buttons rather than CheckButtons, and their art is named Left/Middle/Right
|
||||
-- (plus the Disabled set Blizzard swaps in for the selected tab) rather than
|
||||
-- the server's Left/Center/Right -- both flavours are covered by the shared
|
||||
-- strip in Skinning.lua. They're real children of the frame, so no level parent
|
||||
-- is needed: normal parent/child z-order already puts them above its panel.
|
||||
--
|
||||
-- Six exist today, two of them gated behind content an account may not have, so
|
||||
-- the row is walked until the first missing index rather than against a
|
||||
-- hardcoded count -- same shape as Skin:CollectionTabs.
|
||||
--
|
||||
-- This is the one row in the plugin that interlocks: every tab past the first is
|
||||
-- anchored 16px back into the one before it (probed) so the native end caps
|
||||
-- overlap, which reads as a single merged bar once the art is gone. Hence the
|
||||
-- unoverlap flag; the server-authored rows sit apart already and don't take it.
|
||||
local MAX_TABS = 10
|
||||
|
||||
local function SkinTabs()
|
||||
for i = 1, MAX_TABS do
|
||||
local tab = _G[FRAME_NAME.."Tab"..i]
|
||||
if not tab then break end
|
||||
|
||||
Skin:Tab(tab, nil, true)
|
||||
end
|
||||
end
|
||||
|
||||
-- The row hangs 6px clear of the frame's bottom edge (probed), which the native
|
||||
-- art spanned with the tabs' own ornate tops -- gone with the art, so the row
|
||||
-- reads as detached from the window it belongs to. Only the first tab carries a
|
||||
-- vertical offset at all, the rest being anchored off its edge, so moving it
|
||||
-- moves the row.
|
||||
--
|
||||
-- Set to the backdrop inset rather than to zero: the tab's flat backdrop starts
|
||||
-- that far inside the tab, so a tab flush with the frame still leaves its
|
||||
-- visible edge floating. Written as an absolute rather than as an adjustment to
|
||||
-- what's there, which keeps it idempotent across the repeated skin passes.
|
||||
local function AnchorTabRow()
|
||||
local tab = _G[FRAME_NAME.."Tab1"]
|
||||
if not tab then return end
|
||||
|
||||
local point, relativeTo, relativePoint, x = tab:GetPoint(1)
|
||||
if not point then return end
|
||||
|
||||
tab:SetPoint(point, relativeTo, relativePoint, x, Skin.TabBackdropInset)
|
||||
end
|
||||
|
||||
-- The nine-slice draws the ornate purple border and the teal inner panel over
|
||||
-- the top of the frame, doubling up with the Transparent template's own border
|
||||
-- -- but unlike the talent and wardrobe frames' nine-slices it isn't purely
|
||||
-- decorative: the window's title is one of its regions (probed: nine BORDER
|
||||
-- textures, one ARTWORK texture, and the title FontString). So it's stripped
|
||||
-- rather than run through Skin:HideArt, whose Hide() would take the title down
|
||||
-- with the border. StripTextures only touches Textures, so the art goes and the
|
||||
-- FontString stays.
|
||||
local function StripBorderArt()
|
||||
local nineSlice = _G[FRAME_NAME.."NineSlice"]
|
||||
if not nineSlice then return end
|
||||
|
||||
nineSlice:StripTextures()
|
||||
end
|
||||
|
||||
-- That title has no name of its own -- there is no ChallengesFrameTitleText,
|
||||
-- and none of the other usual suffixes resolve either (probed) -- so it's
|
||||
-- picked out by type, the same way the vanity frame's pager arrows are picked
|
||||
-- out by position. The nine-slice owns exactly one FontString.
|
||||
local function GetTitle()
|
||||
local nineSlice = _G[FRAME_NAME.."NineSlice"]
|
||||
if not nineSlice then return end
|
||||
|
||||
for i = 1, nineSlice:GetNumRegions() do
|
||||
local region = select(i, nineSlice:GetRegions())
|
||||
|
||||
if region:GetObjectType() == "FontString" then return region end
|
||||
end
|
||||
end
|
||||
|
||||
-- Vertical slack left above the title inside the widened panel.
|
||||
local TITLE_PADDING = 6
|
||||
|
||||
-- The title is drawn above the frame's own top edge -- the native art carries a
|
||||
-- banner up there that the frame's rect doesn't include -- so a panel sized to
|
||||
-- the frame leaves the title floating over the open world. The panel is built
|
||||
-- as a backdrop rather than through SetTemplate for exactly this reason: a
|
||||
-- template's backdrop is drawn as regions of the frame itself and can only ever
|
||||
-- match its rect, while a backdrop is a child frame with points of its own, so
|
||||
-- its top edge can be pushed up to take the title in.
|
||||
--
|
||||
-- The distance is measured live rather than hardcoded: it's whatever gap the
|
||||
-- server's own layout left between the title and the frame, so this stays right
|
||||
-- if that layout changes. Re-measured on every show for the same reason the tab
|
||||
-- heights are (see Skinning.lua) -- on a cold /reload the layout isn't settled
|
||||
-- at skin time, and a one-shot measurement catches a stale value.
|
||||
--
|
||||
-- The hit rect is grown to match. The frame is its own drag target (probed), and
|
||||
-- without this the widened panel would have a band along its top that looks part
|
||||
-- of the window but can't be grabbed.
|
||||
local function UpdatePanelTop(frame)
|
||||
local backdrop = frame.backdrop
|
||||
local title = GetTitle()
|
||||
if not (backdrop and title) then return end
|
||||
|
||||
local frameTop, titleTop = frame:GetTop(), title:GetTop()
|
||||
if not (frameTop and titleTop) then return end
|
||||
|
||||
local offset = titleTop - frameTop + TITLE_PADDING
|
||||
if offset < 0 then offset = 0 end
|
||||
|
||||
backdrop:Point("TOPLEFT", frame, "TOPLEFT", 0, offset)
|
||||
backdrop:Point("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
|
||||
|
||||
-- Negative insets grow the hit rect outwards rather than shrinking it.
|
||||
frame:SetHitRectInsets(0, 0, -offset, 0)
|
||||
end
|
||||
|
||||
-- Per-tab contents. Each of the six tabs owns its own copy of these widgets
|
||||
-- rather than sharing one set, so they're skinned per tab; only the Trials tab
|
||||
-- is done so far and the rest follow the same shape.
|
||||
local TRIALS_TAB = FRAME_NAME.."TrialsTab"
|
||||
local TRIALS_LIST = TRIALS_TAB.."Challenges"
|
||||
local TRIALS_SCROLL = TRIALS_LIST.."ScrollFrame"
|
||||
|
||||
-- The list container's one and only region: a 2px rule drawn across the bottom
|
||||
-- of the panel from the store art (probed). It's the native panel's own divider,
|
||||
-- and with the panel around it flat there's nothing left for it to divide.
|
||||
local LIST_DIVIDER_ART = "perksactivities"
|
||||
|
||||
local function SkinTrialsTab()
|
||||
Skin:SearchBox(_G[TRIALS_TAB.."Search"])
|
||||
|
||||
-- Cleared by file rather than stripped: the container is the scroll frame's
|
||||
-- own parent, so a blind strip is a blunter instrument than one known region
|
||||
-- needs, and StripArtByFile's noop holds if the list ever re-arts it.
|
||||
Skin:StripArtByFile(_G[TRIALS_LIST], LIST_DIVIDER_ART)
|
||||
|
||||
-- The same nine "UI-Silver-Button" slices and ChatFrameExpandArrow caret as
|
||||
-- the vanity and wardrobe dropdowns (probed), so it takes the same handler.
|
||||
-- Nothing is passed for its popout: the Trials tab has exactly three children
|
||||
-- and none of them is a menu (probed), so wherever the list is built it isn't
|
||||
-- there. Skin:Dropdown skips that half when it's handed nil.
|
||||
Skin:Dropdown(_G[TRIALS_TAB.."FilterDropDown"])
|
||||
|
||||
-- The arrows are named off the scroll frame rather than off the bar, which is
|
||||
-- the other reason S:HandleScrollBar can't find them on its own.
|
||||
Skin:ScrollBar(
|
||||
_G[TRIALS_SCROLL.."ScrollBar"],
|
||||
_G[TRIALS_SCROLL.."ScrollBarThumb"],
|
||||
_G[TRIALS_SCROLL.."ScrollUpButton"],
|
||||
_G[TRIALS_SCROLL.."ScrollDownButton"]
|
||||
)
|
||||
end
|
||||
|
||||
-- The close button is anchored inside the frame's own top-right corner, which
|
||||
-- stopped being the corner the player sees once the panel grew up over the title
|
||||
-- band -- it ends up floating a title's height below the top edge. Re-anchored
|
||||
-- to the panel instead, and re-run on every pass so it follows the panel's top
|
||||
-- whenever that gets re-measured.
|
||||
--
|
||||
-- No offset of its own: Skin:CloseButton normalises the button to a 32px box
|
||||
-- with a 12px X centred in it, so the margin off the corner is already built in.
|
||||
local function AnchorCloseButton(frame)
|
||||
local close = _G[FRAME_NAME.."CloseButton"]
|
||||
if not (close and frame.backdrop) then return end
|
||||
|
||||
close:ClearAllPoints()
|
||||
close:SetPoint("TOPRIGHT", frame.backdrop, "TOPRIGHT", 0, 0)
|
||||
end
|
||||
|
||||
-- Nothing here re-runs on a tab switch, and nothing needs to: switching tabs
|
||||
-- doesn't fire the frame's OnShow, and the one thing a switch does disturb is
|
||||
-- the tab art, which Skin:Tab watches from its own OnUpdate. The title
|
||||
-- fontstring is reused across tabs rather than swapped for a per-tab one -- its
|
||||
-- text changes, the object doesn't -- so it only needs the one pass too.
|
||||
local function SkinContents()
|
||||
StripBorderArt()
|
||||
Skin:Title(GetTitle())
|
||||
UpdatePanelTop(_G[FRAME_NAME])
|
||||
Skin:CloseButton(_G[FRAME_NAME.."CloseButton"])
|
||||
AnchorCloseButton(_G[FRAME_NAME])
|
||||
SkinTabs()
|
||||
AnchorTabRow()
|
||||
SkinTrialsTab()
|
||||
end
|
||||
|
||||
local function SkinFrame(frame)
|
||||
if not frame.CoASkinned then
|
||||
frame.CoASkinned = true
|
||||
|
||||
-- A single BACKGROUND texture is all the frame owns (probed), and it's
|
||||
-- decorative -- nothing functional is drawn as a region of this frame,
|
||||
-- unlike the vanity store, whose currency counters are. So it's stripped
|
||||
-- outright. Stripped before the backdrop is made, not after: a backdrop
|
||||
-- built first would be a child frame and survive, but the order is kept
|
||||
-- the same as Skin:Panel's for one less rule to remember.
|
||||
frame:StripTextures()
|
||||
frame:CreateBackdrop("Transparent")
|
||||
|
||||
frame:HookScript("OnShow", SkinContents)
|
||||
end
|
||||
|
||||
SkinContents()
|
||||
end
|
||||
|
||||
local function TryHook()
|
||||
local frame = _G[FRAME_NAME]
|
||||
if not frame then return false end
|
||||
|
||||
SkinFrame(frame)
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function CoA:InitializeChallengesFrame()
|
||||
if not E.private.skins.blizzard.enable then return end
|
||||
|
||||
Skin:OnFrameAvailable(TryHook)
|
||||
end
|
||||
+250
-14
@@ -210,6 +210,23 @@ function Skin:Button(button, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- S:HandleNextPrevButton routes through S:HandleButton, so it inherits exactly
|
||||
-- the lit-border-on-a-dead-button behaviour OnButtonEnter is here to correct,
|
||||
-- and scroll arrows spend a good deal of their life disabled at one end of their
|
||||
-- list or the other. Same separate guard as Skin:Button, and for the same
|
||||
-- reason: these frames re-run their skin passes, and HookScript stacks.
|
||||
function Skin:NextPrevButton(button, direction)
|
||||
if not button then return end
|
||||
|
||||
S:HandleNextPrevButton(button, direction)
|
||||
|
||||
if not button.CoAButtonSkinned then
|
||||
button.CoAButtonSkinned = true
|
||||
|
||||
button:HookScript("OnEnter", OnButtonEnter)
|
||||
end
|
||||
end
|
||||
|
||||
-- Several of these controls draw their art as anonymous regions of one atlas
|
||||
-- file rather than through the named Left/Middle/Right fields or the Normal/
|
||||
-- Pushed/Disabled set S:HandleButton knows how to clear, so its own clearing
|
||||
@@ -316,16 +333,170 @@ function Skin:Dropdown(dropdown, menu)
|
||||
self:Panel(menu)
|
||||
end
|
||||
|
||||
-- The CoA search boxes draw their border and magnifier from the stock
|
||||
-- "CommonSearch" file, tinted to the server's own colour, as anonymous regions
|
||||
-- -- so S:HandleEditBox can't reach them: it only alpha-0s the named Left/
|
||||
-- Middle/Right pieces a stock Blizzard edit box has.
|
||||
--
|
||||
-- Cleared by file rather than by a blind strip, which is what a first pass did:
|
||||
-- an EditBox draws its own caret and its selection highlight as plain solid
|
||||
-- textures of its own (probed: four of them, sub-pixel wide and text-high, shown
|
||||
-- only while the box has focus), and StripTextures takes those with the border,
|
||||
-- leaving a search box you can type in with no cursor in it.
|
||||
local SEARCH_BOX_ART = "CommonSearch"
|
||||
|
||||
function Skin:SearchBox(box)
|
||||
if not box or box.CoASearchSkinned then return end
|
||||
box.CoASearchSkinned = true
|
||||
|
||||
self:StripArtByFile(box, SEARCH_BOX_ART)
|
||||
S:HandleEditBox(box)
|
||||
end
|
||||
|
||||
-- Scroll bars ------------------------------------------------------------------
|
||||
--
|
||||
-- S:HandleScrollBar can't be used on these: the CoA bars are hand-built rather
|
||||
-- than the stock Slider widget, so the thumb is a Button with art of its own
|
||||
-- (probed) where a stock bar has a thumb *texture*, and HandleScrollBar reaches
|
||||
-- for the thumb through GetThumbTexture and then calls SetTexture on what it
|
||||
-- finds. It also looks the arrow buttons up as children of the bar, and these
|
||||
-- are named off the scroll frame instead. The treatment below is the same one it
|
||||
-- applies, only with each piece handed in by the caller.
|
||||
--
|
||||
-- The art on both pieces is applied lazily, and that is what defeated two
|
||||
-- earlier passes at this: at skin time the thumb's textures read back nil, so
|
||||
-- matching them by file found nothing to clear, and the scroll code arts them a
|
||||
-- moment later when it first measures the list. Clearing, noop'ing the regions'
|
||||
-- SetTexture, and alpha'ing them all failed the same way -- not because the art
|
||||
-- came back, but because it was never there yet to be taken.
|
||||
--
|
||||
-- So it's handled from OnUpdate, the same answer the tab art needed and for the
|
||||
-- same reason: nothing fires when it happens, and the check is a three-region
|
||||
-- alpha compare that only writes when something has actually re-arted the
|
||||
-- widget. Blanket rather than matched by file: neither the track nor the thumb
|
||||
-- carries anything but its own art -- no label, no counter -- which is the only
|
||||
-- thing StripArtByFile's file matching is there to protect.
|
||||
local function HideScrollArt(frame)
|
||||
for i = 1, frame:GetNumRegions() do
|
||||
local region = select(i, frame:GetRegions())
|
||||
|
||||
if region:GetObjectType() == "Texture" and region:GetAlpha() ~= 0 then
|
||||
region:SetAlpha(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Colours and states taken from ElvUI's own proportional scroll bars
|
||||
-- (S:HandleProportionalScroll), which is what every other scroll bar in this UI
|
||||
-- looks like: the value colour at a quarter alpha, three quarters while hovered
|
||||
-- or held. Its handlers are locals in ElvUI's file, so the four scripts are
|
||||
-- written out here rather than reached for.
|
||||
--
|
||||
-- The active flag is what keeps a thumb lit while it's being dragged: the
|
||||
-- pointer routinely leaves a thin thumb mid-drag, and OnLeave would otherwise
|
||||
-- dim it while the list is still moving under it.
|
||||
local SCROLL_BUTTON_GAP = 1
|
||||
local SCROLL_THUMB_ALPHA = 0.25
|
||||
local SCROLL_THUMB_ACTIVE_ALPHA = 0.75
|
||||
|
||||
local function SetThumbColor(thumb, alpha)
|
||||
if not thumb.backdrop then return end
|
||||
|
||||
local r, g, b = unpack(E.media.rgbvaluecolor)
|
||||
thumb.backdrop:SetBackdropColor(r, g, b, alpha)
|
||||
end
|
||||
|
||||
local function ThumbOnEnter(thumb)
|
||||
SetThumbColor(thumb, SCROLL_THUMB_ACTIVE_ALPHA)
|
||||
end
|
||||
|
||||
local function ThumbOnLeave(thumb)
|
||||
if thumb.CoAThumbActive then return end
|
||||
|
||||
SetThumbColor(thumb, SCROLL_THUMB_ALPHA)
|
||||
end
|
||||
|
||||
local function ThumbOnMouseDown(thumb)
|
||||
thumb.CoAThumbActive = true
|
||||
|
||||
SetThumbColor(thumb, SCROLL_THUMB_ACTIVE_ALPHA)
|
||||
end
|
||||
|
||||
local function ThumbOnMouseUp(thumb)
|
||||
thumb.CoAThumbActive = nil
|
||||
|
||||
SetThumbColor(thumb, SCROLL_THUMB_ALPHA)
|
||||
end
|
||||
|
||||
function Skin:ScrollBar(bar, thumb, up, down)
|
||||
if not bar or bar.CoASkinned then return end
|
||||
bar.CoASkinned = true
|
||||
|
||||
-- The track gets no backdrop of its own, only its art taken off, which is
|
||||
-- what ElvUI's own proportional scroll bars do: against these panels an empty
|
||||
-- channel reads cleaner than a second filled block behind the thumb, and the
|
||||
-- thumb is legible on its own at the value colour.
|
||||
bar:HookScript("OnUpdate", HideScrollArt)
|
||||
|
||||
if thumb then
|
||||
thumb:HookScript("OnUpdate", HideScrollArt)
|
||||
|
||||
-- Filling the thumb rather than inset inside it: the thumb is already
|
||||
-- narrower than the track it runs in, so an inset backdrop reads as a thin
|
||||
-- line rather than as a grip. The track's own gutter is the separation.
|
||||
--
|
||||
-- Levelled off the thumb rather than off the track: the thumb sits a level
|
||||
-- above the track already, so a backdrop built at the track's level would
|
||||
-- land underneath the thumb's own regions.
|
||||
thumb:CreateBackdrop("Transparent")
|
||||
thumb.backdrop:SetFrameLevel(thumb:GetFrameLevel() + 1)
|
||||
thumb.backdrop:SetAllPoints()
|
||||
|
||||
SetThumbColor(thumb, SCROLL_THUMB_ALPHA)
|
||||
|
||||
thumb:HookScript("OnEnter", ThumbOnEnter)
|
||||
thumb:HookScript("OnLeave", ThumbOnLeave)
|
||||
thumb:HookScript("OnMouseDown", ThumbOnMouseDown)
|
||||
thumb:HookScript("OnMouseUp", ThumbOnMouseUp)
|
||||
end
|
||||
|
||||
-- Re-anchored onto the track's ends after skinning, and cleared first rather
|
||||
-- than pointed on top of what's there: HandleNextPrevButton resizes them to
|
||||
-- ElvUI's own 18px, so whatever the native layout anchored them by no longer
|
||||
-- lines up with the track, and a second point left in place would stretch the
|
||||
-- button between the two.
|
||||
if up then
|
||||
self:NextPrevButton(up, "up")
|
||||
up:ClearAllPoints()
|
||||
up:Point("BOTTOM", bar, "TOP", 0, SCROLL_BUTTON_GAP)
|
||||
end
|
||||
|
||||
if down then
|
||||
self:NextPrevButton(down, "down")
|
||||
down:ClearAllPoints()
|
||||
down:Point("TOP", bar, "BOTTOM", 0, -SCROLL_BUTTON_GAP)
|
||||
end
|
||||
end
|
||||
|
||||
-- Tabs -----------------------------------------------------------------------
|
||||
--
|
||||
-- S:HandleTab can't be used on any of these: it clears the tab body by name,
|
||||
-- looking for a "Middle" piece, and every CoA tab names theirs "Center", so the
|
||||
-- body survives and the ElvUI backdrop just lands behind the old art.
|
||||
-- S:HandleTab still isn't used on any of these. On the server-authored tabs it
|
||||
-- can't be: it clears the tab body by name, looking for a "Middle" piece, and
|
||||
-- those name theirs "Center", so the body survives and the ElvUI backdrop just
|
||||
-- lands behind the old art. The Challenges window's tabs are stock Blizzard
|
||||
-- ones that do use "Middle" (probed), so HandleTab would reach them -- they go
|
||||
-- through the same strip anyway, because one code path is what keeps every tab
|
||||
-- row in this UI looking alike.
|
||||
--
|
||||
-- One list covers both tab flavours: the talent frame's tabs carry the
|
||||
-- Disabled variants as well, the wardrobe's category tabs don't, and the
|
||||
-- lookups for the ones that don't exist simply come back nil.
|
||||
local TAB_TEXTURES = {"Left", "Center", "Right", "LeftDisabled", "CenterDisabled", "RightDisabled"}
|
||||
-- One list covers all three tab flavours: the talent frame's tabs carry the
|
||||
-- Disabled variants as well, the wardrobe's category tabs don't, the Challenges
|
||||
-- tabs use Blizzard's Left/Middle/Right plus the Disabled set Blizzard swaps in
|
||||
-- to draw the selected tab, and the lookups for the ones that don't exist
|
||||
-- simply come back nil.
|
||||
local TAB_TEXTURES = {
|
||||
"Left", "Center", "Middle", "Right",
|
||||
"LeftDisabled", "CenterDisabled", "MiddleDisabled", "RightDisabled",
|
||||
}
|
||||
|
||||
-- Blizzard's own tab code re-sets these textures both when a frame reopens and,
|
||||
-- separately, on every tab switch -- and a switch never fires the owning
|
||||
@@ -348,7 +519,11 @@ function Skin:StripTabArt(tab)
|
||||
if checked then checked:SetTexture(nil) end
|
||||
end
|
||||
|
||||
-- Exposed: a module that re-anchors a tab row against the frame it belongs to
|
||||
-- has to know how far inside its tab the backdrop actually starts, or it lines
|
||||
-- the tab up flush and leaves the visible edge floating by this much.
|
||||
local TAB_BACKDROP_INSET = 3
|
||||
Skin.TabBackdropInset = TAB_BACKDROP_INSET
|
||||
|
||||
-- Native tab sizing is a retail leftover -- every other ElvUI tab row in this
|
||||
-- client reads bigger -- so labelled tabs are grown, off the font's own current
|
||||
@@ -372,20 +547,59 @@ local TAB_LABEL_OFFSET = 12
|
||||
-- different float -- so a strict ~= compare never held and this grew every tick
|
||||
-- without bound. Half a pixel of slack absorbs the rounding while still
|
||||
-- catching a genuine Blizzard-driven change, which is always a full tab's worth
|
||||
-- of height.
|
||||
local TAB_HEIGHT_EPSILON = 0.5
|
||||
-- of height. The same slack covers the width below.
|
||||
local TAB_SIZE_EPSILON = 0.5
|
||||
|
||||
local function UpdateTabSize(tab)
|
||||
if not tab.CoAGrowTab then return end
|
||||
|
||||
local height = tab:GetHeight()
|
||||
|
||||
if not tab.CoAGrownHeight or math.abs(height - tab.CoAGrownHeight) > TAB_HEIGHT_EPSILON then
|
||||
if not tab.CoAGrownHeight or math.abs(height - tab.CoAGrownHeight) > TAB_SIZE_EPSILON then
|
||||
tab.CoAGrownHeight = height + TAB_GROWTH
|
||||
tab:SetHeight(tab.CoAGrownHeight)
|
||||
end
|
||||
end
|
||||
|
||||
-- Native panel tabs interlock: each is anchored back into the one before it by
|
||||
-- the width of the art's end cap, so the ornate ends overlap rather than butting
|
||||
-- together. Strip the art, put a flat backdrop in its place, and that overlap
|
||||
-- has nothing left to hide it -- the row reads as one merged bar, and the tabs'
|
||||
-- hit rects overlap along with their backdrops.
|
||||
--
|
||||
-- The tab is narrowed by exactly what its anchor was pulled back by, rather than
|
||||
-- being pushed along to make room: pushing grows the row by the overlap times
|
||||
-- the number of tabs, which on the Challenges window overhangs the frame by four
|
||||
-- times the slack the row has. Absorbing it instead leaves the row's total
|
||||
-- extent exactly where the native layout put it.
|
||||
--
|
||||
-- Opt-in per row rather than detected from the anchor: a tab anchored onto its
|
||||
-- neighbour with a negative offset is the signature of an interlocking row, but
|
||||
-- it would also match any row that merely sits tight, and silently resizing the
|
||||
-- server-authored rows on that guess isn't worth the convenience.
|
||||
local function UnoverlapTab(tab)
|
||||
local point, relativeTo, relativePoint, x, y = tab:GetPoint(1)
|
||||
if not (point and x) or x >= 0 then return end
|
||||
|
||||
tab.CoATabOverlap = -x
|
||||
tab:SetPoint(point, relativeTo, relativePoint, 0, y)
|
||||
end
|
||||
|
||||
-- Re-checked every tick with the same self-healing shape as the grown height,
|
||||
-- and for the same reason: the native tab code re-measures a tab off its own
|
||||
-- label, and a width written once would be handed straight back.
|
||||
local function UpdateTabWidth(tab)
|
||||
local overlap = tab.CoATabOverlap
|
||||
if not overlap then return end
|
||||
|
||||
local width = tab:GetWidth()
|
||||
|
||||
if not tab.CoAUnoverlappedWidth or math.abs(width - tab.CoAUnoverlappedWidth) > TAB_SIZE_EPSILON then
|
||||
tab.CoAUnoverlappedWidth = width - overlap
|
||||
tab:SetWidth(tab.CoAUnoverlappedWidth)
|
||||
end
|
||||
end
|
||||
|
||||
-- Tabs that aren't children of the frame they belong to (the talent frame's
|
||||
-- are separately-placed siblings) sit behind its panel art once grown, so they
|
||||
-- get bumped above it. Re-applied every tick rather than on show or on select:
|
||||
@@ -404,15 +618,29 @@ end
|
||||
-- still comes back, so there's no event to catch it from. Checked from OnUpdate
|
||||
-- instead: a cheap GetTexture() compare that only pays for the full strip when
|
||||
-- the art has actually reappeared.
|
||||
--
|
||||
-- Two probes rather than one: a stock Blizzard tab draws its selected state by
|
||||
-- hiding the Left/Middle/Right set and showing the Disabled set in its place,
|
||||
-- so watching only the inactive half would miss art returning on whichever tab
|
||||
-- is currently open. The three pieces of a set are always re-arted together, so
|
||||
-- one probe per set is enough.
|
||||
local TAB_ART_PROBES = {"Left", "LeftDisabled"}
|
||||
|
||||
local function UpdateTabArt(tab)
|
||||
BumpTabLevel(tab)
|
||||
UpdateTabSize(tab)
|
||||
UpdateTabWidth(tab)
|
||||
|
||||
local name = tab:GetName()
|
||||
local tex = name and _G[name.."Left"]
|
||||
if not name then return end
|
||||
|
||||
if tex and tex:GetTexture() then
|
||||
Skin:StripTabArt(tab)
|
||||
for _, suffix in ipairs(TAB_ART_PROBES) do
|
||||
local tex = _G[name..suffix]
|
||||
|
||||
if tex and tex:GetTexture() then
|
||||
Skin:StripTabArt(tab)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -443,7 +671,11 @@ end
|
||||
-- levelParent is the frame the tab must draw above, and is only needed for tabs
|
||||
-- that aren't its children (see BumpTabLevel). Pass nil for tabs parented to
|
||||
-- the frame they belong to -- normal parent/child z-order already covers those.
|
||||
function Skin:Tab(tab, levelParent)
|
||||
--
|
||||
-- unoverlap is for rows of interlocking native tabs (see UnoverlapTab). Only the
|
||||
-- tabs anchored onto a neighbour take it; the first tab in a row has nothing to
|
||||
-- pull back from and is left alone by the offset check.
|
||||
function Skin:Tab(tab, levelParent, unoverlap)
|
||||
if not tab then return end
|
||||
|
||||
-- Set on every call rather than only the first: the collection tabs are
|
||||
@@ -457,6 +689,10 @@ function Skin:Tab(tab, levelParent)
|
||||
|
||||
self:StripTabArt(tab)
|
||||
|
||||
if unoverlap then
|
||||
UnoverlapTab(tab)
|
||||
end
|
||||
|
||||
-- Default rather than Transparent: tabs sit below their frame over the
|
||||
-- open world, so a see-through panel reads as washed out instead of as
|
||||
-- the solid tabs the retail layout has.
|
||||
|
||||
Reference in New Issue
Block a user