UI

Contributor26 min readUpdated

In short

Guide window, hub and options

On this page
  1. Options registry
  2. Guide window, guide menu and zone art
  3. Journey panel
  4. Gamepad (UI/Gamepad.lua, UI/GamepadGuide.lua)

Options registry#

Any module adds settings to the options window without touching UI files:

lua
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; Recorder 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 (Guide & 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 hub 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 pack 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 Window").

  • Guide window (UI/GuideFrame.lua): tab strip, a slim navigator (prev, step number, next, Skip, the bug that opens the report window, menu), one focus block from the step view's groups/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's image under 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 menu grew into, docs/SUITE.md "UI"). A section rail (W.Shell with rail = 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 in nav, not in a tab row; W.ShellTabs on the hub is for views inside one division, and the section's frame starts under it. A section registered with foot = true sits 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 import and SHOW_IMPORT open 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's folder when set, else leveling by faction and band, other guides by category); search also matches folder and keywords. 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's reward tiles and why, "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.LookupTotal from UI/Lookup.lua): an "In the Codex" 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 }) and SHOW_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's search, enter, step hooks); 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 (its nav): 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.offset for "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 for status, 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 on CODEX_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 of Lookup.Sources (asked once per selection; label, detail, travel, chance / price / standing; ones this character cannot use dimmed with whyText), a "Guide" button on the selected source, and pinned "Guide me" ("Open guide" when Lookup.CuratedGuide has one) calling Lookup.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) from Polestar.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" while Dungeons.HandinPlan holds a finished quest (DS.HandIn -> Dungeons.HandIn; the tooltip counts them and names what is not finished), then "Pick up the quests" while Dungeons.PickupPlan holds 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.PaintQuests on scroll): the title in the client's difficulty colour (GetQuestDifficultyColor, else QuestDifficultyColors by level gap) lifted to 4.5:1, the state from Compat.IsQuestCompleted, IsOnQuest, IsQuestReadyForTurnIn and 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 to Dungeons.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.Quest per id, a pin each), Choose one / You get item tiles (quality border; hover GameTooltip: SetHyperlink, where Gear adds its upgrade line; modified clicks through HandleModifiedItemClick), 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 opens Dungeons.Attune in 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.DungeonsSection exposes Open, 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]) and UI.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) registers ZoneArt.RegisterOverlays(build, { [uiMapArtID] = { tileWidth, tileHeight, { textureWidth, textureHeight, offsetX, offsetY, fileDataID, ... }, ... } }). It is drawn only when C_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 as MapExplorationDataProvider places 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 to ZA.REFRAME times 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.ShellNav heading 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 per Guides.Packs() record: title, credits, guide count, website as a W.Link (the address without its scheme; the click opens UI.ShowCopy("<title> website", <address>)), source, and a W.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 addon is, and "Read more:" with a W.Link to docs/GUIDE_PACKS.md opening UI.ShowCopy("Community guides, explained", <address>) (ns.DocsURL() with GUIDE_PACKS.md for PACKS.md: the TOC's X-Website + /blob/HEAD/docs/GUIDE_PACKS.md, else "docs/GUIDE_PACKS.md in Polestar's source"). While Guides.ProblemCount() > 0 the page's sidebar row carries the warning dot (a page spec's dot). 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 guide Guides.Suggest() picks on its zone painting, with its level range and pitch; the beta note while ns.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 saved PolestarCharDB as 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 welcome open it with every page.

  • Copy window (UI/Copy.lua, frame PolestarCopy, UI.ShowCopy(title, text) or SHOW_COPY): a small window with the band, a title, a read-only W.CopyBox holding the text, selected and focused, and how to copy it; ✕ and Escape close it. /polestar where uses it. opts (UI.ShowCopy(title, text, opts)): icon and iconColor put a mark before the title, hint replaces the line above the box; the Polestar Discord's invite (UI.ShowDiscord) comes with Discord's mark.

  • Reporting a problem (UI/Report.lua, frame PolestarReport, UI.ShowReport() or SHOW_REPORT): the guide window's bug (in the navigator while a guide is open), its menu's "Report a problem" and /polestar report open it on the step on screen. The player types what went wrong (250 letters); the report is UI.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), then UI.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, object ids, map <id> @x,y as a guide writes it (live unless in the Era frame) and spot i of n for 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, balanced or roads, 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
lua
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 rows in 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 follows spec.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.hints turns 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-triangle in Media/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.Open of 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 button QoL.Buttons.item shows the goal's item, or a use goal) is redirected to the secure binding CLICK PolestarQuestItemButton (G.Redirect, an override binding: not with the gamepad interface, where A clicks that line like any other); X runs the line's OnEnter (its tooltip) and again its OnLeave; LB / RB Engine.Prev/Next; Y sets ui.compact and ends the mode; B ends it; hiding the window or GAMEPAD_COMBAT ends it.
  • Bindings and chords.Bindings.xml: POLESTAR_TOGGLE, _NEXT, _PREV, _FOCUS, _MENU (TOGGLE_PICKER), _OPTIONS (TOGGLE_OPTIONS), _REPORT, and the quest buttons' CLICK bindings; Polestar.Bindings runs them. Nothing is bound unasked. A chord ("PADLSHOULDER+PADDUP") is Polestar's own, in PolestarDB.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 per GAMEPAD_BINDINGS_CHANGED) every 0.05 s, and every frame while one is held; then, out of combat, PolestarPadChords reads the gamepad first and keeps the second button from the game; any other button goes on. A chord does nothing in a fight (chordsFree, and runChord itself: 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.WhoUses names 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 binding control (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 at PLAYER_REGEN_DISABLED).
Was this page helpful?

Start with, or act

  • PlayersInstall and first run
  • PlayersCommunity guides
  • PlayersFAQ
  • Guide authorsWriting a community guide
  • Guide authorsGuide format cheatsheet
  • ContributorsContributing
  • ActionCopy the AddOns folder (Windows)C:\Program Files (x86)\World of Warcraft\_classic_beta_\Interface\AddOns
  • ActionCopy the AddOns folder (macOS)/Applications/World of Warcraft/_classic_beta_/Interface/AddOns
  • ActionFind an optionEvery setting with its default; type its name here to jump to it.
  • ActionEvery slash command