# 220 — Hero button manager

Requested 2026-10-06: the buttons of the hero are configurable per widget, on the same principle as the context
menu manager (170), in every cinematic Home section (`hero_contract.SECTIONS`: Pokračovat, Filmy, Seriály, MyTV,
Vinyl). The Home detail of a film / series keeps its fixed buttons (out of scope).

## What the user gets

- Every widget of a hero section has its own ordered configuration `main` / `more` / `hidden` of the actions of
  its context menu (`context.catalog(row)`). `main` are the hero buttons, `more` opens from the button
  **Další možnosti** (a native context popup that always ends with *Upravit tlačítka hero*), `hidden` is nowhere.
- At most four buttons, Další možnosti included (`hero_buttons.LIMIT`): what does not fit moves to the front of
  `more` and the editor says so.
- Defaults are the buttons the hero had: play / continue + Více informací (a series plays its next episode, a
  channel goes live and opens); Vinyl: Přehrát, Přehrát náhodně, favorite, Další možnosti.
- The first button is the primary one (bold label; with the play icon when it is a play action). Without the focus
  it looks like the others: a light primary read as the focused button (Standa 2026-10-15), only the focus is gold. An action the item
  cannot have is not drawn; the others close the gap and the navigation skips it.
- Editor: the context menu editor (`DialogMilionarContextManager.xml`, `context_dialogs.MenuManager` with the
  `Hero` model). Entries: the widget manager (button *Tlačítka hero*, saved with the section's draft), the item's
  context menu (More), the hero's Další možnosti, and C / long OK / menu on a hero button.

## How it works

| Part | Where |
|------|-------|
| Configuration, limit, availability, layout on the item, dispatcher | `wcs/skin/hero_buttons.py` |
| Ids of the buttons and sections | `wcs/skin/hero_contract.py` (`BASES`, `BUTTON_*`): `<base>`+901 primary with icon, +906 primary plain, +902..904, group list +905 |
| Storage | skin strings `mil.<sec>.<n>.hero` (encoded like `menu`) and `.herorev` (its checksum, part of the row URL `hb=`, so saving reloads the row); `slots.write_slot(hero=...)`, `KEEP` leaves it |
| Item properties | `milionar.hero.b<1-4>` builtin, `b<n>l` label, `b1i` = `play`; builders register ready builtins with `hero.direct` (`directs`, `do.<action>`, `dl.<action>`) |
| Skin | `gen_cinema.managed_buttons` (films' template and Vinyl's); `gen_slots.CONTENT`; first screen: fields `g1..g4`, `gl1..gl4`, `gi` (`startrows.FORMAT` 3) |

- **Fast path.** The buttons are written in `context.attach` (the output boundary of a row) from the item and the
  cache only: `hero_buttons.can` decides by the item's kind, never with a request or a store lookup. An action
  without a ready builtin is `RunPlugin(?action=skin_hero&do=<action>&data=<ctxdata>)`; pressed, it is resolved by
  the context menu's own code (`context.resolve`) and run, or answers "not available for this item".
- **Labels** are the context menu's static ones; only the builders' own are live (Pokračovat / Přehrát, Vinyl's
  add / remove favorite from the cached state).
- `MilCineIs_*` expressions are referenced by the button states (`$EXP` inside an expression) instead of repeated.
- The widget manager's right column: buttons 56 high, gap 3, so all eleven fit.

## Rows without titles, live redraw, Back (2026-10-06, after the user's test)

- **A genre row has no title of its own**: its hero borrows one from the first row that has. While the genre row is
  the current row, its own configuration applies. The lender's items carry that layout under
  `milionar.hero.s<slot>.*` (`hero_buttons.lent`, only for folder-only rows with a saved configuration); the skin
  reads it through `MilCineUse_<sec>_<k>_<n>` (slot k current, hero item from slot n) before the item's own
  buttons. Its dispatched buttons carry `owner=<widget id>`, so More options and the editor are the genre widget's.
- `play` and `next` are one button to the viewer (`PLAY_ALIAS`): a configured "play" on a series is its next episode.
- **Live redraw everywhere**: the revision is one per section, `mil.<sec>.herorev` (`slots.touch_hero`, a new token
  whenever a slot's buttons or type change), in every row URL of the section (`hb=`); the first screen rebuilds
  through `startrows` (its slot signature holds the buttons). The per-slot `herorev` is gone.
- **Back** on a hero button returns to the rows (the group list's `<onback>`): the row and item it came from,
  also after the click option "focus hero buttons".
- C / long OK on a hero button edits the current row's widget (also a genre row), else the first row with titles.

## Verification (2026-10-06)

Offline: `test_hero_buttons.py` (16), plus the touched suites (rows, slots, widget context, defaults, Vinyl cinema
and performance, row performance, performance profile, cinema, detail, startrows, warm): all green; the Home detail
XML is byte-identical. Live Kodi 21.3: default buttons in Pokračovat, Filmy and Vinyl; a custom configuration of
Rozkoukané filmy (Pokračovat, Přehrát od začátku, Označit zhlédnuté, Další možnosti) drawn and navigated; the More
popup; the editor from the popup, from the widget manager and from C on a hero button; Výchozí nastavení + Uložit
reloaded the row with the defaults. The test configuration was removed.

Not run live: pressing a dispatched action (it would change watched state / lists), Seriály and MyTV heroes with a
custom configuration, the limit notification in the editor.

Second pass, live: Seriály, genre row with the user's one-button configuration shows one button; saving the
defaults in its editor redrew the hero with two buttons within a second, focus kept on the row; the user's
configuration was put back. Back from 30901 returned to row 30100, item kept.

## Sizes (2026-10-15)

Every hero button is as wide as its text: 64 px high, 36 px of padding on both sides of its content (Kodi's auto width
= text + 2 x `textoffsetx`), between 120 and 520 px, 24 px apart. Kodi pads both sides alike, so the room of the play
triangle is a run of spaces at the start of the label (em + thin space = 30 px in the bold font) and the icon is drawn at
PAD - 7 (Estuary's triangle starts 7 px inside its 32 px icon): measured on native captures, ink to edge is 36.8 px left
and right. (The Pro tebe chips became the managed button "Zobrazit", action `fy_mode`: docs/plan/pro-tebe/030.) The
Home detail's buttons are a group list like a section's
(it needs an id: Kodi takes the noop edge of a group list with id 0 for wrapping round). Constants and the reasoning:
`gen_cinema.py` (PAD, MIN_W, MAX_W, PLAY_ROOM, PLAY_X). The vinyl track list dialog (`DialogVinylDetail.xml`) uses a
compact variant (20 px text, padding 28, gap 16) so its five buttons fit its 1000 px column.

## Where their context menus open (2026-10-15)

Kodi centres a context menu on the focused control. A card's menu opens over its row (the row is the focused
control); a hero button lies in a group list, which places its buttons only when it draws them, so Kodi took the list's
own position and the menus of hero buttons (Další možnosti, Zobrazit, the context key on a button) opened at the
list's left end. Every hero (each section and the Home detail) has an invisible anchor, id <base> + 907
(`gen_cinema.HERO_ANCHOR`): it spans the rows' width (left 46, right 0, like a row) at the buttons' height. While a hero
button's menu is open the anchor holds the focus (`wcs/skin/menu_anchor.anchored`, used by `hero_buttons.run`,
`foryou/mode.choose` and `context.open_menu`), so the menu opens where a card's menu opens, over the buttons; then the
button gets the focus back.
