UI
Contributor26 min readUpdated
Guide window, hub and options
On this page
Options registry#
Any module adds settings to the options window without touching UI files:
Polestar.RegisterOptions({
page = "Quests", icon = "icon-accept", order = 30, -- sidebar page (created on first use), its title as shown
key = "quests", -- optional: the page's stable key (default: made from page)
tab = "Map", section = "Markers", -- tab row entry and section header
controls = {
{ kind = "toggle", key = "qol.mapObjectives", label = "Objective markers", help = "One line." },
{ kind = "slider", key = "qol.markerScale", label = "Marker size", min = 0.5, max = 2, step = 0.05, format = "%.2f\195\151", -- example key
release = true }, -- optional: write when the drag ends
{ kind = "dropdown", key = "gear.spec", label = "Weights", choices = { { value = "auto", label = "Automatic" }, ... } }, -- example key (the real one is gear.spec.<CLASS>)
-- choices may be a function (choices that come and go); get()/set(v) may stand in for the path
{ kind = "color", key = "qol.markerColor", label = "Marker colour", -- example key
shown = function() return r, g, b end, source = function() return "Scheme" end, linked = { "other.key" } }, -- example key
-- shown: the colour in effect while the key holds none; source: where it comes from;
-- linked: paths a cancelled pick restores too. Right-click resets (sliders too).
{ kind = "button", label = "Reset lockouts", help = "One line.", onClick = function() end },
{ kind = "binding", action = "POLESTAR_NEXT", label = "Next step", help = "One line." }, -- a key binding row (UI/GamepadGuide.lua)
},
})Read the rest of this section463 words
Controls read and write Polestar.Get/Set; disabled = function() return bool end is optional;
registered labels and help are searchable. Pages sort by order; RecorderThe optional Polestar_Recorder addon that records play; record import turns its sessions into data/recordings. and
About always close the list. A page or tab with no available control is left
out of the sidebar and tab row. Every row resets to its default on right-click.
The built-in pages are grouped by what the player is doing: Guide (10:
Window, Arrow & routes, Automation), Appearance (20: Colour & type,
Windows, Arrow), Map (30), Questing (40: Quests, Rewards & gear,
Talents, Buttons), Discovery (50: Lookup, Dungeons, Rares), Quality of Life (60:
Travel, Social, Vendors, Trackers), Community guides (70, a custom page:
UI/Packs.lua), Gamepad (75, registered by UI/GamepadGuide.lua:
Navigation, Key bindings), Recorder, About. The options window
declares every tab and section of the module-filled pages, in order, and the
modules register their controls into them by name (page = L["Questing"],
tab = L["Rewards & gear"], section = L["Gear advice"]): Quests, QoL,
Gear and Talents fill Questing; Lookup, Dungeons and Rares fill Discovery; QoL and Maps
(flight timer, instances) fill Quality of Life. On the Map page (GuideOne route, written as a .guide file and compiled to Lua; its id reads like forever/alliance/human/01-elwynn-forest. &
quests: Guide, Quest markers; Rares & travel: Rares and treasures, Dungeons and
travel; Zones: Zones, Coordinates) Nav, Quests, Rares and Maps place every map
and minimap setting.
A page is opened by its key (/polestar options <key>, UI.ShowOptions(key),
Options.Select), not its title: the built-in pages give English keys
(guide, appearance, map, questing, discovery, qualityoflife,
guidepacks, recorder, about) so a translated title does not move them;
a registered page without key takes one made from its title. Old page keys
(guidewindow, navigation, automation, quests, gear, lookup,
dungeons, rares, …) reach the page and tab that now hold their settings
(ALIASES in UI/Options.lua); a page's own key wins over an alias.
The sidebar's page list is its own scroll area (f.NavScroll, W.ShellNav)
from 12px under the search to the footer bar's line, so the version and stage
chip below it are never crowded, whatever the page count, font or menu scale;
the selected row is scrolled into view. The hubPolestar's main window: a rail of sections (Guides, Lookup, Dungeons, Rares) with Options at its foot (UI/Picker.lua). shares it. Its items are
{ key, label, icon, count, dot }: dot puts a warning dot at the row's
right edge instead of the count (a page spec's dot = function() end, the
Community guides page while a packAn addon of guides for Polestar, built by the compiler from a folder with a pack.json (the folder is a pack too); Polestar's own is Polestar_Guides (pack id guides). has problems), and { heading = true, label }
is a muted heading over the rows after it, not a choice (the menu's "Community
guides").
The settings window is a rail shell like the hub (W.Shell with rail): it
shows the hub's sections (UI.Picker.RailItems(), and at its foot
UI.Picker.FootItems()) with Options marked, and a
section reopens the hub there. Switching between the two places the window
being opened at the other's top-left corner (UI.Picker.TakePlace), so the
rail does not move.
Guide window, guide menu and zone art#
Reading flow: docs/DESIGN.md ("Intent" and "The Guide WindowThe small window that shows the current step and its goals while playing (UI/GuideFrame.lua).").
-
Guide window (
UI/GuideFrame.lua): tab strip, a slim navigator (prev, stepOne stop in a guide: a title, optional conditions and tip, and at least one goal. number, next, Skip, the bug that opens the report window, menu), one focus block from the step view'sgroups/focus(the person to talk to now with only their goals, or a field step's objectives), counters right after the goal text (in text, success once complete; the accent is kept for the current goal's glyph, the arrow's target mark, tabs, primary buttons and the progress fill), a "Then: <next>" line with "Person n of N", tips folded to one line (an opened tip shows the step'simageunder it: the texture in its pack's addon folder, as wide as the text column, its aspect kept, at most 160 high; none for a pack without an addon folder, or a file the client cannot load), and the guide's progress bar with percent. Up next is off by default (ui.upcoming = 0). Transparent mode fades the whole navigator (step number with its chevrons) until hovered and keeps the progress track as a faint ink line. -
Hub (
UI/Picker.lua,UI.ShowPicker(opts); the window the guide menuThe hub's Guides section, where a player picks a guide. grew into,docs/SUITE.md"UI"). A section rail (W.Shellwithrail = true,W.ShellRail) at the window's left: the emblem, then one button per section from the 160 line (glyph over label, 56px rows; eight fit at 680 tall), Options at its foot. The sidebar holds the wordmark, the hub search and the active section's index (W.ShellNav). Guides is the built-in section; every other module registers:lua UI.RegisterSection({ key = "Dungeons", label = L["Dungeons"], icon = "icon-dungeon", order = 30, build = function(parent, hub) end, -- once; parent spans the list and details panes render = function() end, -- every hub render while shown (W.ShellTitle/W.ShellTabs on hub) nav = function() return items, activeKey end, -- its index in the sidebar ({ key, label, icon, count }) onNav = function(key) end, -- set the state, then UI.Picker.Render() search = function(text) end, enter = function() end, step = function(delta) end, -- optional searchPlaceholder = "...", -- optional; or a list of wordings, longest first: the first that fits the box shows })UI.Picker.ShowSection(key)shows one ("Guides" returns to the guide list),P.Section()names it,P.Query()is the search text. A section's top-level divisions go innav, not in a tab row;W.ShellTabson the hub is for views inside one division, and the section's frame starts under it. A section registered withfoot = truesits at the rail's foot, above Options, instead of among the sections (P.FootItems(onOptions): Options, then each foot section, from the bottom up). -
Import (
UI/Import.lua, a foot section, order 90;/polestar importandSHOW_IMPORTopen it). The two ways to bring someone else's guides into Polestar side by side (an import string for one small guide, a community addon for a route or a set), then a single-line box: what is pasted is read at once (Import.Preview) and shown before anything is saved ("Title · by Author · N steps · levels a–b · faction · license"), with Import (Update when an older version is imported; nothing for the same or a newer one, with a line saying so) and Cancel, or the reason it is refused. Enter imports. Below, "Your imported guides" (title, "By author · version · license", why a stored guide did not register, Delete with a second click to confirm), then "Want more than one guide?" (community addons in three steps and the GUIDE_PACKS.md link). -
Guide menu (the Guides section): three panes. Left: the categories with counts. Middle: Home (continue and suggested), Browse, Current (open tabs), Recent tabs over one line per guide,
Title (min–max), in folders (a guide'sfolderwhen set, else leveling by faction and band, other guides by category); search also matchesfolderandkeywords. The faction filter (ui.pickerFaction, default this character's) on the player's own faction lists only the guides this character can follow (Guides.GuideMatches: no other race's or class's guides); the other faction and All list every guide. Right: the selected guide's zone art, facts (led by a guide'srewardtiles andwhy, "Guide data"), progress, pitch, pros and cons, a favourite star, "Load guide" and "Open in new tab". Selecting never loads; the buttons, a double-click or Enter do. Its search also lists Codex matches under the guides (UI.LookupRows,UI.LookupCount,UI.LookupTotalfromUI/Lookup.lua): an "In the CodexLookup's database of items, mounts, pets, recipes, NPCs, rares, quests, zones, dungeons and treasures, with where each comes from (codex build, the Polestar_Codex addon)." folder with up to three per kind and a "Look up everything" line; picking one opens the Lookup section at it. -
Lookup (
UI/Lookup.lua, the hub section "Lookup", order 20;UI.ShowLookup({ query, kind, id })andSHOW_LOOKUP(query, kind, id)open the hub at it). Search only, never browsing: the hub's search box drives it (Search.Query, run when typing pauses for 0.15 s; the section'ssearch,enter,stephooks); an empty search lists the recent lookups (CharState().ui.recentLookups, newest first, at most 10, kept when the player opens a page) and a hint. Sidebar (itsnav): Everything and the kinds the search found, with counts; a kind filters the results only. Left: the results grouped by kind (five per kind under Everything with a "Show all" line; under one kind pages of 50,opts.offsetfor "Show more"), one line each (item icon or kind glyph in the gutter, the name in its quality colour lifted to 4.5:1 or its subject colour, a muted "No longer obtainable" / "No known source" chip forstatus, the subtitle right); only the rows in view are drawn, and only they get a subtitle (Search.Subtitle). Lookup has no tabs, so no tab row: it comes back only to say why the Codex is not there to search ("Loading the Codex…" re-queries onCODEX_LOADED); Enter is named in each row's tooltip. Right: the selection on a band (the zone art of its best located source), facts, the ranked sources ofLookup.Sources(asked once per selection; label, detail, travel, chance / price / standing; ones this character cannot use dimmed withwhyText), a "Guide" button on the selected source, and pinned "Guide me" ("Open guide" whenLookup.CuratedGuidehas one) callingLookup.Guide; nothing guides to an unobtainable entity. Guide results open in a new tab or among the guides; dungeons also open under Dungeons ("Open dungeon",Dungeons.Open(key)). Without the Codex pack (Search.Status) it says what is missing and still searches guides. Up/Down walk the results, Enter acts, Escape closes the hub. -
Dungeons (
UI/Dungeons.lua, the hub section "Dungeons", order 30;SHOW_DUNGEON(key)fromPolestar.Dungeons.Open(key)opens the hub at a page, nil at Home). A dungeon quest journal: dungeons grouped by level, a page per dungeon (docs/DESIGN.md, "Intent"). Sidebar (nav): For your level, All dungeons, 10–20 … 50–60, Raids, with counts; the faction filter (CharState().ui.dungeonsFaction, default this character's) as segments at the right of the tab row. Home: two cards per line in level order, the zone painting centred on the entrance (List()carries it; Home never decodes a page), name, levels, zone, quests for the filter, Forever and Your level chips; only the cards in view get their painting, the rest as they scroll in. The hub search filters the cards by name; Enter opens the first. A page: the painting in the header band with the title drawn on it (the hub's own Title, Subtitle and Band are faded/hidden while a page shows and restored on Home and when the section hides), a back chevron and the actions, right to left from the band's corner with the primary first, three at most (by priority; "Show on map" gives way first). The primary is "Hand in the quests" whileDungeons.HandinPlanholds a finished quest (DS.HandIn->Dungeons.HandIn; the tooltip counts them and names what is not finished), then "Pick up the quests" whileDungeons.PickupPlanholds quests to take (DS.PickUp->Dungeons.PickUp: the guide opens in a new tab and the hub steps aside; the tooltip counts the quests and those in the log), else "Guide me to the entrance" (Dungeons.GuideTo). After the primary: the other of the two guides ("Hand in the quests" is shown while any of the page's quests is in the log; with none finished its tooltip and, clicked, chat say what is open,Dungeons.HandinText), "Guide me to the entrance" and "Show on map" (Dungeons.ShowOnMap); with nothing to pick up or hand in, "Nothing to pick up" (shown while the page has quests for this side) says why in its tooltip and, clicked, in chat (DS.NothingText); no guide is built. Tabs Quests n, Attunement (Keys when each is a key; only on pages with one for the filter's side), Bosses n, Map. Quests: a block per quest planned from the data (the objective measured on one shared string) so only the blocks in view are drawn (DS.PaintQuestson scroll): the title in the client's difficulty colour (GetQuestDifficultyColor, elseQuestDifficultyColorsby level gap) lifted to 4.5:1, the state fromCompat.IsQuestCompleted,IsOnQuest,IsQuestReadyForTurnInand the level, level, requirement, classes, picked up inside, shareable; Objective, Starts/Ends with pins (Dungeons.GuideToPoint); the Starts line (pin and text) is one link toDungeons.GiverTarget: the giver of the first prerequisite still to do (its turn-in when that one is in the log), else the quest's giver, its tooltip saying which; the Chain (Dungeons.Questper id, a pin each), Choose one / You get item tiles (quality border; hoverGameTooltip: SetHyperlink, where Gear adds its upgrade line; modified clicks throughHandleModifiedItemClick), XP, money and reputation chips. Bosses: kill order, name, level, each tied quest ("Kill it for", "Drops X for", "Drops X, which starts", "Part of"), a line opening the quest. Attunement (Dungeons.Attunements,Dungeons.AttunementState): per entry a head (state glyph, the name in quest or item colour, a chip: Done, "Not in your bags" for a key whose chain is done, "n of m done"; the facts: kind, who needs it, level, side; our note; the item tile with "You have it" or "Not in your bags or bank"; "Guide me through it" while something is left, which opensDungeons.Attunein a new tab as the pick-up guide does) and a row per step of the filter's side (state glyph, the quest in its difficulty colour or what drops it, its state, giver and zone or chance and instance; a click sets the arrow there); the other side's entries carry no state. Map: the entrance and the quest givers on its zone painting. Blocks, rows, tiles, chips and cards are pooled; quest states refresh on QUEST_ACCEPTED, QUEST_REMOVED, QUEST_TURNED_IN and PLAYER_LEVEL_UP, the Attunement tab also on BAG_UPDATE_DELAYED and UPDATE_FACTION.UI.DungeonsSectionexposesOpen,Home,SetBand,SetTab,SetFaction,ShowQuest,Attune,Shown(shown.attune: per entry its id, done, chip, guide, owned and step states) for tests and the preview. -
Zone art (
UI/ZoneArt.lua):UI.ZoneArt.Create(parent, width, height [, fadeColor])andUI.ZoneArt.Paint(art, uiMapID, focusX, focusY, index) -> painted, reveal. The zone's world map painting from the player's own client, drawn the way the world map draws it: the base layer (C_Map.GetMapArtLayers/GetMapArtLayerTextures, layer 1, tiles row-major), then every overlay over it, so the zone shows whole, without fog of war. The client only lists the overlays this character has explored (C_MapExplorationInfo.GetExploredMapTextures), so the full list is generated:UI/ZoneArtOverlays.lua(maps overlays, below) registersZoneArt.RegisterOverlays(build, { [uiMapArtID] = { tileWidth, tileHeight, { textureWidth, textureHeight, offsetX, offsetY, fileDataID, ... }, ... } }). It is drawn only whenC_Map.GetMapArtID(uiMapID)is the art it was built for (and the tile size matches); otherwise the explored overlays are (reveal"explored", "full" with the table). Overlays split into tiles, the last column and row cropped from a power-of-two file, exactly asMapExplorationDataProviderplaces them, at the same desaturation, above the base. The place (focusX/Y, live-frame percent) lands mid-band horizontally and vertically mid-way between the top shade and the bottom fade (art.psFocusY), where nothing covers it; near an edge the painting zooms in up toZA.REFRAMEtimes so the place can stay there, and it never shows past the painting's edges. Only the base and overlay tiles the band shows get a texture, from per-band pools. Used by the route choice cards and the menu; the night band stands in without the art. -
Community guides in the menu.Our categories (Leveling, Professions, …) list only our guides; All, Favourites, Current, Recent and search cover every shown guide. A "Community guides" heading in the sidebar (a
W.ShellNavheading row), present only while a shown pack has guides, lists one entry per such pack (pack:<id>, "<title> · <author>"), its guides grouped as ours are. Pack guides carry their pack's title in rows and results; the details pane has a credits line for every guide ("By <author> · <pack title> <version> · <license>", plus the pack's source; ours "By Polestar"). The empty states name both Polestar Guides and community addons. -
Community guides page (
UI/Packs.lua, Options order 70,/polestar packs): one row perGuides.Packs()record: title, credits, guide count, website as aW.Link(the address without its scheme; the click opensUI.ShowCopy("<title> website", <address>)), source, and aW.Switch(Guides.SetPackShown) or the status text ("turned off in the AddOns list" with Enable and reload, "not loaded[: <reason>]", "needs Polestar <v> or newer" (or "needs a newer Polestar"), "broken: registered no guides", "refused: <reason>"); with problems a warning chip, Show (the list) and Copy report (P.Report: pack, versions, client build, every problem). Then Suggest guides from (behavior.suggestFrom,Guides.SuggestSources()), what a community addonEvery other pack, a separate addon made and supported by its author, listed under Community guides (GUIDE_PACKS.md). is, and "Read more:" with aW.Linkto docs/GUIDE_PACKS.md openingUI.ShowCopy("Community guides, explained", <address>)(ns.DocsURL()withGUIDE_PACKS.mdforPACKS.md: the TOC'sX-Website+/blob/HEAD/docs/GUIDE_PACKS.md, else "docs/GUIDE_PACKS.md in Polestar's source"). WhileGuides.ProblemCount() > 0the page's sidebar row carries the warning dot (a page spec'sdot). Nothing goes to chat. -
Welcome (
UI/Welcome.lua,SHOW_WELCOME): centred, four pages under a starlit hero, with step dots, Skip, Back and Next in the footer: Hello, <name> (what Polestar does, then the guide window, the arrow and/polestar guides); Set up (quest automation, the guide at login, the colour scheme as cards that apply live); Route style (nav.routeStyle, the setting Options' Route style dropdown writes, as three cards, Fastest, Balanced with its Recommended tag and Roads, each saying what it does: a click sets it live and Nav/Walk.lua searches again, right-click or the gamepad's Y puts Balanced back, and the pick follows the setting however it changes; a line on what every style keeps you away from); Start (the guideGuides.Suggest()picks on its zone painting, with its level range and pitch; the beta note whilens.routesBeta; the Discord invite in a copy box; what community guides are; then Start guide, or Choose a guide without one, and Browse guides). Once per new character (PolestarCharDB.welcomeSeen: 0 for a character with no savedPolestarCharDBas Polestar loaded,ns.IsNewCharacter(); set seen silently for one with saved data and no flag), 2 s after login, never in combat, during a cinematic or over the route choice; a due welcome that never opened stays due for the next login, and one that opened is marked on the character as it opens (a /reload or logout with it up does not repeat it); one the intro cinematic closes comes back on its page when the cinematic ends. Set up and Route style are in it only while the account has not had its welcome (PolestarDB.welcomeSeen); the page set is fixed when a showing starts. Any close marks the account (and the character). About's "Show the welcome again" and/polestar welcomeopen it with every page. -
Copy window (
UI/Copy.lua, framePolestarCopy,UI.ShowCopy(title, text)orSHOW_COPY): a small window with the band, a title, a read-onlyW.CopyBoxholding the text, selected and focused, and how to copy it; ✕ and Escape close it./polestar whereuses it.opts(UI.ShowCopy(title, text, opts)):iconandiconColorput a mark before the title,hintreplaces the line above the box; the Polestar Discord's invite (UI.ShowDiscord) comes with Discord's mark. -
Reporting a problem (
UI/Report.lua, framePolestarReport,UI.ShowReport()orSHOW_REPORT): the guide window's bug (in the navigator while a guide is open), its menu's "Report a problem" and/polestar reportopen it on the step on screen. The player types what went wrong (250 letters); the report isUI.Report.Text(description, details, spot): their words after "What went wrong:", then as bold lines under it the right spot when one is marked ("Right spot:") and the other addons that are loaded, names only, quest, map and guide addons first, then A to Z ("Addons:", left out when there are none), thenUI.Report.Details()in a code block, one fact a line, taken when the window opens: Polestar's version and stage, the client build and locale; race, class, level (XP %) and faction (never the name or realm); the guide (title, id, pack and version); "Step n of N: title", with "the guide is at step m" while another step is looked at, or "skipped"; each goal (the first six, then "... k more goals") as[>]the arrow's,[x]done,[-]skipped,[n/a]not for this character or[ ]open, its line and count,quest,npc,item,objectids,map <id> @x,yas a guide writes it (liveunless in the Era frame) andspot i of nfor the arrow's spawn spot; the arrow's target and distance (and "your own waypoint" with its position when the player set it); where the player stands (zone, subzone,map <id> @x,y live); "Routes:" with the route style (nav.routeStyle:fastest,balancedorroads, in English) and only the routing settings away from their defaults (real paths off,travel planner off,handed to Shortest Path Forever), after Where so the bug bot still finds Arrow and Where by position and reads this line by its style word; other packs that are on; and the errors (Polestar.Errors(), plus with !BugGrabber its errors this session in Polestar's files,BugGrabber:GetDB()) by their path from the AddOns folder, and the calls the game blocked (Polestar.Blocked()) as "blocked in <addon>: <function> ( -
Per-character UI state in
CharState().ui:recentGuides(ids, newest first, at most 10),favoriteGuides(id -> true),pickerSelected(id).
Journey panel#
UI/JourneyPanel.lua (UI.JourneyPanel; adapted from Shortest Path
Forever's journey tracker with Cillian Berragan's permission) shows the
travel route's steps in the guide window, right under the focus they lead
to and above "Then:": a header ("Journey", the remaining time and distance,
a chevron; a click collapses it, nav.journeyOpen), "To <destination>"
(the target's label, in the place colour), then one row per leg from the
current one on (at most eight, then "3 more steps"): the leg's number in
the route, its glyph (by leg.kind, or leg.step for a walk's chokepoint:
lift, gate, bridge, cave, pass), its caption (leg.label; without one the
kind's words and where it ends, never "Unknown") and its time. Boats,
zeppelins, the tram and lifts add a line, "average wait 0:15 · ride 0:10"
("leaves in" once a ride timed the loop; "on board · 0:42" while riding).
A walk whose engine path enters the other faction's towns (leg.hubs) adds
"Hostile town: Southshore" (names joined by commas) in the warning colour.
The current row wears the accent glyph and the 2px selection bar; a click
on it opens the skip menu (Nav.prompts.OpenMenu).
Data: Nav.GetRoute() (legs, current index), Nav.travel.Active() (more
than one leg: a lone walk is the arrow's own distance and time, so the panel
stays away), Nav.travel.Progress() (a current walk's seconds and yards
left along its path; any other current leg, a flight, hearth or teleport,
counts its planned time down from when it became current, stamped on every
route event even while the window is closed), leg.seconds, leg.yards and
ns.Transit.NextDeparture(kind, id, at, side) asked at the time the route
reaches the dock; standing at the dock (60 yd), Transit.NextArrival too,
so a visit leaving within the boarding window shows its own wait rather than
the next loop. A departure line appears only where a departure is known.
Times are whole seconds rounded up, unreadable numbers count 0, and the
header adds up the rows. Steps are repainted on ROUTE_CHANGED,
ROUTE_ADVANCED and settings; a 1 s C_Timer ticker, running only while
the panel is on screen, changes the times alone (SetFormattedText), so the
window is laid out on a tick only when a departure line comes or goes.
GuideFrame.Layout places it (JourneyPanel.Layout
returns its height); never in compact mode. Off with nav.journeySteps
(Options, Arrow & routes, Routes, "Show the journey"); without Nav neither
setting exists. Non-secure frames only. Tests: tests/ui-journey.test.js;
render: docs/preview/journey.svg.
Gamepad (UI/Gamepad.lua, UI/GamepadGuide.lua)#
Ported from the translated names database (@moust4ki), GPL-3.0, and rebuilt on the Theme.
No Polestar window takes a button from the game while it is merely shown:
a window holds the gamepad only while it is the newest one open, out of
combat, with a gamepad active (the game's gamepad interface, or
gamepad.withMouse on with the keyboard and mouse) and gamepad.navigate on (focus mode needs
only a gamepad), through a frame that reads the buttons first
(PolestarPadInput, OnGamePadButtonDown), so neither the game's gamepad
interface nor another addon's overrides (Easy Controller, ConsolePort) go
before it; any other button goes on to the game. With the keyboard and
mouse interface, temporary override bindings (SetOverrideBindingClick on
the hidden buttons PolestarPad<KEY>) sit under the frame for a client where
the frame gets nothing. With Forever's gamepad interface on
(Polestar.GamepadUI(), see Gamepad interface) none
is set: the game's own bindings on those buttons stay as its binding stack
left them, and the frame alone reads the window's buttons. Both are cleared
when the window closes. At PLAYER_REGEN_DISABLED (before the lockdown:
nothing is set in a fight) the window lets go first, then a binding row's
capture ends, each step in its own pcall, so an error in a row's callback
cannot leave the frame shown or a binding set for the fight; until
PLAYER_REGEN_ENABLED no window takes the gamepad, then the newest open one
takes it again. A button already down when a window takes the gamepad stays
the game's until released (G.heldKeys); a press that opened the window
stays Polestar's.
A window that shows by itself waits to be asked: the welcome at login (or
back after a fight or the route choice) and the route choice the engine
offers (spec.unasked()) take no button, unless a Polestar window held the
gamepad as they came. The focus binding (POLESTAR_FOCUS) gives the newest
waiting window the gamepad (G.TakeWaiting) before it starts focus mode,
and the waiting window's hint bar shows that binding's button when it has
one. Opened by the player (a binding, a chord, a click, A in another
window), every window takes the gamepad at once.
Read the rest of this section1074 words
Polestar.Gamepad.Register(frame, spec) -- navigation while `frame` is shown (the contract every window uses)
-- spec = { unasked = fn -> bool (this showing came by itself: wait for the focus binding),
-- rows = fn -> { widget, ... } (default G.Collect(frame): every shown, enabled, clickable
-- widget, the outermost one), initial = fn(rows) -> index, back = fn (B, Start; default Hide),
-- prevTab / nextTab = fn (LB / RB), reset = fn(w) -> handled (Y; default: a setting row's
-- right-click), x = fn(w) (X), activate = fn(w) -> handled (A; default w:Click()),
-- key = fn(key, w, isRepeat) -> handled, onFocus = fn(w),
-- close = button | false (where B's chip sits; default frame.Close),
-- actionLabel / xLabel / resetLabel = fn(w) -> text, backLabel, tabsLabel = text }
Polestar.Gamepad.IsActive() -> bool -- Polestar.GamepadUI(); with the keyboard and mouse only with gamepad.withMouse on (off by default) and C_GamePad / GamePadEnable with a device plugged in
Polestar.Gamepad.Style() -> "xbox" | "playstation" -- gamepad.glyphs, "auto" reads C_GamePad's labelStyle ("Shapes")
Polestar.Gamepad.ButtonName(button), KeyName(key) -> text -- "A" / "Cross", "LT + A", "LB + D-pad up", "Ctrl + N"
Polestar.Gamepad.Glyph(parent[, name]) -> frame -- frame:SetKeys(key or { button, ... }) -> width
Polestar.Gamepad.BindingKeys(action), ShownKeys(action[, pad]), KeyText(action), Bind(action, key), Unbind(action[, pad])
Polestar.Gamepad.WhoUses(action, key) -> { kind = "binding"|"addon"|"gamepad", name, action } | nil
Polestar.Gamepad.Capture(opts) -- a key, a button or a chord for a binding row
Polestar.Gamepad.StartFocus(), StopFocus(), ToggleFocus(), IsFocusing() -- the guide window's focus mode
Polestar.Gamepad.TakeWaiting() -> bool -- the newest window waiting to be asked takes the gamepad
Polestar.Gamepad.HasWindow() -> bool -- a Polestar window holds the gamepad (for other gamepad addons)- Navigation.Where widgets have places (a client; the test and preview
stubs place none) the D-pad goes to the nearest widget that way (its
distance plus twice how far it sits off the line), scrolling a list on at
its edge; without, it walks
rowsin order. Left / right step a slider (row.Slider's wheel handler) or a dropdown's choices instead. The D-pad repeats while held (0.4 s, then every 0.12 s). Until the player presses a button the focus followsspec.initial(a window fills its page after it shows); LB / RB start the new page on it again. Registered: the options (LB / RB:Options.StepTab, every page's tabs in order), the hub (LB / RB:Picker.StepSection, the rail's sections; X loads the focused guide), the shared menu (PolestarMenu, B closes it and the window under it has the gamepad back where it was), the welcome (LB / RB its pages), the route choice, the report and copy windows. - The look (docs/DESIGN.md, "Gamepad"): one focus ring (
PolestarPadRing, the focused widget's child: the hover wash under a 2px accent ring at 90%), one hint bar under the window holding the gamepad (PolestarPadHints, the panel skin, clamped to the screen, wrapping to the window's width, 300 at least) and one B chip left of its close button (PolestarPadBadge; the bar then leaves B out: each action shown once). The bar lists the D-pad, what A, X and Y do on the focused widget, LB / RB and B.gamepad.hintsturns the bar and the chip off. Without a gamepad none of it exists. - Chips (
G.Glyph): a 20px circle per face button (Xbox's letter in Caption, PlayStation's shape:pad-cross,pad-circle,pad-square,pad-triangleinMedia/UI) and D-pad direction (the chevrons), a capsule for the rest ("LB", "Ctrl"), "+" between the buttons of a combination; the chip wash, the name in text. - Focus mode (
POLESTAR_FOCUS,TOGGLE_FOCUS; a window waiting to be asked takes the gamepad first):G.Openof the guide window with its own spec: rows are its widgets (tabs, navigator, banner, person, goal lines, tip, up next), the first open goal first; A on a goal line aims the arrow (rowClick), or on the quest item's line (the item buttonQoL.Buttons.itemshows the goalOne thing to do in a step, as a verb and its arguments (accept 783, kill, goto, turnin...).'sitem, or ausegoal) is redirected to the secure bindingCLICK PolestarQuestItemButton(G.Redirect, an override binding: not with the gamepad interface, where A clicks that line like any other); X runs the line'sOnEnter(its tooltip) and again itsOnLeave; LB / RBEngine.Prev/Next; Y setsui.compactand ends the mode; B ends it; hiding the window orGAMEPAD_COMBATends it. - Bindings and chords.Bindings.xml:
POLESTAR_TOGGLE,_NEXT,_PREV,_FOCUS,_MENU(TOGGLE_PICKER),_OPTIONS(TOGGLE_OPTIONS),_REPORT, and the quest buttons'CLICKbindings;Polestar.Bindingsruns them. Nothing is bound unasked. A chord ("PADLSHOULDER+PADDUP") is Polestar's own, inPolestarDB.gamepad.chords[action](account-wide, outside the settings, so "Reset all settings" keeps it), never bound: while chords exist and a gamepad is active, the driver reads their first buttons (IsKeyDown, the chords parsed once perGAMEPAD_BINDINGS_CHANGED) every 0.05 s, and every frame while one is held; then, out of combat,PolestarPadChordsreads the gamepad first and keeps the second button from the game; any other button goes on. A chord does nothing in a fight (chordsFree, andrunChorditself: its window could not take the gamepad, and the button reached the game as well), while a Polestar window holds the gamepad, a binding row waits, the chat has the keyboard or a game window the gamepad's focus (GamepadMode.FrameControlsManager.focusedFrame, the field read, never a call), nor with another shoulder or trigger held (LB + RB + Up is not RB + Up). With the gamepad interface, LB and RB are its targeting modifiers (held, the D-pad and face buttons pick, mark and assist; both held, the D-pad pages the bars and opens the quest tracker or the chat):G.WhoUsesnames any chord on them as the game's. The quest buttons' secure clicks take no chord: a combination is the game's, with a button it makes a modifier (GamePadEmulateShift/Ctrl/Alt). Polestar never changes the game's gamepad settings. - Binding rowsthe registry's
bindingcontrol (G.BindingRow). A (a click) captures (G.Capture): a gamepad button on its release, two buttons as a chord, a keyboard key with its modifiers; B tapped cancels, B held a second binds B, Escape cancels, so do a fight and 20 s idle. A key someone else has (G.WhoUses: another binding or chord, another addon's override, Easy Controller's own chord read from its saved settings, the game's gamepad interface) is named in danger and bound by a second A within 5 s. Y (right-click) unbinds what the row shows: the gamepad's keys while a gamepad is active, else the keyboard's. - Messages:
GAMEPAD_BINDINGS_CHANGED,GAMEPAD_CHANGED(active)(GAME_PAD_CONNECTED/DISCONNECTED/ACTIVE_CHANGED),GAMEPAD_COMBAT(the gamepad went back atPLAYER_REGEN_DISABLED).