forked from EllesmereGaming/EllesmereUI
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathEllesmereUI_ForeverLayout.lua
More file actions
538 lines (500 loc) · 26.8 KB
/
Copy pathEllesmereUI_ForeverLayout.lua
File metadata and controls
538 lines (500 loc) · 26.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
if EUI_CLIENT_BLOCKED then return end -- pre-12.1 client failsafe (EllesmereUI_ClientGate.lua)
--------------------------------------------------------------------------------
-- EllesmereUI_ForeverLayout.lua -- the WoW Forever base layout
--
-- On the Forever client every fresh install starts from one layout instead
-- of a snapshot of wherever Blizzard's frames happened to be:
-- chat bottom-left with the micro menu under it, action bar 1 bottom
-- centre with the pet bar above it, action bars 2+ hidden, the bag bar
-- bottom-right with the damage meter above it and the tooltip above that,
-- the minimap top-right, the player frame left and the target frame right
-- a hundred pixels above action bar 1 with the cast bar centred between
-- them, the stance bar just above the player frame and Blizzard's
-- encounter bar fifty pixels above the target frame.
--
-- Two halves:
-- 1. SeedForeverBaseLayout, called by the first-install loader at the
-- parent's ADDON_LOADED, before any module opens its profile: writes
-- the positions into the fresh profile, stamps the first-install
-- capture flags so no module reads Blizzard's layout, and files a
-- screen-edge anchor for every piece unlock mode owns (the record its
-- "Relative to Screen" menu writes), so each piece keeps its distance
-- to the screen edges on any monitor width.
-- 2. The Edit Mode layout: the micro menu, the bag bar, the encounter bar
-- and Blizzard's own action bars are placed by Blizzard's Edit Mode (on
-- retail too; the Action Bars module only follows the micro menu and
-- the bag bar), so those come from
-- an account layout named "EllesmereUI Forever", written once the
-- layouts have loaded while the account has none of ours; an older
-- version of ours is upgraded in place instead (UpgradeLayout).
-- Edit Mode keeps the layout account-wide but the active choice per
-- character, so each character is switched to it once, on its first
-- login (while still on a Blizzard preset), and owns its choice from
-- then on. The first-install popup always ends in a reload, which
-- lands both halves and clears the Edit Mode taint, the way the
-- profile importer's layout write relies on it.
--
-- Cost: nothing on any other client (the file returns), and on Forever one
-- saved-data lookup per login after a character's first session.
--------------------------------------------------------------------------------
local EllesmereUI = _G.EllesmereUI
if not (EllesmereUI and EllesmereUI.IS_FOREVER) then return end
local EDGE = 10 -- distance from the screen edges
local GAP = 8 -- between stacked pieces
local XP_WIDTH_PCT = 75 -- Blizzard's experience bar, Edit Mode size (percent)
local XP_BAR_H = 14 -- its height, flush with the bottom edge
local BAR_BOTTOM = XP_BAR_H + 12 -- the bottom stack's base, above the experience bar
local BAR_HEIGHT = 45 -- one row of default-size buttons
local MAINBAR_Y = BAR_BOTTOM + 8 -- action bar 1's bottom edge, a little clear of the experience bar
local BAGS_H = 46 -- bag bar, for the meter above it
local CHAT_LEFT = 63.33 -- chat's left edge in from the screen's left
local CHAT_BOTTOM = 108.17 -- its bottom edge up from the screen's bottom (the micro menu below)
local METER_BOTTOM = GAP + BAGS_H + GAP
local METER_H = 150 -- the meter window's default height
-- The tooltip's fixed anchor box (the Blizz UI Enhanced mover, 280 x 165,
-- stored as centre offsets from the UIParent centre): flush above the meter,
-- right edges aligned.
local TT_W, TT_H = 280, 165
local UF_BOTTOM = BAR_BOTTOM + BAR_HEIGHT + 100
local UF_SPREAD = 317 -- player left / target right of centre
-- Target of target: right edges flush with the target frame, just above it.
-- Sizes follow the Unit Frames defaults (target frameWidth 181, health 46 +
-- power 6 four below; target of target frameWidth 101).
local TARGET_W = 181
local TARGET_H = 56
local TOT_W = 101
local TOT_GAP = 6
local TOT_DX = (TARGET_W - TOT_W) / 2 -- centre offset that aligns the right edges
-- Stance bar: just above the player frame (same size as the target frame),
-- right edges flush. Placed by its bottom-right corner, so a class with more
-- or fewer forms grows to the left and the right edge stays put.
local STANCE_RIGHT = -UF_SPREAD + TARGET_W / 2 -- the player frame's right edge
local STANCE_BOTTOM = UF_BOTTOM + TARGET_H + GAP
-- Pet bar: centred just above action bar 1.
local PET_BOTTOM = MAINBAR_Y + BAR_HEIGHT + GAP
-- The unit frames' vertical centre: the Resource Bars cast bar sits there,
-- centred between the player and target frames.
local UF_MID = UF_BOTTOM + TARGET_H / 2
-- Blizzard's encounter bar: over the target frame, this far above it.
local ENCOUNTER_GAP = 50
local MINIMAP_SIZE = 200
EllesmereUI.FOREVER_MINIMAP_SIZE = MINIMAP_SIZE -- the minimap's own first-activation default there
local MINIMAP_RIGHT = 23.33 -- minimap's right edge in from the screen's right
local MINIMAP_TOP = 62.5 -- its top edge down from the screen's top
-- The Edit Mode layout carries a version in its name from v2 on ("EllesmereUI
-- Forever v2"); the first shipped without one. A newer version UPGRADES the
-- older layout of ours in place (UpgradeLayout): same slot, so characters on
-- it stay on it, and everything the player saved into it survives -- only the
-- pieces that version changed move, and only where the player left them
-- alone. Never rebuild an existing one. Bump on every change AND add that
-- version's step to UpgradeLayout.
local LAYOUT_NAME = "EllesmereUI Forever"
local LAYOUT_VERSION = 4
local function LayoutFullName()
if LAYOUT_VERSION > 1 then return LAYOUT_NAME .. " v" .. LAYOUT_VERSION end
return LAYOUT_NAME
end
-- One of ours, at any version.
local function IsOurLayout(name)
return name == LAYOUT_NAME or (type(name) == "string" and name:match("^EllesmereUI Forever v%d+$") ~= nil)
end
local function Pos(point, x, y)
return { point = point, relPoint = point, x = x, y = y }
end
-- The chat module's genesis capture asks for this instead of reading
-- Blizzard's placement.
function EllesmereUI.ForeverChatPosition()
return Pos("BOTTOMLEFT", CHAT_LEFT, CHAT_BOTTOM)
end
--------------------------------------------------------------------------------
-- 1. Profile seed
--------------------------------------------------------------------------------
local function Sub(t, key)
if type(t[key]) ~= "table" then t[key] = {} end
return t[key]
end
local function ProfileAddons()
if not EllesmereUIDB then EllesmereUIDB = {} end
local profiles = Sub(EllesmereUIDB, "profiles")
local prof = Sub(profiles, EllesmereUIDB.activeProfile or "Default")
return Sub(prof, "addons")
end
-- The screen-edge anchor record unlock mode writes: target = the edge strip,
-- side = where the element sits relative to it, offsets edge-to-edge along
-- the anchored axis and centre-relative across it; a second edge may hold
-- the cross axis (a corner).
local function EdgeAnchor(target, side, offsetX, offsetY, edgeKey, edgeSide, edgeOffset)
local rec = { target = target, side = side, offsetX = offsetX, offsetY = offsetY }
if edgeKey then
rec.edge = { key = edgeKey, side = edgeSide, offset = edgeOffset }
end
return rec
end
function EllesmereUI.SeedForeverBaseLayout()
local addons = ProfileAddons()
-- Chat: bottom-left, room for the micro menu under it. Ownership stamped,
-- so the chat module never captures Blizzard's placement.
local chat = Sub(Sub(addons, "EllesmereUIChat"), "chat")
chat.chatPosition = EllesmereUI.ForeverChatPosition()
chat._chatPosOwnership = 1
-- Tabs inside one continuous chat panel.
chat.extendBgBehindTabs = true
-- Minimap: top-right, 200 wide.
local mm = Sub(Sub(addons, "EllesmereUIMinimap"), "minimap")
mm.position = Pos("TOPRIGHT", -MINIMAP_RIGHT, -MINIMAP_TOP)
mm.mapSize = MINIMAP_SIZE
mm._capturedOnce = true
-- Two buttons stand on the button row out of the group, in this order:
-- ours, then the error-list addon's (its LibDBIcon name; an absent button
-- costs nothing). A player's regroup clears the entry for good.
local ug = Sub(mm, "ungroupedButtons")
ug.EllesmereUIMinimapButton = 1
ug.LibDBIcon10_BugSack = 2
-- Unit frames: player left, target right, above action bar 1; target of
-- target right-aligned just above the target; pet centred just below the
-- player.
local uf = Sub(Sub(addons, "EllesmereUIUnitFrames"), "positions")
uf.player = Pos("BOTTOM", -UF_SPREAD, UF_BOTTOM)
uf.target = Pos("BOTTOM", UF_SPREAD, UF_BOTTOM)
uf.targettarget = Pos("BOTTOM", UF_SPREAD + TOT_DX, UF_BOTTOM + TARGET_H + TOT_GAP)
uf.pet = { point = "TOP", relPoint = "BOTTOM", x = -UF_SPREAD, y = UF_BOTTOM - GAP }
-- Resource Bars cast bar (the cast bar shown by default): centred between
-- the player and target frames.
local erb = Sub(addons, "EllesmereUIResourceBars")
Sub(erb, "castBar").unlockPos = { point = "CENTER", relPoint = "BOTTOM", x = 0, y = UF_MID }
-- Damage meter: the one window, bottom-right above the bag bar, open on
-- Damage Done (a window with no mode shows its mode picker instead).
local dm = Sub(Sub(addons, "EllesmereUIDamageMeters"), "dm")
local win = Sub(Sub(dm, "windows"), 1)
win.position = Pos("BOTTOMRIGHT", -EDGE, METER_BOTTOM)
win.width = win.width or 375
win.height = win.height or METER_H
if win.curDMType == nil and Enum and Enum.DamageMeterType then
win.curDMType = Enum.DamageMeterType.DamageDone
end
-- Action bars: bar 1 bottom centre with the pet bar centred above it, the
-- stance bar just above the player frame, every other bar hidden,
-- Blizzard's own XP and reputation bars in place of the module's.
local ab = Sub(addons, "EllesmereUIActionBars")
ab.useBlizzardDataBars = true
local barPos = Sub(ab, "barPositions")
barPos.MainBar = Pos("BOTTOM", 0, MAINBAR_Y)
barPos.PetBar = Pos("BOTTOM", 0, PET_BOTTOM)
barPos.StanceBar = { point = "BOTTOMRIGHT", relPoint = "BOTTOM", x = STANCE_RIGHT, y = STANCE_BOTTOM }
local bars = Sub(ab, "bars")
for _, key in ipairs({ "Bar2", "Bar3", "Bar4", "Bar5", "Bar6", "Bar7", "Bar8", "Bar9", "Bar10" }) do
local b = Sub(bars, key)
b.barVisibility = "never"
b.alwaysHidden = true
end
-- No module reads Blizzard's layout: the capture flags start stamped.
EllesmereUIDB._capturedOnce_EAB = true
EllesmereUIDB._capturedOnce_CDM = true
-- Screen-edge anchors, keyed by unlock element.
local anchors = Sub(EllesmereUIDB, "unlockAnchors")
anchors.ECHAT_MainChat = EdgeAnchor("SCREEN_LEFT", "RIGHT", CHAT_LEFT, 0, "SCREEN_BOTTOM", "TOP", CHAT_BOTTOM)
anchors.EBS_Minimap = EdgeAnchor("SCREEN_RIGHT", "LEFT", -MINIMAP_RIGHT, 0, "SCREEN_TOP", "BOTTOM", -MINIMAP_TOP)
anchors.EDM_Win1 = EdgeAnchor("SCREEN_RIGHT", "LEFT", -EDGE, 0, "SCREEN_BOTTOM", "TOP", METER_BOTTOM)
anchors.MainBar = EdgeAnchor("SCREEN_BOTTOM", "TOP", 0, MAINBAR_Y)
anchors.player = EdgeAnchor("SCREEN_BOTTOM", "TOP", -UF_SPREAD, UF_BOTTOM)
anchors.target = EdgeAnchor("SCREEN_BOTTOM", "TOP", UF_SPREAD, UF_BOTTOM)
-- An element link has the same record shape: the target of target rides
-- the target frame's top edge, its centre offset keeping the right edges flush.
anchors.targettarget = EdgeAnchor("target", "TOP", TOT_DX, TOT_GAP)
-- The pet rides the player frame's bottom edge the same way, centred, so
-- it clears whatever that look hangs below the frame (a level badge).
anchors.pet = EdgeAnchor("player", "BOTTOM", 0, -GAP)
EllesmereUI._foreverLayoutFresh = true
end
-- The seed fits the EllesmereUI frames. The WoW Forever look's player frame
-- is the 232 x 100 stock box with its art inset (the top and right insets of
-- the Unit Frames kit), so when a whole-UI look switch at first install (the
-- module picker's reload, the style picker) applies it, the stance bar moves
-- up to clear that art, right edges flush; a switch to any other look puts
-- it back on the seed's spot, where Blizzard Style and Classic WoW UI always
-- have it. Only ever from a spot this layout placed it on: a bar the player
-- moved stays put.
local STOCK_BOX_W, STOCK_BOX_H = 232, 100
local STOCK_PLAYER_ART = {
forever = { t = 16.5, r = 18 },
}
local function StanceSpot(styleKey)
local art = STOCK_PLAYER_ART[styleKey]
if not art then return STANCE_RIGHT, STANCE_BOTTOM end
return -UF_SPREAD + STOCK_BOX_W / 2 - art.r, UF_BOTTOM + STOCK_BOX_H - art.t + GAP
end
function EllesmereUI.ForeverLayoutForLook(styleKey)
local barPos = EllesmereUI._abBarPositions
or Sub(Sub(ProfileAddons(), "EllesmereUIActionBars"), "barPositions")
local sb = barPos.StanceBar
if not (type(sb) == "table" and sb.point == "BOTTOMRIGHT" and sb.relPoint == "BOTTOM") then return end
local placed = sb.x == STANCE_RIGHT and sb.y == STANCE_BOTTOM
if not placed then
for key in pairs(STOCK_PLAYER_ART) do
local x, y = StanceSpot(key)
if sb.x == x and sb.y == y then placed = true; break end
end
end
if placed then sb.x, sb.y = StanceSpot(styleKey) end
end
--------------------------------------------------------------------------------
-- 2. Edit Mode layout
--------------------------------------------------------------------------------
local function FindSystem(layout, system, systemIndex)
for _, entry in ipairs(layout.systems or {}) do
if entry.system == system and entry.systemIndex == systemIndex then return entry end
end
return nil
end
-- Anchors a system to a screen corner or edge of UIParent. The anchor table
-- is edited in place: action bars carry bottom-stack fields beside it.
local function AnchorSystem(entry, point, x, y)
local a = entry.anchorInfo
if type(a) ~= "table" then a = {}; entry.anchorInfo = a end
a.point, a.relativeTo, a.relativePoint, a.offsetX, a.offsetY = point, "UIParent", point, x, y
entry.isInDefaultPosition = false
end
local function SetSetting(entry, setting, value)
local rows = entry.settings
if type(rows) ~= "table" then rows = {}; entry.settings = rows end
for _, row in ipairs(rows) do
if row.setting == setting then row.value = value; return end
end
rows[#rows + 1] = { setting = setting, value = value }
end
local function GetSetting(entry, setting)
for _, row in ipairs(entry.settings or {}) do
if row.setting == setting then return row.value end
end
return nil
end
-- Version of one of our layouts from its name (the first shipped unversioned).
local function LayoutVersionOf(name)
if name == LAYOUT_NAME then return 1 end
return tonumber(type(name) == "string" and name:match(" v(%d+)$") or nil) or 1
end
-- Brings an older version of our layout up to date IN PLACE: only what each
-- newer version changed, and only where the player has not changed it, so
-- the positions a player saved into it survive the update. modern = a copy
-- of the Modern preset (the base every version was built from).
local function UpgradeLayout(layout, from, modern)
-- v3: the encounter bar left the Modern preset's bottom-centre spot,
-- which lands between the player and target frames here. Moved only while
-- it still sits where the preset put it.
if from < 3 then
local enc = Enum.EditModeSystem.EncounterBar and FindSystem(layout, Enum.EditModeSystem.EncounterBar, nil)
if enc and enc.isInDefaultPosition ~= false then
AnchorSystem(enc, "BOTTOM", UF_SPREAD, UF_BOTTOM + TARGET_H + ENCOUNTER_GAP)
end
end
-- v4: Blizzard's other action bars no longer start Hidden (the layout
-- outlives the addon, and Hidden beats Blizzard's own Action Bars
-- checkboxes). A bar still Hidden takes the Modern preset's visibility.
if from < 4 then
local AB = Enum.EditModeSystem.ActionBar
local idx = Enum.EditModeActionBarSystemIndices or {}
local visSetting = Enum.EditModeActionBarSetting and Enum.EditModeActionBarSetting.VisibleSetting
local vis = Enum.ActionBarVisibleSetting
if AB ~= nil and visSetting ~= nil and vis and vis.Hidden ~= nil then
for _, name in ipairs({ "Bar2", "Bar3", "RightBar1", "RightBar2", "ExtraBar1", "ExtraBar2", "ExtraBar3" }) do
local entry = idx[name] ~= nil and FindSystem(layout, AB, idx[name])
if entry and GetSetting(entry, visSetting) == vis.Hidden then
local pe = FindSystem(modern, AB, idx[name])
local v = pe and GetSetting(pe, visSetting)
if v == nil then v = vis.Always end
if v ~= nil then SetSetting(entry, visSetting, v) end
end
end
end
end
end
-- Returns true once the layout exists and this character is on it for its
-- first time (written now, or found and switched to), false to retry.
local function WriteEditModeLayout()
if InCombatLockdown() then return false end
local mgr = _G.EditModeManagerFrame
-- Layouts arrive with EDIT_MODE_LAYOUTS_UPDATED; the manager's account
-- settings are the sign they have.
if not (mgr and mgr.accountSettings) then return false end
if mgr.editModeActive or (mgr.IsShown and mgr:IsShown()) then return false end
if not (C_EditMode and C_EditMode.GetLayouts and C_EditMode.SaveLayouts and C_EditMode.SetActiveLayout) then return false end
local PLM = _G.EditModePresetLayoutManager
if not (PLM and PLM.GetCopyOfPresetLayouts and Enum and Enum.EditModeSystem and Enum.EditModeLayoutType) then return false end
local info = C_EditMode.GetLayouts()
if not (info and type(info.layouts) == "table") then return false end
-- The presets come first in the game's own list: the active-layout index
-- counts them, and a copy of the Modern one is the base of ours.
local presets = PLM:GetCopyOfPresetLayouts()
if type(presets) ~= "table" or #presets == 0 then return false end
local presetCount = #presets
local fullName = LayoutFullName()
for i, l in ipairs(info.layouts) do
-- A layout of ours from a newer build (a tester build, then back to
-- this one) counts as current too: never renamed down, never re-stepped.
if l.layoutName == fullName
or (IsOurLayout(l.layoutName) and LayoutVersionOf(l.layoutName) > LAYOUT_VERSION) then
-- Already written, by an earlier session or another character:
-- the layout is account-wide, the active choice per character.
-- A character still on a Blizzard preset has never had its
-- first look at ours, so it is switched once; one on any saved
-- layout, ours or its own, keeps that choice.
local ours = presetCount + i
if info.activeLayout ~= ours and (info.activeLayout or 0) <= presetCount then
C_EditMode.SetActiveLayout(ours)
end
return true
end
end
-- The Modern preset: the base every version of ours was built from.
local base = presets[1]
local modernIndex = Enum.EditModePresetLayouts and Enum.EditModePresetLayouts.Modern
for _, p in ipairs(presets) do
if modernIndex ~= nil and p.layoutIndex == modernIndex then base = p end
end
-- This character's layout before the list changes: it moves to ours only
-- from a Blizzard preset; any saved layout, its own or ours, stays its choice.
local wasActive = info.activeLayout or 0
local activeSaved = wasActive > presetCount and info.layouts[wasActive - presetCount] or nil
-- A character on our older layout needs no move: the upgrade keeps its slot.
local takeOver = activeSaved == nil
-- SaveLayouts takes the whole set the way the game keeps it: the presets
-- first (read-only, carried for index alignment), then the saved layouts,
-- with activeLayout indexing that merged list. Every character stores its
-- active layout as an index, so the saved layouts never change order.
-- An older version of ours is UPGRADED IN PLACE (UpgradeLayout): it keeps
-- its slot and everything the player saved into it, and only the pieces a
-- newer version changed move, where the player left them alone. Only an
-- account with none of ours gets a fresh one, ahead of the first
-- character layout, with the account layouts.
local merged = presets
local slot
for _, l in ipairs(info.layouts) do
merged[#merged + 1] = l
if not slot and IsOurLayout(l.layoutName) then
UpgradeLayout(l, LayoutVersionOf(l.layoutName), base)
l.layoutName = fullName
slot = #merged
end
end
if not slot then
-- A copy of the Modern preset, with the pieces moved.
local layout = CopyTable(base)
layout.layoutIndex = nil
layout.layoutType = Enum.EditModeLayoutType.Account
layout.layoutName = fullName
-- Blizzard's other action bars keep the Modern preset's visibility: the
-- Action Bars module hides Blizzard's bars itself while it runs (bars 2+
-- start hidden in its own settings), and this layout outlives the addon,
-- so hiding them here would keep them hidden after EllesmereUI is removed.
local AB = Enum.EditModeSystem.ActionBar
local idx = Enum.EditModeActionBarSystemIndices or {}
local main = FindSystem(layout, AB, idx.MainBar)
if main then AnchorSystem(main, "BOTTOM", 0, MAINBAR_Y) end
local micro = Enum.EditModeSystem.MicroMenu and FindSystem(layout, Enum.EditModeSystem.MicroMenu, nil)
if micro then AnchorSystem(micro, "BOTTOMLEFT", EDGE, GAP) end
local bags = Enum.EditModeSystem.Bags and FindSystem(layout, Enum.EditModeSystem.Bags, nil)
if bags then AnchorSystem(bags, "BOTTOMRIGHT", -EDGE, GAP) end
-- The encounter bar keeps the Modern preset's bottom-centre spot
-- otherwise, which lands between the player and target frames here.
local enc = Enum.EditModeSystem.EncounterBar and FindSystem(layout, Enum.EditModeSystem.EncounterBar, nil)
if enc then AnchorSystem(enc, "BOTTOM", UF_SPREAD, UF_BOTTOM + TARGET_H + ENCOUNTER_GAP) end
-- Blizzard's experience bar: bottom centre, three quarters wide. The
-- stored size is a slider STEP, not the percentage: Edit Mode shows
-- raw * step + min (50 to 130 in steps of 5, so the preset's 10 reads
-- 100). The live display info converts when the system frame is up;
-- the same formula stands in for it otherwise.
local stbIdx = Enum.EditModeStatusTrackingBarSystemIndices
local xp = Enum.EditModeSystem.StatusTrackingBar and stbIdx and stbIdx.StatusTrackingBar1 ~= nil
and FindSystem(layout, Enum.EditModeSystem.StatusTrackingBar, stbIdx.StatusTrackingBar1)
if xp then
AnchorSystem(xp, "BOTTOM", 0, 0)
local sizeSetting = Enum.EditModeStatusTrackingBarSetting and Enum.EditModeStatusTrackingBarSetting.Size
if sizeSetting ~= nil then
local raw = (XP_WIDTH_PCT - 50) / 5
local sys = _G.MainStatusTrackingBarContainer
local di = sys and sys.settingDisplayInfoMap and sys.settingDisplayInfoMap[sizeSetting]
if di and di.ConvertValue then
local ok, v = pcall(di.ConvertValue, di, XP_WIDTH_PCT, false)
if ok and type(v) == "number" then raw = v end
end
SetSetting(xp, sizeSetting, raw)
end
end
slot = #merged + 1
for i = presetCount + 1, #merged do
if merged[i].layoutType == Enum.EditModeLayoutType.Character then slot = i; break end
end
table.insert(merged, slot, layout)
end
if mgr.ReconcileWithModern then
for i = presetCount + 1, #merged do mgr:ReconcileWithModern(merged[i]) end
end
local active = slot
if not takeOver then
for i = presetCount + 1, #merged do
if merged[i] == activeSaved then active = i; break end
end
end
info.layouts = merged
info.activeLayout = active
C_EditMode.SaveLayouts(info)
C_EditMode.SetActiveLayout(active)
return true
end
-- The tooltip's fixed anchor box has no screen-edge anchor of its own (the
-- element takes no anchor target), so its centre is computed from the
-- screen size once that is final, in the world, over the skin's own login
-- seed (Blizzard's Edit Mode spot), and the box is re-parked at once.
local function PlaceTooltipAnchor()
local prof = EllesmereUI.GetActiveProfileData()
if not prof then return end
local uw, uh = UIParent:GetWidth(), UIParent:GetHeight()
if not uw or not uh or uw <= 0 or uh <= 0 then return end
prof.tooltipFixedPos = {
centerX = uw / 2 - EDGE - TT_W / 2,
centerY = METER_BOTTOM + METER_H + GAP + TT_H / 2 - uh / 2,
}
if EllesmereUI._applyTooltipFixedAnchor then EllesmereUI._applyTooltipFixedAnchor() end
end
-- Every login of a character that has not had its first look at this version
-- of the layout (stamped by character with the version below, in the
-- account's saved data, so a bump's in-place upgrade runs once more; a
-- fresh install is such a login too), and never when an external installer
-- owns the first run. Retried on the layouts event and a few times after
-- entering the world; drops out for good once done, and at once for a
-- stamped character.
local function SeenByCharacter()
if not EllesmereUIDB then return nil end
return Sub(EllesmereUIDB, "foreverEditModeSeen")
end
local writer = CreateFrame("Frame")
writer:RegisterEvent("PLAYER_ENTERING_WORLD")
writer:SetScript("OnEvent", function(self, event)
local guid = UnitGUID("player")
local seen = SeenByCharacter()
if EllesmereUI._externalInstaller or not guid or not seen or seen[guid] == LAYOUT_VERSION then
self:UnregisterAllEvents()
return
end
if event == "PLAYER_ENTERING_WORLD" then
self:UnregisterEvent("PLAYER_ENTERING_WORLD")
self:RegisterEvent("EDIT_MODE_LAYOUTS_UPDATED")
-- The tooltip box is profile data: first session of an install only.
if EllesmereUI._foreverLayoutFresh then
PlaceTooltipAnchor()
end
end
local tries = 0
local function Attempt()
if seen[guid] == LAYOUT_VERSION then return end
if WriteEditModeLayout() then
seen[guid] = LAYOUT_VERSION
EllesmereUI._foreverLayoutFresh = nil
self:UnregisterAllEvents()
return
end
tries = tries + 1
if tries < 10 then C_Timer.After(1, Attempt) end
end
Attempt()
end)